Model binding is a mechanism ASP.NET MVC uses to create parameter objects defined in controller action methods. The parameters can be of any type, from simple to complex ones. It simplifies working with data sent by the browser because data is automatically assigned to the specified model. Without this mechanism, developers would need to manually assign values to model properties, which would result in writing almost the same code in every action method.

How default model binding works?

Let’s say we are working on an application for managing employees of a company. In our app, we will have an EmployeeController class with the following action method:

[HttpGet]
public ActionResult Edit(int id)
{
    //code to fetch employee
    ...
    return View(employee);
}

Since the default route configuration is used, when you navigate to /Employee/Edit/5, the default action invoker will use the routing information to discover that the Edit action method of the EmployeeController should handle the request. However, it can not invoke the Edit method without a valid value for the id parameter. The ControllerActionInvoker then looks for the responsible model binder for the int type and, if you did not provide your own, the DefaultModelBinder is selected.

By default, the DefaultModelBinder searches for the values in four locations, in the following order:

Source Description
Request.Form Values submitted in a form
RouteData.Values Route values, e.g. id in /Employee/Edit/{id}
Request.QueryString Data extracted from the query string portion of the URL
Request.Files Uploaded files

The search stops as soon as the value is found. In this case, the binder will first look in the Form collection. Since there was no form submitted and thus no value named id in that collection, it will look in the RouteData dictionary. The binder finds what it was looking for and does not go on to search in QueryString and Files collections. The ControllerActionInvoker now has all that’s necessary to invoke the method.

Customizing model binding

Sometimes, it may be convenient to use a custom model binder. We can do this by providing our own implementation of the IModelBinder interface. An alternative is to make your custom model binder inherit from DefaultModelBinder class and override the BindModel method. In my example, I will use the first way and implement IModelBinder directly.

Let’s say we want to create a form for adding new employees. We define a controller action method:

[HttpPost]
public ActionResult Create(EmployeeViewModel employee)
{
    ......
    return RedirectToAction("Index");
}

The form will look like this:

@using(Html.BeginForm("Create", "Employee", FormMethod.Post))
{
    <p>
        @Html.Label("FirstName")
        @Html.TextBox("FirstName")
    </p>
    <p>
        @Html.Label("MiddleName")
        @Html.TextBox("MiddleName")
    </p> 
    <p>
        @Html.Label("LastName")
        @Html.TextBox("LastName")
    </p>
    <p>
        @Html.LabelFor(p => p.Address)
        @Html.TextBoxFor(p => p.Address)
    </p>
    <p>
        @Html.LabelFor(p => p.Department)
        @Html.TextBoxFor(p => p.Department)
    </p>
    <p>
        @Html.LabelFor(p => p.JoinDate)
        @Html.TextBoxFor(p => p.JoinDate, new { type = "date" })
    </p>

    <input type="submit" value="Save" />
}

For the purpose of this example, we want to have a form with separate input fields for First, Middle and Last Name, but for some reason, the EmployeeViewModel has only FullName property, and we can not change it:

public class EmployeeViewModel
{
    public int Id { get; set; }

    public string FullName { get; set; }

    public DateTime JoinDate { get; set; }

    public string Address { get; set; }

    public string Department { get; set; }
}

We could deal with this easily within the Create action method, but it would be nicer to have that part of the code abstracted away. So we will create a new class named EmployeeModelBinder and have it implement IModelBinder:

public class EmployeeModelBinder : IModelBinder
    {
        public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
        {
            HttpRequestBase request = controllerContext.HttpContext.Request;

            List<string> nameValues = new List<string>();

            int id = Convert.ToInt32(request.Form.Get("Id"));
            nameValues.Add(request.Form.Get("FirstName"));
            nameValues.Add(request.Form.Get("MiddleName"));
            nameValues.Add(request.Form.Get("LastName"));
            string address = request.Form.Get("Address");
            string department = request.Form.Get("Department");
            DateTime joinDate = Convert.ToDateTime(request.Form.Get("JoinDate"));

            return new EmployeeViewModel()
            {
                Id = id,
                FullName = string.Join(" ", nameValues),
                Address = address,
                Department = department,
                JoinDate = joinDate
            };
        }
    }

The custom model binder must be registered. If we want to do it globally, it can be done in Global.asax.cs within Application_Start() method:

protected void Application_Start()
{
    AreaRegistration.RegisterAllAreas();
    FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
    RouteConfig.RegisterRoutes(RouteTable.Routes);
    BundleConfig.RegisterBundles(BundleTable.Bundles);
    ModelBinders.Binders.Add(typeof(EmployeeViewModel), new EmployeeModelBinder());
}

If we don’t want to register the binder globally, we can attach a ModelBinder attribute to the action method parameter:

[HttpPost]
public ActionResult Create([ModelBinder(typeof(EmployeeModelBinder))] EmployeeViewModel employee)
{
    ...
    return RedirectToAction("Index");
}

Now, when we submit our form with separate name values, the custom model binder will make sure they are all concatenated and assigned to the FullName property of the EmployeeViewModel.

More articles

Your opinion matters!

comments powered by Disqus