Elegant, Opt-in Model Binding to Persistence Entities in ASP.NET MVC

Elegant, Opt-in Model Binding to Persistence Entities in ASP.NET MVC

ASP.NET MVC is awesome. I really enjoy programming with it. Two features that make it great are its “plugability” and extensibility; which is good — because I feel the default model binder can be improved in certain scenarios. One such scenario is when you want to get persistent entities from a repository via your ORM tool. Rather than taking an ID and doing a query — just model bind straight to it.

Negatives to this approach are that it could cause security concerns if you have nested persistent entities automatically loaded based on form values. So ideally, you want to opt-in to this model binder — but you don’t want the opt-in to be a verbose attribute declaration (more on this later).

*For me, the ideal is an elegant, but obvious declaration. And for me, I have this ideal. Read on and it can be yours too.*Shut Up and Code

Rather than just showing the implementation, I’m going to show you a working example. To do this I’ll be using an MVC 3 web application and the new MvcScaffolding — awesomely demonstrated by the genius Steven Sanderson here and here .

If you want to follow this example, these features mean you only need spare 5 minutes of your time.Setup

Let’s do it:

  1. Create a new MVC 3 web application:

a.  Razor view engine

b.  Internet application

  1. Create a domain model

a. Add a “HealthFood” class to the “Models” folder — like so

public class HealthFood

{

public virtual int Id { get; set; }

public virtual string Name { get; set; }

}

Above: HealthFood — our domain model (virtual properties for ORM)

  1. NuGet: Install-Package MvcScaffolding

  2. NuGet: Scaffold Controller HealthFood

  3. Run the application after adding a link to the HealthFood controller. Then add a few entries

  • @Html.ActionLink(“Home”, “Index”, “Home”)
  • @Html.ActionLink(“About”, “About”, “Home”)
  • @Html.ActionLink(“Health Foods”, “Index”, “HealthFood”)

Above: A link to the HealthFood controller in _Layout.cshtml

Above: A few HealthFoods added to the systemRain-Check

With a system all set, a few entries added — let’s check what the default behaviour is for viewing an object — the edit action.

public ActionResult Edit(int id)

{

HealthFood p = context.HealthFoods.Single(x => x.Id == id);

return View(p);

}

So, what happens is that we take the Id from the posted form values and query our “repository”. We then return the HealthFood to the view.

What if I want to model bind directly to the HealthFood – what if I’ve got many examples of this scenario — I can save a lot of code and make things DRY — and even testable — if I had a reason for testing controllers (rarely that I ever do).AutoLoad Model Binder

Creating a model binder to retrieve persistent entities is actually fairly easy in this scenario. I’ve avoided the bullet of generic repositories, but I will be adding this feature soon.

Here’s the model binder which I’ve added to a new folder in the project called “ModelBinders”.

public class AutoLoadModelBinder : DefaultModelBinder

{

public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)

{

var typeBeingBoundTo = bindingContext.ModelType;

var valueKey = bindingContext.ModelName;

var rawValue = bindingContext.ValueProvider.GetValue(valueKey);

var entityId = Convert.ToInt32(rawValue.RawValue);

//TODO — tied to implementation details — I know

var dataContext = new AutoLoadModelBinderExampleContext();

var allItemsOfType = dataContext.Set(typeBeingBoundTo);

var entityBeingBoundTo = allItemsOfType.Find(new object[] {entityId});

return entityBeingBoundTo;

}

}

Remember the requirement where I wanted an opt-in scheme – so this model binder doesn’t apply by default. Well, to make it work this way, we apply it to our action parameter like so:

public ViewResult Details([ModelBinder((typeof(AutoLoadModelBinder)))]HealthFood id)

{

return View(id);

}Testing Out the AutoLoad Model Binder

If you now run the app again and navigate to the details page for a HealthFood — it works. It still works. Hence, the model binder works, and it’s an opt-in scheme….

…but the declaration is ugly. It’s clunky — it makes the controller look horrible if I’m honest. What can we do about that?

Well, I figured, I could just extend the ModelBinder attribute, rename it to AutoLoad and hard code the AutoLoadModelBinder into its constructor. But ReSharper doesn’t like that. The compiler doesn’t like that…I don’t’ like that.

So I check out the metadata for the ModelBinder class and I see that this cheeky boy, whilst sealed and having none of it, inherits the “CustomModelBinderAttribute” class. Awesome — game on.Improving the AutoLoad ModelBinder’s Usage

With this new information — the “CustomModelBinderAttribute”

  • a class I never knew existed — I created the following:

public class AutoLoadAttribute : CustomModelBinderAttribute

{

public override IModelBinder GetBinder()

{

return new AutoLoadModelBinder();

}

}

I put this class in the “ModelBinders” folder, but you can stick it anywhere you like (no offence).

All it does is ensure that the model binder used is my AutoLoadModelBinder.Testing out the AutoLoad Attribute

Now we have the “AutoLoad” attribute. We can simplify this:

([ModelBinder(typeof(AutoLoadModelBinder))] HealthFood id)

…to this:

([AutoLoad]HealthFood id)Sanity Check

So let’s test it out. What happens when we head to the details page for a HealthFood now?

**Above: **AutoLoad fantastico — elegant and effective

As expected (read: “hoped”) it still works.Conclusion

Model binding to persistence entities (auto loading as I seem to be calling it) can help to make your controllers look a lot cleaner and be a lot more DRY — by not repeating calls to a repository — and even removing references to repositories altogether. If you have attribute-based web transactions for your ORM tool’s sessions and transactions, then those controllers really can go on a diet.

But let’s be aware that in this example I tied my model binder to the entity framework DbContext. What would be awesome is to make use of MVC 3’s DependencyResolver and the repository pattern to make this binder ORM-agnostic. So whatever repository the DependecyResolver registers, it is that which get’s queried.

What about nested entities, many-to-many relationships in scenarios where we create and update records? The attribute and the model binder still have a lot to incorporate before I’m happy.

I will be augmenting and improving the behaviour of the [AutoLoad] in coming posts.

Also, did you notice how cool the MvcScaffolding was in this scenario and how it plays nicely with NuGet? Keep up the good work Steven Sanderson and the rest of the Web Platform Team.