OdeToCode IC Logo

Lower Case URLs and ASP.NET MVC

Thursday, October 1, 2009

It sounds like an easy question -

“How do I make my ASP.NET MVC application generate lowercase URLs?”

Using lowercase URLs is an SEO best practice, and something you can easily enforce with a tool like URLRewrite. But, the action links generated by the MVC framework generally contain upper case letters (since controller and action names are generally PascalCased).

First, let me say that Graham O’Neale and Nick Berardi have already solved this problem in a foolproof manner. What I am trying below is a little bit easier, but does have one problem if you rely on route names. We always specify route names when using the MapRoute extension methods, but in actuality these names are rarely used to generate URLs in an MVC application. Instead, we generate URLs from controller and action parameters.

Nick and Graham both create a class derived from Route to lowercase the URLs returned by GetVirtualPath. Unfortunately, this means you can’t use the built-in MapRoute extensions method provided by MVC. For various reasons I wanted an approach that could somehow work with the built-in MapRoute methods. In looking for a solution I realized:

  • You can’t intercept or override the existing MapRoute extension methods (of course)
  • You can’t derive from RouteCollection and override the Add method (because it’s not virtual)

What I did come up with is to use a route decorator:

public class LowercasingRoute : RouteBase
{
    public LowercasingRoute(RouteBase routeToWrap)
    {
        _inner = routeToWrap;
    }

    public override RouteData GetRouteData(HttpContextBase httpContext)
    {
        return _inner.GetRouteData(httpContext);
    }

    public override VirtualPathData GetVirtualPath(...)
    {
        var result = _inner.GetVirtualPath(context, values);
        if (result != null && result.VirtualPath != null)
        {
            result.VirtualPath = 
                result.VirtualPath.ToLowerInvariant();
        }
        return result;
    }
    RouteBase _inner;
}

Then, after all the route registration code is finished, wrap the existing routes in the routing table with the LowercasingRoute.

private void DecorateRoutes(RouteCollection routeCollection)
{
    for (int i = 0; i < routeCollection.Count; i++)
    {
        routeCollection[i] = new LowercasingRoute(routeCollection[i]);
    }
}

Unfortunately, I lose all the route names using this approach. The RouteCollection class takes the route name we pass in MapRoute and stores it in a private dictionary and you never see it again. This isn’t an issue as most MVC applications will never use a route name to generate a URL, but it makes me nervous enough to wonder what to do next.

  • Give up and use custom MapRoute extension methods?
  • Use reflection to read the dictionary of route names?
  • Fork the source?
  • Open a request on Connect?

What do you think?