Experiments with ASP .NET MVC Model Binding

I am really becoming a fan of the ASP .NET MVC framework, it has a lot of flexibility and lends itself well to design patterns that I currently use.  I also like the emerging MVVm pattern that has really become widely used among RIA apps.  It functions well with MVC and SilverLight because it gives us the flexibility to use ORMs like Entity Framework and Linq2Sql without having to battle with the lost context problem.

I decided this weekend to sit down with MVC and play around with a feature that I really dig: Model Binding.  For the uninitiated, model binding in MVC offers developers a way to specify an entity as the argument to a function and based on data sent over via the request create the object for you.  The out of the box binder works great and will cover the vast majority of cases.  I decided to take this one step further and I wanted to be able to totally bind up my proxy classes (essentially the ViewModels) without having throw away properties that were just used for this purpose and without creating an inheritance hierarchy to allow subclass proxy objects to be used for the binding, offering essentially the same thing as option 1.  I wanted this to all happen inside the model binder, so it would be totally transparent.

My result was pretty good, but not perfect.  It relies on a dependency and therefore is not 100% transparent and standalone.  This is because of what we are doing, we need to abstract the set of the property we need, this could be done via an enforced rule that constructors for proxy entities must follow or have you entities inherit from a common interfaces to guarantee that you have such a setter.  I choose to go with the later and defined IEntity as such:

public interface IEntity
{
     int ID { get; set; }
     string Name { get; set; }
}

For our example, the Name setter will not be used, but it is there because it really makes sense that an Entity should provide a name for its object.

So very briefly, the view model pattern relies on the creation of entity classes which are translated from generated entities.  This ensures that extraneous information is not passed to the client which can happen frequently when you use generated code in an all-or-nothing scenario.  Furthermore, you can reuse these objects within each other and thus gain higher amounts of code reuse which usually translates to greater maintainability.  In this example we are going to be focusing on my SeriesEntity class, defined as such:

public class SeriesEntity : IEntity
{
     // simple data references
     public int SeriesId { get; set; }
     public string Name { get; set; }
     public bool IsActive { get; set; }

     // complex data references
     public IList Seasons { get; set; }
     public IList Genres { get; set; }
     public StudioEntity Studio { get; set; }
     
     #region IEntity Members
     public int ID
     {
          get
          {
               return SeriesId;
          }
          set
          {
               SeriesId = value;
          }
     }
     #endregion
}

The use of IList here is crucial to the solution I am going to propose, though you could make this any collection type, with the exception of an array.

One of the really neat things about MVC is that in addition to allowing you the ability to override the default binder, you can also specify a binder of a specific type, all you need to is define the binders dictionary in Global.asax.

protected void Application_Start()
{
     RegisterRoutes(RouteTable.Routes);
     RegisterModelBinders(ModelBinders.Binders);
}

private void RegisterModelBinders(
     ModelBinderDictionary binderDictionary)
{
     binderDictionary.DefaultBinder =
          new Binders.AnimeModelBinder();
     binderDictionary.Add(
          typeof(SeriesEntity), new SeriesModelBinder());
}

I am showing this as an example of what you can do with the binding at a type level.  We will be using the the DefaultModelBinder base class, so lets start with that class, firstly it must inherit from DefaultModelBinder.  This class provides a variety of virtual methods that you can override, the first one we will look at is BindProperty, this is my implementation:

protected override void BindProperty(
     ControllerContext controllerContext,
     ModelBindingContext bindingContext,
     PropertyDescriptor propertyDescriptor)
{
     // need name of the property we are trying to bind
     var propertyName = propertyDescriptor.Name;
 
     // find the property on the object we are binding
     var property = bindingContext.Model.GetType()
          .GetProperty(propertyName);
            
     if (property != null)
          property.SetValue(bindingContext.Model,
          GetObjectValueFromProperty(property,
               bindingContext.ValueProvider[propertyName]),
               null);
}

The BindProperty method is called each time a new property is discovered that could potentially be binded to.  You can control this action by specifying Exclude when defining the parameter list for your action, example:

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

