Friday, August 30, 2013

Getting the database name from an Entity Framework Context

This seems like it should be so easy, but it took awhile to figure out.  Both methods below take an ObjectContext.  You can get that from a DbContext using ((IObjectContextAdapter)myDbContext).ObjectContext.


public static string ADOConnectionString( ObjectContext context )
{
    return ( (EntityConnection)context.Connection ).StoreConnection.ConnectionString;
}

/// This works regardless of how the connection string names the database ("initial catalog", "database", etc.).
public static string DatabaseName( ObjectContext context )
{
    return new SqlConnectionStringBuilder( ADOConnectionString( context ) ).InitialCatalog;
}

Thursday, August 15, 2013

Querying an Entity Framework Model for Column Type Information

In the process of writing an import system for importing data into a database modeled by EF, I needed to be able to grab table/column information from the model.  Following is a method I wrote for doing that (it's more difficult than it seems).

First, here is the class I'm populating in the main method.  Of course, you could add properties to this as necessary:

Update:  There is a bug in the code below.  It's impossible to edit the (pasted in) HTML now.  the select after "var foreignKeyNames" below should be:
select ( p.FromEndMember.RelationshipMultiplicity == RelationshipMultiplicity.One ) ? 
( (AssociationType)p.RelationshipType ).ReferentialConstraints[0].FromProperties[0].Name :
( (AssociationType)p.RelationshipType ).ReferentialConstraints[0].ToProperties[0].Name );

public class EntityTypeInfo
{
    public string EntityName { get; set; }
    public string PropertyName { get; set; }
    public Type EntityType { get; set; }

    ///
    /// Identity, Computed columns
    ///
    public bool StoreGenerated { get; set; }
    public bool IsForeignKey { get; set; }
}

Now the main method along with a helper method (see update above):
  1. /// <summary>
  2. /// For the given table/entity list, build a list of type info.
  3. /// </summary>
  4. public static List<EntityTypeInfo> SimplePropertiesFrom( ObjectContext context, List<string> tables )
  5. {
  6.     var result = new List<EntityTypeInfo>();
  7.  
  8.     /// We have to draw from two different spaces to get all the info we need.
  9.     var conceptualMetadata = context.MetadataWorkspace.GetItems( DataSpace.CSpace );
  10.     /// context.MetadataWorkspace.GetItems() for storage space only works if queries have already run which we can't ensure here.
  11.     var storageMetadata = ( (EntityConnection)context.Connection ).GetMetadataWorkspace().GetItems( DataSpace.SSpace );
  12.  
  13.     var query = from c in conceptualMetadata
  14.                 join s in storageMetadata on
  15.                     c.BuiltInTypeKind == BuiltInTypeKind.EntityType ? ( (EntityType)c ).Name : "X" // If the item isn't an EntityType, we don't want a join, so just make these 2 values different
  16.                     equals
  17.                     s.BuiltInTypeKind == BuiltInTypeKind.EntityType ? ( (EntityType)s ).Name : ""
  18.                 where c.BuiltInTypeKind == BuiltInTypeKind.EntityType
  19.                 select new { conceptual = c as EntityType, storage = s as EntityType };
  20.     query.ToList().ForEach( field =>
  21.     {
  22.         if ( tables.Contains( field.conceptual.Name ) )
  23.         {
  24.             var foreignKeyProps = ( from p in field.conceptual.NavigationProperties
  25.                                     where p.RelationshipType is AssociationType && ((AssociationType)p.RelationshipType).IsForeignKey
  26.                                     select ( ( AssociationType )p.RelationshipType ).ReferentialConstraints[0].FromProperties[0].Name );
  27.             foreach ( var p in field.conceptual.Properties )
  28.             {
  29.                 result.Add( new EntityTypeInfo
  30.                 {
  31.                     EntityName = field.conceptual.Name,
  32.                     PropertyName = p.Name,
  33.                     EntityType = ( (PrimitiveType)p.TypeUsage.EdmType ).ClrEquivalentType,
  34.                     StoreGenerated = IsStoreGenerated( p, field.storage ),
  35.                     IsForeignKey = foreignKeyProps.Contains( p.Name )
  36.                 } );
  37.             }
  38.         }
  39.     } );
  40.     return result;
  41. }
  42.  
  43. private static bool IsStoreGenerated( EdmProperty conceptualEntityProperty, EntityType storageEntityType )
  44. {
  45.     EdmMember storageProperty;
  46.     storageEntityType.Members.TryGetValue( conceptualEntityProperty.Name, true, out storageProperty );
  47.     if ( storageProperty == null )
  48.     {
  49.         return false;
  50.     }
  51.     else
  52.     {
  53.         Facet f;
  54.         if ( storageProperty.TypeUsage.Facets.TryGetValue( "StoreGeneratedPattern", false, out f) )
  55.         {
  56.             return ( ( (StoreGeneratedPattern)f.Value ) == StoreGeneratedPattern.Identity ) || ( ( (StoreGeneratedPattern)f.Value ) == StoreGeneratedPattern.Computed );
  57.         }
  58.         else
  59.         {
  60.             /// If it's not store generated, the above property won't be there (StoreGeneratedPattern.None is never referenced).
  61.             return false;
  62.         }
  63.     }
  64. }

