Original: Interpretation of ASP. 5 & MVC6 Series (6): Middleware detailed
In the 1th chapter of the project structure analysis, we mentioned that Startup.cs
as an entry point for the entire program, it is equivalent to the traditional Global.asax
file, i.e., the information used to initialize the system level (for example, the routing configuration in MVC). In this chapter we will analyze how to initialize these system-level information here.
Pipeline differences between old and new versions
The biggest difference between ASP. NET 5 and previous versions is a new rewrite of the HTTP pipeline, in previous versions, where request filters are typically HttpModule
used for module components that respond to HttpApplication
events that are defined within each cycle for authentication, global error handling , log and other functions. The traditional form forms authentication is one HTTPModule
. HTTPModule
not only can Request
the request be filtered, but it can also interact with and Response
modify the response. These HttpModule components inherit from the IHttpModule interface, and the interface is System.Web.dll
in.
The HttpModule code can be added not only in the various event cycles in Global.asax, but also in a separate compilation into a class library and registered in Web. config.
The new version of the ASP. NET 5 discards the heavyweight System.Web.dll, and accordingly introduces Middleware
the concept, Middleware
which is officially defined as follows:
Pass through components The form a pipeline between a server and application to inspect, route, or modify request and res Ponse messages for a specific purpose.
Between the pipeline pipeline between the server and the application, multiple middleware components are interspersed for a specific purpose, and the request and response responses are checked
routing, or modification.
The definition and the traditional HttpModule
as well as the HttpHandler
special image.
Registration and configuration of middleware
In Asp.net5, access to the request pipeline (Pipeline) is performed in the startup class, when the class is a contract class, and the ConfigureServices
methods, Configure
methods, and corresponding parameters in it are also pre-agreed and cannot be changed.
Dependency Handling in Middleware: Configureservices method
Dependency injection is used in the various default middleware in Asp.net5, so when using functionality in middleware, it is necessary to register the type and mapping relationships required for dependency injection in the dependency injection management system, the Iservicecollection collection, and con The Figureservices method receives a parameter of type iservicecollection, which is a collection of all registered types. Managed through the native Dependency injection component (about dependency injection in Asp.net5, which we'll explain in a separate section), within which we can add new types and type mappings to the collection, as shown in the following example:
// Add MVC services to the services container.services.AddMvc();
The code in the example is used to add an MVC module-related service type to the system to support the MVC feature, which is an extension method for adding multiple MVC-related types to the collection.
Registration and configuration of middleware: Configure method
The signature of the Configure method is as follows:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerfactory){ // ...}
Configure
The method receives three parameters: the parameters of the IApplicationBuilder
type are used to build the configuration information for the entire application, the parameters of the class are used to access the contents of the IHostingEnvironment
env
system environment variables, the ILoggerFactory
type of loggerfactory
content processing for the log-related, the IApplicationBuilder
type of parameters is most important, The parameter instance app has a series of extension methods for registering various middleware in the request pipeline (Pipeline). The main difference between this approach and the previous HTTP pipeline in asp: The composite model in the new version replaces the event model in the old version. This also requires that in the new version of ASP. Middleware component registration is very important, because the latter component may be used to the previous component, so it must be registered in a dependent order, for example, the current MVC project template code example is as follows:
// Add static files to the request pipeline.app.UseStaticFiles();// Add cookie-based authentication to the request pipeline.app.UseIdentity();// Add MVC to the request pipeline.app.UseMvc(routes =>{ /*...*/});
In the example, the extension method, in the extension method, is UseStaticFiles
UseIdentity
called by the extension method method, and finally the UseMvc
IApplicationBuilder
method is app.UseMiddleware
called app.Use
to register the new middleware, the method is defined as follows:
public interface IApplicationBuilder{ //... IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware);}
By code, you can see that middleware is Func<RequestDelegate, RequestDelegate>
an instance of the Func that receives a RequestDelegate
parameter and returns a RequestDelegate
value of type. RequestDelegate
The source code is as follows:
public delegate Task RequestDelegate(HttpContext context);
Through the source code, we can see that it RequestDelegate
is a delegate function that receives an instance of the HttpContext
type and returns an Task
asynchronous object of type. That RequestDelegate
is, a function that can return a RequestDelegate
function of its own type. This is how the entire ASP. Pipelien is constructed, where each middleware is chained to the next middleware, and the object can be modified or maintained throughout the process, HttpConext
of course, HttpContext
Includes our often-manipulated HttpRequest
and HttpResponse
instance objects.
Note: HttpContext
, HttpRequest
HttpResponse
in ASP. NET 5, are new types that are redefined.
Definition of middleware
Since each middleare is Func<RequestDelegate, RequestDelegate>
an instance, is it not the definition of middleware to satisfy a rule? Is it inherited from an abstract base class or an excuse? By checking the relevant code, we see that middleware is defined in the form of a contract, with the following specific conventions:
- The first parameter of the constructor must be the next processing function in the processing pipeline, i.e. requestdelegate;
- You must have an Invoke function and accept the context parameter (that is, httpcontent), and then return to the Task;
Examples are as follows:
public class MiddlewareName{ RequestDelegate _next; public MiddlewareName(RequestDelegate next) { _next = next;// 接收传入的RequestDelegate实例 } public async Task Invoke(HttpContext context) { // 处理代码,如处理context.Request中的内容 Console.WriteLine("Middleware开始处理"); await _next(context); Console.WriteLine("Middleware结束处理"); // 处理代码,如处理context.Response中的内容 }}
The template code can be seen, first of all, a middleware constructor to receive an instance of Requestdelegate, first saved in a private variable, and then by calling the Invoke
method (and receiving the HttpContent
instance) and returning one Task
, and in the call Invoke
method, to pass the await _next(context);
statement, the chain to the next middleware, our processing code is mainly in the chain statement before and after the execution of the relevant code.
For example, if we want to record the execution time of a page, first we define a timerecordermiddleware with the following code:
public class TimeRecorderMiddleware{ RequestDelegate _next; public TimeRecorderMiddleware(RequestDelegate next) { _next = next; } public async Task Invoke(HttpContext context) { var sw = new Stopwatch(); sw.Start(); await _next(context); var newDiv = @"<div id=""process"">页面处理时间:{0} 毫秒</div></body>"; var text = string.Format(newDiv, sw.ElapsedMilliseconds); await context.Response.WriteAsync(text); }}
There are many ways to register middleware, and the following is the instance registration code:
app.Use(next => new TimeRecorderMiddleware(next).Invoke);
Alternatively, you can register using the Usemiddleware extension method, as shown in the following example:
app.UseMiddleware<TimeRecorderMiddleware>();//app.UseMiddleware(typeof(TimeRecorderMiddleware)); 两种方式都可以
Of course, you can also define an extension method of your own to register the middleware, with the following code:
public static IApplicationBuilder UseTimeRecorderMiddleware(this IApplicationBuilder app){ return app.UseMiddleware<TimeRecorderMiddleware>();}
Finally, in the startup class of the Configure method to register, the code is as follows:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerfactory){ app.UseTimeRecorderMiddleware(); // 要放在前面,以便进行统计,如果放在Mvc后面的话,就统计不了时间了。 // 等等}
Compile, restart, and access the page, and at the bottom of the page you will see the page's run-time prompt content.
Use of common middleware functions
App. Useerrorpage ()
In the case of ihostingenvironment.environmentname for development, the error message is displayed, and the type of error message is displayed, which can be set by an additional errorpageoptions parameter, can be set to display all, or it can be set to show only co One or more of okies, Environment, Exceptiondetails, Headers, Query, SourceCode sourcecodelinecount.
App. Useerrorhandler ("/home/error")
Captures all program exception errors and jumps the request to the specified page to achieve the purpose of friendly prompting.
App. Usestaticfiles ()
The ability to open a static file can also take the pipeline pipeline process.
App. Useidentity ()
Opens a cookie-based ASP. NET identity authentication feature to support pipeline request processing.
To define middleware functions directly using delegates
Since middleware is an instance of a Func<RequestDelegate, RequestDelegate>
delegate type, we can also avoid having to define a separate class, in Startup
which case it is possible to use a delegate invocation, as in the following example:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerfactory){ app.Use(new Func<RequestDelegate, RequestDelegate>(next => content => Invoke(next, content))); // 其它}// 注意Invoke方法的参数private async Task Invoke(RequestDelegate next, HttpContext content){ Console.WriteLine("初始化组件开始"); await next.Invoke(content); Console.WriteLine("管道下步执行完毕");}
Make a simple middleware base class
Although there are agreed methods, but sometimes we in the development of the time often confused, can not think of what is the contract, so, here we could define an abstract base class, and then all the middleware at the time of definition to inherit the abstract class and overload the Invoke method, So you can avoid the problems that the Convention forgets. The code is as follows:
/// <summary>/// 抽象基类/// </summary>public abstract class AbstractMiddleware{ protected RequestDelegate Next { get; set; } protected AbstractMiddleware(RequestDelegate next) { this.Next = next; } public abstract Task Invoke(HttpContext context);}/// <summary>/// 示例Middleware/// </summary>public class DemoMiddleware : AbstractMiddleware{ public DemoMiddleware(RequestDelegate next) : base(next) { } public async override Task Invoke(HttpContext context) { Console.WriteLine("DemoMiddleware Start."); await Next.Invoke(context); Console.WriteLine("DemoMiddleware End."); }}
Use the same method as above.
Terminates a chained call or blocks all middleware
In some cases, of course, according to certain conditions to judge, may not need to continue to carry on, but to the confidant to return the results, then you can ignore the call in your middleware await next.Invoke(content);
, direct use · The Response.writeasync method outputs the content.
In addition, in some cases, you may need to implement a function similar to handler in previous versions, that is, not often any pipeline directly responds to response, The new version of ASP. NET provides a run method to implement this function, only need to invoke the following code in the Configure method to achieve similar content output.
app.Run(async context =>{ context.Response.ContentType = "text/html"; await context.Response.WriteAsync("Hello World!");});
For content on the ASP. NET 5 runtime, please visit: https://msdn.microsoft.com/en-us/magazine/dn913182.aspx
Legacy issues
In an MVC project, all the dependency injection types are obtained through the IServiceProvider instance, which can now be obtained in the following form:
var services = Context.RequestServices; // Controller中var services = app.ApplicationServices; // Startup中
Once the instance has been obtained, you can obtain an object of a type by using the following methods:
var controller = (AccountController)services.GetService(typeof(AccountController));// 要判断获取到的对象是否为null
If you refer to the Microsoft.Framework.DependencyInjection namespace, you can also use the following three extension methods:
var controller2 = (AccountController)services.GetService<AccountController>(); // 要判断获取到的对象是否为null//如下两种方式,如果获取到的AccountController实例为null的话,就会字段抛异常,而不是返回nullvar controller3 = (AccountController)services.GetRequiredService(typeof(AccountController));var controller4 = (AccountController)services.GetRequiredService<AccountController>();
So that's the problem? How can I get to HttpContext and Iapplicationbuilder instances in order to use these dependency injection services without the startup and controller?
How do I get iapplicationbuilder instances?
Answer: In startup, the Iapplicationbuilder instance is saved in a singleton variable, and the whole station can be used later.
How do I get HttpContext instances?
Answer: Dependency injection of a generic class referencing a Dependency injection section
Reference: Http://www.mikesdotnetting.com/article/269/asp-net-5-middleware-or-where-has-my-httpmodule-gone
Synchronization and recommendations
This article has been synchronized to the Catalog index: Interpreting ASP. & MVC6 Series
Interpreting ASP 5 & MVC6 Series (6): Middleware detailed