Custom data binding with MonoRail 1.0

Yesterday I had a solution where I wanted to receive a domain entity as an action parameter, similar in conceptĀ  to the built-in ActiveRecord data-binding. In my situation though I wanted to use a slug to identify the entity. My URL’s look like this:

/news/slug-goes-here

which I want to result in calling into:

public class NewsController
{
    public void Index(NewsItem item)
    {
    }
}

The first step here is to setup routing to suit. We currently just use an XML file to hold our routing rules, so this route entry just looks like

 <rule> 
    <pattern>^/news/([^/]+)$</pattern>
    <replace>/news/index?item=$1</replace>
  </rule>

With this in place, our Index action will receive item as an argument. All that’s left to do is tell MonoRail how to bind the item argument to our NewsItem type. There are two approaches available here: you can derive a class From DataBinder and pass it to the SmartDispatchController contructor, or you can create a new attribute that implements IParameterBinder and attach it to the action’s parameter. Unfortunately in MonoRail 1.0 the DataBinder class from what I can tell is really only extensible via inheritance, so I have used the attribute approach.

MonoRail’s source is a real help here, since you can inspect DataBindAttribute.cs to see what the class should be doing. Here is the implementation I ended up with:

public class NewsItemAttribute : Attribute, IParameterBinder
	{
		public int CalculateParamPoints(SmartDispatcherController controller, ParameterInfo parameterInfo)
		{
                        CompositeNode node = controller.ObtainParamsNode(From);
                        IDataBinder binder = CreateBinder();
                        return binder.CanBindObject(typeof(string), prefix, node) ? 10 : 0;
		}
 
		public object Bind(SmartDispatcherController controller, ParameterInfo parameterInfo)
		{
                        IDataBinder binder = CreateBinder();
                        ConfigureValidator(controller, binder);
                        CompositeNode node = controller.ObtainParamsNode(From);
                        var slug = (string) binder.BindObject(typeof(string), prefix, exclude, allow, node);
			var instance = IoC.Resolve<INewsItemRepository>().GetBySlug(slug);
                        BindInstanceErrors(controller, binder, instance);
                        PopulateValidatorErrorSummary(controller, binder, instance);
                        return instance;
		}
	}

The only line of note here is the IoC.Resolve() call. To my knowledge you cant use DI with attributes, so we’re stuck with using the service locator approach. The rest of the code is straight from the DataBindAttribute source.

I’ve not yet gotten the chance to use ASP.NET MVC in a project, but from what I’ve read the ModelBinder facility appears a much cleaner approach to this problem than that possible with MonoRail 1.0.

Leave a Reply