ASP.NET Core Routing Tutorial

This is part 6 in our series about ASP.NET Core:

Most parts of elmah.io consist of small services. While they may not be microservices, they are in fact small and each do one thing. We recently started experimenting with ASP.NET Core (or just Core for short) for some internal services and are planning a number of blog posts about the experiences we have made while developing these services.

In this post, we will dig into the new routing features in Core. If you have a background in ASP.NET MVC and Web API, routing shouldn't be a new concept for you. For those of you who just started doing web development in .NET, routing is responsible for mapping a request URL to a handler in the shape of a controller. When receiving a request, Core looks for a handler, able to process the incoming request based on a set of rules. In fact, routing is "just" a piece of middleware, that we learned about in the previous post.

To create a new route, you will need to know about the route template syntax. While a routing syntax have been available in ASP.NET MVC from the beginning, Core heavily extend the possibilities. When creating a new web application in Core, a default route is automatically set up in Startup.cs. If you are starting with an empty project, you will need to install the Microsoft.AspNetCore.Routing NuGet package:

Install-Package Microsoft.AspNetCore.Routing

To add MVC to the web project, call the UseMvc-method in Startup.cs and give it a default template as shown below:

app.UseMvc(routes =>
{
    routes.MapRoute(
        name: "default",
        template: "{controller=Home}/{action=Index}/{id?}");
});

(If you create a new MVC application through Visual Studio, the NuGet package is automatically installed and the UseMvc-method added to Startup.cs)

The template specified in the UseMvc-method, will tell Core to match all requests to /some/url/42, to a controller named SomeController with an action named Url taking an id parameter. Notice the curly braces inside the template? Content inside a curly brace, represents a segment, which we will get back to later. Unlike previously, default values can be embedded as part of a segment, rather than through the defaults parameter (though still supported). In the example above, requesting / would hit the Index action of a HomeController without any id parameter or the value of that parameter set to null. Optional parameters are postfixed with a question mark (id?).

Would your users appreciate fewer errors?

➡️ Reduce errors by 90% with elmah.io error logging and uptime monitoring ⬅️

Besides the inline default values and optional parameters, we haven't really seen anything not available in the previous versions of ASP.NET yet. Let's look at some of the new features added to routing in Core.

Route constraints, open up for much greater control of allowed values inside a route. Most of us having worked with ASP.NET MVC, have been spending hours trying to figure out why a route didn't match a controller. Route constraints is a new feature, which will put some constraints (D'oh!) on the possible values in one or more segments. Let's look at an example:

routes.MapRoute(
    name: "default",
    template: "{controller=Home}/{action=Index}/{id:int}");

In the example, we tell Core that the id parameter, must be of type integer. This tell Core not to map the following request, since the last segment of the URL is a string: some/url/somestring. You can even define ranges on integers, maxlength on strings and much more using Routing Constraints. For more information about the possibilities, check out the official Route Constraint Reference.

Another new feature is the usage of wildcards. Wildcards acts as a kind of catch-all and can be used as part of any segment. Example:

routes.MapRoute(
    name: "default",
    template: "{controller=Home}/{action=Index}/{id:int}/{*more}");

This would map requests to both /some/url/42 and /some/url/42/details. It would even map /some/url/42/details/show, since * acts as a wild card for zero or more segments.

Like previous, route templates can be specified in attributes rather than during startup:

[Route("home/index/{id:int}/{*more}")]
public IActionResult Index(int id)
{
    ...
    return View();
}