65.9K
CodeProject 正在变化。 阅读更多。
Home

WebAPI 中的异常处理

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.88/5 (12投票s)

2014年2月26日

CPOL

4分钟阅读

viewsIcon

106636

downloadIcon

1821

在 ASP.NET WebApi 中实现异常处理的 3 种不同方法

引言

在本文中,我将尝试解释在 ASP.NET WebApi 中实现异常处理的 3 种不同方法。我假设开发人员对 WebApi 的工作原理有基本的了解。我将使用 Visual Studio 2013 IDE 进行开发。WebApi 将托管在自托管环境中。为了托管 WebApi 服务,我将使用管理员模式的控制台应用程序。需要管理员模式,因为托管机制需要访问您配置的端口。演示时,我们将使用 JSON 输出。

背景

在我正在进行的项目中,服务需要实现 WebApi 中的异常处理。因此,我决定评估 WebApi 中可用的异常处理机制。花了一天时间,我找到了三种实现异常处理的方法。它们是:

  • 方法内部的异常处理
  • 使用属性进行异常处理
  • 使用 IHttpActionInvoker 进行全局异常处理

Using the Code

首先,让我们设置必要的开发环境来开发一个包含所有可能场景的示例。以管理员模式打开 Visual Studio,然后使用控制台模板创建一个示例应用程序。要添加对所需程序集的引用,您可以通过 Nuget 包管理器轻松安装 Microsoft.AspNet.WebApi.SelfHost

成功安装 WebApi.SelfHost Nuget 后,添加以下命名空间,它们是示例应用程序所必需的。

using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http;
using System.Web.Http.Controllers;
using System.Web.Http.Filters; 

在不同的层,例如业务层、数据访问层,我倾向于创建自定义异常类。此类有助于我们理解错误的类型和层。当引发业务验证异常时,它也有助于我们发送自定义错误消息。让我们创建两个样本异常类(BusinessDataAccess),它们都派生自 CustomException

 public class CustomException : Exception
    {
        public virtual string ErrorCode { get; set; }
        public virtual string ErrorDescription { get; set; }
    } 
public class BusinessLayerException : CustomException
    {
       
        public BusinessLayerException(string errorCode, string errorDescription)
            : base()
        {
            base.ErrorCode = errorCode;
            base.ErrorDescription = errorDescription;
        }
    } 
public class DataLayerException : CustomException
    {
        public DataLayerException(string errorCode, string errorDescription)
            : base()
        {
            base.ErrorCode = errorCode;
            base.ErrorDescription = errorDescription;
        }
    } 

自定义异常类有两个属性:ErrorCodeErrorDescription,我们将在引发自定义异常时使用它们。

让我们添加一个新的控制器类,它将向外部进程公开我们的 Web 方法。出于演示目的,我们将有三个方法:

  • GetErrorMethod 将直接从方法返回自定义错误响应。
  • GetHandlerException 将引发业务异常,但在此处异常将由不同的机制处理。
  • 第三个方法 GetGlobalErrorMessage 用于创建内部服务器错误。

    public class TestController : System.Web.Http.ApiController
    {
        public HttpResponseMessage GetMethodError()
        {
            try
            {
                throw new Exception("Get Custom error message from Method");
            }
            catch (Exception Ex)
            {
                var errorMessagError 
                    = new System.Web.Http.HttpError(Ex.Message) { { "ErrorCode", 405 } };
 
                throw new 
                    HttpResponseException(ControllerContext.Request.CreateErrorResponse
                    (HttpStatusCode.MethodNotAllowed, errorMessagError));
            }
        }
 
        [MethodAttributeExceptionHandling]
        public HttpResponseMessage GetHandlerException()
        {
            throw new 
                BusinessLayerException("4001", 
                "Your account is in negative. please recharge");
        }
 
        public HttpResponseMessage GetGlobalErrorMessage()
        {
            int i = 0;
            var val = 10 / i;
            return new 
                HttpResponseMessage(HttpStatusCode.OK);
        }
    } 

