.Net之过滤器基本使用
过滤器就是对目标对象(程序集、类、方法等)进行扩展,使得在运行时可以获取到被扩展对象的额外的信息,通过额外的信息来影响目标对象的行为。
ASP.NET Core 有以下五种Filter 可以使用:
中间件和过滤器的功能类似,区别就是关注点是不同的,所要处理的职责不同,过滤器是更贴合业务的,关注应用程序本身(可以对你请求的数据或者返回的结果进行处理操作,中间件是没有这个能力的),中间件都可以在请求之前和之后进行操作。请求完成之后传递给下一个请求。
netcore和net相比增加了资源过滤器和结果过滤器,并且异常过滤器和方法过滤器设置了异步的使用方法,如果同步过滤器和异步的都设置了,那么只会调用异步的方法。
示例代码环境:vs2022、.Net6
源码地址:https://github.com/azrng/dotnet-sample/tree/main/src/MVCFilter
过滤器特性
作用范围
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public class ApiResultFilterAttribute : Attribute, IAsyncResultFilter
第一行指定了该特性作用的范围,AttributeTargets就是目标对象,是一个枚举
//指定可以对它们应用属性的应用程序元素。
[ComVisible(true)]
[Flags]
public enum AttributeTargets
{
//可以对程序集应用属性。
Assembly = 1,
//可以对模块应用属性。
Module = 2,
//可以对类应用属性。
Class = 4,
//可以对结构应用属性,即值类型。
Struct = 8,
Enum = 16,
//可以对构造函数应用属性。
Constructor = 32,
//可以对方法应用属性。
Method = 64,
//可以对属性 (Property) 应用属性 (Attribute)。
Property = 128,
//可以对字段应用属性。
Field = 256,
//可以对事件应用属性。
Event = 512,
//可以对接口应用属性。
Interface = 1024,
//可以对参数应用属性。
Parameter = 2048,
//可以对委托应用属性。
Delegate = 4096,
//可以对返回值应用属性。
ReturnValue = 8192,
//可以对泛型参数应用属性。
GenericParameter = 16384,
//可以对任何应用程序元素应用属性。
All = 32767
}
过滤器类型
下面过滤器的顺序就是执行的顺序
以下示例引用nuget包
<PackageReference Include="AzrngCommon" Version="1.2.5" />
如果过滤器中使用了构造函数注入,那么我们需要使用到ServiceFilter
//[Authorization_01Filter]//无参数过滤器
[ServiceFilter(typeof(Authorization_01Filter))]
[HttpGet]
public IEnumerable<WeatherForecast> Get01()
并且这个过滤器也需要进行注册,例如注册这个授权过滤器
//授权过滤器
builder.Services.AddScoped(typeof(Authorization_01Filter));
Authorization Filter
Authorization是五种Filter中优先级最高的,通常用于验证Request合不合法、用户身份是否被认证(然后授权等)、。
权限控制器过滤器,可以通过Authonization可以实现复杂的权限角色认证、登录授权等操作实现事例如下:
[AttributeUsage(AttributeTargets.Class)]
public class Authorization_01Filter : Attribute, IAuthorizationFilter
{
/*
权限控制器过滤器,可以通过Authonization可以实现复杂的权限角色认证、登录授权等操作实现事例
猜想:是否到底这一步的都应该是已经经过身份认证的用户,这边只是做一些授权操作,还是说这个地方做认证以及授权操作
*/
private readonly ILogger<Authorization_01Filter> _logger;
public Authorization_01Filter(ILogger<Authorization_01Filter> logger)
{
_logger = logger;
}
public void OnAuthorization(AuthorizationFilterContext context)
{
_logger.LogInformation("进入授权过滤器");
/*
实现效果:自定义身份验证,当用户调用登录接口的时候,会查询数据库并且将该用户信息存入redis(格式Wie:key:随机数,value:用户信息),然后返回随机数
当请求其它接口的时候,验证请求头中是否传输了Authorization,如果没传直接返回401
当传输了token,那么就拿着值去查询redis,然后验证通过后将用户信息存入上下文的User中
*/
if (!context.HttpContext.Request.Headers.Any(t => t.Key == "Authorization"))
context.Result = new JsonResult(new ResultModel { Code = "401", Message = "认证失败" });
var token = context.HttpContext.Request.Headers.FirstOrDefault(t => t.Key == "Authorization").Value;
if (token != "123456")//这里替换查询redis操作
{
context.Result = new JsonResult(new ResultModel { Code = "401", Message = "认证失败" });
}
//如果查询到上面传输的信息,那么就存储到上下文中
var claims = new[]
{
new Claim(ClaimTypes.NameIdentifier, "zyp"),
new Claim(ClaimTypes.Role, "admin"),
};
var claimIdentities = new List<ClaimsIdentity>
{
new ClaimsIdentity(claims)
};
context.HttpContext.User.AddIdentities(claimIdentities);
}
}
异步方案
[AttributeUsage(AttributeTargets.Class)]
public class Authorization_01AsyncFilter : Attribute, IAsyncAuthorizationFilter
{
private readonly ILogger<Authorization_01Filter> _logger;
public Authorization_01AsyncFilter(ILogger<Authorization_01Filter> logger)
{
_logger = logger;
}
public Task OnAuthorizationAsync(AuthorizationFilterContext context)
{
_logger.LogInformation("进入授权过滤器");
if (!context.HttpContext.Request.Headers.Any(t => t.Key == "Authorization"))
context.Result = new JsonResult(new ResultModel { Code = "401", Message = "认证失败" });
var token = context.HttpContext.Request.Headers.First(t => t.Key == "Authorization").Value;
if (token != "123456")//这里替换查询redis操作
{
context.Result = new JsonResult(new ResultModel { Code = "401", Message = "认证失败" });
}
//如果查询到上面传输的信息,那么就存储到上下文中
var claims = new[]
{
new Claim(ClaimTypes.NameIdentifier, "zyp"),
new Claim(ClaimTypes.Role, "admin"),
};
var claimIdentities = new List<ClaimsIdentity>
{
new ClaimsIdentity(claims)
};
context.HttpContext.User.AddIdentities(claimIdentities);
return Task.CompletedTask;
}
}
使用示例
/// <summary>
/// 获取天气(同步验证IAuthorizationFilter)
/// </summary>
/// <returns></returns>
//[Authorization_01Filter]//无参数过滤器
[ServiceFilter(typeof(Authorization_01Filter))]
[HttpGet]
public IEnumerable<WeatherForecast> Get01()
{
const string str = @"
业务逻辑开始处理
处...
理...
中...
业务逻辑结束处理";
_logger.LogInformation(str);
return (new WeatherForecast()).GetList();
}
Resource Filter
Resource是第二优先,会在Authorization之后,Model Binding之前执行。通常会是需要对Model加工处理才用也适合做缓存,因为是在创建控制器实例之前执行的。
同步方案
[AttributeUsage(AttributeTargets.All)]
public class Resource_01Filter : Attribute, IResourceFilter
{
/*
使用场景:可以做缓存:比如说第一次请求先到OnResourceExecuting,根据请求地址或者参数去判断是否已经保存数据,没有发现往下走创建Action实例,
然后在OnResourceExecuted进行存储,然后再一次访问这个接口时候,OnResourceExecuting直接就赋值Result,所以就不再创建控制器实例
具体示例:根据请求地址做接口缓存、根据请求参数做缓存处理
*/
/// <summary>
/// 模拟数据源
/// </summary>
private static readonly Dictionary<string, object> _dictionaryCache = new Dictionary<string, object>();
private readonly ILogger<Result_01Filter> _logger;
public Resource_01Filter(ILogger<Result_01Filter> logger)
{
_logger = logger;
}
/// <summary>
/// 在接口被调用前触发
/// </summary>
/// <param name="context"></param>
public void OnResourceExecuting(ResourceExecutingContext context)
{
_logger.LogInformation("同步 OnResourceExecuting");
if (context.HttpContext.Request.Method == "Get")
{
//定义一个key存储缓存
string key = context.HttpContext.Request.Path.ToString();
if (_dictionaryCache.Any(t => t.Key == key))
{
//如果缓存有内容就直接返回
context.Result = _dictionaryCache[key] as IActionResult; //Result 短路器
}
//如果没有缓存就接着运行,然后再executed里面设置缓存
}
else
{
context.HttpContext.Request.EnableBuffering();//可以实现多次读取Body
var sr = new StreamReader(context.HttpContext.Request.Body);
string data = sr.ReadToEndAsync().GetAwaiter().GetResult();//不允许同步读取
if (data == null)//body取不到数据直接跳过,一般情况下不会出现该情况
return;
//获取到body的请求字符串
_logger.LogInformation("data=" + data);
//根据请求字符串去做处理解析是否做缓存,本次示例是获取到请求的boyd里面的ID,如果存在id,那么就做资源缓存(id作为key),
var jobject = JObject.Parse(data);
if (jobject["id"]?.ToString() != null)
{
string key = context.HttpContext.Request.Path.ToString() + jobject["id"].ToString();
if (_dictionaryCache.Any(t => t.Key == key))
{
//如果缓存有内容就直接返回
context.Result = _dictionaryCache[key] as IActionResult; //Result 短路器
}
}
context.HttpContext.Request.Body.Seek(0, SeekOrigin.Begin);//读取到Body后,重新设置Stream到起始位置
context.HttpContext.Request.Body.Position = 0;
}
}
/// <summary>
/// 在接口调用结束时候触发
/// </summary>
/// <param name="context"></param>
public void OnResourceExecuted(ResourceExecutedContext context)
{
_logger.LogInformation("异步 OnResourceExecuted");
//把数据存储缓存 Key---path; 实际情况这里缓存应该加上过期时间
if (context.HttpContext.Request.Method == "Get")
{
string key = context.HttpContext.Request.Path.ToString();//将请求路径作为缓存的key
_dictionaryCache[key] = context.Result;//刚才接口返回的值
}
else
{
context.HttpContext.Request.Body.Seek(0, SeekOrigin.Begin);//读取到Body后,重新设置Stream到起始位置
context.HttpContext.Request.Body.Position = 0;
var sr = new StreamReader(context.HttpContext.Request.Body);
string data = sr.ReadToEndAsync().GetAwaiter().GetResult();//不允许同步读取
if (data == null)
return;
var jobject = JObject.Parse(data);
if (jobject["id"]?.ToString() != null)
{
string key = context.HttpContext.Request.Path.ToString() + jobject["id"].ToString();
_dictionaryCache[key] = context.Result;//刚才接口返回的值
}
}
}
}
异步方案
[AttributeUsage(AttributeTargets.All)]
public class Resource_02Filter : Attribute, IAsyncResourceFilter
{
}
Action Filter
最常使用的Filter,请求和返回都会经过它。跟Resource Filter很类似,但并不会经过Model Binding,因为进这个过滤器的时候已经走过了Model Binding。
可以通过ActionFilter拦截每个执行方法进行一系列的操作,比如:执行日志、性能监控、数据校验参数验证或加密、权限控制等一系列操作。使用Action Filter 需要实现IActionFilter 抽象接口,IActionFilter 接口要求实现OnActionExecuted 和OnActionExecuting 方法
同步方案:
[AttributeUsage(AttributeTargets.All)]
public class Action_01Filter : Attribute, IActionFilter
{
private readonly ILogger<Action_01Filter> _logger;
public Action_01Filter(ILogger<Action_01Filter> logger)
{
_logger = logger;
}
public void OnActionExecuting(ActionExecutingContext context)
{
_logger.LogInformation("action 执行前");
//如果标记有允许所有,不做处理那么就跳过
if (context.ActionDescriptor.EndpointMetadata.Any(t => t.GetType() == typeof(AllowAnonymousAttribute)))
{
return;
}
//记录请求来的一些参数
var queryUrl = context.HttpContext.Request.Query;
string path = context.HttpContext.Request.Path;
string name = context.HttpContext.User.Identity?.Name;
_logger.LogInformation($"Action信息 queryUrl:{queryUrl},path:{path},name:{name}");
}
public void OnActionExecuted(ActionExecutedContext context)
{
_logger.LogInformation("action 执行后");
}
}
异步过滤器
[AttributeUsage(AttributeTargets.All)]
public class Action_02Filter : Attribute, IAsyncActionFilter
{
private readonly ILogger<Action_01Filter> _logger;
public Action_02Filter(ILogger<Action_01Filter> logger)
{
_logger = logger;
}
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
_logger.LogInformation("action 执行前");
//如果标记有允许所有,不做处理那么就跳过
if (context.ActionDescriptor.EndpointMetadata.Any(t => t.GetType() == typeof(AllowAnonymousAttribute)))
{
return;
}
//记录请求来的一些参数
var queryUrl = context.HttpContext.Request.Query;
string path = context.HttpContext.Request.Path;
string name = context.HttpContext.User.Identity?.Name;
_logger.LogInformation($"Action信息 queryUrl:{queryUrl},path:{path},name:{name}");
await next();
_logger.LogInformation("action 执行后");
}
}
此方法不同于ActionFilter的是,它能够处理异步操作,同时它是在模型绑定完成之后执行,只有一个异步方法OnActionExecutionAsync。
另外还可以继承自ActionFilterAttribute,这东西有点类似于Action过滤器和Result的合并使用
[AttributeUsage(AttributeTargets.All)]
public class Action_03Filter : ActionFilterAttribute
{
private readonly ILogger<Action_01Filter> _logger;
public Action_03Filter(ILogger<Action_01Filter> logger)
{
_logger = logger;
}
public override void OnActionExecuted(ActionExecutedContext context)
{
_logger.LogInformation("action 执行后 OnActionExecuted 1 ");
base.OnActionExecuted(context);
}
public override void OnActionExecuting(ActionExecutingContext context)
{
_logger.LogInformation("action 执行前 OnActionExecuting 2 ");
base.OnActionExecuting(context);
}
public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
//会进该方法
//OnActionExecuted OnActionExecuting方法我理解是应该在这里根据条件判断去调用上面的方法
_logger.LogInformation("action 执行前 OnActionExecutionAsync 3 ");
await next();
_logger.LogInformation("action 执行前 OnActionExecutionAsync 4 ");
}
public override void OnResultExecuted(ResultExecutedContext context)
{
_logger.LogInformation("result 执行后 OnResultExecuted 5 ");
base.OnResultExecuted(context);
}
public override void OnResultExecuting(ResultExecutingContext context)
{
_logger.LogInformation("result 执行前 OnResultExecuting 6 ");
base.OnResultExecuting(context);
}
public override async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
{
//会进该方法
//OnResultExecuted、OnResultExecuting方法并不会直接触发,而是根据条件在当前方法中执行调用的
_logger.LogInformation("result 执行前 OnResultExecutionAsync 7 ");
await next();
_logger.LogInformation("result 执行后 OnResultExecutionAsync 8 ");
//返回
//context.Result = new ObjectResult("");
//await base.OnResultExecutionAsync(context, next);
}
}
Exception Filter
异常处理的Filter,可以进行全局的异常日志收集等操作,使用该过滤器需要实现IExceptionFilter接口,实现这个接口需要实现OnException方法,当系统发送未捕捉的异常时候就会触发这个方法,这个方法里面包含了一个ExceptionContext异常上下文,其中包含了具体的异常信息,然后就需要使用日志组件等记录下这个异常。
注意:只能捕捉Action异常,如果是要做全局异常捕捉还是建议去使用中间件。
首先自定义一个异常过滤器,然后实现IExceptionFilter接口
实现方法,先判断异常是否处理过,如果没有处理过那么就进行处理。
同步方案:
public class Exception_01Filter : Attribute, IExceptionFilter
{
private readonly ILogger<Exception_01Filter> _logger;
private readonly IModelMetadataProvider _modelMetadataProvider;
public Exception_01Filter(ILogger<Exception_01Filter> logger,
IModelMetadataProvider modelMetadataProvider)
{
_logger = logger;
_modelMetadataProvider = modelMetadataProvider;
}
public void OnException(ExceptionContext context)
{
if (context.ExceptionHandled)
return;
//日志收集
_logger.LogError(context.Exception, "出错" + context?.Exception?.Message ?? "异常");
var response = new ResultModel<string>()
{
Message = $"处理失败 {context.Exception.Message}",
IsSuccess = false,
Code = "500"
};
//或者
context.Result = new JsonResult(response);
//如果是mvc使用,那么就可以返回错误界面
//var result = new ViewResult { ViewName = "~/Views/Shared/Error.cshtml" };
//result.ViewData = new Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary(_modelMetadataProvider, context.ModelState);
//context.Result = result;//断路器只要一对result赋值就不继续往后赋值了
context.ExceptionHandled = true;
}
}
异步方案
public class Exception_02Filter : Attribute, IAsyncExceptionFilter
{
private readonly ILogger<Exception_02Filter> _logger;
//构造注入日志组件
public Exception_02Filter(ILogger<Exception_02Filter> logger)
{
_logger = logger;
}
public async Task OnExceptionAsync(ExceptionContext context)
{
if (context.ExceptionHandled)
return;
//日志收集
_logger.LogError(context.Exception, context?.Exception?.Message ?? "异常");
var response = new ResultModel<string>()
{
Message = $"处理失败 {context.Exception.Message}",
IsSuccess = false,
Code = "500"
};
var setting = new JsonSerializerSettings
{
ContractResolver = new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver()//指定序列化方式为驼峰式
};
await context.HttpContext.Response.WriteAsync(JsonConvert.SerializeObject(response, setting));
//或者
//context.Result = new JsonResult(response);//断路器只要一对result赋值就不继续往后赋值了
context.ExceptionHandled = true;
}
}
Result Filter
当Action完成后,最终会经过的Filter,可以对结果进行格式化、大小写转换、双语系统等一系列操作。比如根据返回的结果不同,渲染不同的视图(中文视图或者英文视图)等
同步方案:
[AttributeUsage(AttributeTargets.All)]
public class Result_01Filter : Attribute, IResultFilter
{
private readonly IModelMetadataProvider _modelMetadataProvider;
public Result_01Filter(IModelMetadataProvider modelMetadataProvider)
{
_modelMetadataProvider = modelMetadataProvider;
}
/// <summary>
/// 在result执行前发生(在view 呈现前),使用场景:设置客户端缓存,服务器端压缩
/// </summary>
/// <param name="context"></param>
public void OnResultExecuting(ResultExecutingContext context)
{
// 在结果执行之前调用的一系列操作 mvc中使用根据不同的参数等返回不同的页面
//var param = context.HttpContext.Request.Query["View"];
//if (param == "1")//显示中文系统
//{
// var result = new ViewResult { ViewName = "~/Views/Test/Chinese.cshtml" };
// result.ViewData = new Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary(_modelMetadataProvider, context.ModelState);
// context.Result = result;
//}
//设置响应头
//context.HttpContext.Response.Headers.Add("", new string[] { "" });
Console.WriteLine("OnResultExecuting");
context.Result = new JsonResult(ResultModel<object>.Success((ObjectResult)context.Result));
}
/// <summary>
/// 渲染视图后执行,当Action完成后
/// </summary>
/// <param name="context"></param>
public void OnResultExecuted(ResultExecutedContext context)
{
var path = context.HttpContext.Request.Path;
// 在结果执行之后调用的操作...
Console.WriteLine($"OnResultExecuted Path:{path}");
//注意:目前我并不知道找个方法适合做什么,并且context.Result方法也是只读的。
}
}
异步方案
[AttributeUsage(AttributeTargets.All)]
public class Result_02Filter : Attribute, IAsyncResultFilter
{
public async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
{
context.Result = new JsonResult(ResultModel<object>.Success((ObjectResult)context.Result));
Console.WriteLine("之前");
await next();
Console.WriteLine("之后");
}
}
同样的这里我们还可以继承自ResultFilterAttribute。
注册方式
过滤器的注册方式有:Action、Controller、全局
Action注册方式
局部注册方式,针对控制器中的某个方法上标注特性的方式进行注册,代码如下
[AuthonizationFilter()]
public IActionResult Index()
{
return View();
}
Controller注册方式
如果使用action方式,如果一个控制器中的好几个action方式都使用了这个过滤器,那么我们就需要一个一个action进行标注注册,所以我们直接使用控制器注册方式,必须是无参数的构造函数
[AuthonizationFilter()]
public class FirstController : Controller
{
private ILogger<FirstController> _logger;
public FirstController(ILogger<FirstController> logger)
{
_logger = logger;
}
public IActionResult Index()
{
return View();
}
}
如果要传递参数,需要通过TyperFilter标记在方法和控制器
[TypeFilter(typeof(customerAttribute))]
全局注册方式
比如说我们需要全局系统中的异常或者收集操作日志等,需要全局注册一个Exception来实现,就不需要一个一个action或者控制器进行注册
services.AddMvc(options =>
{
//options.Filters.Add(typeof(CustomerExceptionFilter));
options.Filters.Add<CustomerExceptionFilter>();
options.Filters.Add(new AuthorizeFilter());
options.Filters.Add(typeof(AuthorizeFilter));
})
依赖注入两种方式
ServiceFilterAttribute
将要用的ActionFilter本身注册为一个Service注册到DI容器中(必须)。通过ServiceFilter从容器中检索你的ActionFile,并且注入到需要的地方
//步骤一
services.AddScoped(typeof(CustomResourceFilterAttribute));
//步骤二
在action上使用serviceFilter
[ServiceFilter(typeof(FilterInjectAttribute))]
[ServiceFilter(typeof(FilterInjectAttribute),IsReusable = true)]
ServiceFilter有一个属性叫IsReusable。从字面意思也很好理解,就是是否可重用的意思。显而易见如果这个属性设置为True,那么多个请求就会复用这个ActionFilter,但是并不是真正的单例
可以支持无参构造函数,可以支持依赖注入,但是必须注册服务
TypeFilterAttribute
使用TypeFilterAttribute注入的ActionFilter并不从DI容器中查找,而是直接通过Microsoft.Extensions.DependencyInjection.ObjectFactory来实例化对象。所以我们的FilterInjectAttribute不需要提前注册到DI容器中。
[TypeFilter(typeof(MyExceptionFilter))]
[TypeFilter(typeof(MyExceptionFilter), Arguments = new object[] { "aa", "bb" })]
Arguments参数是TypeFilterAttribute跟ServiceFilterAttribute的一个重要区别,ServiceFilterAttribute并没有这属性。Arguments类型为object数组。通过TypeFilterAttribute实例化的ActionFilter,如果它的构造器中的参数类型在DI容器中找不到,会继续在Arguments参数列表里按顺序获取。
可以支持无参构造函数,可以支持依赖注入
资料
ASP.NET Core 中的过滤器:https://docs.microsoft.com/zh-cn/aspnet/core/mvc/controllers/filters?view=aspnetcore-6.0
鸣谢:
https://mp.weixin.qq.com/s/HYBCyfu_IPqpnRj5eSpGdQ
本文来自博客园,作者:{春光牛牛,yak},转载请注明原文链接:https://www.cnblogs.com/yakniu/p/16366886.html
欢迎各位大佬们评论指正
QQ讨论群:610129902
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战