Saturday, October 27, 2012

ASP.NET MVC ~ JSON decimal binding problem

With ASP.NET MVC, we will face problem when mapping JSON decimal values in an AJAX call to the model. For example consider the following class and the action method:

public class MyClass {
  
public decimal decValue1 { get; set; }
  
public int intValue1 { get; set; }
  
public decimal decValue2 { get; set; }
}

public ActionResult MyMethod(MyClass mc) {
  
return View();
}

and assume the following jquery ajax call:

$.ajax({
   type
: "POST",
   url
: "/Default/MyMethod",
   contentType
:'application/json',
   data
:JSON.stringify({decValue1:21.00,intValue1:3,decValue2:6.36})
});

On executing the above code, we will notice that the value for decValue1 will not be binded properly and that it will hold a value of 0. The reason is that the method JSON.stringify converts the value as follows before actually posting:

{"decValue1":21,"intValue1":3,"decValue2":6.36}

The default model binding behaviour believes the value of decValue1 to be of type integer (21) and therefore ignores to map this value to the destination type which is decimal. In order to overcome this problem, we will have to create our custom decimal binder and then register that in the application start event. The following is the code provided by Phil Haack to manage the custom binding of decimal types:

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

public class DecimalModelBinder : IModelBinder
{
                  public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
                  {
                         ValueProviderResult valueResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
                         ModelState modelState = new ModelState { Value = valueResult };
                         object actualValue = null;

                         try
                        {
                                             actualValue = Convert.ToDecimal(valueResult.AttemptedValue, CultureInfo.CurrentCulture);
                         }
                        catch (FormatException e)
                        {
                                             modelState.Errors.Add(e);
                         }

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

and then we should register this custom decimal binder class in the application start event in the Global.asax.cs file as follows:

protected void Application_Start() 
{
    AreaRegistration.RegisterAllAreas();
    
    ModelBinders.Binders.Add(typeof(decimal), new DecimalModelBinder());
 
    // All that other stuff you usually put in here...
}

Now, if we execute the same code, we should see the values mapped properly.

No comments: