Grouping Routes Part 2

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

In part 1 of this series, we looked at the scenario for grouping routes and how we can implement matching incoming requests with a grouped set of routes.

In this blog post, I’ll flesh out the implementation of URL Generation.

Url Generation Implementation

URL generation for a group route is tricky, especially when using named routes because the individual routes that make up the group aren’t in the main route collection.

As I noted before, the only route that’s actually added to the route table is the GroupRoute. Thus if you supply a route name for one of the child routes (such as “r1”) during URL generation, you’ll get a null URL.

Interestingly enough, in this case, if you don’t use named routes when using URL generation, everything works just fine. However, since I heartily recommend using named routes all the time, I should cover that situation.

So what we need to do here is supply two route names during URL generation. One for the group route, and one for the child route. How do we supply the child route name? We’re going to have to supply it in the route values. Here’s an example of generating an URL in this manner:

@Html.RouteLink("Hello World Child", "group", new { __RouteName = "hello-world3" }) 

Note that the second parameter, “group”, refers to the route name for the GroupRoute that we registered. The route value __RouteName is passed into the GroupRoute so that it can look in its own collection of routes for the matching child route.

In the following code sample, I’ve highlighted the essential part of the URL generation logic within the GroupRoute class.

public override VirtualPathData GetVirtualPath(RequestContext 
    requestContext, RouteValueDictionary values) {
  string routeName = values.GetRouteName();
  var virtualPath = ChildRoutes.GetVirtualPath(requestContext, 
    routeName as string, values.WithoutRouteName());
  if (virtualPath != null) {
    string rewrittenVirtualPath = 
      virtualPath.VirtualPath.WithoutApplicationPath(requestContext);
    string directoryPath = VirtualPath.WithoutTildePrefix(); // remove tilde
    rewrittenVirtualPath = rewrittenVirtualPath.Insert(0, 
    directoryPath.WithoutTrailingSlash());
    virtualPath.VirtualPath = rewrittenVirtualPath.Remove(0, 1);
  }

  return virtualPath;
}

The code grabs the route name for the child route from the supplied route values. Notice that I’m using an extension method I wrote in my last blog post.

The block of code after the highlighted portion rewrites the virtual path back to the full virtual path for the parent GroupRoute. This ensures that the virtual path that’s eventually returned to the caller will actually work, since the individual routes within the group don’t have a clue that they’re within a group.

In a follow-up blog post, I’ll wrap up this series and provide access to the full source code.

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

Comments

avatar

3 responses

  1. Avatar for simon
    simon January 9th, 2011

    I really wish you'd address the problem of inheriting route values sometime in a blog post :-) Even when using RouteUrl(...) with an explicit named route route parameters such as 'gender', 'id' and even 'controller' and 'action' are inherited if they're not explicitly specified. It's tripped me up many times. Thanks!
    http://stackoverflow.com/questions/1148443

  2. Avatar for Adam
    Adam January 9th, 2011

    @Haack
    Good Stuff. I am trying it myself and can't say I have it working just yet, so I can't wait for the second part with source code! I hope it's not too far away, because I know I'm missing something simple.

  3. Avatar for Igorbek
    Igorbek January 12th, 2011

    Hello, Phil.
    Thanx for the article.
    I found some unexpected behavior when generating URL for actions: UriHelper.Action (or others) implictly takes route values from current context.
    For example:
    Routes:
    routes.MapRoute("User", "{userId}/{controller}/{action}/{id}",
    new { controller = "Home", action = "Index", id = UrlParameter.Optional }, new { userId = "^[1-9]\\d*$" });
    routes.MapRoute("Default", "{controller}/{action}/{id}", new { controller = "Home", action = "Index", id = UrlParameter.Optional});
    Controller:
    public class HomeController : Controller
    {
    public ActionResult Index() { return View(); }
    public ActionResult Test(int userId) { return View("Index"); }
    }
    View 'Index':
    Expected: "/", Actual: "@Url.Action("Index", "Home")"
    Expected: "10/Home/Test", Actual: "@Url.Action("Test", "Home", new { userId = 10 })"
    Result of '/':
    Expected: "/", Actual: "/"
    Expected: "10/Home/Test", Actual: "/10/Home/Test"
    All right!
    Result of '/10/Home/Test':
    Expected: "/", Actual: "/10"
    Expected: "10/Home/Test", Actual: "/10/Home/Test"
    Why? The route value 'userId' set implictly from current route values into generated route link!
    Can you help me?