ASP.NET MVC下基于异常处理的完整解决方案总结

EntLib的异常处理应用块(Exception Handling Application Block)是一个不错的异常处理框架,它使我们可以采用配置的方式来定义异常处理策略。而ASP.NET MVC是一个极具可扩展开发框架,在这篇文章中我将通过它的扩展实现与EntLib的集成,并提供一个完整的解决异常处理解决方案。

一、基本异常处理策略

我们首先来讨论我们的解决方案具体采用的异常处理策略:

对于执行Controller的某个Action方法抛出的异常,我们会按照指定配置策略进行处理。我们可以采取日志记录、异常替换和封装这些常用的异常处理方式;

对于处理后的异常,如果异常处理策略规定需要将其抛出,则会自动重定向到与异常类型匹配的出错页面。我们会维护一个异常类型和Error View的匹配关系;

对于处理后的异常,如果异常处理策略规定不需要将其抛出,则会执行与当前Action操作相匹配的错误处理Action进行处理。异常处理Action方法默认采用“On{Action}Error”这样的命名规则,而当前上下文会与异常处理操作方法的参数进行绑定。除次之外,我们会设置当前ModelState的错误信息;

如果用户不曾定义相应的异常处理Action,依然采用“错误页面重定向”方式进行异常处理。

二、通过自定义Action处理异常

为了让读者对上面介绍的异常处理页面有一个深刻的理解,我们来进行一个实例演示。该实例用于模拟用户登录,我们定义了如下一个只包含用户名和密码两个属性的Model:LoginInfoModel。

  namespace Artech.Mvc.ExceptionHandling.Models
   {
     public class LoginInfo
     {
       [Display(Name ="User Name")]
       [Required(ErrorMessage = "User Name is manadatory!")]
       public string UserName { get; set; }

       [Display(Name = "Password")]
      [DataType(DataType.Password)]
      [Required(ErrorMessage = "Password is manadatory!")]
      public string Password { get; set; }
    }
  }

我们定义了如下一个AccountController,它是我们自定义的BaseController的子类。AccountController在构造的时候调用基类构造函数指定的参数代表异常处理策略的配置名称。SignIn方法代表用于进行“登录”的操作,而OnSignInError就表示该操作对应的异常处理操作。如果在SignIn操作中抛出的异常经过处理后无需再抛出,则会通过调用OnSignInError,而此时ModelState已经被设置了相应的错误消息。

  public class AccountController BaseController
   {
     public AccountController()
       base("myPolicy")
     { }

     public ActionResult SignIn()
     {
       return View(new LoginInfo());
    }
    [HttpPost]
    public ActionResult SignIn(LoginInfo loginInfo)
    {
      if (!ModelState.IsValid)
      {
        return this.View(new LoginInfo { UserName = loginInfo.UserName });
      }

      if (loginInfo.UserName != "Foo")
      {
        throw new InvalidUserNameException();
      }

      if (loginInfo.Password != "password")
      {
        throw new UserNamePasswordNotMatchException();
      }

      ViewBag.Message = "Authentication Succeeds!";
      return this.View(new LoginInfo { UserName = loginInfo.UserName });
    }

    public ActionResult OnSignInError(string userName)
    {
      return this.View(new LoginInfo { UserName = userName });
    }
  }

具体定义在SignIn操作方法中的认证逻辑是这样的:如果用户名不是“Foo”则抛出InvalidUserNameException异常;如果密码不是“password”则抛出UserNamePasswordNotMatchException异常。下面是SignIn操作对应的View的定义:

  @model Artech.Mvc.ExceptionHandling.Models.LoginInfo
   @{
     ViewBag.Title = "SignIn";
   }
   @Html.ValidationSummary()
   @if (ViewBag.Messages != null)
   {
     @ViewBag.Messages
   }
  @using (Html.BeginForm())
  {
    @Html.EditorForModel()
    <input type="submit" value="SignIn" />
  }

在AccountController初始化时指定的异常处理策略“myPolicy”定义在如下的配置中。我们专门针对SignIn操作方法抛出的InvalidUserNameException和UserNamePasswordNotMatchException进行了处理,而ErrorMessageSettingHandler是我们自定义的异常处理器,它仅仅用于设置错误消息。如下面的代码片断所示,如果上述的这两种类型的异常被抛出,最终的错误消息会被指定为“User name does not exist!”和“User name does not match password!”。

  <exceptionHandling>
    <exceptionPolicies>
     <add name="myPolicy">
      <exceptionTypes>
       <add name="InvalidUserNameException"
          type="Artech.Mvc.ExceptionHandling.Models.InvalidUserNameException, Artech.Mvc.ExceptionHandling"
         postHandlingAction="None">
        <exceptionHandlers>
         <add name="ErrorMessageSettingHandler"
           type="Artech.Mvc.ExceptionHandling.ErrorMessageSettingHandler, Artech.Mvc.ExceptionHandling"
           errorMessage="User name does not exist!"/>
       </exceptionHandlers>
      </add>
      <add name="UserNamePasswordNotMatchException"
          type="Artech.Mvc.ExceptionHandling.Models.UserNamePasswordNotMatchException, Artech.Mvc.ExceptionHandling"
         postHandlingAction="None">
       <exceptionHandlers>
        <add name="ErrorMessageSettingHandler"
           type="Artech.Mvc.ExceptionHandling.ErrorMessageSettingHandler, Artech.Mvc.ExceptionHandling"
           errorMessage="User name does not match password!"/>
       </exceptionHandlers>
      </add>
     </exceptionTypes>
    </add>
   </exceptionPolicies>
  </exceptionHandling>

现在我们通过路由映射将AccountController和Sign设置为默认Controller和Action后,开启我们的应用程序。在输入错误的用户名和错误明码的情况下在ValidationSummary中将自动得到相应的错误消息。

三、通过配置的Error View处理异常

在上面的配置中,针对InvalidUserNameException和UserNamePasswordNotMatchException这两种异常类型的配置策略都将PostHandlingAction属性设置为“None”,意味着不会将原来的异常和处理后的异常进行重新抛出。现在我们将该属性设置为“ThrowNewException”,意味着我们会将处理后的异常重新抛出来。

  <exceptionHandling>
    <exceptionPolicies>
     <add name="myPolicy">
      <exceptionTypes>
       <add name="InvalidUserNameException" type="Artech.Mvc.ExceptionHandling.Models.InvalidUserNameException, Artech.Mvc.ExceptionHandling"
         postHandlingAction="ThrowNewException">
       ...
       <add name="UserNamePasswordNotMatchException" type="Artech.Mvc.ExceptionHandling.Models.UserNamePasswordNotMatchException, Artech.Mvc.ExceptionHandling"
         postHandlingAction="ThrowNewException">
       ...
      </add>
     </exceptionTypes>
    </add>
   </exceptionPolicies>
  </exceptionHandling>

按照我们上面的异常处理策略,在这种情况下我们将采用“错误页面”的方式来进行异常处理。也HandleErrorAttribute的处理方式类似,我们支持异常类型和Error View之间的匹配关系,而这是通过类似于如下的配置来定义的。值得一提的是,这里的异常类型是经过处理后重新抛出的异常。

  <artech.exceptionHandling>
    <add exceptionType="Artech.Mvc.ExceptionHandling.Models.InvalidUserNameException, Artech.Mvc.ExceptionHandling"
       errorView="InvalideUserNameError"/>
    <add exceptionType="Artech.Mvc.ExceptionHandling.Models.UserNamePasswordNotMatchException, Artech.Mvc.ExceptionHandling"
       errorView="UserNamePasswordNotMatchError"/>
   </artech.exceptionHandling>

如上面的配置所示,我们为InvalidUserNameException和UserNamePasswordNotMatchException这两种异常类型定义了不同的Error View,分别是“InvalideUserNameError”和“UserNamePasswordNotMatchError”,详细定义如下所示:

  @{
     Layout = null;
   }
   <!DOCTYPE html>
   <html>
   <head>
     <title>Error</title>
   </head>
   <body>
    <p style="colorRed; font-weightbold">Sorry,the user name you specify does not exist!</p>
  </body>
  </html>

  @{
    Layout = null;
  }
  <!DOCTYPE html>
  <html>
  <head>
    <title>Error</title>
  </head>
  <body>
    <p style="colorRed; font-weightbold">Sorry, The password does not match the given user name!</p>
  </body>
  </html>

现在我们按照上面的方式运行我们的程序,在分别输入错误的用户名和密码的情况下会自动显现相应的错误页面。

四、自定义ActionInvoker:ExceptionActionInvoker

