Sie sind auf Seite 1von 1

A Generic LINQ-to-SQL ModelBinder for ASP.

NET MVC
In my previous post, Assigning responsibility in ASP.NET MVC I discussed using ASP.NET MVC's ModelBinder framework to perform SELECT operations against the database. Using this technique, your Action methods (e.g. Products/Details/1) recieve a domain object (Product p) instead of an id (int id). This makes your Action methods more database agnostic, easier to unit test, and saves you repeating your database select inside every Action method that needs to convert an id into a domain object. In this post, I describe an implementation of a generic ModelBinder to select domain objects from any Linq-to-SQL datacontext. While researching the ASP.NET MVC ModelBinder framework, I came across Maarten Balliauw's blog post Creatng a generic Linq-to-SQL ModelBinder for the ASP.NET MVC framework. Maarten uses dataContext.ExecuteQuery to execute a generated SQL query against the datacontext and replace the Action method's integer id parameter with a matching domain object. This caught my attention because it addressed one of my previous criticisms of the ASP.NET MVC framework specifically the difficulty of unit-testing presentation logic which is tangled up with database queries. After deciding that it was appropriate to perform this database operation inside the ModelBinder (rather than inside the body of the Action method), I decided to create a new implementation of the Linq-to-SQL ModelBinder - using Linq-based queries rather than dataContext.ExecuteQuery to hit the database (for elegance, and also because as Maarten notes dataContext.ExecuteQuery can be vulnerable to SQL injection). The key transformation we are performing is:
dataContext.Table.FirstOrDefault(e => e.id == someIdWeHave);

This is the code that we would have to repeat inside our Action methods - and the code we would have to use some mockup framework to replace in order to effectively unit-test our presentation logic. This selects a table from our DataContext, and then selects the first row in that table with an id (primary key) matching the ID the user has provided (Products/Details/1). We can make this more generic by delegating the specifics out to external code:
// C is the type of the Data Context (MyDataContext) // T is the type of the Domain Object we are selecting (Product) // K is the type of the Primary Key of the Domain Object (int) // These three values can be set elsewhere Func<C, System.Data.Linq.Table<T>> tableSelector = (c => c.Products); Func<K, Func<T, bool>> entitySelector = (i => (p => p.id == i)); K id = 1; C context = new C(); return tableSelector(context).FirstOrDefault(entitySelector(id));

I've attached a demo implementation of this code as an ASP.NET MVC ModelBinder below. You register it in Global.asax like this:
ModelBinders.Binders.RegisterLinqModelBinder<MyDataContext, Product, int>( (c => c.Products), (i => (p => p.id == i)), "id");

The last parameter there ("id") tells the ModelBinder what RouteValue to look for in the request (in this case "id" to match {controller}/{action}/{id} for Products/Details/1). I don't want to go through every tiny detail of the implementation, so I'll just direct you to download my example. It uses VS 2008 with Linq-to-SQL, .NET 3.5 SP1 and the ASP.NET MVC Release Candidate. Have fun with it. Feel free to use it if you like, but I'd appreciate it if you dropped me an e-mail or left a comment here.

Das könnte Ihnen auch gefallen