GetMethodError 在内部实现 try catch 块。此方法处理内部生成的任何错误。在生成任何错误的情况下,它将返回 HttpResponseException。建议在使用 HttpError 返回异常时也使用它。

 catch (Exception Ex)
            {
                var errorMessagError 
                    = new System.Web.Http.HttpError(Ex.Message) { { "ErrorCode", 405 } };

                throw new 
                    HttpResponseException
                    (ControllerContext.Request.CreateErrorResponse(HttpStatusCode.MethodNotAllowed, errorMessagError));
            } 

现在我们需要托管我们的服务,以便其他进程可以访问这些方法。托管再次分为两个部分:配置和托管机制。对于配置,我创建了一个单独的 static 类,以便所有配置逻辑都属于同一代码块。

public static class WebApiConfig
{
 public static void Register(System.Web.Http.HttpConfiguration config)
  {
     //Route Configuration
     config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{action}"
     );
            
     //Only JSON OUPUT
     var appXmlType = config.Formatters.XmlFormatter.SupportedMediaTypes.FirstOrDefault
     (t => t.MediaType == "application/xml");
     config.Formatters.XmlFormatter.SupportedMediaTypes.Remove(appXmlType);
  }
} 

自托管将从 Program.Main 方法进行。代码如下:

public static void Main()
{
   var config = new System.Web.Http.SelfHost.HttpSelfHostConfiguration("https://:8080");
   WebApiConfig.Register(config);
 
   using (var server = new System.Web.Http.SelfHost.HttpSelfHostServer(config))
   {
        server.OpenAsync().Wait();
        Console.WriteLine("Press Enter to quit.");
        Console.ReadLine();
   }
} 

从 URL 获取的输出:https://:8080/api/test/getMethodError 如下:

{"Message":"Get Custom error message from Method","ErrorCode":405} 

GetHandlerException 触发业务异常。如果您注意到,在此方法的顶部,我添加了一个属性(MethodAttributeExceptionHandling),它负责处理由此方法引发的任何异常。此属性是我们创建的继承自 ExceptionFilterAttribute 的自定义属性。我们覆盖了 ExceptionFilterAttribute 中的 OnException 方法。在引发错误后,此方法将执行。要获取异常详细信息,您可以访问对象 actionExecutedContext.Exception

[MethodAttributeExceptionHandling]
public HttpResponseMessage GetHandlerException()
{
   throw new 
         BusinessLayerException("4001", "Your account is in negative. please recharge");
 
}  

MethodAttributeExceptionHandling 的代码如下:

public class MethodAttributeExceptionHandling : ExceptionFilterAttribute
{
 
        public override void OnException(HttpActionExecutedContext actionExecutedContext)
        {
            if (actionExecutedContext.Exception is BusinessLayerException)
            {
                var businessException = actionExecutedContext.Exception as BusinessLayerException;
                var errorMessagError = new System.Web.Http.HttpError(businessException.ErrorDescription) { { "ErrorCode", businessException.ErrorCode } };
                actionExecutedContext.Response =
                    actionExecutedContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest, errorMessagError);
 
            }
            else if (actionExecutedContext.Exception is DataLayerException)
            {
                var dataException = actionExecutedContext.Exception as DataLayerException;
                var errorMessagError = new System.Web.Http.HttpError(dataException.ErrorDescription) { { "ErrorCode", dataException.ErrorCode } };
                actionExecutedContext.Response =
                    actionExecutedContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest, errorMessagError);
 
            }
            else
            {
                var errorMessagError = new System.Web.Http.HttpError("Oops some internal Exception. Please contact your administrator") { { "ErrorCode", 500 } };
                actionExecutedContext.Response =
                    actionExecutedContext.Request.CreateErrorResponse(HttpStatusCode.InternalServerError, errorMessagError);
            }
        }
}  

需要配置 MethodAttributeException 来处理异常。这可以通过将此对象添加到配置中来完成。

config.Filters.Add(new MethodAttributeExceptionHandling()); 

从 URL 获取的输出:https://:8080/api/test/GetHandlerException 如下:

{"Message":"Your account is in negative. please recharge","ErrorCode":4001} 

现在,如果我们想要一个全局异常包装器怎么办?这可以通过添加继承自 ApiControllerActionInvoker 的自定义类来完成。我创建了一个自定义类 CustomApiControllerActionInvoker,它覆盖了 InvokeActionAsync 方法。这里的逻辑与我们在 MethodAttributeExceptionHandling 中所做的类似。我们需要更改访问异常及其返回类型的方式。在这里,我们将以集合的形式获得异常,并且需要以任务的形式返回 HttpResponseMessage。

