Question

I have a model that has an integer property. When the model is submitted with 23443, the model binder works great and the value is available in the action. But if the model is submitted with a thousands separator like 23,443, the value isn't parsed and the property is zero. But I found that a property with the type of decimal could have the thousands separator and it would parse and be populated correctly.

I found out that by default Int32.Parse() doesn't parse thousands separator but Decimal.Parse() does allow thousands separator. I don't want to have to write a check like:

public ActionResult Save(Car model, FormCollection form) {
    Int32 milage;
    if(model.MyProperty == 0 && Int32.TryParse(form["MyProperty"], NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out milage) {
        model.MyProperty = milage;
    } else
        ModelState.AddModelError("Invalid", "Property looks invalid");

    [...]
}

every time I deal with these fields. It looks ugly, and moves all the validation out of the model attributes. Changing the type of the property to decimal just to make the model binding work doesn't seem like a smart idea. When I look into the model binder, it looks like it's using TypeConverter to complete the conversions from string to the type. And it looks like Int32Converter uses Int32.Parse() with NumberStyles.Integer.

Is there a way to change the behavior of Int32Converter to allow the thousands separator to be parsed by default? Maybe override the default NumberStyles on Int32.Parse() across the entire app? Or is adding my own model binder which parses the integers with NumberStyles.AllowThousands the only/correct course of action?

Was it helpful?

Solution

I think, you can add custom binder for int type.

Demo: http://dotnetfiddle.net/VSMQzw

Useful links:

updated

Based on Haacked article:

using System;
using System.Globalization;
using System.Web.Mvc;

public class IntModelBinder : IModelBinder
{
    #region Implementation of IModelBinder

    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) 
    {
        ValueProviderResult valueResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
        ModelState modelState = new ModelState { Value = valueResult };
        bindingContext.ModelState[bindingContext.ModelName] = modelState;

        object actualValue = null;
        try 
        {
            actualValue = Int32.Parse(valueResult.AttemptedValue, NumberStyles.Number, CultureInfo.InvariantCulture);
        }
        catch (FormatException e) 
        {
            modelState.Errors.Add(e);
        }

        bindingContext.ModelState.Add(bindingContext.ModelName, modelState);
        return actualValue;
    }

    #endregion
}

And in the Application_Start event (probably in the Global.asax), add:

ModelBinders.Binders.Add(typeof(int), new IntModelBinder());
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top