Named Routes in MVC

In this post you will learn what are ‘Named Routes’ and how to use them to generate URLs. I will explain this using problem and then solution approach.

Introduction

In MVC naming route is not required, we just create route without name and hand it to routing engine and it just works. But there are situations when we need named routes, it allows more control over route selection when generating URLs. It also helps to avoids route ambiguity issue.

Why use ‘Named Routes’? Let’s face the problem.

When we create MVC Application we get following route defined.

routes.MapRoute(
    name: "Default",
    url: "{controller}/{action}/{id}",
    defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);

In above code, you can see the route name is ‘Default’. Now, let’s create another route just above the ‘Default’ route.

routes.MapRoute(
    name: "FriendsRoute",
    url: "Contacts/2013/{action}/{id}",
    defaults: new { controller = "Friends", action = "Index", id = UrlParameter.Optional }
);

Keep in mind, MVC routing system respect ordering route. Your last route must be generic as possible, and your previous route must be specific as possible and that’s why above route should go before ‘Default’ route.

Now, let’s go ahead and create two RouteLink without specifying which route to use to generate the links.

@Html.RouteLink("Go to home", new {controller = "Home", action = "Index"})
@Html.RouteLink("Go to friends", new {controller = "Friends", action = "Index"})

In above code, to create RouteLink I have used following version of overloaded method, image given below.


Please note I’m just making use of ‘linkText’ and ‘routeValues’, and not using any route name (Default or FriendsRoute) in order to generate URLs. So, it is completely up to MVC to pick the route. Once you have defined both routes (in RouteConfig.cs page), run the application.


This is fine for these simple cases, but there are situations where this can bite you. Let’s add a ‘page route’ as given below (yellow highlighted) just above the ‘FriendsRoute’, means this route is going to be on the top.

public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

    routes.MapPageRoute("new", "anypage/url", "~/aspx/profile.aspx");

    routes.MapRoute(
        name: "FriendsRoute",
        url: "Contacts/2013/{action}/{id}",
        defaults: new { controller = "Friends", action = "Index", id = UrlParameter.Optional }
    );

    routes.MapRoute(
        name: "Default",
        url: "{controller}/{action}/{id}",
        defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
    );
}

Now, see what happened.


Can you see all the URLs goes into a subtle behavior of routing, which is admittedly somewhat of an edge case and this is something that we run into from time to time.

Even don’t create that page route, just put ‘FriendsRoute’ after ‘Default’ route, this also goes into subtle behavior of routing.

The reason is simple, all the routes you have has URL route format {controller}/{action}/{id}, and you are expected to supply values for controller, action, and id to generating URLs. In this case, because the new route doesn't have any URL parameters, it matches every URL generation attempt because technically, any route value is supplied for each URL parameter, admit it.

Fix is very simple, use names for all your routes and always use the route name when generating URLs. Letting Routing engine sort out which route you want to use to generate a URL is really leaving it to chance to bite you. If you need to use non-named routes and leave it all on Routing engine, I recommend to write unit tests to verify the expected URL generation.

Let’s fix above problem by using ‘Named Routes’.

I just updated my RouteLink and included ‘routeName’ as given below.

@Html.RouteLink(linkText: "Go to home", routeName: "Default", routeValues: new {controller="Home", action="Index"})
@Html.RouteLink(linkText: "Go to friends", routeName: "FriendsRoute", routeValues: new {controller="Friends", action="Index"})

Now, run the application, you will find both URLs working but rest all will be still in subtle routing behavior. And to fix this, just bring that ‘page route’ at the bottom and also write unit tests to verify the URL generation.


Hope this helps.

Comments

  1. wow... Named Routes are really great. Didn't ever think about such situations.

    Though it is off-topic, I was wondering how we can implement the dynamic routing that depends on database data instead of hard-coding it in Global file like -
    example.com/user1
    example.com/user2/profile etc.
    Can you please shed some light on this concept of routing. It would be really very helpful.

    Thanks.

    ReplyDelete

Post a Comment

Popular posts from this blog

Migrating database from ASP.NET Identity to ASP.NET Core Identity

Customize User's Profile in ASP.NET Identity System

Lambda two tables and three tables inner join code samples