筛选器
1.筛选器(filter,也可以翻译为“过滤器”)是ASP.NET Core中提供的一种切面编程机制,它允许开发人员创建自定义筛选器来处理横切关注点,也就是在ASP.NET Core特定的位置执行我们自定义的代码,比如在控制器的操作方法之前执行数据检查的代码,或者在ActionResult执行的时候向响应报文头中写入自定义数据等。
2.ASP.NET Core中的筛选器有以下5种类型:授权筛选器(Authorization filter)、资源筛选器(Resource filter)、操作筛选器(Action filter)、异常筛选器(Exception filter)、结果筛选器(Result filter)。
3.所有筛选器一般有同步和异步两个版本,在大部分场景下,异步筛选器的性能更好,而且可以支持在实现类中编写异步调用的代码。
异常筛选器
当系统中出现未经处理的异常的时候,异常筛选器就会执行,我们可以在异常筛选器中对异常进行处理。
这样的异常信息只有客户端才知道,网站的运维人员不知道这个异常的存在,我们需要在程序中把未处理异常记录到日志中。
1.当系统中出现未处理异常的时候,我们需要统一给客户端返回如下格式的响应报文:{“code”:”500”,”message”:”异常信息”}。对于开发环境中message是异常堆栈,对于其他环境message用一个general的报错信息。
2.实现IAsyncExceptionFiler接口。注入IHostEnvironment得知运行环境。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| public class MyExceptionFilter : IAsyncExceptionFilter { private readonly ILogger<MyExceptionFilter> logger; private readonly IHostEnvironment env;
public MyExceptionFilter(ILogger<MyExceptionFilter> logger, IHostEnvironment env) { this.logger = logger; this.env = env; }
public Task OnExceptionAsync(ExceptionContext context) { Exception exception = context.Exception; logger.LogError(exception,"UnhandledException occured"); string message; if(env.IsDevelopment()) { message = exception.ToString(); } else { message= "程序中出现未处理异常"; } ObjectResult result = new ObjectResult(new { code = 500, message = message }); result.StatusCode = 500; context.Result = result; context.ExceptionHandled = true; return Task.CompletedTask; } }
|
设置context.ExceptionHandled = true;通过这样的方式来告知ASP.NET Core不再执行默认的异常响应逻辑。
我们在Program.cs的builder.Build之前添加全局的筛选器。
1 2 3 4
| builder.Services.Configure<MvcOptions>(options => { options.Filters.Add<MyExceptionFilter>(); });
|
MvcOptions是ASP.NET Core项目的主要配置对象,我们在第3行代码中向Filters注册全局的筛选器,这样,项目中所有的ASP.NET Core中的未处理异常都会被MyExxceptionFilter处理。用这种方式注入的筛选器是由依赖注入机制进行管理的,因此我们可以通过构造方法为筛选器注入其他的服务。
操作筛选器基础
每次ASP.NET Core中控制器的操作器的操作方法执行的时候,操作筛选器都会被执行,我们可以在操作方法执行之前和执行之后执行一些代码,完成特定的功能。
操作筛选器一般实现IAsyncActionFilter接口,这个接口定义OnActionExecutionAsync方法。
1
| Task OnActionExecutionAsync (ActionExecutingContext context, ActionExecutionDelegate next)
|
其中,context参数代表Action执行的上下文对象,从context中我们可以获取请求的路径、参数值等信息;next参数代表下一个要执行的操作筛选器。一个项目中可以注册多个操作筛选器,这些操作筛选器组成一个链,上一个筛选器执行完了再执行下一个。next就是一个用来指向下一个筛选器的话,next就会执行要执行的操作方法。

1.编写一个实现了IAsyncActionFilter接口的类MyActionFilter1.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public class MyActionFilter1 : IAsyncActionFilter { public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) { Console.WriteLine("MyActionFilter1:开始执行"); ActionExecutedContext r = await next(); if(r.Exception != null) { Console.WriteLine("MyActionFilter1:发生异常"); } else { Console.WriteLine("MyActionFilter1:执行结束"); } } }
|
next的返回值是操作方法的执行结果,返回值是ActionExecutedContext类型的。如果操作方法执行的时候出现了未处理异常,那么ActionExecutedContext的Exception属性就是异常对象,ActionExecutedContext的Result属性就是操作方法的执行结果。
2.编写一个和MyActionFilter1类似的类MyActionFilter2.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public class MyActionFilter2 : IAsyncActionFilter { public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) { Console.WriteLine("MyActionFilter2:开始执行"); ActionExecutedContext r = await next(); if(r.Exception != null) { Console.WriteLine("MyActionFilter2:发生异常"); } else { Console.WriteLine("MyActionFilter2:执行结束"); } } }
|
3.在Program.cs中注册这两个操作筛选器。
1 2 3 4 5 6
| builder.Services.Configure<MvcOptions>(options => { options.Filters.Add(new MyActionFilter1()); options.Filters.Add(new MyActionFilter2()); });
|
4.在控制其中增加一个要测试的操作方法。
1 2 3 4 5 6
| [HttpGet] public string Get() { Console.WriteLine("TextController:Get方法被调用"); return "Hello, World!"; }
|
运行

