Learn ASP.NET Core Convention-Based URL Routing

Learn ASP.NET Core Convention-Based URL Routing

The concept of Routing is very vast and I have covered it in 6 tutorials which are:

So make sure you cover each of these one by one.

ASP.NET Core URL Routing is used to create URL patterns for your application. It matches incoming HTTP requests and dispatching those requests to the application’s endpoints. Endpoints are those units that are responsible for request-handling. These are defined in the Configure() method of Startup.cs class.

The Routing System has 2 main functions:

  • 1. Maps the incoming URLs to Controllers and Actions.
  • 2. Generates outgoing URLs so that a specific action method is called when the user clicks a link.

There are 2 ways to create routes in ASP.NET Core:

Let’s learn how URL Routing works by creating a simple Application.

Creating a Simple Application

In your Visual Studio create a new ASP.NET Core Web Application project and name it URLRouting.

Make sure to select the Framework, Version, Template and Authentication as given below:

1) .NET Core
2) ASP.NET Core 3.1
3) Empty (for template)
4) No Authentication

The below 2 images explains this:

ASP.NET Core Web Application
ASP.NET Core Empty Template

Once your project is created, open the Startup.cs class and support for MVC Framework, developer error pages, static files and default route as shown in the below code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace URLRouting
{
    public class Startup
    {
        // This method gets called by the runtime. Use this method to add services to the container.
        // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllersWithViews();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");
                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                app.UseHsts();
            }

            app.UseHttpsRedirection();
            app.UseStaticFiles();

            app.UseRouting();

            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                // Default route
                endpoints.MapControllerRoute(
                    name: "default",
                    pattern: "{controller=Home}/{action=Index}/{id?}");
            });
        }
    }
}

Next add a Controller by the name of HomeController inside the Controllers folder and add an action called Index to it. The code is shown below:

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

namespace URLRouting.Controllers
{
    public class HomeController : Controller
    {
        public IActionResult Index()
        {
            return View();
        }
    }
}

Also add an Index view file inside the Views ➤ Home Folder. It’s code is shown below:

@{ Layout = null; }

<!DOCTYPE html>
<html>
<head>
    <title>Routing</title>
</head>
<body>
    <h1>'Home' Controller, 'Index' View</h1>
</body>
</html>

Now run your application. You will see the Index View’s Contents displayed by the browser. See the image below:

home controller index view

Before explaining how the URL Routing has worked here, I will explain what segments are in URLs.

The basic building blocks of an ASP.NET Core application are Controllers, Actions & Views. I have covered them in 3 separate tutorials:

What are Segments in URL

Segments are the parts of the URL, excluding the hostname and query string, and are separated by the ‘/’ character.

The given image explains what segments are:

segments in url

So a URL can have 0 or infinite segments but most applications just need no more than 3 segments.

When there is an incoming HTTP request the Routing System job is to match the URL and extract the segments values from it.

Check the Configure() method of Startup.cs class where I defined a default route (‘default’ is just a name and can be anything). Inside the UseEndpoints method I added Endpoints for Controller’s action. For this I used the MapControllerRoute() which is used to create a single route. The single route is named default route.

app.UseEndpoints(endpoints =>
{
    // Default route
    endpoints.MapControllerRoute(
        name: "default",
        pattern: "{controller=Home}/{action=Index}/{id?}");
});
For earlier versions of ASP.NET Core like 2.0, 2.1 & 2.2. We used the app.UseMvc() method which is very much similar to app.UseEndpoints(). See it’s code below:
app.UseMvc(routes =>
{
   routes.MapRoute("default", "{controller=Home}/{action=Index}/{id?}");
});
ASP.NET Core 3.0 introduces the concept of Endpoint Routing so it is not advisable to use app.UseMvc() method anymore.

The segment are represented inside the braces ({ … }). The default route has 3 segments:

  • 1. Controller with default value of Home
  • 2. Action with default value of Index
  • 3. The ‘id’ having ? character like {id?} that defines id as optional.

The default value for segments is optional. It only tells the routing system to take a given default value for the corresponding segment if the segment is missing from the URL.

