Wie gebe ich NotFound () IHttpActionResult mit einer Fehlermeldung oder Ausnahme zurück?


98

Ich gebe ein NotFound zurück IHttpActionResult, wenn in meiner WebApi GET-Aktion etwas nicht gefunden wurde. Zusammen mit dieser Antwort möchte ich eine benutzerdefinierte Nachricht und / oder die Ausnahmemeldung (falls vorhanden) senden. Die aktuellen ApiController‚s NotFound()Verfahren liefern nicht eine Überlast eine Nachricht zu übergeben.

Gibt es eine Möglichkeit, dies zu tun? oder muss ich meinen eigenen Brauch schreiben IHttpActionResult?


Möchten Sie dieselbe Nachricht für alle nicht gefundenen Ergebnisse zurückgeben?
Nikolai Samteladze

@NikolaiSamteladze Nein, es kann je nach Situation eine andere Nachricht sein.
Ajay Jadhav

Antworten:


84

Sie müssen Ihr eigenes Aktionsergebnis schreiben, wenn Sie die Form der Antwortnachricht anpassen möchten.

Wir wollten die gängigsten Formen von Antwortnachrichten für Dinge wie einfache leere 404s bereitstellen, aber wir wollten diese Ergebnisse auch so einfach wie möglich halten. Einer der Hauptvorteile der Verwendung von Aktionsergebnissen besteht darin, dass Ihre Aktionsmethode für Unit-Tests viel einfacher ist. Je mehr Eigenschaften wir den Aktionsergebnissen hinzufügen, desto mehr Dinge muss Ihr Komponententest berücksichtigen, um sicherzustellen, dass die Aktionsmethode das tut, was Sie erwarten.

Ich möchte oft auch die Möglichkeit haben, eine benutzerdefinierte Nachricht bereitzustellen. Sie können also einen Fehler protokollieren, damit wir in Betracht ziehen, dieses Aktionsergebnis in einer zukünftigen Version zu unterstützen: https://aspnetwebstack.codeplex.com/workitem/list/advanced

Eine schöne Sache an den Aktionsergebnissen ist jedoch, dass Sie immer ziemlich einfach Ihre eigenen schreiben können, wenn Sie etwas anderes machen möchten. So könnten Sie es in Ihrem Fall tun (vorausgesetzt, Sie möchten die Fehlermeldung in Text / Klartext; wenn Sie JSON möchten, würden Sie etwas anderes mit dem Inhalt tun):

public class NotFoundTextPlainActionResult : IHttpActionResult
{
    public NotFoundTextPlainActionResult(string message, HttpRequestMessage request)
    {
        if (message == null)
        {
            throw new ArgumentNullException("message");
        }

        if (request == null)
        {
            throw new ArgumentNullException("request");
        }

        Message = message;
        Request = request;
    }

    public string Message { get; private set; }

    public HttpRequestMessage Request { get; private set; }

    public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
    {
        return Task.FromResult(Execute());
    }

    public HttpResponseMessage Execute()
    {
        HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.NotFound);
        response.Content = new StringContent(Message); // Put the message in the response body (text/plain content).
        response.RequestMessage = Request;
        return response;
    }
}

public static class ApiControllerExtensions
{
    public static NotFoundTextPlainActionResult NotFound(this ApiController controller, string message)
    {
        return new NotFoundTextPlainActionResult(message, controller.Request);
    }
}

Dann können Sie in Ihrer Aktionsmethode einfach Folgendes tun:

public class TestController : ApiController
{
    public IHttpActionResult Get()
    {
        return this.NotFound("These are not the droids you're looking for.");
    }
}

Wenn Sie eine benutzerdefinierte Controller-Basisklasse verwendet haben (anstatt direkt von ApiController zu erben), können Sie auch das "this" entfernen. Teil (der leider beim Aufrufen einer Erweiterungsmethode erforderlich ist):

public class CustomApiController : ApiController
{
    protected NotFoundTextPlainActionResult NotFound(string message)
    {
        return new NotFoundTextPlainActionResult(message, Request);
    }
}

