Explain ASP Net core MVC source code learning: Routing

Time:2021-12-17

preface

I’m going to take time to look at ASP Net core MVC source code, I hereby record what I have learned, which can be regarded as a note.

Routing is a basic part of MVC, so before learning other MVC source codes, you should first learn the routing system, ASP Net core’s routing system has changed greatly compared with the previous MVC. It re integrates web API and MVC.

Routing source address:Routing-dev_jb51.rar

Introduction to routing function

Routing is an important part of MVC. It is mainly responsible for mapping the received HTTP requests to a specific routing handler, that is, to the action of a specific controller in MVC.

The route is started in ASP Net core MVC application is started as a middleware when it is started. Details will be given in the next article.

Generally speaking, routing extracts information from the requested URL address, and then matches it according to these information, so as to map it to the specific handler. Therefore, routing is a middleware framework based on URL.
Another function of routing is to generate the URL of the response, that is, to generate a link address for redirection or link.

Routing middleware mainly includes the following parts:

  1. URL match
  2. URL generation
  3. Irouter interface
  4. Routing template
  5. Template constraint

Getting Started

ASP. Net core routing is mainly divided into two projects:Microsoft.AspNetCore.Routing.Abstractions,Microsoft.AspNetCore.Routing。 The former is an abstraction of various functions provided by a route, and the latter is a concrete implementation.

In the process of reading the source code, I suggest we first roughly browse the project structure, then find out the key classes, and then read them by the portal program.

Microsoft.AspNetCore.Routing.Abstractions

After looking at the whole structure, I may find several key interfaces. Understanding the functions of these interfaces can help us get twice the result with half the effort in subsequent reading.

IRouter

stay Microsoft.AspNetCore.Routing.AbstractionsA key interface in is IRouter:


public interface IRouter
{
 Task RouteAsync(RouteContext context);

 VirtualPathData GetVirtualPath(VirtualPathContext context);
}

This interface mainly does two things: the first is to process routes according to the routing context, and the second is to obtain routes according to the virtual path contextVirtualPathData

IRouteHandler

Another key interface isIRouteHandlerAccording to the name, it is mainly an interface that abstracts and defines the routing handler model.


public interface IRouteHandler
{
 RequestDelegate GetRequestHandler(HttpContext httpContext, RouteData routeData);
}

It returns a RequestDelegate A delegate that you may be familiar with. It encapsulates the method of processing HTTP requests. It is located inMicrosoft.AspNetCore.Http.Abstractions Students who have read my previous blog should know better.

In this interfaceGetRequestHandler The method has two parameters. The first one is httpcontext. I won’t talk more about it. I’ll mainly look at the second parameterRouteData

RouteData, encapsulates the data information in the current route. It contains three main attributes, namelyDataTokens, Routers, Values

DataTokens: is the key value pair Dictionary of some related attributes attached to the matching path.

Routers: it’s aIlist<IRouter>List, indicating that routedata may contain sub routes.

Values: key values contained in the path of the current route.

One moreRouteValueDictionary, which is a collection class. It is mainly used to store some data information in the route. It is not used directlyIEnumerable<KeyValuePair<string, string>> This data structure should be for its internal storage. The conversion is complex. Its constructor receives an object object, and it will try to convert the object object into a collection that it can recognize.

IRoutingFeature

I can see the purpose of this interface at a glance according to the name of this interface. Remember that I mentioned something called toolbox in my blog about HTTP pipeline processIRoutingFeature It is also an integral part of it. Let’s take a look at its definition:


public interface IRoutingFeature
{
 RouteData RouteData { get; set; }
}

It turned out that he just packed itRouteData, into httpcontext.

IRouteConstraint

When reading this interface, I looked at the notes. It turns out that the parameter check in routing mainly depends on this interface.

We all know that when we write a route URL address expression, it is sometimes written as follows:Route("/Product/{ProductId:long}") , there is one in this expression {ProductId:long}Parameter constraints, then its main function implementation depends on this interface.