Monday, July 08, 2013

Simplest Example of Posting File + Object to ASP.NET Web API

I searched through quite a few samples of posting multipart (I need to post a file and a custom object as with a form using Web API Client Libraries for 4.0) data to an ASP.NET Web API 4 service.  I couldn't get MultipartFormDataStreamProvider.FileData to populate on the server.  All the examples I looked at were really complicated (even current ones).

It turns out I was simply calling the wrong overload of MultipartFormDataContent.Add (see comment in client portion below).

Client:

var client = new HttpClient() ;
var formData = new MultipartFormDataContent();
/// FileContent code from stackoverflow.com/questions/17497584/cant-post-a-file-to-asp-net-web-api-from-httpclient
formData.Add( new FileContent( @"C:\temp\killme.xml" ), "name_not_used", "killme.xml" );  // third param causes provider.FileData on server to be populated.
formData.Add( new ObjectContent<MyCustomObject>( new MyCustomObject{ Path=@"C:\temp\killme.xml" }, new JsonMediaTypeFormatter() ), "options" );
client.PostAsync( "http://localhost:52800/api/Import", formData ).ContinueWith( ( task ) =>
{
    Console.WriteLine( task.Result.StatusCode.ToString() );
    client.Dispose();
} );

Server:

public HttpResponseMessage Post()
{
    if ( Request.Content.IsMimeMultipartContent() )
    {
        string root = HttpContext.Current.Server.MapPath( "~/App_Data" );  // This call can't be in async portion.
        var provider = new MultipartFormDataStreamProvider( root );

        try
        {
            Request.Content.ReadAsMultipartAsync( provider ).ContinueWith( task =>
            {
                var options = JsonConvert.DeserializeObject<MyCustomObject>( provider.FormData["options"] );
                if ( !task.IsFaulted )
                {
                    foreach ( MultipartFileData file in provider.FileData )                    {
                        Debug.Print( file.Headers.ContentDisposition.FileName );   // file name parameter to formData.Add back on client.
                        Debug.Print( "Server file path: " + file.LocalFileName );  // posted file automatically streamed to path specified by root var above.
                    }
                }
            } );

        }
        catch ( Exception e )
        {
            ;  // TBD
        }
        return new HttpResponseMessage( HttpStatusCode.Accepted );
    }
    else
    {
        return new HttpResponseMessage( HttpStatusCode.BadRequest );
    }
}

Friday, July 05, 2013

Web API Post with a String

I was trying for the longest time to get my first ASP.NET Web API project up and running with the simplest post.  I'm using Visual Studio 2012 with Web API 4 (along with the Web API Client Libraries nuget package).  My simplest get was working, but my post wasn't.

Here's my controller:

    public class ImportController : ApiController
    {

        public string Get()
        {
            return "hi mom";
        }

        public HttpResponseMessage Post( object s )
        {
            return new HttpResponseMessage( HttpStatusCode.Accepted );
        }

    }

Originally I had that parameter to the Post method as a string.  Then I tried to just post up using a simple HTML form with an input (text box) form element.  That didn't work.  Nor did this:

