Dictionary Model Binder in ASP.NET MVC2 and MVC3

In a decidedly typical turn of events, Microsoft changed the API of BindModel in ASP.NET MVC 2 such that it breaks DefaultDictionaryBinder. No longer can you enumerate through the ValueProvider, instead you can only Get a value which you know the name of. I’ve updated the code to work with MVC2 and also tested it with the new MVC 3 RC.

The code is compatible with ASP.NET MVC 1, 2 and 3. To use it for ASP.NET MVC 1, just set the conditional compiler directive ASPNETMVC1 and it will enable the MVC 1 code, otherwise it will work with MVC version 2 and 3.

The code is now up at github: DefaultDictionaryBinder.cs.

There’s also an example MVC3 project showing the basic functionality of the Dictionary Binder: link

13 thoughts on “Dictionary Model Binder in ASP.NET MVC2 and MVC3

  1. prokofyev

    Thanks for the update! Very useful code but by some reason it’s unappreciated. Will promote it on occasion.

  2. prokofyev

    @Loune I propose to allow the use of GUID dictionary keys, replace line

    dictKey = Convert.
    ChangeType(key.Substring(bindingContext.ModelName.Length + 1, endbracket – bindingContext.ModelName.Length – 1), ga[0]);

    to

    dictKey = TypeDescriptor.GetConverter(ga[0]).
    ConvertFrom(key.Substring(bindingContext.ModelName.Length + 1,
    endbracket – bindingContext.ModelName.Length – 1));

    in DefaultDictionaryBinder class source.

    Many thanks!

  3. Loune Post author

    @prokofyev Thanks for the suggestion. I’ve changed it to use TypeConverters and updated the code in the repository now.

  4. Deathsmith

    Hi,

    thanks for this work, it helped us a lot.
    But I think the result of:

    nextBinder.BindModel(controllerContext, bindingContext)

    should be returned ;)

  5. Serg

    Nice work! Really helpful.

    I would suggest just one little improvement. Since you already know what your type is dictionary you can declare result as
    System.Collections.IDictionary result = null;

    All dictionary classes including generics Dictionary are implementing this interface

    now you can cast safely
    if (result == null)
    result = (System.Collections.IDictionary)CreateModel(controllerContext, bindingContext, modelType);

    if(!result.Contains(dictKey))
    {
    result.Add(dictKey, newPropertyValue);
    }

    That way you avoid using reflection and thus it should be better for performance.

  6. Martin Sher

    Thanks. This is very useful. However the problem I have is that my dictionary is a property within a class. For example my class is

    public class Team
    {
    public string Name {get; set;}
    public Dictionary scores {get; set;}
    }

    How do I modify your example to get his to work with a form and postback.

    As I can have other classes with dictionaries in them, I don’t want to have to create specific model binders for each and every class. I just want the dictionary properties to be bound. Out the box the defaultBinder ignores them.

    Any help.

    Thanks
    Martin

  7. Levi

    @Martin – The solution is to use this as the default model binder instead.

    http://stackoverflow.com/a/1487076/246561

    Where his code says ‘// do your thing’, instantiate a new DictionaryModelBinder and call BindModel().

    Loune’s binder has to be modified slightly – no need for the constructors and the checks with idicttype can be removed – replace all references with just the modelType instead.

    Going to try and get a fork of his code up soon to illustrate better.

  8. Jörgen L

    Im using this code but have made a pair of modifications that I think are of value:

    1. The code is failing miserably in regard to performance in a case where the element of the dictionary is an object, especially if it is a recursive structure where the element in itself contains a dictionary, since it will rebind the same dictionary each time it finds a reference to it, ie. for each field in the contained element. I solved that by adding a dictionary to use as a lookup table where I add each model name that has been bound, skipping entries in the key loop if the found dictionary is already bound. In my application this changed the time to bind from 10 seconds to non-noticable, changing the total number of iterations in the key loop from 250 thousand times to a few hundred…

    2. Since I render the dictionary values in a separate partial view this gave the effect on naming that the fields were named like “dict.[key]” instead of “dict[key]”. I changed the condition for matching the model name to test for both variants, which solved my problem.

    With those modifications the code runs lika a clockwork for me. Thanks!

  9. Remco

    Worked great for me, also with nested complex structures. I made few adjustments to make it work when: (1.) your Action method takes an IDictionary (not a concrete Dictionary) and (2.) support for bindingContext.FallbackToEmptyPrefix so it takes fields as the Htmlhelpers generate them when your model is of IDictionary.
    /* 1: to support an IDictionary as an action method argument: …*/
    if (modelType.IsInterface && modelType.IsGenericType && modelType.GetGenericTypeDefinition() == typeof(IDictionary))
    {
    idictType = modelType;
    }
    /* 2: to support keys like “[24].Child[83].Child” (as the default Html helpers generate them when your view takes a dictionary as a model) .. */
    foreach (string key in GetValueProviderKeys(controllerContext))
    {
    int startbracket = key.StartsWith(bindingContext.ModelName + “[“, StringComparison.InvariantCultureIgnoreCase)
    ? bindingContext.ModelName.Length
    : (bindingContext.FallbackToEmptyPrefix ? key.IndexOf(‘[‘) : -1);
    if (startbracket>=0)
    {
    /*… and replace “bindingContext.ModelName.Length” with “startbracket”in the rest of the loop */

    I hope this is useful to anyone

  10. ashr

    In the light of what Jörgen said, what I noticed is my validators get called multiple times when I use this binder. On one model I have in my dictionary, there are 16 validation attributes; funny thing is each attribute get called 16 times. I’m not 100% sure it’s the dictionary binder causing this issue, but I have a sneaky suspiscion that it is. I will try Jörgen’s approach, hopefully it fixes this issue as well.

Leave a Reply

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