??AOP(Aspect Oriented Programming)住诸,即面向切面編程骑祟。可以在不修改之前的代碼為基礎(chǔ)闽撤,動(dòng)態(tài)增加新功能。.NET5中提供了5種AOP的Filter脯颜,分別是
- ActionFilter(方法)
- ResourceFilter(資源)
- ExceptionFilter(異常)
- ResultFilter(結(jié)果)
- AuthorizationFilter(鑒權(quán)授權(quán))
1. ActionFilter
??ActionFilter在方法的執(zhí)行前后可執(zhí)行相應(yīng)操作哟旗。
1.1 ActionFilter基本使用
??(1) 自定義一個(gè)CustomActionFilterAttribute,并且繼承Attribute
栋操,實(shí)現(xiàn)IActionFilter
的OnActionExecuting
和 OnActionExecuted
方法闸餐。OnActionExecuting在方法執(zhí)行前執(zhí)行,OnActionExecuted在方法執(zhí)行后執(zhí)行矾芙。
public class CustomActionFilterAttribute : Attribute, IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
Console.WriteLine("執(zhí)行OnActionExecuting");
}
public void OnActionExecuted(ActionExecutedContext context)
{
Console.WriteLine("執(zhí)行OnActionExecuted");
}
}
??(2) 在Controller的方法上添加特性標(biāo)記舍沙。
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
public HomeController(ILogger<HomeController> logger)
{
_logger = logger;
}
[CustomActionFilter]
public IActionResult Index()
{
Console.WriteLine("執(zhí)行控制器中的Index");
return View();
}
// ...
}
}
打印的結(jié)果為:
執(zhí)行OnActionExecuting
執(zhí)行控制器中的Index
執(zhí)行OnActionExecuted
1.2 ActionFilter的多種使用
??除上述來使用ActionFilter,也可通過繼承ActionFilterAttribute
(系統(tǒng)提供的實(shí)現(xiàn))剔宪,根據(jù)自己的需要拂铡,覆寫不同的方法,達(dá)到自己的訴求葱绒。
public class CustomActionFilterSystemAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
Console.WriteLine("執(zhí)行OnActionExecuting");
}
public override void OnActionExecuted(ActionExecutedContext context)
{
Console.WriteLine("執(zhí)行OnActionExecuted");
}
}
??異步版本的實(shí)現(xiàn)和媳,通過實(shí)現(xiàn)IAsyncActionFilter
接口來實(shí)現(xiàn)。
public class CustomActionFilterAsyncAttribute : Attribute, IAsyncActionFilter
{
public Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
return Task.Run(() => {
Console.WriteLine("執(zhí)行OnActionExecutionAsync");
});
}
}
1.3 ActionFilter的應(yīng)用
??可以用來記錄日志哈街,通過log4net將日志寫入文件留瞳,ILogger依賴注入。當(dāng)構(gòu)造函數(shù)有了參數(shù)后控制器方法上的[CustomActionFilter]
特性就不能這么寫了骚秦,應(yīng)該寫為[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("執(zhí)行CustomActionFilterAttribute.OnActionExecuting");
}
public void OnActionExecuted(ActionExecutedContext context)
{
_logger.LogInformation(JsonConvert.SerializeObject(context.Result));
_logger.LogInformation("執(zhí)行CustomActionFilterAttribute.OnActionExecuted");
}
}
1.4 ActionFilter的多種注冊(cè)
??(1)[CustomActionFilter]
---Fitler必須有無參數(shù)構(gòu)造函數(shù)她倘。
??(2)[TypeFilter(typeof(CustomActionFilterAttribute))]
,可以沒有無參數(shù)構(gòu)造函數(shù)作箍,可以支持依賴注入硬梁。
??(3)[ServiceFilter(typeof(CustomActionFilterAttribute))]
,可以沒有無參數(shù)構(gòu)造函數(shù)胞得,可以支持依賴注入荧止,但是必須要注冊(cè)服務(wù)。
1.5 FilterFactory擴(kuò)展定制
??為什么寫上TypeFilter
這樣的特性就可以支持依賴注入呢?它是由IOC容器來完成的跃巡。
??(1)現(xiàn)在我們自定義一個(gè)特性類CustomFilterFactory危号,繼承Attribute
,實(shí)現(xiàn)接口IFilterFactory
,并實(shí)現(xiàn)接口中的方法素邪。
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中注冊(cè)外莲。
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddTransient<CustomActionFilterAttribute>();
}
??(3)最后再將 [CustomFilterFactory(typeof(CustomActionFilterAttribute))]
標(biāo)記到控制器的Action方法上就行了,和[TypeFilter(typeof(CustomActionFilterAttribute))]
效果一樣兔朦。
1.6 Filter的生效范圍
- 標(biāo)記在Action上偷线,就只對(duì)當(dāng)前Action生效。
- 標(biāo)記在Controller上沽甥,就對(duì)Controller上中的所有Action生效声邦。
- 全局注冊(cè),對(duì)于當(dāng)前整個(gè)項(xiàng)目中的Action都生效摆舟,在ConfigureServices中增加以下代碼即可
services.AddMvc(option =>
{
option.Filters.Add<CustomActionFilterAttribute>(); //全局注冊(cè):
});
1.7 Filter的執(zhí)行順序
??如果定義三個(gè)actionFilter亥曹,分別注冊(cè)全局,控制器盏檐、Action;執(zhí)行順序如何呢歇式?
<1> 控制器實(shí)例化
<2> 全局注冊(cè)的Filter - OnActionExecuting
<3> 控制器注冊(cè)的Filter - OnActionExecuting
<4> Actioin注冊(cè)的Filter - OnActionExecuting
<5> 執(zhí)行Action內(nèi)部的邏輯
<6> Action注冊(cè)的Filter - OnActionExecuted
<7> 控制器注冊(cè)的Filter - OnActionExecuted
<8> 全局注冊(cè)的Filter - OnActionExecuted
??如果想要改變執(zhí)行順序驶悟,需要在注冊(cè)Filter的時(shí)候胡野,指定Order
的值,執(zhí)行順序會(huì)按照值從小到大執(zhí)行痕鳍。例如在方法上添加[CustomActionActionFilterAtrribute(Order = -1)]
硫豆,不添加則Order值為0。
1.8 Filter匿名
??如果全局注冊(cè)笼呆,F(xiàn)ilter生效于所有的Acion熊响,如果有部分Action我希望不生效怎么辦呢?這就需要匿名诗赌,可以避開Filter的檢查汗茄。
??下面自定義Filter匿名。
??(1)自定義一個(gè)特性類CustomAllowAnonymousAttributet
铭若,將[CustomAllowAnonymous]
特性添加到需要避開的方法上洪碳。
??(2)在需要匿名的Filter內(nèi)部,檢查是否需要匿名(檢查是否標(biāo)記的有匿名特性),如果有就直接避開叼屠。
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))
{
//斷路器嫂侍,只要是對(duì)Result賦值,就不繼續(xù)往后走了;
context.Result = CacheDictionary[key] as IActionResult;
}
Console.WriteLine("執(zhí)行OnResourceExecuting");
}
public void OnResourceExecuted(ResourceExecutedContext context)
{
string key = context.HttpContext.Request.Path;
CacheDictionary[key] = context.Result;
Console.WriteLine("執(zhí)行OnResourceExecuted");
}
}
[CustomResourceFilter]
public IActionResult IndexResource()
{
ViewBag.Date = DateTime.Now;
return View();
}
3. ExceptionFilter
??ExceptionFilter用來處理異常挑宠。
??(1)自定義一個(gè)CustomExceptionFilterAttribute
,實(shí)現(xiàn)IExceptionFilter
接口菲盾。
??(2)實(shí)現(xiàn)方法,先判斷痹栖,異常是否被處理過亿汞,如果沒有被處理過,就處理揪阿。如果是ajax請(qǐng)求疗我,就返回JosnResult,如果不是Ajax請(qǐng)求南捂,就返回錯(cuò)誤頁(yè)面吴裤。
??(3)全局注冊(cè)使用(和之前的ActionFilter全局注冊(cè)一樣)。
public class CustomExceptionFilterAttribute : Attribute, IExceptionFilter
{
private IModelMetadataProvider _modelMetadataProvider = null;
public CustomExceptionFilterAttribute(IModelMetadataProvider modelMetadataProvider)
{
_modelMetadataProvider = modelMetadataProvider;
}
public void OnException(ExceptionContext context)
{
if (!context.ExceptionHandled) //異常是否被處理過
{
//在這里處理 如果是Ajax請(qǐng)求===返回Json
if (this.IsAjaxRequest(context.HttpContext.Request))//header看看是不是XMLHttpRequest
{
context.Result = new JsonResult(new
{
Result = false,
Msg = context.Exception.Message
});//中斷式---請(qǐng)求到這里結(jié)束了溺健,不再繼續(xù)Action
}
else
{
//跳轉(zhuǎn)到異常頁(yè)面
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; //斷路器---只要對(duì)Result賦值--就不繼續(xù)往后了麦牺;
}
context.ExceptionHandled = true;
}
}
private bool IsAjaxRequest(HttpRequest request)
{
string header = request.Headers["X-Requested-With"];
return "XMLHttpRequest".Equals(header);
}
}
?ExceptionFilter能捕捉到哪些異常?
- 控制器實(shí)例化異常 [True]
- 異常發(fā)生在Try-cache中 [False]
- 在視圖中發(fā)生異常 [False]
- Service層發(fā)生異常 [True]
- 在Action中發(fā)生異常 [True]
- 請(qǐng)求錯(cuò)誤路徑異常 [True] 可以使用中間件來支持鞭缭,只要不是200的狀態(tài)剖膳,就都可以處理。
4. ResultFilter
??ResultFilter在return 返回時(shí)調(diào)用岭辣。
??它的應(yīng)用吱晒,比如雙語(yǔ)言系統(tǒng),其實(shí)就需要兩個(gè)視圖根據(jù)語(yǔ)言的不同沦童,來選擇不同的視圖來渲染仑濒。因?yàn)樵阡秩疽晥D之前,會(huì)進(jìn)入到OnResultExecuting
方法偷遗,就可以在這個(gè)方法中確定究竟使用哪一個(gè)視圖文件墩瞳。
??(1)自定義一個(gè)類,繼承Attribute,實(shí)現(xiàn)IResultFilter接口氏豌,實(shí)現(xiàn)方法
??(2)標(biāo)記在Action方法頭上
??(3)執(zhí)行順序:視圖執(zhí)行前喉酌,渲染視圖,視圖執(zhí)行后
public class CustomResultFilterAttribute : Attribute, IResultFilter
{
private IModelMetadataProvider _modelMetadataProvider = null;
public CustomResultFilterAttribute(IModelMetadataProvider modelMetadataProvider)
{
_modelMetadataProvider = modelMetadataProvider;
}
/// <summary>
/// 渲染視圖之前執(zhí)行
/// </summary>
/// <param name="context"></param>
public void OnResultExecuting(ResultExecutingContext context)
{
//在這里就可以有一個(gè)判斷泵喘,符合某個(gè)情況泪电,就使用哪一個(gè)視圖;
Console.WriteLine("渲染視圖之前執(zhí)行");
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>
/// 渲染視圖之后執(zhí)行
/// </summary>
/// <param name="context"></param>
public void OnResultExecuted(ResultExecutedContext context)
{
Console.WriteLine("渲染視圖之后執(zhí)行");
}
}
5. AuthorizationFilter
AuthorizationFilter用來做鑒權(quán)授權(quán)涣旨。通過中間件來支持歪架。
5.1 鑒權(quán)授權(quán)
??(1)在Startup中方法Configure的app.UseRouting()之后,在app.UseEndpoints()之前霹陡,增加鑒權(quán)授權(quán)和蚪。 鑒權(quán)app.UseAuthentication()
,授權(quán)app.UseAuthorization()
止状。
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory)
{
......
// 鑒權(quán) 監(jiān)測(cè)用戶是否登錄
app.UseAuthentication();
// 授權(quán) 監(jiān)測(cè)有沒有權(quán)限訪問后續(xù)頁(yè)面
app.UseAuthorization();
......
}
??(2)在Startup中方法ConfigureServices內(nèi)添加注冊(cè)。
public void ConfigureServices(IServiceCollection services)
{
......
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.LoginPath = new PathString("/Home/Login"); //如果授權(quán)失敗就跳轉(zhuǎn)到登錄
});
......
}
??(3)在指定Action上加上[Authorize]
特性做鑒權(quán)授權(quán)攒霹。也可以全局標(biāo)記怯疤。
[Authorize]
public IActionResult Index()
{
return View();
}
[AllowAnonymous] //避開權(quán)限檢查
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 鑒權(quán):鑒權(quán),檢測(cè)有沒有登錄催束,登錄的是誰集峦,賦值給User
//rolelist 是登錄成功后用戶的角色---是來自于數(shù)據(jù)庫(kù)的查詢;不同的用戶會(huì)查詢出不同的角色抠刺;
var rolelist = new List<string>() {
"Admin",
"Teacher",
"Student"
};
//ClaimTypes.Role就是做權(quán)限認(rèn)證的標(biāo)識(shí)塔淤;
var claims = new List<Claim>()//鑒別你是誰,相關(guān)信息
{
new Claim(ClaimTypes.Role,"Admin"),
new Claim(ClaimTypes.Name,name),
new Claim("password",password),//可以寫入任意數(shù)據(jù)
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),//過期時(shí)間:30分鐘
}).Wait();
#endregion
var user = HttpContext.User;
return base.Redirect("/Home/Index");
}
else
{
base.ViewBag.Msg = "賬號(hào)密碼錯(cuò)誤";
}
#endregion
//}
//else
//{
// base.ViewBag.Msg = "驗(yàn)證碼錯(cuò)誤";
//}
return View();
}
5.1 鑒權(quán)授權(quán)-角色授權(quán)
??即用戶角色不同速妖,在訪問頁(yè)面時(shí)需要做不同的攔截高蜂。
??(1)一個(gè)特性標(biāo)記到方法上,通過逗號(hào)分隔不同角色罕容,只要是有一個(gè)角色符合就能夠訪問备恤,角色之間是或者的關(guān)系。
[Authorize(Roles = "Admin,Teacher,Student")]
??(2)多個(gè)特性標(biāo)記锦秒,多個(gè)角色之前是且的關(guān)系露泊,必須要包含所有的角色,才能夠訪問旅择。
[Authorize(Roles = "Admin")]
[Authorize(Roles = "Teacher")]
[Authorize(Roles = "Student")]
5.2 鑒權(quán)授權(quán)-策略授權(quán)
??上面的角色授權(quán)是在代碼中寫死了角色惭笑,但我們更希望能夠用處理邏輯來完成校驗(yàn),就需要策略授權(quán)砌左。
??(1)添加CustomAuthorizationRequirement
類繼承自IAuthorizationRequirement
脖咐,用來傳遞策略名稱铺敌。
public class CustomAuthorizationRequirement : IAuthorizationRequirement
{
public string Name { get; set; }
public CustomAuthorizationRequirement(string policyname)
{
this.Name = policyname;
}
}
??(2)添加CustomAuthorizationHandler
類專用做檢驗(yàn)邏輯汇歹, 繼承自泛型抽象類AuthorizationHandler<CustomAuthorizationRequirement>
。
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
CustomAuthorizationRequirement requirement)
{
if (requirement.Name == "Policy01")
{
///策略1的邏輯
}
if (requirement.Name == "Policy02")
{
///策略1的邏輯
}
//其實(shí)這里可以去數(shù)據(jù)庫(kù)里面去做一些查詢偿凭,然后根據(jù)用戶的信息产弹,做計(jì)算;如果符合就context.Succeed(requirement);
//否則就Task.CompletedTask;
//context.User 鑒權(quán)成功(登錄成功以后)弯囊,用戶的信息痰哨;
var role = context.User.FindFirst(c => c.Value.Contains("admin"));
if (role != null)
{
context.Succeed(requirement); //驗(yàn)證通過了
}
return Task.CompletedTask; //驗(yàn)證不通過
}
??(3)添加注冊(cè),并支持多種策略。
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)標(biāo)記到使用的方法上匾嘱。
[Authorize(policy: "customPolicy01")]
public IActionResult IndexPolicy()
{
return View();
}