Matthew Jones

Now that we've discussed some basics of routing in ASP.NET Core 3.0, we can move on to talking about convention-based routing in MVC.

"Convention-based" routing is a form of routing in which a small group of routes are defined and evaluated against URLs for matches. We define these routes in our Startup.cs file, and any request URL is evaluated to see if it matches any defined route. If such a match is found, the request is directed to the correct controller and action.

The AddControllersWithViews() method is new in ASP.NET Core 3.0, and adds MVC controller and views to the service layer so that we might use Dependency Injection (DI) for them. Note that it does not add services for Razor Pages or SignalR.

First is the route name, which is "account" in this case. This is a shorthand way of referring to this particular route, which we will use later when creating links for this route.

Second is the route pattern, which is the expected pattern of a URL that will match this route. In this case, the URL will be <root>/account/{id}, where the ID parameter is optional (as designated by the ? symbol).

Finally, we have the defaults. These are the controller and action that this route will map incoming requests to. In our case, the controller is Account and the action is Index.

With this URL, the second route will never be matched, because this URL matches the first route and that one will be evaluated first. Consequently, a URL of "/user/index/7" will never be redirected to the appropriate controller and action.

Because of this, you should define your routes in an order from most-specific to least-specific, possibly ending with a "catchall" route that is the basic route for your application. In this way, you can avoid situations like the (admittedly contrived) one above.

Multiple Parameters in Routes

Let's say we need multiple parameters passed to an action. In fact, let's say we have the following action:

This works perfectly fine! But what if we expect our action parameters to exist as part of the URL? Further, let's shorten the expected URL by removing the "/home" part. The URL we now expect looks like this:

/parameters/2/default/81

This is much cleaner, and depending on your usage may be much easier for your users to read and understand. To match this URL, we need a new route, and that route looks like the following:

The pattern for this route, "parameters/{level}/{type}/{id}" is looking for the value "parameters" in the first segment followed by three more segments, which be mapped to level, type, and ID respectively. If only two segments exist, this route will not be matched.

Mapping Routes to Areas

Areas are a feature of ASP.NET Core 3.0 that allow for "splitting" your apps in to different, dedicated sections, each with their own controllers, views, or Razor Pages. For example, you might have a "Blog" area, which would be located in the folder /Areas/Blog in your project, and that area would have its own controllers and views.

This route will match the Index() action in the controller. Note that this method wants us to explicitly specify the area name, because the route middleware needs to know what area to place matched URLs into.

These actions are distinct actions in C# because they have different parameters. However, it will be difficult to match them using the standard convention route.

Say our system encounters the following URL:

/account/indexambiguous/5

We know from reading the URL that we most likely mean to match the first action, the one with the integer parameter. However, the routing system in ASP.NET Core 3.0 cannot make that determination, and will throw an AmbiguousActionException when attempting to map this URL.

We can solve this problem in several ways, including renaming the actions, using Route Constraints, or specifying different, designated routes for each of these actions.

Example Route Matching

For our final example, let's see all the routes we defined in the sample project and show how many URLs will map to them. Here's all the routes defined:

Given these routes and in this order, let's see how some URLs would be matched.

Example #1: Simple Area

/blog/post/index/7

Matched. This route matches the first convention route exactly, meaning it would be mapped to the Blog area, Post controller, Index action, with ID 7.

Example #2: User

/user/19

Matched. This matches the "user" route, so it would be mapped to User controller, Index action, with ID 19.

Example #3: Blog Index

/blog/post/this-is-a-sample-post

Not matched. This URL does not match any of the defined routes, and so ASP.NET Core 3.0 will return 404 Not Found.

Example #4: Parameter Mismatch

/parameters/final/7/18

Matched, but with an issue. This is a tricky one. This will, in fact, match the "parameters" route, because it has the specified three segments. But ASP.NET Core will not be able to map "final" to an integer value for the level parameter, so instead it will ignore that segment and map the default value of the type (0 for integers) to the parameter.

Note that "7" is a valid string value, and will be correctly mapped to the type parameter.

Example #5: Blog Post without Title

/blog/post/article

Matched, and that is a problem. Which article are we referring to? Unless this is some kind of index page, we have no way to specify what article to display to the user. We would need to do some refactoring (either including the article identifier in the URL or having another way to access it, or changing the route entirely) to achieve what we intend.

Summary

Convention-based routing in ASP.NET Core 3.0 allows us to define a set of routes that "match" request URLs and their segments to appropriate controllers, actions, and parameters. This system is flexible, allowing for many uses and many routes.

Remember that:

We must enable routes in Startup.cs by using AddControllersWithViews() and UseEndpoints().

Routes are evaluated in the order they are defined. Define your routes in a most-specific to least-specific order.

You can define routes with default actions, controllers, and parameters.