public class TestController : CustomApiController
{
    public IHttpActionResult Get()
    {
        return NotFound("These are not the droids you're looking for.");
    }
}

1
Ich habe genau die ähnliche Implementierung von 'IHttpActionResult' geschrieben, aber nicht spezifisch für das Ergebnis 'NotFound'. Dies wird wahrscheinlich für alle 'HttpStatusCodes' funktionieren. Mein CustomActionResult Code sieht so etwas wie dies Und mein Controler der 'Get ()' Aktion sieht wie folgt aus : public‘IHttpActionResult Get () {return CustomNotFoundResult ( " Meessage zu Return"); } 'Außerdem habe ich einen Fehler in CodePlex protokolliert, um dies in der zukünftigen Version zu berücksichtigen.
Ajay Jadhav

Ich benutze ODataController und musste this.NotFound ("blah") verwenden;
Jerther

1
Sehr schöner Beitrag, aber ich möchte nur gegen den Vererbungstipp empfehlen. Mein Team hat sich vor langer Zeit dazu entschlossen, genau das zu tun, und es hat die Klassen dadurch sehr aufgebläht. Ich habe erst kürzlich alles in Erweiterungsmethoden umgestaltet und mich von der Vererbungskette entfernt. Ich würde den Leuten ernsthaft empfehlen, sorgfältig zu überlegen, wann sie eine solche Vererbung verwenden sollten. Normalerweise ist die Komposition viel besser, weil sie viel mehr entkoppelt ist.
Julealgon

6
Diese Funktionalität sollte sofort einsatzbereit sein. Das Einfügen eines optionalen "ResponseBody" -Parameters sollte keine Auswirkungen auf Unit-Tests haben.
Theodore Zographos

230

Hier ist ein Einzeiler für die Rückgabe eines IHttpActionResult NotFound mit einer einfachen Nachricht:

return Content(HttpStatusCode.NotFound, "Foo does not exist.");

24
Die Leute sollten diese Antwort abstimmen. Es ist schön und einfach!
Jess

2
Beachten Sie, dass diese Lösung den HTTP-Header-Status nicht auf "404 Not Found" setzt.
Kasper Halvas Jensen

4
@KasperHalvasJensen Der http-Statuscode vom Server lautet 404. Benötigen Sie etwas mehr?
Anthony F

4
@AnthonyF Du hast recht. Ich habe den Controller.Content (...) verwendet. Ich hätte den ApiController benutzen sollen. Inhalt (...) - Mein schlechtes.
Kasper Halvas Jensen

Danke Kumpel, genau das habe ich gesucht
Kaptein Babbalas

28

Sie können verwenden, ResponseMessageResultwenn Sie möchten:

var myCustomMessage = "your custom message which would be sent as a content-negotiated response"; 
return ResponseMessage(
    Request.CreateResponse(
        HttpStatusCode.NotFound, 
        myCustomMessage
    )
);

Ja, wenn Sie viel kürzere Versionen benötigen, müssen Sie wahrscheinlich Ihr benutzerdefiniertes Aktionsergebnis implementieren.


Ich habe mich für diese Methode entschieden, da sie ordentlich schien. Ich habe gerade die benutzerdefinierte Nachricht an anderer Stelle definiert und den Rückkehrcode eingerückt.
ozzy432836

Ich mag dies besser als Content, weil es tatsächlich ein Objekt zurückgibt, das ich mit einer Message-Eigenschaft analysieren kann, genau wie die Standard-BadRequest-Methode.
user1568891

7

Sie können die ReasonPhrase-Eigenschaft der HttpResponseMessage-Klasse verwenden

catch (Exception exception)
{
  throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.NotFound)
  {
    ReasonPhrase = exception.Message
  });
}

