在ASP.NET Web API中返回错误的最佳做法

我担心我们将错误返回给客户端的方式。

当出现错误时,是否通过抛出HttpResponseException立即返回错误:

public void Post(Customer customer)
{
    if (string.IsNullOrEmpty(customer.Name))
    {
        throw new HttpResponseException("Customer Name cannot be empty", HttpStatusCode.BadRequest) 
    }
    if (customer.Accounts.Count == 0)
    {
         throw new HttpResponseException("Customer does not have any account", HttpStatusCode.BadRequest) 
    }
}

或者我们累积所有错误,然后发回客户端:

public void Post(Customer customer)
{
    List<string> errors = new List<string>();
    if (string.IsNullOrEmpty(customer.Name))
    {
        errors.Add("Customer Name cannot be empty"); 
    }
    if (customer.Accounts.Count == 0)
    {
         errors.Add("Customer does not have any account"); 
    }
    var responseMessage = new HttpResponseMessage<List<string>>(errors, HttpStatusCode.BadRequest);
    throw new HttpResponseException(responseMessage);
}

这只是一个示例代码,无论是验证错误还是服务器错误都无关紧要,我只想知道每种方法的最佳做法以及优缺点。


对我来说,我通常会发回一个HttpResponseException并相应地根据抛出的异常设置状态码,如果异常是致命的,将决定我是否立即发回HttpResponseException

在一天结束时它的API发送回应而不是视图,所以我认为可以向消费者发送带有异常和状态代码的消息。 我目前不需要累积错误并将它们发回,因为大多数例外通常是由于不正确的参数或调用等原因造成的。

我的应用程序的一个例子是,有时客户端会询问数据,但没有任何数据可用,所以我抛出一个自定义的noDataAvailableException并让它冒泡到web api应用程序,然后在我的自定义过滤器中捕获它发回相关消息以及正确的状态码。

我并不十分确定最佳做法是什么,但目前这对我来说是很有用的,所以即使我在做。

更新

由于我回答了这个问题,所以在这个主题上写了一些博客文章:

http://weblogs.asp.net/fredriknormen/archive/2012/06/11/asp-net-web-api-exception-handling.aspx

(这个在夜间版本中有一些新功能)http://blogs.msdn.com/b/youssefm/archive/2012/06/28/error-handling-in-asp-net-webapi.aspx

更新2

