Routing in Asp.Net Core 3.0

Routing is a pattern matching system which maps incoming request to appropriate controller and action methods.

There are two types of routing in ASP.Net Core

  • Conventional Based routing
  • Attribute-based routing

Routing in ASP.NET Core 3.0 is a bit different than older version if you see Configure method of ASP.NET Core 2.2 you will see routing we use to implement using an app.UseMVC method and if we want to use other services like (MVC, Razor Pages, SignalR, etc.), then we need to configure it in the Configure method.

Now the problem with routing was the other middleware in the pipeline did not know the request which is sent is going to be handled by which middleware, to overcome this in ASP.NET CORE introduce UseEndpointRouting ‘Endpoint routing middleware‘ middleware and UseEndpoints ‘Endpoint middleware‘.

Endpoint routing middleware uses metadata and also checks request URL and determine which endpoint will best to handle request then selected endpoint is set in the HttpContext object from here other middleware can access the chosen Endpoint after that UseEndpoints ‘Endpoint middleware‘ will execute the endpoint which chose.

After understanding how new routing works in asp.net core 3.0 next let’s create a simple application and using both Conventional Based routing and Attribute-based routing.

We have created an ASP.NET Core project for implementing Convention Routing and Attribute-based routing.

The Convention Routing is a default routing it is present when you create a new application. It is called convention routing because it establishes a convention for URL paths.

  • the first path segment maps to the controller name
  • the second maps to the action name.
  • the third segment is used for an optional id used to map to a model entity

Reference:- https://docs.microsoft.com/en-us/aspnet/core/mvc/controllers/routing?view=aspnetcore-3.1

Convention Routing which is available by default

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Home/Error");
        app.UseHsts();
    }
    app.UseHttpsRedirection();
    app.UseStaticFiles();
    app.UseRouting();
    app.UseAuthorization();
    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllerRoute(
            name: "default",
            pattern: "{controller=Home}/{action=Index}/{id?}");
    });
}

Details of each parameter

endpoints.MapControllerRoute(
     name: "default",
     pattern: "{controller=Home}/{action=Index}/{id?}",
     constraints: null,
     dataTokens: null,
     defaults: new {controller = "Home", action = "Validate"});

name (String)
The name of the route.
pattern (String)
The URL pattern of the route.
Defaults (Object)
An object that contains default values for route parameters. The object’s properties represent the names and values of the default values.
Constraints (Object)
An object that contains constraints for the route. The object’s properties represent the names and values of the constraints.
dataTokens (Object)
An object that contains data tokens for the route. The object’s properties represent the names and values of the data tokens.

Referenced from:- https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.builder.controllerendpointroutebuilderextensions.mapcontrollerroute?view=aspnetcore-3.1

Now let’s add a controller with name ProductController and it will have an action method with name ProductDetails which will take id as an input parameter.

Code Snippet

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;

namespace WebRouting.Controllers
{
    public class ProductController : Controller
    {
        public IActionResult ProductDetails(int? id)
        {
            return View();
        }
    }
}

Now we are going to add Route for handling this request. Here we have added a Route with the name ‘ProductRoute‘ and it has URL pattern for ProductRoute “Product/{id}” which tells that any URL that starts with /Product, must be handled by ProductController.

If you see ProductRoute in below code snippet, we haven’t specified {action} in the URL pattern because we want every URL that starts with Product should always use ProductDetails action of ProductController.

Code Snippet

app.UseEndpoints(endpoints =>
 {
     endpoints.MapControllerRoute(
         name: "ProductRoute",
         pattern: "Product/{id:int}",
         defaults: new { controller = "Product", action = "ProductDetails" });

     endpoints.MapControllerRoute(
         name: "default",
         pattern: "{controller=Home}/{action=Index}/{id?}",
         constraints: null,
         dataTokens: null,
         defaults: new { controller = "Home", action = "Index" });
 });

Next, we are going to register various route and see which kind of pattern is handled by which URL.

First scenario

In this scenario, we have route prefix ‘Search‘ and placeholder for the category which we are going to pass as a parameter to ProductDetailsbyCategory action method in Product Controller.

endpoints.MapControllerRoute(
         name: "ProductCategoryRoute",
         pattern: "Search/{category}",
         defaults: new { controller = "Product", action = "ProductDetailsbyCategory" });

URL: – http://localhost:44341/search/mobile

URL Controller Action Id Parameter 1 Parameter 2
http://localhost:44341/search/mobile Product ProductDetailsbyCategory mobile

Second scenario

In this scenario, we have route prefix ‘Search‘ and two placeholders for the category and another for subCategory which we are going pass as a parameter to ProductDetailsbyCategoryandsubCategory action method in Product Controller.

endpoints.MapControllerRoute(
     name: "ProductCategoryandsubCategoryRoute",
     pattern: "Search/{category}/{subCategory}",
     defaults: new { controller = "Product", action = "ProductDetailsbyCategoryandsubCategory" });

URL: – https://localhost:44341/search/mobile/motorola

URL Controller Action Id Parameter 1 Parameter 2
https://localhost:44341/search/mobile/motorola Product ProductDetailsbyCategoryandsubCategory mobile motorola

Third scenario