client.PostAsync( "http://localhost:52800/api/Import", new StringContent( "where my service at?" ) {  } ).ContinueWith( ( task ) =>
{
    Console.WriteLine( task.Result.StatusCode.ToString() );
    client.Dispose();
} );

Nor did anything else I tried.  I suppose the built-in deserializer just wouldn't match anything posted up to a string (nor scalar types I believe).  So after changing the parameter type to object and changing the client call from "PostAsync" to "PostAsJsonAsync" it worked.

Hope it can save someone some time.

Tuesday, June 04, 2013

Completely Uninformative Nuget Error

Using Visual Studio, you get this error upon opening a solution you haven't opened in awhile:

[Referenced Project].csproj : error  : The imported project "[Your Solution]\.nuget\nuget.targets" was not found. Confirm that the path in the declaration is correct, and that the file exists on disk.  [Referenced Project].csproj

The reason is that (I think) nuget has not been enabled for this solution which references a project that contains a nuget referenced assembly, which nuget reference has been set when the other solution is open.

Solution: Right click on the solution and select "Enable Nuget Package Restore."  Now you should be able to reload the offending project(s) without error.

I couldn't find a solution when I searched; hope this helps somebody.

Tuesday, November 06, 2012

Dynamic Entity Framework Filtering

I recently created a library that works against an Entity Framework (V5) model and allows for creation of a central method which accepts:
  • A generic type parameter indicating which entity type to return.
  • Any number of parameters (int or list of ints indicating ids) which will contain ids of related entities.
The library will return generated code for the linq to EF query which when compiled (by passing it to a compiler helper) will return IQueryable (for further filtering if necessary) of the type of entity indicated by the type parameter.  So the method the user of the library would use would look something like:

EntityService.All( ParentEntityId, OtherRelatedId, ListOfYetOtherRelatedIds )

This can greatly reduce the amount of simple inner join queries necessary in your repository.  You can find it at codeplex.  Hope someone else finds it useful.

Wednesday, October 24, 2012

MSBuild incorrectly skips projects set to build for given configuration

I ran into this problem and couldn't find any solutions for it anywhere. I was using MSBuild with VS2012. When I tried to build my solution, there seemed not to be any rhyme or reason as to what projects it chose to build. I would get this message in the log:

The project "[MyProject]" is not selected for building in solution configuration "Debug|Any CPU".

If I opened the solution, opened the Build Configuration Manager, ensured "Debug" was selected under "Active solution configuration", looked at my project, ensured "Any CPU" was selected under platform, I could see that the box in the "Build" column was checked. Most (but not all) that were unchecked were built.

The solution for me was to just create a new solution configuration (an option in the "Active solution configuration" drop list). I named it "ForMSBuild" so as not to confuse other devs, and selected to copy the settings from Debug. I selected "Any CPU" in the "Active solution platform" drop list, ensured each project had "Any CPU" selected and made my "Build" selections as necessary. After adjusting the params passed to MSBuild to refer to this new configuration, it built correctly.

Update:  I later found that builds were run directly in visual studio using the main Debug configuration.  I was getting build errors in deprecated test projects that were set to not build in the configuration.  I'm convinced there is a bug in 2012.

Tuesday, March 27, 2012

Using EPPlus Library to Convert XLSX to CSV

The EPPlus library for Excel data is great, but it has no built-in functionality for converting .xlsx to .csv.

