Getting The Route Name For A Route

asp.net, asp.net mvc, code 0 comments suggest edit

A question I often receive via my blog and email goes like this:

Hi, I just got an email from a Nigerian prince asking me to hold some money in a bank account for him after which I’ll get a cut. Is this a scam?

The answer is yes. But that’s not the question I wanted to write about. Rather, a question that I often see on StackOverflow and our ASP.NET MVC forums is more interesting to me and it goes something like this:

How do I get the route name for the current route?

My answer is “You can’t”. Bam! End of blog post, short and sweet.

Joking aside, I admit that’s not a satisfying answer and ending it there wouldn’t make for much of a blog post. Not that continuing to expound on this question necessarily will make a good blog post, but expound I will.

It’s not possible to get the route name of the route because the name is not a property of the Route. When adding a route to a RouteCollection, the name is used as an internal unique index for the route so that lookup for the route is extremely fast. This index is never exposed.

The reason why the route name can’t be a property becomes more apparent when you consider that it’s possible to add a route to multiple route collections.

var routeCollection1 = new RouteCollection();
var routeCollection2 = new RouteCollection();

var route = new Route("{controller}/{action}", new MvcRouteHandler());

routeCollection1.Add("route-name1", route);
routeCollection2.Add("route-name2", route);

So in this example, we add the same route to two different route collections using two different route names when we added the route. So we can’t really talk about the name of the route here because what would it be? Would it be “route-name1” or “route-name2”? I call this the “Route Name Uncertainty Principle” but trust me, I’m alone in this.

Some of you might be thinking that ASP.NET Routing didn’t have to be designed this way. I address that at the end of this blog post. For now, this is the world we live in, so let’s deal with it.

Let’s do it anyways

I’m not one to let logic and an irrefutable mathematical proof stand in the way of me and getting what I want. I want a route’s name, and golly gee wilickers, I’m damn well going to get it.

After all, while in theory I can add a route to multiple route collections, I rarely do that in real life. If I promise to behave and not do that, maybe I can have my route name with my route. How do we accomplish this?

It’s simple really. When we add a route to the route collection, we need to tell the route what the route name is so it can store it in its DataTokens dictionary property. That’s exactly what that property of Route was designed for. Well not for storing the name of the route, but for storing additional metadata about the route that doesn’t affect route matching or URL generation. Any time you need some information stored with a route so that you can retrieve it later, DataTokens is the way to do it.

I wrote some simple extension methods for setting and retrieving the name of a route.

public static string GetRouteName(this Route route) {
    if (route == null) {
        return null;
    }
    return route.DataTokens.GetRouteName();
}

public static string GetRouteName(this RouteData routeData) {
    if (routeData == null) {
        return null;
    }
    return routeData.DataTokens.GetRouteName();
}

public static string GetRouteName(this RouteValueDictionary routeValues) {
    if (routeValues == null) {
        return null;
    }
    object routeName = null;
    routeValues.TryGetValue("__RouteName", out routeName);
    return routeName as string;
}

public static Route SetRouteName(this Route route, string routeName) {
    if (route == null) {
        throw new ArgumentNullException("route");
    }
    if (route.DataTokens == null) {
        route.DataTokens = new RouteValueDictionary();
    }
    route.DataTokens["__RouteName"] = routeName;
    return route;
}

Yeah, besides changing diapers, this is what I do on the weekends. Pretty sad isn’t it?

So now, when I register routes, I just need to remember to call SetRouteName.

routes.MapRoute("rName", "{controller}/{action}").SetRouteName("rName");

BTW, did you know that MapRoute returns a Route? Well now you do. I think we made that change in v2 after I begged for it like a little toddler. But I digress.

Like eating a Turducken, that code doesn’t sit well with me. We’re repeating the route name twice here which is prone to error. Ideally, MapRoute would do it for us, but it doesn’t. So we need some new and improved extension methods for mapping routes.

public static Route Map(this RouteCollection routes, string name, 
    string url) {
  return routes.Map(name, url, null, null, null);
}

public static Route Map(this RouteCollection routes, string name, 
    string url, object defaults) {
  return routes.Map(name, url, defaults, null, null);
}

public static Route Map(this RouteCollection routes, string name, 
    string url, object defaults, object constraints) {
  return routes.Map(name, url, defaults, constraints, null);
}

public static Route Map(this RouteCollection routes, string name, 
    string url, object defaults, object constraints, string[] namespaces) {
  return routes.MapRoute(name, url, defaults, constraints, namespaces)
    .SetRouteName(name);
}

These methods correspond to some (but not all, because I’m lazy) of the MapRoute extension methods in the System.Web.Mvc namespace. I called them Map simply because I didn’t want to conflict with the existing MapRoute extension methods.

With these set of methods, I can easily create routes for which I can retrieve the route name.

var route = routes.Map("rName", "url");
route.GetRouteName();

// within a controller
string routeName = RouteData.GetRouteName();

With these methods, you can now grab the route name from the route should you need it.

Of course, one question to ask yourself is why do you need to know the route name in the first place? Many times, when people ask this question, what they really are doing is making the route name do double duty. They want it to act as an index for route lookup as well as be a label applied to the route so they can take some custom action based on the name.

In this second case though, the “label” doesn’t have to be the route name. It could be anything stored in data tokens. In a future blog post, I’ll show you an example of a situation where I really do need to know the route name.

Alternate Design Aside

