zoukankan      html  css  js  c++  java
  • MVC 自定义异常过滤特性

    在ASP.NET MVC中,默认的异常处理机制有时候无法满足项目的业务需求,我们可以通过实现IExceptionFilter接口编写自己想要的异常处理代码,比如全局异常捕获,记录错误日志等自定义异常处理操作。

    文章演示代码的下载地址:GlobalExceptionHandle-By-IExceptionFilter ,项目使用的是VS2013开发工具,ASP.NET MVC 5框架,建议下载查看演示,有更加详细的代码和直观的界面。

    ASP.NET MVC默认提供了HandleError特性作为全局异常处理的机制,但是有部分的局限性,比如不捕捉404错误,依赖ASP.NET的自定义错误模块,对SEO不友好(因为错误页面跳转的方式为302)等等,所以如果想在项目中对异常错误进行更多自定义操作,可以继承并重写HandleError特性,或者使用IExceptionFilter编写自己的异常处理特性。

    PS:HandleError也是通过实现IExceptionFilter接口来进行相关操作,具体可以查看HandleErrorAttribute源码。如果没用到HandleError特性的一些属性,还是推荐使用IExceptionFilter接口,这样代码会比较精炼,并且可以更自由的进行功能扩展和代码编写。

    自定义异常过滤器的关键代码

    这里先新建一个ASP.NET MVC项目,接着新建一个类库,以后所有自定义特性和过滤器的类都放在这个类库下,这里我将类库命名为CustomAttributeClassLibrary。

    接下来在类库中添加要编写自定义异常处理的类,这里我命名为CustomExceptionAttribute。

    CustomExceptionAttribute类代码如下:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Web;
    using System.Web.Mvc;
    
    namespace CustomAttributeClassLibrary
    {
        public class CustomExceptionAttribute : FilterAttribute, IExceptionFilter
        {
            public void OnException(ExceptionContext filterContext)
            {
                Exception exception = filterContext.Exception;
                if (filterContext.ExceptionHandled == true)
                {
                    return;
                }
                HttpException httpException = new HttpException(null, exception);
                //filterContext.Exception.Message可获取错误信息
    
                /*
                 * 1、根据对应的HTTP错误码跳转到错误页面
                 * 2、先对Action方法里引发的HTTP 404/400错误进行捕捉和处理
                 * 3、其他错误默认为HTTP 500服务器错误
                 */
                if (httpException != null && (httpException.GetHttpCode() == 400 || httpException.GetHttpCode() == 404))
                {
                    filterContext.HttpContext.Response.StatusCode = 404;
                    filterContext.HttpContext.Response.WriteFile("~/HttpError/404.html");
                }
                else
                {
                    filterContext.HttpContext.Response.StatusCode = 500;
                    filterContext.HttpContext.Response.WriteFile("~/HttpError/500.html");
                }
                /*---------------------------------------------------------
                 * 这里可进行相关自定义业务处理,比如日志记录等
                 ---------------------------------------------------------*/
    
                //设置异常已经处理,否则会被其他异常过滤器覆盖
                filterContext.ExceptionHandled = true;
    
                //在派生类中重写时,获取或设置一个值,该值指定是否禁用IIS自定义错误。
                filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;
            }
        }
    }

    上面代码中主要实现了一个IExceptionFilter接口和一个FilterAttribute基类。这里先说IExceptionFilter接口,接口只提供了一个方法OnException,主要的参数为ExceptionContext类,基本上就是通过ExceptionContext类来获取相关错误信息,以及进行对应的HTTP请求和响应操作。

    ExceptionContext类文档的截图说明:

    ExceptionContext 类属性说明

    至于FilterAttribute抽象类,如果打算将编写的异常过滤器作为特性来使用,那么它必须继承自FilterAttribute或者它的子类HandleErrorAttribute。不作为特性使用的全局操作过滤器可以不继承这个基类。

    下图演示可以看到没有继承FilterAttribute类就无法作为特性来使用:

    未继承FilterAttribute抽象类的过滤器无法作为特性使用

    但是不影响全局操作过滤器的注册和使用:

    注册自定义全局异常过滤器

    记得使用NUGET将ASP.NET MVC 5 安装到新类库中,IExceptionFilter是属于System.Web.Mvc程序集,而且编写自定义的过滤器和特性会使用到很多与System.Web.Mvc命名空间有关的类。

    自定义异常特性类库需要添加ASP.NET MVC命名空间

    到这里就完成了整个自定义异常处理过滤器的功能。

    在项目中使用自定义异常过滤器

    有两种使用方式,一种是通过全局过滤器类GlobalFilterCollection进行注册,还有一种是单独的作为特性在控制器或操作方法中使用。

    我们先将自定义特性类库引用到默认的MVC项目中:

    项目中引入自定义特性类库

    全局注册的使用只需要在FilterConfig过滤器配置类中注册下即可,代码如下:

    public class FilterConfig
    {
        public static void RegisterGlobalFilters(GlobalFilterCollection filters)
        {
            filters.Add(new HandleErrorAttribute(), 1);         
            filters.Add(new CustomExceptionAttribute(), 2);
        }
    }

    PS:关于FilterConfig类,如果不清楚的朋友可以看这篇文章:ASP.NET MVC 5 学习笔记之FilterConfig类

    这里要注意筛选器的运行顺序设置,引用文档资料

    在 ASP.NET MVC 版本 1 和 2 中,OnException(ExceptionContext) 筛选器以正向顺序运行。 在 ASP.NET MVC 版本 3 及更高版本中,此顺序已反转。

    所以Order=2的优先级反而比较高,注册好后整个MVC项目所有的异常都会被我们的CustomExceptionAttribute类捕捉和处理。

    单独的作为特性使用有一个前提,就是筛选器需要继承FilterAttribute基类,上面演示代码中已有说明,使用方法其他的特性一样,只需要在控制器或者相应的操作方法上方声明即可:

    [CustomException]
    public ActionResult Index()
    {
        return View();
    }

    这里贴几段引发异常的操作方法代码,把下面代码放到演示项目中默认的Home控制器中:

    /// <summary>
    /// 抛出HTTP 500
    /// </summary>
    /// <returns></returns>
    public ActionResult ThrowHttp500()
    {
        throw new HttpException(500, "服务器错误");
    }
    /// <summary>
    /// 抛出HTTP 404
    /// </summary>
    /// <returns></returns>
    public ActionResult ThrowHttp404()
    {
        throw new HttpException(404, "页面未找到");
    }
    /// <summary>
    /// 抛出未引用对象异常
    /// </summary>
    /// <returns></returns>
    public ActionResult ThrowNullReferenceException()
    {
        throw new NullReferenceException();
    }
    /// <summary>
    /// 引发输入字符串的格式不正确异常
    /// </summary>
    /// <returns></returns>
    public ActionResult ThrowFormatException()
    {
        string str = "";
        int count = Convert.ToInt32(str);
        return View("Index");
    }

    网站生成运行后输入路径测试,结果如下图:

    实现IExceptionFilter接口的自定义异常过滤器演示结果

    使用IExceptionFilter的一些注意事项

    这里总结使用IExceptionFilter编写自定义异常过滤器的需要注意的地方:

    一、并不依赖自定义异常错误,所以web.config配置文件中的customErrors  mode="On"或者mode="Off"都无法影响到我们自己编写的过滤器的使用。

    但是如果你想要使用customErrors来进行控制,那么也很简单,在我们自定义的过滤器中,只需要使用filterContext.HttpContext.IsCustomErrorEnabled属性进行判断即可:

    IExceptionFilter接口使用ExceptionHandled属性防止多个异常过滤器冲突


    二、只能处理Action操作方法里抛出的异常,超出此范围的异常是无法被捕捉到的。这点非常重要,这是ASP.NET MVC中筛选器自身的特性。无论你是在Controller上声明筛选器,或者通过全局注册,最终都只能应用到某个具体的Action方法。这里所谓的全局注册,其实就是指所有的Action都应用了该筛选器。同样的,如果筛选器在控制器上声明,其实也是表示该控制器下所有的操作方法都要应用此筛选器。筛选器本身只针对ACtion方法!在Controller上声明和全局注册只是设定了其应用范围!

    这里引用《ASP.NET MVC高级编程》的说明:

    异常过滤器用来处理操作或结果执行期间可能抛出的异常。

    注意:IExceptionFilter也能捕获Action方法的其他筛选器所抛出的异常。例如实现IAuthorizationFilterIActionFilter或者IResultFilter的筛选器如果执行期间抛出异常,都是能被捕捉到的。

    PS:筛选器=过滤器!不同的翻译别称。

    主要无法处理的范围和情况有:

    1. 视图中抛出的异常。
    2. IIS级别的错误(例如访问不存在的静态文件)。
    3. ASP.NET MVC的路由映射错误问题,例如访问不存在的路由和url。

    如果遇到上面的情况又想捕获到异常,也是有两个解决办法的,一个是使用Global.asax的Application_Error事件处理这部分筛选器无法捕捉的异常。另外一个是直接设置IIS上的.NET错误即可,其实就是设置自定义异常错误,设置web.config配置文件中的customErrors节点的mode="On",然后设置对应的error节点和相应的HTTP错误跳转页面。注意自定义筛选器的优先级顺序要设置下,如果没有什么业务需求并且不考虑SEO就可以使用这种简单的方法。


    三、IIS级的错误并不会被捕捉到,因为此类错误还未进入ASP.NET 的处理流程,这个问题上面第二点就有提到。比如下图中输入某个物理路径不存在的页面,这里直接就显示IIS默认的错误页,需要在服务器上进行相关设置:

    实现IExceptionFilter接口的异常过滤器无法捕捉IIS级错误


    四、在我们的筛选器CustomExceptionAttribute代码中,可以看到使用了一个ExceptionHandled属性:

    if (filterContext.ExceptionHandled == true)

    这个属性十分重要,是为了防止使用多个异常过滤器导致操作发生冲突。在使用多个异常过滤器的情况下此属性作用十分明显!

    如果为true,则表示已有其他filter处理过此异常 ,就不需要再次进行处理了,以免覆盖其他filter所解决的Exception。

    如果为false,就要对该Exception进行相应的处理,处理之后将ExceptionHandled属性设置为true,表示异常已经处理,否则操作会被其他异常过滤器覆盖掉,造成冲突。

    默认情况下如果ExceptionHandled属性没有被其他异常处理过滤器设置为true ,那么ASP.NET MVC将使用默认的异常处理,如果没有开启customErrors,将会显示异常详细页面(错误黄页)。


    五、操作相关的页面跳转,如果网站要考虑搜索引擎优化方法,一定不要使用下面的两种方法进行跳转:

    //filterContext.HttpContext.Response.Redirect("~/HttpError/500.html");
    //filterContext.HttpContext.Server.Transfer("~/HttpError/500.html");
    

    因为这两种方法你设置了StatusCode是无效的。

    此外要注意路由在传统的ASP.NET 跳转方法中存在不兼容问题,如果想要使用MVC的路由和视图进行跳转,可以参考下面的代码:

    /*--------------------------------------------------------------------------
    * 下面的范例是使用MVC控制器和视图进行跳转,具体可查阅HandleErrorAttribute源码                
    --------------------------------------------------------------------------*/
    string controllerName = (string)filterContext.RouteData.Values["controller"];
    string actionName = (string)filterContext.RouteData.Values["action"];
    HandleErrorInfo model = new HandleErrorInfo(filterContext.Exception, controllerName, actionName);
    filterContext.Result = new ViewResult
    {
        ViewName = "视图名称",
        MasterName = "母板名称",
        ViewData = new ViewDataDictionary<HandleErrorInfo>(model),
        TempData = filterContext.Controller.TempData
    };
    filterContext.ExceptionHandled = true;
    filterContext.HttpContext.Response.Clear();
    filterContext.HttpContext.Response.StatusCode = 500;

    上面的代码在视图结果中还传入了错误信息实体,如果只想要简单的跳转到某个视图或控制器,其实只要设置ActionResult即可,代码如下:

    filterContext.Result = new ViewResult() { ViewName="视图名称"};
    filterContext.ExceptionHandled = true;
    filterContext.HttpContext.Response.Clear();
    filterContext.HttpContext.Response.StatusCode = 500;


    出处:https://shiyousan.com/post/635833789557065314

    版权声明:本文采用知识共享许可协议:署名-相同方式共享 4.0 国际(CC BY-SA 4.0)。欢迎转载本文,转载请声明出处或保留此段声明。

     
  • 相关阅读:
    【Mongodb教程 第九课 】MongoDB 删除文档
    【Mongodb教程 第八课 】MongoDB 更新文档
    【Mongodb教程 第七课 】MongoDB 查询文档
    【Mongodb教程 第六课 】MongoDB 插入文档
    【Mongodb教程 第五课 】MongoDB 删除集合
    【Mongodb教程 第四课 】MongoDB 创建集合
    【Mongodb教程 第三课 】MongoDB 删除数据库
    【Mongodb教程 第二课 】 MongoDB 创建数据库 use 命令
    题解 P2821 【变幻数】
    题解 P6249 【神帖】
  • 原文地址:https://www.cnblogs.com/qtiger/p/10824796.html
Copyright © 2011-2022 走看看