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

2019-05-14 11:37发布

我对我们的错误返回到客户端的方式的担忧。

难道我们立即返回错误抛出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);
}

这仅仅是一个示例代码,也没关系无论是验证错误或服务器错误,我只是想知道最好的做法,每种方法的利弊。

Answer 1:

对我来说,我通常会发送回一个HttpResponseException和状态码取决于相应抛出的异常上。如果该异常是致命与否将决定我是否送回设置HttpResponseException马上。

在一天结束时它的API发回的响应,而不是意见,所以我觉得它的细发回消息,异常和状态代码提供给消费者。 我目前还不需要积累错误,并把他们送回作为最异常通常是由于不正确的参数或电话等。

在我的应用程序的一个例子是,有时客户会要求数据,但心不是任何可用的数据,所以我抛出一个自定义noDataAvailableException,让它泡到Web API的应用程序,其中,然后在我的自定义过滤器捕获它发回相关用正确的状态代码一起消息。

我对什么我们的最佳实践,但是这是为我工作目前因此多数民众赞成在做什么我不是100%肯定。

更新

因为我回答了这个问题几篇博客已经写了一篇:

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记录,并改掉到它的意义设置正确的HTTP状态代码和相关友好的错误因为身体再次在HttpResponseException消息。 对于我们没有预料客户会收到默认的500内部服务器错误异常,但一个通用的消息,由于安全方面的原因。

更新3

近日,拿起网页API 2,对于发回的一般错误后,我们现在使用的IHttpActionResult接口,特别是内置类在System.Web.Http.Results命名空间,如NOTFOUND,错误请求当他们配合,如果他们不我们扩展它们,例如用一个响应消息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);
    }
}


Answer 2:

的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"
}

建议:不要把HTTP错误500,除非出现灾难性错误(例如,WCF故障除外)。 挑选一个代表你的数据的状况相适应的HTTP状态代码。 (见下面的apigee链接)。

链接:

  • 在ASP.NET的Web API异常处理 (asp.net)和
  • 基于REST的API设计:有关错误是什么? (apigee.com)


Answer 3:

它看起来像您遇到验证更多的麻烦比错误/异常,所以我会说一些关于两者。

验证

控制器的操作一般应采取地方,验证直接宣布模型输入模式。

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对象,你不应该需要做的。

我通常会创建自己的类型为“安全”的例外,我希望客户会知道如何处理和包装所有其他与通用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)
    {
    }
}


Answer 4:

你可以抛出一个HttpResponseException

HttpResponseMessage response = 
    this.Request.CreateErrorResponse(HttpStatusCode.BadRequest, "your message");
throw new HttpResponseException(response);


Answer 5:

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

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

    return ResponseMessage(
        Request.CreateResponse(
            HttpStatusCode.BadRequest, 
            validationErrors));
}


Answer 6:

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

return BadRequest("message");


Answer 7:

您可以使用自定义ActionFilter在网页API来验证模型

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

}

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



Answer 8:

建立在Manish Jain的回答(这是指对Web API 2简化了的东西):

1)使用验证结构 ,以应对尽可能多的验证错误越好。 这些结构也可以用来应对来自未来的形式要求。

public class FieldError
{
    public String FieldName { get; set; }
    public String FieldMessage { get; set; }
}

// a result will be able to inform API client about some general error/information and details information (related to invalid parameter values etc.)
public class ValidationResult<T>
{
    public bool IsError { get; set; }

    /// <summary>
    /// validation message. It is used as a success message if IsError is false, otherwise it is an error message
    /// </summary>
    public string Message { get; set; } = string.Empty;

    public List<FieldError> FieldErrors { get; set; } = new List<FieldError>();

    public T Payload { get; set; }

    public void AddFieldError(string fieldName, string fieldMessage)
    {
        if (string.IsNullOrWhiteSpace(fieldName))
            throw new ArgumentException("Empty field name");

        if (string.IsNullOrWhiteSpace(fieldMessage))
            throw new ArgumentException("Empty field message");

        // appending error to existing one, if field already contains a message
        var existingFieldError = FieldErrors.FirstOrDefault(e => e.FieldName.Equals(fieldName));
        if (existingFieldError == null)
            FieldErrors.Add(new FieldError {FieldName = fieldName, FieldMessage = fieldMessage});
        else
            existingFieldError.FieldMessage = $"{existingFieldError.FieldMessage}. {fieldMessage}";

        IsError = true;
    }

    public void AddEmptyFieldError(string fieldName, string contextInfo = null)
    {
        AddFieldError(fieldName, $"No value provided for field. Context info: {contextInfo}");
    }
}

public class ValidationResult : ValidationResult<object>
{

}

