ASP. NET Core 2.0: 8. Pipe chart, core chart
This article uses a GIF animation to continue to talk about the request processing pipeline of ASP. NET Core, and provides a detailed study on the pipeline configuration, construction, and request processing process. (ASP. NET Core series directory)
I. Overview
As mentioned above, requests are forwarded to httpContext => Application to generate Response. The RequestDelegate type of this Application is essentially public delegate Task RequestDelegate (HttpContext context); that is, it receives HttpContext and returns the Task, which is composed of various middleware Func <RequestDelegate, requestDelegate> middleware is nested together. Its construction is completed by ApplicationBuilder. Let's take a look at this ApplicationBuilder:
1 public class ApplicationBuilder : IApplicationBuilder 2 { 3 private readonly IList<Func<RequestDelegate, RequestDelegate>> _components = new List<Func<RequestDelegate, RequestDelegate>>(); 5 public IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware) 6 { 7 _components.Add(middleware); 8 return this; 9 }11 public RequestDelegate Build()12 {13 RequestDelegate app = context =>14 {15 context.Response.StatusCode = 404;16 return Task.CompletedTask;17 }; 19 foreach (var component in _components.Reverse())20 {21 app = component(app);22 }24 return app;25 }26 }
ApplicationBuilder has a collection of IList <Func <RequestDelegate, RequestDelegate> _ components and a Use (Func <RequestDelegate, RequestDelegate> middleware) method used to add content to the collection, their types show that they are used to add and store middleware. Let's talk about the general process:
The final returned RequestDelegate type Application is the pipeline for processing HttpContext. This pipeline is composed of multiple middleware connected in a certain order. startupFilters will not talk about it, taking Startup, which we are very familiar with, as an example, its Configure method has successively performed UseBrowserLink, use1_exceptionpage, UseStaticFiles, UseMvc, and other methods by default. After the request enters the pipeline, requests are also processed by various middleware in this order. First, they enter UseBrowserLink, and then UseBrowserLink calls the next middleware UseDeveloperExceptionPage, after the request arrives at UseMVC in turn, it is processed to generate Response, and then reverse returns to the middleware in turn. Normally, after the request arrives at the MVC middleware, it is processed to generate Response and then reverse returns, instead of reaching the final 4 04. This 404 is an insurance operation to prevent other layers from being configured or not handled.
The pipe is like a tower. The saying goes that Tang Miao passes the Jinguang temple to sweep the Jinguang tower and begins to sweep from the front door to the first layer, then, the stairs at the front door enter the second, third, and fourth layers. Then, the backdoors on the fourth layer are scanned until the backdoors exit, after being swept to the 5th layer (top layer) by Tang Miao, he found that Foo was stolen by Xiaoer and bapol and shouted: Wukong, and foo was stolen by monsters! (404 ...)
The following uses the four images as an example to describe the entire process:
Figure 1
A "regular" pipeline is constructed and run in this way. You can see the relationship between the configuration sequence of each middleware in the Startup file and the sequence of the final pipeline, next we will create several middleware to try it out, and then let's take a look at the pipelines that are not "regular" long.
Ii. Custom Middleware
Write
public class FloorOneMiddleware { private readonly RequestDelegate _next; public FloorOneMiddleware(RequestDelegate next) { _next = next; } public async Task InvokeAsync(HttpContext context) { Console.WriteLine("FloorOneMiddleware In"); //Do Something //To FloorTwoMiddleware await _next(context); //Do Something Console.WriteLine("FloorOneMiddleware Out"); } }
This is the first layer of the tower. // Do Something after entering the first layer indicates the work to be done at the first layer, and then enters the second layer through _ next (context, the following // Do Something operation is performed after the second layer. The same is true for Layer 2 calls. Another Extension Method of UseFloorOne is as follows:
public static class FloorOneMiddlewareExtensions { public static IApplicationBuilder UseFloorOne(this IApplicationBuilder builder) { Console.WriteLine("Use FloorOneMiddleware"); return builder.UseMiddleware<FloorOneMiddleware>(); } }
In this way, you can also write app. UseFloorOne () in the Configure method of Startup. This middleware is part of the pipeline.
The preceding example is used to write a simple middleware following the system's default middleware. Here, you can also use a brief method to write it directly in the Configure method of Startup:
app.Use(async (context,next) =>{ Console.WriteLine("FloorThreeMiddleware In"); //Do Something //To FloorThreeMiddleware await next.Invoke(); //Do Something Console.WriteLine("FloorThreeMiddleware Out");});
You can also implement the work of the previous example. However, we recommend that you write the code in that way, which is concise and readable in Startup.
Copy the first and second examples to form the following code:
public void Configure(IApplicationBuilder app, IHostingEnvironment env) { app.UseFloorOne(); app.UseFloorTwo(); app.Use(async (context,next) => { Console.WriteLine("FloorThreeMiddleware In"); //Do Something //To FloorThreeMiddleware await next.Invoke(); //Do Something Console.WriteLine("FloorThreeMiddleware Out"); }); app.Use(async (context, next) => { Console.WriteLine("FloorFourMiddleware In"); //Do Something await next.Invoke(); //Do Something Console.WriteLine("FloorFourMiddleware Out"); }); if (env.IsDevelopment()) { app.UseBrowserLink(); app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler("/Home/Error"); } app.UseStaticFiles(); app.UseMvc(routes => { routes.MapRoute( name: "default", template: "{controller=Home}/{action=Index}/{id?}"); }); }
Run the following command to check the log:
1 CoreMiddleware> Use FloorOneMiddleware 2 CoreMiddleware> Use FloorTwoMiddleware 3 CoreMiddleware> Hosting environment: Development 4 CoreMiddleware> Content root path: C:\Users\FlyLolo\Desktop\CoreMiddleware\CoreMiddleware 5 CoreMiddleware> Now listening on: http://localhost:10757 6 CoreMiddleware> Application started. Press Ctrl+C to shut down. 7 CoreMiddleware> info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1] 8 CoreMiddleware> Request starting HTTP/1.1 GET http://localhost:56440/ 9 CoreMiddleware> FloorOneMiddleware In10 CoreMiddleware> FloorTwoMiddleware In11 CoreMiddleware> FloorThreeMiddleware In12 CoreMiddleware> FloorFourMiddleware In13 CoreMiddleware> info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[1]14 CoreMiddleware> Executing action method CoreMiddleware.Controllers.HomeController.Index (CoreMiddleware) with arguments ((null)) - ModelState is Valid15 CoreMiddleware> info: Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.ViewResultExecutor[1]16 CoreMiddleware> Executing ViewResult, running view at path /Views/Home/Index.cshtml.17 CoreMiddleware> info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[2]18 CoreMiddleware> Executed action CoreMiddleware.Controllers.HomeController.Index (CoreMiddleware) in 9896.6822ms19 CoreMiddleware> FloorFourMiddleware Out20 CoreMiddleware> FloorThreeMiddleware Out21 CoreMiddleware> FloorTwoMiddleware Out22 CoreMiddleware> FloorOneMiddleware Out23 CoreMiddleware> info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]24 CoreMiddleware> Request finished in 10793.8944ms 200 text/html; charset=utf-8
We can see that the Use FloorOneMiddleware and Use FloorTwoMiddleware in the first two rows write the corresponding middleware into the collection _ components, and the middleware itself is not executed, then lines 10 to 12 are processed by the custom example in sequence. Lines 13 to 18 are processed in the middleware MVC, and the corresponding Controller and View are found and called, then there is the reverse return of 19-22, and the finished Request returns status 200. This example again verifies the Request processing process in the pipeline.
In the case of 404, comment out all the four custom middleware in the Configure method and run it again.
1 // The preceding content does not change. 2 CoreMiddleware> kernel In 3 CoreMiddleware> kernel In 4 CoreMiddleware> kernel In 5 CoreMiddleware> kernel In 6 CoreMiddleware> kernel Out 7 CoreMiddleware> kernel Out 8 CoreMiddleware floorTwoMiddleware Out 9 CoreMiddleware> FloorOneMiddleware Out10 CoreMiddleware> info: microsoft. aspNetCore. hosting. internal. webHost [2] 11 CoreMiddleware> Request finished in 218.7216 ms 404
As you can see, the MVC processing part is gone because the middleware has been commented out, and the last one shows that the system returns status 404.
So since MVC can process requests normally without entering 404, how can we do this? Does it work without calling the next middleware? Try changing FloorFour
1 app.Use(async (context, next) =>2 {3 Console.WriteLine("FloorFourMiddleware In");4 //await next.Invoke();5 await context.Response.WriteAsync("Danger!");6 Console.WriteLine("FloorFourMiddleware Out");7 });
Run again and check that the output and the above are not significantly changed, but the last 404 is changed to 200. The "404" Page cannot be found .." It also becomes the "Danger!" That we want to output! ", To achieve the desired effect.
But in general, we do not write this way. ASP. NET Core provides three methods to configure pipelines: Use, Run, and Map.
3. Use, Run, and Map
If you have already used it before, you will not need to mention it. For the above problems, Run is generally used for processing. Run is mainly used as the end of the pipeline. For example, the above can be changed to this:
app.Run(async (context) =>{ await context.Response.WriteAsync("Danger!");});
Because it is used as the end of the pipeline, the next parameter is omitted. Although it can be implemented with use, we recommend using Run.
Map:
Static IApplicationBuilder Map (this IApplicationBuilder app, PathString pathMatch, Action <IApplicationBuilder> configuration); pathMatch is used to match the Request path, for example, "/Home", which must start, determine whether the path starts with pathMatch.
If yes, enter Action <IApplicationBuilder> configuration). Does this parameter look like the Configure method of startup? This is like entering another pipeline we configured. It is a branch, such
Figure 2
For example:
app.UseFloorOne();app.Map("/Manager", builder =>{ builder.Use(async (context, next) => { await next.Invoke(); }); builder.Run(async (context) => { await context.Response.WriteAsync("Manager."); });});app.UseFloorTwo();
After entering the first layer, a Map is added to request localhost: 56440./ManagerWhen the address such as/index (is it a bit like Area), it will enter the new branch created by this Map, and the result is that the page displays "Manager." will no longer enter the following FloorTwo. If it does not start with "/Manager", it continues to enter FloorTwo. Although we feel that this Map is flexible in our pipeline configuration, the method that can only match the beginning of path is too limited, don't worry, let's take a look at MapWhen.
Map When:
The MapWhen method is a flexible version of Map. It replaces the original PathMatch with a Func <HttpContext, bool> predicate, which is more open. It returns a bool value, now let's just change the chestnuts.
app.MapWhen(context=> {return context.Request.Query.ContainsKey("XX");}, builder =>{ //...TODO...}
Enter this branch when the request parameter contains "XX.
As shown in figure 2, once you enter the branch, you cannot return to the original branch. What if you want to enter some middleware under certain circumstances, but can continue the subsequent middleware after the execution? Compared with MapWhen, Use also has UseWhen.
UseWhen:
Like MapWhen, it enters a branch when the condition is met and continues the subsequent middleware after the branch is completed. Of course, the premise is that there is no short-circuit such as Run in the branch.
app.UseWhen(context=> {return context.Request.Query.ContainsKey("XX");}, builder =>{ //...TODO...}
Iv. IStartupFilter
We can only specify one Startup class as the Startup class. Can we define pipelines elsewhere? At the beginning of the article, the Configure method of startupFilters and _ startup will be called to build the pipeline, and multiple UseXXX methods defined in the method will be called to write the middleware into _ components. Define a StartupFilter to implement the Configure method of IStartupFilter. Its usage is similar to that of Configure of Startup. However, remember to call next (app) at last ).
1 public class TestStartupFilter : IStartupFilter 2 { 3 public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next) 4 { 5 return app => { 6 app.Use(async (context, next1) => 7 { 8 Console.WriteLine("filter.Use1.begin"); 9 await next1.Invoke();10 Console.WriteLine("filter.Use1.end");11 });12 next(app);13 };14 }15 }
Copy one and register it with ConfigureServices of startup:
public void ConfigureServices(IServiceCollection services){ services.AddMvc(); services.AddSingleton<IStartupFilter,TestStartupFilter>(); services.AddSingleton<IStartupFilter, TestStartupFilter2>();}
This configuration takes effect. Now let's take a look at its effective mechanism. Review the BuildApplication method of WebHost:
1 private RequestDelegate BuildApplication () 2 {3 //.... 4 var startupFilters = _ applicationServices is omitted. getService <IEnumerable <IStartupFilter> (); 5 Action <IApplicationBuilder> configure = _ startup. configure; 6 foreach (var filter in startupFilters. reverse () 7 {8 configure = filter. configure (configure); 9} 10 11 configure (builder); 12 13 return builder. build (); 14}
Taking a closer look at this code, it is actually very similar to the pipeline building process. Let's compare it with the following:
Is it similar to the flip splicing process in Figure 1? Is it because of the splicing order. Compare the execution sequence of the middleware after pipeline construction. After understanding it, you should be able to think of the execution sequence of Configure of each IStartupFilter and Startup. Yes, it is in the order of dependency injection: TestStartupFilter => TestStartupFilter2 => Startup.