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;
}
}

}

Wednesday, September 08, 2010

Implementing Session Timeout Check in MVC

This took me awhile to get right; hope it saves some time for somebody else. I simply want to decorate a given controller (at the class level) with an attribute that will cause a redirect to login page (with a session timeout message) on session timeout while using forms authentication.

Here's the attribute:


     public class CheckSessionAttribute : ActionFilterAttribute 

     {

         public override void OnActionExecuting( ActionExecutingContext filterContext )

         {

             if ( filterContext.HttpContext.Session.IsNewSession )

             {

                 FormsAuthentication.SignOut();

                 filterContext.Controller.TempData[Constants .TEMPDATA_KEYS .TIMEOUT] = "Your session has timed out.  Please login again to continue." ;

                 filterContext.Result = new RedirectResult ( "/" );

             }

         }

     }

 

 


Now you need only check the presence of that TempData key on your logon view in order to show a proper timeout message instead of the standard login message.
Note the use of RedirectResult. The old Response.Redirect will do a proper redirect, but won't terminate the original request (even with the overload containing the parameter that tells it to do so). Response.Redirect should really raise an error when used in an MVC app.

Thursday, August 12, 2010

Fix for Extremely Slow Development Browser Performance

I'm running Windows 7 with Visual Studio 2010. When I would debug web applications (where VS would use Cassini, the development web server), browser performance was extremely slow. We're talking almost a second for retrieval of every image (all on my local machine). Most of the time it seemed like IE didn't experience the problem, but something changed and it too seemed to stop working.
After coming across this post and doing what it recommended, the problem went away. I've no idea why that configuration change in Firefox would affect IE, but all my problems vanished.
Maybe this will help some other poor chap; it got to the point where I just couldn't work at all.

Friday, April 23, 2010

Detecting Session Timeout in ASP.Net WebForms

[Edit]
Disregard all this crap. It's simply not possible to detect session timeout using cookieless sessions. Detecting a post with a redirect does in fact trigger the condition in Application_EndRequest below (in my defense, it seemed to work in a test project and was deceiving).
[/Edit]

Everyone is starting new projects with MVC these days, but for all those maintenance projects in ASP.Net WebForms, here is what I've settled on as the best way to detect session timeout. I've had 1 or 2 other methods in production that didn't cause problems, but were not quite correct.

I was quite surprised when initially investigating this issue to see how many ways people had come up with to do it.

I had a completely different version of this up on this post for quite awhile that didn't work when using cookieless sessions.

Here's the assumption: if a user's session has timed out, but they've issued an HTTP POST, that indicates a session timeout. If you're not using cookieless sessions, that's all there is to it; check this somewhere in a page base class:
Request.HttpMethod.Equals( "POST" ) && (Session.IsNewSession)
The problem with cookieless sessions is, you never get to that condition on a page. In either case, Session.IsNewSession by itself is completely useless because that may be true on an initial GET (not a session timeout condition). With cookieless sessions, sometimes it will be true and the request will be a GET (by the time it gets to your page) when the user posts after a session timeout. This is what happens when using cookieless sessions: the user posts up, ASP.Net detects a session timeout and issues a 302 telling the browser to go to the same place, but use GET. Thus, on any page, there is absolutely no way to know whether a timeout condition has occurred.

Consequently, the way I handled this problem is by detecting the above stated condition (user's session has timed out, current request is a post) in Global.asax. A Response.Redirect or Server.Transfer works fine with this. The problem that will occur with this approach is that the flag for session timeout will be set, but before the user with the timeout round trips back to the server with a GET, another user will hit Session_Start and receive an incorrect session timeout condition. Also, the user who timed out might, for whatever reason, not make the round trip back to the server. In this case, again, a user issuing his first request will see a session timeout. I'm willing to accept that, on rare occasion, this happens. Knowing users, he will simply try again, get the page he requested, and think little of it.

Global.asax code:
     public  class  Global  : System.Web.HttpApplication 

     {

 

         private  bool  _sessionTimedOutCookieless = false ;

 

         void  Application_EndRequest( object  sender, EventArgs  e )

         {

             /// This condition occurs when using cookieless sessions (but perhaps not with "AutoDetect" - that mode is evil) 

             /// The redirect will occur within the context of this same application instance, so the private variable will still 

             /// be alive. 

             if  ( Request.HttpMethod.Equals( "POST"  ) && (Response.StatusCode == 302) )

             {

                 _sessionTimedOutCookieless = true ;

             }

         }

 

         void  Session_Start( object  sender, EventArgs  e )

         {

             if  ( _sessionTimedOutCookieless || (SessionTimedOutWithCookies()) )

             {

                 _sessionTimedOutCookieless = false ;

                 Session.Add( "timeout" , true  );

             }

         }

 

         private  bool  SessionTimedOutWithCookies()

         {

             return  Request.HttpMethod.Equals( "POST"  ) && (Session.IsNewSession);

         }

 

     }

 

 




In a base page class:

         protected  void  Page_PreInit()

         {

             if  ( Session["timeout" ] != null  )

             {

                 _timedout = true ; // do something about this wherever 

                 Session.Remove( "timeout"  );

             }

         }

 

 

Friday, February 19, 2010

EF4 & SQL Server Date / DateTime Columns

When using EF4 against SQL Server 2008 (though this would apply to 2005 as well), I kept getting this error on insert:

System.Data.SqlClient.SqlException: The conversion of a datetime2 data type to a datetime data type resulted in an out-of-range value


I had 2 date columns in the table I was attempting to insert into to begin with. One was just a LastDateModified column with a default of GetDate() so I was mentally filtering that column out and focusing on the other column. I changed that column from date to datetime which didn't fix the problem. I then changed that column to DateTime(2,7) and it still didn't fix it. Running a trace on the SQL that EF4 was attempting to insert revealed the problem. EF4 isn't smart enough to see that my LastDateModified column has a default and shouldn't bother sending a value when the programmer doesn't set one. It sent some huge min date that didn't fit into a datetime column.

Thus, the workaround is to either set any columns I won't be explicitly setting in code to type date(2,7) or to explicitly set the value of those columns in code to DateTime.Now. In any case, you can't use the SQL Server 2008 date column data type (which sucks).