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