筛选器Filter 1、Filter是面向切面编程机制,在ASP.NET Core特定的位置执行我们自定义的代码 2、有5种Filter类型:Authorization filter、Resource filter、Action filter、Exception filter、Result filter。 3、所有筛选器一般有同步异步两个版本比如IActionFilter、IAsyncActionFilter接口
异常筛选器 当系统中出现未经处理的异常时,异常筛选器会执行。 目标:当系统中出现未处理异常的时候,我们统一给客户端返回如下格式的响应报文:{“code”:”500”,”message”:”异常信息”},对于开发环境中message是异常堆栈,对于其他环境message用一个general的报错信息。 实现:实现IAsyncExceptionFilter接口。注入IHostEnvironment得知运行环境。
1 2 3 4 5 6 //Action方法         [HttpGet]         public string MakeErr() {            string s =  System.IO.File.ReadAllText("f:/1.txt");             return s;         } 
 
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 //创建自定义异常筛选器     public class MyExceptionFilter : IAsyncExceptionFilter {         private readonly IWebHostEnvironment webHostEnvironment;//将需要用到的服务注入,我们需要这个来判断当前为什么环境         public MyExceptionFilter(IWebHostEnvironment webHostEnvironment) {             this.webHostEnvironment = webHostEnvironment;         }         public Task OnExceptionAsync(ExceptionContext context) {             //当Action中发生未处理异常,该方法会被调用             //context.Exception代表异常信息对象             //如果给context.ExceptionHandled赋值为true,其他的ExceptionFilter就不会再执行             //context.Result的值会被输出给客户端,是一个IActionResult类型的对象             string msg;             if(webHostEnvironment.IsDevelopment()) {                 //如果是开发环境                 msg = context.Exception.ToString();             } else {                 msg = "服务器端发生未处理异常";             }             //ObjectResult继承ActionResult实现IActionResult             ObjectResult obj = new ObjectResult(new {code= 500 ,message = msg});             context.Result = obj;             context.ExceptionHandled = true;//告诉别的ExceptionFilter不用执行了             return Task.CompletedTask;         }     } 
 
1 2 3 4 //Program.cs             builder.Services.Configure<MvcOptions>(options => {                 options.Filters.Add<MyExceptionFilter>();//注入自定义异常筛选器,注意!!!:多个筛选器Add的时候要注意顺序             }); 
 
Action筛选器 用于控制器中每个action方法执行之前和之后执行自定义代码,比如在执行action之前判断用户有没有权限或者在执行之后记录日志。 一个筛选器分为前代码和后代码,有多个ActionFilter执行顺序为: 1前->2前->3前->Action->3后->2后->1后 自定义Action筛选器需要实现IActionFilter/IAsyncActionFilter接口。
1 2 3 4 5 6 //Action方法 [HttpGet]         public string TestAction() {             Console.WriteLine("执行中");             return "执行中";         } 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 //创建自定义Action筛选器     public class MyActionFilter : IAsyncActionFilter {         public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) {             //next为委托,调用next()程序就会向下执行             Console.WriteLine("Action1执行前");             ActionExecutedContext result = await next();//next()的返回值表示action执行中是否出现异常             if(result.Exception != null) {                 Console.WriteLine("Action1发生了异常");             } else {                 Console.WriteLine("Action1执行成功");             }         }     } 
 
1 2 3 4 //Program.cs             builder.Services.Configure<MvcOptions>(options => {                 options.Filters.Add<MyActionFilter>();             }); 
 
筛选器案例 案例一(TransactionScope) 需求: 1、数据库事务:要么全部成功,要么全部失败。 2、自动化:启动、提交以及回滚事务。 3、当一段使用EFCore进行数据库操作的代码放到TransactionScope声明的范围中的时候,这段代码就会自动被标记为“支持事务”。 4、TransactionScope实现了IDisposable接口,如果一个TransactionScope的对象没有调用Complete()就执行了Dispose()方法,则事物会被回滚,否则事务就会被提交。 5、TransactionScope支持嵌套式事务,即便里面的TransactionScope已经提交,但是外面的TransactionScope失败了,事务依然会回滚。 6、.Net Core中的TransactionScope不像.Net FX一样有MSDTC分布式事务提升的问题,请使用最终一致性事务。 接下来我会用MySql数据库举例:
1 2 3 4 5 6 //appsetting.json {   "ConnectionStrings": {     "DefaultConnection": "server=localhost;user=root;password=your_password;database=your_database"   } } 
 
1 2 3 4 5 6 7 8 9 10 11 //实体类     public class Book {         public long Id { get; set; }         public string Name { get; set; }         public double Price { get; set; }     }     public class Person {         public long Id { get; set; }         public string Name { get; set; }         public int Age { get; set; }     } 
 
1 2 3 4 5 6 7 8 //连接数据库 public class MyDbContext:DbContext {         public DbSet<Book> Books { get; set; }         public DbSet<Person> Persons { get; set; }         public MyDbContext(DbContextOptions<MyDbContext> options) : base(options) {             }     } 
 