In this scenario, we have route prefix ‘Product‘ and one placeholder for productid which we are going pass as a parameter to ProductDetails action method in Product Controller.

endpoints.MapControllerRoute(
    name: "ProductRoute",
    pattern: "Product/{id:int}",
    defaults: new { controller = "Product", action = "ProductDetails" });

URL: – https://localhost:44341/product/1

URL Controller Action Id Parameter 1 Parameter 2
https://localhost:44341/product/1 Product ProductDetails 1

Fourth scenario

The default route which is present when we create new ASP.NET Core Application.

endpoints.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}",
    defaults: new { controller = "Home", action = "Index" });

URL: – https://localhost:44341/product/Cart

URL Controller Action Id Parameter 1 Parameter 2
https://localhost:44341/product/1 Product ProductDetails 1

Attribute-based routing

In attribute routing, we use an attribute to define routes using attribute routing give you more control on routing compare to conventional routing. In attribute routing, we directly apply Route attribute on the controller and action method.

Lists the constraints that are supported

Constraint Description Example
alpha Matches uppercase or lowercase Latin alphabet characters (a-z, A-Z) {x:alpha}
bool Matches a Boolean value. {x:bool}
datetime Matches a DateTime value. {x:datetime}
decimal Matches a decimal value. {x:decimal}
double Matches a 64-bit floating-point value. {x:double}
float Matches a 32-bit floating-point value. {x:float}
guid Matches a GUID value. {x:guid}
int Matches a 32-bit integer value. {x:int}
length Matches a string with the specified length or within a specified range of lengths. {x:length(6)} {x:length(1,20)}
long Matches a 64-bit integer value. {x:long}
max Matches an integer with a maximum value. {x:max(10)}
maxlength Matches a string with a maximum length. {x:maxlength(10)}
min Matches an integer with a minimum value. {x:min(10)}
minlength Matches a string with a minimum length. {x:minlength(10)}
range Matches an integer within a range of values. {x:range(10,50)}
regex Matches a regular expression. {x:regex(^\d{3}-\d{3}-\d{4}$)}

Reference from:-  https://docs.microsoft.com

We do not require any changes in Configure method we need to see we have endpoints.MapControllerRoute method in UseEndpoints method which Adds endpoints for controller actions to the IEndpointRouteBuilder.

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
 {
     if (env.IsDevelopment())
     {
         app.UseDeveloperExceptionPage();
     }
     else
     {
         app.UseExceptionHandler("/Home/Error");
         app.UseHsts();
     }
     app.UseHttpsRedirection();
     app.UseStaticFiles();
     app.UseRouting();
     app.UseAuthorization();
     app.UseEndpoints(endpoints =>
     {
         endpoints.MapControllerRoute(
             name: "default",
             pattern: "{controller=Home}/{action=Index}/{id?}");
     });
 }

Here we are going to use the Same ProductController which we have used in Conventional routing which will help us to understand attribute routing easily.

In below code, I have added Route attribute to each action method with, without parameters and with routing constraints.

Code snippet

using Microsoft.AspNetCore.Mvc;
namespace WebRouting.Controllers
{
    public class ProductController : Controller
    {
        [Route("/Product/ProductDetails/{id:int}")]
        public IActionResult ProductDetails(int? id)
        {
            return View();
        }

        [Route("Search/{category}")]
        public IActionResult ProductDetailsbyCategory(string category= "")
        {
            return View();
        }

        [Route("Search/{category}/{subCategory}")]
        public IActionResult ProductDetailsbyCategoryandsubCategory(string category = "", string subCategory = "")
        {
            return View();
        }

        [Route("MyCart")]
        public IActionResult Cart()
        {
            return View();
        }
    }
}

After adding all route for a sample next let’s see how we can access then each in simple steps.

Route with Name  ( eg: /mycart )

To access the below action method, we are going to enter URL https://localhost:44341/mycart

[Route("MyCart")]
public IActionResult Cart()
{
    return View();
}

Route with integer parameter and integer route constraint (eg: /Product/ProductDetails/1)

To access the below action method, we are going to enter URL https://localhost:44341/Product/ProductDetails/1

[Route("/Product/ProductDetails/{id:int}")]
public IActionResult ProductDetails(int? id)
{
     return View();
}

Route with one string parameter ‘category’ ( eg: /search/mobile)

To access the below action method, we are going to enter URL https://localhost:44341/search/mobile

[Route("Search/{category}")]
public IActionResult ProductDetailsbyCategory(string category= "")
{
    return View();
}

Route with two string parameter ‘category’ and ‘subCategory’ ( eg: /search/mobile/motorola)

To access the below action method, we are going to enter URL:-  https://localhost:44341/search/mobile/motorola

[Route("Search/{category}/{subCategory}")]
public IActionResult ProductDetailsbyCategoryandsubCategory(string category = "", 
string subCategory = "")
 {
     return View();
 }

By Saineshwar Bageri

I am Microsoft MVP | C# Corner MVP | Code Project MVP | FULL STACK .NET Developer and working on .Net Web Technology (Asp.net, Asp.net Core,.Net Core, C#, Sqlserver, MVC, Windows, Console Application, javascript, jquery, json, ORM Dapper) and also a freelance developer.