468 Stimmen

Bewährte Verfahren zur Fehlerrückgabe in ASP.NET Web API

Ich habe Bedenken hinsichtlich der Art und Weise, wie wir Fehler an den Kunden zurückgeben.

Geben wir einen Fehler sofort zurück, indem wir HttpResponseException wenn wir einen Fehler erhalten:

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

Oder wir sammeln alle Fehler und senden sie an den Kunden zurück:

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

Dies ist nur ein Beispiel-Code, es spielt keine Rolle, entweder Validierung Fehler oder Server-Fehler, ich möchte nur die beste Praxis, die Vor- und Nachteile der einzelnen Ansatz zu kennen.

25voto

Mick Punkte 6128

Für Web API 2 meine Methoden konsequent zurückgeben IHttpActionResult so ich verwenden...

public IHttpActionResult Save(MyEntity entity)
{
    ....
    if (...errors....)
        return ResponseMessage(
            Request.CreateResponse(
                HttpStatusCode.BadRequest, 
                validationErrors));

    // otherwise success
    return Ok(returnData);
}

10voto

Willkommen im Jahr 2022! Jetzt haben wir andere Antworten in .NET (seit ASP.NET Core 2.1). Sehen Sie sich diesen Artikel an: Verwendung der ProblemDetails-Klasse in ASP.NET Core Web API in dem der Autor die folgenden bewährten Verfahren erläutert:

  1. Wie man den Standard umsetzt IETF RFC 7807 die ein "Problemdetail" als eine Möglichkeit definiert, maschinenlesbare Details von Fehlern in einer HTTP-Antwort zu übertragen, um die Notwendigkeit zu vermeiden, neue Fehlerantwortformate für HTTP-APIs zu definieren.
  2. Wie die Modellvalidierung die ProblemDetails um eine Liste von Validierungsfehlern zu erstellen - die direkte Antwort auf die Frage, ob die Verarbeitung nach dem ersten Fehler abgebrochen werden soll.

So sieht die JSON-Ausgabe aus, wenn wir ProductDetails und mehrere Fehler:

enter image description here

5voto

LokeshChikkala Punkte 51

Sie können benutzerdefinierte ActionFilter in Web Api verwenden, um das Modell zu validieren:

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

Registrieren der Klasse CustomAttribute in webApiConfig.cs config.Filters.Add(new DRFValidationFilters());

4voto

Aufbauend auf Manish Jain Antwort (die für Web API 2 gedacht ist, was die Sache vereinfacht):

1) Verwendung Validierungsstrukturen so viele Validierungsfehler wie möglich zu beantworten. Diese Strukturen können auch verwendet werden, um auf Anfragen aus Formularen zu reagieren.

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) Dienstebene が返ってきます。 ValidationResult s, unabhängig davon, ob die Operation erfolgreich war oder nicht. z.B:

    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 wird die Antwort auf der Grundlage des Ergebnisses der Servicefunktion konstruieren

Eine Möglichkeit besteht darin, praktisch alle Parameter als optional einzustufen und eine benutzerdefinierte Validierung durchzuführen, die eine aussagekräftigere Antwort liefert. Außerdem achte ich darauf, dass keine Ausnahme über die Dienstgrenze hinausgeht.

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

3voto

Rusty Punkte 109

Verwenden Sie die eingebaute Methode "InternalServerError" (verfügbar in ApiController):

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

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X