案例:自动启动事务的操作筛选器
1.数据库事务:要么全部成功、要么全部失败。
2.自动化:启动、提交以及回滚事务。
3.当一段使用EF Core进行数据库操作的代码放到TransactionScope声明的范围中的时候,这段代码就会自动被标记为“支持事务”。
4.TransactionScope实现了IDisposale接口,如果一个TransactionScope的对象没有调用Conmplete()就执行了Dispose()方法,则事务会被回滚,否则事务就会被提交。
5.TransactionScope还支持嵌套式事务。
6..NET Core中的TransactionScope不像.NET FX一样有MSDTC分布式事务提升的问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public class DemoController : ControllerBase { private MyDbContext ctx;
public DemoController(MyDbContext ctx) { this.ctx = ctx; } [HttpGet] public async Task<string> Test1() { using (TransactionScope tx=new TransactionScope()) { ctx.Books.Add(new Book { Name = ".Net1", Price = 1 }); await ctx.SaveChangesAsync(); ctx.Personcs.Add(new Personcs { Name = "YOUXIANYU", Age = 19 }); await ctx.SaveChangesAsync(); return "ok"; } } }
|
我们编写的操作方法中,可能不希望有的方法自动启用事务控制,可以给这些操作方法添加一个自定义的NotTransactionlAttribute。
1 2 3 4
| [AttributeUsage(AttributeTargets.Method)] public class NotTransactionalAttribute:Attribute { }
|
然后,开发筛选器TransactionScopeFilter,其OnActionExecutionAsync方法的实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public class TransactionScopeFilt : IAsyncActionFilter { public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) { bool hasNotTransactionalAttribute = false; if (context.ActionDescriptor is ControllerActionDescriptor) { var actionDesc = (ControllerActionDescriptor)context.ActionDescriptor; hasNotTransactionalAttribute = actionDesc.MethodInfo.IsDefined(typeof(NotTransactionalAttribute), false); } if (hasNotTransactionalAttribute) { await next(); return; } using var txScope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled); var result = await next(); if (result.Exception == null) { txScope.Complete(); } } }
|
配置Program.cs程序集
1 2 3 4
| builder.Services.Configure<MvcOptions>(options => { options.Filters.Add(typeof(TransactionScopeFilt)); });
|
案例:开发请求限流器
1.Action Filter可以在满足条件的时候终止操作方法的执行。
2.在Action Filter中,如果我们不调用await next(),就可以终止Action方法的执行了。
3.为了避免而已客户端频繁发送大量请求消耗服务器资源,我们要实现“一秒钟内只允许最多有一个来自同一个IP地址的请求”。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| public class RateLimitFilter : IAsyncActionFilter { private readonly IMemoryCache memCache;
public RateLimitFilter(IMemoryCache memCache) { this.memCache = memCache; }
public Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) { string removeIP = context.HttpContext.Connection.RemoteIpAddress.ToString(); string cacheKey = $"LastVisitTick_{removeIP}"; long? lastTick = memCache.Get<long?>(cacheKey); if (lastTick == null || Environment.TickCount64 - lastTick > 1000) { memCache.Set(cacheKey, Environment.TickCount64, TimeSpan.FromSeconds(10)); return next(); } else { context.Result = new ContentResult { StatusCode = 429, Content = "请求过快,请稍后再试" }; return Task.CompletedTask; } } }
|
启用内存缓存
1 2 3 4
| builder.Services.Configure<MvcOptions>(options => { options.Filters.Add(typeof(RateLimitFilter)); });
|
中间件
中间件是ASP.NET Core的核心组件,MVC框架、相应缓存、身份验证、CORS、Swagger等都是内置中间件。

什么是中间件
1.广义上来讲:Tomcat、WebLogic、Redis、ISS;狭义上来讲,ASP.NET Core中的中间件指ASP.ENT Core中的一个组件。
2.中间件由前逻辑、next、后逻辑3部分组成,前逻辑为第一段要执行的逻辑代码、next为指向下一个中间件的调用、后逻辑为从下一个中间件执行返回所执行的逻辑代码。每个HTTP请求都要经历一个系列中间件的处理,每个中间件对于请求进行特定的处理后,再转到下一个中间件,最终的业务逻辑代码执行完成后,相应的内容也会按照处理的相反顺序进行处理,然后形成HTTP响应报文返回给客户端。
3.中间件组成一个管道,整个ASP.ENT Core的执行过程就是HTTP请求和相应按照中间件组装的顺序在中间件之间流转的过程。开发人员可以对组成管道的中间件按照需要进行自由组合。
中间件的三个概念
Map、Use和Run。Map用来定义一个管道可以处理那些请求,Use和Run用来定义管道,一个管道由若干个Use和一个Run组成,每个Use引入一个中间件,而Run是用来执行最终的核心应用逻辑。

