Assigning responsibility in ASP.NET MVC
I've been working on a more generic implementation of Maarten Balliauw's Linq-to-SQL Model Binder. Maarten's implementation bothers me because it builds up a query as a string and uses MyDataContext.ExecuteQuery to get the result as a non-generic IEnumerable. I don't find this implementation particularly elegant, and in particular Maarten notes that as it stands his code is vulnerable to SQL Injection.
A similar post by Timothy Khouri (using ToString() to build up a base-64 representation of an object to pass between requests, and a matching ModelBuilder to re-build the object on the other side) was criticised on the grounds that "you wouldn't ever want any database interaction going on during the binding process".
This got me thinking a little bit about how responsibility is divided up in ASP.NET MVC.
The ModelBinder in ASP.NET MVC sits between the HTTP Request and the Action method. Its normal purpose is to build complex types out of form input - so your UserController.Create method recieves a User domain object instead of an object representing the Form.
This has two advantages. First, you no longer need to mock-up a HTTP Form in order to unit test your Action method (and you can unit test your ModelBinder with a mock HTTP form seperately). Second, since the responsibility of translating the POST into a domain object is pushed down into the ModelBinder, you don't need to repeat yourself every time you need to generate a User object from a form POST.
In my previous post on ASP.NET MVC I criticised the framework's testability on the grounds that you "cannot" unit test an Action method that hits the database (such a test is an integration test by definition). With the new ModelBinder framework in the Release Candidate I found that it was possible to circumvent this limitation - in short by using the ModelBinder to translate a primary key into a matching domain object, instead of performing the query in the body of the method. The ModelBinder picks up the id out of a HTTP Request (such as "User/Details/1"), and your Action method recieves a User object.
This gives you the same two advantages we have above. You no longer need a database to unit test your Action method (and you can perform integration testing on your ModelBinder seperately). You also no longer need to repeat the code in your Action method which was previously performing this function. We also have a third advantage - your Action method is now database agnostic. You can modify your data context (or replace it with a different persistance paradigm), and your Action methods do not need to change.
For me this far outweighs any concerns about performing database operations inside the model binder. Those operations didn't belong in the Action method in the first place.
I'll be sharing my implementation of a generic Linq-to-SQL ModelBinder with you in a subsequent post.
Post new comment