Vielen Dank. Nun ... das sollte funktionieren, aber dann muss ich die HttpResponseException in jeder Aktion selbst erstellen. Um den Code weniger zu halten, überlegte ich, ob ich WebApi 2-Funktionen (wie die vorgefertigten Methoden NotFount () , Ok () ) verwenden und die ReasonPhrase-Nachricht an sie weitergeben könnte.
Ajay Jadhav

Sie können Ihre eigene Erweiterungsmethode NotFound (Ausnahme Ausnahme) erstellen, die korrekte HttpResponseException
Dmytro Rudenko

@DmytroRudenko: Aktionsergebnisse wurden eingeführt, um die Testbarkeit zu verbessern. Wenn Sie hier HttpResponseException auslösen, würden Sie dies gefährden. Auch hier haben wir keine Ausnahme, aber das OP sucht nach einer Nachricht zurück.
Kiran Challa

Ok, wenn Sie NUint nicht zum Testen verwenden möchten, können Sie Ihre eigene Implementierung von NotFoundResult schreiben und dessen ExecuteAsync für die Rückgabe Ihrer Nachrichtendaten neu schreiben. Und geben Sie die Instanz dieser Klasse als Ergebnis Ihres Aktionsaufrufs zurück.
Dmytro Rudenko

1
Beachten Sie, dass Sie jetzt den Statuscode direkt übergeben können, z. B. HttpResponseException (HttpStatusCode.NotFound)
Mark Sowul

3

Sie können ein benutzerdefiniertes Ergebnis für ausgehandelte Inhalte erstellen, wie von d3m3t3er vorgeschlagen. Allerdings würde ich von erben. Wenn Sie es nur für die Rückgabe von NotFound benötigen, müssen Sie den http-Status vom Konstruktor nicht initialisieren.

public class NotFoundNegotiatedContentResult<T> : NegotiatedContentResult<T>
{
    public NotFoundNegotiatedContentResult(T content, ApiController controller)
        : base(HttpStatusCode.NotFound, content, controller)
    {
    }

    public override Task<HttpResponseMessage> ExecuteAsync(
        CancellationToken cancellationToken)
    {
        return base.ExecuteAsync(cancellationToken).ContinueWith(
            task => task.Result, cancellationToken);
    }
}

2

Ich habe es gelöst, indem ich einfach OkNegotiatedContentResultden HTTP-Code in der resultierenden Antwortnachricht abgeleitet und überschrieben habe. Mit dieser Klasse können Sie den Inhaltskörper mit einem beliebigen HTTP-Antwortcode zurückgeben.

public class CustomNegotiatedContentResult<T> : OkNegotiatedContentResult<T>
{
    public HttpStatusCode HttpStatusCode;

    public CustomNegotiatedContentResult(
        HttpStatusCode httpStatusCode, T content, ApiController controller)
        : base(content, controller)
    {
        HttpStatusCode = httpStatusCode;
    }

    public override Task<HttpResponseMessage> ExecuteAsync(
        CancellationToken cancellationToken)
    {
        return base.ExecuteAsync(cancellationToken).ContinueWith(
            task => { 
                // override OK HTTP status code with our own
                task.Result.StatusCode = HttpStatusCode;
                return task.Result;
            },
            cancellationToken);
    }
}

1

Wenn Sie NegotitatedContentResult<T>wie erwähnt von der Basis erben und Ihre nicht transformieren müssen content(z. B. nur eine Zeichenfolge zurückgeben möchten), müssen Sie die ExecuteAsyncMethode nicht überschreiben .

Sie müssen lediglich eine geeignete Typdefinition und einen Konstruktor bereitstellen, der der Basis mitteilt, welcher HTTP-Statuscode zurückgegeben werden soll. Alles andere funktioniert einfach.

Hier sind Beispiele für beide NotFoundund InternalServerError:

public class NotFoundNegotiatedContentResult : NegotiatedContentResult<string>
{
    public NotFoundNegotiatedContentResult(string content, ApiController controller)
        : base(HttpStatusCode.NotFound, content, controller) { }
}

