• MVC异常过滤器


     MVC过滤器

    • 一般的过滤器执行顺序
    1. IAuthorizationFilter->OnAuthorization(授权)
    2. IActionFilter          ->OnActionExecuting(行为)
    3. Action
    4. IActionFilter          ->OnActionExecuted(行为) 
    5. IResultFilter          ->OnResultExecuting(结果)
    6. View
    7. IResultFilter          ->OnResultExecuted(结果)
    8. *IExceptionFilter    ->OnException(异常),此方法并不在以上的顺序执行中,有异常发生时即会执行,有点类似于中断
    • 当同时在Controller和Action中都设置了过滤器后,执行顺序一般是由外到里,即“全局”->“控制器”->“行为”
    1. Controller->IAuthorizationFilter->OnAuthorization
    2. Action     ->IAuthorizationFilter->OnAuthorization
    3. Controller->IActionFilter          ->OnActionExecuting
    4. Action     ->IActionFilter          ->OnActionExecuting
    5. Action
    6. Action     ->IActionFilter          ->OnActionExecuted
    7. Controller->IActionFilter          ->OnActionExecuted
    8. Controller->IResultFilter          ->OnResultExecuting
    9. Action     ->IResultFilter          ->OnActionExecuting
    10. Action     ->IResultFilter          ->OnActionExecuted
    11. Controller->IResultFilter          ->OnActionExecuted
    • 因为异常是从里往外抛,因次异常的处理顺序则刚好相反,一般是由里到外,即“行为”->“控制器”->“全局”
    1. Action     ->IExceptionFilter->OnException
    2. Controller->IExceptionFilter->OnException 

     
    系统自带的异常处理
    我们习惯使用的过滤器,要么是为Action加上Attribute,要么就是为Controller加上Attribute。上面所说的全局过滤器是怎么回事呢?先看看Gloabal里的代码:
    protected void Application_Start(){ 
    //注册Area AreaRegistration.RegisterAllAreas(); 
    //注册过滤器 RegisterGlobalFilters(GlobalFilters.Filters); 
    //注册路由 RegisterRoutes(RouteTable.Routes);
    }
     public static void RegisterGlobalFilters(GlobalFilterCollection filters)

        filters.Add(new HandleErrorAttribute()); 


    由上可知,在应用程序启动的时候就已经注册了全局过滤器,HandleErrorAttribute就是系统自带的异常过滤器。在这注册的全局过滤器,可以不用到每个Controller或者是每个Action去声明,直接作用于全局了,即可以捕捉整个站点的所有异常。看看它的源码是怎么处理异常的:

    public virtual void OnException(ExceptionContext filterContext)
            {
                if (filterContext == null)
                {
                    throw new ArgumentNullException("filterContext");
                }
                if (!filterContext.IsChildAction && (!filterContext.ExceptionHandled && filterContext.HttpContext.IsCustomErrorEnabled))
                {
                    Exception innerException = filterContext.Exception;
                    if ((new HttpException(null, innerException).GetHttpCode() == 500) && this.ExceptionType.IsInstanceOfType(innerException))
                    {
                        string controllerName = (string) filterContext.RouteData.Values["controller"];
                        string actionName = (string) filterContext.RouteData.Values["action"];
                        HandleErrorInfo model = new HandleErrorInfo(filterContext.Exception, controllerName, actionName);
                        ViewResult result = new ViewResult {
                            ViewName = this.View,
                            MasterName = this.Master,
                            ViewData = new ViewDataDictionary<HandleErrorInfo>(model),
                            TempData = filterContext.Controller.TempData
                        };
                        filterContext.Result = result;
                        filterContext.ExceptionHandled = true;
                        filterContext.HttpContext.Response.Clear();
                        filterContext.HttpContext.Response.StatusCode = 500;
                        filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;
                    }
                }
            }


    HandleErrorAttribute的异常处理逻辑里,生成了一个HandleErrorInfo类的Model,并设置返回的结果为一个新生成的ViewResult。这个视图默认的ViewName是Error,对应于Share文件夹里的Error视图。而自带的Error视图没有用到HandleErrorInfo的Model,因此公开的信息也不是很多,可以根据具体的需求改造一下。例如:
       

    @model HandleErrorInfo
    <br />
    <div class="container"> 
    <div class="alert alert-error"> 
    <h4> Exception:</h4> <br /> 
    <p> 
    There was a <b>@Model.Exception.GetType().Name</b> while rendering <b>@Model.ControllerName</b>'s<b>@Model.ActionName</b> action.</p> <p> @Model.Exception.Message 
    </p> 
    </div>
      <div class="alert"> <h4> Stack trace:</h4> <br /> <pre>@Model.Exception.StackTrace</pre> </div>
     </div>



    这个过滤器要能起效,还需要在配置文件中配置一下:<customErrors mode="On" />

    自定义的异常统一处理

    在实现异常的统一处理之前,先来明确一下需求:

    1. 站点所有页面在异常发生后,均需要记录异常日志,并转向错误提示页面(异常内容的详略程度由具体需求决定)
    2. 所有返回JSON数据的异步请求,不但需要记录异常日志,而且需要向客户端返回JSON格式的错误信息提示,而不是转向错误提示页面(异步请求也不可能转向错误提示页面)
    3. 采用AOP思想,将异常处理解耦
    4. 尽量精简声明Attribute的重复代码
      [AttributeUsage(AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
        public class JsonExceptionAttribute : HandleErrorAttribute
        {
            public override void OnException(ExceptionContext filterContext)
            {
                if (!filterContext.ExceptionHandled)
                {                                
                    //返回异常JSON
                    filterContext.Result = new JsonResult
                    {
                        Data = new { Success = false, Message = filterContext.Exception.Message }
                    };
                }
            }
        }
    说明:需要注意的是,不需要调用base.OnException,否则会跳过LogExceptionAttribute先执行HandleErrorAttribute的处理逻辑,从而返回结果不再是JsonResult,而是ViewResult,客户端也就无法处理非JSON的结果了。
    这里也不需要设置filterContext.ExceptionHandled = true,否则在LogExceptionAttribute处理时,因为 !filterContext.ExceptionHandled 的判断条件,LogExceptionAttribute的逻辑不会执行,也就不会记录异常日志了。 
       [AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = false)]
        public class LogExceptionAttribute : HandleErrorAttribute
        {
            public override void OnException(ExceptionContext filterContext)
            {
                if (!filterContext.ExceptionHandled)
                {
                    string controllerName = (string)filterContext.RouteData.Values["controller"];
                    string actionName = (string)filterContext.RouteData.Values["action"];
                    string msgTemplate = "在执行 controller[{0}] 的 action[{1}] 时产生异常";
                    LogManager.GetLogger("LogExceptionAttribute").Error(string.Format(msgTemplate, controllerName, actionName), filterContext.Exception);
                }
     
                if (filterContext.Result is JsonResult)
                {
                    //当结果为json时,设置异常已处理
                    filterContext.ExceptionHandled = true;
                }
                else
                {
                    //否则调用原始设置
                    base.OnException(filterContext);
                }
            }
        } 

    修改全局过滤器:

     public static void RegisterGlobalFilters(GlobalFilterCollection filters)
     {
          filters.Add(new HandleError2LogAttribute());//全局的日志异常过滤器
          //filters.Add(new HandleErrorAttribute());
    }

    调用示例:
    [HttpPost]
    [JsonException]
    public JsonResult Add(string ip, int port)
    {
            ...  //处理逻辑
            return Json(new { Success = true, Message = "添加成功" });
    } 
  • 相关阅读:
    分解质因数算法
    js 的 Math 对象
    字符串操作
    简化求质数算法
    数值类型小数点后是否可以接零问题
    新博第一篇,思考的重要性与求质数算法
    一、制作屏幕录像
    四、同步线程
    常见问题
    jni数据处理
  • 原文地址:https://www.cnblogs.com/shi2310/p/7405915.html
Copyright © 2020-2023  润新知