ASP.NET MVC集成EntLib实现“自动化”异常处理[实例篇]

简介:

个人觉得异常处理对于程序员来说是最为熟悉的同时也是最难掌握的。说它熟悉,因为仅仅就是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呈现出来的。

image

二、通过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>
  16:         @Html.DisplayFor(m=>m.ErrorMessage)
  17:     </h3>
  18:     <ul>
  19:         <li>Controller: @Html.DisplayFor(m => m.ControllerName)</li>
  20:         <li>Action: @Html.DisplayFor(m => m.ActionName)</li>
  21:         <li>Exception: 
  22:             <ul>
  23:                 <li>Message: @Html.DisplayFor(m => m.Exception.Message)</li>
  24:                 <li>Type: @Model.Exception.GetType().FullName</li>
  25:                 <li>StackTrace: @Html.DisplayFor(m => m.Exception.StackTrace)</li>
  26:             </ul>
  27:         </li>
  28:     </ul>
  29: </body>
  30: </html>

上面这个View的Model类型是具有如下定义的ExtendedHandleErrorInfo。它继承自HandleErrorInfo,只额外定义了一个表示错误消息的ErrorMessage属性。在上面的这个View中,我们将错误消息、异常类型和StackTrace和当前Controller/Action的名称呈现出来。

   1: public class ExtendedHandleErrorInfo : HandleErrorInfo
   2: {
   3:     public string ErrorMessage { get; private set; }
   4:     public ExtendedHandleErrorInfo(Exception exception, string controllerName, string actionName, string errorMessage)
   5:         : base(exception, controllerName, actionName)
   6:     {
   7:         this.ErrorMessage = errorMessage;
   8:     }
   9: }

当利用EntLib的EHAB对从Index方法中抛出的异常进行处理后采用错误View的方式来响应请求,我们需要按照如下的方式将应用在该方法上的HandleErrorActionAttribute特性注释掉。

   1: [ExceptionPolicy("defaultPolicy")]
   2: public class HomeController : ExtendedController
   3: {    
   4:     //其他成员
   5:     [HttpPost]
   6:     //[HandleErrorAction("OnIndexError")]
   7:     public ActionResult Index(LoginInfo loginInfo)
   8:     {
   9:         //省略实现
  10:     }
  11: }

再次运行该程序并分别输入错误的用户名和密码后,默认的错误View(Error.cshtml)将会以如下图所示地效果把处理后的异常结果呈现出来。

image

三、自动创建JsonResult响应Ajax请求

用于实施认证的Action方法Index可以通过普通的HTTP-POST的形式来调用,同样也可以通过Ajax请求的方式来调用。对于Ajax请求来说,我们最终会将通过EntLib处理后的异常封装成如下一个类型为ExceptionDetail的对象。如下面的代码片断所示,ExceptionDetail具有与Exception对应的属性设置。最终根据抛出异常对象创建的ExceptionDetail对象会被用于创建一个JsonResult对象对当前Ajax请求予以响应。

   1: public class ExceptionDetail
   2: {
   3:     public ExceptionDetail(Exception exception,string errorMessage=null)
   4:     {
   5:         this.HelpLink = exception.HelpLink;
   6:         this.Message = string.IsNullOrEmpty(errorMessage) ? exception.Message : errorMessage;
   7:         this.StackTrace = exception.StackTrace;
   8:         this.Type = exception.GetType().ToString();
   9:         if (exception.InnerException != null)
  10:         {
  11:             this.InnerException = new ExceptionDetail(exception.InnerException);
  12:         }
  13:     }
  14:  
  15:     public string HelpLink { get; set; }
  16:     public ExceptionDetail InnerException { get; set; }
  17:     public string Message { get; set; }
  18:     public string StackTrace { get; set; }
  19:     public string Type { get; set; }
  20: }

当客户端接收到回复的Json对象后,可以通过检测其是否具有一个ExceptionType属性(对于一个ExceptionDetail对象来说,该属性不可能为Null)来判断是否发生异常。作为演示我们对Action方法Index对应的View进行了如下的改动。

   1: @model LoginInfo
   2: <html>
   3:     <head>
   4:         <title>用户登录</title>
   5:         <script type="text/javascript" src="@Url.Content("~/Scripts/jquery-1.6.2.js")"></script>
   1:  
   2:         <script type="text/javascript" src="@Url.Content("~/Scripts/jquery.unobtrusive-ajax.js")">
   1: </script>
   2:         <script type="text/javascript">
   3:             function login(data) {
   4:                 if (data.ExceptionType) {
   5:                     alert(data.Message);
   6:                 }
   7:                 else {
   8:                     alert("认证成功");
   9:                 }
  10:             }
  11:         
