📜 ⬆️ ⬇️

Preparing ASP.NET5, issue number 4 - details about routing

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.

  1. Create an empty ASP.NET 5 project (by choosing Empty Template) and name it “AspNet5Routing”.
  2. 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" }, 

  3. In the Startup.cs file, add the use of the Microsoft.AspNet.Routing namespace:

     using Microsoft.AspNet.Routing; 

  4. 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(); } 

  5. 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, //     "default" template, //     "{controller}/{action}/{id}" ObjectToDictionary(defaults), ObjectToDictionary(constraints), ObjectToDictionary(dataTokens), inlineConstraintResolver)); 

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.

  1. 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" }, 

  2. 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.

  1. 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(); } 

  2. 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); // Adding the attribute route comes after running the user-code because // we want to respect any changes to the DefaultHandler. routes.Routes.Insert(0, AttributeRouting.CreateAttributeMegaRoute( routes.DefaultHandler, app.ApplicationServices)); return app.UseRouter(routes.Build()); } 

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", //name "api/rconstraint/{controller}", //template new { foo = "Bar" }, //defaults new { controller = new RegexRouteConstraint("^(my.*)$") }); //constraints 

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; //  url    routeOptions.AppendTrailingSlash = true; //     url }); } 

“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:


But in ASP.NET 5, the route pattern got some additional features:

  1. The ability to set right in it the default values ​​for variable parts of the route: {controller = Home} / {action = Index} .
  2. 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 Aleksandrovich
Lead .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

Source: https://habr.com/ru/post/268037/


All Articles