2) 服务层将返回ValidationResult S,无论操作是成功还是失败。 例如:

    public ValidationResult DoSomeAction(RequestFilters filters)
    {
        var ret = new ValidationResult();

        if (filters.SomeProp1 == null) ret.AddEmptyFieldError(nameof(filters.SomeProp1));
        if (filters.SomeOtherProp2 == null) ret.AddFieldError(nameof(filters.SomeOtherProp2 ), $"Failed to parse {filters.SomeOtherProp2} into integer list");

        if (filters.MinProp == null) ret.AddEmptyFieldError(nameof(filters.MinProp));
        if (filters.MaxProp == null) ret.AddEmptyFieldError(nameof(filters.MaxProp));


        // validation affecting multiple input parameters
        if (filters.MinProp > filters.MaxProp)
        {
            ret.AddFieldError(nameof(filters.MinProp, "Min prop cannot be greater than max prop"));
            ret.AddFieldError(nameof(filters.MaxProp, "Check"));
        }

        // also specify a global error message, if we have at least one error
        if (ret.IsError)
        {
            ret.Message = "Failed to perform DoSomeAction";
            return ret;
        }

        ret.Message = "Successfully performed DoSomeAction";
        return ret;
    }

3)API控制器将基于服务功能结果构建响应

一种选择是把几乎所有的参数为可选,并执行自定义验证它返回一个更有意义的响应。 此外,我注意不要让任何异常超越的服务边界。

    [Route("DoSomeAction")]
    [HttpPost]
    public HttpResponseMessage DoSomeAction(int? someProp1 = null, string someOtherProp2 = null, int? minProp = null, int? maxProp = null)
    {
        try
        {
            var filters = new RequestFilters 
            {
                SomeProp1 = someProp1 ,
                SomeOtherProp2 = someOtherProp2.TrySplitIntegerList() ,
                MinProp = minProp, 
                MaxProp = maxProp
            };

            var result = theService.DoSomeAction(filters);
            return !result.IsError ? Request.CreateResponse(HttpStatusCode.OK, result) : Request.CreateResponse(HttpStatusCode.BadRequest, result);
        }
        catch (Exception exc)
        {
            Logger.Log(LogLevel.Error, exc, "Failed to DoSomeAction");
            return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, new HttpError("Failed to DoSomeAction - internal error"));
        }
    }


Answer 9:

使用内置的“InternalServerError”方法(在ApiController提供):

return InternalServerError();
//or...
return InternalServerError(new YourException("your message"));


Answer 10:

只是为了更新ASP.NET的WebAPI的当前状态。 该接口是现在所谓IActionResult和实施并没有太大的变化:

[JsonObject(IsReference = true)]
public class DuplicateEntityException : IActionResult
{        
    public DuplicateEntityException(object duplicateEntity, object entityId)
    {
        this.EntityType = duplicateEntity.GetType().Name;
        this.EntityId = entityId;
    }

    /// <summary>
    ///     Id of the duplicate (new) entity
    /// </summary>
    public object EntityId { get; set; }

    /// <summary>
    ///     Type of the duplicate (new) entity
    /// </summary>
    public string EntityType { get; set; }

    public Task ExecuteResultAsync(ActionContext context)
    {
        var message = new StringContent($"{this.EntityType ?? "Entity"} with id {this.EntityId ?? "(no id)"} already exist in the database");

        var response = new HttpResponseMessage(HttpStatusCode.Ambiguous) { Content = message };

        return Task.FromResult(response);
    }

    #endregion
}


Answer 11:

对于这些错误,其中modelstate.isvalid是假的,因为它是由代码抛出我通常发送错误。 它很容易理解谁在消费我的服务的开发者。 我一般用下面的代码发送的结果。

     if(!ModelState.IsValid) {
                List<string> errorlist=new List<string>();
                foreach (var value in ModelState.Values)
                {
                    foreach(var error in value.Errors)
                    errorlist.Add( error.Exception.ToString());
                    //errorlist.Add(value.Errors);
                }
                HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.BadRequest,errorlist);}

这将差错在下面的格式客户基本上是错误的列表:

    [  
    "Newtonsoft.Json.JsonReaderException: **Could not convert string to integer: abc. Path 'Country',** line 6, position 16.\r\n   
at Newtonsoft.Json.JsonReader.ReadAsInt32Internal()\r\n   
at Newtonsoft.Json.JsonTextReader.ReadAsInt32()\r\n   
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.ReadForType(JsonReader reader, JsonContract contract, Boolean hasConverter, Boolean inArray)\r\n   
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateObject(Object newObject, JsonReader reader, JsonObjectContract contract, JsonProperty member, String id)",

       "Newtonsoft.Json.JsonReaderException: **Could not convert string to integer: ab. Path 'State'**, line 7, position 13.\r\n   
at Newtonsoft.Json.JsonReader.ReadAsInt32Internal()\r\n   
at Newtonsoft.Json.JsonTextReader.ReadAsInt32()\r\n   
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.ReadForType(JsonReader reader, JsonContract contract, Boolean hasConverter, Boolean inArray)\r\n   
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateObject(Object newObject, JsonReader reader, JsonObjectContract contract, JsonProperty member, String id)"
    ]


文章来源: Best practice to return errors in ASP.NET Web API