Thursday, July 30, 2009

NHibernate and ASP.NET - UpdateModel problem

Using NHibernate as a persistence layer is fun. Not only does it hide the complexity of a database completly but also allows one to use an object model instead of playing around with database query results.
In the case of NHibernate it's even better as it provides session management for web applications so with minimum effort you can just use lazy loading from within your views very easily.

At some point though problems will emerge. One of them is using ISession.Get without specifying the type (which might be unknown at the time of querying or might depend on the actual request from the page) with Controller.UpdateModel method. Here's how it goes:

1. UpdateModel is a generic method that expects the actual type that's being mapped to as the generic parameter.
2. It then passes the processing to TryUpdateModel which is a generic method as well.
3. TryUpdateModel takes this generic parameter, retrieves it's type metadata and uses it (among other things) to fill in a ModelBindingContext structure.
4. This ModelBindingContext is then passed on to IModelBinder.BindModel method.

It's all nice and dandy if we can specify the model's type. But if we don't know the actual type it results in processing of a model of type Object and none of the properties will get mapped. Because of the fact that there's no specialized model binder for type Object it gets passed on to the DefaultModelBinder implementation held by ModelBinders.Binders.DefaultBinder.

There's however an extremly nice and easy solution to this. We're going to use the decorator pattern to wrap up the current default model binder and provide it with the necessary information from NHibernate's ISessionFactory.

Here's an example implementation of such a model binder that can provide this additional implementation. I've called it NHibernateAwareModelBinder.


public class NHibernateAwareModelBinder : IModelBinder
{
private readonly ICollection mappedClasses;
private readonly IModelBinder defaultModelBinder;

public NHibernateAwareModelBinder(
ICollection mappedClasses,
IModelBinder defaultModelBinder)
{
this.mappedClasses = mappedClasses;
this.defaultModelBinder = defaultModelBinder;
}

public object BindModel(
ControllerContext controllerContext,
ModelBindingContext bindingContext)
{
if (bindingContext.Model != null)
{
string modelType = bindingContext.Model
.GetType().FullName;
if (mappedClasses.Contains(modelType))
{
bindingContext.ModelType =
Type.GetType(modelType);
}
}
return defaultModelBinder.BindModel(
controllerContext,
bindingContext);
}
}

And here's how you'd use this class in your Application_Start event:

ModelBinders.Binders.DefaultBinder =
new NHibernateAwareModelBinder(
SessionFactory.GetAllClassMetadata().Keys,
ModelBinders.Binders.DefaultBinder);

As you can see we're using the current default model binder as the actual binder but we're decorating it's behavior with type resolution. Nice, isn't it?

Have fun!

2 comments:

thWelly said...

if (mappedClasses.Contains(modelType))


This part I can't compile. It mean there is no method called Contains in ICollection.

Maybe a issue because I use NHibernate 3?

Matthias Hryniszak said...

Man that was like 3 years ago :) It's perfectly understandable that the API has changed over that time.