public class CustomApiControllerActionInvoker : ApiControllerActionInvoker
    {
        public override Task<HttpResponseMessage> InvokeActionAsync(HttpActionContext actionContext, CancellationToken cancellationToken)
        {
            var result = base.InvokeActionAsync(actionContext, cancellationToken);
 
            if (result.Exception != null && result.Exception.GetBaseException() != null)
            {
                var baseException = result.Exception.InnerExceptions[0];//result.Exception.GetBaseException();

                if (baseException is BusinessLayerException)
                {
                    var baseExcept = baseException as BusinessLayerException;
                    var errorMessagError = new System.Web.Http.HttpError(baseExcept.ErrorDescription) 
                    { { "ErrorCode", baseExcept.ErrorCode } };
                    return Task.Run<HttpResponseMessage>(() => 
                    actionContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest, errorMessagError));
                }
                else if (baseException is DataLayerException)
                {
                    var baseExcept = baseException as DataLayerException;
                    var errorMessagError = new System.Web.Http.HttpError(baseExcept.ErrorDescription) 
                    { { "ErrorCode", baseExcept.ErrorCode } };
                    return Task.Run<HttpResponseMessage>(() => 
                    actionContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest, errorMessagError));
                }
                else
                {
                    var errorMessagError = new System.Web.Http.HttpError
                    ("Oops some internal Exception. Please contact your administrator") 
                    { { "ErrorCode", 500 } };
                    return Task.Run<HttpResponseMessage>(() => 
                    actionContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest, errorMessagError));
                }
            }
 
            return base.InvokeActionAsync(actionContext, cancellationToken);
        }
    } 

需要使用服务来配置 CustomApiControllerActionInvoker。这可以通过从服务替换 IHttpActionInvoker 来完成。

config.Services.Replace(typeof(IHttpActionInvoker), new CustomApiControllerActionInvoker());  

请注意,一旦我们替换了 IHttpActionInvoker,MethodAttributeExceptionHandling 逻辑将无法正常工作。

从 URL 获取的输出:https://:8080/api/test/GetGlobalErrorMessage 如下:

{"Message":"Oops some internal Exception. Please contact your administrator","ErrorCode":500} 

完整的代码块将如下所示:

using System;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;
 
namespace TempTestConsoleApplication
{
    class Program
    {
        public static void Main()
        {
            var config = new System.Web.Http.SelfHost.HttpSelfHostConfiguration("https://:8080");
            WebApiConfig.Register(config);
 
            using (var server = new System.Web.Http.SelfHost.HttpSelfHostServer(config))
            {
                server.OpenAsync().Wait();
                Console.WriteLine("Press Enter to quit.");
                Console.ReadLine();
            } 
        }
    }
 
    public class CustomException : Exception
    {
        public virtual string ErrorCode { get; set; }
        public virtual string ErrorDescription { get; set; }
    }
 
    public class BusinessLayerException : CustomException
    {
       
        public BusinessLayerException(string errorCode, string errorDescription)
            : base()
        {
            base.ErrorCode = errorCode;
            base.ErrorDescription = errorDescription;
        }
    }
 
    public class DataLayerException : CustomException
    {
        public DataLayerException(string errorCode, string errorDescription)
            : base()
        {
            base.ErrorCode = errorCode;
            base.ErrorDescription = errorDescription;
        }
    }
 
    public static class WebApiConfig
    {
        public static void Register(System.Web.Http.HttpConfiguration config)
        {
            //Route Configuration
            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{action}"
            );
            
            config.Filters.Add(new MethodAttributeExceptionHandling());
            config.Services.Replace(typeof(IHttpActionInvoker), new CustomApiControllerActionInvoker());
 
