ASP.NET MVC – Model Binders

24 Jul
2011

ASP.NET MVC Model Binders map posted form values to a .NET classes, instead of accepting primitive parameters or form value collection you can accept models/view models as input to your action methods. ASP.NET has a default model binder called DefaultModelBinder. It basically works with most of the .NET types like primitive types, arrays, IList, ICollection and IDictionary.

ASP.NET MVC also has extensibility for model binders and you can implement IModelBinder interface (or you can extend DefaultModelBinder) and you can create your custom model binders. IModelBinder interface contains a BindModel method that called by ASP.NET MVC framework and you can have a full control over the deserialization process.

In this post I will demonstrate how default model binder works for simple model binding. I always recommend to creating ViewModel for your MVC application over directly using Models in your controller layer. Because normally you don’t get all properties about your domain model from a request input. You get input request to your controller as ViewModel validate input and execute your logic and do what ever you need to do, like map it back to a Domain Model type and persist it.

In this post I will basically create a form to add a movie record and will use a ViewModel to get form inputs from http request.

Let’s create the view model

public class MovieViewModel
{
    public string Title { get; set; }
    public string Storyline { get; set; }
    public string ReleaseDate { get; set; }
    public string Genre { get; set; }
}

And create a view to enter and post inputs.

@model ModelBindersSample.Models.MovieViewModel

@{
    ViewBag.Title = "Create";
    Layout = "~/Views/Shared/_Layout.cshtml";
}

<h2>Add Movie Record</h2>

@using (Html.BeginForm()) {
    @Html.ValidationSummary(true)
    <fieldset>
        <legend>MovieViewModel</legend>

        <div class="editor-label">
            @Html.LabelFor(model => model.Title)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Title)
            @Html.ValidationMessageFor(model => model.Title)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.Storyline)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Storyline)
            @Html.ValidationMessageFor(model => model.Storyline)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.ReleaseDate)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.ReleaseDate)
            @Html.ValidationMessageFor(model => model.ReleaseDate)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.Genre)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Genre)
            @Html.ValidationMessageFor(model => model.Genre)
        </div>

        <p>
            <input type="submit" value="Add Movie" />
        </p>
    </fieldset>
}

modelbinders1

When I filled the form and POST it with submit button I can see all form inputs from browser network inspector.

modelbinders2

All this fields are properties in MovieViewModel, one way to get all this form values in server side we can create an action method with 4 parameters to read all form input values. ASP.NET MVC Framework will match all inputs with the right parameter by using form input name and parameter name, so names should be identical.

modelbinders3

As you can see from the above screenshot ASP.NET MVC Framework mapped all inputs and fields correctly. But this is not a good way to read all inputs if you have longer forms creating an action method with very long signature (too much parameters) is not something good. Another way (still not the best) to read all inputs is to read by using a FormCollection.

modelbinders4

Best way for this scenario is to directly accept a .NET type, MovieViewModel as input and let ASP.NET MVC DefaultModelBinder to create a new instance of MovieViewModel and map all inputs to this instance and pass it to the Create action as parameter.

modelbinders5

In this sample there is no complicated mappings and there is no need to create a custom model binder for MovieViewModel type, ASP.NET MVC DefaultModelBinder does everything for us and in the action method we can just use movieViewModel parameter to get form inputs.

If you want to have some control over DefaultModelBinder you can use BindAttribute to exclude some properties by using Exclude or Include properties.

Let’s exclude ReleaseDate from auto mapping list of DefaultModelBinder.

modelbinders6

And to see Include sample let’s just map Title and Storyline.

modelbinders7

You can also decorate MovieViewModel class with Bind attribute to define binding options for this type in application level instead of action method level.

using System.Web.Mvc;

    [Bind(Include = "Title, Storyline")]
    public class MovieViewModel
    {
        public string Title { get; set; }
        public string Storyline { get; set; }
        public string ReleaseDate { get; set; }
        public string Genre { get; set; }
    }

If this level of control over DefaultModelBinder is not enough for requirements you can create an custom model binder for your ViewModel.

I will create a basic model binder for MovieViewModel and it will just map all fields and it will replace “_” characters with space character in the Title input (just to show some custom mapping code!). It’s not the best place to do your all validation but if it’s related with your custom mapping logic you can provide validation errors to your view model by using passed BindingContext parameter. In this sample I just demonstrated a required field check for Title.

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

            var title = request.Form.Get("Title");
            title = title.Replace("_", " ");

            if (string.IsNullOrEmpty(title))
                bindingContext.ModelState.AddModelError("Title", "The title is required.");

            var storyline = request.Form.Get("Storyline");
            var genre = request.Form.Get("Genre");
            var releaseDate = request.Form.Get("ReleaseDate");

            return new MovieViewModel
                       {
                           Genre = genre,
                           ReleaseDate = releaseDate,
                           Storyline = storyline,
                           Title = title
                       };
        }
    }

After we created model binder we need to register this type to ASP.NET MVC Framework as binder of MovieViewModel. For this we need to add a new instance of MovieViewModelBinder to the ModelBinders.Binders list in Application_Start method of Global.asax.cs

protected void Application_Start()
{
     ModelBinders.Binders.Add(typeof(MovieViewModel), new MovieViewModelBinder());
     //...
}

The BindModel method has two parameters.ControllerContext provides information about current request, controller and action method. BindingContext provides some metadata information about target model (in this case MovieViewModel).

I am planning to create a sample to show how to create a generic custom model binder for a RESTful service oriented MVC application and blog about it later.



1 Response to ASP.NET MVC – Model Binders

Avatar

Swapnil Maldhure

March 26th, 2012 at 12:45 pm

Thanks a loat for the Feed, I got what I wanted.

Comment Form

top