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
如果你想及时得到个人撰写文章以及著作的消息推送,或者想看看个人推荐的技术资料,可以扫描左边二维码(或者长按识别二维码)关注个人公众号(原来公众帐号 蒋金楠的自媒体将会停用)。
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
相关文章
|
4月前
|
存储 Shell Linux
快速上手基于 BaGet 的脚本自动化构建 .net 应用打包
本文介绍了如何使用脚本自动化构建 `.net` 应用的 `nuget` 包并推送到指定服务仓库。首先概述了 `BaGet`——一个开源、轻量级且高性能的 `NuGet` 服务器,支持多种存储后端及配置选项。接着详细描述了 `BaGet` 的安装、配置及使用方法,并提供了 `PowerShell` 和 `Bash` 脚本实例,用于自动化推送 `.nupkg` 文件。最后总结了 `BaGet` 的优势及其在实际部署中的便捷性。
183 10
|
4天前
|
监控 前端开发 API
一款基于 .NET MVC 框架开发、功能全面的MES系统
一款基于 .NET MVC 框架开发、功能全面的MES系统
|
29天前
|
开发框架 缓存 .NET
GraphQL 与 ASP.NET Core 集成:从入门到精通
本文详细介绍了如何在ASP.NET Core中集成GraphQL,包括安装必要的NuGet包、创建GraphQL Schema、配置GraphQL服务等步骤。同时,文章还探讨了常见问题及其解决方法,如处理复杂查询、错误处理、性能优化和实现认证授权等,旨在帮助开发者构建灵活且高效的API。
27 3
|
3月前
|
开发框架 .NET API
Windows Forms应用程序中集成一个ASP.NET API服务
Windows Forms应用程序中集成一个ASP.NET API服务
111 9
|
4月前
|
开发框架 前端开发 JavaScript
ASP.NET MVC 教程
ASP.NET 是一个使用 HTML、CSS、JavaScript 和服务器脚本创建网页和网站的开发框架。
51 7
|
5月前
|
jenkins 测试技术 持续交付
解锁.NET项目高效秘籍:从理论迷雾到实践巅峰,持续集成与自动化测试如何悄然改变游戏规则?
【8月更文挑战第28天】在软件开发领域,持续集成(CI)与自动化测试已成为提升效率和质量的关键工具。尤其在.NET项目中,二者的结合能显著提高开发速度并保证软件稳定性。本文将从理论到实践,详细介绍CI与自动化测试的重要性,并以ASP.NET Core Web API项目为例,演示如何使用Jenkins和NUnit实现自动化构建与测试。每次代码提交后,Jenkins自动触发构建流程,通过编译和运行NUnit测试确保代码质量。这种方式不仅节省了时间,还能快速发现并解决问题,推动.NET项目开发迈向更高水平。
54 8
|
4月前
|
存储 开发框架 前端开发
ASP.NET MVC 迅速集成 SignalR
ASP.NET MVC 迅速集成 SignalR
85 0
|
5月前
|
Java Spring UED
Spring框架的异常处理秘籍:打造不败之身的应用!
【8月更文挑战第31天】在软件开发中,异常处理对应用的稳定性和健壮性至关重要。Spring框架提供了一套完善的异常处理机制,包括使用`@ExceptionHandler`注解和配置`@ControllerAdvice`。本文将详细介绍这两种方式,并通过示例代码展示其具体应用。`@ExceptionHandler`可用于控制器类中的方法,处理特定异常;而`@ControllerAdvice`则允许定义全局异常处理器,捕获多个控制器中的异常。
60 0
|
5月前
|
Java Spring 传感器
AI 浪潮席卷,Spring 框架配置文件管理与环境感知,为软件稳定护航,你还在等什么?
【8月更文挑战第31天】在软件开发中,配置文件管理至关重要。Spring框架提供强大支持,便于应对不同环境需求,如电商项目的开发、测试与生产环境。它支持多种格式的配置文件(如properties和YAML),并能根据环境加载不同配置,如数据库连接信息。通过`@Profile`注解可指定特定环境下的配置生效,同时支持通过命令行参数或环境变量覆盖配置值,确保应用稳定性和可靠性。
75 0
|
5月前
|
Devops 持续交付 开发者
.NET自动化之旅:是Azure DevOps还是GitHub Actions能够打造完美的CI/CD流水线?
【8月更文挑战第28天】在现代软件开发中,持续集成(CI)与持续部署(CD)是提升代码质量和加速交付的关键实践。对于 .NET 应用,Azure DevOps 和 GitHub Actions 等工具可高效构建 CI/CD 流水线,提升开发效率并确保软件稳定可靠。Azure DevOps 提供一站式全流程管理,支持 YAML 定义的自动化构建、测试和部署;GitHub Actions 则以简洁灵活著称,适用于 .NET 项目的自动化流程。选择合适的工具可显著提高开发效率并确保高质量标准。
31 0