Search code examples
c#asp.net-mvcasp.net-mvc-3modelbinders

ASP.NET MVC3 - Dynamic forms (ModelBind to a Dictionary<string, string> or NameValueCollection) - How?


Lets say I have a a form that can have new text inputs created in JavaScript at runtime. And I want to bind the values to a NameValueCollection (or a Dictionary). Does ASP.NET MVC3 natively allow this?

In other words, how do I get this to work?

Assuming this is the HTML form...

<!-- if someone posted this form -->
<form action="MyExample">
    <input type="hidden" name="id" value="123" />
    <input type="text" name="things.abc" value="blah" />
    <input type="text" name="things.def" value="happy" />
    <input type="text" name="things.ghi" value="pelicans" />
    <input type="submit" />
</form>

... and this is is the "Action" in the Controller ...

public ActionResult MyExample(int id, NameValueCollection things)
{
    // At this point, `things["abc"]` should equal `"blah"`
    return Content(string.Format("Things has {0} values.", things.Count));
}

Do I need to make my own custom model binder? Or am I just naming the input boxes incorrectly?


Solution

  • I don't think the default ASP.NET MVC3 model binder does this, so I've made the following helper class. It works, I just didn't want to make this if the DefaultModelBinder handles this already.

    I won't mark this as the answer for a while in hopes that someone will tell me how to get this to work correctly without a custom class. But for those of you who have the same need, here's the code.

    Global.asax.cs

    // Add this line in the Application_Start() method in the Global.asax.cs
    ModelBinders.Binders.DefaultBinder = new NameValueAwareModelBinder();
    

    Custom model binder

    using System.Collections.Specialized;
    using System.Web.Mvc;
    
    namespace Lil_Timmys_Example.Helpers
    {
        public class NameValueAwareModelBinder : DefaultModelBinder
        {
            public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
            {
                if (bindingContext.ModelMetadata.ModelType == typeof(NameValueCollection))
                {
                    var result = new NameValueCollection();
    
                    string prefix = bindingContext.ModelName + ".";
    
                    var queryString = controllerContext.HttpContext.Request.QueryString;
                    foreach (var key in queryString.AllKeys)
                    {
                        if (key.StartsWith(prefix))
                        {
                            result[key.Substring(prefix.Length)] = queryString.Get(key);
                        }
                    }
    
                    var form = controllerContext.HttpContext.Request.Form;
                    foreach (var key in form.AllKeys)
                    {
                        if (key.StartsWith(prefix))
                        {
                            result[key.Substring(prefix.Length)] = form.Get(key);
                        }
                    }
    
                    return result;
                }
                else
                {
                    return base.BindModel(controllerContext, bindingContext);
                }
            }
        }
    }