Use ModelBinder to Generically Bind Complex Types

[Update: Simone brought my attention to the fact that ComplexModelBinder which comes with the framework does exactly that.  You can find more info here]

ASP.NET MVC Preview 5 introduce the ModelBinder attribute that can be used to decorate a complex type in an Action.  This allows us to have actions that look like this

public ActionResult Create([ModelBinder(typeof(GenericBinder))] ContactList myList)

 

Instead of this:

public ActionResult Create(string name, string description)

 

The problem is that you have to build a Binder for every complex type you want to use as a parameter.  For example, Maarten Balliauw created a model binder on his blog and it looks like this:

public class ContactBinder : IModelBinder
{
    #region IModelBinder Members
    public object GetValue(ControllerContext controllerContext, 
                 string modelName, Type modelType, 
                 ModelStateDictionary modelState)
    {
        if (modelType == typeof(Contact))
        {
            return new Contact
            {
                Name = controllerContext.HttpContext.Request.Form["name"] ?? "",
                Email = controllerContext.HttpContext.Request.Form["email"] ?? "",
                Message = controllerContext.HttpContext.Request.Form["message"] ?? ""
            };
        }
        return null;
    }
    #endregion
}

 

Now that is a lot of typing and because I am lazy, I decided to create a generic binder that uses reflection and can work with all my complex types. 

Note: By generic I mean common – it has nothing to do with .net Generics

Also note that this will only work if you follow these conventions:

  1. The html field name must match the property name
  2. User lower case names for the html fields
  3. You don’t have to user lower case on your model properties

Here is the very rough and untested Generic Binder:

class GenericBinder : IModelBinder
{
    public object GetValue(ControllerContext controllerContext, 
                            string modelName, Type modelType, 
                            ModelStateDictionary modelState)
    {
        var instance = Activator.CreateInstance(modelType);
        foreach (var prop in modelType.GetProperties())
        {
            prop.SetValue(instance, 
                    controllerContext.HttpContext.Request
                                    .Form[prop.Name.ToLower()], 
                    null);
        }
        return instance;
    }
}

 

If you find any bugs or have a better implementation, please share.

Advertisements

0 thoughts on “Use ModelBinder to Generically Bind Complex Types

  1. The ComplexModelBinder doesn't make much sense in his post.How do you use it? Why the need to 'register' it.Seems to me if you pass a complex type as a parameter, it would map the form variables to the type with reflection – and only if you need to have more complex mapping would you create a specific binder…

    Like

  2. Well how about this:I exposed the protected UpdateModel in my controller: public void UpdateModel(BusinessSavingsRequest bsr, string[] keys) { base.UpdateModel(bsr, keys); }and then call it from my binder: public object GetValue(ControllerContext controllerContext, string modelName, Type modelType, ModelStateDictionary modelState) { BusinessSavingsRequest bsr = new BusinessSavingsRequest(); string[] properties = typeof(BusinessSavingsRequest).GetProperties().Select(p => p.Name).ToArray(); ((MvcApplication.Controllers.BusinessSavingsController)controllerContext.Controller).UpdateModel(bsr, properties); return bsr; }Perhaps if this is all it needed to do I wouldnt have a custom binder for the type.

    Like

  3. Well how about this:I exposed the protected UpdateModel in my controller: public void UpdateModel(BusinessSavingsRequest bsr, string[] keys) { base.UpdateModel(bsr, keys); }and then call it from my binder: public object GetValue(ControllerContext controllerContext, string modelName, Type modelType, ModelStateDictionary modelState) { BusinessSavingsRequest bsr = new BusinessSavingsRequest(); string[] properties = typeof(BusinessSavingsRequest).GetProperties().Select(p => p.Name).ToArray(); ((MvcApplication.Controllers.BusinessSavingsController)controllerContext.Controller).UpdateModel(bsr, properties); return bsr; }Perhaps if this is all it needed to do I wouldnt have a custom binder for the type.

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s