SingingEels : Development Community & Resource

Login

Articles

  • ADO.NET (2)
  • ASP.NET (36)
  • LINQ (4)
  • Security (2)
  • Silverlight (3)
  • SQL (7)
  • Standards (5)
  • WCF (2)

Syndication

  • Articles RSS
  • Blogs RSS

Contribute

  • Our Authors List
  • Member Sign-Up
  • Suggestions Box
ASP.NET Hosting with MS SQL 2008 – Click Here!

Model Binders in ASP.NET MVC - Part 2

(Oct 02 2008 - 06:44:49 AM by Timothy Khouri) - [print article]
ASP.NET Hosting with MS SQL 2008 – Click Here!

The previous article on ModelBinders gave a basic introduction, showed a few code samples, and showed a creative usage of ModelBinders. However, some questions arose, some claims were challenged (particularly about comparing ModelBinders to the WebForms ViewState) and some controversial code needs further flushing out. This article will do just that.

To begin with, we need to define the list of items that we are going to resolve in this article, as well as the list of rules/methods we are going to use to determine the worth and quality of these items. By the end of this article we will resolve:

  1. Does the use of ModelBinders hinder the URL in anyway? Can we still achieve "RESTful" URLs if we use ModelBinders?
  2. In what ways do ASP.NET MVC ModelBinders compare to the ViewState feature of ASP.NET WebForms?
  3. We have demonstrated that we *can* use ModelBinders to recreate stateful objects from a database, but *should* we ever do that? Are there any benefits? Are we violating cosmic laws?

Examining the Issues

The first item may seem odd as ModelBinders have nothing to do with your URL, but for some reason multiple comments were made (on a previous article) expressing concerns for such. The second question is good to examine because MVC doesn't have the ability to use the ViewState, so we'll see how to cover some web-development-challenges with ModelBinders that we might have solved with the ViewState in the past. The final topic to cover is probably the most important, and is definitely the most controversial. As such, we will use the following rules to determine a good solution:

  1. Does the solution fit one or both of the main pillars of the MVC design pattern - Testability and Separating Concerns?
  2. Does the solution provide practical benefits to the developer?
  3. And finally, since the responsibility of communicating with the "Model" falls to the "Controller", are we violating MVC by using ModelBinders in the process of retrieving objects from the database?

We will include a full sample project for you to download and play with yourself (written in Visual Studio 2008, compiled for ASP.NET MVC Preview 5). In order to keep the article focused on the issues and not necessarily the code, most code samples in the article will be trimmed.

ModelBinders and RESTful URLs

Because ModelBinders are not used by either the Routing Engine or ASP.NET MVC when creating links, they really play no part in the making or breaking RESTful URLs. This misconception may have arisen because the previous article showed a query string variable ("?targetCustomer=blah") being passed to the ModelBinder. This may have made it seem that the URL was dependant on the ModelBinder, or that the ModelBinder was relying on the URL. This is not the case at all. ASP.NET MVC was passing the "targetCustomer" property to the ModelBinder only because there was a matching entry in the "RouteData" collection (which in this case happened to come from the URL).

The URL "/Home/Details?targetCustomer=12345" could have been expressed as "/Customers/Details/12345" just by changing how we setup our routes. So to clarify the point again: ModelBinders do not have anything to do with the forming of URLs in an MVC application. To achieve RESTful URLs in an ASP.NET application, you would use the ASP.NET Routing Engine.

MVC ModelBinders Compared to WebForms ViewState

Since web applications are built on a disconnected framework, web developers who want to simulate a stateful web application have to overcome the challenge of rebuilding the 'state' of the application upon the next post to the server. In ASP.NET WebForms, the ViewState handled this responsibility. One common scenario that was handled with the ViewState is that of building a dynamic amount of input fields back into "objects" (TextBoxes, DropDownLists, etc) on the server.

How would ModelBinders handle that scenario? The answer to that is fairly simple, and we still achieve the desired result of transforming the HTTP post into objects on the server. Let's assume that we have a form that allows users to enter multiple addresses to associate with their account. The form is dynamic and allows the user to keep adding, or deleting addresses in JavaScript, but ultimately we need to post this variable amount of input fields to the server and rebuild them as objects. Here is what we could do with ModelBinders:

public class MultiAddressBinder : DefaultModelBinder
{
   public override object GetValue(ControllerContext ctx,
      string modelName, Type modelType, ModelStateDictionary state)
   {
       if (ctx.HttpContext.Request["Line1"] == null)
       {
           return new Address[0];
       }

       string[] line1s = ctx.HttpContext.Request.Form.GetValues("Line1");
       string[] line2s = ctx.HttpContext.Request.Form.GetValues("Line2");
       string[] cities = ctx.HttpContext.Request.Form.GetValues("City");
       string[] states = ctx.HttpContext.Request.Form.GetValues("State");
       string[] zips = ctx.HttpContext.Request.Form.GetValues("Zip");

       List<Address> addresses = new List<Address>();

       for (int i = 0; i < line1s.Length; i++)
       {
           addresses.Add(new Address(line1s[i], line2s[i], cities[i], states[i], zips[i]));
       }

       return addresses.ToArray();
   }
}

Then, to register our ModelBinder so that all "Address[]" parameters for my ActionMethods will know how to build themselves, we'll simply add this line to our Global.asax file:

protected void Application_Start()
{
   ModelBinders.Binders.Add(typeof(Address[]),
       new MultiAddressBinder());
}

The end result will look like this:

Dynamic address fields in an ASP.NET MVC application Dynamic address fields deserialized by ASP.NET MVC ModelBinders

As is simply demonstrated, ModelBinders can be used to solve many of the same challenges that the ViewState has solved for traditional ASP.NET. Does this mean that ModelBinders and the ViewState are synonymous? By no means! The technologies are different, their design and purpose are different. The similarities are in their ability to bring a more object-oriented way of programming to stateless web applications.

Restoring Stateful Objects with ModelBinders

The final section of this article will deal with the controversial topic of rebuilding an object that lives in a database with a ModelBinder. It seems that rebuilding an object from anywhere else is "fine", but the moment we introduce a stateful object, people tend to get very nervous. Are we violating cosmic laws? Let's see.

In the Model-View-Controller (MVC) design pattern, the responsibility of communicating with the Model (the database) falls to the Controller. ModelBinders have nothing to do with the MVC design pattern, but rather they are simply a 'helper feature' of "ASP.NET MVC" to provide a more object-oriented (and therefore more testable) approach to coding your ActionMethods.

At the outset of this article, we mentioned that we will judge the worthiness of this solution based on criteria mentioned above. We have just hit the first point which will be flushed out a bit more later - Improved Testability.

In the previous article we inherited from the DefaultModelBinder class, and implemented our functionality that way. In this next example we are going to add the IModelBinder attribute to our Controller itself. This way, we can still feel good about "the Controller accesses the Model" when it comes to rebuilding stateful objects, but we still get the concrete benefits that ModelBinders provide. Here's our code (abbreviated here, full code in downloadable solution at the end):

// In the Global.asax...
ModelBinders.Add(typeof(Customer),
   new StatefulObjectBinder());

// Our "StatefulObjectBinder" class...

public class StatefulObjectBinder : DefaultModelBinder
{
   public override object GetValue(ControllerContext ctx,
      string modelName, Type modelType, ModelStateDictionary state)
   {
       object result = null;

       if (ctx.Controller is IModelBinder)
       {
           result = ((IModelBinder)ctx.Controller).GetValue(ctx,
               modelName, modelType, state);
       }

       return result;
   }
}

Now that this simple "middle-man" instance has been built, our Controller can retrieve the object from the database (as it normally would have anyway). So, you may be wondering: "why the song and dance?" (Meaning, why did we do all this work... what benefits do we get?)

Here are the direct benefits that we achieve from the above methodology:

  1. Since we can now code our ActionMethods to take concrete objects (instead of parsing the HttpRequest for them), we are able to write unit tests *much* easier. Take for example the simple unit test that says "if a customer was not found, redirect to the customer search page." If you didn't use a ModelBinder, you would have to "mock" up your database calls, and fake it so that passing in one ID will return a customer, then fake it so that passing in a different ID will return null, *then* you can finally test your logic. However, using a ModelBinder as demonstrated in this article, you can test the logic with absolutely zero mocking, just by calling "Display(new Customer())" and "Display(null)" - amazing!
  2. We are holding true to the Single Responsibility Principal and the Separating Concerns patterns much stronger. If you think about it, the method "DisplayCustomer(int customerID)" has the responsibility to do two things: 1) get the customer, then 2) display it. Whereas using ModelBinders lets us make the method "DisplayCustomer(Customer c)" - now we only have one task: display it - beautiful!
  3. Even developers who do not use TDD or Separating Concerns will benefit in this way: Because you can have dozens of methods that require restoring your "Customer" object (or any other object) from the database, you would have to code this logic in a single place and call it in every one of those methods. Using ModelBinders lets you skip one of those steps. You still have to code the logic, but ASP.NET MVC will automatically call it for you because you registered the type "Customer" with your ModelBinder one time - awesome!

In Conclusion

The purpose of this article was to show that even though ASP.NET MVC is realtively young, it has some powerful and compelling features. Just this one topic alone of ModelBinders has contributed much buzz, including two articles here on SingingEels which doesn't happen often for one feature.

In a talk that I recently watched online, Ken Robinson said that "if you're not prepared to be wrong, you'll never come up with anything original." If nothing else, I hope that my risky usage of new technologies encourages you to experiment with code some more yourself.

Here is the full solution (Visual Studio 2008 SP1, MVC Preview 5). Enjoy: SingingEels_MVC_ModelBindersPart2.zip

  • Oct 18 2008 - 03:17:55 PM amajid

    How/when/where would you validate the values you are assigning to the properties?

  • Oct 18 2008 - 07:56:09 PM Timothy Khouri

    The MVC answer seems to be to put the validation on your Model itself... the new beta that just came out seems to have some really cool functionality on that front with the "TryUpdateModel" method.

    Also, what I'm doing in the "MultiAddressBinder" example above is basically out of the box functionality now with the ComplexBinder that comes from Microsoft. The beta is looking great.

    Thanks,

    -Timothy

You must be logged in to add comments. If you have not already done so, you can create an account here. If you already are a member, you first need to login before you can comment.

Developer / Architect / Author

People to Follow

Experts in the categories related to this article.

  • Jonathan Carter

Related Blogs

These are the most recent blog posts related to this article.

  • A Change to the MVC "ActionSelectionAttribute"?
  • How to Handle "Side Content" in ASP.NET MVC
  • LINQ to SQL - Am I Hitting The Database?
  • ASP.NET MVC - Issue with CSS Class Name on ActionLinks
  • Site Updates, Bug Fixes and Syndication in .NET 3.5

Related Ads

SingingEels.com as of Jan 05 2009 - 09:12:26 PM - (0.0468738)