Learn ASP.NET Core Convention-Based URL Routing

Learn ASP.NET Core Convention-Based URL Routing

ASP.NET Core MVC URL Routing is used to create patterns of URLs for your application. The Routing System has 2 functions:

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

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

1. Convention-Based Routing.
2. Attribute-Based Routing.

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 called URL Routing.

On the dialog box select the Framework, Version, Template and Authentication as given below:

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

The below image explains this:

asp-net core empty template

Once your project is created, open the Startup 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;

namespace URLRouting
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
        }

        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            app.UseDeveloperExceptionPage();
            app.UseStatusCodePages();
            app.UseStaticFiles();

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

Next add a Controller of name ‘HomeController’ inside the Controllers folder and add an ‘Index’ action to it. The controller’s code is given 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 inside the Views ➤ Home Folder. Add the Index View code as given 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 contents displayed in the browser. See the image below:

home controller index view

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

What are Segments in URL

Segments are the parts of the URL, excluding the hostname and query string, that 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.

In the above section I defined a default route (default is just a name and can be any).

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

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

1. Controller with default value of Login
2. Action with default value of Index

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.

In the current application when you run it the Index view of the Home Controller is invoked even thought there are no segments in the URL. 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
2 /Home/Index/3 No match

Clearly you can see the current route will match 0, 1 and 2 segments in the URL. For 3 and more 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.UseMvc(routes =>
{
    routes.MapRoute(
        name: "default",
        template: "{controller}/{action}");
});

And run your application. You will get 404 error message because the routing system cannot maps the URL to any controller.

Go to the URL – ‘http://localhost:58470/Home/Index’ and this time you will see the Home Controller Index method is evoked.

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

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.

Convention-Based Routing

In ASP.NET Core MVC 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.UseMvc() method like what you have seen in the above section.

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 shown below:

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

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

This route will match 3 segments. The first one should be ‘News’. The second and third are optional segments for Controller and Action. If omitted then 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.UseMvc(routes =>
{
    routes.MapRoute(
        name: "news",
        template: "News{controller}/{action}");
});

In the above route I have added a static text ‘News’ that will join with the Controller segment value. Now run your application and go to the URL – ‘http://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 this thing:

app.UseMvc(routes =>
{
    routes.MapRoute(
        name: "shop",
        template: "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 URL segment. The URL pattern doesn’t contain a segment for controller, so the default value is used. The defaults argument provides the controller value.

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 – ‘http://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 can provide the default values of both Controller and Action like shown in the below route:

app.UseMvc(routes =>
{
    routes.MapRoute(
        name: "old",
        template: "Shopping/Old",
        defaults: new { controller = "Home", action = "Index" });
});

The above route maps the Home Controller’s Index method for the URL – ‘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 your Configure() method:

app.UseMvc(routes =>
{
    routes.MapRoute(
        name: "old",
        template: "Shopping/Old",
        defaults: new { controller = "Home", action = "Index" });

    routes.MapRoute(
        name: "shop",
        template: "Shopping/{action}",
        defaults: new { controller = "Home" });
});

Add a new Action method to the Home Controller and name it ‘Old’. Its content 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 – ‘http://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 – ‘http://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.UseMvc(routes =>
{
    routes.MapRoute(
        name: "shop",
        template: "Shopping/{action}",
        defaults: new { controller = "Home" });
	
    routes.MapRoute(
        name: "old",
        template: "Shopping/Old",
        defaults: new { controller = "Home", action = "Index" });
});

Re-run your application and request the same URL – ‘http://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, which is –

routes.MapRoute(
name: "shop",
       template: "Shopping/{action}",
       defaults: new { controller = "Home" });

Since I changed the route ordering in the Configure method.

Therefore always remember to place your most specific routes first else the routing system will do the mapping 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 Configure() method:

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    app.UseDeveloperExceptionPage();
    app.UseStatusCodePages();
    app.UseStaticFiles();
    app.UseMvc(routes =>
    {
        routes.MapRoute(
            name: "MyRoute",
            template: "{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 by name ‘Check’ to the Home Controller:

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

This action method obtains the value of the custom ‘id’ variable 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 is shown below. It shows the Value of the ViewBag variable which holds the Custom Segment ‘Id’ value.

@{ 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 – ‘http://localhost:58470/Home/Check/cSharp’.

The URL Routing system of ASP.NET Core MVC will match the third segment (cSharp) in this URL as the value for the id variable.

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. go to the URL – ‘http://localhost:58470/Home/Check’. Then the routing system does not find the value of Custom Segment Variable ‘id’, and so empty value of ‘id’ is displayed in the browser.

empty custom segment value

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, then the ASP.NET Core MVC framework will automatically pass the values obtained from these URL parameter variables to the parameters.

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

routes.MapRoute(
name: "MyRoute",
       template: "{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 id value to the ViewBag variable.

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

See the below image:

route constraint passed

In 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 which you will learn later. But for this tutorial it is enough to know just the basics.

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

For example Change the type of the ‘id’ parameter of ‘Check’ action method to sting.

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

Now when you request the URL – ‘http://localhost:58470/Home/Check/hello’ or ‘http://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.

In the below route I have made the id segment as optional:

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

When no value has been supplied for an optional segment variable, the value of the corresponding parameter will be null.

To test this change the Check Action method 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 (checked 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 – ‘http://localhost:58470/Home/Check/100’. You will see the ‘Null Value’ displayed on the browser as shown in the below image:

optional segment variable

In your Configure() method you can use the app.UseMvcWithDefaultRoute(); which is automatically setting the route we discussed above i.e. It is just a shortcut to set the below route:

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

’*catchall’ for Variable Length Routes

So far we have seen routes that can match URL with at most 3 segments. What if you have URL 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:

routes.MapRoute(
    name: "MyRoute",
    template: "{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 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:

routes.MapRoute(
    name: "MyRoute",
    template: "{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 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 catchall to the Action method. This parameter will contains the value of 4th, 5th, 6th,…. segments. ViewBag variable ‘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 ViewBag’s variable ‘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) is 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.

Next tutorial on URL Routing – Learn ASP.NET Core Route Constraint in details with lots of examples

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.