个人觉得异常处理对于程序员来说是最为熟悉的同时也是最难掌握的。说它熟悉,因为仅仅就是try/catch/finally而已。说它难以掌握,则是因为很多开发人员却说不清楚try/catch/finally应该置于何处?什么情况下需要对异常进行日志记录?什么情况下需要对异常进行封装?什么情况下需要对异常进行替换?对于捕获的异常,在什么情况下需要将其再次抛出?什么情况下则不需要?
合理的异常处理应该是场景驱动的,在不同的场景下,采用的异常处理策略往往是不同的。异常处理的策略应该是可配置的,因为应用程序出现怎样的异常往往是不可预测的,现有异常策略的不足往往需要在真正出现某种异常的时候才会体现出来,所以我们需要一种动态可配置的异常处理策略维护方式。目前有一些开源的异常处理框架提供了这种可配置的、场景驱动的异常处理方式,EntLib的Exception Handling Application Block(以下简称EHAB)就是一个不错的选择。[源代码从这里下载][本文已经同步到《How ASP.NET MVC Works?》中]
目录
一、通过指定Handle-Error-Action响应请求
二、通过Error View显示错误消息
三、自动创建JsonResult响应Ajax请求
一、通过指定Handle-Error-Action响应请求
在正式介绍如何通过扩展实现与EntLib以实现自动化异常处理之前,我们不妨先来体验一下异常处理具有怎样的“自动化”特性。以用户登录场景为例,我们在通过Visual Studio的ASP.NET MVC项目模板创建的Web应用中定义了如下一个简单的数据类型LoginInfo封装用户登录需要输入的用户名和密码。
1: public class LoginInfo
2: {
3: [DisplayName("用户名")]
4: [Required(ErrorMessage="请输入{0}")]
5: public string UserName { get; set; }
6:
7: [DisplayName("密码")]
8: [Required(ErrorMessage = "请输入{0}")]
9: [DataType(DataType.Password)]
10: public string Password { get; set; }
11: }
然后我们定义了如下一个HomeController。基于HTTP-GET的Action方法Index将会呈现一个用户登录View,该View使用创建的LoginInfo对象作为其Model。真正的用户验证逻辑定义在另一个应用了HttpPostAttrubute特性的Index方法中:如果用户名不为Foo,抛出InvalidUserNameException异常;如果密码不是“password”,则抛出InvalidPasswordException异常。InvalidUserNameException和InvalidPasswordException是我们自定义的两种异常类型。
1: [ExceptionPolicy("defaultPolicy")]
2: public class HomeController : ExtendedController
3: {
4: public ActionResult Index()
5: {
6: return View(new LoginInfo());
7: }
8:
9: [HttpPost]
10: [HandleErrorAction("OnIndexError")]
11: public ActionResult Index(LoginInfo loginInfo)
12: {
13: if (string.Compare(loginInfo.UserName, "foo", true) != 0)
14: {
15: throw new InvalidUserNameException();
16: }
17:
18: if (loginInfo.Password != "password")
19: {
20: throw new InvalidPasswordException();
21: }
22: return View(loginInfo);
23: }
24:
25: [HttpPost]
26: public ActionResult OnIndexError(LoginInfo loginInfo)
27: {
28: return View(loginInfo);
29: }
30: }
上面定义的HomeController具有三点与自动化异常处理相关的地方:
- HomeController继承自自定义的基类ExtendedController,后者完成了对异常的自动化处理。
- HomeController类型上应用了自定义的ExceptionPolicyAttribute特性用于指定默认采用的异常处理策略名称(“defaultPolicy”)。
- 基于HTTP-POST的Index方法上应用了HandleErrorActionAttribute特性用于指定一个Handle-Error-Action名称,当异常在目标Action执行过程中抛出并通过EHAB处理后,指定的Action会被执行以实现对请求的响应。对于我们的例子来说,从Index方法抛出的异常被处理后会调用OnIndexError方法作为对当前请求的响应。
下面是代表登录页面的View的定义,这是一个Model类型为LoginInfo的强类型View。在该View中,作为Model的LoginInfo对象以编辑默认呈现在一个表单中,表单中提供了一个“登录”提交表单。除此之外,View中还具有个ValidationSummary。
1: @model LoginInfo
2: <html>
3: <head>
4: <title>用户登录</title>
5: <style type="text/css">
6: .validation-summary-errors{color:Red}
7: </style>
8: </head>
9: <body>
10: @using (Html.BeginForm())
11: {
12: @Html.ValidationSummary(true)
13: @Html.EditorForModel()
14: <input type="submit" value="登录" />
15: }
16: </body>
17: </html>
通过HomeController的定义我们知道两种不同类型的异常(InvalidUserNameException和InvalidPasswordException)分别在输入无效用户名和密码是被抛出来,而我们需要处理的就是这两种类型的异常。正对它们的异常处理策略定义在如下的配置中,策略名称就是通过应用在HomeController上的ExceptionPolicyAttribute特性指定的“defaultPolicy”。
1: <configuration>
2: <configSections>
3: <section name="exceptionHandling"
4: type="Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.Configuration.ExceptionHandlingSettings, Microsoft.Practices.EnterpriseLibrary.ExceptionHandling" />
5: </configSections>
6: <exceptionHandling>
7: <exceptionPolicies>
8: <add name="defaultPolicy">
9: <exceptionTypes>
10: <add type="MvcApp.InvalidUserNameException, MvcApp" postHandlingAction="ThrowNewException" name="InvalidUserNameException">
11: <exceptionHandlers>
12: <add name ="ErrorMessageHandler" type="MvcApp.ErrorMessageHandler, MvcApp" errorMessage="用户名不存在"/>
13: </exceptionHandlers>
14: </add>
15:
16: <add type="MvcApp.InvalidPasswordException, MvcApp" postHandlingAction="ThrowNewException" name="InvalidPasswordException">
17: <exceptionHandlers>
18: <add name ="ErrorMessageHandler" type="MvcApp.ErrorMessageHandler, MvcApp" errorMessage="密码与用户名不匹配"/>
19: </exceptionHandlers>
20: </add>
21: </exceptionTypes>
22: </add>
23: </exceptionPolicies>
24: </exceptionHandling>
25: ...
26: </configuration>
通过上面的这样异常策略配置可以看到:我们使用一个自定义的名为ErrorMessageHandler的ExceptionHandler来处理抛出来的InvalidUserNameException和InvalidPasswordException异常,而ErrorMessageHandler仅仅是指定一个友好的错误消息,该消息一般会呈现给最终的用户。运行该程序后一个用于登录页面会呈现出来,当我们输入错误的用户名和密码的时候,相应的错误消息(在配置中通过ErrorMessageHandler设置的错误消息)会以如图7-16所示的效果显示出来,其实整个View是通过执行Action方法OnIndexError返回的ViewResult呈现出来的。
二、通过Error View显示错误消息
除了通过执行对应的Handle-Error-Action来呈现异常处理后的最终结果之外,还支持错误页面的错误呈现方法。简单起见,我们只是用名称为Error的View来作为最终的错误页面。为了演示基于错误页面的呈现方式,我们按照如下的方式重新定义了\Views\Shared\目录下的Error.cshtml。
1: @model ExtendedHandleErrorInfo
2: @{
3: Layout = null;
4: }
5: <!DOCTYPE html>
6: <html>
7: <head>
8: <meta name="viewport" content="width=device-width" />
9: <title>Error</title>
10: <style type="text/css">
11: h3 {color:Red;}
12: </style>
13: </head>
14: <body>
15: <h3>