Programming

ASP.NET 웹 API에서 오류를 반환하는 모범 사례

procodes 2020. 2. 29. 15:38
반응형

ASP.NET 웹 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);
}

이것은 샘플 코드 일 뿐이며 유효성 검사 오류 또는 서버 오류는 중요하지 않습니다. 각 방법의 우수 사례, 장단점을 알고 싶습니다.


나를 위해 나는 보통 a를 다시 보내고 HttpResponseException예외에 따라 상태 코드를 설정하고 예외가 치명적인지 여부는 HttpResponseException즉시 다시 보낼지 여부를 결정합니다 .

하루가 끝나면 API가 응답이 아닌 뷰를 다시 보내므로 예외 및 상태 코드가있는 메시지를 소비자에게 다시 보내는 것이 좋습니다. 현재 대부분의 예외는 일반적으로 잘못된 매개 변수 또는 호출 등으로 인해 오류를 누적하고 다시 보낼 필요가 없습니다.

내 응용 프로그램의 예는 때때로 클라이언트가 데이터를 요청하지만 사용 가능한 데이터가 없기 때문에 사용자 정의 noDataAvailableException을 던지고 웹 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

최근에 Web API 2를 가져온 후 일반적인 오류를 다시 보내기 위해 IHttpActionResult 인터페이스, 특히 NotFound, BadRequest와 같은 System.Web.Http.Results 네임 스페이스 (예 : 적합하지 않은 경우)에 대한 내장 클래스를 사용합니다. 예를 들어 응답 메시지와 함께 알려지지 않은 결과를 확장하십시오.

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 오류 예외)가 없으면 HTTP 오류 500을 발생시키지 마십시오. 데이터 상태를 나타내는 적절한 HTTP 상태 코드를 선택하십시오. (아래의 apigee 링크를 참조하십시오.)

연결:


오류 / 예외보다 유효성 검사에 더 많은 문제가있는 것 같습니다. 둘 다에 대해 조금 말씀 드리겠습니다.

확인

컨트롤러 작업은 일반적으로 유효성 검사가 모델에서 직접 선언되는 입력 모델을 가져와야합니다.

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

그런 다음 ActionFilter자동으로 유효성 검사 메시지를 클라이언트로 다시 보내는를 사용할 수 있습니다 .

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

HttpResponseException을 던질 수 있습니다

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

Web API 2의 경우 내 메소드는 지속적으로 IHttpActionResult를 반환하므로 다음을 사용합니다.

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

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

ASP.NET Web API 2를 사용하는 경우 가장 쉬운 방법은 ApiController Short-Method를 사용하는 것입니다. 이로 인해 BadRequestResult가 발생합니다.

return BadRequest("message");

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

}

webApiConfig.cs에 CustomAttribute 클래스를 등록하십시오. config.Filters.Add (new DRFValidationFilters ());


에 구축 Manish Jain(일을 간단하게 웹 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) 작업의 성공 여부에 관계없이 서비스 계층ValidationResults 를 반환 합니다. 예 :

    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 Controller 는 서비스 기능 결과를 기반으로 응답을 구성합니다.

하나의 옵션은 사실상 모든 매개 변수를 선택 사항으로 지정하고보다 의미있는 응답을 리턴하는 사용자 정의 유효성 검증을 수행하는 것입니다. 또한 예외가 서비스 경계를 ​​벗어나지 않도록주의하고 있습니다.

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

내장 된 "InternalServerError"메소드를 사용하십시오 (ApiController에서 사용 가능).

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

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
}

modelstate.isvalid가 false 인 오류의 경우 일반적으로 코드에서 발생하는 오류를 보냅니다. 내 서비스를 사용하는 개발자에게는 이해하기 쉽습니다. 나는 일반적으로 아래 코드를 사용하여 결과를 보냅니다.

     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)"
    ]

참고 URL : https://stackoverflow.com/questions/10732644/best-practice-to-return-errors-in-asp-net-web-api



반응형