一个简单的ASP.NET MVC异常处理模块

 

一、前言

  异常处理是每个系统必不可少的一个重要部分,它可以让我们的程序在发生错误时友好地提示、记录错误信息,更重要的是不破坏正常的数据和影响系统运行。异常处理应该是一个横切点,所谓横切点就是各个部分都会使用到它,无论是分层中的哪一个层,还是具体的哪个业务逻辑模块,所关注的都是一样的。所以,横切关注点我们会统一在一个地方进行处理。无论是MVC还是WebForm都提供了这样实现,让我们可以集中处理异常。

  在MVC中,在FilterConfig中,已经默认帮我们注册了一个HandleErrorAttribute,这是一个过滤器,它继承了FilterAttribute类和实现了IExceptionFilter接口,关于过滤器的执行过程,可以看我的上一篇文章。说到异常处理,马上就会联想到500错误页面、记录日志等,HandleErrorAttribute可以轻松的定制错误页,默认就是Error页面;而记录日志我们也只需要继承它,并替换它注册到GlobalFilterCollection即可。关于HandleErrorAttribute很多人都知道怎么使用了,这里就不做介绍了。

  ok,开始进入主题!在MVC中处理异常,相信开始很多人都是继承HandleErrorAttribute,然后重写OnException方法,加入自己的逻辑,例如将异常信息写入日志文件等。当然,这并没有任何不妥,但良好的设计应该是场景驱动的,是动态和可配置的。例如,在场景一种,我们希望ExceptionA显示错误页面A,而在场景二中,我们希望它显示的是错误页面B,这里的场景可能是跨项目了,也可能是在同一个系统的不同模块。另外,异常也可能是分级别的,例如ExceptionA发生时,我们只需要简单的恢复状态,程序可以继续运行,ExceptionB发生时,我们希望将它记录到文件或者系统日志,而ExceptionC发生时,是个较严重的错误,我们希望程序发生邮件或者短信通知。简单地说,不同的场景有不同的需求,而我们的程序需要更好的面对变化。当然,继承HandleErrorAttribute也完全可以实现上面所说的,只不过这里我不打算去扩展它,而是重新编写一个模块,并且可以与原有的HandleErrorAttribute共同使用。

二、设计及实现

2.1 定义配置信息

  从上面已经可以知道我们要做的事了,针对不同的异常,我们希望可以配置它的处理程序,错误页等。如下一个配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!--自定义异常配置-->
<settingException>
  <exceptions>
    <!--add优先级高于group-->
    <add exception="Exceptions.PasswordErrorException"
         view ="PasswordErrorView"
         handler="ExceptionHandlers.PasswordErrorExceptionHandler"/>
    <groups>
      <!--group可以配置一种异常的view和handler-->
      <group view="EmptyErrorView" handler="ExceptionHandlers.EmptyExceptionHandler">
        <add exception="Exceptions.UserNameEmptyException"/>
        <add exception="Exceptions.EmailEmptyException"/>
      </group>       
    </groups>
  </exceptions>
</settingException>

  其中,add 节点用于增加具体的异常,它的 exception 属性是必须的,而view表示错误页,handler表示具体处理程序,如果view和handler都没有,异常将交给默认的HandleErrorAttribute处理。而group节点用于分组,例如上面的UserNameEmptyException和EmailEmptyException对应同一个处理程序和视图。

  程序会反射读取这个配置信息,并创建相应的对象。我们把这个配置文件放到Web.config中,保证它可以随时改随时生效。

2.2 异常信息包装对象

  这里我们定义一个实体对象,对应上面的节点。如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class ExceptionConfig
{
    /// <summary>
    /// 视图
    /// </summary>
    public string View{get;set;}
 
    /// <summary>
    /// 异常对象
    /// </summary>
    public Exception Exception{get;set;}
 
    /// <summary>
    /// 异常处理程序
    /// </summary>
    public IExceptionHandler Handler{get;set;}
}

2.3 定义Handler接口

  上面我们说到,不同异常可能需要不同处理方式。这里我们设计一个接口如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
public interface IExceptionHandler
{
    /// <summary>
    /// 异常是否处理完成
    /// </summary>
    bool HasHandled{get;set;}
 
    /// <summary>
    /// 处理异常
    /// </summary>
    /// <param name="ex"></param>
    void Handle(Exception ex);
}

  各种异常处理程序只要实现该接口即可。

2.3 实现IExceptionFilter

  这是必须的。如下,实现IExceptionFilter接口,SettingExceptionProvider会根据异常对象类型从配置信息(缓存)获取包装对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class SettingHandleErrorFilter : IExceptionFilter
{
    public void OnException(ExceptionContext filterContext)
    {
        if(filterContext == null)
        {
            throw new ArgumentNullException("filterContext");
        }
        ExceptionConfig config = SettingExceptionProvider.Container[filterContext.Exception.GetType()];
        if(config == null)
        {
            return;
        }
        if(config.Handler != null)
        {
            //执行Handle方法               
            config.Handler.Handle(filterContext.Exception);
            if (config.Handler.HasHandled)
            {
                //异常已处理,不需要后续操作
                filterContext.ExceptionHandled = true;
                return;
            }
        }           
        //否则,如果有定制页面,则显示
        if(!string.IsNullOrEmpty(config.View))
        {
            //这里还可以扩展成实现IView的视图
            ViewResult view = new ViewResult();
            view.ViewName = config.View;
            filterContext.Result = view;
            filterContext.ExceptionHandled = true;
            return;
        }
        //否则将异常继续传递
    }
}