When you run the current application you will notice that the Index View of the Home Controller is invoked even thought there are no segments in the URL (i.e. URL is just https://localhost:58470/). The reason being the default values of the segments coming to play, which are – ‘Home’ for the controller and ‘Index’ for action.

Below is the table that will show all possible URL matching done by the current route:

Segments URL Maps to:
0 / controller = Home action = Index
1 /Home controller = Home action = Index
1 /About controller = About action = Index
2 /Home/Index controller = Home action = Index
2 /Home/List controller = Home action = List
2 /About/Show controller = About action = Show
3 /Home/Index/3 controller = About action = Index
3 /Home/List/3 No match

Clearly you can see the current route will match up to 3 segments in the URL. For more than 3 segments it will fail and will give 404 error message.

Now remove the default values of the Controller and View from the route as shown below:

app.UseEndpoints(endpoints =>
{
    // Default route
    endpoints.MapControllerRoute(
        name: "default",
        pattern: "{controller}/{action}");
});

And run your application. You will get HTTP 404 error message because the routing system cannot maps the URL to any controller. I have illustrated this in the below image:

HTTP Error 404

Now go to the URL – https://localhost:58470/Home/Index (your port may be different) and this time you will see the Home Controller’s Index method is evoked.

So this route will match exactly 2 segments in the URL. For other number of segments like 0, 1, 3, 4, … it will fail and give 404 error message.

The given table shows all possible URL matching given by this route:

Segments URL Maps to:
0 / No match
1 /Home No match
2 /Home/Index controller = Home action = Index
2 /Home/List controller = Home action = List
2 /About/List controller = About action = List
3 /Home/Show/4 No match

Convention-Based Routing

In ASP.NET Core the Convention-Based Routing is that where the routes are applied inside the Configure() method of the Startup class. This is done by using app.UseEndpoints() method like what you have seen in the above sections. I will cover every aspect of this form of routing in this tutorial.

Static Text in Routes

URL Routing supports static texts in routes. To understand it remove the default route and add a route which contains a static text as given below:

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllerRoute(
        name: "news1",
        pattern: "News/{controller=Home}/{action=Index}");
});

The route contains a static text – News in it. Run the application and go to the URL – https://localhost:58470/News. You will find the Home Controller’s Index action is invoked.

Now I explain what happened here. The route will match 3 segments. The first one should be News, the second and third are optional segments for Controller and Action. If omitted, the default values which are Home for Controller and Index for Action will be used.

You can also combine static text with variable segments (like controllers or actions). Remove the route with that shown below:

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllerRoute(
        name: "news2",
        pattern: "News{controller}/{action}");
});

Here I have added a static text News that will join with the Controller’s segment value. Now run your application and go to the URL – https://localhost:58470/NewsHome/Index. You will find the Home Controller’s Index Action method is invoked.

static text routing
Changing Controller Mappings with URL

Using static text in routes can also help in preserving Old URLs. Suppose you had a controller named Shopping and now you replaced it with a Home Controller. You must make sure that the previous URLs (eg /Shopping/Clothes, /Shopping/Electronics, /Shopping/Grocery, etc) should now map to Home Controller.

This is how you will add a route for performing this thing:

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllerRoute(
        name: "shop",
        pattern: "Shopping/{action}",
        defaults: new { controller = "Home" });
});

The route matches 2 Segments where the first segment is Shopping.

The action value is taken from the second segment. The URL pattern doesn’t contain a segment for controller, so the default value is used. The defaults argument provides the controller value which is the Home controller.

So now the ULRs like /Shopping/Clothes, /Shopping/Electronics, /Shopping/Grocery will be mapped to the Home Controller instead of the Shopping Controller.

To check, open the URL – https://localhost:58470/Shopping/Index in your browser. You will find the Home Controller’s Index method is invoked. See the below image:

changing controller mappings
Providing Default Values of Controller and Action for a URL

If you want only a specific Controller and Action should be mapped to a URL then you have to provide the default values for both Controller and Action like shown in the below route:

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllerRoute(
        name: "old",
        pattern: "Shopping/Old",
        defaults: new { controller = "Home", action = "Index" });
});

The above route maps the Home Controller’s Index method for the URL – https://localhost:58470/Shopping/Old.

See the below image:

default value for controller action
Routes Order is Important

More than one route can be applied in an application. But always remember, routes are applied in the order in which they are defined. The Routing System tries to match the incoming URL against the first defined route and proceeds only to the next route only if there is no match. Therefore you must define the most specific routes first.

Let’s understand it by adding the below 2 routes to the Configure() method of the Startup class:

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllerRoute(
        name: "old",
        pattern: "Shopping/Old",
        defaults: new { controller = "Home", action = "Index" });

    endpoints.MapControllerRoute(
      name: "shop",
      pattern: "Shopping/{action}",
      defaults: new { controller = "Home" });
});

Add a new Action method called Oldto the Home Controller. It’s code is shown below:

public IActionResult Old ()
{
    return View();
}

Next add Old View inside the Views ➤ Home folder with contents given below:

@{ Layout = null; }

<!DOCTYPE html>
<html>
<head>
    <title>Routing</title>
</head>
<body>
    <h1>'Home' Controller, 'Old' View</h1>
</body>
</html>

Run your application and go to the URL – https://localhost:58470/Shopping/Old. You will find the Index action of the Home Controller getting invoked.