/// Defines the contract that a class must implement in order to check whether a URL parameter
/// value is valid for a constraint.
public interface IRouteConstraint
{
 bool Match(
  HttpContext httpContext,
  IRouter route,
  string routeKey,
  RouteValueDictionary values,
  RouteDirection routeDirection);
}

Microsoft.AspNetCore.Routing

Microsoft.AspNetCore.RoutingMainly rightAbstractions When we read the code, we can start from its entry.

RoutingServiceCollectionExtensions Is an extended ASP Net core Di is an extension class used for configservice in this method. Routing exposes an iroutingbuilder interface to allow users to add their own routing rules. Let’s take a look:

public static IApplicationBuilder UseRouter(this IApplicationBuilder builder, Action<IRouteBuilder> action)
{
 //... slightly
 
 //Construct a routerbuilder to provide action delegate configuration
 var routeBuilder = new RouteBuilder(builder);
 action(routeBuilder);
 
 //Call one of the following extension methods, routebuilder Build() see below
 return builder.UseRouter(routeBuilder.Build());
}

public static IApplicationBuilder UseRouter(this IApplicationBuilder builder, IRouter router)
{
  //... slightly
  
 return builder.UseMiddleware<RouterMiddleware>(router);
}

routeBuilder.Build() Build a collectionRouteCollection, used to save allIRouter Handler information, including user configured router.

RouteCollection It has also been realizedIRouter Therefore, it also has the ability of routing processing.

The entry of routing middleware isRouterMiddleware This class is registered in the pipeline processing flow of HTTP through this middleware, ASP Net core MVC will take it as a part of its configuration item by default. Of course, you can also take out routing separately.

Let’s take a lookInvoke What does the method do? It’s located inRouterMiddleware.cs File.


public async Task Invoke(HttpContext httpContext)
{
  var context = new RouteContext(httpContext);
  context.RouteData.Routers.Add(_router);

  await _router.RouteAsync(context);

  if (context.Handler == null)
  {
    _logger.RequestDidNotMatchRoutes();
    await _next.Invoke(httpContext);
  }
  else
  {
    httpContext.Features[typeof(IRoutingFeature)] = new RoutingFeature()
    {
      RouteData = context.RouteData,
    };

    await context.Handler(context.HttpContext);
  }
}

Firstly, the routing context is initialized through httpcontext, and then the user configured routing rules are added to the routes in the routing context routedata.

nextawait _router.RouteAsync(context) Yes, it isIRouter In the interfaceRouteAsync There’s no way.

We followedRouteAsync What does this function do internally? We tracked it againRouteCollection.cs This class:

Let’s take a look at the routeasync process:


public async virtual Task RouteAsync(RouteContext context)
{
  var snapshot = context.RouteData.PushState(null, values: null, dataTokens: null);

  for (var i = 0; i < Count; i++)
  {
    var route = this[i];
    context.RouteData.Routers.Add(route);

    try
    {
      await route.RouteAsync(context);

      if (context.Handler != null)
      {
        break;
      }
    }
    finally
    {
      if (context.Handler == null)
      {
        snapshot.Restore();
      }
    }
  }
}

I think this class, including function design, is very clever. If it’s me, I may not be able to think of it, so we can learn a lot of new knowledge by looking at the source code.

Why is the design ingenious?RouteCollection It inherits irouter, but does not specifically process the route. Instead, it redistributes the routing context to the specific routing handler through a loop. Let’s take a look at his process:

1. To improve performance, a routedatasnapshot snapshot snapshot object is created. Routedatasnapshot is a structure that stores the routing data information in the route.

2. Loop the routers in the current routecollection, add them to the routers in the routercontext, and then give the routercontext to the router for processing.

3. When no handler handles the current route snapshot.Restore() Reinitialize the snapshot state.

Next, let’s look at the specific routing processing objectsRouteBase Start.