2.4 读取配置文件,创建异常信息包装对象

  这部分代码比较多,事实上,你只要知道它是在读取web.config的自定义配置节点即可。SettingExceptionProvider用于提供容器对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
public class SettingExceptionProvider
{
    public static Dictionary<Type, ExceptionConfig> Container =
        new Dictionary<Type, ExceptionConfig>();
 
    static SettingExceptionProvider()
    {
        InitContainer();
    }
 
    //读取配置信息,初始化容器
    private static void InitContainer()
    {
        var section = WebConfigurationManager.GetSection("settingException"as SettingExceptionSection;
        if(section == null)
        {
            return;
        }
        InitFromGroups(section.Exceptions.Groups);
        InitFromAddCollection(section.Exceptions.AddCollection);
    }
 
    private static void InitFromGroups(GroupCollection groups)
    {                     
        foreach (var group in groups.Cast<GroupElement>())
        {  
            ExceptionConfig config = new ExceptionConfig();
            config.View = group.View;
            config.Handler = CreateHandler(group.Handler);
            foreach(var item in group.AddCollection.Cast<AddElement>())
            {
                Exception ex = CreateException(item.Exception);
                config.Exception = ex;
                Container[ex.GetType()] = config;
            }
        }
    }
 
    private static void InitFromAddCollection(AddCollection collection)
    {
        foreach(var item in collection.Cast<AddElement>())
        {
            ExceptionConfig config = new ExceptionConfig();
            config.View = item.View;
            config.Handler = CreateHandler(item.Handler);
            config.Exception = CreateException(item.Exception);
            Container[config.Exception.GetType()] = config;
        }
    }
 
    //根据完全限定名创建IExceptionHandler对象
    private static IExceptionHandler CreateHandler(string fullName)            
    {
        if(string.IsNullOrEmpty(fullName))
        {
            return null;
        }
        Type type = Type.GetType(fullName);
        return Activator.CreateInstance(type) as IExceptionHandler;
    }
 
    //根据完全限定名创建Exception对象
    private static Exception CreateException(string fullName)
    {
        if(string.IsNullOrEmpty(fullName))
        {
            return null;
        }
        Type type = Type.GetType(fullName);
        return Activator.CreateInstance(type) as Exception;
    }
}

  以下是各个配置节点的信息:

  settingExceptions节点:

  exceptions节点:

  Group节点集:

  group节点:

  add节点集:

  add节点:

三、测试

  ok,下面测试一下,首先要在FilterConfig的RegisterGlobalFilters方法中在,HandlerErrorAttribute前注册我们的过滤器:

  filters.Add(new SettingHandleErrorFilter())。

3.1 准备异常对象

   准备几个简单的异常对象:

1
2
3
public class PasswordErrorException : Exception{}
public class UserNameEmptyException : Exception{}
public class EmailEmptyException : Exception{}

3.2 准备Handler

  针对上面的异常,我们准备两个Handler,一个处理密码错误异常,一个处理空异常。这里没有实际处理代码,具体怎么处理,应该结合具体业务了。如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class PasswordErrorExceptionHandler : IExceptionHandler
{
    public bool HasHandled{get;set;}
     
    public void Handle(Exception ex)
    {
        //具体处理逻辑...
    }
}
 
public class EmptyExceptionHandler : IExceptionHandler
{
    public bool HasHandled { getset; }
 
    public void Handle(Exception ex)
    {
        //具体处理逻辑...
    }
}

3.3 抛出异常

  按照上面的配置,我们在Action中手动throw异常

1
2
3
4
5
6
7
8
9
10
11
12
public ActionResult Index()
{
    throw new PasswordErrorException();
}
public ActionResult Index2()
{
    throw new UserNameEmptyException();
}
public ActionResult Index3()
{
    throw new EmailEmptyException();
}

  可以看到,相应的Handler会被执行,浏览器也会出现我们配置的错误页面。

四、总结

  事实上这只是一个比较简单的例子,所以我称它为简单的模块,而是用框架、库之类的词。当然我们可以根据实际情况对它进行扩展和优化。微软企业库视乎也集成这样的模块,有兴趣的朋友可以了解一下。

  源码下载

NET MVC异常处理模块的更多相关文章

  1. 一个简单的ASP.NET MVC异常处理模块

    一.前言 异常处理是每个系统必不可少的一个重要部分,它可以让我们的程序在发生错误时友好地提示.记录错误信息,更重要的是不破坏正常的数据和影响系统运行.异常处理应该是一个横切点,所谓横切点就是各个部分都 ...

