在ASP.NET Web API中返回错误的最佳实践

cuo*_*gle 364 c# rest 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) 
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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);
}
Run Code Online (Sandbox Code Playgroud)

这只是一个示例代码,无论是验证错误还是服务器错误都无关紧要,我只想了解最佳实践,每种方法的优缺点.

gdp*_*gdp 282

对我来说,我通常会发回一个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

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

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

  2. 对于更高层中发生的错误,服务器错误,我们让异常气泡到Web API应用程序,这里我们有一个全局异常过滤器查看异常,用elmah和trys记录它以理解它设置正确的http状态代码和相关的友好错误消息在HttpResponseException中再次作为正文.对于我们不期望的异常,客户端将收到默认的500内部服务器错误,但由于安全原因会收到通用消息.

更新3

近日,拿起网页API 2,对于发回的一般错误后,我们现在使用的IHttpActionResult接口,特别是内置类在System.Web.Http.Results命名空间,如NOTFOUND,错误请求当他们配合,如果他们不我们扩展它们,例如带有响应消息的未发现结果:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

  • @DanielLittle 重读他的问题。我引用:“这只是一个示例代码,验证错误或服务器错误都没有关系” (2认同)
  • @gdp 即便如此,它确实有两个组成部分,验证和异常,所以最好同时涵盖这两个部分。 (2认同)

Man*_*ain 174

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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"
}
Run Code Online (Sandbox Code Playgroud)

建议:除非发生灾难性错误(例如,WCF故障异常),否则不要抛出HTTP错误500.选择一个代表数据状态的相应HTTP状态代码.(见下面的apigee链接.)

链接:

  • 根据http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html,客户端错误是400级代码,服务器错误是500级代码.因此,在许多情况下,对于Web API而言,500错误代码可能非常合适,而不仅仅是"灾难性"错误. (8认同)
  • 我会更进一步,从DAL/Repo抛出一个ResourceNotFoundException,我在Web Api 2.2 ExceptionHandler中检查Type ResourceNotFoundException,然后返回"找不到id xxx的产品".这样,它通常锚定在架构中而不是每个动作. (3认同)
  • 您需要“使用 System.Net.Http;”才能显示“CreateResponse()”扩展方法。 (2认同)

Dan*_*tle 74

看起来你在验证方面遇到的问题多于错误/异常,所以我会对两者都说一点.

验证

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

public class Customer
{ 
    [Require]
    public string Name { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

然后,您可以使用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);
        }
    }
} 
Run Code Online (Sandbox Code Playgroud)

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

错误处理

最好将消息返回给客户端,代表发生的异常(带有相关的状态代码).

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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

然后你可以在全球注册它.

GlobalConfiguration.Configuration.Filters.Add(new ApiExceptionFilterAttribute());
Run Code Online (Sandbox Code Playgroud)

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

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; }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我的API可以抛出的示例异常.

public class NotAuthenticatedException : ApiException
{
    public NotAuthenticatedException()
        : base(HttpStatusCode.Forbidden)
    {
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 您能否添加一个如何使用ApiExceptionFilterAttribute类的示例? (2认同)

tar*_*nov 36

你可以抛出一个HttpResponseException

HttpResponseMessage response = 
    this.Request.CreateErrorResponse(HttpStatusCode.BadRequest, "your message");
throw new HttpResponseException(response);
Run Code Online (Sandbox Code Playgroud)


Mic*_*ick 22

对于Web API 2,我的方法始终返回IHttpActionResult,所以我使用...

public IHttpActionResult Save(MyEntity entity)
{
  ....

    return ResponseMessage(
        Request.CreateResponse(
            HttpStatusCode.BadRequest, 
            validationErrors));
}
Run Code Online (Sandbox Code Playgroud)


Fab*_*rts 17

如果您使用的是ASP.NET Web API 2,最简单的方法是使用ApiController Short-Method.这将导致BadRequestResult.

return BadRequest("message");
Run Code Online (Sandbox Code Playgroud)

  • 严格来说,对于模型验证错误,我使用接受 ModelState 对象的 BadRequest() 重载:`return BadRequest(ModelState);` (3认同)

Ger*_*ski 9

欢迎来到 2022 年!现在我们在 .NET 中有其他答案(自 ASP.NET Core 2.1 起)。看这篇文章:Using the ProblemDetails Class in ASP.NET Core Web API,其中作者解释了以下最佳实践:

  1. 如何实现标准IETF RFC 7807,它将“问题详细信息”定义为在 HTTP 响应中携带机器可读的错误详细信息的方式,以避免需要为 HTTP API 定义新的错误响应格式。
  2. 模型验证如何使用ProblemDetails类填充验证错误列表 - 一般规则问题的直接答案,是否在第一个错误后中断处理。

ProductDetails作为一个预告,如果我们使用和 多个错误,JSON 输出将如下所示:

在此输入图像描述


小智 5

您可以在 Web Api 中使用自定义 ActionFilter 来验证模型:

public class DRFValidationFilters : ActionFilterAttribute
{

    public override void OnActionExecuting(HttpActionContext actionContext)
    {
        if (!actionContext.ModelState.IsValid)
        {
            actionContext.Response = actionContext.Request
                .CreateErrorResponse(HttpStatusCode.BadRequest, actionContext.ModelState);

            //BadRequest(actionContext.ModelState);
        }
    }

    public override Task OnActionExecutingAsync(HttpActionContext actionContext,
        CancellationToken cancellationToken)
    {

        return Task.Factory.StartNew(() =>
        {
            if (!actionContext.ModelState.IsValid)
            {
                actionContext.Response = actionContext.Request
                    .CreateErrorResponse(HttpStatusCode.BadRequest, actionContext.ModelState);
            }
        });
    }

    public class AspirantModel
    {
        public int AspirantId { get; set; }
        public string FirstName { get; set; }
        public string MiddleName { get; set; }
        public string LastName { get; set; }
        public string AspirantType { get; set; }
        [RegularExpression(@"^\(?([0-9]{3})\)?[-. ]?([0-9]{3})[-. ]?([0-9]{4})$",
            ErrorMessage = "Not a valid Phone number")]
        public string MobileNumber { get; set; }
        public int StateId { get; set; }
        public int CityId { get; set; }
        public int CenterId { get; set; }


        [HttpPost]
        [Route("AspirantCreate")]
        [DRFValidationFilters]
        public IHttpActionResult Create(AspirantModel aspirant)
        {
            if (aspirant != null)
            {

            }
            else
            {
                return Conflict();
            }

            return Ok();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

在 webApiConfig.cs 中注册 CustomAttribute 类 config.Filters.Add(new DRFValidationFilters());