Aliasing and Localizing Routes in ASP.NET MVC

I had come across a forum post where someone wanted to localize urls in the RouteTable and I thought I would come up with my first stab at a better solution.  What I discovered turned out to be a pretty simple way of what I am calling ‘Aliasing Routes’ where a route token can have an alias value as a localized value.

DOWNLOAD – My entire MvcSamples VSTS solution (complete with Moq and 100% code coverage)

This is how you do it (the complete source is available for download above):

Step 1: Persistence – we need a way to store the aliases, so I created a custom ConfigurationSection (an external file or database would also work) that looks like this when complete:

  1. <aliasRoutes>
  2.   <routes>
  3.     <add token="controller" value="home" aliases="inicio, beginning, baile, start" />
  4.     <add token="controller" value="about" aliases="info, sobre, more, additional" />
  5.     <add token="action" value="signin" aliases="login, logon, ingresar, anmelden" />
  6.   </routes>
  7. </aliasRoutes>

Step 2: Module – we need a way to inspect route data and change route values when applicable, so I derived from UrlRoutingModule that looks like this when complete:

  1. public class AliasRoutingModule : UrlRoutingModule {
  2.     protected static readonly object itemsKey = new object();
  3.  
  4.     private class RequestInfo {
  5.         public string OriginalPath { get; set; }
  6.         public IHttpHandler HttpHandler { get; set; }
  7.     }
  8.  
  9.     public override void PostMapRequestHandler(HttpContextBase context) {
  10.         Guard.AgainstNullParameter(context, "context");
  11.  
  12.         RequestInfo info = context.Items[itemsKey] as RequestInfo;
  13.         if (info == null) return;
  14.  
  15.         context.RewritePath(info.OriginalPath);
  16.         context.Handler = info.HttpHandler;
  17.     }
  18.  
  19.     public override void PostResolveRequestCache(HttpContextBase context) {
  20.         Guard.AgainstNullParameter(context, "context");
  21.  
  22.         RouteData routeData = this.RouteCollection.GetRouteData(context);
  23.         if (routeData == null) return;
  24.  
  25.         IRouteHandler routeHandler = routeData.RouteHandler;
  26.         if (routeHandler == null) throw new InvalidOperationException("No RouteHandler identified");
  27.         if (routeHandler is StopRoutingHandler) return;
  28.  
  29.         IDictionary<string, object> values = new Dictionary<string, object>(routeData.Values);
  30.         AliasRoutesSection section = SectionRepository.Current.AliasRoutes;
  31.  
  32.         //*** magic happens here
  33.         foreach (string token in values.Keys) {
  34.             string alias = values[token].ToString();
  35.             string value = section.Routes.GetValue(token, alias);
  36.  
  37.             if (value != null) routeData.Values[token] = value;
  38.         }
  39.  
  40.         RequestContext requestContext = new RequestContext(context, routeData);
  41.         IHttpHandler httpHandler = routeHandler.GetHttpHandler(requestContext);
  42.         if (httpHandler == null) throw new InvalidOperationException("No HttpHandler identified");
  43.  
  44.         RequestInfo info = new RequestInfo {
  45.             OriginalPath = context.Request.Path,
  46.             HttpHandler = httpHandler
  47.         };
  48.  
  49.         context.Items[itemsKey] = info;
  50.         context.RewritePath("~/UrlRouting.axd");
  51.     }
  52. }

Step 3: Web Config – need to change our UrlRoutingModule registration in the web.config to the new AliasRoutingModule:

  1.   <httpModules>
  2.     <add name="ScriptModule"
  3.          type="System.Web.Handlers.ScriptModule, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
  4.     <add name="AliasRoutingModule"
  5.          type="Salient6.MvcSamples.Web.Modules.AliasRoutingModule"/>
  6.   </httpModules>
  7. </system.web>

Step 4: RegisterRoutes – our Global stays clean and manageable:

  1. public static void RegisterRoutes(RouteCollection routes) {
  2.     routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
  3.     routes.IgnoreRoute("content/{*pathInfo}");
  4.     routes.IgnoreRoute("favicon.ico");
  5.  
  6.     routes.MapRoute(
  7.         "Default",                                              
  8.         "{controller}/{action}/{id}",                           
  9.         new { controller = "home", action = "index", id = "" }
  10.     );
  11. }

Step 5: Browser – we should be able to get to the Home Controller now by navigating to /start:

Alias Route Start

OR something way cooler like /inicio/anmelden  which is a combination of Controller and Action aliases!

Alias Route Start

What’s nice about this is you can alias and/or localize your url without having to MapRoute, you can make changes in the config without having to recompile, and you can mix and match aliases as long as they resolve to a valid endpoint!

Anyway, I hope folks find this useful!

Jason Conway

kick it on DotNetKicks.com

No Comments