筛选器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 "响应结果……"; } }