好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

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

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

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

目录 
一、基本异常处理策略 
二、通过自定义Action处理异常 
三、通过配置的Error View处理异常 
四、自定义ActionInvoker:ExceptionActionInvoker 
五、自定义Controller:BaseController

一、基本异常处理策略

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

对于执行Controller的某个Action方法抛出的异常,我们会按照指定配置策略进行处理。我们可以采取日志记录、异常替换和封装这些常用的异常处理方式; 对于处理后的异常,如果异常处理策略规定需要将其抛出,则会自动重定向到与异常类型匹配的出错页面。我们会维护一个异常类型和Error View的匹配关系; 对于处理后的异常,如果异常处理策略规定 不 需要将其抛出,则会执行与当前Action操作相匹配的 错误处理Action 进行处理。异常处理Action方法默认采用“On{Action}Error”这样的命名规则,而当前上下文会与异常处理操作方法的参数进行绑定。除次之外,我们会设置当前ModelState的错误信息; 如果用户不曾定义相应的异常处理Action,依然采用“错误页面重定向”方式进行异常处理。 二、通过自定义Action处理异常

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

    1:   namespace  Artech.Mvc.ExceptionHandling.Models
    2:  {
    3:       public   class  LoginInfo
    4:      {
    5:          [Display(Name = "User Name" )]
    6:          [Required(ErrorMessage =  "User Name is manadatory!" )]
    7:           public   string  UserName { get; set; }
    8:   
    9:          [Display(Name =  "Password" )]
   10:          [DataType(DataType.Password)]
   11:          [Required(ErrorMessage =  "Password is manadatory!" )]
   12:           public   string  Password { get; set; }
   13:      }
   14:  }

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

    1:   public   class  AccountController : BaseController
    2:  {
    3:       public  AccountController()
    4:          :  base ( "myPolicy" )
    5:      { }
    6:   
    7:       public  ActionResult SignIn()
    8:      {
    9:           return  View( new  LoginInfo());
   10:      }
   11:      [HttpPost]
   12:       public  ActionResult SignIn(LoginInfo loginInfo)
   13:      {
   14:           if  (!ModelState.IsValid)
   15:          {
   16:               return   this .View( new  LoginInfo { UserName = loginInfo.UserName });
   17:          }
   18:   
   19:           if  (loginInfo.UserName !=  "Foo" )
   20:          {
   21:               throw   new  InvalidUserNameException();
   22:          }
   23:   
   24:           if  (loginInfo.Password !=  "password" )
   25:          {
   26:               throw   new  UserNamePasswordNotMatchException();
   27:          }
   28:   
   29:          ViewBag.Message =  "Authentication Succeeds!" ;
   30:           return   this .View( new  LoginInfo { UserName = loginInfo.UserName });
   31:      }
   32:   
   33:       public  ActionResult OnSignInError( string  userName)
   34:      {
   35:           return   this .View( new  LoginInfo { UserName = userName });
   36:      }
   37:  }

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

    1:  @model Artech.Mvc.ExceptionHandling.Models.LoginInfo
    2:  @{
    3:      ViewBag.Title = "SignIn";
    4:  }
    5:  @Html.ValidationSummary()
    6:  @if (ViewBag.Messages != null)
    7:  { 
    8:      @ViewBag.Messages
    9:  }
   10:  @using (Html.BeginForm())
   11:  { 
   12:      @Html.EditorForModel()
   13:       <  input   type  ="submit"   value  ="SignIn"   /> 
   14:  }

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

    1:   <  exceptionHandling  > 
    2:     <  exceptionPolicies  > 
    3:       <  add   name  ="myPolicy"  > 
    4:         <  exceptionTypes  > 
    5:           <  add   name  ="InvalidUserNameException"  
    6:                  type  ="Artech.Mvc.ExceptionHandling.Models.InvalidUserNameException, Artech.Mvc.ExceptionHandling" 
    7:                postHandlingAction  ="None"  > 
    8:             <  exceptionHandlers  > 
    9:               <  add   name  ="ErrorMessageSettingHandler" 
   10:                    type  ="Artech.Mvc.ExceptionHandling.ErrorMessageSettingHandler, Artech.Mvc.ExceptionHandling" 
   11:                    errorMessage  ="User name does not exist!"  /> 
   12:             </  exceptionHandlers  > 
   13:           </  add  > 
   14:           <  add   name  ="UserNamePasswordNotMatchException"  
   15:                  type  ="Artech.Mvc.ExceptionHandling.Models.UserNamePasswordNotMatchException, Artech.Mvc.ExceptionHandling" 
   16:                postHandlingAction  ="None"  > 
   17:             <  exceptionHandlers  > 
   18:               <  add   name  ="ErrorMessageSettingHandler" 
   19:                    type  ="Artech.Mvc.ExceptionHandling.ErrorMessageSettingHandler, Artech.Mvc.ExceptionHandling" 
   20:                    errorMessage  ="User name does not match password!"  /> 
   21:             </  exceptionHandlers  > 
   22:           </  add  >           
   23:         </  exceptionTypes  > 
   24:       </  add  > 
   25:     </  exceptionPolicies  > 
   26:   </  exceptionHandling  > 

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

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

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

    1:   <  exceptionHandling  > 
    2:     <  exceptionPolicies  > 
    3:       <  add   name  ="myPolicy"  > 
    4:         <  exceptionTypes  > 
    5:           <  add   name  ="InvalidUserNameException"   type  ="Artech.Mvc.ExceptionHandling.Models.InvalidUserNameException, Artech.Mvc.ExceptionHandling" 
    6:                postHandlingAction  ="ThrowNewException"  > 
    7:           ...
    8:           <  add   name  ="UserNamePasswordNotMatchException"   type  ="Artech.Mvc.ExceptionHandling.Models.UserNamePasswordNotMatchException, Artech.Mvc.ExceptionHandling" 
    9:                postHandlingAction  ="ThrowNewException"  > 
   10:            ...
   11:           </  add  >           
   12:         </  exceptionTypes  > 
   13:       </  add  > 
   14:     </  exceptionPolicies  > 
   15:   </  exceptionHandling  > 

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

    1:   <  artech.exceptionHandling  > 
    2:     <  add   exceptionType  ="Artech.Mvc.ExceptionHandling.Models.InvalidUserNameException, Artech.Mvc.ExceptionHandling" 
    3:           errorView  ="InvalideUserNameError"  /> 
    4:     <  add   exceptionType  ="Artech.Mvc.ExceptionHandling.Models.UserNamePasswordNotMatchException, Artech.Mvc.ExceptionHandling" 
    5:           errorView  ="UserNamePasswordNotMatchError"  /> 
    6:   </  artech.exceptionHandling  > 

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

    1:  @{
    2:      Layout = null;
    3:  }
    4:   <!  DOCTYPE   html  > 
    5:   <  html  > 
    6:   <  head  > 
    7:       <  title  > Error </  title  > 
    8:   </  head  > 
    9:   <  body  > 
   10:       <  p   style  ="color:Red; font-weight:bold"  > Sorry,the user name you specify does not exist! </  p  > 
   11:   </  body  > 
   12:   </  html  > 
   13:   
   14:  @{
   15:      Layout = null;
   16:  }
   17:   <!  DOCTYPE   html  > 
   18:   <  html  > 
   19:   <  head  > 
   20:       <  title  > Error </  title  > 
   21:   </  head  > 
   22:   <  body  > 
   23:       <  p   style  ="color:Red; font-weight:bold"  > Sorry, The password does not match the given user name! </  p  > 
   24:   </  body  > 
   25:   </  html  > 

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

