.net6 过滤器、管道模型

管道处理模型

1、[中间件](https://learn.microsoft.com/zh-cn/aspnet/core/fundamentals/middleware/?view=aspnetcore-7.0)

  • 可以在典型应用中了解现有中间件的顺序,以及在哪里添加自定义中间件。 你可以完全控制如何重新排列现有中间件,或根据场景需要注入新的自定义中间件。

    • ExceptionHandler异常捕获中间件: 如果接下来的流程中出现了异常, 可以使用他捕获,

    • 不建议改变CORS之前的顺序, 容易出错

ASP.NET Core 中间件管道

  • program.cs中 app.Use的就是中间件

2、Filter过滤器

  • Filter过滤器就是AOP. 面向切面编程

  • AOP 面向切面编程, 就是把无关业务的逻辑, 作为业务前置、业务中、业务后置执行, 比如权限验证、记录日志等操作

  • 上述中间件执行完成之后进入执行Filter过滤器

  • .net core 6中有多重过滤器流程 称作管道模型, 使用中间件链接

    • AuthorizationFilters 权限验证过滤器

    • ResourceFilters 资源过滤器

      • 擅长缓存

    • Model Binding 模型绑定

      • 接口中传入的模型参数, 与模型进行绑定

    • ActionFilters 行为过滤器

      • 擅长模型验证

      • 日志记录

    • ExceptionFilters 异常过滤器

      • 线程捕获异常错误

    • ResultFilters结果过滤器

      • 擅长处理结果

(0)五大过滤器

过滤器 名称   功能
AuthorizationFilter 授权过滤器 1 权限验证
ResourceFilters 资源管理过滤 2 缓存
ActionFilters 行为过滤器 3 模型验证、日志记录
ExceptionFilters 异常过滤器 4 异常处理
ResultFilters 结果过滤器 5 结果处理

(1)自定义过滤器

  • context是当前应用程序上下文, 包括如下内容

namespace SecondDemo.Filters
{
    //自定义方法过滤器 -》 这样就获得了特性[CtmActionFilterAttribute]
    public class CtmActionFilterAttribute : Attribute, IActionFilter
    {
        //方法执行后
        public void OnActionExecuted(ActionExecutedContext context)
        {
            //throw new NotImplementedException();
            Console.WriteLine("方法执行后");
        }
        //方法执行中 、 前
        public void OnActionExecuting(ActionExecutingContext context)
        {
            //throw new NotImplementedException();
            Console.WriteLine("方法执行中");
        }
    }
}

(2)注册方式

  • 方法注册: 注册到方法上边

  • 类注册: 注册到控制器上边

  • 全局注册: 注册到Program.cs

1.方法注册
  • 直接把过滤器注册到方法上边

[HttpGet(Name ="测试自定义方法过滤器")]
[CtmActionFilter]
public void Get() 
{
    Console.WriteLine("大家好 执行了");
}
2.类注册
  • 直接把过滤器注册到类上边

  • 类下边所有方法, 全部共享

  • 相同类型的过滤器, 类注册的要先于方法注册的执行

  • 不同类型的过滤器, 只按照管道模型的顺序执行

    [CtmClassFilterClass]
    [ApiController]
    [Route("/api/[controller]/[action]")]
    public class TestFilterController:ControllerBase
    {
        [HttpGet(Name ="测试自定义方法过滤器")]
        public void Get() 
        {
            Console.WriteLine("大家好 执行了");
        }
    }
3.全局注册
  • 所有的控制器方法在执行之前都会进入该过滤器

  • 相同类型的过滤器, 全局注册的要先于其他两个注册的执行

//全局注册过滤器
builder.Services.AddControllers(opt => 
{
    opt.Filters.Add(typeof(CtmActionFilterEnvAttribute))
});
builder.Services.AddControllers(opt => 
{
    opt.Filters.Add<CtmActionFilterEnvAttribute>()
544454});
0.注册执行顺序
  • 全局注册, 数字越小越靠前执行

builder.Services.AddControllers(opt => 
                                { 
                                    opt.Filters.Add<CtmActionFilterEnv1Attribute>(1));
                                    opt.Filters.Add<CtmActionFilterEnv2Attribute>(2));
                                }

(4)filter的IOC

用过滤器, 建议使用过滤器构造函数注入, 作为方法特性, 当有属性需要作为构造器参数的时候, 如下例子

