.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   老菜农  阅读(320)  评论(0编辑  收藏  举报

相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律

导航

统计信息

点击右上角即可分享
微信分享提示