基本中间件使用
创建一个ASP.NET Core空项目。
简单的自定义中间件
为了能够更清晰地了解中间件,我们创建一个空的ASP.NET Core项目,然后手动添加中间件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| app.Map("/test", async (pipeBuilder) => { pipeBuilder.Use(async (context, next) => { context.Response.ContentType = "text/html"; await context.Response.WriteAsync("1 start<br/>"); await next.Invoke(); await context.Response.WriteAsync("1 end<br/>"); }); pipeBuilder.Use(async (context, next) => { await context.Response.WriteAsync("2 start<br/>"); await next.Invoke(); await context.Response.WriteAsync("2 end<br/>"); }); pipeBuilder.Run(async context => { await context.Response.WriteAsync("Run<br/>"); }); });
|
需要注意的是,按照微软的建议,如果我们在一个中间件中使用ctx.Response.WriteAsync等方式向客户端发送响应,我们就不能再执行next.Invoke把请求转到其他中间件了。因为其他中间件有可能对Response进行了更改,比如修改响应状态码、修改报文头或者向响应报文中写入其他数据,这样就会造成响应报文体被损坏的问题。因此,在代码中的中间件中,我们在向报文体中写入内容后,又执行next.Invoke是不推荐的行为。
中间件类
1.如果定义中间件的代码比较复杂,或者需要重复使用一个中间件的话,最好把中间的代码放到一个单独的类中,这样的类我们称之为“中间件类”。
2.中间件类是一个普通的.NET类,它不需要继承任何父类或者实现任何接口,但是这个类需要有一个构造方法,构造方法至少有一个RequertDelegate类型的参数,这个参数用来指向下一个中间件。这个类还需要定义一个名字为Invoke或InvokeAsync的方法,方法至少有一个HttpContext类型的参数,方法的返回值必须是Task类型。中间件类的构造方法和Invoke(或InvokeAsync)方法还可以定义其他参数,其他参数的值会通过依赖注入自动赋值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class TestIMddleware { private readonly RequestDelegate next; public TestIMddleware(RequestDelegate next) { this.next = next; } public async Task InvokeAsync(HttpContext context) { await context.Response.WriteAsync("TestIMddleware start<br/>"); await next.Invoke(context); await context.Response.WriteAsync("TestIMddleware end<br/>"); } }
|
开发一个简单的中间件类,这个中间件类会检查请求中是否有password为123的查询字符串,而且会把请求报文体按照JSON格式尝试解析为dynamic类型的对象,并且把pynamic对象放入context.Items中供后续的中间件或者Run使用。
JsonSerializer.Deserialize<dynamic>
把json反序列化为dynamic类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| public class CheckMiddleware { private readonly RequestDelegate next; public CheckMiddleware(RequestDelegate next) { this.next = next; } public async Task InvokeAsync(HttpContext context) { string pwd = context.Request.Query["passwork"]; if (pwd == "123") { if (context.Request.HasJsonContentType()) { var reqStream = context.Request.BodyReader.AsStream(); var obj = JsonSerializer.Deserialize<dynamic>(reqStream); context.Items["BodyJson"] = obj; } await next(context); } else { context.Response.StatusCode = 401; await context.Response.WriteAsync("没有权限访问"); } } }
|
使用中间件类CheckAndParsingMiddleware,修改后的Program.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| app.Map("/test", async (pipeBuilder) => { pipeBuilder.Use(async (context, next) => { context.Response.ContentType = "text/html"; await context.Response.WriteAsync("1 start<br/>"); await next.Invoke(); await context.Response.WriteAsync("1 end<br/>"); }); pipeBuilder.Use(async (context, next) => { await context.Response.WriteAsync("2 start<br/>"); await next.Invoke(); await context.Response.WriteAsync("2 end<br/>"); }); pipeBuilder.UseMiddleware<TestIMddleware>(); pipeBuilder.Run(async context => { await context.Response.WriteAsync("Run<br/>"); dynamic obj = context.Items["BodyJson"]; if(obj != null) { await context.Response.WriteAsync($"BodyJson: {obj}<br/>"); } }); pipeBuilder.Run(async context => { await context.Response.WriteAsync("Run<br/>"); }); });
|

Filter(筛选器)和Middileware(中间件)的区别
中间件是ASP.NET Core这个基础提供的功能,而Filter是ASP.NET Core WVC中提供的功能。ASP.NET Core MVC是由MVC中间件提供的框架,而Filter属于MVC中间件提供的功能。

区别
- 中间件可以处理所有的请求,而Filter只能处理对控制器的请求;中间件运行在一个更底层、更抽象的级别,因此在中间件中无法处理MVC中间件特有的概念。
- 中间件和Filter可以完成很多相似的功能。“未处理异常中间件”和“未处理异常Filter”;“请求限流中间件”和“请求限流Filter”的区别。
- 优先选择使用中间件;但是如果这个组件只针对MVC或者需要调用一些MVC相关的类的时候,我们就只能选择Filter。