更新我们的错误处理流程,我们有两种情况:

  • 对于像找不到的一般错误,或传递给动作的无效参数,我们会返回HttpResponseException以立即停止处理。 此外,对于我们的操作中的模型错误,我们将把模型状态字典交给Request.CreateErrorResponse扩展,并将其包装在HttpResponseException中。 添加模型状态字典会生成响应正文中发送的模型错误列表。

  • 对于发生在更高层的错误,服务器错误,我们让异常冒泡到Web API应用程序,在这里我们有一个全局异常过滤器,它查看异常,用elmah记录它并尝试理解它,以设置正确的http状态码和一个相关友好的错误消息作为HttpResponseException中的主体。 对于我们并不期望客户端会收到默认500内部服务器错误的例外情况,但出于安全原因而使用通用消息。

  • 更新3

    最近,在拿起Web API 2之后,为了发回一般错误,我们现在使用IHttpActionResult接口,特别是System.Web.Http.Results命名空间中的内置类,比如NotFound,BadRequest,如果它们不合适扩展它们,例如一个没有找到响应消息的结果:

    public class NotFoundWithMessageResult : IHttpActionResult
    {
        private string message;
    
        public NotFoundWithMessageResult(string message)
        {
            this.message = message;
        }
    
        public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
        {
            var response = new HttpResponseMessage(HttpStatusCode.NotFound);
            response.Content = new StringContent(message);
            return Task.FromResult(response);
        }
    }
    

    ASP.NET Web API 2真的简化了它。 例如,下面的代码:

    public HttpResponseMessage GetProduct(int id)
    {
        Product item = repository.Get(id);
        if (item == null)
        {
            var message = string.Format("Product with id = {0} not found", id);
            HttpError err = new HttpError(message);
            return Request.CreateResponse(HttpStatusCode.NotFound, err);
        }
        else
        {
            return Request.CreateResponse(HttpStatusCode.OK, item);
        }
    }
    

    找不到该项目时,将以下内容返回给浏览器:

    HTTP/1.1 404 Not Found
    Content-Type: application/json; charset=utf-8
    Date: Thu, 09 Aug 2012 23:27:18 GMT
    Content-Length: 51
    
    {
      "Message": "Product with id = 12 not found"
    }
    

    建议:除非发生灾难性错误(例如WCF Fault Exception),否则不要抛出HTTP Error 500。 选择一个表示数据状态的适当的HTTP状态码。 (请参阅下面的apigee链接。)

    链接:

  • ASP.NET Web API(asp.net)和.NET中的异常处理
  • REST风格的API设计:错误呢? (apigee.com)

  • 看起来你在验证方面比在错误/异常方面遇到更多麻烦,所以我会稍微说一下。

    验证

    控制器操作通常应采用输入模型,直接在模型上声明验证。

    public class Customer
    { 
        [Require]
        public string Name { get; set; }
    }
    

    然后你可以使用一个ActionFilter自动发送Valiation消息回客户端。

    public class ValidationActionFilter : ActionFilterAttribute
    {
        public override void OnActionExecuting(HttpActionContext actionContext)
        {
            var modelState = actionContext.ModelState;
    
            if (!modelState.IsValid) {
                actionContext.Response = actionContext.Request
                     .CreateErrorResponse(HttpStatusCode.BadRequest, modelState);
            }
        }
    } 
    

    有关此检查的更多信息,请访问http://ben.onfabrik.com/posts/automatic-modelstate-validation-in-aspnet-mvc

    错误处理

    最好将消息返回给代表发生异常的客户端(使用相关的状态码)。

    如果你想指定一个消息Request.CreateErrorResponse(HttpStatusCode, message)你必须使用Request.CreateErrorResponse(HttpStatusCode, message) 。 但是,这将代码绑定到Request对象,您不应该这样做。

    我通常会创建自己的“安全”异常类型,我希望客户端知道如何处理和包装所有其他类型的通用500错误。

    使用动作过滤器处理异常将如下所示:

    public class ApiExceptionFilterAttribute : ExceptionFilterAttribute
    {
        public override void OnException(HttpActionExecutedContext context)
        {
            var exception = context.Exception as ApiException;
            if (exception != null) {
                context.Response = context.Request.CreateErrorResponse(exception.StatusCode, exception.Message);
            }
        }
    }
    

    然后你可以在全球注册。

    GlobalConfiguration.Configuration.Filters.Add(new ApiExceptionFilterAttribute());
    

    这是我的自定义异常类型。

    using System;
    using System.Net;
    
    namespace WebApi
    {
        public class ApiException : Exception
        {
            private readonly HttpStatusCode statusCode;
    
            public ApiException (HttpStatusCode statusCode, string message, Exception ex)
                : base(message, ex)
            {
                this.statusCode = statusCode;
            }
    
            public ApiException (HttpStatusCode statusCode, string message)
                : base(message)
            {
                this.statusCode = statusCode;
            }
    
            public ApiException (HttpStatusCode statusCode)
            {
                this.statusCode = statusCode;
            }
    
            public HttpStatusCode StatusCode
            {
                get { return this.statusCode; }
            }
        }
    }
    

    我的API可能抛出的示例异常。

    public class NotAuthenticatedException : ApiException
    {
        public NotAuthenticatedException()
            : base(HttpStatusCode.Forbidden)
        {
        }
    }
    
    链接地址: http://www.djcxy.com/p/45655.html

    上一篇: Best practice to return errors in ASP.NET Web API

    下一篇: PHP rest API returning JSON for webpages