四、自定义ActionInvoker:ExceptionActionInvoker

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

    1:   using  System;
    2:   using  System.Collections.Generic;
    3:   using  System.Linq;
    4:   using  System.Web;
    5:   using  System.Web.Mvc;
    6:   using  Artech.Mvc.ExceptionHandling.Configuration;
    7:   using  Microsoft.Practices.EnterpriseLibrary.Common.Configuration;
    8:   using  Microsoft.Practices.EnterpriseLibrary.ExceptionHandling;
    9:   namespace  Artech.Mvc.ExceptionHandling
   10:  {
   11:       public   class  ExceptionActionInvoker: ControllerActionInvoker
   12:      {
   13:           protected  ExceptionHandlingSettings ExceptionHandlingSettings{get;  private  set;}
   14:           protected   virtual  Func< string , HandleErrorInfo, ViewResult> GetErrorView { get;  private  set; }
   15:           public  ExceptionPolicyImpl ExceptionPolicy { get;  private  set; }
   16:           public  ExceptionActionInvoker( string  exceptionPolicy,Func< string , HandleErrorInfo, ViewResult> getErrorView)
   17:          {
   18:               this .ExceptionPolicy = EnterpriseLibraryContainer.Current.GetInstance<ExceptionPolicyImpl>(exceptionPolicy);
   19:               this .GetErrorView = getErrorView;
   20:               this .ExceptionHandlingSettings = ExceptionHandlingSettings.GetSection();
   21:          }
   22:   
   23:           public   override   bool  InvokeAction(ControllerContext controllerContext,  string  handleErrorActionName)
   24:          {
   25:              ExceptionContext exceptionContext = controllerContext  as  ExceptionContext;
   26:               if  ( null  == exceptionContext)
   27:              {
   28:                   throw   new  ArgumentException( "The controllerContext must be ExceptionContext!" ,  "controllerContext" );
   29:              }
   30:               try 
   31:              {
   32:                  exceptionContext.ExceptionHandled =  true ;
   33:                   if  ( this .ExceptionPolicy.HandleException(exceptionContext.Exception))
   34:                  {
   35:                      HandleRethrownException(exceptionContext);
   36:                  }
   37:                   else 
   38:                  {
   39:                       if  (ExceptionHandlingContext.Current.Errors.Count == 0)
   40:                      {
   41:                          ExceptionHandlingContext.Current.Errors.Add(exceptionContext.Exception.Message);
   42:                      }
   43:                      ControllerDescriptor controllerDescriptor =  this .GetControllerDescriptor(exceptionContext);
   44:                      ActionDescriptor handleErrorAction = FindAction(exceptionContext, controllerDescriptor, handleErrorActionName);
   45:                       if  ( null  != handleErrorAction)
   46:                      {
   47:                          IDictionary< string ,  object > parameters = GetParameterValues(controllerContext, handleErrorAction);
   48:                          exceptionContext.Result =  this .InvokeActionMethod(exceptionContext, handleErrorAction, parameters);
   49:                      }
   50:                       else 
   51:                      {
   52:                          HandleRethrownException(exceptionContext);
   53:                      }
   54:                  }
   55:                   return   true ;
   56:              }
   57:               catch  (Exception ex)
   58:              {
   59:                  exceptionContext.Exception = ex;
   60:                  HandleRethrownException(exceptionContext);
   61:                   return   true ;
   62:              }
   63:          }
   64:           protected   virtual   void  HandleRethrownException(ExceptionContext exceptionContext)
   65:          {
   66:               string  errorViewName =  this .GetErrorViewName(exceptionContext.Exception.GetType());
   67:               string  controllerName = ( string )exceptionContext.RouteData.GetRequiredString( "controller" );
   68:               string  action = ( string )exceptionContext.RouteData.GetRequiredString( "action" );
   69:              HandleErrorInfo handleErrorInfo =  new  HandleErrorInfo(exceptionContext.Exception, controllerName, action);
   70:              exceptionContext.Result =  this .GetErrorView(errorViewName, handleErrorInfo);
   71:          }
   72:           protected   string  GetErrorViewName(Type exceptionType)
   73:          {
   74:              ExceptionErrorViewElement element = ExceptionHandlingSettings.ExceptionErrorViews
   75:                  .Cast<ExceptionErrorViewElement>().FirstOrDefault(el=>el.ExceptionType == exceptionType);
   76:               if ( null  != element)
   77:              {
   78:                   return  element.ErrorView;
   79:              }
   80:               if ( null == element &&  null  != exceptionType.BaseType!=  null )
   81:              {
   82:                   return  GetErrorViewName(exceptionType.BaseType);
   83:              }
   84:               else 
   85:              {
   86:                   return   "Error" ;
   87:              }
   88:          }
   89:      }
   90:  }


