c#中的AOP面向切面编程
AOP(Aspct Oriented Programming)在不修改源代码的基础上,通过特性的方式添加一些业务逻辑。就是一些特性类
在asp.net core中通过过滤器【Filter】来支持AOP的
一、资源缓存:IResourceFilter和IAsyncResourceFilter(异步)
天生为缓存而生,应用场景就是用于缓存。(需要实现两个方法)以特性的方式调用在控制器方法上
代码编译顺序:OnResourceExecuting------》控制器构造方法-----》控制器普通方法-----OnResourceExecuted
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; namespace Advanced.NET6.Project.Utility.Filters { public class AopResourceFiltersAttribute : Attribute, IResourceFilter //1.特性,2.缓存接口(需要实现两个方法) { private static Dictionary<string, object> dt = new Dictionary<string, object>();//定义字典来记录缓存 public void OnResourceExecuting(ResourceExecutingContext context)//参数控制器是的实例 { Console.WriteLine("在控制器的构造方法和普通方法,前执行"); string key = context.HttpContext.Request.Path;//获取请求路径 if (dt.ContainsKey(key)) //如果存在个路径,表示有记录缓存 { context.Result = (IActionResult)dt[key];//保存不变,返回路径,IActionResult表示一个控制器的试图类型 } } public void OnResourceExecuted(ResourceExecutedContext context) { Console.WriteLine("在控制器的构造方法和普通方法执行完成,后执行"); string key = context.HttpContext.Request.Path;//获取请求路径 dt[key] = context.Result;//记录缓存,把控制器方法保存起来 } } }
控制器调用,以特性的方式定义在方法上
using Advanced.NET6.Project.Models; using Advanced.NET6.Project.Utility.Filters; using Microsoft.AspNetCore.Mvc; using System.Diagnostics; namespace Advanced.NET6.Project.Controllers { public class HomeController : Controller { public HomeController() { Console.WriteLine("控制器构造方法,已执行。。。"); } [AopResourceFilters]//调用缓存特性 public IActionResult Index() { Console.WriteLine("控制器index方法,已执行。。。"); ViewBag.datetime = DateTime.Now.ToString("yyyy-MM-dd ss");//测试数据,关注秒的变化 return View(); } public IActionResult Privacy() { return View(); } [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] public IActionResult Error() { return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier }); } } }
测试数据
<h2>后台index方法输出的时间:@ViewBag.datetime</h2> <h2>前台定义的时间无缓存变化:@DateTime.Now.ToString("yyyy-MM-dd ss");</h2>
扩展:IAsyncResourceFilter(异步版本)两个方法变一个方法,还是多线程处理的
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; namespace Advanced.NET6.Project.Utility.Filters { public class AopResourceFiltersAttribute : Attribute, IAsyncResourceFilter //1.特性,2.异步缓存(只有一个方法) { private static Dictionary<string, object> dt = new Dictionary<string, object>();//定义字典来记录缓存 public async Task OnResourceExecutionAsync(ResourceExecutingContext context, ResourceExecutionDelegate next) { Console.WriteLine("在控制器的构造方法和普通方法,前执行"); string key = context.HttpContext.Request.Path;//获取请求路径 if (dt.ContainsKey(key)) //如果存在个路径,表示有记录缓存 { context.Result = (IActionResult)dt[key];//保存不变,返回路径,IActionResult表示一个控制器的试图类型 } else { Console.WriteLine("在控制器的构造方法和普通方法执行完成,后执行"); var resource = await next.Invoke();//这句话就是去执行控制器构造方法和普通方法 dt[key] = resource.Result;//记录缓存,把控制器方法保存起来 } } } }
二、方法的前后记录:IActionFilter和IAsyncActionFilter(异步)
就是方法前后的记录,就是执行在一个普通方法之前之后的两个方法,适用于记录日志
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; namespace Advanced.NET6.Project.Utility.Filters { public class AopActionFilterAttribute : Attribute, IActionFilter //1.特性,2.日志记录log { private readonly ILogger<AopActionFilterAttribute> logger; public AopActionFilterAttribute(ILogger<AopActionFilterAttribute> logger) { this.logger = logger; } public void OnActionExecuting(ActionExecutingContext context)//参数是控制器的实例 { Console.WriteLine("执行于方法之前"); var controllerName =context.HttpContext.GetRouteValue("Controller");//获取控制器名 var actionName = context.HttpContext.GetRouteValue("action");//获取方法名 var para = context.HttpContext.Request.QueryString.Value;//获取参数 logger.LogInformation($"马上执行{controllerName}控制器下的{actionName}方法,参数为:{para}"); } public void OnActionExecuted(ActionExecutedContext context) { Console.WriteLine($"执行于方法之后"); var result = Newtonsoft.Json.JsonConvert.SerializeObject(context.Result); logger.LogInformation($"方法的返回结果为:{result}"); } } }
调用
using Advanced.NET6.Project.Models; using Advanced.NET6.Project.Utility.Filters; using Microsoft.AspNetCore.Mvc; using System.Diagnostics; namespace Advanced.NET6.Project.Controllers { public class HomeController : Controller { public HomeController() { Console.WriteLine("控制器构造方法,已执行。。。"); } //[AopActionFilter]//不能这样调用ioc容器的问题后面在讲 [TypeFilter(typeof(AopActionFilterAttribute))]//需要把类名写全,因为特性的构造方法有参数,所以需要这样获取类型 //[ServiceFilter(typeof(AopActionFilterAttribute))]//他和TypeFilter一样都是做了特性实例的一个ioc容器的封装,只是ServiceFilter需要注册,上面的不需要注册。 public IActionResult Index(int id) { Console.WriteLine("控制器index方法,已执行。。。"); ViewBag.datetime = DateTime.Now.ToString("yyyy-MM-dd ss");//测试数据,关注秒的变化 ViewData["user"] = Newtonsoft.Json.JsonConvert.SerializeObject(new { id = id, name = "张三", age = 18 }); object str = "测试返回结果的日志信息"; return View(str); } public IActionResult Privacy() { return View(); } [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] public IActionResult Error() { return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier }); } } }
基于TypeFilter和ServiceFilter自定义封装的特性,[CustomFilterFactory(typeof(AopActionFilterAttribute))]来调用,不过AopActionFilterAttribute也需要注册
public class CustomFilterFactoryAttribute : Attribute, IFilterFactory { private readonly Type type; public CustomFilterFactoryAttribute(Type type) { this.type = type; } public bool IsReusable => true; public IFilterMetadata CreateInstance(IServiceProvider serviceProvider) { return (IFilterMetadata)serviceProvider.GetService(type); } }
演示效果
扩展:IAsyncActionFilter 两个方法变一个方法,异步版本,调用时改为 [TypeFilter(typeof(AopAsyncActionFilterAttribute))]
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; namespace Advanced.NET6.Project.Utility.Filters { public class AopAsyncActionFilterAttribute : Attribute, IAsyncActionFilter //异步编程日志记录log { private readonly ILogger<AopAsyncActionFilterAttribute> logger; public AopAsyncActionFilterAttribute(ILogger<AopAsyncActionFilterAttribute> logger) { this.logger = logger; } public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) { Console.WriteLine("执行于方法之前"); var controllerName = context.HttpContext.GetRouteValue("Controller");//获取控制器名 var actionName = context.HttpContext.GetRouteValue("action");//获取方法名 var para = context.HttpContext.Request.QueryString.Value;//获取参数 logger.LogInformation($"马上执行{controllerName}控制器下的{actionName}方法,参数为:{para}"); var Controller = await next.Invoke();//表示正在执行控制器里的方法 Console.WriteLine($"执行于方法之后"); var result = Newtonsoft.Json.JsonConvert.SerializeObject(Controller.Result); logger.LogInformation($"方法的返回结果为:{result}"); } } }
三、结果生成前后扩展:IResultFilter和IAsyncResultFilter(异步)
就是在方法返回结果之前和之后的两个扩展方法,就是方法渲染试图前后插入的两个方法,用于渲染结果的统一处理,或者是json格式的统一处理
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; namespace Advanced.NET6.Project.Utility.Filters { public class AopResultFilterAttribute : Attribute, IResultFilter { public void OnResultExecuting(ResultExecutingContext context) { Console.WriteLine("执行于返回结果之前"); if (context.Result is JsonResult) { JsonResult jr = (JsonResult)context.Result; context.Result = new JsonResult(new AjaxResult() { Success = true, Message = "OK", Data = jr.Value }); } } public void OnResultExecuted(ResultExecutedContext context) { Console.WriteLine($"执行于返回结果之后"); } } public class AjaxResult//实体类 { public bool Success { get; set; } public string? Message { get; set; } public object? Data { get; set; } } }
调用
using Advanced.NET6.Project.Models; using Advanced.NET6.Project.Utility.Filters; using Microsoft.AspNetCore.Mvc; using System.Diagnostics; namespace Advanced.NET6.Project.Controllers { public class HomeController : Controller { public HomeController() { Console.WriteLine("控制器构造方法,已执行。。。"); } [AopResultFilter] public IActionResult Index() { Console.WriteLine("控制器index方法,已执行。。。"); return Json(new { id=1,name="张三",age=18 }); } public IActionResult Privacy() { return View(); } [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] public IActionResult Error() { return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier }); } } }
扩展:异步版本 IAsyncResultFilter
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using System; namespace Advanced.NET6.Project.Utility.Filters { public class AopResultFilterAttribute : Attribute, IAsyncResultFilter { public async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next) { Console.WriteLine("执行于返回结果之前"); if (context.Result is JsonResult) { JsonResult jr = (JsonResult)context.Result; context.Result = new JsonResult(new AjaxResult() { Success = true, Message = "OK", Data = jr.Value }); } var Controller = await next.Invoke();//表示正在执行控制器里的方法 Console.WriteLine($"执行于返回结果之后"); } } public class AjaxResult//实体类 { public bool Success { get; set; } public string? Message { get; set; } public object? Data { get; set; } } }
IAlwaysRun是响应结果的补充:IAlwaysRunResultFilter和IAsyncAlwaysRunResultFilter
using Microsoft.AspNetCore.Mvc.Filters; namespace Advanced.NET6.Project.Utility.Filters { public class IAsyncAlwaysRunAttribute: Attribute, IAsyncAlwaysRunResultFilter { public async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next) { Console.WriteLine("响应结果前的代码补充"); var Controller = await next.Invoke();//表示正在执行控制器里的方法 Console.WriteLine("响应结果后的代码补充"); } } }
ActionFilterAttribute是上面3个Filter的结合体
public class AopAllActionFilterAttribute : ActionFilterAttribute //抽象类:都是控制器前后执行的方法 { //实现需要的抽象类即可,注意:当我们执行异步版本时,同步版本filter失效。C#推荐我们所以异步版本。 }
特性注册在方法上,一个方法生效;注册在控制器上,控制器所有方法生效;全局注册所有控制器方法都生效【Program.cs】不想生效的控制器或方法标记特性:[AllowAnonymous]
builder.Services.AddControllersWithViews(options => { options.Filters.Add<AopAllActionFilterAttribute>();//通过委托来全局注册 });
匿名支持:[AllowAnonymous]是系统给我们的取消注册,但有的不能生效(IResourceFilter和IActionFilter,IResultFilter),只能自己扩展
public class AopAllowAnonymousAttribute : Attribute { //使用定义一个匿名特性【AopAllowAnonymous】,就是自定义特性,在方法执行前用if判断拦截。 }
在每个Filter的方法中添加判断,取消注册就用[AopAllowAnonymous]来代替[AllowAnonymous]
if (context.ActionDescriptor.EndpointMetadata.Any(a => a.GetType().Equals(typeof(AopAllowAnonymousAttribute)))) { return;//查询当前控制器或方法是否存在特性[AopAllowAnonymous],存在就退出。 }
四、异常处理:IExceptionFilter和IAsyncExceptionFilter
异常处理的特性(ExceptionFilter + 中间件注册 = 所有异常处理),避免我们写大量的try 和 catch
public class AopExceptionFilterAttribute : Attribute, IExceptionFilter, IAsyncExceptionFilter//异常处理(同步和异步) { public void OnException(ExceptionContext context)//同步:有异常就会执行 { throw new NotImplementedException(); } public Task OnExceptionAsync(ExceptionContext context)//异步,如果有异步方法,不会执行同步 { throw new NotImplementedException(); } }
中间件【Program.cs】
///如果Http请求中的Response中的状态不是200,就会进入Home/Error中; app.UseStatusCodePagesWithReExecute("/Home/Error/{0}");//只要不是200 都能进来 //下面这个是自己拼装一个Reponse 输出 app.UseExceptionHandler(errorApp => { errorApp.Run(async context => { context.Response.StatusCode = 200; context.Response.ContentType = "text/html"; await context.Response.WriteAsync("<html lang=\"en\"><body>\r\n"); await context.Response.WriteAsync("ERROR!<br><br>\r\n"); var exceptionHandlerPathFeature = context.Features.Get<IExceptionHandlerPathFeature>(); Console.WriteLine("&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&"); Console.WriteLine($"{exceptionHandlerPathFeature?.Error.Message}"); Console.WriteLine("&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&"); if (exceptionHandlerPathFeature?.Error is FileNotFoundException) { await context.Response.WriteAsync("File error thrown!<br><br>\r\n"); } await context.Response.WriteAsync("<a href=\"/\">Home</a><br>\r\n"); await context.Response.WriteAsync("</body></html>\r\n"); await context.Response.WriteAsync(new string(' ', 512)); // IE padding }); });
异常特性封装
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.ViewFeatures; namespace Advanced.NET6.Project.Utility.Filters { public class AopExceptionFilterAttribute : Attribute, IExceptionFilter//异常处理 { private readonly IModelMetadataProvider _metadataProvider; public AopExceptionFilterAttribute(IModelMetadataProvider metadataProvider) { this._metadataProvider = metadataProvider;//将驱动程序注入进来,错误信息带人错误页需要 } public void OnException(ExceptionContext context)//同步:有异常就会执行 { if (context.ExceptionHandled == false) { if (IsAjaxRequest(context.HttpContext.Request))//判断是否ajax请求json { context.Result = new JsonResult(new { succeess = false, message = context.Exception.Message }); } else { ViewResult result= new ViewResult { ViewName="~/views/shared/error.cshtml" };//错误页面 result.ViewData = new ViewDataDictionary(_metadataProvider, 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); } } }
五、权限验证:AuthorizeAttribute
http协议是无状态请求,每个人都可以访问网页,但有点页面或方法不想别人访问就需要通过cookie或session来记录一种状态,这种状态叫访问权限
访问权限有两个步骤:1,鉴权-----》2,授权;需要在【Program.cs】启用
app.UseAuthentication();//鉴权:在请求中获取用户信息 app.UseAuthorization();//授权:拿用户信息去判断是否有权访问
配置鉴权
builder.Services.AddAuthentication(option => { //配置鉴权,选择用哪种方式来鉴权 option.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;//默认方式:cookie option.DefaultChallengeScheme= CookieAuthenticationDefaults.AuthenticationScheme; option.DefaultSignInScheme= CookieAuthenticationDefaults.AuthenticationScheme; option.DefaultSignOutScheme= CookieAuthenticationDefaults.AuthenticationScheme; option.DefaultForbidScheme= CookieAuthenticationDefaults.AuthenticationScheme; }).AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, option => { option.LoginPath = "/Home/Login";//如果没有找到用户---表示鉴权失败,跳转到登录页面,先登录角色 option.AccessDeniedPath = "/Home/Privacy";//第二步有用户信息,角色授权,如果无权限访问跳转到没有权限提示页面。 });
给方法设置权限验证
//[Authorize]//默认授权方式(参数1,:授权方式;参数2:角色授权) //[Authorize(AuthenticationSchemes = CookieAuthenticationDefaults.AuthenticationScheme)]//指定授权方式,所有角色可访问 //[Authorize(AuthenticationSchemes = CookieAuthenticationDefaults.AuthenticationScheme, Roles = "admin")] //必须是管理员admin, //[Authorize(AuthenticationSchemes = CookieAuthenticationDefaults.AuthenticationScheme, Roles = "admin1")] //一个角色表示并且关系,表示必须包含admin和admin1都存在才能访问。 [Authorize(AuthenticationSchemes = CookieAuthenticationDefaults.AuthenticationScheme, Roles = "Admin,Teache")] //多个角色表示或的关系,只需要存在一个都可以访问 public IActionResult Index() { var user = HttpContext.User;//获取页面信息,用户信息,测试 return View(); }
创建一个实体类,用于登录测试
public class CurrentUser { public int Id { get; set; } [Display(Name = "用户名")] public string? Name { get; set; } public string? Account { get; set; } [Display(Name = "密码")] public string? Password { get; set; } [DataType(DataType.EmailAddress)] [Display(Name = "E-mail")] public string? Email { get; set; } public DateTime LoginTime { get; set; } }
创建用于登录页后台
[HttpGet] public IActionResult Login() { return View(); } [HttpPost]//提交登录信息 public async Task<IActionResult> Login(string name, string password) { if ("admin".Equals(name) && "123456".Equals(password)) { var claims = new List<Claim>()//用户信息集合 { new Claim("Userid","1"), new Claim(ClaimTypes.Role,"Admin"), new Claim(ClaimTypes.Role,"User"), new Claim(ClaimTypes.Name,$"{name}--来自于Cookies"), new Claim(ClaimTypes.Email,$"net@163.com"), new Claim("password",password),//可以写入任意数据 new Claim("Account","Administrator"), new Claim("role","admin"), new Claim("QQ","110120119") }; ClaimsPrincipal userPrincipal = new ClaimsPrincipal(new ClaimsIdentity(claims, "Customer"));//记录用户信息 HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, userPrincipal, new AuthenticationProperties {//包用户信息写入cookie,参数1是鉴权方式 ExpiresUtc = DateTime.UtcNow.AddMinutes(30),//cookie过期时间:30分钟 }).Wait(); var user = HttpContext.User; return base.Redirect("/Home/Index"); } else { base.ViewBag.Msg = "用户或密码错误"; } return await Task.FromResult<IActionResult>(View()); }
登录页前台
@using Advanced.NET6.Project.Models; @model CurrentUser @{ ViewBag.Title = "登录"; } <h2>登录</h2> <div class="row"> <div class="col-md-8"> <section id="loginForm"> @using (Html.BeginForm("Login", "Home", new { sid = "123456", Account = "admin" }, FormMethod.Post, true, new { @class = "form-horizontal", role = "form" })) { @Html.AntiForgeryToken() <hr /> @Html.ValidationSummary(true) <div class="mb-3 row"> @Html.LabelFor(m => m.Name, new { @class = "col-sm-1 col-form-label" }) <div class="col-md-6"> @Html.TextBoxFor(m => m.Name, new { @class = "form-control", @placeholder="请输入您的用户名" }) </div> </div> <div class="mb-3 row"> @Html.LabelFor(m => m.Password, new { @class = "col-md-1 control-label" }) <div class="col-md-6"> @Html.PasswordFor(m => m.Password, new { @class = "form-control",@placeholder="请输入密码" }) </div> </div> <div class="mb-3 row"> <div class="col-md-offset-2 col-md-6"> <button type="submit" class="btn btn-primary mb-3">登录</button> @base.ViewBag.Msg </div> </div> } </section> </div> </div>
策略授权:如果不配置,系统默认可以角色授权Roles = "角色名"
builder.Services.AddAuthorization(option => { //配置授权 option.AddPolicy("rolePolicy", policyBuilder => { //添加授权策略(“取策略名”,委托授权方式) policyBuilder.RequireRole("Admin"); //授权角色 policyBuilder.RequireClaim("Account");//必须包含某一个Claim键值对,键和值都可以做授权判断 policyBuilder.RequireAssertion(context =>//业务逻辑授权 { bool bResult = context.User.HasClaim(c => c.Type == ClaimTypes.Role)//用户信息,键必须包含这个Role && context.User.Claims.First(c => c.Type.Equals(ClaimTypes.Role)).Value == "Admin"//值必须有的用户 && context.User.Claims.Any(c => c.Type == ClaimTypes.Name);//Claim的键必须包含Name return bResult; }); policyBuilder.AddRequirements(new QQEmailRequirement());//指定对象做验证返回布尔值 }); });
调用时需要改为字段策略
[Authorize(AuthenticationSchemes = CookieAuthenticationDefaults.AuthenticationScheme, Policy = "rolePolicy")]//指定策略授权
AddRequirements是指定对象做验证,他还可以传IAuthorizationRequirement接口的一个数组,做第三方数据验证会用到。
public class QQEmailRequirement: IAuthorizationRequirement { //我们可以创建一个帮助类实现这个接口,他其实就是个空接口,里面什么都不写,通过类或者接口直接映射到帮助类,创建QQHandler }
创建一个第三方数据的模拟类库
namespace UserServiceClass { public interface IUserService { public bool Validata(string userId, string qq); } public class UserService : IUserService { public bool Validata(string userId, string qq) { //在这里去链接数据库去校验这个QQ是否正确 return true; } } }
最后创建一个帮助类做验证的业务逻辑
using Microsoft.AspNetCore.Authorization; using UserServiceClass; namespace Advanced.NET6.Project.Utility { public class QQHandler : AuthorizationHandler<QQEmailRequirement>//QQEmailRequirement这个对象会找到泛型的一个实现类QQHandler {//帮助类,继承抽象的泛型父类,必须传递一个能接收IAuthorizationRequirement接口的类型 private IUserService _UserService; public QQHandler(IUserService userService)//IOC容器依赖注入把IUserService注入进来 { this._UserService = userService; } //实现抽象类,这里可以做验证 protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, QQEmailRequirement requirement) { if (context.User.Claims.Count() == 0)//判断是否有数据 { return Task.CompletedTask;//异步任务处理的,返回已经完成的对象 } //查询 string userId = context.User.Claims.First(c => c.Type == "Userid").Value; string qq = context.User.Claims.First(c => c.Type == "QQ").Value; //验证 if (_UserService.Validata(userId, qq)) { context.Succeed(requirement); //验证通过了 } return Task.CompletedTask;//返回已经完成的对象 } } }
另外需要将以上用到的类和接口依赖注入到IOC容器才能使用【Program.cs】
builder.Services.AddTransient<IUserService, UserService>();//模拟的第三方数据 builder.Services.AddTransient<IAuthorizationHandler, QQHandler>();//依赖注入到IOC容器来,构造方法才能找到这些类型
Filter生命周期
a.权限验证:进入到Authorization
b.开始缓存:ResourceFilter 中的 -OnResourceExecuting
c.开始创建控制器实例
d.日志记录方法前:ActionFilter 中的 - OnActionExecuting
e.执行控制器Action方法
f.日志记录方法后:ActionFilter 中的 - OnActionExecuted
g.结果生成前的扩展:ResultFilter 中的 - OnResultExecuting
h.响应结果前的补充:AlwaysRunResultFilter 中的 - OnResultExecuting
i.渲染视图
j.响应结果后补充:AlwaysRunResultFilter 中的 - OnResultExecuted
k.结果生成后的扩展:ResultFilter 中的 - OnResultExecuted
l.结束缓存:ResourceFilter中的 -OnResourceExecuted
默认执行顺序:全局-----》控制器------》方法
可以通过Order属性来控制某个特性的执行顺序,如Order=10,值为整数,值越小就优先执行,值越大就后执行
[CustomActionActionFilterAttribute(Order =-99)]//使用Order属性改变顺序,需要把特性定义为继承父类的关系