public class CtmAuthorizationFilterAttribute : Attribute, IAuthorizationFilter
    {
​
        private IActionsManager ActionsManager;
        public CtmAuthorizationFilterAttribute(IActionsManager actionsManager)
        {
            ActionsManager = actionsManager;
        }
}
  • 使用TypeFilter过滤器构造器注册, 先去把参数加入IOC容器, 再到相应的方法上注册为方法特性

    builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());
    builder.Host.ConfigureContainer<ContainerBuilder>(builder =>
    {
        builder.RegisterType<ActionsManager>().As<IActionsManager>();
    });
    [HttpGet]
    [TypeFilter(typeof(CtmAuthorizationFilterAttribute))]
    public string TestAuthorizationActionFilter(int userId, string userName)
    {
        //...
    }
  • 配合IOC容器注册使用ServiceFilter

    • 使用ServiceFilter, 无论是filter本身还是filter需要注入的参数, 都需要在构造IOC中注册

    builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());
    builder.Host.ConfigureContainer<ContainerBuilder>(builder =>
    {
        builder.RegisterType<ActionsManager>().As<IActionsManager>();
        builder.RegisterType<CtmActionFilterAttribute>();
    });
    [HttpGet]
    [ServiceFilter(typeof(CtmAuthorizationFilterAttribute))]
    public string TestAuthorizationActionFilter(int userId, string userName)
    {
        //...
    }

3、AuthorizationFilter授权过滤器

  • 是过滤器管道中第一个过滤器

    • 第一个过滤器, 还没有进行到类型绑定阶段Model Binding

    • 无法直接通过context上下文获取接口参数

  • 控制对方法的访问

  • 只能拦截执行前的, 不能拦截执行后的

namespace SecondDemo.Models
{
    //方法的信息
    public class Action
    {
        public int Id { get; set; }
        public string ControllerName { get; set; }
        public string ActionName { get; set; }
        /// <summary>
        /// 返回用户拥有的方法列表
        /// </summary>
        /// <param name="UserId"></param>
        /// <returns></returns>
        public static List<Models.Action> GetActionByUserId(int UserId)
        {
            if (UserId == 6)
            {
                return new List<Action> { new Action { ActionName = "Get", ControllerName = "TestAuthorizationController", Id = 1 } };
            }
            return default;
        }
    }
}
namespace SecondDemo.Models
{
    //用户的信息
    public class User
    {
        public int Id { get; set; }
        public string UserName { get; set; }
    }
}
​
namespace SecondDemo.Controllers
{
    [ApiController]
    [Route("/api/[controller]/[action]")]
    public class TestAuthorizationController : ControllerBase
    {
        [HttpGet]
        [CtmActionFilterClassAttribute]
        public string Get(int userId, string userName)
        {
            return userId.ToString();
        }
    }
}
namespace SecondDemo.Filters
{
    /// <summary>
    /// 鉴定用户权限
    /// </summary>
    public class CmtAuthorizationFilterAttribute : Attribute, IAuthorizationFilter
    {
        public void OnAuthorization(AuthorizationFilterContext context)
        {
            //获取请求参数列表
            string paramValue = context.HttpContext.Request.QueryString.Value;
            //获取字典<参数主键, 参数值>
            Dictionary<string, object> paramDic = paramValue.GetParam();
            //判断是否具有如下的ID
            if (paramDic.TryGetValue(AuthorizationConst.USER_ID, out object value))
            {
                //获取用户ID、用户的权限列表
                int userId = Convert.ToInt32(paramDic[AuthorizationConst.USER_ID]);
                List<Models.Action> actionsList = Models.Action.GetActionByUserId(userId);
                //获取当前请求的 接口/控制器名
                string actionName = context.RouteData.Values["action"].ToString();
                string controllerName = context.RouteData.Values["controller"].ToString();
                //对比当前接口/控制器名字是否相同
                if (!actionsList.Any(action => action.ActionName == actionName && action.ControllerName == controllerName))
                {
                    throw new Exception("用户无权限");
                }
                /*var sameAction = from action in actionsList
                            where action.ActionName == actionName
                            where action.ControllerName == controllerName
                            select action;
                if (sameAction == null)
                {
                    throw new Exception("用户无权限");
                }*/
            }
            throw new Exception("用户没有分配权限");
        }   
    }
}
namespace SecondDemo.Extensions
{
    public static class AuthorizationParameterExtensions
    {
        /// <summary>
        /// 增强string, 获取请求参数
        /// </summary>
        /// <param name="paramValue"></param>
        /// <returns></returns>
        public static Dictionary<string, object> GetParam(this string paramValue)
        {
            paramValue.Replace("?", "").Trim();
            //获取参数
            string[] paramValues = paramValue.Split("%");
            Dictionary<string, object> paraDic = new Dictionary<string, object>();
            //遍历切割方法请求路径, 获取到请求参数的key和值
            foreach (var param in paramValues)
            {
                paraDic.Add(param.Split("=")[0].Trim(), param.Split("=")[1].Trim());
            }
            return paraDic;
        }
​
    }

4、ResourceFilter资源过滤器

(1)短路器

