Monday, November 28, 2011

Learning MVC: Model Binders

A model binder is a powerful concept in MVC. Implementing a model binder allows to pass an object to the controller methods automatically by the framework. In my case, I was looking for a way to get the id of the currently logged in user. This has to be checked any time user-specific data is accessed - which is, essentially, always. Placing extra code into each and every method of every controller does not look like a good solution. Here's how a model binder can be used:

First, implement IModelBinder. I only need it to return an int, and I get this int from my Users table.

public class IUserModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
int userID = 0;
if (controllerContext == null)
{
throw new ArgumentNullException("controllerContext");
}
if (bindingContext == null)
{
throw new ArgumentNullException("bindingContext");
}
if (Membership.GetUser() != null)
{
MembershipUser member = Membership.GetUser();
string guid = member.ProviderUserKey.ToString();

using (modelGTDContainer db = new modelGTDContainer())
{
userID = db.Users.Single(t => t.UserGUID == guid).UserID;
}
}
return userID;
}
}

Next, I need to register the model binder when the application starts - in the global.asax.cs I only need to add one line to Application_Start

protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();

RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
ModelBinders.Binders[typeof(int)] = new IUserModelBinder();
}

And finally, I add an int parameter to any controller method that needs a userID to be passed. And, by the magics of MVC, it is there! This is a huge saving of work by adding just a few lines of code.

//
// GET: /InBasket/
[Authorize]
public ViewResult Index(int userID)
{
var inbasket = repository.FindUserInBasketItems(userID);
return View(inbasket.ToList());
}

What's bad - I have to hit a database every time I access any data to get the user ID, and then hit a database again when I select the actual data for the user. How to fix it? Well, one way is to use the user Guid as a primary key for the Users table. However, it is a string, not an int. Performance may suffer anyway, and the indexing will be affected. But, thanks to a model binder, if I want to change this in the future, it will only have to be done in one place.

References:

IPrincipal (User) ModelBinder in ASP.NET MVC for easier testing

Multi User App with MVC3, ASP.NET Membership - User Authentication / Data Separation by . Also posted on my website

No comments: