MVC Custom Model Binders and CheckBoxList

Back to ASP.Net MVC! In this post, I am going to discuss about custom model binders and a real-time use of custom model binders in ASP.Net MVC. Before getting in to custom model binders, as the name suggests, there is also a default model binder. If you are interested in custom model binders for mvc, I am sure you are aware about the model binding capabilities of the ASP.Net MVC framework. For starters let me shortly introduce you to default model binding. Assume there is a class as given below:

public class UserInfo
{
    public string UserName { get; set; }
    public string UserEmail { get; set; }
}

With this object, you can do the following to get these 2 information from a user. A default model binder comes in to picture in this case.

  • Have an action method, called, Index
  • The action method returns a view which is strongly typed to the UserInfo class
  • Have an action method called Index decorated with the [HttpPost] attribute and taking UserInfo as a parameter
  • When user enters something and presses the “Submit” button, the action method decorated with [HttpPost] is called
  • The DefaultModelBinder takes the responsibility of mapping the route values and form values to an instance of UserInfo and then passes it to the action method

As you noticed in the listing above, default model binders make it easy in case you have a simple object as above. But, if your model is not a simple object, you may have to use custom model binders. Let us consider building a check box list. In this case, you display an arbitrary list of check box items. So the default model binder would not suffice. So in order to resolve this issue, we need custom model binders. Towards the end of the post, there is a fully working project that utilizes the check box list user control I have created. I hope it acts as a resource to explain the project more comprehensively than I could do here. Without further ado, let me get started on how custom model binders could be used.

The intention is to create a check box list which does not enforce any limits on the number of items. Every check box item could be considered as a collection of the following elements:

  • A check box itself
  • A label containing what the check box represents
  • A hidden field to store the value
  • A hidden field to store the check box state

Note: This implementation is quite different from other ways of implementing a check box list. Towards the end of this post, there are a few helpful links. Have a look at them too!

In order to create these elements, I am using a few custom html helper extensions. This is contained within a class called CustomHtmlHelpers. I am not going to list out the content of this file, but, just give a short description of what every method does.

  • CheckBoxInput – This method creates a chechbox (without a label), a hidden field to track the status of the checkbox. This is required because, if the checkbox is unchecked, there won’t be a value posted to the server when the submit button is clicked. In general, check box lists available on the internet will help you get the values checked by the user, but you will have to regenerate the mode using a database query (or something else). But in this case I reconstruct the entire list using the form values posted.
  • CheckBoxInputLabel – This method creates a label for every checkbox. Clicking on this label would also
  • CheckBoxValue – This generates a hidden field that holds the values for the checkbox
  • CheckBoxListHeader – This generates a header for every individual check box list. This contains a label and a hidden field to store the header

With these extensions, the following user control denotes a sigle check box list:

@model SampleAppForCheckBoxList.Models.CheckBoxListViewModel
@using SampleAppForCheckBoxList.Infrastructure

<div class="cpHeader">
    @Html.CheckBoxListHeader(Model.HeaderText)
</div>
<div class="cpContent">
    @foreach (var item in Model.Items)
    {
        @Html.CheckBoxValue(Model.HeaderText, item, Model.Items.IndexOf(item))
        @Html.CheckBoxInput(Model.HeaderText, item, Model.Items.IndexOf(item))
        @Html.CheckBoxInputLabel(Model.HeaderText, item, Model.Items.IndexOf(item))
        <br />
    }
</div>

If you notice in the above snippet, section under “cpHeader” denotes the header area and “cpContent” denotes the content area – where all the check boxes are listed. Line 2 imports the custom html helper described in the earlier step.

Note: This could also be added to the web.config.

Now, let us create a custom model binder. A custom model binder in ASP.Net MVC should implement the IModelBinder interface. This interface has a single method called BindModel which returns an object. Given below is the implementation of this custom model binder.

public class CheckBoxListViewModelBinder : IModelBinder
{
	public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
	{
	    List<CheckBoxListViewModel> models = new List<CheckBoxListViewModel>();

	    string[] formKeys = controllerContext.HttpContext.Request.Form.AllKeys.ToArray();

	    List<string> rootItems = formKeys.Where(s => s.StartsWith("hdrTitle")).ToList();

	    foreach (var item in rootItems)
	    {
		string hdrValue = item.Split('_')[1];
		string[] txtValues = formKeys.Where(s => s.StartsWith("lblLabel_" + hdrValue)).ToArray();
		string[] valValues = formKeys.Where(s => s.StartsWith("valValue_" + hdrValue)).ToArray();
		string[] hdnValues = formKeys.Where(s => s.StartsWith("hdnChk_" + hdrValue)).ToArray();

		CheckBoxListViewModel model = new CheckBoxListViewModel();
		model.HeaderText = Regex.Replace(hdrValue, "([a-z])([A-Z])", "$1 $2");
		model.Items = new List<CheckBoxListItem>();
		for (int index = 0; index < txtValues.Count(); index++)
		{
		    CheckBoxListItem _item = new CheckBoxListItem();
		    _item.Text = bindingContext.GetValue(txtValues[index]);
		    _item.Value = bindingContext.GetValue(valValues[index]);
		    _item.IsChecked = bool.Parse(bindingContext.GetValue(hdnValues[index]));

		    model.Items.Add(_item);
		}

		models.Add(model);
	    }

	    if (rootItems.Count == 1)
		return models.First();
	    else
		return models;
	}
}

