We continue our column on the topic ASP.NET5 with a publication from Stanislav Boyarintsev ( masterL ) - a developer of corporate web systems from ItWebNet. In this article, Stanislav talks in great detail about the routing mechanism in ASP.NET5. Previous articles from the column can always be read on the link #aspnetcolumn - Vladimir Yunev
How was the routing system organized to ASP.NET 5
Routing to ASP.NET 5 was performed using the ASP.NET module
UrlRoutingModule . The module passed through a
collection of routes (usually objects of the
Route class) stored in the Routes static property of the
RouteTable class, selected a route that matched the current request and called the route handler that was stored in the RouteHandler property of the Route class - each registered route could have its own handler. In the MVC application, this handler was
MvcRouteHandler , which took on further work with the request.
Routes to the RouteTable.Routes collection were added during the application setup process.
Typical routing configuration code in an MVC application:
RouteTable.Routes.MapRoute( name: "Default", url: "{controller}/{action}/{id}", defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } );
Where
MapRoute is an extension-method declared in the System.Web.Mvc namespace, which added a new route to the Routes property in the Routes property using MvcRouteHandler as a handler.
')
We could do it on our own:
RouteTable.Routes.Add(new Route( url: "{controller}/{action}/{id}", defaults: new RouteValueDictionary(new { controller = "Home", action = "Index", id = UrlParameter.Optional }), routeHandler: new MvcRouteHandler()) );
How is the routing system in ASP.NET 5 organized: Short version
ASP.NET 5 no longer uses modules; for processing requests, the “middleware” entered as part of the transition to
OWIN (Open Web Interface) allows you to run ASP.NET 5 applications not only on the IIS server.
Therefore, routing is now performed using
RouterMiddleware . The entire project implementing routing
can be downloaded from github . Within this concept, the request is transferred from one middleware to another, in the order in which they are registered at the start of the application. When the request reaches the RouterMiddleware, it compares whether the requested Url address is appropriate for any registered route, and if it does, calls the handler of this route.
How is the routing system in ASP.NET 5 organized? Long option
In order to understand how the routing system works, let's connect it to an empty ASP.NET 5 project.
- Create an empty ASP.NET 5 project (by choosing Empty Template) and name it “AspNet5Routing”.
- Add dependencies ("dependencies") of the project in the project.json file "Microsoft.AspNet.Routing":
"dependencies": { "Microsoft.AspNet.Server.IIS": "1.0.0-beta5", "Microsoft.AspNet.Server.WebListener": "1.0.0-beta5", "Microsoft.AspNet.Routing": "1.0.0-beta5" },
- In the Startup.cs file, add the use of the Microsoft.AspNet.Routing namespace:
using Microsoft.AspNet.Routing;
- Add the necessary services (services that the routing system uses in its work) in the ConfigureServices method () of the Startup.cs file:
public void ConfigureServices(IServiceCollection services) { services.AddRouting(); }
- Finally, we configure the routing system in the Configure () method of the Startup.cs file:
public void Configure(IApplicationBuilder app) { var routeBuilder = new RouteBuilder(); routeBuilder.DefaultHandler = new ASPNET5RoutingHandler(); routeBuilder.ServiceProvider = app.ApplicationServices; routeBuilder.MapRoute("default", "{controller}/{action}/{id}"); app.UseRouter(routeBuilder.Build()); }
It is taken from an
example in the project of routing .
Let's analyze the last step in more detail:
var routeBuilder = new RouteBuilder(); routeBuilder.DefaultHandler = new ASPNET5RoutingHandler(); routeBuilder.ServiceProvider = app.ApplicationServices;
Create an instance of RouteBuilder and fill its properties. The interest is caused by the DefaultHandler property with the
IRouter type - judging by the name, it should contain a request handler. I put an instance of ASPNET5RoutingHandler into it - a query processor that I invented, let's create it:
using Microsoft.AspNet.Routing; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNet.Http; namespace AspNet5Routing { public class ASPNET5RoutingHandler : IRouter { public VirtualPathData GetVirtualPath(VirtualPathContext context) { } public async Task RouteAsync(RouteContext context) { await context.HttpContext.Response.WriteAsync("ASPNET5RoutingHandler work"); context.IsHandled = true; } } }
The IRouter interface requires us only two methods, GetVirtualPath and RouteAsync.
The GetVirtualPath method — we are familiar with from previous versions of ASP.NET — it was in the interface of the
RouteBase class from which the
Route class that represents the route inherited. This method was responsible for building Url (for example, when we called the ActionLink method:
Html .ActionLink (“link”, “Index”)).
And in the RouteAsync method, we process the request and write the result of the processing in Response.
The following line is the Configure method:
routeBuilder.MapRoute("default", "{controller}/{action}/{id}");
Like two drops of water, similar to the use of the MapRoute method in MVC 5, its parameters are the name of the added route and the pattern with which the requested Url will be matched.
The MapRoute () itself, like in MVC 5, is an
extension-method , and its call ultimately boils down to Creating an instance of the
TemplateRoute class and adding it to the Routes collection of our RouteBuilder object:
routeBuilder.Routes.Add(new TemplateRoute(routeCollectionBuilder.DefaultHandler, name,
What is interesting about the Routes property is the IRouter collection, that is, the TemplateRoute also implements the IRouter interface, just like the ASPNET5RoutingHandler we created, by the way, it is passed to the TemplateRoute constructor.
And finally the last line:
app.UseRouter(routeBuilder.Build());
Call routeBuilder.Build () - creates an instance of the
RouteCollection class and adds to it all the elements from the Route property of the RouteBuilder class.
And the app.UseRouter () method
turns out to be an extension-method, which actually connects
RouterMiddleware to the request processing pipeline, passing to it the RouteCollection object created and populated in the Build () method.
public static IApplicationBuilder UseRouter([NotNull] this IApplicationBuilder builder, [NotNull] IRouter router) { return builder.UseMiddleware<RouterMiddleware>(router); }
And judging by the constructor RouterMiddleware:
public RouterMiddleware( RequestDelegate next, ILoggerFactory loggerFactory, IRouter router)
The RouteCollection object also implements the IRouter interface, as well as ASPNET5RoutingHandler with TemplateRoute.
So we got the following matryoshka:
Our
ASPNET5RoutingHandler request handler is packaged in
TemplateRoute , the TemplateRoute itself or several TemplateRoute instances (if we called the MapRoute () method several times) are packaged in a
RouteCollection , and
RouteCollection is passed to the
RouterMiddleware designer and stored in it.
This completes the process of setting up the routing system, you can start the project, go to: "/ Home / Index / 1" and see the result: "ASPNET5RoutingHandler work".
Well, briefly go over what happens with the routing system during the incoming request:
When the queue reaches
RouterMiddleware , in the list of running middleware, it calls the
RouteAsync () method on the saved IRouter instance - this is an object of the
RouteCollection class.
RouteCollection, in turn, passes through the
IRouter instances stored in it - in our case it will be
TemplateRoute and call the
RouteAsync () method in them.
TemplateRoute checks whether the requested Url matches its template (passed in the TemplateRoute constructor: "{controller} / {action} / {id}") and if it matches, calls the instance of
IRouter stored in it - which is our
ASPNET5RoutingHandler .
We connect the routing system to the MVC application
Now let's see how the MVC Framework communicates with the routing system.
Create an empty ASP.NET 5 project again using the Empty template.
- Add dependencies ("dependencies") of the project in the project.json file "Microsoft.AspNet.Mvc":
"dependencies": { "Microsoft.AspNet.Server.IIS": "1.0.0-beta5", "Microsoft.AspNet.Server.WebListener": "1.0.0-beta5", "Microsoft.AspNet.Mvc": "6.0.0-beta5" },
- In the Startup.cs file , add the use of the Microsoft.AspNet.Builder namespace:
using Microsoft.AspNet.Builder;
The extensions methods we need for the MVC connection are in it.
- Add the services that the MVC Framework uses in its work: in the ConfigureServices () method of the Startup.cs file:
public void ConfigureServices(IServiceCollection services) { services.AddMvc(); }
- Configure the MVC application in the Configure () method of the Startup.cs file:
Three different methods are available to us:
one.
public void Configure(IApplicationBuilder app) { app.UseMvc() }
2
public void Configure(IApplicationBuilder app) { app.UseMvcWithDefaultRoute() }
3
public void Configure(IApplicationBuilder app) { return app.UseMvc(routes => { routes.MapRoute( name: "default", template: "{controller=Home}/{action=Index}/{id?}"); }); }
Let's immediately see the
implementation of these methods :
First method:
public static IApplicationBuilder UseMvc(this IApplicationBuilder app) { return app.UseMvc(routes => { }); }
Calls the third method, passing the delegate to Action <IRouteBuilder>, which does nothing.
The second method:
public static IApplicationBuilder UseMvcWithDefaultRoute(this IApplicationBuilder app) { return app.UseMvc(routes => { routes.MapRoute( name: "default", template: "{controller=Home}/{action=Index}/{id?}"); }); }
Also calls the third method, only the delegate Action <IRouteBuilder> adds the default route.
The third method:
public static IApplicationBuilder UseMvc( this IApplicationBuilder app, Action<IRouteBuilder> configureRoutes) { MvcServicesHelper.ThrowIfMvcNotRegistered(app.ApplicationServices); var routes = new RouteBuilder { DefaultHandler = new MvcRouteHandler(), ServiceProvider = app.ApplicationServices }; configureRoutes(routes);
It does the same thing as we did in the previous section when registering a route for your handler, only sets the
MvcRouteHandler instance as the final handler and makes a call to the
CreateAttributeMegaRoute method - which is responsible for adding the attributes set by the controllers' attributes and action methods (Attribute-Based Routing ).
Thus, all three methods will include Attribute-Based routing in our application, but in addition, the call to the second method will add the default route, and the third method allows you to specify any routes we need by passing them using a delegate (Convention-Based Routing).
Convention Based Routing
As I wrote above, it is configured by calling the
MapRoute () method - and the process of using this method has not changed since MVC 5 — we can transfer the name of the route, its pattern, default values ​​and restrictions to the
MapRoute () method.
routeBuilder.MapRoute("regexStringRoute",
Attribute-Based Routing
Unlike MVC 5, where the routing using attributes, it was necessary to specifically include, in MVC 6, it is enabled by default.
It should also be remembered that routes defined using attributes take precedence when searching for matches and choosing the right route (as compared to convention-based routes).
To set the route, you need to use the
Route attribute both on the action methods and on the controller (in MVC 5, the
RoutePrefix attribute was used to set the route on the controller).
[Route("appointments")] public class Appointments : ApplicationBaseController { [Route("check")] public IActionResult Index() { return new ContentResult { Content = "2 appointments available." }; } }
As a result, this method of action will be available at: "/ appointments / check".
Configure the routing system
In ASP.NET 5, a new service configuration engine called
Options -
GitHub project has appeared . It allows you to make some settings of the routing system.
The meaning of his work is that when setting up an application in the
Startup.cs file
, we transfer a certain object to the dependency registration system, with properties defined in a certain way, and when the application is running, the object gets and, depending on the values ​​of the properties set, work
The
RouteOptions class is used to configure the routing system.
For convenience, the
ConfigureRouting extension method is available to us:
public void ConfigureServices(IServiceCollection services) { services.ConfigureRouting( routeOptions => { routeOptions.LowercaseUrls = true;
“Behind the scenes,” it simply makes a call to the
Configure method by passing the delegate
Action <RouteOptions> to it :
public static void ConfigureRouting( this IServiceCollection services, Action<RouteOptions> setupAction) { if (setupAction == null) { throw new ArgumentNullException(nameof(setupAction)); } services.Configure(setupAction); }
Route pattern
The principles of working with the route pattern are the same as in MVC 5:
- Address segments are separated by a slash: firstSegment / secondSegment .
- The constant part of a segment is considered if it is not bordered by curly brackets, the correspondence of such a route to the requested Url occurs only if the same values ​​are present in the address: firstSegment / secondSegment - this route corresponds only to the address of the form: siteDomain / firstSegment / secondSegment .
- Variable parts of a segment are taken in curly brackets: firstSegment / {secondSegment} - the pattern will correspond to any two segment addresses, where the first segment is “firstSegment” and the second segment can be any character set (except slash - since this will mark the beginning of the third segment ):
"/ firstSegment / index"
"/ firstSegment / index-2"
- Restrictions for the variable part of the segment, as the name implies, limit the allowable values ​​of the variable segment and are set after the ":" character. Several variables can be imposed on one variable part, parameters are passed using parentheses: firstSegment / {secondSegment: minlength (1): maxlength (3)} . String designation of restrictions can be found in the GetDefaultConstraintMap () method of the RouteOptions class .
- In order to make the last segment “greedy”, so that it will absorb the rest of the address line, you need to use the * symbol: {controller} / {action} / {* allRest} - will correspond to the address: "/ home / index / 2 ", and the address:" / home / index / 2/4/5 ".
But in ASP.NET 5, the route pattern got some additional features:
- The ability to set right in it the default values ​​for variable parts of the route: {controller = Home} / {action = Index} .
- Set the optional part of the segment using the symbol?: {Controller = Home} / {action = Index} / {id?} .
Also, when using the route pattern in the attributes, changes occurred:
When setting up routing through attributes, the parameters designating the controller and the action method should now be addressed by taking them in square brackets and using the words "controller" and "action": "[controller] / [action]" - and you can only use them in such View - neither default values, nor restrictions, nor options, nor greed are allowed.
That is, it is allowed:
Route("[controller]/[action]/{id?}") Route("[controller]/[action]")
You can use them separately:
Route("[controller]") Route("[action]")
Not allowed:
Route("{controller}/{action}") Route("[controller=Home]/[action]") Route("[controller?]/[action]") Route("[controller]/[*action]")
The general scheme of the route pattern is as follows:
constantPart-{variablePart}/{paramName:constraint1:constraint2=DefaultValue?}/{*lastGreedySegment}
Conclusion
In this article, we went over the ASP.NET 5 routing system, looked at how it was organized, and connected it to an empty ASP.NET 5 project using our route handler. Dismantled ways to connect it to the MVC application and configuration using the Options mechanism. We stopped at the changes that have occurred in the use of Attribute-Based routing and route pattern.
To authors
Friends, if you are interested in supporting the column with your own material, please write to me at
vyunev@microsoft.com to discuss all the details. We are looking for authors who can interestingly tell about ASP.NET and other topics.

about the author
Boyarintsev Stanislav AleksandrovichLead .NET Programmer at ItWebNet, Kirov
masterL.NET programmer with 4 years of experience. Engaged in the development of corporate web systems. Professional interests: ASP.NET MVC.
Blog:
boyarincev.net