对于上述的两种不同的异常处理方式最终是通过自定义的ActionInvoker来实现的,我们将其命名为ExceptionActionInvoker。如下面的代码片断所式,ExceptionActionInvoker直接继承自ControllerActionInvoker。属性ExceptionPolicy是一个基于指定的异常策略名称创建的ExceptionPolicyImpl 对象,用于针对EntLib进行的异常处理。而属性GetErrorView是一个用于获得作为错误页面的ViewResult对象的委托。整个异常处理的核心定义在InvokeAction方法中,该方法中指定的handleErrorActionName参数代表的是“异常处理操作名称”,整个方法就是按照上述的异常处理策略实现的。

  using System;
   using System.Collections.Generic;
   using System.Linq;
   using System.Web;
   using System.Web.Mvc;
   using Artech.Mvc.ExceptionHandling.Configuration;
   using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;
   using Microsoft.Practices.EnterpriseLibrary.ExceptionHandling;
   namespace Artech.Mvc.ExceptionHandling
  {
    public class ExceptionActionInvoker ControllerActionInvoker
    {
      protected ExceptionHandlingSettings ExceptionHandlingSettings{get; private set;}
      protected virtual Func<string, HandleErrorInfo, ViewResult> GetErrorView { get; private set; }
      public ExceptionPolicyImpl ExceptionPolicy { get; private set; }
      public ExceptionActionInvoker(string exceptionPolicy,Func<string, HandleErrorInfo, ViewResult> getErrorView)
      {
        this.ExceptionPolicy = EnterpriseLibraryContainer.Current.GetInstance<ExceptionPolicyImpl>(exceptionPolicy);
        this.GetErrorView = getErrorView;
        this.ExceptionHandlingSettings = ExceptionHandlingSettings.GetSection();
      }

      public override bool InvokeAction(ControllerContext controllerContext, string handleErrorActionName)
      {
        ExceptionContext exceptionContext = controllerContext as ExceptionContext;
        if (null == exceptionContext)
        {
          throw new ArgumentException("The controllerContext must be ExceptionContext!", "controllerContext");
        }
        try
        {
          exceptionContext.ExceptionHandled = true;
          if (this.ExceptionPolicy.HandleException(exceptionContext.Exception))
          {
            HandleRethrownException(exceptionContext);
          }
          else
          {
            if (ExceptionHandlingContext.Current.Errors.Count == 0)
            {
              ExceptionHandlingContext.Current.Errors.Add(exceptionContext.Exception.Message);
            }
            ControllerDescriptor controllerDescriptor = this.GetControllerDescriptor(exceptionContext);
            ActionDescriptor handleErrorAction = FindAction(exceptionContext, controllerDescriptor, handleErrorActionName);
            if (null != handleErrorAction)
            {
              IDictionary<string, object> parameters = GetParameterValues(controllerContext, handleErrorAction);
              exceptionContext.Result = this.InvokeActionMethod(exceptionContext, handleErrorAction, parameters);
            }
            else
            {
              HandleRethrownException(exceptionContext);
            }
          }
          return true;
        }
        catch (Exception ex)
        {
          exceptionContext.Exception = ex;
          HandleRethrownException(exceptionContext);
          return true;
        }
      }
      protected virtual void HandleRethrownException(ExceptionContext exceptionContext)
      {
        string errorViewName = this.GetErrorViewName(exceptionContext.Exception.GetType());
        string controllerName = (string)exceptionContext.RouteData.GetRequiredString("controller");
        string action = (string)exceptionContext.RouteData.GetRequiredString("action");
        HandleErrorInfo handleErrorInfo = new HandleErrorInfo(exceptionContext.Exception, controllerName, action);
        exceptionContext.Result = this.GetErrorView(errorViewName, handleErrorInfo);
      }
      protected string GetErrorViewName(Type exceptionType)
      {
        ExceptionErrorViewElement element = ExceptionHandlingSettings.ExceptionErrorViews
          .Cast<ExceptionErrorViewElement>().FirstOrDefault(el=>el.ExceptionType == exceptionType);
        if(null != element)
        {
          return element.ErrorView;
        }
        if(null== element && null != exceptionType.BaseType!= null)
        {
          return GetErrorViewName(exceptionType.BaseType);
        }
        else
        {
          return "Error";
        }
      }
    }
  }

五、自定义Controller:BaseController

