ASP.NET Core Middleware (转载)
What is Middleware?
Put simply, you use middleware components to compose the functionality of your ASP.NET Core application. Anything your application does, even something as basic as serving up static files, is performed by middleware. An application without any middleware will simply return a 404 - Not Found response for every request. This gives you complete control over how requests are handled and lets you keep your application as lean as possible.
In this post, we’ll look at the following topics, which cover the basics of middleware:
- The Middleware Pipeline
- Using Standard Middleware
- Inline Middleware with Use and Run
- Branching with Map and MapWhen
- Custom Middleware
Next we’ll dive into the ASP.NET Core source code to see what is happening under the hood.
The Middleware Pipeline
As requests are received, they are processed by each middleware component in a construct commonly called the middleware pipeline. The term “pipeline” implies that requests enter one end, are processed by each middleware component in turn, and exit the other end. In reality, the run-time behavior is a variation of the Chain of Responsibility design pattern, with each middleware component having the option of acting on the HttpContext before and after calling the next component in the list:

Note: there is much more going on between the Client and first Middleware, which I will cover in a future post about hosting. For this discussion, the additional detail has been omitted to focus on the immediate topic of middleware.
Middleware components may also choose not to call the following component, instead short-circuiting the rest of the chain and returning immediately.
另外,需要注意的是,每当有请求到达ASP.NET Core服务器时,虽然ASP.NET Core的中间件(Middleware)都会按照注册顺序依次执行(如上面图所示),但是ASP.NET Core的中间件只会创建一次,并不是每当有请求到达ASP.NET Core服务器时,中间件也都会被重新创建。
举个例子,假如我们有下面CustomizedMiddleware中间件类:
public class CustomizedMiddleware
{
private readonly RequestDelegate next; public CustomizedMiddleware(RequestDelegate next)
{
this.next = next;
} public async Task Invoke(Microsoft.AspNetCore.Http.HttpContext context)
{
//Do something...
await next.Invoke(context);
//Do something...
}
}
假如现在有三个请求发送到了ASP.NET Core服务器,那么上面CustomizedMiddleware类中的Invoke方法相应地会执行三次,分别处理三个请求,但是CustomizedMiddleware类的构造函数只会执行一次,因为ASP.NET Core并不会为每次请求都重新创建CustomizedMiddleware中间件,每次请求都会复用ASP.NET Core进程中已经创建好的CustomizedMiddleware中间件对象实例。
Using Standard Middleware
ASP.NET Core comes with many middleware components out of the box, and you add them to your application in the Startup.Configure method as shown below:
public class Startup
{
public void Configure(IApplicationBuilder app)
{
app.UseStaticFiles();
app.UseWelcomePage();
}
}
In this case we are using the StaticFileMiddleware and WelcomePageMiddleware. Middleware components are executed in the order that they are added to the application. In this example, requests are processed by the StaticFileMiddleware first. If a file exists that matches the request, it will be returned and no other middleware components are called. If no matching file is found, the request passes to the WelcomePageMiddleware, which returns a welcome page for all requests it receives.
The standard middleware components typically have extension methods you can use to add them to the pipeline, such as UseStaticFiles() and UseWelcomePage(), which we saw above.
There are too many standard middleware components for me to document in this post, so for now I will simply list them and the project where they can be found on github.
| Component | Project |
|---|---|
| AnalysisMiddleware | Diagnostics |
| AntiforgeryMiddleware | Antiforgery |
| AuthenticationMiddleware | Security |
| ClaimsTransformationMiddleware | Security |
| CookieAuthenticationMiddleware | Security |
| CookiePolicyMiddleware | Security |
| CorsMiddleware | CORS |
| DatabaseErrorPageMiddleware | Diagnostics |
| DefaultFilesMiddleware | StaticFiles |
| DeveloperExceptionPageMiddleware | Diagnostics |
| DirectoryBrowserMiddleware | StaticFiles |
| ElmCaptureMiddleware | Diagnostics |
| ElmPageMiddleware | Diagnostics |
| ExceptionHandlerMiddleware | Diagnostics |
| FacebookMiddleware | Security |
| ForwardedHeadersMiddleware | BasicMiddleware |
| GoogleMiddleware | Security |
| HttpMethodOverrideMiddleware | BasicMiddleware |
| IISPlatformHandlerMiddleware | IISIntegration |
| JwtBearerMiddleware | Security |
| MapMiddleware | HttpAbstractions |
| MapWhenMiddleware | HttpAbstractions |
| MicrosoftAccountMiddleware | Security |
| MigrationsEndPointMiddleware | Diagnostics |
| OAuthMiddleware | Security |
| OpenIdConnectMiddleware | Security |
| ProxyMiddleware | Proxy |
| RequestLocalizationMiddleware | Localization |
| RequestServicesContainerMiddleware | Hosting |
| ResponseBufferingMiddleware | BasicMiddleware |
| RouterMiddleware | Routing |
| RuntimeInfoMiddleware | Diagnostics |
| SessionMiddleware | Session |
| StaticFileMiddleware | StaticFiles |
| StatusCodePagesMiddleware | Diagnostics |
| TwitterMiddleware | Security |
| WebSocketMiddleware | WebSockets |
| WelcomePageMiddleware | Diagnostics |
Inline Middleware with Use and Run
In addition to using predefined middleware components, you can define middleware inline with the Use and Run extension methods.
Use adds middleware, which may call the next middleware component in the pipeline. Run adds terminating middleware, that is, middleware that will never call subsequent middleware.
In the following example, the middleware defined inline with Use writes out a message, then calls the next middleware, and then writes out another message after the next middleware returns. The middleware defined with Run simply writes out a message and returns. If any middleware is added after Run, it will never be called.
public class Startup
{
public void Configure(IApplicationBuilder app)
{
app.Use(async (context, next) =>
{
await context.Response.WriteAsync("<html><body>");
await context.Response.WriteAsync("<div>In first middleware, before next()</div>");
await next();
await context.Response.WriteAsync("<div>In first middleware, after next()</div>");
await context.Response.WriteAsync("</body></html>");
}); app.Run(async context => { await context.Response.WriteAsync("<div>In second middleware</div>"); });
}
}
Running this application and browsing to it, we see the expected messages:

Branching with Map and MapWhen
There are two middleware components we can use to branch the pipeline: MapMiddleware and MapWhenMiddleware.
MapMiddleware lets you branch based on the route and can be added to the pipeline with the Map extension method. In the following example, “Hello… ” will always be written, then, if the first path segment is either “/foo” or “/bar”, “Foo” or “Bar” will be written respectively. If neither matches, “World” will be written. Finally, a closing exclamation point is written before the response is returned.
public class Startup
{
public void Configure(IApplicationBuilder app)
{
app.Use(async (context, next) =>
{
await context.Response.WriteAsync("Hello... ");
await next();
await context.Response.WriteAsync("!");
}); app.Map(new PathString("/foo"), branch =>
{
branch.Run(async context => { await context.Response.WriteAsync("Foo"); });
}); app.Map(new PathString("/bar"), branch =>
{
branch.Run(async context => { await context.Response.WriteAsync("Bar"); });
}); app.Run(async context => { await context.Response.WriteAsync("World"); });
}
}
MapWhenMiddleware lets you branch based on a predicate and can be added to the pipeline with the MapWhen extension method. In this example, we use MapWhen to implement the commonly used FizzBuzz interview question.
public class Startup
{
public void Configure(IApplicationBuilder app)
{
var count = ;
var isFizz = false;
var isBuzz = false; app.Use(async (context, next) =>
{
count++;
isFizz = count % == ;
isBuzz = count % == ; await context.Response.WriteAsync($"{count} -> ");
await next();
}); app.MapWhen(context => isFizz, branch =>
{
branch.Run(async (context) =>
{
await context.Response.WriteAsync("Fizz");
if (isBuzz)
{
await context.Response.WriteAsync("Buzz");
}
});
}); app.MapWhen(context => isBuzz, branch =>
{
branch.Run(async (context) => await context.Response.WriteAsync("Buzz"));
}); app.Run(async (context) => await context.Response.WriteAsync($"{count}"));
}
}
Custom Middleware
You only need to meet two requirements to create your own middleware components:
- Your middleware class must have a non-static public constructor with a RequestDelegate parameter. This will be a reference to the next component in the pipeline.
- Your middleware class must have a public method named Invoke that takes an HttpContext and returns a Task.
Let’s take a look at a simple custom middleware component. The ArnoldQuotesMiddleware writes out a random movie quote from Arnold Schwarzenegger. This is a terminating component; it does not call the next component in the pipeline because Arnold always gets the last word. I have included the code you would use to hold the reference to the next middleware component, but the actual call is commented out.
Here is the constructor for the middleware class. Note that we can include other parameters, which will be provided from the services collection (DI).
public ArnoldQuotesMiddleware(RequestDelegate next, ILoggerFactory loggerFactory)
{
_next = next;
_logger = loggerFactory.CreateLogger<ArnoldQuotesMiddleware>();
}
And here is the Invoke method, which writes out a quote from a list. Invoke can take additional parameters in addition the the HttpContext, and they will be provided using services registered with the DI container.
public async Task Invoke(HttpContext context)
{
var arnoldSays = _quotes[(int)(DateTime.Now.Ticks%_quotes.Count)]; _logger.LogWarning(arnoldSays); await context.Response.WriteAsync(arnoldSays);
//await _next(context);
}
We’ll also create an extension method to follow convention and make it easier to use our middleware:
public static void UseArnoldQuotes(this IApplicationBuilder app)
{
app.UseMiddleware<ArnoldQuotesMiddleware>();
}
Now we can use the extension method to add our ArnoldQuotesMiddleware:
public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)
{
loggerFactory.MinimumLevel = LogLevel.Warning;
loggerFactory.AddConsole(LogLevel.Warning); app.UseArnoldQuotes();
}
You can download the code for this project here:
If you are curious about the two requirements we need to meet for our custom middleware, take a look at the implementation of the UseMiddleware<TMiddleware> method in the ASP.NET Core source code.
Configuring the Middleware Pipeline
What happens when we add middleware to the IApplicationBuilder instance in the Configure method? IApplicationBuilder defines only one method to add middleware, so whether we use an extension method or define middleware inline, all roads lead to this method:
IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware);
As mentioned earlier, RequestDelegate describes a function that processes an HttpContext asynchronously:
public delegate Task RequestDelegate(HttpContext context);
A middleware component can be composed of any number of classes, but at some point, a RequestDelegate is needed to add the middleware to the application. You can think of the RequestDelegate as the entry point into the middleware component.
Going back to the IApplicationBuilder.Use method, you can think of its argument as a configuration function, which takes the entry point to the next component in the pipeline, does whatever work is needed to construct and configure the current middleware component, and returns a RequestDelegate, which will be used to access the middleware currently being configured.
Looking at the implementation of Use in ApplicationBuilder, we see it simply adds the provided configuration function to a private list:
private readonly IList<Func<RequestDelegate, RequestDelegate>> _components = new List<Func<RequestDelegate, RequestDelegate>>(); public IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware)
{
_components.Add(middleware);
return this;
}
其实上面的Use(Func<RequestDelegate, RequestDelegate> middleware)方法在注册一个中间件的时候就会被调用到,假设现在你自己开发了个自定义中间件类CustomizedMiddleware如下所示:
class CustomizedMiddleware
{
private readonly RequestDelegate next; public CustomizedMiddleware(RequestDelegate next)
{
this.next = next;
} public async Task Invoke(Microsoft.AspNetCore.Http.HttpContext context)
{
//Do something...
await next.Invoke(context);
//Do something...
}
}
然后当你在调用
app.UseMiddleware<CustomizedMiddleware>();
注册你的CustomizedMiddleware到ASP.NET Core时,实际上就会调用上面说的Use(Func<RequestDelegate, RequestDelegate> middleware)方法,其参数Func<RequestDelegate, RequestDelegate> middleware也是由ASP.NET Core自动构造并传入的,构造的逻辑类似如下:
//nextMiddlewareRequestDelegate是在CustomizedMiddleware中间件之后注册的中间件的Invoke方法
Func<RequestDelegate, RequestDelegate> middleware = (nextMiddlewareRequestDelegate) =>
{
//当然实际过程中ASP.NET Core肯定是用反射来new下面的CustomizedMiddleware类,并且会在new之前构造好要传入CustomizedMiddleware构造函数的依赖注入接口参数
var currentMiddleware = new CustomizedMiddleware(nextMiddlewareRequestDelegate);
return currentMiddleware.Invoke;
};
Building the Middleware Pipeline
Now that we’ve seen how functions are added to configure middleware, let’s take a look at how the pipeline is built. IApplicationBuilder declares a method named Build(), which is called by the host during startup:
RequestDelegate Build();
Looking at the implementation of Build() in ApplicationBuilder we see it does the following:
- Sets up a RequestDelegate to return a response. This will be the last RequestDelegate in the pipeline and explains why you get a 404 response if you don’t add any middleware to your application.
- Iterates through the middleware configuration functions in reverse order. For each configuration function, the currently referenced RequestDelegate is passed in as the next component in the pipeline. The RequestDelegate returned by the configuration function is then assigned to that reference since it is now the first in the pipeline.
- Finally, after all of the configuration functions have been processed, the first RequestDelegate is returned and this will be called to process any requests received by the application.
public RequestDelegate Build()
{
RequestDelegate app = context =>
{
context.Response.StatusCode = ;
return Task.FromResult();
}; foreach (var component in _components.Reverse())
{
app = component(app);
} return app;
}
ASP.NET Core Middleware (转载)的更多相关文章
- ASP.NET Core Middleware 抽丝剥茧
一. 宏观概念 ASP.NET Core Middleware是在应用程序处理管道pipeline中用于处理请求和操作响应的组件. 每个组件是pipeline 中的一环. 自行决定是否将请求传递给下一 ...
- Prerender Application Level Middleware - ASP.NET Core Middleware
In the previous post Use Prerender to improve AngularJS SEO, I have explained different solutions at ...
- [ASP.NET Core] Middleware
前言 本篇文章介绍ASP.NET Core里,用来处理HTTP封包的Middleware,为自己留个纪录也希望能帮助到有需要的开发人员. ASP.NET Core官网 结构 在ASP.NET Core ...
- Cookies, Claims and Authentication in ASP.NET Core(转载)
Most of the literature concerning the theme of authentication in ASP.NET Core focuses on the use of ...
- SessionState in ASP.NET Core(转载)
问: In asp.net mvc we used to decorate controller for disabling session state by using attribute as [ ...
- Property Injection in Asp.Net Core (转载)
问: I am trying to port an asp.net application to asp.net core. I have property injection (using ninj ...
- Implementing Azure AD Single Sign-Out in ASP.NET Core(转载)
Let's start with a scenario. Bob the user has logged in to your ASP.NET Core application through Azu ...
- ASP.NET Core Middleware管道介绍
public void Configure(IApplicationBuilder app, IHostingEnvironment env) { app.Use(async (context, ne ...
- ASP.NET Core In Process Hosting on IIS with ASP.NET Core 2.2(转载)
ASP.NET Core 2.2 has been out for a while now and with it come some significant improvements to the ...
随机推荐
- Redis实现分布式锁2
redisTemplate实现分布式锁 /** * 分布式锁-加锁 * @param key * @param value 当前时间+超时时间 System.currentTimeMillis()+t ...
- [POI2007]EGZ-Driving Exam
能到达所有路的充要条件是能到达左右两端的路 用vector反向建边对每条路左右分别求个最长不上升子序列 预处理出每条路向左向右分别需要多建多少路才能到达最左端和最右端 然后跑个\(\Theta(n)\ ...
- UOJ#414. 【APIO2018】新家
传送门 首先二分答案 \(mid\),问题变成求区间 \([l-mid,r+mid]\) 在该年份的不同类型个数为 \(k\) 关于年份的限制可以离线下来 现在的问题就是区间数颜色,一个套路就是维护每 ...
- 如何在iview组件中使用jsx
最近选用的框架iview表单组件的render写法让人有点不习惯,尤其是在写比较复杂的逻辑的时候,还是感觉模板式的写法比较方便且可读性较强.而render函数除了支持配置写法外,还支持jsx的写法.由 ...
- VScode基础设置
安装依赖包: • One Monokai • Aglia • One Dark Pro • Material Icon 漂亮的主题: Themes Quokka 是一个调试工具插件,能够根据你正在 ...
- WiFiDog 与 AuthServer
背景 在一些公共场所(比如公交车.地跌.机场等)连接当地的 WiFi 时会弹出一个验证表单,输入验证信息(比如短信验证码)后就能够通过该 WiFi 联网. 本文将介绍通过 OpenWrt WiFiDo ...
- 获取JPEGImageEncoder和JPEGCode这两个类
最近要对PDF做一些操作,在查看别人代码,拿过来借用的时候,由于代码不完整,引用的类也不全,导致JPEGImageEncoder和JPEGCode这两个类找不到,后来网上搜索了下,发现这两个类来自于J ...
- Android Editable
在android的sdk中有讲,“This is the interface for text whose content and markup can be changed (as opposed ...
- How to safely downgrade or remove glibc with yum and rpm
Environment Red Hat Enterprise Linux 5 Red Hat Enterprise Linux 6 glibc, glibc-common, glibc-devel, ...
- Intel酷睿前世今生(二)
上一文,讲述到了酷睿构架的诞生.可以显而易见的知道,酷睿构架其实源于笔记本处理器构架.因为在当年的技术趋势中,因为提升主频而带来的负面影响如发热与高功率已经让普通消费者所不满.然而提升主频并没有提升多 ...