.NET5中AOP的5个过滤器

  AOP(Aspect Oriented Programming),即面向切面编程。可以在不修改之前的代码为基础,动态增加新功能。.NET5中提供了5种AOP的Filter,分别是

  • ActionFilter(方法)
  • ResourceFilter(资源)
  • ExceptionFilter(异常)
  • ResultFilter(结果)
  • AuthorizationFilter(鉴权授权)

1. ActionFilter

  ActionFilter在方法的执行前后可执行相应操作。

1.1 ActionFilter基本使用

  (1) 自定义一个CustomActionFilterAttribute,并且继承Attribute,实现IActionFilterOnActionExecutingOnActionExecuted方法。OnActionExecuting在方法执行前执行,OnActionExecuted在方法执行后执行。

public class CustomActionFilterAttribute : Attribute, IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
Console.WriteLine("执行OnActionExecuting");
}
public void OnActionExecuted(ActionExecutedContext context)
{
Console.WriteLine("执行OnActionExecuted");
}
}

  (2) 在Controller的方法上添加特性标记。

public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
public HomeController(ILogger<HomeController> logger)
{
_logger = logger;
}
[CustomActionFilter]
public IActionResult Index()
{
Console.WriteLine("执行控制器中的Index");
return View();
}
// ...
}
}

打印的结果为:

执行OnActionExecuting
执行控制器中的Index
执行OnActionExecuted

1.2 ActionFilter的多种使用

  除上述来使用ActionFilter,也可通过继承ActionFilterAttribute(系统提供的实现),根据自己的需要,覆写不同的方法,达到自己的诉求。

public class CustomActionFilterSystemAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
Console.WriteLine("执行OnActionExecuting");
}
public override void OnActionExecuted(ActionExecutedContext context)
{
Console.WriteLine("执行OnActionExecuted");
}
}

  异步版本的实现,通过实现IAsyncActionFilter接口来实现。

public class CustomActionFilterAsyncAttribute : Attribute, IAsyncActionFilter
{
public Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
return Task.Run(() => {
Console.WriteLine("执行OnActionExecutionAsync");
});
}
}

1.3 ActionFilter的应用

  可以用来记录日志,通过log4net将日志写入文件,ILogger依赖注入。当构造函数有了参数后控制器方法上的[CustomActionFilter]特性就不能这么写了,应该写为[TypeFilter(typeof(CustomActionFilterAttribute))].

public class CustomActionFilterAttribute : Attribute, IActionFilter
{
private ILogger<CustomActionFilterAttribute> _logger = null;
public CustomActionFilterAttribute(ILogger<CustomActionFilterAttribute> logger)
{
_logger = logger;
}
public void OnActionExecuting(ActionExecutingContext context)
{
_logger.LogInformation(JsonConvert.SerializeObject(context.HttpContext.Request.Query));
_logger.LogInformation("执行CustomActionFilterAttribute.OnActionExecuting");
}
public void OnActionExecuted(ActionExecutedContext context)
{
_logger.LogInformation(JsonConvert.SerializeObject(context.Result));
_logger.LogInformation("执行CustomActionFilterAttribute.OnActionExecuted");
}
}

1.4 ActionFilter的多种注册

  (1)[CustomActionFilter]---Fitler必须有无参数构造函数。
  (2)[TypeFilter(typeof(CustomActionFilterAttribute))],可以没有无参数构造函数,可以支持依赖注入。
  (3)[ServiceFilter(typeof(CustomActionFilterAttribute))],可以没有无参数构造函数,可以支持依赖注入,但是必须要注册服务。

1.5 FilterFactory扩展定制

  为什么写上TypeFilter这样的特性就可以支持依赖注入呢?它是由IOC容器来完成的。

  (1)现在我们自定义一个特性类CustomFilterFactory,继承Attribute,实现接口IFilterFactory,并实现接口中的方法。

public class CustomFilterFactory : Attribute, IFilterFactory
{
private readonly Type _type = null;
public bool IsReusable => true;
public CustomFilterFactory(Type type)
{
_type = type;
}
public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
{
object oInstance = serviceProvider.GetService(_type);
return (IFilterMetadata)oInstance;
}
}

  (2)在Startup类的ConfigureServices中注册。

