asp.net-mvcmodelbinderscustom-model-binder

Asp.net MVC Custom Model Binder for decimals - adds model error when decimal action param is null


I've created a custom model binder based on an article from Haacked. Here's the code:

namespace MyNamespace.Project.ModelBinders
{
    public class DecimalModelBinder : DefaultModelBinder
    {
        public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
        {
            string modelName = bindingContext.ModelName;
            ValueProviderResult valueResult = bindingContext.ValueProvider.GetValue(modelName);

            ModelState modelState = new ModelState { Value = valueResult };

            object actualValue = null;

            try
            {
                //replace commas with periods
                actualValue = Convert.ToDecimal(valueResult.AttemptedValue.Replace(",", "."));
            }
            catch (Exception ex)
            {
                modelState.Errors.Add(ex);
            }

            bindingContext.ModelState.Add(modelName, modelState);

            return actualValue;
        }
    }
} 

When MVC loads a view where the controller action is something like this:

public ActionResult Index(decimal amount)  

It seems to fire the model binding and add the error and this is because amount at this point is null because I have a valid use case where index can be loaded with or without parameters (QueryString). As far as I know MVC doesn't support typical OO method overloading such that you have:

public ActionResult Index(decimal amount) {}  
public ActionResult Index() {}  

So, would it be valid to add a null check to my custom model binder to avoid the error that is raised in the try block, or would this interfere with validation?


Solution

  • I see multiple points here in general. First one is regarding this:

    As far as I know MVC doesn't support typical OO method overloading...

    That's right, but it has a very flexible routes configuration to help with such problems.

    You could configure separate routes for calls having the parameter or not having one. I didn't try that, but this would be a sample using Attribute Routing.

    [Route("index/{amount}"]
    public ActionResult IndexWithAmount(decimal amount) {}  
    
    [Route("index")]
    public ActionResult Index() {}
    

    Another thing you can do, as described here is to not use the Model Binder globally but rather enable it only on specific routes:

    public ActionResult Index(
        [ModelBinder(typeof(DecimalModelBinder))]decimal amount) {}