public class InternalServerErrorNegotiatedContentResult : NegotiatedContentResult<string>
{
    public InternalServerErrorNegotiatedContentResult(string content, ApiController controller)
        : base(HttpStatusCode.InternalServerError, content, controller) { }
}

Und dann können Sie entsprechende Erweiterungsmethoden erstellen für ApiController(oder in einer Basisklasse, falls vorhanden):

public static NotFoundNegotiatedContentResult NotFound(this ApiController controller, string message)
{
    return new NotFoundNegotiatedContentResult(message, controller);
}

public static InternalServerErrorNegotiatedContentResult InternalServerError(this ApiController controller, string message)
{
    return new InternalServerErrorNegotiatedContentResult(message, controller);
}

Und dann funktionieren sie genauso wie die eingebauten Methoden. Sie können entweder die vorhandene NotFound()oder Ihre neue benutzerdefinierte Person aufrufen NotFound(myErrorMessage).

Und natürlich können Sie die "hartcodierten" Zeichenfolgentypen in den benutzerdefinierten Typdefinitionen entfernen und sie generisch belassen, wenn Sie möchten, aber dann müssen Sie sich möglicherweise um die ExecuteAsyncDinge kümmern , je nachdem, was Sie <T>tatsächlich sind.

Sie können den Quellcode durchsuchen, um NegotiatedContentResult<T>zu sehen, was er tut. Da ist nicht viel dran.


1

Ich musste eine IHttpActionResultInstanz im Hauptteil einer IExceptionHandlerKlasse erstellen , um die ExceptionHandlerContext.ResultEigenschaft festzulegen. Ich wollte aber auch einen Brauch setzen ReasonPhrase.

Ich fand, dass a ein ResponseMessageResultWrap HttpResponseMessageeinschließen könnte (wodurch ReasonPhrase einfach eingestellt werden kann).

Beispielsweise:

public class MyExceptionHandler : ExceptionHandler
{
    public override void Handle(ExceptionHandlerContext context)
    {
        var ex = context.Exception as IRecordNotFoundException;
        if (ex != null)
        {
            context.Result = new ResponseMessageResult(new HttpResponseMessage(HttpStatusCode.NotFound) { ReasonPhrase = $"{ex.EntityName} not found" });
        }
    }
}

0

Ich weiß, dass PO mit einem Nachrichtentext gefragt wurde, aber eine andere Option, um nur einen 404 zurückzugeben, besteht darin, dass die Methode ein IHttpActionResult zurückgibt und die StatusCode-Funktion verwendet

    public async Task<IHttpActionResult> Get([FromUri]string id)
    {
       var item = await _service.GetItem(id);
       if(item == null)
       {
           StatusCode(HttpStatusCode.NotFound);
       }
       return Ok(item);
    }

0

Bei den Antworten fehlt ein kleines Problem mit der Entwicklergeschichte. Die ApiControllerKlasse macht immer noch aNotFound() Methode zur Verfügung, die Entwickler verwenden können. Dies würde dazu führen, dass eine 404-Antwort einen unkontrollierten Ergebniskörper enthält.

Ich präsentiere hier einige Teile des Codes " bessere ApiController NotFound-Methode ", die eine weniger fehleranfällige Methode bietet, bei der Entwickler nicht wissen müssen, "wie man eine 404 besser sendet".

  • Erstellen Sie eine Klasse, die vonApiController aufgerufen erbtApiController
    • Ich verwende diese Technik, um Entwickler daran zu hindern, die ursprüngliche Klasse zu verwenden
  • Überschreiben Sie die NotFoundMethode , damit Entwickler die erste verfügbare API verwenden können
  • Wenn Sie davon abraten möchten, markieren Sie dies als [Obsolete("Use overload instead")]
  • Fügen Sie ein Extra hinzu protected NotFoundResult NotFound(string message), das Sie fördern möchten
  • Problem: Das Ergebnis unterstützt keine Reaktion mit einem Körper. Lösung: erben und verwenden NegotiatedContentResult. siehe beigefügte bessere NotFoundResult-Klasse .
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.