ASP.NET MVC3 JSON decimal binding woes

I just stumbled onto this bug the other day and I figured I would share. Given the following controller action and ajax call, what would you expect the value of deez to be in the BreakStuff action?

//Model to be bound.
public class Decimals {
   public decimal d1 { get; set; }
   public decimal d2 { get; set; }
   public decimal d3 { get; set; }
}
//Controller action to check the binding
public ActionResult BreakStuff(Decimals deez) {
   return new EmptyResult();
}
//Ajax call to controller action above
$.ajax({
   type: "POST",
   url: "/Home/BreakStuff",
   contentType:'application/json',
   data:JSON.stringify({d1:42.00,d2:3.14,d3:'17'})
});

A screenshot says a thousand words:

Model values before adding DecimalModelBinder

What happened to d1? The resulting JSON from JSON.stringify({d1:42.00,d2:3.14,d3:'17'}) ends up being {"d1":42,"d2":3.14,"d3":"17"} Cool right? Mostly, except that once this value gets server side, that 42.00 is now seen as an int (42) and the default model binder doesn't want to stuff that into my decimal type. Bummer. Not exactly what I was expecting.

Phil Haack to the rescue! After doing some searching I stumbled across this post which fixes binding to string values with comma separators. It solves the same problem I'm having, so I'll repost the bits here:

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

Then stick this into your Global.asax.cs Application_Start:

protected void Application_Start() {
    ModelBinders.Binders.Add(typeof(decimal), new DecimalModelBinder());
    // Your other stuff goes here.
}

Once I did that and re-ran the code above, everything works as expected. See for yourself:

Model values after adding DecimalModelBinder

  • Dung

    Thanks for your great article.

  • Anish

    Nice article. Very very useful stuff

  • Leandro de los Santos

    Thanks, really usefull. I do a quicker and dirty solution… I change my decimal to floats.

  • Stan

    Thanks!

  • greg

    OK – so why is the “17″ not having the same problem?

  • Sebastien

    I stumped on this problem. You solved my problem but i was wondering the same thing.. why is the ’17′ not having the same problem ?

  • Adriano

    Thanks!

  • Matt

    ’17′ is not having the same problem because it is a string Value and the default mvc model binder does not have an issue binding a string to a decimal.

  • Santhosh

    Thanks for the article. Let me know what happen, if i mention nullable Decimal. Like

    public decimal? d1 { get; set; }