public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddTransient<CustomActionFilterAttribute>();
}

  (3)最后再将 [CustomFilterFactory(typeof(CustomActionFilterAttribute))]标记到控制器的Action方法上就行了,和[TypeFilter(typeof(CustomActionFilterAttribute))]效果一样。

1.6 Filter的生效范围

  • 标记在Action上,就只对当前Action生效。
  • 标记在Controller上,就对Controller上中的所有Action生效。
  • 全局注册,对于当前整个项目中的Action都生效,在ConfigureServices中增加以下代码即可
services.AddMvc(option =>
{
option.Filters.Add<CustomActionFilterAttribute>(); //全局注册:
});

1.7 Filter的执行顺序

  如果定义三个actionFilter,分别注册全局,控制器、Action;执行顺序如何呢?

<1> 控制器实例化
<2> 全局注册的Filter - OnActionExecuting
<3> 控制器注册的Filter - OnActionExecuting
<4> Actioin注册的Filter - OnActionExecuting
<5> 执行Action内部的逻辑
<6> Action注册的Filter - OnActionExecuted
<7> 控制器注册的Filter - OnActionExecuted
<8> 全局注册的Filter - OnActionExecuted

  如果想要改变执行顺序,需要在注册Filter的时候,指定Order的值,执行顺序会按照值从小到大执行。例如在方法上添加[CustomActionActionFilterAtrribute(Order = -1)],不添加则Order值为0。

1.8 Filter匿名

  如果全局注册,Filter生效于所有的Acion,如果有部分Action我希望不生效怎么办呢?这就需要匿名,可以避开Filter的检查。

  下面自定义Filter匿名。
  (1)自定义一个特性类CustomAllowAnonymousAttributet,将[CustomAllowAnonymous]特性添加到需要避开的方法上。
  (2)在需要匿名的Filter内部,检查是否需要匿名(检查是否标记的有匿名特性),如果有就直接避开。

public void OnActionExecuting(ActionExecutingContext context)
{
if(context.ActionDescriptor.EndpointMetadata.Any(item=>item.GetType() == typeof(CustomAllowAnonymousAttribute)))
{
return;
}
// ......
}

2. ResourceFilter

  ResourceFilter就是为了缓存而存在的。

public class CustomResourceFilterAttribute : Attribute, IResourceFilter
{
private static Dictionary<string, object> CacheDictionary = new Dictionary<string, object>();
public void OnResourceExecuting(ResourceExecutingContext context)
{
//在这里就判断是否有缓存,只要是key 不变,缓存就不变
string key = context.HttpContext.Request.Path;
if (CacheDictionary.Any(item => item.Key == key))
{
//断路器,只要是对Result赋值,就不继续往后走了;
context.Result = CacheDictionary[key] as IActionResult;
}
Console.WriteLine("执行OnResourceExecuting");
}
public void OnResourceExecuted(ResourceExecutedContext context)
{
string key = context.HttpContext.Request.Path;
CacheDictionary[key] = context.Result;
Console.WriteLine("执行OnResourceExecuted");
}
}
[CustomResourceFilter]
public IActionResult IndexResource()
{
ViewBag.Date = DateTime.Now;
return View();
}

3. ExceptionFilter

  ExceptionFilter用来处理异常。

  (1)自定义一个CustomExceptionFilterAttribute ,实现IExceptionFilter接口。
  (2)实现方法,先判断,异常是否被处理过,如果没有被处理过,就处理。如果是ajax请求,就返回JosnResult,如果不是Ajax请求,就返回错误页面。
  (3)全局注册使用(和之前的ActionFilter全局注册一样)。