  • 请求经过短路器直接返回给前端了

  • 不会继续在短路器后继续执行

  • ResouceExecutedcontext中, Result可以直接作为短路器使用

    • 只要给这个属性赋值, 直接短路

    • 类型是ActionResult, 常常给他一个JsonResult()的值

context.Result = new JsonResult();

(2)利用中间件缓存实现短路

  • IOC注入缓存中间件, 单例模式注入, 选用瞬态则导致缓存失效

//开启autofac
builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());
//使用autofac进行ioc注入
builder.Host.ConfigureContainer<ContainerBuilder>(builder =>
{
    //注入缓存中间件, 以单例模式注入, 选用瞬态则导致缓存失效
    builder.RegisterType<MemoryCache>().As<IMemoryCache>().SingleInstance();
});
  • controller

[HttpGet]
[TypeFilter(typeof(CtmResourceFilterAttribute))]
public string TestResourceFilterAttribute()
{
    //...
}
  • Filter

namespace FilterDemo.Filters
{
    public class CtmResourceFilterAttribute : Attribute, IResourceFilter
    {
        //缓存
        private IMemoryCache memoryCache;
​
        public CtmResourceFilterAttribute(IMemoryCache memoryCache)
        {
            this.memoryCache = memoryCache;
        }
​
        public void OnResourceExecuted(ResourceExecutedContext context)
        {
            //拿到结束上次访问时的地址, 获取上一次访问的Api[Action]
            string path = context.HttpContext.Request.Path;
            //以上次访问的地址作为key, 将返回的数据存储到缓存中
            memoryCache.Set(CacheConst.REQUEST_PATH_KEY, path);            
        }
​
        public void OnResourceExecuting(ResourceExecutingContext context)
        {
            //获取当前的访问地址
            string path = context.HttpContext.Request.Path;
            //当前缓存中是否包含当前访问的地址
            if (memoryCache.TryGetValue(CacheConst.REQUEST_PATH_KEY, out object pathValue))
            {
                //如果包含, 则使用短路器, 接下来的步骤不执行了, 直接响应回去返回缓存的数据
                context.Result = pathValue as ObjectResult;
            }
        }
    }
}
  • 公众常量

public class CacheConst
{
    public const string REQUEST_PATH_KEY = "request:path";
}

5、ActionFilter行为过滤器

  • 适合搞模型验证, 日志记录

(1)操作日志例子

