在webform中,验证的流程大体以下图:html
在AOP中:web
在Filter中:编程
AuthorizeAttribute权限验证 浏览器
登陆后有权限控制,有的页面是须要用户登陆才能访问的,须要在访问页面增长一个验证,也不能每一个action都一遍。缓存
一、写一个CustomAuthorAttribute,继承自AuthorizeAttribute,重写OnAuthorization方法,在里面把逻辑写成本身的。服务器
二、有方法注册和控制器注册。ide
三、有全局注册,所有控制器所有action都生效。函数
可是在这个里面,首先要验证登陆首页,首页没有邓丽,就跑到登陆页面了,可是登陆页面也要走特性里面的逻辑,又重定向到邓丽。。。循环了。。。。工具
这里有一个AlloAnonymous,这个标签就能够解决这个循环的问题,匿名支持,不须要登陆就能够,可是单单加特性是没有用的,其实须要验证时支持,甚至能够说本身自定义一个特性也是能够的,这个特性里面是空的,只是为了用来作标记。网站
特性的使用范围,但愿特性通用,在不一样的系统,不一样的地址登陆,==》在特性上面加个传参的构造函数。
public class CustomAllowAnonymousAttribute : Attribute { }
CustomAuthorAttribute类
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] public class CustomAuthorizeAttribute : AuthorizeAttribute { private Logger logger = new Logger(typeof(CustomAuthorizeAttribute)); private string _LoginUrl = null; public CustomAuthorizeAttribute(string loginUrl = "~/Home/Login") { this._LoginUrl = loginUrl; } //public CustomAuthorizeAttribute(ICompanyUserService service) //{ //} //不行 public override void OnAuthorization(AuthorizationContext filterContext) { var httpContext = filterContext.HttpContext;//能拿到httpcontext 就能够随心所欲 if (filterContext.ActionDescriptor.IsDefined(typeof(CustomAllowAnonymousAttribute), true)) { return; } else if (filterContext.ActionDescriptor.ControllerDescriptor.IsDefined(typeof(CustomAllowAnonymousAttribute), true)) { return; } else if (httpContext.Session["CurrentUser"] == null || !(httpContext.Session["CurrentUser"] is CurrentUser))//为空了, { //这里有用户,有地址 其实能够检查权限 if (httpContext.Request.IsAjaxRequest()) //httpContext.Request.Headers["xxx"].Equals("XMLHttpRequst") { filterContext.Result = new NewtonJsonResult( new AjaxResult() { Result = DoResult.OverTime, DebugMessage = "登录过时", RetValue = "" }); } else { httpContext.Session["CurrentUrl"] = httpContext.Request.Url.AbsoluteUri; filterContext.Result = new RedirectResult(this._LoginUrl); //短路器:指定了Result,那么请求就截止了,不会执行action } } else { CurrentUser user = (CurrentUser)httpContext.Session["CurrentUser"]; //this.logger.Info($"{user.Name}登录了系统"); return;//继续 } //base.OnAuthorization(filterContext); } }
Filter生效机制
为何加个标签,继承AuthorizeAttribute,重写OnAuthorization方法就能够了呢?控制器已经实例化,调用ExecuteCore方法,找到方法名字,ControllerActionInvokee.InvokeAction,找到所有的Filter特性,InvokeAuthorize--result不为空,直接InvokeActionResult,为空就正常执行Action。
有一个实例类型,有一个方法名称,但愿你反射执行
在找到方法后,执行方法前,能够检测下特性,来自全局的、来自控制器的、来自方法的。价差特性,特性是本身预约义的,按类执行,定个标识,为空就正常,不为空就跳转,正常就继续执行。
Filter原理和AOP面向切面编程
Filter是AOP思想的一种实现,其实就是ControllerActionInvoke这个类中,有个InvokeAction方法,控制器实例化以后,ActionInvoke先后,经过检测预约义Filter而且执行它,达到AOP的目的。
下面是InvokeAction的源码:
public virtual bool InvokeAction(ControllerContext controllerContext, string actionName) { if (controllerContext == null) { throw new ArgumentNullException("controllerContext"); } if (string.IsNullOrEmpty(actionName) && !controllerContext.RouteData.HasDirectRouteMatch()) { throw new ArgumentException(MvcResources.Common_NullOrEmpty, "actionName"); } ControllerDescriptor controllerDescriptor = this.GetControllerDescriptor(controllerContext); ActionDescriptor actionDescriptor = this.FindAction(controllerContext, controllerDescriptor, actionName); if (actionDescriptor != null) { FilterInfo filters = this.GetFilters(controllerContext, actionDescriptor); try { AuthenticationContext authenticationContext = this.InvokeAuthenticationFilters(controllerContext, filters.AuthenticationFilters, actionDescriptor); if (authenticationContext.Result != null) { AuthenticationChallengeContext authenticationChallengeContext = this.InvokeAuthenticationFiltersChallenge(controllerContext, filters.AuthenticationFilters, actionDescriptor, authenticationContext.Result); this.InvokeActionResult(controllerContext, authenticationChallengeContext.Result ?? authenticationContext.Result); } else { AuthorizationContext authorizationContext = this.InvokeAuthorizationFilters(controllerContext, filters.AuthorizationFilters, actionDescriptor); if (authorizationContext.Result != null) { AuthenticationChallengeContext authenticationChallengeContext2 = this.InvokeAuthenticationFiltersChallenge(controllerContext, filters.AuthenticationFilters, actionDescriptor, authorizationContext.Result); this.InvokeActionResult(controllerContext, authenticationChallengeContext2.Result ?? authorizationContext.Result); } else { if (controllerContext.Controller.ValidateRequest) { ControllerActionInvoker.ValidateRequest(controllerContext); } IDictionary<string, object> parameterValues = this.GetParameterValues(controllerContext, actionDescriptor); ActionExecutedContext actionExecutedContext = this.InvokeActionMethodWithFilters(controllerContext, filters.ActionFilters, actionDescriptor, parameterValues); AuthenticationChallengeContext authenticationChallengeContext3 = this.InvokeAuthenticationFiltersChallenge(controllerContext, filters.AuthenticationFilters, actionDescriptor, actionExecutedContext.Result); this.InvokeActionResultWithFilters(controllerContext, filters.ResultFilters, authenticationChallengeContext3.Result ?? actionExecutedContext.Result); } } } catch (ThreadAbortException) { throw; } catch (Exception exception) { ExceptionContext exceptionContext = this.InvokeExceptionFilters(controllerContext, filters.ExceptionFilters, exception); if (!exceptionContext.ExceptionHandled) { throw; } this.InvokeActionResult(controllerContext, exceptionContext.Result); } return true; } return false; }
全局异常处理HandleErrorAttribute
关于异常处理的建议:
一、避免UI层直接看到异常,每一个控制器里面try-catch一下?不是很麻烦吗?
二、这个时候,AOP就登场了,HandleErrorAttribute,本身写一个特性,继承之HandleErrorAttribute,重写OnException,在发生异常以后,会跳转到这个方法。
在这边,必定要
public class CustomHandleErrorAttribute : HandleErrorAttribute { private Logger logger = new Logger(typeof(CustomHandleErrorAttribute)); /// <summary> /// 会在异常发生后,跳转到这个方法 /// </summary> /// <param name="filterContext"></param> public override void OnException(ExceptionContext filterContext) { var httpContext = filterContext.HttpContext;//"随心所欲" if (!filterContext.ExceptionHandled)//没有被别的HandleErrorAttribute处理 { this.logger.Error($"在响应 {httpContext.Request.Url.AbsoluteUri} 时出现异常,信息:{filterContext.Exception.Message}");// if (httpContext.Request.IsAjaxRequest()) { filterContext.Result = new NewtonJsonResult( new AjaxResult() { Result = DoResult.Failed, DebugMessage = filterContext.Exception.Message, RetValue = "", PromptMsg = "发生错误,请联系管理员" }); } else { filterContext.Result = new ViewResult()//短路器 { ViewName = "~/Views/Shared/Error.cshtml", ViewData = new ViewDataDictionary<string>(filterContext.Exception.Message) }; } filterContext.ExceptionHandled = true;//已经被我处理了 } } }
这个是要从新跳转的地址:
必定要考虑到是否是Ajax请求的
多种异常状况,能不能进入自定义的异常呢?
一、Action异常,没有被Catch
二、Action异常,被Catch
三、Action调用Service异常
四、Action正常视图出现异常了
五、控制器构造出现异常
六、Action名称错误
七、任意地址错误
八、权限Filter异常
答案:
一、能够
二、不能够
三、能够,异常冒泡
四、能够,为何呢?由于ExecuteResult是包裹在try里面的
五、不能够的,Filter是在构造完成控制以后方法执行以前完成的
六、不能够的,由于请求都没进MVC流程
七、不能够的,由于请求都没进MVC
八、能够的,权限Filter也是在try里面的。
那这些没有被捕获的异常怎么办?还有一个方法
在Global中增长一个事件
public class MvcApplication : System.Web.HttpApplication { private Logger logger = new Logger(typeof(MvcApplication)); protected void Application_Start() { AreaRegistration.RegisterAllAreas();//注册区域 FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);//注册全局的Filter RouteConfig.RegisterRoutes(RouteTable.Routes);//注册路由 BundleConfig.RegisterBundles(BundleTable.Bundles);//合并压缩 ,打包工具 Combres ControllerBuilder.Current.SetControllerFactory(new ElevenControllerFactory()); this.logger.Info("网站启动了。。。"); } /// <summary> /// 全局式的异常处理,能够抓住漏网之鱼 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> protected void Application_Error(object sender, EventArgs e) { Exception excetion = Server.GetLastError(); this.logger.Error($"{base.Context.Request.Url.AbsoluteUri}出现异常"); Response.Write("System is Error...."); Server.ClearError(); //Response.Redirect //base.Context.RewritePath("/Home/Error?msg=") }
HandleErrorAttribute+Application_Error,粒度不同,能拿到的东西不同
IActionFilter扩展定制
IActionFilter
一、OnActionExecuting 方法执行前
二、OnActionExecuted方法执行后
三、OnResultExecuting结果执行前
四、OnResultExecuted结果执行后
先执行权限Filter,再执行ActionFilter。
执行的顺序:
Global OnActionExecuting
Controller OnActionExecuting
Action OnActionExecuting
Action真实执行
Action OnActionExecuted
Controller OnActionExecuted
Global OnActionExecuted
不一样位置注册的生效顺序:全局---》控制器-----》Action
好像一个俄罗斯套娃,或者说洋葱模型
在同一个位置注册的生效顺序,同一个位置按照前后顺序生效,还有一个Order的参数,不设置Order默认是1,设置以后按照从小到大执行
ActionFilter能干什么?
日志、参数检测、缓存、重写视图、压缩、防盗链、统计访问、不一样的客户端跳转不一样的页面、限流.....
浏览器请求时,会声明支持的格式,默认的IIS是没有压缩的,检测了支持的格式,在响应时将数据压缩(IIS服务器完成的),在响应头里面加上Content-Encoding,浏览器查看数据格式,按照浏览器格式解压(不管你是什么东西,均可以压缩解压的),压缩是IIS,解压是浏览器的。
public class CompressActionFilterAttribute : ActionFilterAttribute { public override void OnActionExecuting(ActionExecutingContext filterContext) { //foreach (var item in filterContext.ActionParameters) //{ // //参数检测 敏感词过滤 //} var request = filterContext.HttpContext.Request; var respose = filterContext.HttpContext.Response; string acceptEncoding = request.Headers["Accept-Encoding"];//检测支持格式 if (!string.IsNullOrWhiteSpace(acceptEncoding) && acceptEncoding.ToUpper().Contains("GZIP")) { respose.AddHeader("Content-Encoding", "gzip");//响应头指定类型 respose.Filter = new GZipStream(respose.Filter, CompressionMode.Compress);//压缩类型指定 } } } public class LimitActionFilterAttribute : ActionFilterAttribute { private int _Max = 0; public LimitActionFilterAttribute(int max = 1000) { this._Max = max; } public override void OnActionExecuting(ActionExecutingContext filterContext) { string key = $"{filterContext.RouteData.Values["Controller"]}_{filterContext.RouteData.Values["Action"]}"; //CacheManager.Add(key,) 存到缓存key 集合 时间 filterContext.Result = new JsonResult() { Data = new { Msg = "超出频率" } }; } }
Filter这么厉害,有没有什么局限性????
虽然很丰富,可是只能以Action为单位,Action内部调用别的类库,加操做就作不到!这种就得靠IOC+AOP扩展。
本篇只是介绍了.NET Framework MVC 中的过滤器Filter(权限特性、Action、Result、Exception),其实在.NET Core MVC 增长了ResourceFilter,加了这个特性,资源特性,Action/Result /Exception三个特性没有什么变化。后面记录到到.NET Core MVC时再详细介绍。