ExceptionActionInvoker最终在我们自定义的Controller基类BaseController中被调用的。ExceptionActionInvoker对象在构造函数中被初始化,并在重写的OnException方法中被调用。

  using System;
   using System.Web.Mvc;
   namespace Artech.Mvc.ExceptionHandling
   {
     public abstract class BaseController Controller
     {
       public BaseController(string exceptionPolicy)
       {
         Func<string, HandleErrorInfo, ViewResult> getErrorView = (viewName, handleErrorInfo) => this.View(viewName, handleErrorInfo);
        this.ExceptionActionInvoker = new ExceptionActionInvoker(exceptionPolicy,getErrorView);
      }
      public BaseController(ExceptionActionInvoker actionInvoker)
      {
        this.ExceptionActionInvoker = actionInvoker;
      }

      public virtual ExceptionActionInvoker ExceptionActionInvoker { get; private set; }

      protected virtual string GetHandleErrorActionName(string actionName)
      {
        return string.Format("On{0}Error", actionName);
      }

      protected override void OnException(ExceptionContext filterContext)
      {
        using (ExceptionHandlingContextScope contextScope = new ExceptionHandlingContextScope(filterContext))
        {
          string actionName = RouteData.GetRequiredString("action");
          string handleErrorActionName = this.GetHandleErrorActionName(actionName);
          this.ExceptionActionInvoker.InvokeAction(filterContext, handleErrorActionName);
          foreach (var error in ExceptionHandlingContext.Current.Errors)
          {
            ModelState.AddModelError(Guid.NewGuid().ToString() ,error.ErrorMessage);
          }
        }
      }
    }
  }

值得一提的是:整个OnException方法中的操作都在一个ExceptionHandlingContextScope中进行的。顾名思义, 我们通过ExceptionHandlingContextScope为ExceptionHandlingContext创建了一个范围。ExceptionHandlingContext定义如下,我们可以通过它获得当前的ExceptionContext和ModelErrorCollection,而静态属性Current返回当前的ExceptionHandlingContext对象。

  public class ExceptionHandlingContext
   {
     [ThreadStatic]
     private static ExceptionHandlingContext current;

     public ExceptionContext ExceptionContext { get; private set; }
     public ModelErrorCollection Errors { get; private set; }

     public ExceptionHandlingContext(ExceptionContext exceptionContext)
    {
      this.ExceptionContext = exceptionContext;
      this.Errors = new ModelErrorCollection();
    }
    public static ExceptionHandlingContext Current
    {
      get { return current; }
      set { current = value; }
    }
  }

在BaseController的OnException方法中,当执行了ExceptionActionInvoker的InvokeAction之后,我们会将当前ExceptionHandlingContext的ModelError转移到当前的ModelState中。这就是为什么我们会通过ValidationSummary显示错误信息的原因。对于我们的例子来说,错误消息的指定是通过如下所示的ErrorMessageSettingHandler 实现的,而它仅仅将指定的错误消息添加到当前ExceptionHandlingContext的Errors属性集合中而已。

  [ConfigurationElementType(typeof(ErrorMessageSettingHandlerData))]
   public class ErrorMessageSettingHandler IExceptionHandler
   {
     public string ErrorMessage { get; private set; }
     public ErrorMessageSettingHandler(string errorMessage)
     {
       thisErrorMessage = errorMessage;
     }
     public Exception HandleException(Exception exception, Guid handlingInstanceId)
    {
      if (null == ExceptionHandlingContextCurrent)
      {
        throw new InvalidOperationException("");
      }

      if (stringIsNullOrEmpty(thisErrorMessage))
      {
        ExceptionHandlingContextCurrentErrorsAdd(exceptionMessage);
      }
      else
      {
        ExceptionHandlingContextCurrentErrorsAdd(thisErrorMessage);
      }
      return exception;
    }
  }

