zoukankan      html  css  js  c++  java
  • MVC过滤器使用案例:统一处理异常顺道精简代码

    MVC过滤器使用案例:统一处理异常顺道精简代码 
    1.MVC过滤器 
    从方法2使用Attribute的思路很容易就能想到MVC的过滤器,利用过滤器的拦截功能能很好的按照AOP思想实现异常处理,并解耦于逻辑层的模块。关于MVC过滤器的介绍,网上的文章很多,推荐《MVC过滤器详解》。这里要着重说一下过滤器的执行顺序。

    一般的过滤器执行顺序 
    IAuthorizationFilter->OnAuthorization(授权) 
    IActionFilter ->OnActionExecuting(行为) 
    Action 
    IActionFilter ->OnActionExecuted(行为) 
    IResultFilter ->OnResultExecuting(结果) 
    View 
    IResultFilter ->OnResultExecuted(结果) 
    *IExceptionFilter ->OnException(异常),此方法并不在以上的顺序执行中,有异常发生时即会执行,有点类似于中断 
    当同时在Controller和Action中都设置了过滤器后,执行顺序一般是由外到里,即“全局”->“控制器”->“行为” 
    Controller->IAuthorizationFilter->OnAuthorization 
    Action ->IAuthorizationFilter->OnAuthorization 
    Controller->IActionFilter ->OnActionExecuting 
    Action ->IActionFilter ->OnActionExecuting 
    Action 
    Action ->IActionFilter ->OnActionExecuted 
    Controller->IActionFilter ->OnActionExecuted 
    Controller->IResultFilter ->OnResultExecuting 
    Action ->IResultFilter ->OnActionExecuting 
    Action ->IResultFilter ->OnActionExecuted 
    Controller->IResultFilter ->OnActionExecuted 
    因为异常是从里往外抛,因次异常的处理顺序则刚好相反,一般是由里到外,即“行为”->“控制器”->“全局” 
    Action ->IExceptionFilter->OnException 
    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,因此公开的信息也不是很多,可以根据具体的需求改造一下。例如:

    View Code 
    这个过滤器要能起效,还需要在配置文件中配置一下:


    2.、自定义的异常统一处理 
    在实现异常的统一处理之前,先来明确一下需求:

    站点所有页面在异常发生后,均需要记录异常日志,并转向错误提示页面(异常内容的详略程度由具体需求决定) 
    所有返回JSON数据的异步请求,不但需要记录异常日志,而且需要向客户端返回JSON格式的错误信息提示,而不是转向错误提示页面(异步请求也不可能转向错误提示页面) 
    采用AOP思想,将异常处理解耦 
    尽量精简声明Attribute的重复代码 
    实现1和3:

    因为整个站点均需要记录异常日志,因此需要设计一个异常日志记录的过滤器(LogExceptionAttribute)进行拦截处理,这样既体现了AOP思想又满足了解耦的需要。代码如下:

    [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);
                }
    
                base.OnException(filterContext);
            }
        }

    LogExceptionAttribute继承了HandleErrorAttribute,重写的OnException方法在记录异常日志后,通过调用base.OnException回到了系统默认的异常处理上,实现了向错误页面的跳转。

    LogExceptionAttribute设置了自己的AttributeUsage特性,AttributeTargets.Class指定该过滤器只能用于类一级,即Controller;AllowMultiple = false设置不允许多次执行,即仅在Controller级执行一次。

    .实现:

    很明显,因为记录异常日志的需求是全局性的,因此采用注册全局性的过滤器,就能满足尽量精简代码的需求。在Gloabal注册过滤器时增加如下代码:

    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
           filters.Add(new HandleErrorAttribute());
           filters.Add(new LogExceptionAttribute());
    }

    实现2:

    返回JSON格式的错误信息不是全局性的,只是某些特定的Action才需要,因此需要设计一个异常过滤器专门返回异常的JSON信息。这个过滤器应该只需要作用于Action即可。根据之前的异常处理顺序,先里后外的原则,在处理异常时,会先处理这个JSON异常过滤器,再处理之前定义的LogExceptionAttribute,从而实现了返回JSON错误信息的同时并记录了异常日志。代码如下:

    [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 }
                    };
                }
            }
        }

    JsonExceptionAttribute里会生成一个新的JsonResult对象,并赋值给返回结果(当然,这里也需要统一整个站点的JSON返回格式);同时通过AttributeTargets.Method指定该过滤器只能用于方法一级,即对应Action。

    需要注意的是,不需要调用base.OnException,否则会跳过LogExceptionAttribute先执行HandleErrorAttribute的处理逻辑,从而返回结果不再是JsonResult,而是ViewResult,客户端也就无法处理非JSON的结果了。

    这里也不需要设置filterContext.ExceptionHandled = true,否则在LogExceptionAttribute处理时,因为 !filterContext.ExceptionHandled 的判断条件,LogExceptionAttribute的逻辑不会执行,也就不会记录异常日志了。

    使用时,仅需要在Action上声明这个特性即可。代码如下:

    [HttpPost]
    [JsonException]
    public JsonResult Add(string ip, int port)
    {
            ...  //处理逻辑
            return Json(new { Success = true, Message = "添加成功" });
    }

    为了配合JsonExceptionAttribute的正常运行,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);
                }
            }
        }

    注意前后比较一下,在LogExceptionAttribute不会直接调用base.OnException了,而是先判断当前的返回结果是不是JsonResult。返回结果是JsonResult,则表明之前处理过JsonExceptionAttribute,此时需要设置 filterContext.ExceptionHandled = true,并不再继续基类HandleErrorAttribute的处理;返回结果不是JsonResult,则调用base.OnException,继续执行基类HandleErrorAttribute的逻辑,转向错误页面。

    如果需要扩展其他类型的异常处理,只需要增加对应的异常过滤器,并在LogExceptionAttribute里进行相应的改造即可。

    3、后记 
    添加以上的过滤器并配合配置文件中改变,我们的异常处理的几点需求就全部完成了。如果没有太大的变化,这样的处理模式是可以通用于MVC站点的。采用这种模式,如果没有特殊需求,在我们的控制器,逻辑层,数据访问层等,都不需要增加额外的异常处理的代码,产生异常后直接外抛,最终都会被异常过滤器拦截并进行处理。

    即使因为特定需求的原因,可能需要为某些代码块加上try{}catch{}进行异常捕获和处理,也推荐在catch语句中处理完毕后仍使用throw语句将异常抛出,统一由LogExceptionAttribute来进行最终的捕捉和处理。这样将大量缩减try{}catch{}语句的重复出现。当然,最终具体如何处理异常还将视具体情况进行调整。

    转自:http://www.cnblogs.com/Showshare/p/exception-handle-with-mvcfilter.html

  • 相关阅读:
    编译原理语法分析程序(最左推导)
    剑指offer:02
    剑指offer:01二维数组中的查找
    pthread编译选项(命令行,codeblocks)
    学习apue的pthread synchronize之condition variables
    pthreads之joinable和detach函数
    c++指针空间大小的决定因素
    2.3.1引用
    2.2.1变量定义
    扫描PDF417崩溃的原因找到:手机摄像头分辨率低
  • 原文地址:https://www.cnblogs.com/mahoumei/p/6824864.html
Copyright © 2011-2022 走看看