As an aside, why is routing designed this way? I wasn’t there when this particular decision was made, but I believe it has to do with performance and safety. With the current API, once a route name has been added to a route collection with a name, internally, the route collection can safely use the route name as a dictionary key for the route knowing full well that the route name cannot change.

But imagine instead that RouteBase (the base class for all routes) had a Name property and the RouteCollection.Add method used that as the key for route lookup. Well it’s quite possible that the value of the route’s name could change for some reason due to a poor implementation. In that case, the index would be out of sync with the route’s name.

While I agree that the current design is safer, in retrospect I doubt many will screw up  a read-only name property which should never change. We could have documented that the contract for the Name property of Route is that it should never change during the lifetime of the route. But then again, who reads the documentation? After all, I offered $1,000 to the first person who emailed me a hidden message embedded in our ASP.NET MVC 3 release notes and haven’t received one email yet. Also, you’d be surprised how many people screw up GetHashCode(), which effectively would have the same purpose as a route’s Name property.

And by the way, there are no hidden messages in the release notes. Did I make you look?

Found a typo or error? Suggest an edit! If accepted, your contribution is listed automatically here.

Comments

avatar

15 responses

  1. Avatar for Drazen Dotlic
    Drazen Dotlic November 28th, 2010
    And by the way, there are no hidden messages in the release notes. Did I make you look?


    I'm ashamed to admit that yes, you have :)
    However, I am also proud to admit that I have read the release notes when they came out originally.
    After a few seconds going through them again I realized that you must have been pulling our leg :)

  2. Avatar for Sly Gryphon
    Sly Gryphon November 28th, 2010

    Hmmm, I'm pretty sure I read the ASP.NET MVC 3 RC release notes earlier today :-)

  3. Avatar for tvanfosson
    tvanfosson November 28th, 2010

    Your example of the new extension methods doesn't use it; it uses the old extension method.
    >>> var route = routes.MapRoute("rName", "url");
    should be
    >>> var route = routes.Map("rName", "url");

  4. Avatar for haacked
    haacked November 28th, 2010

    @tvanfosson Thanks! My fingers are so used to typing MapRoute!

  5. Avatar for wind
    wind November 29th, 2010

    You are a lier :) , but greate post.

  6. Avatar for Bob
    Bob November 30th, 2010

    I just had to run over to MSDN.com to figure out what the heck a route is: msdn.microsoft.com/en-us/library/cc490435.aspx
    "I dream in code", but I don't think that I've ever used the route class before. I don't even think that I've heard of it before. If anyone can provide me with more info, I would be grateful, because now my interest is piqued.

  7. Avatar for Andy West
    Andy West November 30th, 2010

    Thanks for this insight into route names. I ran into this problem when I started unit testing my routes; I wanted to see if a route was being used by inspecting its name. Good to know why it doesn't work that way.

  8. Avatar for Brian
    Brian December 1st, 2010

    This code doesn't need to repeat anything:
    routes.MapRoute("rName", "{controller}/{action}").SetRouteName("rName");

    Behold!
    var routeName = "rName";
    routes.MapRoute(routeName, "{controller}/{action}").SetRouteName(routeName);
    ... Of course, this may still not sit well with you; even with the string literal pulled out into one place, you do still have to repeat the routeName variable (still a little clunky).

  9. Avatar for A-Dubb
    A-Dubb December 3rd, 2010

    Nice read. Very helpful.

  10. Avatar for Prince Nocike
    Prince Nocike December 8th, 2010

    So YOU'RE the one foiling my attempts.

  11. Avatar for Matt
    Matt December 15th, 2010

    What I am curious about as a new ASP.NET MVC user coming from Rails, is will ASP.NET MVC support named routes in the way Rails does? Rails provides a method for all your named routes that resolves to the route so routing throughout the app is less brittle. Just call the method instead of hard coding routes, and any changes to your routes are automatically captured. Does ASP.NET MVC already support this somehow and I've just missed it?

  12. Avatar for Pete Jaffe
    Pete Jaffe September 13th, 2012

    This comment is coming late to this blog post, but I would say this was one of the best writeups I found that mitigated some of my frustrations around not being able to determine the route name of a matched route. I was just surprised that you didn't include "debugging route matching" as one of the potentially main motivations for this desire. I could have saved myself plenty of time trying to reason through which route was matched based on looking at the data available off the Route, and wishing that the cool RouteDebugger library did a better job highlighting matched routes in child actions.
    Next time I find myself in the throws of route matching debugging, I might just implement that use of the DataTokens for storing it. Thanks for the idea, and GE entertaining post. It makes me want to run out and get a Turducken.
    -Pete

  13. Avatar for Saad Alothman
    Saad Alothman July 25th, 2014

    you are really "haacking" it, very helpful. Thanks a lot !

  14. Avatar for Raphaël Thouin
    Raphaël Thouin October 27th, 2014

    I've been using your solution for a project and it works great! But now moving to the Attribute Routing with MVC5, I'm wondering how I could apply your solution to that? Route attribute is a sealed class and cannot be extend, so how would you translate your logic in an attribute routing context?

  15. Avatar for kamranayub
    kamranayub July 30th, 2015

    I'm also trying to figure this out. I would really like to get a JSON object with key/value for route name => url template for my client-side routing.

    edit: Alright, easier to just scan the assembly for [Route] attributes and add to a static list at app_start. Then I just read that to render JSON.