An intuitive Dictionary Model Binder for ASP.NET MVC

The other day I was working on an ASP.NET MVC website and came across a need to post an array from the browser into the web app. The framework comes with something called a Model Binder that automagically converts submitted form data into action parameters of the controller. For example, if we have form submitted data such as
person.FirstName=John&person.LastName=smith
for a theoretical model class ‘Person’, and
[csharp]public ActionResult SavePerson(Person person)[/csharp]
as the action method signature, SavePerson will be executed with the parameter equivalent to
[csharp]new Person() { FirstName = "John", LastName = "Smith" }[/csharp]

The default model binder is pretty powerful, using reflection to dig out and assign all the fields. It also supports arrays and dictionaries, but with big limitations. The array must start at 0 and be unbroken. That is understandable for arrays, but what if you had a dictionary? Surely it can start at any position? Not so. The dictionary has even more obscure requirements, with the need to specify explicit .Key and .Value parameters in your form submission. For example:
dict[0].Key=mykey&dict[0].Value=myvalue
This represented extra work to generate the form on the client side. I just want to input something more intuitive like:
dict[mykey]=myvalue
The ASP.NET MVC framework is highly extensible. It allows you to define your own custom model binder so that’s exactly what I did. Inheriting off DefaultModelBinder, I created DefaultDictionaryBinder that overrode the BindModel method and intercepts when a IDictionary<,> class is being bound.

The code is now up at github: DefaultDictionaryBinder.cs. Note that if you are using this on ASP.NET MVC 1, please define the macro ASPNETMVC1. If you are using it with MVC 2 or MVC 3, it should work as is.

To use, you have to override the default model binder. In global.asax.cs in Application_Start(), add the line:
[csharp]
ModelBinders.Binders.DefaultBinder = new DefaultDictionaryBinder();
[/csharp]

The code is very flexible, only requiring the dictionary key to be of a basic type convertible from string, ie. Dictionary or Dictionary. The value can be any object that is able to be bound by the default model binder.
An example follows:
If your form input is
persons[3].FirstName=John&persons[3].LastName=Smith&persons[4].FirstName=Jane&persons[4].LastName=Doe&
and our action signature
[csharp]public ActionResult SavePersons(Dictionary<int, Person> persons)[/csharp]
the persons parameter would be
[csharp]
new Dictionary<int, Person>() {
{ 3, new Person() {"John", "Smith"} },
{ 4, new Person() {"Jane", "Doe"} },
}
[/csharp]

Simple and intuitive.

Download: DefaultDictionaryBinder.cs Simple Example Project (ASP.NET MVC 3 required)

12 thoughts on “An intuitive Dictionary Model Binder for ASP.NET MVC

  1. Loune Post author

    @prokofyev I actually have a MVC 2 version. As you said they changes the interface so you can no longer enumerate the keys, so in the new version which I’ll post up in the weekend, I just loop through Request.QueryString Request.Form and Request.Files.

  2. ashr

    Dude, this is seriously cool. I thought I was going to have to revert to some really spaghetti like code before I came across your post.

    Thanks a million !

  3. BogDrag

    Very useful code

    It support nested dictionaries with Nullable generic types
    (such as Dictionary<string, Dictionary> ).

    However it seems that an attempt to assign invalid value to int? triggers not one but multiple duplicated errors in related ModelErrorCollection.

    Anyway very good code.

  4. Timothep

    Very useful, thanks a lot.

    As a side note, I noticed that when I try to use an IDictionary instead of a Dictionary in my model, the GetInterface() cast is unsuccessful…

  5. Josh

    Properties of type IDictionary can be supported by replacing the declaration of idictType with

    Type idictType = null;

    if (modelType.IsGenericType && modelType.GetGenericTypeDefinition() == typeof(IDictionary))
    {
    idictType = modelType;
    }
    else
    {
    idictType = modelType.GetInterface(“System.Collections.Generic.IDictionary`2”);
    }

Leave a Reply

Your email address will not be published. Required fields are marked *