</ script >
   6:     </head>
   7:     <body>
   8:         @{
   9:             AjaxOptions options = new  AjaxOptions{OnSuccess = "login"};
  10:          }
  11:         @using (Ajax.BeginForm(options))
  12:         { 
  13:             @Html.EditorForModel()
  14:             <input type="submit" value="登录" />
  15:         }
  16:     </body>
  17: </html>

如上面的代码片断所示,我们通过调用AjaxHelper的BuginForm生成了一个以Ajax形式提交的表单。表单成功提交(服务端因对抛出的异常进行处理而返回一个封装异常的Json对象,对于提交表单的Ajax请求来说依然属于成功提交)后会调用我们定义的回调函数login。在该JavaScript函数中,我们通过得到的对象是否具有一个ExceptionType属性来判断服务端是否抛出异常。如果抛出异常,在通过调用alert方法将错误消息显示出来,否则显示“认证成功”。我们再次运行我们的程序并分别输入不合法的用户名和密码,相应的错误消息会以对话框的形式显示出来,具体的显示效果如下图所示。

image

ASP.NET MVC集成EntLib实现“自动化”异常处理[实例篇]
ASP.NET MVC集成EntLib实现“自动化”异常处理[实现篇]


作者:蒋金楠
微信公众账号:大内老A
微博: www.weibo.com/artech
如果你想及时得到个人撰写文章以及著作的消息推送,或者想看看个人推荐的技术资料,可以扫描左边二维码(或者长按识别二维码)关注个人公众号(原来公众帐号 蒋金楠的自媒体将会停用)。
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
相关文章
|
3月前
|
XML 开发框架 .NET
ASP.NET COR3.1 集成日志插件NLog
ASP.NET COR3.1 集成日志插件NLog
33 0
|
4月前
|
XML SQL 开发框架
|
6月前
|
JSON 开发框架 .NET
ASP.NET Core 集成JWT
什么是JWT?Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也...
233 0
ASP.NET Core 集成JWT
|
3月前
|
开发框架 前端开发 .NET
ASP.NET CORE 3.1 MVC“指定的网络名不再可用\企图在不存在的网络连接上进行操作”的问题解决过程
ASP.NET CORE 3.1 MVC“指定的网络名不再可用\企图在不存在的网络连接上进行操作”的问题解决过程
38 0
|
1月前
|
开发框架 前端开发 .NET
C# .NET面试系列六:ASP.NET MVC
<h2>ASP.NET MVC #### 1. MVC 中的 TempData\ViewBag\ViewData 区别? 在ASP.NET MVC中,TempData、ViewBag 和 ViewData 都是用于在控制器和视图之间传递数据的机制,但它们有一些区别。 <b>TempData:</b> 1、生命周期 ```c# TempData 的生命周期是短暂的,数据只在当前请求和下一次请求之间有效。一旦数据被读取,它就会被标记为已读,下一次请求时就会被清除。 ``` 2、用途 ```c# 主要用于在两个动作之间传递数据,例如在一个动作中设置 TempData,然后在重定向到另
95 5
|
2月前
|
安全 C# 开发者
.NET开源的一键自动化下载、安装、激活Microsoft Office利器
.NET开源的一键自动化下载、安装、激活Microsoft Office利器
|
6月前
|
Java
已解决Java.net.MalformedURLException异常的有效方法java.net.MalformedURLException: no protocol异常处理
已解决Java.net.MalformedURLException异常的有效方法java.net.MalformedURLException: no protocol异常处理
343 0
|
3月前
|
IDE 前端开发 JavaScript
【C#】C# 开发环境配置(Rider 一个.NET 跨平台集成开发环境)
【1月更文挑战第26天】【C#】C# 开发环境配置(Rider 一个.NET 跨平台集成开发环境)
|
5月前
|
开发框架 自然语言处理 前端开发
基于ASP.NET MVC开发的、开源的个人博客系统
基于ASP.NET MVC开发的、开源的个人博客系统
51 0
|
5月前
|
存储 SQL 监控
.NET开源简单易用、内置集成化的控制台、支持持久性存储的任务调度框架 - Hangfire
.NET开源简单易用、内置集成化的控制台、支持持久性存储的任务调度框架 - Hangfire