            //Only JSON OUPUT
            var appXmlType = config.Formatters.XmlFormatter.SupportedMediaTypes.
            FirstOrDefault(t => t.MediaType == "application/xml");
            config.Formatters.XmlFormatter.SupportedMediaTypes.Remove(appXmlType);
        }
    }
 
    public class MethodAttributeExceptionHandling : ExceptionFilterAttribute
    {
 
        public override void OnException(HttpActionExecutedContext actionExecutedContext)
        {
            if (actionExecutedContext.Exception is BusinessLayerException)
            {
                var businessException = actionExecutedContext.Exception as BusinessLayerException;
                var errorMessagError = new System.Web.Http.HttpError(businessException.ErrorDescription) 
                { { "ErrorCode", businessException.ErrorCode } };
                actionExecutedContext.Response =
                    actionExecutedContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest, errorMessagError);
 
            }
            else if (actionExecutedContext.Exception is DataLayerException)
            {
                var dataException = actionExecutedContext.Exception as DataLayerException;
                var errorMessagError = new System.Web.Http.HttpError(dataException.ErrorDescription) 
                { { "ErrorCode", dataException.ErrorCode } };
                actionExecutedContext.Response =
                    actionExecutedContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest, errorMessagError);
 
            }
            else
            {
                var errorMessagError = new System.Web.Http.HttpError("Oops some internal Exception. 
                Please contact your administrator") { { "ErrorCode", 500 } };
                actionExecutedContext.Response =
                    actionExecutedContext.Request.CreateErrorResponse
                    	(HttpStatusCode.InternalServerError, errorMessagError);
            }
        }
    }
 
    public class CustomApiControllerActionInvoker : ApiControllerActionInvoker
    {
        public override Task<HttpResponseMessage> 
        InvokeActionAsync(HttpActionContext actionContext, CancellationToken cancellationToken)
        {
            var result = base.InvokeActionAsync(actionContext, cancellationToken);
 
            if (result.Exception != null && result.Exception.GetBaseException() != null)
            {
                var baseException = result.Exception.InnerExceptions[0];//result.Exception.GetBaseException();

                if (baseException is BusinessLayerException)
                {
                    var baseExcept = baseException as BusinessLayerException;
                    var errorMessagError = new System.Web.Http.HttpError
                    (baseExcept.ErrorDescription) { { "ErrorCode", baseExcept.ErrorCode } };
                    return Task.Run<HttpResponseMessage>(() => 
                    actionContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest, errorMessagError));
                }
                else if (baseException is DataLayerException)
                {
                    var baseExcept = baseException as DataLayerException;
                    var errorMessagError = new System.Web.Http.HttpError
                    (baseExcept.ErrorDescription) { { "ErrorCode", baseExcept.ErrorCode } };
                    return Task.Run<HttpResponseMessage>(() => 
                    actionContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest, errorMessagError));
                }
                else
                {
                    var errorMessagError = new System.Web.Http.HttpError
                    ("Oops some internal Exception. Please contact your administrator") 
                    { { "ErrorCode", 500 } };
                    return Task.Run<HttpResponseMessage>(() => 
                    actionContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest, errorMessagError));
                }
            }
 
            return base.InvokeActionAsync(actionContext, cancellationToken);
        }
    }
 
    public class TestController : System.Web.Http.ApiController
    {
        public HttpResponseMessage GetMethodError()
        {
            try
            {
                throw new Exception("Get Custom error message from Method");
            }
            catch (Exception Ex)
            {
                var errorMessagError 
                    = new System.Web.Http.HttpError(Ex.Message) { { "ErrorCode", 405 } };
 
                throw new 
                    HttpResponseException(ControllerContext.Request.CreateErrorResponse
                    (HttpStatusCode.MethodNotAllowed, errorMessagError));
            }
        }
 
        [MethodAttributeExceptionHandling]
        public HttpResponseMessage GetHandlerException()
        {
            throw new 
                BusinessLayerException("4001", 
                "Your account is in negative. please recharge");
 
        }
 
        public HttpResponseMessage GetGlobalErrorMessage()
        {
            int i = 0;
            var val = 10 / i;
            return new 
                HttpResponseMessage(HttpStatusCode.OK);
        }
    }
}  

关注点

在上面的示例中,我展示了三种处理 WebApi 中异常的不同方法。这三种方法是:

  1. 方法内部的异常处理
  2. 使用属性进行异常处理
  3. 使用 IHttpActionInvoker 进行全局异常处理

您还可以继续评估 Delegate Handlers 以实现异常处理。

参考

© . All rights reserved.