See the below image:

default value for controller action

This happens because when you request the URL – https://localhost:58470/Shopping/Old the routing system starts matching the URL with the routes starting from the first one.

Since the match happens on the first route itself therefore the Index View of the Home Controller is shown on the browser.

Now change the order of the routes by bringing the second route before the first one as shown in the code below:

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllerRoute(
        name: "shop",
        pattern: "Shopping/{action}",
        defaults: new { controller = "Home" });

    endpoints.MapControllerRoute(
        name: "old",
        pattern: "Shopping/Old",
        defaults: new { controller = "Home", action = "Index" });
});

Re-run your application and refresh the same URL – https://localhost:58470/Shopping/Old on your browser. This time you will find the Old Index of the Home Controller getting invoked. See the below image:

route ordering 2

The reason for this is that this time the routing system finds the different matching route for the URL (Since I changed the route ordering in the Configure method), and this route is –

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllerRoute(
        name: "shop",
        pattern: "Shopping/{action}",
        defaults: new { controller = "Home" });
});

Therefore always remember to place your most specific routes first else the routing system will do the mappings wrongly.

Custom Segment Variables

So far you have seen the controller and action segment variables in routes. You can also add your own Custom Segment variables in routes. Let me show how to do it.

Remove all the routes and add the following route in your Startup.cs file Configure() method:

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllerRoute(
        name: "MyRoute",
        pattern: "{controller=Home}/{action=Index}/{id}");
});

In this route I have added a Custom Segment variables by the name id.

Next add a new Action method called Check to the Home Controller:

public IActionResult Check()
{
    ViewBag.ValueofId = RouteData.Values["id"];
    return View();
}

This action method obtains the value of the custom variable called id in the route URL pattern using the RouteData.Values property, and stores this value in a ViewBag variable.

Now create the Check View inside the Home ➤ Check folder. The Check View code is shown below. This view shows the value of the ViewBag variable which holds the value of the Custom Segment called Id.

@{ Layout = null; }

<!DOCTYPE html>
<html>
<head>
    <title>Routing</title>
</head>
<body>
    <h1>'Home' Controller, 'Check' View</h1>
    <h2>Id value is: @ViewBag.ValueofId</h2>
</body>
</html>

Now run your application and go to URL – https://localhost:58470/Home/Check/cSharp.

The URL Routing system of ASP.NET Core will match the third segment which is cSharp in this URL as the value for the ‘id’ variable.

ASP.NET Core Identity is a membership system with which you can add login functionality to your ASP.NET application. Users can create an account and login with a user name and password. Learn it from this article – How to Setup and Configure Identity Membership System in ASP.NET Core

You will see the Id value is: cSharp get displayed on the browser as shown in the below image:

custom segment variable

If you don’t pass a value to the 3rd segment of the URL e.g. open to the URL – https://localhost:58470/Home/Check. Then the routing system does not find any matching routes and so HTTP ERROR 404 is displayed on the browser.

Custom Segment Variable as Action Method Parameters

If you define Parameters in Action Methods in such a way that the names of the parameters are same like the URL parameter variables names, then the ASP.NET Core MVC framework will automatically pass the values obtained from these URL parameter variables to the parameters of the action method.

Consider the same route from the above section. It has a custom segment variable – id.

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllerRoute(
        name: "MyRoute",
        pattern: "{controller=Home}/{action=Index}/{id}");
});

In order to get the value of id passed to the URL, I can add an Id parameter to my Action Method. So change the Check action method by adding id parameter to it as shown below:

public IActionResult Check(int id)
{
    ViewBag.ValueofId = id;
    return View();
}

Also notice inside the method I am simply assigning the value of id variable to the ViewBag variable.

You can check it by running your application and going to the URL – https://localhost:58470/Home/Check/100. There you will see the value 100 displayed on the View.

See the below image:

route constraint passed

Note: I kept the type of the id parameter as Int, but MVC will try to convert it to whatever parameter type is used. This feature is known as Model Binding feature of ASP.NET Core which I have covered in details in another tutorial. But for this tutorial it is enough to know just the basics of Model Binding.

Similarly If I put the type of the id parameter as a string or a DateTime in the action method, then MVC will automatically convert the id’s value to the specified type.

For example Change the type of the id parameter of Check action method to sting as shown below.

public IActionResult Check(string id)
{
    ViewBag.ValueofId = id;
    return View();
}

Now when you request the URL – https://localhost:58470/Home/Check/hello or https://localhost:58470/Home/Check/100. Then MVC will automatically cover the value for the custom segment id (hello and 100) to string.

Optional URL Segment

The Segment which you don’t need to specify for the route is called as optional URL Segment. You denote it by placing a question mark (?) after it.

