.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
,实现IActionFilter
的OnActionExecuting
和 OnActionExecuted
方法。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();
}
本文来自博客园,作者:一纸年华,转载请注明原文链接:https://www.cnblogs.com/nullcodeworld/p/18210626