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
Thanks for the update! Very useful code but by some reason it’s unappreciated. Will promote it on occasion.
@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!
@prokofyev Thanks for the suggestion. I’ve changed it to use TypeConverters and updated the code in the repository now.
Hi,
thanks for this work, it helped us a lot.
But I think the result of:
nextBinder.BindModel(controllerContext, bindingContext)
should be returned ;)
@Deathsmith You’re right. Thanks for pointing it out!
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.
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
@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.
This simply does not work with production release of MVC3. The dictionary in web method parameters is always null.
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!
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
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.
Yeah, the moment you add the model bind cache all validator issues go away.
Modified version: http://ashr.net/DefaultDictionaryBinder.cs.txt