  2. 解析大型.NET ERP系统 设计异常处理模块

    异常处理模块是大型系统必备的一个组件,精心设计的异常处理模块可提高系统的健壮性.下面从我理解的角度,谈谈异常处理的方方面面.我的设计仅仅限定于Windows Forms,供参考. 1 定义异常类型 . ...

  3. ASP.NET MVC异常处理

    ASP.NET MVC异常处理方案 如何保留异常前填写表单的数据 ASP.NET MVC中的统一化自定义异常处理 MVC过滤器详解 MVC过滤器使用案例:统一处理异常顺道精简代码 ASP.NET MV ...

  4. 统一的mvc异常处理

    mvc异常处理 using System; using System.Configuration; using System.Web.Mvc; using Infrastructure.Excepti ...

  5. Extjs4.1.x使用Application动态按需加载MVC各模块

    我们知道Extjs4之后提出了MVC模块开发,将以前肥厚的js文件拆分成小的js模块[model\view\controller\store\form\data等],通过controller拼接黏合, ...

  6. Spring MVC异常处理SimpleMappingExceptionResolver

    Spring MVC异常处理SimpleMappingExceptionResolver[转] (2012-12-07 13:45:33) 转载▼ 标签: 杂谈 分类: 技术分享 Spring3.0中 ...

  7. Spring MVC异常处理代码完整实例

    Spring MVC异常处理流程: 提供构造方法传值: 配置异常处理器的bean

  8. django-rest-framework-源码解析002-序列化/请求模块/响应模块/异常处理模块/渲染模块/十大接口

    简介 当我们使用django-rest-framework框架时, 项目必定是前后端分离的, 那么前后端进行数据交互时, 常见的数据类型就是xml和json(现在主流的是json), 这里就需要我们d ...

  9. Spring MVC异常处理详解

    Spring MVC中异常处理的类体系结构 下图中,我画出了Spring MVC中,跟异常处理相关的主要类和接口. 在Spring MVC中,所有用于处理在请求映射和请求处理过程中抛出的异常的类,都要 ...

随机推荐

  1. Struts2 Action接收表单参数

    struts2 Action获取表单传值    1.通过属性驱动式    JSP:        <form action="sys/login.action" method ...

  2. nginx源代码分析--模块分类

    ngx-modules Nginx 基本的模块大致能够分为四类: handler – 协同完毕client请求的处理.产生响应数据.比方模块, ngx_http_rewrite_module, ngx ...

  3. Servlet和JSP读书笔记(三)之Cookie

    一. 浏览器和服务器之间通信的简单介绍引出Cookie和Session(只是简单的简介,不包含协议方面的知识) 1.当我们在浏览器中输入一个地址后,回车后就可以看到浏览器给我们展示的漂亮页面.在这个过 ...

  4. Indy的TCPServer到底能支持多少个连接

    最近一个项目,最开始使用IdTcpServer,在大压力测试的时候,只连接了800个多一点的客户端(每个客户端连接上之后每秒钟发送一个几十字节的报文,服务器应答).但是持续的时间不会超过10分钟,服务 ...

  5. IT痴汉的工作现状16-职业发展

    回首多年来的工作经历.发现自己的职业发展真是太平庸只是了.就像我的名字张伟,平淡无奇.而我,还是几年前刚入职模样的我,仍然像个涉世未深的矛头小子,相信技术能够改变世界.真是一入IT深似海,为伊消得人憔 ...

  6. 摘要算法CRC8、CRC16、CRC32,MD2 、MD4、MD5,SHA1、SHA256、SHA384、SHA512,RIPEMD、PANAMA、TIGER、ADLER32

    1.CRC8.CRC16.CRC32 CRC(Cyclic Redundancy Check,循环冗余校验)算法出现时间较长,应用也十分广泛,尤其是通讯领域,现在应用最多的就是 CRC32 算法,它产 ...

  7. The mmap module

    The mmap module The mmap module (New in 2.0) This module provides an interface to the operating syst ...

  8. [置顶] iframe使用总结(实战)

    说在前面的话,iframe是可以做很多事情的. 例如: a>通过iframe实现跨域; b>使用iframe解决IE6下select遮挡不住的问题 c>通过iframe解决Ajax的 ...

  9. IE, FireFox, Opera 浏览器支持CSS实现Alpha透明的方法 兼容问题

    一:要解决的问题时:在ie6-ie11下兼容下面透明上传文件button的效果. 实现方式通过滤镜实现. 二:效果图例如以下: watermark/2/text/aHR0cDovL2Jsb2cuY3N ...

  10. MSF 离线攻击

    MSF 离线攻击 MSF连环攻击在internet上实现是不太现实的,网络中的安全设备(防火墙.入侵检测.入侵防护系统). 实验拓扑如下: 实验说明:安全实验中的包过滤防火墙在测试中使用的是linux ...