Here is a sample (posted here because I discovered a pitfall, and it's not a slam dunk) for doing just that.  It's a single file, console app, just add EPPlus via nuget.

Update: Prior code was no good for large files (and said so).  Updated to work with giant files.


Wednesday, January 25, 2012

WPF Mapping / Association Control

When importing data from one store to another, many applications allow visual "mapping" of source fields to destination fields where lines are drawn to connect the two.
I tried in vain to find some kind of WPF/Xaml control that would do that, so I created one:

The control (regular user control) would be useful for any kind of association between two sets of data. The user creates associations by dragging from source to destination. It has a mode for one-to-one (a source item can only be mapped to one destination item) and one-to-many. Moving the scroll bars of the lists redraws the association lines appropriately. It's done MVVC style with models for the control as a whole and for the lists that hold the fields.

I was amazed at how easy this was to do with Xaml. I'm only a recent Xaml user, and when I use it I'm so pleased and dismayed at the same time. Pleased because it's such a rich, well thought out technology. Dismayed because MS has been forced to essentially abandon it in favor of the tyranny of HTML. I wonder how much more difficult this same thing would be to do using HTML5.

Source code here. Hope someone else can make use of it.

Monday, November 28, 2011

WPF Wizard Control

There doesn't seem to really be anything in the way of a wizard control for WPF. Developer Express has one for Winforms, but when it came to WPF, their recent advice is to look at this project until they create one.

I took that project (not a control; merely an example of a wizard implementation) and made it into a reusable user control. It's available on codeplex. Just as with my last post, I hope someone can make use of it.

Thursday, November 03, 2011

Programmatically Creating SSIS Packages

The last post was specific; this one very general.

I created a project on codeplex that is a functional import system using the SSIS runtime. If you are heading down the road of using the SSIS API, the project may be very helpful in helping to figure out just how.

I sure hope it saves somebody a bunch of time.

Thursday, October 13, 2011

Programmatically Creating an SSIS Package with a Flat File Connection Source

I'm using the SSIS API to programmatically create (and execute) a package. The requirement for the first package is to get .csv data from a flat file into a SQL Server table. So I start checking into the MS docs, looks easy enough. Not so fast. When it comes to a flat file connection, there seems to be holes in the API. I ended up having to use one class that (according to the docs) I shouldn't be referencing in my code, but there doesn't seem to be any other way to make the flat file connection work because it simply won't load column metadata from the file. Won't do it.

I found exactly one other sample of doing this (which was helpful), but that sample's method of reading the flat file's columns seemed less than optimal.

Following is a working sample. Sorry for formatting; I simply don't know any way to prevent the software from stripping out my formatting...

The data file looks like the following. Column names in first row.
"this","that"
"data","more data"
"other data","you get the point"

The SQL table is like yay:
CREATE TABLE [dbo].[bubba](
[this] [varchar](max) NULL,
[that] [varchar](max) NULL
) ON [PRIMARY]

The method below uses this helper class which makes use of the TextFieldParser class which seems very useful. I have no idea why it's in the Microsoft.VisualBasic.FileIO namespace, but whatever works:


class FlatFileColumnReader

{

    public List<string> Columns( string path, char delimiter, FieldType ft )

    {

        var tfp = new TextFieldParser( path )

        {

            TextFieldType = ft

        };

        tfp.Delimiters = new string[] {delimiter.ToString()};

        return tfp.ReadFields().ToList();

    }

}



Here is the (big fat, but working) method. Requires following usings. The referenced assemblies were found on my machine in C:\Program Files (x86)\Microsoft SQL Server\100\SDK\Assemblies. Note that they're in the x86 dir. Thus, my application is set to target x86.
using System.Data.Common;
using Microsoft.SqlServer.Dts.Runtime;
using Microsoft.SqlServer.Dts.Runtime.Wrapper;
using Microsoft.SqlServer.Dts.Pipeline.Wrapper;
using Microsoft.VisualBasic.FileIO;


public Microsoft.SqlServer.Dts.Runtime.Package Generate( Microsoft.SqlServer.Dts.Runtime.Application app )

{

    /// Objects

    Microsoft.SqlServer.Dts.Runtime.Package _package;

    Executable _dataFlowTask;

    IDTSComponentMetaData100 _dataSource;

    IDTSComponentMetaData100 _dataDest;

    CManagedComponentWrapper _sourceInstance;

    CManagedComponentWrapper _destinationInstance;

    ConnectionManager _conMgrSource;

    ConnectionManager _conMgrDest;



    /// Create package and data flow task

    _package = new Microsoft.SqlServer.Dts.Runtime.Package();

    _package.DelayValidation = true;

    _dataFlowTask = _package.Executables.Add( "STOCK:PipelineTask" );  // PipelineTask is a DataFlowTask ??

    var pipe = (MainPipe)( (Microsoft.SqlServer.Dts.Runtime.TaskHost)_dataFlowTask ).InnerObject;

    pipe.Events = DtsConvert.GetExtendedInterface( new ComponentEvents() as IDTSComponentEvents );  // my ComponentEvents() just writes some stuff to debug for now



    /// Create connections

    _conMgrSource = _package.Connections.Add( "FLATFILE" );

    _conMgrSource.Properties["Format"].SetValue( _conMgrSource, "Delimited" );

    _conMgrSource.Properties["Name"].SetValue( _conMgrSource, "Flat File Connection" );

    _conMgrSource.Properties["ConnectionString"].SetValue( _conMgrSource, @"C:\temp\Eeemport\bubba.txt" );

    _conMgrSource.Properties["ColumnNamesInFirstDataRow"].SetValue( _conMgrSource, true );

    _conMgrSource.Properties["HeaderRowDelimiter"].SetValue( _conMgrSource, "\r\n" );

    /// If you set the delimiter like this, it'll look correct if you open the resulting package in the UI, but it won't execute (unless you click "Reset Columns")

    //_conMgrSource.Properties["RowDelimiter"].SetValue( _conMgrSource, "{CR}{LF}" );

    _conMgrSource.Properties["TextQualifier"].SetValue( _conMgrSource, "\"" );

    _conMgrSource.Properties["DataRowsToSkip"].SetValue( _conMgrSource, 0 );



    _conMgrDest = _package.Connections.Add( "OLEDB" );

    // This provider wouldn't work

    //_conMgrDest.ConnectionString = @"Provider=Native OLE DB\SQL Server Native Client 10.0;Data Source=.\SQLEXPRESS;Initial Catalog=FASClient;Integrated Security=True";

    _conMgrDest.ConnectionString = @"Provider=SQLOLEDB.1;Integrated Security=SSPI;Persist Security Info=False;Initial Catalog=FASClient;Data Source=.\SQLEXPRESS";

    _conMgrDest.Name = "OLE DB Connection";

    _conMgrDest.Description = "OLE DB Connection";

    _conMgrDest.Properties["RetainSameConnection"].SetValue( _conMgrDest, true );



    /// Create the columns in the flat file connection

    var flatFileConnection = _conMgrSource.InnerObject as IDTSConnectionManagerFlatFile100;

    var fileColumns = new FlatFileColumnReader().Columns( @"C:\temp\Eeemport\bubba.txt", ',', FieldType.Delimited );

    for ( int i = 0; i < fileColumns.Count; i++ )

    {

        /// This object (IDTSConnectionManagerFlatFileColumn100) is not supposed to be referenced by my code according to doc:

        /// http://msdn.microsoft.com/en-us/library/microsoft.sqlserver.dts.runtime.wrapper.idtsconnectionmanagerflatfilecolumn100.aspx

        var column = flatFileConnection.Columns.Add();

        /// Last column delimiter must be newline.

        /// If you select "," for the column delimiter in the designer for a Flat File Connection, and the row delimiter is newline, it does this same thing...

        column.ColumnDelimiter = ( i == fileColumns.Count - 1 ) ? "\r\n" : ",";

        column.TextQualified = true;

        column.ColumnType = "Delimited";

        /// Here's one benefit of creating my own columns:

        /// My destination column in Sql Server is varchar.  The columns seem to be defaulted to DT_WSTR which won't go into a varchar column w/o being

        /// manually changed or run through a data converter component.

        column.DataType = DataType.DT_TEXT;

        column.DataPrecision = 0;

        column.DataScale = 0;

        ( (IDTSName100)column ).Name = fileColumns[i];

    }



    /// Create Data Flow Components

    _dataSource = pipe.ComponentMetaDataCollection.New();

    _dataSource.Name = "Flat File Source";

    _dataSource.ComponentClassID = app.PipelineComponentInfos["Flat File Source"].CreationName;

    _dataSource.ValidateExternalMetadata = false;



    _dataDest = pipe.ComponentMetaDataCollection.New();

    _dataDest.Name = "Sql Server Destination";

    _dataDest.ComponentClassID = app.PipelineComponentInfos["SQL Server Destination"].CreationName;



    ///// Create design instances

    _sourceInstance = _dataSource.Instantiate();

    _sourceInstance.ProvideComponentProperties();



    /// I think this junk must come after ProvideComponentProperties() above

    _dataSource.RuntimeConnectionCollection[0].ConnectionManager = DtsConvert.GetExtendedInterface( _conMgrSource );

    _dataSource.RuntimeConnectionCollection[0].ConnectionManagerID = _conMgrSource.ID;



    _sourceInstance.AcquireConnections( null );  // do we need to do this since we created our own columns?

    _sourceInstance.ReinitializeMetaData();

    _sourceInstance.ReleaseConnections();



    _destinationInstance = _dataDest.Instantiate();

    _destinationInstance.ProvideComponentProperties();



    /// I know SetComponentProperty can only be called after ProvideComponentProperties()

    /// To see available component properties, open an existing package (the XML) with an existing component of that type

    _destinationInstance.SetComponentProperty( "BulkInsertTableName", "[dbo].[bubba]" );

    _dataDest.RuntimeConnectionCollection[0].ConnectionManager = DtsConvert.GetExtendedInterface( _conMgrDest );

    _dataDest.RuntimeConnectionCollection[0].ConnectionManagerID = _conMgrDest.ID;

    _destinationInstance.AcquireConnections( null );

    _destinationInstance.ReinitializeMetaData();

    _destinationInstance.ReleaseConnections();



    //// Hook the path from source to dest

    var path = pipe.PathCollection.New();

    path.AttachPathAndPropagateNotifications( _dataSource.OutputCollection[0], _dataDest.InputCollection[0] );



    /// Do stuff with the virtual input (whatever the @#$% that is)

    var virtualInput = _dataDest.InputCollection[0].GetVirtualInput();

    foreach ( IDTSVirtualInputColumn100 column in virtualInput.VirtualInputColumnCollection )

    {

        _destinationInstance.SetUsageType( _dataDest.InputCollection[0].ID, virtualInput, column.LineageID, DTSUsageType.UT_READONLY );

    }



    /// MapColumns();

    foreach ( IDTSInputColumn100 inputColumn in _dataDest.InputCollection[0].InputColumnCollection )

    {

        var outputColumn = _dataDest.InputCollection[0].ExternalMetadataColumnCollection[inputColumn.Name];

        outputColumn.Name = inputColumn.Name;

        _destinationInstance.MapInputColumn( _dataDest.InputCollection[0].ID, inputColumn.ID, outputColumn.ID );

    }



    //_package.Validate( _package.Connections, null, null, null );



    return _package;

}

Friday, May 06, 2011

File or Folder Delete Utility

I originally put this utility together for one very specific purpose: to delete all files in a given folder whose name started with a number (cleaning up after other processes). It has since been used for other purposes, so I made it easier to extend by using the "pipeline" pattern just like is commonly done with data filtering. File or folder names from the requested directory are passed through filters (Filters.cs). Those meeting filter requirements are processed (either logged or deleted).

The currently implemented filters are:
  • Date (always taken into account)
  • Starts with numeric
  • Name is a guid
It will target either files or folders. Launch the utility with no parameters to see instructions.

To extend with another filter:
  • Add methods to Filters.cs following same pattern as existing (returns IEnumerable of FileInfo for files; IEnumerable of string for folders).
  • Add parameter metadata for the new filter parameter in AppParams.cs.PARAM_STRINGS
  • Add a "mode" property for the new filter in AppParams.cs (Like existing NumericMode).
  • Add a method to read the new param from the command line and set the new property you created above (like existing SetNumericMode).
  • Update the help text that prints when no params or bad params are passed to the utility in Program.cs

Test before using, of course, but I hope it's of use to somebody. If you just want the utility, get it here; if you want the code, it's here.

Wednesday, December 22, 2010

C# Decimal to English Money Converter



I needed a way to convert a money value (decimal) into the kind of English text that appears on a legal document (like a check or a contract), e.g., 1245.36 becomes "One Thousand Two Hundred Forty Five and 36/100 Dollars."
After searching for awhile, I couldn't find anything. Maybe somebody else will make use of this (took a bit longer than I estimated). Has a limitation on millions (I'm not working on a government contract), but this can easily be changed. Requires .Net 4 (uses Tuples).