As you can see from the code listing above, the method receives 2 parameters: a ControllerContext instance and a ModelBindingContext instance. The bindingContext can be used to get the value posted for a certain input element, but, it does not provide a method by which we can get a list of all the fields posted. So I use the Form property from the controllerContext.HttpContext.Request.Form instance. Then as you noticed from the html helper, every element uses a certain id/name format. Using this the values are gathered for every element including the header text. Following is a high level algorithm:

  • Find out the number of header elements – this denotes the number of check box lists used. The id/name format is
    hdrTitle_{header_Text}
  • Then using this every other element can be identified as given below

    • Text of the check box is identified using id/name format of lblLabel_{header_Text}_{index}
    • Value of the check box is identified using id/name format of valValue_{header_Text}_{index}
    • Check box checked status of the check box is identified using id/name format of hdnChk_{header_Text}_{index}
  • A CheckBoxListViewModel object is contructed for every check box list and returned.

Now that we have a custom binder, let us see how to put this in to use. You will have to instruct the framework to invoke this binder in case an action method has a parameter of this type. So adding the following 2 lines to Global.asax.cs Application_Start method would take care of this.

protected void Application_Start()
{
    AreaRegistration.RegisterAllAreas();

    ModelBinders.Binders.Add(typeof(CheckBoxListViewModel), new CheckBoxListViewModelBinder());
    ModelBinders.Binders.Add(typeof(List<CheckBoxListViewModel>), new CheckBoxListViewModelBinder());

    RegisterGlobalFilters(GlobalFilters.Filters);
    RegisterRoutes(RouteTable.Routes);
}

Lines 5 & 6 are of prime importance. These lines indicate that if a parameter of type CheckBoxListViewModel or List<CheckBoxListViewModel> is expected, initialize an instance of CheckBoxListViewModelBinder and pass the controller context and the binding context.

Another important thing to notice is the jquery required to store the status of the check box in the hidden field created for this purpose. Following is the jquery required to do this.

$(document).ready(function () {
    $('.chkClickable').click(function () {
        if ($(this).is(':checked')) {
            $(this).next('.hdnStatus').val('true');
        }
        else {
            $(this).next('.hdnStatus').val('false');
        }
    });
});

Finally, let us put this in to use. If you look in to the DemoController this user control is put in to use as given below:

public ActionResult Index()
{
    return View(GetModel());
}

[HttpPost]
public ActionResult Index(CheckBoxListViewModel model)
{
    ViewData["Choices"] = string.Join(",", model.GetSelectedItems());
    return View(model);
}

private CheckBoxListViewModel GetModel()
{
    var model = new CheckBoxListViewModel();
    model.HeaderText = "Select Languages";
    model.Items = new List<CheckBoxListItem>{ new CheckBoxListItem { Text = "C#", Value = "CSharp", IsChecked = false },
					      new CheckBoxListItem { Text = "Ruby", Value = "Ruby", IsChecked = false },
					      new CheckBoxListItem { Text = "PHP", Value = "PHP", IsChecked = false },
					      new CheckBoxListItem { Text = "Java", Value = "Java", IsChecked = false },
					      new CheckBoxListItem { Text = "Scala", Value = "Scala", IsChecked = false }
    };

    return model;
}

GetModel is simply a method that returns a view model for the check box list. This contains a header text and the items to be displayed in the list. Note that this could very well be a call to the database. The Index action method passes this to the view and the view would in turn pass it on to the user control which would display it.

The action method Index decorated with the [HttpPost] attribute would be invoked when the user submits the form. The method has a CheckBoxListViewModel parameter. So during the post action mvc would invoke the custom binder and repopulate with the user’s choices. The model has the GetSelectedItems method which could be used to identify the items selected by the user.

The project also has another example which has 2 check box lists. This is where the following line from the Global.asax.cs class comes in to use:

ModelBinders.Binders.Add(typeof(List<CheckBoxListViewModel>), new CheckBoxListViewModelBinder());

Note that this method would post a lot more fields (data) in the request than the other method which only posts the selected values.

A demo always helps isn’t it? Here it is!!!

Download the sample app with the user control and associated code files here

Link to github for this project is here – You will be able to get the most latest from here always, because I will check in here in case I make any changes.

Some nice links:

  • Another implementation of check box list is here. I do have similar implementation that returns only the selected values. My implementation uses mvc templates instead of html extensions. When I get a chance I will post my implementation too.
  • A link about a fix for mvc check boxes is here.

Happy coding! Post your comments!!!

Share