asp.net core 2.1 中间件应用
中间件是一种装配到应用管道以处理请求和响应的软件。 每个组件:
- 选择是否将请求传递到管道中的下一个组件。
- 可在调用管道中的下一个组件前后执行工作。
请求委托(Request delegates)用于生成请求管道。 请求委托处理每个 HTTP 请求。
每个中间件扩展方法都通过 Microsoft.AspNetCore.Builder 命名空间在 IApplicationBuilder
上公开。
public class Startup
{
public void Configure(IApplicationBuilder app)
{
app.Run(async context =>
{
await context.Response.WriteAsync("Hello, World!");
});
}
}
中间件(Middleware)的作用
我们知道,任何的一个web框架都是把http请求封装成一个管道,每一次的请求都是经过管道的一系列操作,最终到达我们写的代码中。那么中间件就是在应用程序管道中的一个组件,用来拦截请求过程进行一些其他处理和响应。中间件可以有很多个,每一个中间件都可以对管道中的请求进行拦截,它可以决定是否将请求转移给下一个中间件。
asp.net core 提供了IApplicationBuilder接口来让把中间件注册到asp.net的管道请求当中去,中间件是一个典型的AOP应用。 下面是一个微软官方的一个中间件管道请求图:
可以看到,每一个中间件都都可以在请求之前和之后进行操作。请求处理完成之后传递给下一个请求。
中间件(Middleware)和过滤器(Filter)的区别
熟悉MVC框架的同学应该知道,MVC也提供了5大过滤器供我们用来处理请求前后需要执行的代码。分别是AuthenticationFilter,AuthorizationFilter,ActionFilter,ExceptionFilter,ResultFilter。
根据描述,可以看出中间件和过滤器的功能类似,那么他们有什么区别?为什么又要搞一个中间件呢?
其实,过滤器和中间件他们的关注点是不一样的,也就是说职责不一样,干的事情就不一样。
同作为两个AOP利器,过滤器更贴合业务,它关注于应用程序本身,比如你看ActionFilter 和 ResultFilter,它都直接和你的Action,ActionResult交互了,是不是离你很近的感觉,那我有一些比如对我的输出结果进行格式化啦,对我的请求的ViewModel进行数据验证啦,肯定就是用Filter无疑了。它是MVC的一部分,它可以拦截到你Action上下文的一些信息,而中间件是没有这个能力的。
什么情况我们需要中间件
那么,何时使用中间件呢?我的理解是在我们的应用程序当中和业务关系不大的一些需要在管道中做的事情可以使用,比如身份验证,Session存储,日志记录等。其实我们的 asp.net core项目中本身已经包含了很多个中间件。
中间件的运行方式
默认情况下,中间件的执行顺序根据Startup.cs文件中,在public void Configure(IApplicationBuilder app){} 方法中注册的先后顺序执行。
大概有3种方式可以在管道中注册"中间件"
app.Use()
,IApplicationBuilder
接口原生提供,注册等都用它。
app.Run()
,是一个扩展方法,它需要一个RequestDelegate
委托,里面包含了Http的上下文信息,没有next
参数,因为它总是在管道最后一步执行。
app.Map()
,也是一个扩展方法,类似于MVC的路由,用途一般是一些特殊请求路径的处理。如:www.example.com/token 等。
app.Run()
app.Run(async context =>
{
await context.Response.WriteAsync("Hello, World!");
});
//-------------------Run定义--------------------------
//Run传入一个RequestDelegate
public static class RunExtensions
{
public static void Run(this IApplicationBuilder app, RequestDelegate handler);
}
//RequestDelegate就是一个委托
public delegate Task RequestDelegate(HttpContext context);
app.Use();
app.Use(async (context, next) =>
{
await context.Response.WriteAsync(" ===>1.start.Hello World!");
await next();
await context.Response.WriteAsync(" ===>1.end.Hello World!");
});
//-------------------Use定义--------------------------
public static IApplicationBuilder Use(this IApplicationBuilder app,
Func<HttpContext, Func<Task>, Task> middleware);
案例:
app.Use(async (context, next) =>
{
await context.Response.WriteAsync(" ===>1.start.Hello World!");
await next();
await context.Response.WriteAsync(" ===>1.end.Hello World!");
});
app.Use(async (context, next) =>
{
await context.Response.WriteAsync(" ===>2.start.Hello World!");
await next();
await context.Response.WriteAsync(" ===>2.end.Hello World!");
});
app.Use(async (context, next) =>
{
await context.Response.WriteAsync(" ===>3.start.Hello World!");
//await next();
await context.Response.WriteAsync(" ===>3.end.Hello World!");
});
输出结果:
===>1.start.Hello World! ===>2.start.Hello World! ===>3.start.Hello World! ===>3.end.Hello World! ===>2.end.Hello World! ===>1.end.Hello World!
如果这里 await next();
没有注释
直接网络连接异常、网站服务器失去响应
因为,context.Response
里面有数据,所以 UseMvc时,判断有数据就不加载了。
app.Map()
其中,Map
会根据是否匹配指定的请求路径来决定是否在一个新的分支上继续执行后续的中间件,并且在新分支上执行完后,不再回到原来的管道上,MapWhen
则可以满足更复杂的条件,它接收Func<HttpContext,bool>
类型的参数,并以该参数作为判断条件,因此,它会对 HttpContext
对象进行更细致的判断(如是否包含指定的请求消息头等) ,然后决定是否进入新的分支继续执行指定的中间件。
而UseWhen
与 MapWhen
尽管接受的参数完全一致,但它不像 Map
和MapWhen
一样,由它创建的分支在执行完后会继续回到原来的管道上。下面的例子说明了 Map
方法的功能。
public void Configure(IApplicationBuilder app)
{
//===>1.start.Hello World! ===>Hello from non-Map delegate. <p> ===>1.end.Hello World!
app.Use(async (context, next) =>
{
await context.Response.WriteAsync(" ===>1.start.Hello World!");
await next();
await context.Response.WriteAsync(" ===>1.end.Hello World!");
});
//===>1.start.Hello World! ===>Map Test 1 ===>1.end.Hello World!
app.Map("/map1", HandleMapTest1);
//===>1.start.Hello World! ===>Map Test 2 ===>1.end.Hello World!
app.Map("/map2", HandleMapTest2);
//===>1.start.Hello World! ===>Branch used = master ===>1.end.Hello World!
app.MapWhen(context => context.Request.Query.ContainsKey("branch"),
HandleBranch);
app.Run(async context =>
{
await context.Response.WriteAsync(" ===>Hello from non-Map delegate. <p>");
});
}
private static void HandleMapTest1(IApplicationBuilder app)
{
app.Run(async context =>
{
await context.Response.WriteAsync(" ===>Map Test 1");
});
}
private static void HandleMapTest2(IApplicationBuilder app)
{
app.Run(async context =>
{
await context.Response.WriteAsync(" ===>Map Test 2");
});
}
private static void HandleBranch(IApplicationBuilder app)
{
app.Run(async context =>
{
var branchVer = context.Request.Query["branch"];
await context.Response.WriteAsync($" ===>Branch used = {branchVer}");
});
}
请求 | 响应 |
---|---|
https://localhost:5001/ | ===>1.start.Hello World! ===>Hello from non-Map delegate. ===>1.end.Hello World! |
https://localhost:5001/map1 | ===>1.start.Hello World! ===>Map Test 1 ===>1.end.Hello World! |
https://localhost:5001/map2 | ===>1.start.Hello World! ===>Map Test 2 ===>1.end.Hello World! |
https://localhost:5001/?branch=master | ===>1.start.Hello World! ===>Branch used = master ===>1.end.Hello World! |
MapWhen
基于给定谓词的结果创建请求管道分支。 Func<HttpContext, bool> 类型的任何谓词均可用于将请求映射到管道的新分支。
public static IApplicationBuilder MapWhen(this IApplicationBuilder app,
Func<HttpContext, bool> predicate,
Action<IApplicationBuilder> configuration);
封装中间件
上面的Run,Map内部也是调用的Use,算是对IApplicationBuilder接口扩充,如果你觉得名字都不够准确,那么下面这个扩展方法就是正宗的注册中间件的了,也是功能最强大的。
app.UseMiddleware<>()
,没错,就是这个了。 为什么说功能强大呢?是因为它不但提供了注册中间件的功能,还提供了依赖注入(DI)的功能,以后大部分情况就用它了。
namespace IdentityDemo2.Middleware
{
public class StartMiddleware
{
private readonly RequestDelegate _next;
public StartMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
await context.Response.WriteAsync(" ===>StartMiddleware");
// Call the next delegate/middleware in the pipeline
await _next(context);
}
}
public static class StartMiddlewareExtensions
{
public static IApplicationBuilder UseStart(
this IApplicationBuilder builder)
{
return builder.UseMiddleware<StartMiddleware>();
}
}
}
以下代码通过 Startup.Configure 调用中间件:
public class Startup
{
public void Configure(IApplicationBuilder app)
{
app.UseMiddleware<StartMiddleware>(); //写法1。
app.UseStart(); //写法2。
app.Run(async (context) =>
{
await context.Response.WriteAsync(
$"Hello {CultureInfo.CurrentCulture.DisplayName}");
});
}
}
参考:
zhulongxi,net core 中间件详解及项目实战