I had an ASP.NET MVC project with URLs like this:

Customers/customerX (the details page of Customer ‘customerX’)

…defined by this route in RouteTable.Routes:

RouteTable.Routes.MapRoute(
    "CustomerDetails",
    "Customers/{customerName}",
    new { controller = "Customers", action = "Details" });

…and leading to this action method:

CustomersController.Details(string customerName);

I then found the appropriate Customer object in the action method, and creates a ViewModel for the Details View.

The thing was, I also had an Edit action and a Delete action, and sometimes in other Controllers I had to pass in Customer names and get hold of other Customer objects, and it didn’t end up very DRY. So today I did a spot of refactoring. I updated my action method to:

CustomersController.Details(Customer customer);

…and implemented a custom IValueProvider to provide the Customer object.

A spot of background, just in case: when the ASP.NET MVC binding system is trying to populate the parameters for an action method it wants to execute, it requests them in turn from each of the IValueProvider objects in the static ValueProviderFactories.Factories collection. In my case because the CustomerDetails route had a token named customerName, but the action method was expecting a parameter named customer, the standard IValueProviders couldn’t supply a value for the parameter, and my custom IValueProvider was called into play.

The IValueProvider I wrote looked a bit like this:

using System.Globalization;
using System.Web.Mvc;

public class DomainObjectValueProvider : IValueProvider
{
    private readonly ControllerContext _context;

    public DomainObjectValueProvider(ControllerContext context)
    {
        this._context = context;
    }

    public bool ContainsPrefix(string prefix)
    {
        return prefix == "customer";
    }

    public ValueProviderResult GetValue(string key)
    {
        string customerName = 
            (string)this._context.RouteData.Values["customerName"];

        Customer customer = 
            CustomerService.FindCustomerByName(customerName);

        return new ValueProviderResult(
            customer,
            customerName,
            CultureInfo.CurrentUICulture);
    }
}

The CustomerService there is a static Service Layer object which provides services for the Customer entity. In order to add my DomainObjectValueProvider to the Value Providers system I wrote a ValueProviderFactory like this:

public class DomainObjectValueProviderFactory : 
    ValueProviderFactory
{
    public override IValueProvider GetValueProvider(
        ControllerContext controllerContext)
    {
        return new DomainObjectValueProvider(controllerContext);
    }
}

…and plugged it into the MVC system in Global.asax’s Application_Start() with this:

ValueProviderFactories.Factories.Add(new DomainObjectValueProviderFactory());

And it works a treat! ASP.NET MVC rules :)