1 2 3 //自定义特性 public class NotTransactionAttribute :Attribute{     } 
 
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 //自动启用事务的ActionFilter     public class TransactionScopeFilter : IAsyncActionFilter {         public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) {             //context.ActionDescriptor中是当前被执行的Action方法的描述信息             //context.ActionArguments中是当前被执行的Action方法的参数信息             //context.ActionDescriptor.Parameters[0]是当前被执行的Action方法的第一个参数             ControllerActionDescriptor ctrlActionDesc = context.ActionDescriptor as ControllerActionDescriptor;             bool isTX = false;//是否进行事务控制             if(ctrlActionDesc != null) {//是一个MVC的action                 //ctrlActionDesc.MethodInfo当前的Action方法                 bool hasNotTransactionAttribute = ctrlActionDesc.MethodInfo                     .GetCustomAttributes(typeof(NotTransactionAttribute), false)                     .Any();                 isTX = !hasNotTransactionAttribute;             }             if(isTX) {                 using(TransactionScope tx = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled)) {                     var r = await next();                     if(r.Exception == null) {                         tx.Complete();                     }                 }             } else {                 await next();             }         }     } 
 
1 2 3 4 5 6 7 8 9 //Program.cs            builder.Services.AddDbContext<MyDbContext>(options => {                 string constr = builder.Configuration.GetSection("ConnectionStrings:DefaultConnection").Value;                 var serverVersion = new MySqlServerVersion(new Version(8, 0, 29));                 options.UseMySql(constr, serverVersion);             });             builder.Services.Configure<MvcOptions>(opt => {                 opt.Filters.Add<TransactionScopeFilter>();             }); 
 
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 32 33 34 35 36 37 38 39 40 41 42 43 44 //控制器     [Route("[controller]/[action]")]     [ApiController]     public class DemoController : ControllerBase {         private MyDbContext ctx;         public DemoController(MyDbContext ctx) {             this.ctx = ctx;         }         [HttpPost]         [NotTransaction]         public string Test1() {             using (TransactionScope tx = new TransactionScope()) {                 ctx.Books.Add(new Book { Name = "aaa", Price = 34 });                 ctx.SaveChanges();//一个事务                 ctx.Persons.Add(new Person { Name = "ljhljh", Age = 18 });                 ctx.SaveChanges();//一个事务                 tx.Complete();                 return "ok";             }         }         [HttpPost]         [NotTransaction]         public async Task<string> Test2() {             //异步需要向TransactionScope传入这个参数,原理是:             //同步的代码在TheadLocal中(相当于当前线程的全局变量)             //异步的代码在AsyncLocal中(相当于当前异步线程的全局变量)             using (TransactionScope tx = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled)) {                 ctx.Books.Add(new Book { Name = "aaa", Price = 34 });                 await ctx.SaveChangesAsync();//一个事务                 ctx.Persons.Add(new Person { Name = "ljh", Age = 18 });                 await ctx.SaveChangesAsync();//一个事务                 tx.Complete();                 return "ok";             }         }         [HttpPost]         public async Task<string> Test3() {             ctx.Books.Add(new Book { Name = "aaa", Price = 34 });             await ctx.SaveChangesAsync();//一个事务             ctx.Persons.Add(new Person { Name = "ljh", Age = 18 });             await ctx.SaveChangesAsync();//一个事务             return "ok";         }     } 
 
案例二(限流器) 需求: 对用户访问进行限流。 通过缓存机制,将用户的ip地址存放到缓存中。 如果缓存中ip已过期或者超过1秒就执行action。 ActionFilter可以在满足条件的时候终止操作方法的执行。 在ActionFilter中如果不调用next()就可以终止Action方法的执行了。
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 RateLimitFilter : IAsyncActionFilter {         private readonly IMemoryCache memoryCache;         public RateLimitFilter(IMemoryCache memoryCache) {             this.memoryCache = memoryCache;         }         public Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) {             string userIp = context.HttpContext.Connection.RemoteIpAddress.ToString();//用户ip             string cacheKey = $"Limit_{userIp}";             long? lastTick = memoryCache.Get<long?>(cacheKey) ;//获取用户ip对应最后一次登录时间             if (lastTick == null || Environment.TickCount64 - lastTick > 1000) {                 //如果用户ip缓存已过期或者超过一秒                 memoryCache.Set(cacheKey, Environment.TickCount64,TimeSpan.FromSeconds(10));//存储当前时间并且设置10秒固定过期时间                 return next();             } else {                ObjectResult result = new ObjectResult("访问过于频繁"){ StatusCode = 429};                 context.Result = result;                 return Task.CompletedTask;             }         }     } 
 
1 2 3 4 5 //Program.cs builder.Services.Configure<MvcOptions>(opt => {                 opt.Filters.Add<TransactionScopeFilter>();                 opt.Filters.Add<RateLimitFilter>();             }); 
 
1 2 3 4 5 6 7 8 9 //控制器     [Route("[controller]/[action]")]     [ApiController]     public class RateLimitController : ControllerBase {         [HttpGet]         public string LimitTest() {             return "响应结果……";         }     }