【ASP.NET Core】过滤器(Filters)
过滤器接口
- 授权过滤器:IAuthorizationFilter、IAsyncAuthorizationFilter(AuthorizeFilter)
通常用于验证请求合法性 - 资源过滤器:IResourceFilter、IAsyncResourceFilter
适合做缓存 - Action过滤器:IActionFilter、IAsyncActionFilter
通常用于记录Action日志、校验Action参数 - Result过滤器:IResultFilter、IAsyncResultFilter
通常用于对执行结果格式化处理 - 异常过滤器:IExceptionFilter、IAsyncExceptionFilter
异常信息处理 - IAlwaysRunResultFilter
ActionFilter、ResultFilter、ResourceFilter中只要是对HttpContext.Result赋值,就不再继续往后了。
如果在ResourceFilter处理后,HttpContext赋值后,也想在后面来一个补充呢?
ActionFilterAttribute
ActionFilterAttribute
是一个框架提供的抽象类,实现了IActionFilter
、IAsyncActionFilter
、IResultFilter
、IAsyncResultFilter
如果同时重写了同步方法、异步方法,同步方法不会被执行
注册全局过滤器
services.AddMvc(o =>
{o.Filters.Add(typeof(CustomerResourceFilterAttribute));}
);
Filter执行顺序
a.验证权限,进入到Authorization
b.ResourceFilter 中的 -OnResourceExecuting
c.开始创建控制器实例
d.ActionFilter 中的 - OnActionExecuting
e.执行Action方法
f.ActionFilter 中的 - OnActionExecuted
g.ResultFilter 中的 - OnResultExecuting
h.AlwaysRunResultFilter 中的 - OnResultExecuting
i.渲染视图
j.AlwaysRunResultFilter 中的 - OnResultExecuted
k.ResultFilter 中的 - OnResultExecuted
l.ResourceFilter中的 -OnResourceExecuted
如何在过滤器中注入服务
使用TypeFilter
和ServiceFilter
都可以解决过滤器注入问题,使用方法如下:
TypeFilter
使用TypeFilter,直接将[CustomerActionFilter]
替换为[TypeFilter(typeof(CustomerActionFilterAttribute))]
即可
ServiceFilter
使用ServiceFilter,将[CustomerActionFilter]
替换为 [ServiceFilter(typeof(CustomerActionFilter))]
。然后注册过滤器services.AddSingleton<CustomerActionFilterAttribute>();
过滤器上下文
过滤器中的行为,都会有一个上下文参数,这些上下文参数都继承自抽象类FilterContext
,而FilterContext
又继承自ActionContext
(这也从侧面说明了,过滤器就是为Action服务的):
public class ActionContext
{
// Action相关的信息
public ActionDescriptor ActionDescriptor { get; set; }
// HTTP上下文
public HttpContext HttpContext { get; set; }
// 模型绑定和验证
public ModelStateDictionary ModelState { get; }
// 路由数据
public RouteData RouteData { get; set; }
}
public abstract class FilterContext : ActionContext
{
public virtual IList<IFilterMetadata> Filters { get; }
public bool IsEffectivePolicy<TMetadata>(TMetadata policy) where TMetadata : IFilterMetadata {}
public TMetadata FindEffectivePolicy<TMetadata>() where TMetadata : IFilterMetadata {}
}
public class ActionDescriptor
{
// 标识该Action的唯一标识,其实就是一个Guid
public string Id { get; }
// 路由字典,包含了controller、action的名字等
public IDictionary<string, string> RouteValues { get; set; }
// 特性路由的相关信息
public AttributeRouteInfo? AttributeRouteInfo { get; set; }
// Action的约束列表
public IList<IActionConstraintMetadata>? ActionConstraints { get; set; }
// 终结点元数据,咱们一般用不到
public IList<object> EndpointMetadata { get; set; }
// 路由中的参数列表,包含参数名、参数类型、绑定信息等
public IList<ParameterDescriptor> Parameters { get; set; }
public IList<ParameterDescriptor> BoundProperties { get; set; }
// 过滤器管道中与当前Action有关的过滤器列表
public IList<FilterDescriptor> FilterDescriptors { get; set; }
// Action的个性化名称
public virtual string? DisplayName { get; set; }
// 共享元数据
public IDictionary<object, object> Properties { get; set; }
}
过滤器执行顺序(IOrderedFilter)
针对同一类型的过滤器,我们可以有多个实现。默认的,如果将同一作用域的同一类型的过滤器的多个实现作用到某个Action上,则这些过滤器实例的执行顺序是按照注册的顺序进行的。
但是,有时我们就需要手动指定执行顺序,比如我们想注册一个过滤器,在框架注册的过滤器之前执行,这就用到了IOrderedFilter接口。
public interface IOrderedFilter : IFilterMetadata
{
// 执行顺序
int Order { get; }
}
IOrderedFilter接口很简单,只有一个Order属性,表示执行顺序,默认值为0。Order值越小,则过滤器的Before方法越先执行,After方法越后执行。
案例:
public class MyActionFilter1 : ActionFilterAttribute
{
public MyActionFilter1()
{
Order = -1;
}
//略...
}
ResourceFilter
资源过滤器在过滤器管道中第二个被执行,通常用于请求结果的缓存和短路过滤器管道。与Action过滤器的区别是资源过滤器在Controller创建之间就执行了
和ActionFilter的区别是:OnResourceExecuting在Controller创建之前调用,OnResourceExecuted最后执行
同步:
public class CustomerResourceFilterAttribute : Attribute, IResourceFilter
{
private static ConcurrentDictionary<string, object> CACHE_DICT = new ConcurrentDictionary<string, object>();
private string _cacheKey;
public void OnResourceExecuting(ResourceExecutingContext context)
{
_cacheKey = context.HttpContext.Request.Path.ToString();
if (CACHE_DICT.TryGetValue(_cacheKey, out object result))
{
var actionResult = result as IActionResult;
if (actionResult != null)
{
context.Result = actionResult;
}
}
}
public void OnResourceExecuted(ResourceExecutedContext context)
{
if (!CACHE_DICT.ContainsKey(_cacheKey))
{
if (context.Result != null)
{
CACHE_DICT.TryAdd(_cacheKey, context.Result);
}
}
}
}
异步:
public class CustomerAsyncResourceFilterAttribute : Attribute, IAsyncResourceFilter
{
private static ConcurrentDictionary<string, object> CACHE_DICT = new ConcurrentDictionary<string, object>();
public async Task OnResourceExecutionAsync(ResourceExecutingContext context, ResourceExecutionDelegate next)
{
string cacheKey = context.HttpContext.Request.Path.ToString();
if (CACHE_DICT.TryGetValue(cacheKey, out object result))
{
context.Result = (IActionResult)result;
}
else
{
ResourceExecutedContext resource = await next.Invoke();
if (resource.Result != null)
{
CACHE_DICT.TryAdd(cacheKey, resource.Result);
}
}
}
}
ResourceFilter案例
下面过滤器需要修改query参数和body,因为ResourceFilter在ModelBinder之前执行,所以必须使用ResourceFilter
public class CustomerAsyncResourceFilterAttribute : Attribute, IAsyncResourceFilter
{
public async Task OnResourceExecutionAsync(ResourceExecutingContext context, ResourceExecutionDelegate next)
{
var request = context.HttpContext.Request;
var response = context.HttpContext.Response;
if (request.Method == "GET")
{
if (request.QueryString.HasValue)
{
var queryString = request.QueryString.Value?.Substring(1);
if (!string.IsNullOrEmpty(queryString))
{
queryString = Regex.Match(queryString, "(sign=)*(?<sign>[\\S]+)").Groups[2].Value;
queryString = RSACryptoHelper.RSADecryptOpenSSL(CONST.RSA_PRIVATE_KEY, queryString);
request.QueryString = new QueryString(queryString.StartsWith("?") ? queryString : "?" + queryString);
}
}
}
else if (request.Method == "POST")
{
//这里ReadToEnd执行完毕后requestBodyStream流的位置会从0到最后位置(即request.ContentLength)
var body = await new StreamReader(request.BodyReader.AsStream()).ReadToEndAsync();//读取body
body = Regex.Match(body, "(sign=)*(?<sign>[\\S]+)").Groups[2].Value;
body = RSACryptoHelper.RSADecryptOpenSSL(CONST.RSA_PRIVATE_KEY, body);
byte[] newBodyBytes = Encoding.UTF8.GetBytes(body);
var requestBodyStream = new MemoryStream();
requestBodyStream.Seek(0, SeekOrigin.Begin);
requestBodyStream.Write(newBodyBytes, 0, newBodyBytes.Length);
request.Body = requestBodyStream;
request.Body.Seek(0, SeekOrigin.Begin);
}
ResourceExecutedContext resource = await next.Invoke();
}
}
ActionFilter
Action过滤器在实例化控制器之后、Action执行前后执行,通常用于记录Action日志、校验Action参数
同步:
public class CustomerActionFilterAttribute : Attribute, IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
Console.WriteLine("Arguments:" + JsonConvert.SerializeObject(context.ActionArguments));
}
public void OnActionExecuted(ActionExecutedContext context)
{
Console.WriteLine("Result:" + JsonConvert.SerializeObject(context.Result));
}
}
异步:
public class CustomerAsyncActionFilterAttribute : Attribute, IAsyncActionFilter
{
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
Console.WriteLine("Arguments:" + JsonConvert.SerializeObject(context.ActionArguments));
ActionExecutedContext executedContext = await next();
Console.WriteLine("Result:" + JsonConvert.SerializeObject(executedContext.Result));
}
}
ResultFilter
在视图渲染和结果的时候,统一处理
比如修改JSON格式,对结果进行进一步加工
public class CustomAsyncResultFilterAttribute : Attribute, IAsyncResultFilter
{
public async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
{
{
if (context.Result is JsonResult)
{
JsonResult result = (JsonResult)context.Result;
context.Result = new JsonResult(new AjaxResult()
{
Success = true,
Message = "OK",
Data = result.Value
});
}
}
await next.Invoke();
}
}
ExceptionFilter
public class CustomExceptionFilterAttribute : ExceptionFilterAttribute
{
private readonly IHostingEnvironment _hostingEnvironment;
private readonly IModelMetadataProvider _modelMetadataProvider;
public CustomExceptionFilterAttribute(
IHostingEnvironment hostingEnvironment,
IModelMetadataProvider modelMetadataProvider)
{
_hostingEnvironment = hostingEnvironment;
_modelMetadataProvider = modelMetadataProvider;
}
public override void OnException(ExceptionContext filterContext)
{
if (!filterContext.ExceptionHandled)//异常有没有被处理过
{
string controllerName = (string)filterContext.RouteData.Values["controller"];
string actionName = (string)filterContext.RouteData.Values["action"];
if (this.isAjaxRequest(filterContext.HttpContext.Request))//检查请求头
{
filterContext.Result = new JsonResult(
new AjaxResult()
{
Result = DoResult.Failed,
PromptMsg = "系统出现异常,请联系管理员",
DebugMessage = filterContext.Exception.Message
}//这个就是返回的结果
);
}
else
{
var result = new ViewResult { ViewName = "~/Views/Shared/Error.cshtml" };
result.ViewData = new ViewDataDictionary(_modelMetadataProvider, filterContext.ModelState);
result.ViewData.Add("Exception", filterContext.Exception);
filterContext.Result = result;
}
filterContext.ExceptionHandled = true;//异常已被处理,则异常处理中间件就认为没有错误了,不会进入到处理逻辑中。
}
}
private bool isAjaxRequest(HttpRequest request)
{
string header = request.Headers["X-Requested-With"];
return "XMLHttpRequest".Equals(header);
}
}
AuthorizeFilter
案例:不通过中间件实现认证功能,通过IAuthorizationFilter实现认证
/// <summary>
/// 自定义验证WebApi是否已登录
/// </summary>
public class ApiAuthFilterAttribute : Attribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationFilterContext context)
{
var httpContext = context.HttpContext;
var services = httpContext.RequestServices;
var request = httpContext.Request;
var response = httpContext.Response;
string clientToken = request.Headers["TOKEN"].ToString();
AuthUser authUser = GetUser(clientToken);
if (authUser == null)
{
context.Result = new ForbidResult();
}
else
{
ClaimsIdentity userIdentity = new ClaimsIdentity(new List<Claim> { new Claim(ClaimTypes.NameIdentifier, authUser.UserID.ToString()), new Claim(ClaimTypes.Name, authUser.UserName) }, "ticket");
ClaimsPrincipal userPrincipal = new ClaimsPrincipal(userIdentity);
httpContext.User = userPrincipal;
}
}
}
BaseController:
[TypeFilter(typeof(ApiAuthFilterAttribute))]
public class BaseController : ControllerBase
{
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!