1. The routebase constructor is initializedRouteTemplate, Name, DataTokens, Defaults.
Defaults is the default configured routing parameter.

2、RouteAsync A series of checks will be performed in. If there is no match to the route corresponding to the URL, it will be returned directly.

3. Using route parameter matcher RouteConstraintMatcher Match. If no match is found, return directly.

4. If the match is successful, it will be triggeredOnRouteMatched(RouteContext context)Function, which is an abstract function. The concrete implementation is located in Route.cs Yes.

Then we continue to trackRoute.cs Onroutematch in, let’s take a look:


protected override Task OnRouteMatched(RouteContext context)
{
  
  context.RouteData.Routers.Add(_target);
  return _target.RouteAsync(context);
}

_ Target is worth the handler of the current route, so which route handler is it? Let’s explore together.

We know that we create a total of routesMapRoute,MapGet,MapPost,MapPut,MapDelete,MapVerb… Wait, let’s talk about the routing processing procedures of each one. Here’s an example:


app.UseRouter(routes =>{
  routes.DefaultHandler = new RouteHandler((httpContext) =>
  {
    var request = httpContext.Request;
    return httpContext.Response.WriteAsync($"");
  });
          
  routes
  .MapGet("api/get/{id}", (request, response, routeData) => {})
  .MapMiddlewareRoute("api/middleware", (appBuilder) => 
             appBuilder.Use((httpContext, next) => httpContext.Response.WriteAsync("Middleware!")
           ))
  .MapRoute(
     name: "AllVerbs",
     template: "api/all/{name}/{lastName?}",
     defaults: new { lastName = "Doe" },
     constraints: new { lastName = new RegexRouteConstraint(new Regex("[a-zA-Z]{3}",RegexOptions.CultureInvariant, RegexMatchTimeout)) });
});

According to the above example,

MapRoute: in this way, the defaulthandler must be assigned a handler, otherwise an exception will be thrown. Usually, we will use the routehandler class.

MapVerb: mappost, mapput, etc. are similar to it. It provides the handler as a requestdelegate delegate, that is to say, we are actually processing httpcontext by ourselves and will not be processed by routehandler.

MapMiddlewareRouteYou need to import a IApplicationBuilder delegate. Actually IApplicationBuilder Build is also a RequestDelegate, which will be an RouteHandler class inside new and then MapRoute.

All these spearheads point to routehandler. Let’s take a lookRouteHandler All right.

public class RouteHandler : IRouteHandler, IRouter
{
  // ... slightly

  public Task RouteAsync(RouteContext context)
  {
    context.Handler = _requestDelegate;
    return TaskCache.CompletedTask;
  }
}

Nothing was done, but the incoming requestdelegate was assigned to the routecontext handler.

Finally, the code executes toRouterMiddleware ClassInvoke Last line of method await context.Handler(context.HttpContext),At this time, the handler delegate is called to execute the user code.

summary

Let’s summarize the above process:

First, the incoming request will go to the registered routermiddleware middleware, and then it routeasync calls the methods on each route in order. When a request arrives, the irouter instance selects whether the processing has been set toRouteContext Handler A non empty requestdelegate on. If route has set up a handler for the request, the route processing aborts and starts calling the set hanlder handler to process the request. If the current request attempts all routes and no handler is found, call next to hand over the request to the next Middleware in the pipeline.

The processing flow of routing template and parameter constraint source code will not be mentioned one by one. If you are interested, you can directly look at the source code.

The above is the whole content of this article. I hope it will be helpful to your study, and I hope you can support developpaer.

Recommended Today

Chapter 45 SQL command from (I)

Chapter 45 SQL command from (I) A select clause that specifies one or more tables to query. outline SELECT … FROM [optimize-option] table-ref [[AS] t-alias][,table-ref [[AS] t-alias]][,…] parameter optimize-optioN – optional – specifies a single keyword or a series of keywords separated by spaces for query optimization options (optimizer tips). The following keywords are supported:%ALLINDEX、%FIRSTTABLE […]