Introduction to ASP.NET Core part 9: MVC continued with routing

Introduction

In the previous post we continued our exploration of MVC in ASP.NET Core. We discussed how a controller action can return various response types that implement the IActionResult interface. The are many return types to choose from and many of them map to a standard HTTP status code like 200 OK, 201 Created etc. There are other response types such as ObjectResult that serialises the provided object using a serialiser which usually defaults to JSON. Finally we saw how to connect a controller action with a view that accepts an object. The view can use the injected object to render dynamic HTML. The default view file type in .NET Core is a Razor view with the cshtml file extension. We can mix HTML and C#-like syntax in a cshtml file. So we have managed to connect the various parts of the MVC pattern in our demo application: a URL is routed to a controller and an action method, the action method builds a model which is injected into a view and the view uses the model to build the final HTML that’s sent back to the user.

In this post we’ll take another look at routing.

Routing with the route builder

In preparation for the next tests let’s add a new controller called RouteController to the Controllers folder of the demo application DotNetCoreBookstore. Replace its default Index action method with the following:

public string Index()
{
	return "Hello from the Index action method of the Route controller"; 
}

We’ve already seen that the standard way of building routes is to use the UseMvc middleware. Here’s a reminder of our default route:

app.UseMvc(routeBuilder =>
	{
		routeBuilder.MapRoute("Default", "{controller=Home}/{action=Index}/{id?}");
	});

This covers many URL routing needs like /home/index/, /products/search/10 and the like. We can define the controller and action defaults in an anonymous object as well:

private void RegisterRoutes(RouteCollection routes)
{
     routeBuilder.MapRoute("Default", "{controller}/{action}/{id?}", new { controller = "Home", action = "Index"});
}

We can make ID compulsory and also define a data type by the ‘:’ delimiter as follows:

private void RegisterRoutes(RouteCollection routes)
{
     routeBuilder.MapRoute("Default", "{controller}/{action}/{id:int}", new { controller = "Home", action = "Index"});
}

If you run the demo application now then it won’t work because our Home/Index action method doesn’t have an id. If we add one to the signature…:

public string Index(int id)
{
	return "This is a killer homepage from the Home controller!!!!";
}

…and navigate to http://localhost:%5Bport%5D/home/index/20 then the index page will be rendered. Now remove the ID from the Index method and also make the id parameter in the default route optional again.

We can make the id parameter optional and also define a constraint on it, like it must be an integer if present as follows:

routeBuilder.MapRoute("Default", "{controller}/{action}/{id?}",
					defaults: new { controller = "Home", action = "Index" }
					, constraints: new { id = new IntRouteConstraint() });

IntRouteConstraint() is located in the Microsoft.AspNetCore.Routing.Constraints namespace. This namespace includes many more data type constraints. If you type…

routeBuilder.MapRoute("Default", "{controller}/{action}/{id?}",
					defaults: new { controller = "Home", action = "Index" }
					, constraints: new { id = new Constraint });

…in the constraints anonymous object then IntelliSense will provide a long list of constraint types like BoolRouteConstraint, GuidRouteConstraint, StringRouteConstraint, OptionalRouteConstraint and many more.

We can have hard-coded strings in the route templates like here:

routeBuilder.MapRoute("Route", "routing/{controller}/{action}");

The Index action of the Route controller can be reached as /routing/route/index .

Add a new action method to the Route controller called Activity:

public string Activity(string activity)
{
	return "This is the activity action method in the Route controller. Your activity: " + activity;
}

If we now navigate to /route/activity then the result will be…:

“This is the activity action method in the Route controller. Your activity: ”

…since we didn’t define any activity. If we try /route/activity/sport then it will fail since the id parameter must be an integer as we defined the default route above.

We can fortunately define multiple routes of course and the routing engine will test all of them until it finds a matching one. If none is found then an exception is thrown.

We can now test another URL routing feature with the ‘*’ catch-all route parameter. Add the following route to the UseMvc middleware:

routeBuilder.MapRoute("Route", "Route/{*activity}", defaults: new { controller = "Route", action = "Activity"});

We can now navigate to /route/activity/sport and the result will be “This is the activity action method in the Route controller. Your activity: activity/sport”.

The /route/activity were matched to the Route controller and the Activity action method even though there’s no placeholder for them. The defaults section defines these values. The hard-coded “Route” bit in the template defines that the URL must have /Route as the first section. E.g. /whatever/activity/sport won’t work. The “activity” bit in “*activity” is matched up with the “activity” parameter of the action method and catches all values after the “Route” section in the URL. In this case it will be assigned the value “activity/sport”. If we change the action method parameter “activity” to “whatever”…:

public string Activity(string whatever)
{
	return "This is the activity action method in the Route controller. Your activity: " + whatever;
}

…and navigate to /route/activity/sport then “whatever” will be null. The parameter name must match the routing template as follows:

routeBuilder.MapRoute("Route", "Route/{*whatever}", defaults: new { controller = "Route", action = "activity"});

The route will work as before and “whatever” will get the value “activity/sport” like in the previous example.

Routing with attributes

We also have the choice of declaring our routes by attributes on the controllers and action methods. For this example add another .NET Core MVC controller to the Controllers folder called AttributesController. Remove the default Index method and replace it with the following:

public string Index()
{
	return "Hello from the Index action method of the Attributes controller.";
}

The Microsoft.AspNetCore.Mvc namespace has a Route attribute class. Say that we want the Attributes controller to be matched with the resource name “mickeymouse”:

[Route("mickeymouse")]
public class Attributes : Controller
{
      public string Index()
      {
	     return "Hello from the Index action method of the Attributes controller.";
      }
}

…i.e. the URL to reach the Index method of the Attributes controller will be http://localhost:%5Bport%5D/mickeymouse. The default action method name of Index is still picked up correctly from the Default route declaration in Startup.cs. We can declare a different route for the Index method as well:

[Route("mickeymouse")]
public class Attributes : Controller
{        
	[Route("donaldduck")]
	public string Index()
	{
		return "Hello from the Index action method of the Attributes controller.";
	}
}

The correct URL will be http://localhost:%5Bport%5D/mickeymouse/donaldduck .

The tokens “controller” and “action” can also be used with attributes but they are surrounded by square-brackets instead of curly braces. The following is the most basic example:

[Route("[controller]/[action]")]
public class Attributes : Controller
{        
	public string Index()
	{
		return "Hello from the Index action method of the Attributes controller.";
	}
}

The correct URL for the Index action method will be http://localhost:%5Bport%5D/attributes/index .

We can assign multiple routes to the same controller or action:

[Route("mickeymouse")]
[Route("pluto")]
[Route("looneytunes")]
public class Attributes : Controller
{        
	[Route("donaldduck")]
	public string Index()
	{
		return "Hello from the Index action method of the Attributes controller.";
	}
}

The Attributes controller will be reachable by those three resource names, e.g. http://localhost:%5Bport%5D/pluto/donaldduck

There’s another group of attributes that correspond to HTTP verbs: HttpGet, HttpPost, HttpPut etc. They declare the HTTP verb that should be associated with a given action method. They also accept a route declaration. Example:

[HttpGet("donaldduck")]
public string Index()
{
	return "Hello from the Index action method of the Attributes controller.";
}

The HttpGet attribute will restrict the access to the Index action method to GET requests only. We’ll see a useful application of this type of attributes later on when we get to model binding and insertions.

We’ve covered the most common cases of routing in this post. There are many more peculiarities that are covered in the relevant Microsoft documentation pages here and here.

Read the next part of the series here.

View the list of MVC and Web API related posts here.

Advertisements

About Andras Nemes
I'm a .NET/Java developer living and working in Stockholm, Sweden.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

ultimatemindsettoday

A great WordPress.com site

Elliot Balynn's Blog

A directory of wonderful thoughts

Robin Sedlaczek's Blog

Developer on Microsoft Technologies

HarsH ReaLiTy

My goal with this blog is to offend everyone in the world at least once with my words… so no one has a reason to have a heightened sense of themselves. We are all ignorant, we are all found wanting, we are all bad people sometimes.

Softwarearchitektur in der Praxis

Wissenswertes zu Webentwicklung, Domain-Driven Design und Microservices

the software architecture

thoughts, ideas, diagrams,enterprise code, design pattern , solution designs

Technology Talks

on Microsoft technologies, Web, Android and others

Software Engineering

Web development

Disparate Opinions

Various tidbits

chsakell's Blog

Anything around ASP.NET MVC,WEB API, WCF, Entity Framework & AngularJS

Cyber Matters

Bite-size insight on Cyber Security for the not too technical.

Guru N Guns's

OneSolution To dOTnET.

Johnny Zraiby

Measuring programming progress by lines of code is like measuring aircraft building progress by weight.

%d bloggers like this: