ASP.NET Core基本原理(2)-中间件
原文链接:Application Startup
什么是中间件
中间件是装配到应用管道中用来处理请求和响应的软件组件。管道中的每一个组件都可以选择是否将请求移交给下一个组件,并且可以在管道中调用下一个组件之前或者之后执行指定的操作。请求委托被用于构建请求管道。请求委托会处理每一个HTTP请求。
请求委托通过在传递给Startup
类中的Configure
方法的IApplicationBuilder
类型上使用Run
,Map
,Use
扩展方法进行配置。一个单独的请求委托可以被指定为一个内嵌的匿名方法,或定义在一个可重用的类中。这些可重用的类就是中间件或中间件组件。每个请求管道中的中间件负责调用管道中的下一个组件,或者在适当的时候将调用链短路。
通过IApplicationBuilder创建中间件管道
ASP.NET请求管道由一系列的请求委托所组成,一个接着一个被调用,如图所示,执行线程跟随着黑色箭头:
每一个委托在下一个委托调用之前或之后都有机会去执行操作。任何委托都可以选择停止传递请求到下一个委托,而自己处理该请求。这就是请求管道的短路,这种设计可以避免一些不必要的工作。例如,一个授权(Authorization)中间件只有在请求通过身份验证之后才能调用下一个委托;否则它就会被短路并返回"Not Authorized"的响应。异常处理委托必须在管道的早期被调用,这样它们就可以捕获到发生在管道内更深层次的异常。
打开默认的网站模板,Configure
方法添加了如下中间件组件:
- 错误处理(针对开发环境和非开发环境)
- 静态文件服务器
- 身份验证
- MVC
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文件夹中的文件都是可被公开访问的。如果你想让基于授权来提供这些文件:
- 将它们存放在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
你可以通过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) | ��对服务静态文件和目录浏览提供支持 |