源代码从这里下载:http://xiazai.jb51.net/201701/yuanma/ExceptionHandling_jb51.rar

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • 总结十条.NET异常处理建议

    .NET中从始至终要紧记异常处理的策略:抛出具体的一个异常,而不是只抛出Exception类型的异常,这样能方便我们捕获对应类型的异常.我们在编写代码时要注意考虑到应用程序最差的情况:显示有好的信息,并提供适当的管理员联系信息. 1.不要抛出"new Exception()" 请别这样做.Exception是一个非常抽象的异常类,捕获这类异常通常会产生很多负面影响.通常情况下应该定义我们自己的异常类,并且需要区分系统抛出的异常和我们自己抛出的异常. 2.不要将重要的异常信息存储在Mes

  • ASP.NET MVC异常处理模块详解

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

  • .NET(C#):Emit创建异常处理的方法

    目录 Emit异常处理流程 显示Exception对象的Message属性 返回目录 Emit异常处理流程来看这种C#异常处理代码: 复制代码 代码如下: static void doo(Exception e) { try { throw e; } catch (ApplicationException ex) { Console.WriteLine("捕获ApplicationException"); } catch { Console.WriteLine("捕获Exce

  • 一些.NET对多线程异常处理技巧分享

    多线程环境 在我们的产品 SE 中,出现多线程的地方主要有两大类,一类是通过 ThreadPool 或 new Thread 主动发起多线程,另一类是 Socket 通讯回调. 多线程异常捕获 对于一般的异常处理来说,我们只要简单的将可能出错的语句包含在 try/catch 语句中即可.我也曾经简单的将该方法运用于多线程的异常捕获,结果并非如此,代码如下: 复制代码 代码如下: public static void Main() {     try     {         new Threa

  • ASP.NET Core异常和错误处理(8)

    在这一章,我们将讨论异常和错误处理.当 ASP.NET Core应用程序中发生错误时,您可以以各种不同的方式来处理.让我们来看看通过添加一个中间件来处理异常情况,这个中间件将帮助我们处理错误. 要模拟出错,让我们转到应用程序,运行,如果我们只是抛出异常的话,看看程序是如何运转转的. using Microsoft.AspNet.Builder; using Microsoft.AspNet.Hosting; using Microsoft.AspNet.Http; using Microsoft

  • 关于.NET异常处理的思考总结

    年关将至,对于大部分程序员来说,马上就可以闲下来一段时间了,然而在这个闲暇的时间里,唯有争论哪门语言更好可以消磨时光,估计最近会有很多关于java与.net的博文出现,我表示要作为一个吃瓜群众,静静的看着大佬们发表心情. 以上的废话说的够多了,这里就不再废话了,还是切入正题吧. 在项目开发中,对于系统和代码的稳定性和容错性都是有对应的要求.实际开发项目中的代码与样例代码的区别,更多的是在代码的运行的稳定性.容错性.扩展性的比较.因为对于实现一个功能来说,实现功能的核心代码是一样的,可能只是在写法

  • ASP.NET mvc异常处理的方法示例介绍

    1.首先常见保存异常的类(就是将异常信息写入到文件中去) 复制代码 代码如下: public class LogManager { private string logFilePath = string.Empty; public LogManager(string logFilePath) { this.logFilePath = logFilePath; FileInfo file = new FileInfo(logFilePath); if (!file.Exists) { file.C

  • 详谈.NET的异常处理

    年关将至,对于大部分程序员来说,马上就可以闲下来一段时间了,然而在这个闲暇的时间里,唯有争论哪门语言更好可以消磨时光,估计最近会有很多关于java与.net的博文出现,我表示要作为一个吃瓜群众,静静的看着大佬们发表心情. 以上的废话说的够多了,这里就不再废话了,还是切入正题吧. 在项目开发中,对于系统和代码的稳定性和容错性都是有对应的要求.实际开发项目中的代码与样例代码的区别,更多的是在代码的运行的稳定性.容错性.扩展性的比较.因为对于实现一个功能来说,实现功能的核心代码是一样的,可能只是在写法

  • .NET中的异常和异常处理用法分析

    本文较为详细的分析了.NET中的异常和异常处理用法.分享给大家供大家参考.具体分析如下: .NET中的异常(Exception) .net中的中异常的父类是Exception,大多数异常一般继承自Exception. 可以通过编写一个继承自Exception的类的方式,自定义异常类! 异常处理机制: 复制代码 代码如下: Try {     //可能发生异常的代码     //后续代码     } //Try以外的代码 catch(Exception e) { } finally { } 上述代

  • Asp.net Mvc 身份验证、异常处理、权限验证(拦截器)实现代码

    1.用户登录 验证用户是否登录成功步骤直接忽略,用户登录成功后怎么保存当前用户登录信息(session,cookie),本文介绍的是身份验证(其实就是基于cookie)的,下面看看代码. 引入命名空间 using System.Web.Security; 复制代码 代码如下: Users ModelUser = new Users() { ID = 10000, Name = UserName, UserName = UserName, PassWord = PassWord, Roles =

随机推荐