//开启autofac
builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());
//使用autofac进行ioc注入
builder.Host.ConfigureContainer<ContainerBuilder>(builder =>
{
    builder.RegisterType<User>();
}
namespace FilterDemo.Filters
{
    public class CtmActionFilterAttribute : Attribute, IActionFilter
    {
        //日志组件
        private readonly ILogger<CtmActionFilterAttribute> _logger;
        //用户
        private readonly User user;
​
        //构造函数注入日志组件
        public CtmActionFilterAttribute(ILogger<CtmActionFilterAttribute> logger, User user)
        {
            _logger = logger;
            this.user = user;
        }
​
        public void OnActionExecuted(ActionExecutedContext context)
        {
            throw new NotImplementedException();
        }
​
        public void OnActionExecuting(ActionExecutingContext context)
        {
            //获取请求参数
            IDictionary<string, object?> arguments = context.ActionArguments;
            //获取请求路径
            string path = context.HttpContext.Request.Path;
            if (arguments.ContainsKey("user"))
            {
                User user = arguments["user"] as User;
                _logger.LogInformation($"{user.userName} is visting {path} at {DateTime.Now}");
            }
            else 
            {
                _logger.LogInformation($"{user.userName} is visting {path} at {DateTime.Now}");
            }
        }
    }
}
namespace FilterDemo.Controllers
{
    [ApiController]
    [Route("/api/[controller]/[action]")]
    public class TestController
    {
        [HttpGet]
        //使用过滤器构造器注入
        [TypeFilter(typeof(CtmActionFilterAttribute))]
        //配合IOC容器注册才能使用ServiceF
        [ServiceFilter(typeof(CtmActionFilterAttribute))]
        public string TestActionTypeFilter()
        {
            return "ok";
        }
    }
}

6、ExceptionFilter异常过滤器

  • 捕获错误信息

  • 作为接口方法特性, 可以在过滤器中捕获

  • 过滤器中记录错误日志, 并且使用短路器响应回去

例子

public class CtmExceptionFilterAttribute : Attribute, IExceptionFilter
{
    //日志
    private readonly ILogger<CtmExceptionFilterAttribute> logger;
    public CtmExceptionFilterAttribute(Logger<CtmExceptionFilterAttribute> logger)
    {
        this.logger = logger;
    }
​
    public void OnException(ExceptionContext context)
    {
        //获取异常信息
        string exceptionMessage = context.Exception.Message;
        //记录错误日志
        logger.LogError(exceptionMessage);
        //短路器返回, 错误信息
        context.Result = new ContentResult
        {
            Content = context.Exception.Message
        };
    }
}
[HttpGet]
[TypeFilter(typeof(CtmExceptionFilterAttribute))]
public string TestExceptionFiltterAttribute() 
{
    throw new Exception("test");
    return "ok";
}

(1)捕获范围

  • 只能捕获到ActionFilter执行前, 执行中, 执行后

  • 非常适合捕获发生在操作中的异常

  • 不像错误处理中间件那样灵活, 建议使用中间件处理异常

7、ResultFilterIAsyncResultFilter结果过滤器

  • 仅当操作或操作过滤器生成操作结果时, 才会执行结果过滤器, 不会在以下情况下执行结果过滤器

    • 授权*过滤器或资源过滤器使管道短路

    • 异常过滤器通过生成操作结果来处理异常

  • 如果在IResultFilter.OnResultExecuting中引发异常, 则会导致

    • 阻止操作结果和后续过滤器的执行

    • 结果被视为失败而不是成功

namespace FilterDemo.Filters
{
    public class CtmResultFilterAttribute : Attribute, IResultFilter
    {
        public void OnResultExecuted(ResultExecutedContext context)
        {
            //...
        }
​
        public void OnResultExecuting(ResultExecutingContext context)
        {
            //...
        }
    }
}

(1)IAlwaysRunResultFilterIAsyncAlwaysRunResultFilter

  • 这两个接口声明了一个针对所有操作结果运行IResultFilter实现, 不管发生什么异常都会正常运行

    • 不管资源过滤器、授权过滤器设置短路

    • 不挂异常过滤器执行

  • 应用: 如果在接口执行中出现异常, 控制器会返回状态码, 通过AOP根据不同状态码返回给前端

    public class CtmAlwaysRunResultFilterAttribute : Attribute, IAlwaysRunResultFilter
    {
    ​
        public void OnResultExecuting(ResultExecutingContext context)
        {
            //如果在接口执行中出现异常, 控制器会返回状态码
            if (context.Result is StatusCodeResult statusCodeResult
                && statusCodeResult.StatusCode == StatusCodes.Status404NotFound)
            {
                //根据不同状态码返回给前端
                context.Result = new ObjectResult("找不到资源") { StatusCode = StatusCodes.Status404NotFound };
            }
        }
    }

8、过滤器执行顺序

(1)未指定执行顺序

过滤器执行顺序如下全局注册 => 类注册 => 方法注册 =>

(2)program.cs中使用全局注册指定执行顺序

  • 数字越小, 执行顺序越靠前

builder.Services.AddControllers(opt => 
{ 
    opt.Filters.Add<CtmActionFilterEnv1Attribute>(1));
    opt.Filters.Add<CtmActionFilterEnv2Attribute>(2));
}

(3)Controller中使用特性的属性指定执行顺序

[TypeFilter(构造器, 参数, 可重复用, 执行顺序)]
public class Controller
{
    [TypeFilter(构造器, 参数, 可重复用, 执行顺序)]
    public string Test()
    {
        //...
    }
}
[HttpGet]
[TypeFilter(typeof(CtmExceptionFilterAttribute), Order = 1)]
public string TestExceptionFiltterAttribute() 
{
    throw new Exception("test");
    return "ok";
}

posted on 2023-08-10 22:55  老菜农  阅读(143)  评论(0编辑  收藏  举报

导航