public class CustomExceptionFilterAttribute : Attribute, IExceptionFilter
{
private IModelMetadataProvider _modelMetadataProvider = null;
public CustomExceptionFilterAttribute(IModelMetadataProvider modelMetadataProvider)
{
_modelMetadataProvider = modelMetadataProvider;
}
public void OnException(ExceptionContext context)
{
if (!context.ExceptionHandled) //异常是否被处理过
{
//在这里处理 如果是Ajax请求===返回Json
if (this.IsAjaxRequest(context.HttpContext.Request))//header看看是不是XMLHttpRequest
{
context.Result = new JsonResult(new
{
Result = false,
Msg = context.Exception.Message
});//中断式---请求到这里结束了,不再继续Action
}
else
{
//跳转到异常页面
var result = new ViewResult { ViewName = "~/Views/Shared/Error.cshtml" };
result.ViewData = new ViewDataDictionary(_modelMetadataProvider, context.ModelState);
result.ViewData.Add("Exception", context.Exception);
context.Result = result; //断路器---只要对Result赋值--就不继续往后了;
}
context.ExceptionHandled = true;
}
}
private bool IsAjaxRequest(HttpRequest request)
{
string header = request.Headers["X-Requested-With"];
return "XMLHttpRequest".Equals(header);
}
}

 ExceptionFilter能捕捉到哪些异常?

  • 控制器实例化异常 [True]
  • 异常发生在Try-cache中 [False]
  • 在视图中发生异常 [False]
  • Service层发生异常 [True]
  • 在Action中发生异常 [True]
  • 请求错误路径异常 [True] 可以使用中间件来支持,只要不是200的状态,就都可以处理。

4. ResultFilter

  ResultFilter在return 返回时调用。

  它的应用,比如双语言系统,其实就需要两个视图根据语言的不同,来选择不同的视图来渲染。因为在渲染视图之前,会进入到OnResultExecuting方法,就可以在这个方法中确定究竟使用哪一个视图文件。

  (1)自定义一个类,继承Attribute,实现IResultFilter接口,实现方法
  (2)标记在Action方法头上
  (3)执行顺序:视图执行前,渲染视图,视图执行后

public class CustomResultFilterAttribute : Attribute, IResultFilter
{
private IModelMetadataProvider _modelMetadataProvider = null;
public CustomResultFilterAttribute(IModelMetadataProvider modelMetadataProvider)
{
_modelMetadataProvider = modelMetadataProvider;
}
/// <summary>
/// 渲染视图之前执行
/// </summary>
/// <param name="context"></param>
public void OnResultExecuting(ResultExecutingContext context)
{
//在这里就可以有一个判断,符合某个情况,就使用哪一个视图;
Console.WriteLine("渲染视图之前执行");
string view = context.HttpContext.Request.Query["View"];//也可以是配置文件
if (view == "1") //中文
{
var result = new ViewResult { ViewName = "~/Views/Seventh/IndexOne.cshtml" };
result.ViewData = new ViewDataDictionary(_modelMetadataProvider, context.ModelState);
context.Result = result;
}
else
{
var result = new ViewResult { ViewName = "~/Views/Seventh/IndexOne-2.cshtml" };
result.ViewData = new ViewDataDictionary(_modelMetadataProvider, context.ModelState);
context.Result = result;
}
}
/// <summary>
/// 渲染视图之后执行
/// </summary>
/// <param name="context"></param>
public void OnResultExecuted(ResultExecutedContext context)
{
Console.WriteLine("渲染视图之后执行");
}
}

5. AuthorizationFilter

AuthorizationFilter用来做鉴权授权。通过中间件来支持。

5.1 鉴权授权

  (1)在Startup中方法Configure的app.UseRouting()之后,在app.UseEndpoints()之前,增加鉴权授权。 鉴权app.UseAuthentication(),授权app.UseAuthorization()

public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory)
{
......
// 鉴权 监测用户是否登录
app.UseAuthentication();
// 授权 监测有没有权限访问后续页面
app.UseAuthorization();
......
}

  (2)在Startup中方法ConfigureServices内添加注册。

public void ConfigureServices(IServiceCollection services)
{
......
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.LoginPath = new PathString("/Home/Login"); //如果授权失败就跳转到登录
});
......
}

  (3)在指定Action上加上[Authorize]特性做鉴权授权。也可以全局标记。

