Home  >  Article  >  Backend Development  >  Solution to EntLib exception handling under ASP.NET

Solution to EntLib exception handling under ASP.NET

Y2J
Y2JOriginal
2017-05-08 09:21:191523browse

ASP.NET MVC is a very extensible development framework. In this article, I will integrate it with EntLib through its extension and provide a complete solution for exception handling.

EntLib's Exception Handling Application Block is a good exception handling framework that allows us to define exception handling strategies through configuration. ASP.NET MVC is a very extensible development framework. In this article, I will integrate it with EntLib through its extension and provide a complete exception handling solution.

1. Basic exception handling strategy

Let’s first discuss the specific exception handling strategy adopted by our solution:

For exceptions thrown by executing a certain Action method of the Controller, we will handle it according to the specified configuration strategy. We can adopt common exception handling methods such as logging, exception replacement and encapsulation;

For handled exceptions, if the exception handling policy stipulates that they need to be thrown, they will be automatically redirected to the Error page matching exception type. We will maintain a matching relationship between the exception type and the Error View;

For the handled exception, if the exception handling policy stipulates that it does not need to be thrown, an operation matching the current Action operation will be executed. Error handlingAction to handle. The exception handling Action method uses the naming rule "On{Action}Error" by default, and the current context will be bound to the parameters of the exception handling action method. In addition to the times, we will set the error message of the current ModelState;

If the user has not defined the corresponding exception handling Action, the "error message" will still be used Page redirection" method for exception handling.

2. Handle exceptions through custom Action

In order to give readers a deep understanding of the exception handling page introduced above, let’s Conduct a example demonstration. This instance is used to simulate user login. We define the following Model that only contains two attributes: username and password: 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; }
    }
  }

We define the following AccountController, which is a subclass of our custom BaseController. AccountController calls the base class constructor when constructing. The specified parameters represent the configuration name of the exception handling strategy. The SignIn method represents the "login" operation, and OnSignInError represents the exception handling operation corresponding to the operation. If the exception thrown in the SignIn operation is handled and no longer needs to be thrown, OnSignInError will be called, and the ModelState has been set with the corresponding error message.

  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 });
    }
  }

The authentication logic specifically defined in the SignIn operation method is as follows: if the user name is not "Foo", an InvalidUserNameException exception is thrown; if the password is not "password", a UserNamePasswordNotMatchException exception is thrown. The following is the definition of the View corresponding to the SignIn operation:

  @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" />
  }

The exception handling policy "myPolicy" specified when the AccountController is initialized is defined in the following configuration. We specifically handle the InvalidUserNameException and UserNamePasswordNotMatchException thrown by the SignIn operation method, and the ErrorMessageSettingHandler is our custom exception handler, which is only used to set the error message. As shown in the code snippet below, if the above two types of exceptions are thrown, the final error messages will be specified as "User name does not exist!" and "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>

Now we set AccountController and Sign as the default Controller and Action through routingmapping, and then start our application. If you enter an incorrect username and incorrect password, you will automatically get the corresponding error message in the ValidationSummary.

3. Handling exceptions through the configured Error View

In the above configuration, for the two exceptions InvalidUserNameException and UserNamePasswordNotMatchException The configuration strategy of the type all sets the PostHandlingAction attribute to "None", which means that the original exception and the handled exception will not be re-thrown. Now we set this property to "ThrowNewException", which means we will rethrow the handled exception.


  <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;
    }
  }

【相关推荐】

1.ASP免费视频教程

2.ASP教程

3.李炎恢ASP基础视频教程

The above is the detailed content of Solution to EntLib exception handling under ASP.NET. For more information, please follow other related articles on the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn