Search code examples
asp.net-mvc-3c#-4.0custom-model-binder

How to use a custom binder to normalize user input?


Based on user input, I want to nullify some properties of an entity before it gets to the controller's Action. In a crude example (the real model is a lot more complicated), let's say my entity have a BillingType property that defines if the client will be billed either monthly or fortnightly:

public class BillingMethod
{
    public int Id { get; set; }
    public int BillingTypeValue { get; set; }
    public BillingType BillingType
    {
        get
        {
            return (BillingType)BillingTypeValue;
        } 
        set
        {
            BillingTypeValue = (int)value;
        }
    }

    public int? DayOfMonth { get; set; }
    public int? DayOfFirstFortnight { get; set; }
    public int? DayOfSecondFortnight { get; set; }
}

public enum BillingType
{
    Monthly,
    Fortnightly
}

Now let's say the user chooses to charge monthly and then sets the DayOfMonth property to 15. Then he changes his mind and sets the Billing Type to fortnightly and sets the two fortnigth day properties and finnaly submits the form. What I need is a way to nullify the unused property (DayOfMonth, in this example) before it gets to the Controller's Action.

I know I can do this via javascript as the user changes from one billing type to another, or by intercepting the form's onSubmit event. Or even inside the Action, before saving it to the context, but I need a way to do it once and forget about it.

I think the best way to do this is by using a custom model binder but I don't have any experience at it. I tried creating a new ModelBindingContext but I couldn't realize how to get and parse form data inside a new object so I clearly need some directions.


Solution

  • I managed to modify the object using a custom binder. First I call base.BindModel and then I modify the properties before returning it.

    public class BillingMethodModelBinder : DefaultModelBinder
    {
        public override object BindModel(ControllerContext cContext, ModelBindingContext bContext)
        {
            var newBillingMethod = (BillingMethod)base.BindModel(cContext, bContext);
            var bType = newBillingMethod.BillingTypeValue;
            if (bType == (int)BillingType.Monthly)
            {
                newBillingMethod.DayOfFirstFortnight = null;
                newBillingMethod.DayOfSecondFortnight = null;
            }
            else
            {
                newBillingMethod.DayOfMonth = null;
            }
    
            return newBillingMethod;
        }
    }
    

    And, of course, add the custom binder in global.asax's Application_Start():

    ModelBinders.Binders.Add(typeof(BillingMethod), new BillingMethodModelBinder());
    

    That worked great for me, I'll wait for a day or so before accepting this answer just in case someone comes with a better solution or points me to any problems I may encounter while using this method