Chapter 7. Routing
Routing is one of the important concepts in the ASP.NET MVC application as it takes care of incoming requests and maps them to the appropriate controller's actions.
In this chapter, we are going to learn about the following topics:
- Using the
MapRoute
method to configure routing - Different types of routing with examples—convention and attribute-based
- Using HTTP verbs in attribute-based routing
We briefly discussed routing in Chapter 3 , Controllers. In this chapter, we are going to discuss routing along with several options available to customize it in ASP.NET Core.
Convention-based routing
The routing engine is responsible for mapping the incoming requests to the appropriate action method of the controller.
In the Configure
method of the Startup
class, we have mapped the following route:
app.UseMvc(routes => { routes.MapRoute(name: "default", template: "{controller=Employee}/{action=Index}/{id?}"); });
The MapRoute
method has two parameters:
name
: This represents the name of the route as we could configure multiple routes for the same application.template
: This signifies the actual configuration for the route. There are three parts to this configuration value. As we are supplying default parameters, if the values are not passed, it will take the default parameter values.{controller=Employee}
: The first value acts as the name of the controller and we use theEmployee
controller as the default controller when the controller value is not available in the URL.{action=Index}
: TheIndex
action method will be acting as the default action method and the second parameter from the URL will be taken as the action method name.{id?
}
: By specifying "?
" after theid
parameter, we are saying thatid
is the optional parameter. If the value is passed as the third parameter, theid
value will be taken. Otherwise, it would not be considered.
There is another method with the same functionality. The app.UseMvcWithDefaultRoute()
method configures the route "{controller=Employee}/{action=Index}/{id?}"
. But we have used the earlier method to show that we can customize the route as we want.
Let us see a few examples and observe how our routing engine works. We are assuming the following routing for the preceding examples:
"{controller=Employee}/{action=Index}/{id?}"
Example 1
URL-http://localhost:49831/
In this URL, we have not passed a value for the controller
, action
, or id
. Since we have not passed anything, it would take the default values for the controller and the action. So, the URL is converted into the following URL by the routing engine:
http://localhost:49831/Employee/Index
Example 2
URL-http://localhost:49831/Employee/
In this URL, we have passed the value for the controller
(the first parameter), which is Employee
, whereas we did not pass anything for action
method (the second parameter) or id
(the third parameter). So, the URL will be converted into the following URL by taking the default value for the action
method:
http://localhost:49831/Employee/Index
Example 3
URL-http://localhost:49831/Manager/List
The routing engine will take the first parameter, Manager
, as the controller
method name and the second parameter, List
, as the action
method name.
Example 4
URL-http://localhost:49831/Manager/Details/2
We have passed all three parameters in this URL. So, the first parameter value, Manager
, will be considered as the controller
method name, the second parameter value will be considered as the action
method name, and the third parameter value will be considered as the id
method name.
When defining the map route, we have used the MapRoute
method with a couple of parameters. The first parameter, name
, represents the name of the route and the second parameter, template
, represents the URL pattern to be matched along with the default values:
routes.MapRoute(name: "default", template: "{controller=Employee}/{action=Index}/{id?}");
There are other overloaded variations of this MapRoute
method. The following is another commonly overloaded MapRoute
method, where the incoming URL pattern and the default values are passed for different parameters. The name of the route is FirstRoute
and this route will be applied for all URLs starting with Home
. The default values for the controller and the action are Home
and Index2
respectively:
routes.MapRoute(name:"FirstRoute", template:"Home", defaults:new {controller ="Home", action="Index2"});
You can define any number of routing maps for your ASP.NET MVC application. There is no restriction or limit on the routing maps. Let us add another routing map to our application. We have added another route map called FirstRoute
to our application (highlighted in bold):
public void Configure(IApplicationBuilder app)
{
app.UseIISPlatformHandler();
app.UseMvc(routes =>
{
routes.MapRoute(name:"FirstRoute",
template:"Home", defaults:new {controller ="Home", action="Index2"});
routes.MapRoute(name: "default",
template: "{controller=Employee}/{action=Index}/{id?}");
});
}
And we have added another controller
method by the name HomeController
with a couple of simple action
methods returning different strings:
public class HomeController : Controller { // GET: /<controller>/ public IActionResult Index() { return Content("Index action method"); } public IActionResult Index2() { return Content("Index2 action method"); } }
When you try to access the application through the URL, http://localhost:49831/Hello
, both routing maps, FirstRoute
and the default
, match with the URL pattern.
Which map routing, do you think, will get applied in this scenario?
The routing engine maps the incoming URL based on the following factors:
- Matching pattern.
- On the order defined in the routing engine.
The first factor is an obvious one. For a routing map to be picked up by the routing engine, the pattern of the incoming URL should get matched with the defined template in the routing map.
The second factor is subtle but important. If more than one routing map matches with the incoming URL, the routing engine will pick the first URL as defined in the configuration. For example, if the incoming URL matches with both the FirstRoute
and default
maps, the routing engine will pick the FirstRoute
map as it was defined first in the configuration.
If the routing engine could not map the incoming URL to any of the mapping routes, we get an HTTP 404 error
, meaning that no resource could be found. You can see the status (200 means OK, 404 means No resource found) by looking at the Network tab in the developer tools as shown in the following screenshot:
Attribute-based routing
Until now, we have used convention-based routing. In convention-based routing, we define the routing templates (which are just parameterized strings) in a centralized place these are applicable to all the available controllers. The problem with convention-based routing is that, if we want to define different URL patterns for different controllers, we need to define a custom URL pattern that is common to all the controllers. This makes things difficult.
There is another option for configuring the routing engine-attribute-based routing. In attribute-based routing, instead of configuring all the routing in a centralized location, the configuration will happen at the controller level.
Let us see an example of attribute-based routing.
First, let us remove the convention-based routing that we created earlier in the Configure
method in the startup.cs
class file:
public void Configure(IApplicationBuilder app) { app.UseIISPlatformHandler(); app.UseMvc(); //app.UseMvc(routes => //{ // routes.MapRoute(name: "FirstRoute", // template: "Hello", // defaults: new { controller = "Home", // action = "Index2" }); // routes.MapRoute(name: "default", // template:" // {controller=Employee}/{action=Index}/{id?}"); //}); }
Then, we can configure the routing at the controller itself. In the following code, we have added the routing configuration for the home
controller that we created earlier:
namespace Validation.Controllers { public class HomeController : Controller { // GET: /<controller>/ [Route("Home")] public IActionResult Index() { return Content("Index action method"); } [Route("Home/Index3")] public IActionResult Index2() { return Content("Index2 action method"); } } }
We have used the Route
attribute in the action
methods of the controller. The value passed in the Route
attribute will be acting as the URL pattern. For example, when we access the URL http://localhost:49831/Home/
, the Index
method of HomeController
will be called. When we access the URL http://localhost:49831/Home/Index3
, the Index2
method of HomeController
will be called. Please note that the URL pattern and action
method name do not need to match. In the preceding example, we are calling the Index2
action method but the URL pattern uses Index3
, http://localhost:49831/Home/Index3
.
When you use attribute-based routing and convention-based routing together, attribute-based routing will take precedence.
Route attribute at the controller level
You will notice that, with the URL pattern for the action
methods, Index
and Index2
, we repeat the controller name, Home
, in both URL patterns, Home
and Home/Index3
. Instead of repeating the controller
method name (or any common part in the URL) at the action
method level, we can define it at the controller
level.
In the following code, the common part of the URL (Home
) is defined at the controller
level and the unique part is defined at the action
method level. When the URL pattern is getting mapped to the action
methods of the controller, both route parts (at the controller
level and at the action
method level) are merged and matched. So there will be no difference between the routes defined earlier and those that follow.
If you want two parameters in attribute-based routing, you can pass them within curly braces. In the following example, we did this for the SayHello
action method.
For example, the URL pattern http://localhost:49831/Home/Index3
, will still get mapped to Index2
method of the Homecontroller
:
namespace Validation.Controllers { [Route("Home")] public class HomeController : Controller { // GET: /<controller>/ [Route("")] public IActionResult Index() { return Content("Index action method"); } [Route("Index3")] public IActionResult Index2() { return Content("Index2 action method"); } [Route("SayHello/{id}")] public IActionResult SayHello(int id) { return Content("Say Hello action method"+id); } } }
Passing routing values in HTTP action verbs in the Controller
Instead of passing the routing values as Route
attributes, we can even pass the routing values in HTTP action verbs such as HTTPGet
and HTTPPost
.
In the following code, we have used the HTTPGet
attribute to pass the route values. For the Index
method, we did not pass any value and hence no route value will get appended to the route value defined at the controller
method level. For the Index2
method, we are passing the value Index3
and Index3
will get appended to the route value defined at the controller
level. Please note that only URLs with GET
methods will be mapped to the action
methods. If you access the same URL pattern with the POST
method, these routes will not get matched and hence these action
methods will not get called.
namespace Validation.Controllers { [Route("Home")] public class HomeController : Controller { // GET: /<controller>/ [HttpGet()] public IActionResult Index() { return Content("Index action method"); } [HttpGet("Index3")] public IActionResult Index2() { return Content("Index2 action method"); } } }
Route Constraints
Route Constraints enable you to constrain the type of values that you pass to the controller action. For example, if you want to restrict the value to be passed to the int
type int
, you can do so. The following is one such instance:
[HttpGet("details/{id:int?}")] public IActionResult Details(int id) { return View(); }
ASP.NET 5 (ASP.NET Core) even supports default parameter values so that you can pass the default parameters:
[HttpGet("details/{id:int = 123}")] public IActionResult Details(int id) { return View(); }
Summary
In this chapter, we have learned about routing and how it works. We learned about different kinds of routing available. We discussed convention-based routing and attribute-based routing with different examples. We also discussed route constraints and the default parameter values that could be passed.
In the next chapter, we are going to see how we can make the application look good.