public ActionResult Create([Bind(Exclude = "SeriesId,Seasons")]SeriesEntity series)

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

Notice the use of Exclude here, which ensures that the binder will not get SeriesId and Seasons to bind.  While our binder can handle it not being there, the lessens the load on the binder.

So lets understand the Binder, first we make a call into GetObjectValueFromProperty function which essentially determines if we need to do anything special with the values, if we dont we make a call to Convert.ChangeType:

return Convert.ChangeType(result.AttemptedValue, property.PropertyType);

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

I love this method, as it calls the underlying parsing logic to convert the value from (in this case) a string to whatever I desire.  It will fail, of course, if the value is not as expected, but this would happen with the normal binder as well.  So at this point, we can handle simple one-to-one arguments for value types now.  Lets add a means to work with complex objects.

At this point we need to introduce a dependency within our code, that is we need a way to construct our proxy entities and since these are generally custom based on the application, this is where IEntity comes into play.  Thus we create a method to generate an instance of a type that, using IEntity, we can set a value to:

private object GetInstanceWithId(int entityId, Type type)
{
     var constructor = type.GetConstructor(new Type[] { });
     var value = constructor.Invoke(null);

     ((IEntity) value).ID = entityId;
     return value;
}

Looking at this code there is one flaw and one major area of improvement.  The first one is this casting is unsafe, we have no idea if we can convert the type passed to IEntity and we are hardcoding IEntity rather then letting it being provided.  This is not that big of a deal as this is not code most developers working with this binder would ever see, but it is a hard dependency.  The best way to check this is before we call the function, we validate the the property type CAN be cast to IEntity, example:

private bool CanConvertToType(Type type, Type conversionType)
{
     var interfaces = type.GetInterfaces();
     return interfaces.Length > 0 &&
          interfaces.Select(t => t.Name)
               .Contains(conversionType.Name);
}

Simply pass the type of the property and the a type reference to the type we want to check the cast for and this method will tell you, though it only works for interfaces.

The final pieces to this is binding a collection of values.  One of the things we can do with HTML is send a comma delimited list of values from the view by naming controls with the same name.  This will form the basis for our collection binding.  The reason we use IList for our entities is so we can perform the binding and as I came to find, creating generic arrays is something .NET does not allow.  So the first step is to determine if the property we are looking at IS in fact a collection, we can reuse our CanConvertToType function and pass typeof(ICollection) as the conversionType parameter.

The next step is determining the actual type being held by the collection, so we can determine if we can make objects that can be stored in this collection.  We can make a simple call to get this information:

Type elementType = property.PropertyType.GetGenericArguments()[0];

Because this is a generic, there will always be at least one argument, and because its an IList we know that the first argument is the contained type.  Once we confirm that this type can be used with IEntity we can break apart incoming value and create the IList reference we are going to return.  These two lines handle these tasks:

var valueArray = result.AttemptedValue.Split(',');
var valueList = (IList)Activator.CreateInstance(
     (typeof(List).MakeGenericType(elementType)));

This code is fairly self explanation, but to reiterate, create a string array from the values coming from the form submit, and use the Activator class to create an instance of List that we then cast to an IList.

Finally, we iterate over the string array, and call our GetInstanceWithId function to generate an instance of the underlying type with the ID property set via the abstraction provided by IEntity.

So there you have it, with some very clean reflection we can build an entire proxy entity via the custom model binder, this sets us up for validation and then actions within the controller method.  This also centralizes how we set the values for our objects helping with maintainability.  Finally, if the case arises that a totally different binder is needed for a certain type, MVC does allow type level model binding configuration which allows MVC to use a certain binder for a certain type.  I wonder if you can specify a interface type thus you can declare an model binder for a set of entities.

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

I hope to add validation to this example for the next entry.

http://cid-18f9d8896416d2fb.skydrive.live.com/embedicon.aspx/BlogFiles/AMCode.zip

Advertisements

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