【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是一个框架提供的抽象类,实现了IActionFilterIAsyncActionFilterIResultFilterIAsyncResultFilter
如果同时重写了同步方法、异步方法,同步方法不会被执行

注册全局过滤器

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

如何在过滤器中注入服务

使用TypeFilterServiceFilter都可以解决过滤器注入问题,使用方法如下:

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
    {
    }
posted @ 2020-01-03 13:32  .Neterr  阅读(1645)  评论(0编辑  收藏  举报