Sorry for poor code formatting.

First the decimal extension class that contains a method used by the actual converter class and also has a method to call the converter itself:


public static class DecimalExtension
{

/// <summary>
/// Evaluates just the portion of the value to the right of the decimal place and returns it as a 2 character string.
/// Returns 00 if the value is a whole number.
/// </summary>
/// <returns>The value to the right of the decimal place. Returns 0 if the value is a whole number.</returns>
public static string GetDecimalNumbers( this decimal number )
{
int divint = Convert.ToInt32( Decimal.Floor( number ) );
decimal decValue = number - divint;

var result = decValue.ToString();

if ( result.Length > 1 )
{
result = result.Substring( 2 );
if ( result.Length > 2 )
{
result = result.Substring( 0, 2 );
}
else if ( result.Length < 2 )
{
result += "0";
}
}
else
{
result = "00";
}

return result;
}

public static string ToEnglishMoney( this decimal d )
{
return new DecimalToEnglishMoney( d ).ToString();
}

}

Now for the actual converter class:


public class DecimalToEnglishMoney
{

private enum DigitPlace
{
Ones = 0,
Tens = 1,
Hundreds = 2
}

private string _result;

public DecimalToEnglishMoney( decimal d )
{
_result = FormatWholePortion( d );
_result += FormatDecimalPortion( d );
}

public override string ToString()
{
return _result;
}

private string FormatDecimalPortion( decimal d )
{
var result = string.Empty;
var decimalPortion = d.GetDecimalNumbers();
result += string.Format( " and {0}/100 Dollars", decimalPortion );
return result;
}

private string FormatWholePortion( decimal d )
{
var result = string.Empty;
var wholePortion = (int)Math.Floor( (double)d );
var groups = FormatToNumberGroups( wholePortion );
groups.ForEach( g =>
{
result += (result == string.Empty) ? string.Empty : " ";
var formattedChunk = FormatChunkToEnglish( g.Item2 );
var amountDescriptor = (g.Item1 == string.Empty) ? string.Empty : " " + g.Item1; // "Thousand"
result += formattedChunk;
result += (amountDescriptor == string.Empty) ? string.Empty : amountDescriptor;
} );
return result;
}

/// <summary>
/// Returns the chunk name (blank, Thousand, Million) and the chunk number
/// </summary>
/// <param name="i"></param>
/// <returns>"Million" "NNN"</returns>
private List<Tuple<string, string>> FormatToNumberGroups( int i )
{
var result = new List<Tuple<string, string>>();
var asString = i.ToString();
var count = 1;
while ( asString.Length > 0 )
{
var chunkStartPos = (asString.Length < 3) ? 0 : asString.Length - 3;
var chunkLength = (asString.Length < 3) ? asString.Length : 3;
var chunk = asString.Substring( chunkStartPos, chunkLength );
switch ( count )
{
case 2:
result.Add( new Tuple<string, string>( "Thousand", chunk ) );
break;
case 3:
result.Add( new Tuple<string, string>( "Million", chunk ) );
break;
default:
/// First chunk is blank and we're not expecting anything > than million
result.Add( new Tuple<string, string>( string.Empty, chunk ) );
break;
}
count++;
asString = (asString.Length > 3) ? asString.Substring( 0, asString.Length - 3 ) : string.Empty;
}
result.Reverse();
return result;
}

public string FormatChunkToEnglish( string chunk )
{
Debug.Assert( (chunk.Length <= 3) && (chunk.Length >= 1), "Expecting 1-3 digit portion of a number to format to english." );

var onesDigit = OnesDigit( chunk );
var tensDigit = TensDigit( chunk );
var hundredsDigit = HundredsDigit( chunk );

var onesDigitFormatted = FormatDigit( onesDigit, DigitPlace.Ones, chunk );
var tensDigitFormatted = (tensDigit == string.Empty) ? string.Empty : FormatDigit( tensDigit, DigitPlace.Tens, chunk );
var hundredsDigitFormatted = (hundredsDigit == string.Empty) ? string.Empty : FormatDigit( hundredsDigit, DigitPlace.Hundreds, chunk );

var result = onesDigitFormatted;
if ( tensDigitFormatted != string.Empty )
{
result = ( result == string.Empty ) ? tensDigitFormatted : string.Format( "{0} {1}", tensDigitFormatted, result );
}
if ( hundredsDigitFormatted != string.Empty )
{
result = string.Format( "{0} {1}", hundredsDigitFormatted, result );
}

return result;
}

#region These all deal with the 1-3 digit chunks

private string HundredsDigit( string chunk )
{
return (chunk.Length > 2) ? chunk.Substring( chunk.Length - 3, 1 ) : string.Empty;
}

private string TensDigit( string chunk )
{
return (chunk.Length > 1) ? chunk.Substring( chunk.Length - 2, 1 ) : string.Empty;
}

private string OnesDigit( string chunk )
{
return chunk.Substring( chunk.Length - 1 );
}

private bool ContainsTeen( string allDigits )
{
return TensDigit( allDigits ) == "1";
}

#endregion

private string FormatDigit( string digit, DigitPlace digitPlace, string allDigits )
{
/// Param must be string due to the way it's constructed
Debug.Assert( digit.Length == 1, "Expecting to format single digit while converting number to english, but received multiple digits" );

switch ( digit )
{
case "0":
return string.Empty;
case "1":
switch ( digitPlace )
{
case DigitPlace.Ones:
return ContainsTeen(allDigits) ? string.Empty : "One";
case DigitPlace.Tens:
return FormatTeen(allDigits);
case DigitPlace.Hundreds:
return "One Hundred";
}
break;
case "2":
switch ( digitPlace )
{
case DigitPlace.Ones:
return ContainsTeen(allDigits) ? string.Empty : "Two";
case DigitPlace.Tens:
return "Twenty";
case DigitPlace.Hundreds:
return "Two Hundred";
}
break;
case "3":
switch ( digitPlace )
{
case DigitPlace.Ones:
return ContainsTeen(allDigits) ? string.Empty : "Three";
case DigitPlace.Tens:
return "Thirty";
case DigitPlace.Hundreds:
return "Three Hundred";
}
break;
case "4":
switch ( digitPlace )
{
case DigitPlace.Ones:
return ContainsTeen(allDigits) ? string.Empty : "Four";
case DigitPlace.Tens:
return "Forty";
case DigitPlace.Hundreds:
return "Four Hundred";
}
break;
case "5":
switch ( digitPlace )
{
case DigitPlace.Ones:
return ContainsTeen(allDigits) ? string.Empty : "Five";
case DigitPlace.Tens:
return "Fifty";
case DigitPlace.Hundreds:
return "Five Hundred";
}
break;
case "6":
switch ( digitPlace )
{
case DigitPlace.Ones:
return ContainsTeen(allDigits) ? string.Empty : "Six";
case DigitPlace.Tens:
return "Sixty";
case DigitPlace.Hundreds:
return "Six Hundred";
}
break;
case "7":
switch ( digitPlace )
{
case DigitPlace.Ones:
return ContainsTeen(allDigits) ? string.Empty : "Seven";
case DigitPlace.Tens:
return "Seventy";
case DigitPlace.Hundreds:
return "Seven Hundred";
}
break;
case "8":
switch ( digitPlace )
{
case DigitPlace.Ones:
return ContainsTeen(allDigits) ? string.Empty : "Eight";
case DigitPlace.Tens:
return "Eighty";
case DigitPlace.Hundreds:
return "Eight Hundred";
}
break;
case "9":
switch ( digitPlace )
{
case DigitPlace.Ones:
return ContainsTeen(allDigits) ? string.Empty : "Nine";
case DigitPlace.Tens:
return "Ninety";
case DigitPlace.Hundreds:
return "Nine Hundred";
}
break;
default:
return string.Empty;
}
return string.Empty; // not a good sign when you have to add code just to make it compile
}

/// <summary>
/// </summary>
/// <param name="allDigits">Either 2 or 3 characters long and the tens digit is a one</param>
/// <returns></returns>
private string FormatTeen( string allDigits )
{
Debug.Assert( (allDigits.Length == 2) || (allDigits.Length == 3) );
switch ( OnesDigit(allDigits) )
{
case "0":
return "Ten";
case "1":
return "Eleven";
case "2":
return "Twelve";
case "3":
return "Thirteen";
case "4":
return "Fourteen";
case "5":
return "Fifteen";
case "6":
return "Sixteen";
case "7":
return "Seventeen";
case "8":
return "Eighteen";
case "9":
return "Nineteen";
default:
return string.Empty;
}
}

}