原文链接: Application Startup
中间件是装配到应用管道中用来处理请求和响应的软件组件。管道中的每一个组件都可以选择是否将请求移交给下一个组件,并且可以在管道中调用下一个组件之前或者之后执行指定的操作。请求委托被用于构建请求管道。请求委托会处理每一个HTTP请求。
请求委托通过在传递给 Startup
类中的 Configure
方法的 IApplicationBuilder
类型上使用 Run
, Map
, Use
扩展方法进行配置。一个单独的请求委托可以被指定为一个内嵌的匿名方法,或定义在一个可重用的类中。这些可重用的类就是 中间件 或 中间件组件 。每个请求管道中的中间件负责调用管道中的下一个组件,或者在适当的时候将调用链短路。
ASP.NET请求管道由一系列的请求委托所组成,一个接着一个被调用,如图所示,执行线程跟随着黑色箭头:
每一个委托在下一个委托调用之前或之后都有机会去执行操作。任何委托都可以选择停止传递请求到下一个委托,而自己处理该请求。这就是请求管道的短路,这种设计可以避免一些不必要的工作。例如,一个授权(Authorization)中间件只有在请求通过身份验证之后才能调用下一个委托;否则它就会被短路并返回”Not Authorized”的响应。异常处理委托必须在管道的早期被调用,这样它们就可以捕获到发生在管道内更深层次的异常。
打开默认的网站模板, Configure
方法添加了如下中间件组件:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { loggerFactory.AddConsole(Configuration.GetSection("Logging")); loggerFactory.AddDebug(); if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); app.UseDatabaseErrorPage(); app.UseBrowserLink(); } else { app.UseExceptionHandler("/Home/Error"); } app.UseStaticFiles(); app.UseIdentity(); app.UseMvc(routes => { routes.MapRoute( name: "default", template: "{controller=Home}/{action=Index}/{id?}"); }); }
上述代码(在非开发环境), UseExceptionHandler
是第一个被添加到管道的中间件,因此会捕获之后调用中出现的任何异常。
Static File Module
提供了无需授权检查的功能,由它服务的任何文件,包含在那些位于 wwwroot 文件夹中的文件都是可被公开访问的。如果你想让基于授权来提供这些文件:
FileResult
表示授权被应用的地方。 被静态文件模块处理的请求会在管道中被短路。如果请求不是通过静态文件模块来处理,那么它会被传给 Identity module
来执行身份验证。如果请求未通过验证,则管道将被短路。否则,将会调用管道的最后一站-MVC框架。
注:你添加中间件的顺序通常会影响它们对请求产生影响的顺序,然后在响应时会以相反的顺序。这对应用程序的安全、性能和功能都非常关键。在上面的代码中, Static File Module
在管道的早期被调用,这样可以及时短路,避免了请求进行到不必要的组件中。身份验证中间件在任何需要身份验证的处理请求之前被添加进来。异常处理必须在其它中间件之前被注册以便捕获其它组件的异常。
最简单的ASP.NET应用设置一个简单的请求委托来处理所有的请求。这样就不是一个真实的请求管道,通过调用一个简单的匿名函数来响应每一个HTTP请求。
app.Run(async context => { await context.Response.WriteAsync("Hello, World!"); });
App.Run
委托会终止管道,下面的盒子中,只有第一个委托会被运行。
public void Configure(IApplicationBuilder app) { app.Run(async context => { await context.Response.WriteAsync("Hello, World!"); }); app.Run(async context => { await context.Response.WriteAsync("Hello, World, Again!"); }); }
将多个请求委托链接在一起, next
参数表示管道内的下一个委托。你可以通过不调用 next 参数来终止(短路)管道。你通常可以在调用下一个委托之前和之后执行操作:
public void ConfigureLogInline(IApplicationBuilder app, ILoggerFactory loggerfactory) { loggerfactory.AddConsole(minLevel: LogLevel.Information); var logger = loggerfactory.CreateLogger(_environment); app.Use(async (context, next) => { logger.LogInformation("Handling request."); await next.Invoke(); logger.LogInformation("Finished handling request."); }); app.Run(async context => { await context.Response.WriteAsync("Hello from " + _environment); }); }
当应用程序运行的环境设置成 LogInline
时, ConfigureLogInline
方法会被调用。
在上述例子中,调用 await next.Invoke()
将会调用下一个委托 await context.Response.WriteAsync("Hello from " + _environment);
。客户端会收到所期望的响应(“Hello from LogInline”)。
你可以通过 Run
, Map
和 Use
来配置HTTP管道。 Run
方法将会短路管道(因为它不会调用 next
请求委托),因此 Run
方法应该只在管道结尾被调用。 Run
方法是一种惯例,有些中间件也会暴露它们自己的Run[Middleware]方法,你也必须在管道的末尾进行运行。下面两个中间件是相同的,其中一个使用 Use
版本的没有使用到 next
参数:
public void ConfigureEnvironmentOne(IApplicationBuilder app) { app.Run(async context => { await context.Response.WriteAsync("Hello from " + _environment); }); } public void ConfigureEnvironmentTwo(IApplicationBuilder app) { app.Use(async (context, next) => { await context.Response.WriteAsync("Hello from " + _environment); }); }
Map*
扩展被用于分支管道,当前的实现支持基于请求路径或使用谓词进行分支。 Map
扩展方法被用于匹配基于请求路径的请求委托。 Map
只接受一个路径和配置了一个单独中间件的管道的功能。在下面的例子中,任何基于 /maptest
基本路径的请求都会被 HandleMapTest
方法中所配置的管道所处理。
private static void HandleMapTest(IApplicationBuilder app) { app.Run(async context => { await context.Response.WriteAsync("Map Test Successful"); }); } public void ConfigureMapping(IApplicationBuilder app) { app.Map("/maptest", HandleMapTest); }
当使用了 Map
,每一个请求所匹配的路径段将从 HttpRequest.Path
中移除,并附加到 HttpRequest.PathBase
中。
除了基于路径的映射外, MapWhen
方法还支持基于谓词的中间件分支,允许以一种非常灵活的方式构造单独的管道。任何 Func<HttpContext, bool>
类型的谓词都可被用于将请求映射到一个新的管道分支。在下面的例子中,一个简单的谓词被用来检测字符变量 branch
是否存在:
private static void HandleBranch(IApplicationBuilder app) { app.Run(async context => { await context.Response.WriteAsync("Branch used."); }); } public void ConfigureMapWhen(IApplicationBuilder app) { app.MapWhen(context => { return context.Request.Query.ContainsKey("branch"); }, HandleBranch); app.Run(async context => { await context.Response.WriteAsync("Hello from " + _environment); }); }
上述配置中,任何包含 branch
的查询字符串的请求将使用定义在 HandleBranch
方法中的管道(将得到”Branch used.”的响应)。其它请求(不包含 branch
的查询字符串的请求)将被 await context.Response.WriteAsync("Hello from " + _environment);
所定义的委托所处理。
ASP.NET带来了下列中间件组件:
中间件 | 描述 |
---|---|
身份验证(Authentication) | 提供身份验证支持 |
跨域资源共享(CORS) | 配置跨域资源共享 |
路由(Routing) | 定义和约定请求路由 |
会话(Session) | 对管理用户会话提供支持 |
静态文件(Static Files) | 对服务静态文件和目录浏览提供支持 |
我的个人博客