Zaleca się zwracać błędy w ASP.NET web API

mam obawy co do tego, że zwracamy błąd klienta.

zwracamy błąd natychmiast, rzucając HttpResponseException kiedy otrzymujemy błąd:

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

lub gromadzimy wszystkie błędy, a następnie wysyłamy do klienta:

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

to tylko przykład kodu, nie ma znaczenia ani błędy sprawdzania poprawności, ani błąd serwera, chciałbym tylko wiedzieć najlepszą praktykę, plusy i minusy każdego podejścia.


dla mnie ja zazwyczaj wysyłam temu HttpResponseException i zaznacz kod stanu odpowiednio w zależności od wyjątki, i jeśli wyjątek krytyczny lub nie, wykrywa, wyślę czy ja z powrotem HttpResponseException natychmiast.

na koniec dnia jego API wysyła z powrotem odpowiedzi, a nie reprezentacji, więc myślę, że go normalnie wysłać wiadomość z kodem wyjątku i statusu konsumenta. Obecnie nie trzeba gromadzić błędy i wysyłać je z powrotem, ponieważ większość wyjątków zwykle z powodu nieprawidłowych ustawień lub połączeń itp.

na przykład w mojej aplikacji polega na tym, że czasami klient poprosi o dane, ale nie ma żadnych dostępnych danych, więc rzucam użytkownika wyjątek noDataAvailableException i pozwalam mu bulgotać w aplikacji Web api, gdzie następnie w moim użytkownika filtrze, który przechwytuje go, wysyłając z powrotem odpowiedni komunikat wraz z właściwym kodem stanu.

nie Jestem na 100% przekonany, że najlepsza praktyka dla tego, ale to działa dla mnie obecnie jest tak, że to jest to, co ja wykonujący.

aktualizacja:

Z tego czasu, jak odpowiedziałem na to pytanie, było napisane kilka postów w blogu na ten temat:

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

(ma kilka nowych funkcji w nocnych złożeniach) http://blogs.msdn.com/b/youssefm/archive/2012/06/28/error-handling-in-asp-net-webapi.aspx

aktualizacja 2

aktualizacja naszego procesu obsługi błędów, mamy dwa przypadki:

  1. dla typowych błędów, takich jak: nie znaleziono lub nieprawidłowe parametry przekazywane do działania, zwracamy HttpResponseException, aby natychmiast przerwać przetwarzanie. Ponadto, dla błędów modelu w naszych działaniach będziemy słownik stanu modelu w Request.CreateErrorResponse rozszerzenie i zawiń go w HttpResponseException. Dodanie słownika stanu modelu prowadzi do listy wysłanych błędów modelu w treści odpowiedzi.

  2. dla błędów występujących na wyższych poziomach, błędów serwera, pozwalamy moczowego wyjątków w aplikacji sieci web API, tutaj mamy globalny filtr wyjątków, który patrzy na wyjątek, rejestruje go za pomocą elmah i stara się go zrozumieć, wyznaczając odpowiedni kod stanu http i odpowiednie przyjazne komunikat o błędzie jako ciała ponownie w HttpResponseException. Do wyjątków, które nie oczekujemy, klient otrzyma wewnętrzny serwer domyślnie 500 błąd, ale ogólny komunikat ze względów bezpieczeństwa.

aktualizacja 3

niedawno, po zebraniu web API 2, do wysyłania ogólnych błędów teraz używamy IHttpActionResult interfejs, specyficznie zbudowany w klasach, aby w systemie.Sieć.Http.Przestrzeń nazw wyników, takie jak NotFound, BadRequest, gdy są one odpowiednie, jeśli nie są one rozszerzają je, na przykład, wynik notfound z wzajemnych wiadomości:

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 jest bardzo uproszczone. Na przykład, następujący kod:

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

zwraca następujący zawartość w przeglądarce, gdy element nie znaleziono:

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

wniosek: nie wyrzucaj HTTP Error 500, jeśli nie ma katastrofalne błędy (np. wyjątek błąd WCF). Wybierz odpowiedni kod stanu HTTP, reprezentujący stan danych. (Zob. link apigee poniżej.)

linki:


wygląda na to, że nie masz więcej problemów z weryfikacją, niż z błędami/wyjątkami, więc powiem trochę o obu.

sprawdź

czynności kontrolera zazwyczaj muszą brać wejściowe modelu, gdzie kontrola ogłasza się bezpośrednio na modelu.

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

następnie można użyć ActionFilter to automatycznie wysyła wiadomości valiation z powrotem do klienta.

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

aby uzyskać więcej informacji o tym sprawdź http://ben.onfabrik.com/posts/automatic-modelstate-validation-in-aspnet-mvc

obsługa błędów

najlepiej jest zwrócić klientowi wiadomość, reprezentujący wyjątek, który się stało (z odpowiednim kodem stanu).

z pudełka, należy użyć Request.CreateErrorResponse(HttpStatusCode, message) jeśli chcesz ustawić komunikat. Jednak to wiąże się kod z Request obiekt, który nie trzeba robić.

zazwyczaj tworzę swój własny typ" bezpiecznego " wyjątek, który, jak się spodziewam, klient będzie wiedział, jak obsługiwać i zawijać wszystkie pozostałe ze wspólną błędem 500.

korzystanie z filtra działań dla obsługi wyjątków będzie wyglądać następująco:

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

następnie można go zarejestrować na całym świecie.

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

to mój własny typ wyjątku.

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; }
        }
    }
}

wyjątek przykład, który może rzucić mój API.

public class NotAuthenticatedException : ApiException
{
    public NotAuthenticatedException()
        : base(HttpStatusCode.Forbidden)
    {
    }
}

możesz wrzucić HttpResponseException

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

dla Web API 2 moje metody kolejno zwracają IHttpActionResult, więc używam...

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

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

można użyć niestandardowego ActionFilter w Web Api do sprawdzania poprawności modelu

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

}

zarejestrować klasa CustomAttribute w webApiConfig.cez konfig.Filtr.Add (new DRFValidationFilters ());


dom Manish Jainodpowiedź (który jest przeznaczony dla web API 2, który upraszcza rzeczy):

1) Użyć struktury sprawdzania w odpowiedzi jak najwięcej błędów. Struktury te mogą być również używane do odpowiedzi na żądania pochodzące z formularzy.

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) warstwa serwisu wrócą ValidationResults, niezależnie od działalności być z powodzeniem lub nie. Na przykład:

    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) sterownik API będzie zbudować odpowiedź na podstawie wyników funkcji obsługi

jeden z wariantów-umieścić praktycznie wszystkie parametry jako opcjonalne i przeprowadzić własny test, który zwraca bardziej sensownej odpowiedzi. Poza tym, zależy mi na tym, aby ani jeden wyjątek nie wykracza poza granice usługi.

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

użyj wbudowanego metoda" InternalServerError " (dostępny w ApiController):

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

Jeśli korzystasz z sieci ASP.NET API-interfejs 2, najprostszym sposobem jest użycie ApiController krótki sposób. To doprowadzi do BadRequestResult.

return BadRequest("message");

po prostu zaktualizować obecny stan ASP.NET WebAPI. Interfejs jest teraz nazywany IActionResult i realizacja nie zmienił:

[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
}

dla tych błędów, gdzie modelstate.isvalid jest false, ja zazwyczaj wysyłam błąd, ponieważ jest ona generowana jest kodem. Jego łatwe do zrozumienia dla dewelopera, który zużywa mój serwis. Ja zazwyczaj wysyłam wynik, za pomocą kodu poniżej.

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

To wysyła błąd klienta w formacie poniżej, który jest w zasadzie lista błędów:

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