[Authorize]
public IActionResult Index()
{
return View();
}
[AllowAnonymous] //避开权限检查
public IActionResult Login()
{
return View();
}
[HttpPost]
[AllowAnonymous]
public IActionResult Login(string name, string password, string verify)
{
string verifyCode = base.HttpContext.Session.GetString("CheckCode");
//if (verifyCode != null && verifyCode.Equals(verify, StringComparison.CurrentCultureIgnoreCase))
//{
#region 鉴权:鉴权,检测有没有登录,登录的是谁,赋值给User
//rolelist 是登录成功后用户的角色---是来自于数据库的查询;不同的用户会查询出不同的角色;
var rolelist = new List<string>() {
"Admin",
"Teacher",
"Student"
};
//ClaimTypes.Role就是做权限认证的标识;
var claims = new List<Claim>()//鉴别你是谁,相关信息
{
new Claim(ClaimTypes.Role,"Admin"),
new Claim(ClaimTypes.Name,name),
new Claim("password",password),//可以写入任意数据
new Claim("Account","admin"),
new Claim("role","admin"),
new Claim("admin","admin"),
new Claim("User","admin")
};
foreach (var role in rolelist)
{
claims.Add(new Claim(ClaimTypes.Role, role));
}
ClaimsPrincipal userPrincipal = new ClaimsPrincipal(new ClaimsIdentity(claims, "Customer"));
HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, userPrincipal, new AuthenticationProperties
{
ExpiresUtc = DateTime.UtcNow.AddMinutes(30),//过期时间:30分钟
}).Wait();
#endregion
var user = HttpContext.User;
return base.Redirect("/Home/Index");
}
else
{
base.ViewBag.Msg = "账号密码错误";
}
#endregion
//}
//else
//{
// base.ViewBag.Msg = "验证码错误";
//}
return View();
}

5.1 鉴权授权-角色授权

  即用户角色不同,在访问页面时需要做不同的拦截。

  (1)一个特性标记到方法上,通过逗号分隔不同角色,只要是有一个角色符合就能够访问,角色之间是或者的关系。

[Authorize(Roles = "Admin,Teacher,Student")]

  (2)多个特性标记,多个角色之前是且的关系,必须要包含所有的角色,才能够访问。

[Authorize(Roles = "Admin")]
[Authorize(Roles = "Teacher")]
[Authorize(Roles = "Student")]

5.2 鉴权授权-策略授权

  上面的角色授权是在代码中写死了角色,但我们更希望能够用处理逻辑来完成校验,就需要策略授权。

  (1)添加CustomAuthorizationRequirement类继承自IAuthorizationRequirement,用来传递策略名称。

public class CustomAuthorizationRequirement : IAuthorizationRequirement
{
public string Name { get; set; }
public CustomAuthorizationRequirement(string policyname)
{
this.Name = policyname;
}
}

  (2)添加CustomAuthorizationHandler类专用做检验逻辑, 继承自泛型抽象类AuthorizationHandler<CustomAuthorizationRequirement>

protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
CustomAuthorizationRequirement requirement)
{
if (requirement.Name == "Policy01")
{
///策略1的逻辑
}
if (requirement.Name == "Policy02")
{
///策略1的逻辑
}
//其实这里可以去数据库里面去做一些查询,然后根据用户的信息,做计算;如果符合就context.Succeed(requirement);
//否则就Task.CompletedTask;
//context.User 鉴权成功(登录成功以后),用户的信息;
var role = context.User.FindFirst(c => c.Value.Contains("admin"));
if (role != null)
{
context.Succeed(requirement); //验证通过了
}
return Task.CompletedTask; //验证不通过
}

  (3)添加注册,并支持多种策略。

services.AddSingleton<IAuthorizationHandler, CustomAuthorizationHandler>();
services.AddAuthorization(option =>
{
option.AddPolicy("customPolicy01", policy =>
{
policy.AddRequirements(new CustomAuthorizationRequirement("Policy01"));
});
});
services.AddAuthorization(option =>
{
option.AddPolicy("customPolicy02", policy =>
{
policy.AddRequirements(new CustomAuthorizationRequirement("Policy02"));
});
});

  (4)标记到使用的方法上。

[Authorize(policy: "customPolicy01")]
public IActionResult IndexPolicy()
{
return View();
}
posted @   一纸年华  阅读(44)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示

目录导航

1. ActionFilter
1.1 ActionFilter基本使用
1.2 ActionFilter的多种使用
1.3 ActionFilter的应用
1.4 ActionFilter的多种注册
1.5 FilterFactory扩展定制
1.6 Filter的生效范围
1.7 Filter的执行顺序
1.8 Filter匿名
2. ResourceFilter
3. ExceptionFilter
4. ResultFilter
5. AuthorizationFilter
5.1 鉴权授权
5.1 鉴权授权-角色授权
5.2 鉴权授权-策略授权