Remove all the routes from your app and add just one which is shown below. In this route I made the id segment as optional by adding ‘?’ against it:

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllerRoute(
        name: "MyRoute1",
        pattern: "{controller=Home}/{action=Index}/{id?}");
});

So now when no value is supplied for the optional segment variable then the value of the corresponding parameter will be null.

To test this change the Check action method of the Home Controller as shown in the below Code:

public IActionResult Check()
{
    ViewBag.ValueofId = RouteData.Values["id"];
    return View();
}

Run your application and go to the URL – https://localhost:58470/Home/Check. You will see the Empty Value displayed on the browser as shown in the below image:

empty custom segment value

The empty value is displayed because the routing system does not find the value of this Custom Segment Variable called id, and so empty value of id is displayed in the browser

Next, change the Check action method of the Home Controller as shown in the below Code:

public IActionResult Check(string id)
{
    ViewBag.ValueofId = id ?? "Null Value";
    return View();
}

I changed the type of id parameter to string. Now the code – ViewBag.ValueofId = id ?? "Null Value" tells that if ‘id’ value is null (done by the ?? operator) then put the string “Null Value” to the ViewBag variable else put the id value to the ViewBag variable.

Run your application and go to the URL – https://localhost:58470/Home/Check. You will see the Null Value displayed on the browser as shown in the below image:

optional segment variable

“*catchall” for Variable Length Routes

So far you have seen routes that can match URL with at most 3 segments. What if you have URL is having 4, 5 or more segments?

One way is to add a new routes each for matching 4, 5, etc segments.

For example for matching the 4 segment URL – http://localhost:58470/Home/Check/Hello/World you can add a new route which is given below:

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllerRoute(
        name: "MyRoute2",
        pattern: "{controller=Home}/{action=Index}/{id?}/{idtwo?}");
});

This will work but if the 5th segment comes then you have to add new routes for matching 5 segments.

This is tiring and causes code duplication. Instead you can use the term *catchall in your routes to match the variable number of segments.

Change the route to add {*catchall} as the 4th segment. It should be like:

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllerRoute(
        name: "MyRoute2",
        pattern: "{controller=Home}/{action=Index}/{id?}/{*catchall}");
});

I have added the {*catchall} as the 4th segment in the route. The first 3 segments are used for mapping controller, action & id variable. If the URL contains more than 3 segments then they are all assigned to the catchall variable.

The table below tells about all the routes which this route will match:

Segments URL Maps to:
0 / controller = Home action = Index id = Null
1 /Home controller = Home action = Index id = Null
2 /Home/CatchallTest controller = Home action = CatchallTest Id = Null
3 /Home/CatchallTest/Hello controller = Home action = catchallTest id = Hello
4 /Home/CatchallTest/Hello/How controller = Home action = catchallTest id = Hello catchall = How
5 /Home/CatchallTest/Hello/How/Are controller = Home action = catchallTest id = Hello catchall = How/Are
6 /Home/CatchallTest/Hello/How/Are/U controller = Home action = catchallTest id = Hello catchall = How/Are/You

To test this, in your Home Controller Code add a new Action method with name as CatchallTest. Put the following code to it:

public IActionResult CatchallTest(string id, string catchall)
{
    ViewBag.ValueofId = id;
    ViewBag.ValueofCatchall = catchall;
    return View();
}

I have just added a new parameter with name called catchall to the Action method. This parameter will contains the value of 4th, 5th, 6th,…. segments. The ViewBag variable called ValueofCatchall will store this value of catchall.

Next add the CatchallTest View inside the Views ➤ Home folder with the code given below:

@{ Layout = null; }

<!DOCTYPE html>
<html>
<head>
    <title>Routing</title>
</head>
<body>
    <h1>'Home' Controller, 'CatchallTest' View</h1>
    <h2>Id value is: @ViewBag.ValueofId</h2>
    <h2>Catchall value is: @ViewBag.ValueofCatchall</h2>
</body>
</html>

The View will show the value of ViewBag’s variable called ValueofCatchall which stores the value of catchall segment.

Run the application and go to the URL – http://localhost:58470/Home/CatchallTest/Hello/How/Are/U. You will find the catchall value – How/Are/U (value of 4th, 5th,6th url segments) displayed on the view.

catchall

You can download the full codes of this tutorial from the below link:

Download

Conclusion

In this tutorial you learned how to create routes in your ASP.NET Core application using Convention-Based Routing. I hoped you liked this tutorial. Look out for other tutorials on ASP.NET Core.

Share this article -

yogihosting

ABOUT THE AUTHOR

This article has been written by the Technical Staff of YogiHosting. Check out other articles on "ASP.NET Core, jQuery, EF Core, SEO, jQuery, HTML" and more.