tags:

views:

213

answers:

3

Is there any way to get UpdateModel or TryUpdateModel to parse a money or currency formatted value such as $1,200.00 into a decimal without blowing chunks?

A: 

Are you able to parse the value up front before calling either of these methods? If so, you could use the following method to do so

    var provider = (NumberFormatInfo)CultureInfo.InvariantCulture.NumberFormat.Clone();
    provider.CurrencySymbol = "$";
    var x = decimal.Parse(
        "$1,200",
        NumberStyles.AllowCurrencySymbol | NumberStyles.AllowDecimalPoint | NumberStyles.AllowThousands,
        provider);
AdamRalph
This i think would be great as a html helper
griegs
Well parsing it normally isn't a problem, but I have a number of "money" fields and I'd rather not junk of my controllers parsing around TryUpdateModel, if at all possible.
cadmium
@cadmium use a custom model binder, see the link in my answer.
eglasius
+3  A: 

Use a custom model binder.

An example of using one to parse a decimals differently

eglasius
Not as simple or elegant as I was hoping, but with some tweaking it actually worked, so thank you. I will posted my tweaked binder.
cadmium
+1  A: 

Answer was awarded to Freddy Rios since his link provided me with the base to do this, but the code needed some fixing up:

   // http://www.crydust.be/blog/2009/07/30/custom-model-binder-to-avoid-decimal-separator-problems/
public class MoneyParsableModelBinder : DefaultModelBinder
{

    public override object BindModel(ControllerContext controllerContext,
      ModelBindingContext bindingContext)
    {

        object result = null;
        // Added support for decimals and nullable types - c.
        if (
            bindingContext.ModelType == typeof(double)
            || bindingContext.ModelType == typeof(decimal)
            || bindingContext.ModelType == typeof(double?)
            || bindingContext.ModelType == typeof(decimal?)
            )
        {

            string modelName = bindingContext.ModelName;
            string attemptedValue = bindingContext.ValueProvider[modelName].AttemptedValue;

            // Depending on cultureinfo the NumberDecimalSeparator can be "," or "."
            // Both "." and "," should be accepted, but aren't.
            string wantedSeperator = NumberFormatInfo.CurrentInfo.NumberDecimalSeparator;
            string alternateSeperator = (wantedSeperator == "," ? "." : ",");

            if (attemptedValue.IndexOf(wantedSeperator) == -1
              && attemptedValue.IndexOf(alternateSeperator) != -1)
            {
                attemptedValue = attemptedValue.Replace(alternateSeperator, wantedSeperator);
            }

            // If SetModelValue is not called it may result in a null-ref exception if the model is resused - c. 
            bindingContext.ModelState.SetModelValue(modelName, bindingContext.ValueProvider[modelName]);

            try
            {
                // Added support for decimals and nullable types - c.
                if (bindingContext.ModelType == typeof(double) || bindingContext.ModelType == typeof(double?))
                {
                    result = double.Parse(attemptedValue, NumberStyles.Any);
                }
                else
                {
                    result = decimal.Parse(attemptedValue, NumberStyles.Any);
                }
            }
            catch (FormatException e)
            {
                bindingContext.ModelState.AddModelError(modelName, e);
            }
        }
        else
        {
            result = base.BindModel(controllerContext, bindingContext);
        }

        return result;
    }
}

It ain't pretty, but it works.

cadmium