五、自定义Controller:BaseController

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

    1:   using  System;
    2:   using  System.Web.Mvc;
    3:   namespace  Artech.Mvc.ExceptionHandling
    4:  {
    5:       public   abstract   class  BaseController : Controller
    6:      {
    7:           public  BaseController( string  exceptionPolicy)
    8:          {
    9:              Func< string , HandleErrorInfo, ViewResult> getErrorView = (viewName, handleErrorInfo) =>  this .View(viewName, handleErrorInfo);
   10:               this .ExceptionActionInvoker =  new  ExceptionActionInvoker(exceptionPolicy,getErrorView);
   11:          }
   12:           public  BaseController(ExceptionActionInvoker actionInvoker)
   13:          {
   14:               this .ExceptionActionInvoker = actionInvoker;
   15:          }
   16:   
   17:           public   virtual  ExceptionActionInvoker ExceptionActionInvoker { get;  private  set; }
   18:   
   19:           protected   virtual   string  GetHandleErrorActionName( string  actionName)
   20:          {
   21:               return   string .Format( "On{0}Error" , actionName);
   22:          }
   23:   
   24:           protected   override   void  OnException(ExceptionContext filterContext)
   25:          {
   26:               using  (ExceptionHandlingContextScope contextScope =  new  ExceptionHandlingContextScope(filterContext))
   27:              {
   28:                   string  actionName = RouteData.GetRequiredString( "action" );
   29:                   string  handleErrorActionName =  this .GetHandleErrorActionName(actionName);
   30:                   this .ExceptionActionInvoker.InvokeAction(filterContext, handleErrorActionName);
   31:                   foreach  (var error  in  ExceptionHandlingContext.Current.Errors)
   32:                  {
   33:                      ModelState.AddModelError(Guid.NewGuid().ToString() ,error.ErrorMessage);
   34:                  }
   35:              }
   36:          }
   37:      }
   38:  }

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

    1:   public   class  ExceptionHandlingContext
    2:  {
    3:      [ThreadStatic]
    4:       private   static  ExceptionHandlingContext current;
    5:   
    6:       public  ExceptionContext ExceptionContext { get;  private  set; }
    7:       public  ModelErrorCollection Errors { get;  private  set; }
    8:   
    9:       public  ExceptionHandlingContext(ExceptionContext exceptionContext)
   10:      {
   11:           this .ExceptionContext = exceptionContext;
   12:           this .Errors =  new  ModelErrorCollection();
   13:      }
   14:       public   static  ExceptionHandlingContext Current
   15:      {
   16:          get {  return  current; }
   17:          set { current =  value ; }
   18:      }
   19:  }

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

    1:  [ConfigurationElementType( typeof (ErrorMessageSettingHandlerData))]
    2:   public   class  ErrorMessageSettingHandler : IExceptionHandler
    3:  {
    4:       public   string  ErrorMessage { get;  private  set; }
    5:       public  ErrorMessageSettingHandler( string  errorMessage)
    6:      {
    7:           this .ErrorMessage = errorMessage;
    8:      }
    9:       public  Exception HandleException(Exception exception, Guid handlingInstanceId)
   10:      {
   11:           if  ( null  == ExceptionHandlingContext.Current)
   12:          {
   13:               throw   new  InvalidOperationException( "..." );
   14:          }
   15:   
   16:           if  ( string .IsNullOrEmpty( this .ErrorMessage))
   17:          {
   18:              ExceptionHandlingContext.Current.Errors.Add(exception.Message);
   19:          }
   20:           else 
   21:          {
   22:              ExceptionHandlingContext.Current.Errors.Add( this .ErrorMessage);
   23:          }
   24:           return  exception;
   25:      }
   26:  }  

 

作者: Artech  
出处: http://artech.cnblogs测试数据  
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

源代码从这里下载: https://files.cnblogs测试数据/artech/ExceptionHandling.mvc.rar

作者: Leo_wl

    

出处: http://HdhCmsTestcnblogs测试数据/Leo_wl/

    

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

版权信息

查看更多关于ASP.NET MVC下基于异常处理的完整解决方案的详细内容...

  阅读:53次