ASP. NET Core MVC source code learning: Routing, mvcrouting
Preface
Recently, I plan to take a look at the source code of ASP. NET Core MVC. I would like to take a note of what I have learned.
Routing is the basic part of MVC, so before learning other MVC source code, you should first learn the routing system, ASP. the routing system of NET Core has changed a lot compared with the previous Mvc. it reintegrates Web Api and MVC.
Routing Source Code address: Routing-dev_jb51.rar
Routing
Routing is an important part of MVC. It is mainly responsible for ing received Http requests to a specific routing handler, in MVC, It is routed to the Action of a specific Controller.
The routing startup method is started as a middleware when the ASP. NET Core MVC application is started. The detailed information is provided in the next article.
In general, routes extract information from the requested URL address and then match the information to map it to a specific processing program, therefore, routing is a URL-based middleware framework.
A route also generates a response URL, that is, a link address can be generated for redirection or link.
The routing middleware mainly includes the following parts:
- URL matching
- URL generation
- IRouter Interface
- Routing Template
- Template Constraints
Getting Started
ASP. NET Core Routing is mainly divided into two projects:Microsoft.AspNetCore.Routing.Abstractions,Microsoft.AspNetCore.Routing
. The former is the abstraction of each function provided by a route, and the latter is the specific implementation.
When reading the source code, we suggest you first look at the project structure, find out the key classes, and then read them by the entry program.
Microsoft. AspNetCore. Routing. Abstractions
After reading the entire 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
In Microsoft.AspNetCore.Routing.Abstractions
One of the key interfaces is IRouter
:
public interface IRouter{ Task RouteAsync(RouteContext context); VirtualPathData GetVirtualPath(VirtualPathContext context);}
This interface is mainly used for two tasks. The first is to process routes based on the routing context, and the second is to obtain routes based on the virtual path context.VirtualPathData
.
IRouteHandler
Another key interface isIRouteHandler
According to the name, we can see that it is mainly an interface that is abstracted and defined for the routing handler model.
public interface IRouteHandler{ RequestDelegate GetRequestHandler(HttpContext httpContext, RouteData routeData);}
It returns RequestDelegate
This delegate may be familiar to everyone. It encapsulates the methods used to process Http requests.Microsoft.AspNetCore.Http.Abstractions
.
In this interfaceGetRequestHandler
There are two parameters for the method. The first parameter is HttpContext. I will not talk about it much, but let's take a look at the second parameter.RouteData
.
RouteData
, Encapsulates the data information in the current route. It contains three main attributes:DataTokens
,Routers
,Values
.
DataTokens
: Is the key-Value Pair Dictionary of some related attributes attached to the matching path.
Routers
: IsIlist<IRouter>
List, indicating that RouteData may contain vro.
Values: the key value in the current route path.
There is anotherRouteValueDictionary
It is a collection class, mainly used to store some data information in the route, not directly usedIEnumerable<KeyValuePair<string, string>>
This data structure should be complicated for its internal storage conversion. Its constructor receives an Object, and it will try to convert the Object into a set that can be recognized by itself.
IRoutingFeature
I can see the purpose of this interface at a Glance Based on the name of this interface. Do you remember that I mentioned a toolbox in the Http pipeline process in my previous blog?IRoutingFeature
It is also an integral part. Let's take a look at its definition:
public interface IRoutingFeature{ RouteData RouteData { get; set; }}
It turns out that he just wrappedRouteData
To HttpContext.
IRouteConstraint
When I read this interface, I took a look at the notes. The original parameter checks in the routing mainly rely on this interface.
We all know that when we write a Route Url expression, we sometimes write the following:Route("/Product/{ProductId:long}")
In this expression, there is {ProductId:long}
Parameter constraints, the main function implementation is achieved by 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.Routing
MainlyAbstractions
Is a major implementation of, we can read the code from its entry.
RoutingServiceCollectionExtensions
Is an extension 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 ){//... // construct a RouterBuilder and provide it to the action delegate Palace to configure var routeBuilder = new RouteBuilder (builder); action (routeBuilder); // call the following extension method, routeBuilder. build () See the return builder below. useRouter (routeBuilder. build ();} public static IApplicationBuilder UseRouter (this IApplicationBuilder builder, IRouter router ){//... return builder. useMiddleware <RouterMiddleware> (router );}
routeBuilder.Build()
Built a setRouteCollection
To save allIRouter
The processing program information, including the user-configured Router.
RouteCollection
It also implementsIRouter
So it also has the capability of routing processing.
The ingress of Routing middleware isRouterMiddleware
This class is registered to the Http pipeline processing process through this middleware, ASP. NET Core MVC uses it as a part of its configuration by default. Of course, you can also use Routing independently.
Let's take a look.Invoke
What is done in the method, it is 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); }}
First, use httpContext to initialize the RouteContext, and then add the route rules configured by the user to the Routers in the RouteData context.
Nextawait _router.RouteAsync(context)
Is usedIRouter
InterfaceRouteAsync
Method.
Follow upRouteAsync
What does this function do internally? We tracked it again.RouteCollection.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 is mine, I may not be able to come up with it, so we can learn a lot of new knowledge by reading the source code.
Why is design clever?RouteCollection
It inherits the IRouter but does not specifically process the route. Instead, it re-distributes the route context to the specific route Handler through a loop. Let's take a look at his process:
1. To improve performance, a RouteDataSnapshot snapshot object is created. RouteDataSnapshot is a struct that stores Route data information in the Route.
2. loop through the Router in the current RouteCollection, add it to the Routers in the RouterContext, and submit the RouterContext to the Router for processing.
3. When no handler processes the current route snapshot.Restore()
Reinitialize the snapshot status.
Next we will look at the specific route processing object.RouteBase
Start.
1. The RouteBase constructor will initializeRouteTemplate
,Name
,DataTokens
,Defaults
.
Ults is the default route parameter.
2,RouteAsync
Will perform a series of checks, if the URL does not match the corresponding routing will directly return.
3. Use the route Parameter RouteConstraintMatcher
If no match is found, the system returns the same result.
4. If the matching succeedsOnRouteMatched(RouteContext context)
Function, which is an abstract function. Route.cs
.
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 current route processing program. Which route processing program is it? Let's explore it together.
We know that we have a totalMapRoute
,MapGet
,MapPost
,MapPut
,MapDelete
,MapVerb
... Wait for this write method. Let's talk about how each of its route handlers works. The following is 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
: To use this method, you must assign a handler to DefaultHandler; otherwise, an exception will be thrown. Generally, we will use the RouteHandler class.
MapVerb
: MapPost, MapPut, and so on are similar to it. It provides the handler as a RequestDelegate delegate. That is to say, we actually process HttpContext on our own and will not process it through RouteHandler.
MapMiddlewareRoute
: You need to input an IApplicationBuilder delegate. In fact, after IApplicationBuilder is built, it is also a RequestDelegate. It will create a new RouteHandler class internally and then call MapRoute.
All these points point to RouteHandler. Let's take a look.RouteHandler
Right.
Public class RouteHandler: IRouteHandler, IRouter {//... slightly public Task RouteAsync (RouteContext context) {context. Handler = _ requestDelegate; return TaskCache. CompletedTask ;}}
Nothing is done, just assigning the passed RequestDelegate to the RouteContext handler.
Finally, the code will be executedRouterMiddleware
ClassInvoke
The last line of the method. await context.Handler(context.HttpContext),
At this time, the Handler delegate is called to execute user code.
Summary
Let's summarize the above process:
First, the incoming request is sent to the registered RouterMiddleware middleware, and then its RouteAsync calls the methods on each route in order. When a request arrives, the IRouter instance selects whether to processRouteContext Handler
A non-empty RequestDelegate on. If the Route has set a handler for the request, the Route processing will stop and start calling the configured Hanlder handler to process the request. If no handler is found for the current request, call next to send the request to the next middleware in the pipeline.
The routing template and the source code processing process of parameter constraints are different. If you are interested, you can directly look at the source code.
The above is all the content of this article. I hope it will be helpful for your learning and support for helping customers.