webentwicklung-frage-antwort-db.com.de

Wie kann ich NotFound () IHttpActionResult mit einer Fehlermeldung oder einer Ausnahme zurückgeben?

Ich gebe ein NotFound IHttpActionResult zurück, 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 NotFound() -Methode der aktuellen ApiController stellt keine Überladung zum Übergeben einer Nachricht bereit.

Gibt es eine Möglichkeit, dies zu tun? oder muss ich mein eigenes IHttpActionResult schreiben?

91
Ajay Jadhav

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

Wir wollten die gebräuchlichsten Antwortnachrichtenformen für einfache leere 404-Nachrichten 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 einfacher zu Unit-Tests wird. Je mehr Eigenschaften wir für Aktionsergebnisse festlegen, desto mehr Dinge muss Ihr Komponententest berücksichtigen, um sicherzustellen, dass die Aktionsmethode das tut, was Sie erwarten.

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

Eine nette Sache an den Aktionsergebnissen ist jedoch, dass Sie Ihre eigenen ziemlich leicht schreiben können, wenn Sie etwas etwas anderes machen möchten. Hier ist, wie Sie es in Ihrem Fall machen könnten (vorausgesetzt, Sie möchten die Fehlermeldung in Text/Klar; wenn Sie JSON wollen, würden Sie etwas anderes mit dem Inhalt machen):

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 verwenden (anstatt direkt von ApiController zu erben), können Sie auch das "this" entfernen. Teil (der leider beim Aufruf einer Erweiterungsmethode benötigt wird):

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.");
    }
}
81
dmatson

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

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

Sie können ResponseMessageResult verwenden, wenn 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 Ihr benutzerdefiniertes Aktionsergebnis implementieren.

28
Kiran Challa

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

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

Ich habe es gelöst, indem ich einfach von OkNegotiatedContentResult abgeleitet und den HTTP-Code in der resultierenden Antwortnachricht überschrieben habe. Mit dieser Klasse können Sie den Inhaltshauptteil 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);
    }
}
2
demeter

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 nicht vom Konstruktor aus 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
Andrei S

Wenn Sie wie erwähnt von der Basis NegotitatedContentResult<T> Erben und Ihren content nicht transformieren müssen (z. B. nur einen String zurückgeben möchten), dann geben Sie nicht ' Die Methode ExecuteAsync muss nicht überschrieben werden.

Sie müssen lediglich eine entsprechende 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 NotFound und 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 für ApiController erstellen (oder dies in einer Basisklasse tun, falls Sie eine haben):

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() aufrufen oder Ihre neue benutzerdefinierte NotFound(myErrorMessage) aufrufen.

Und natürlich können Sie die "hartcodierten" Zeichenfolgentypen in den benutzerdefinierten Typdefinitionen loswerden und sie generisch lassen, wenn Sie möchten, aber dann müssen Sie sich kann um das Zeug ExecuteAsync kümmern , abhängig davon, was Ihr <T> tatsächlich ist.

Sie können im Quellcode nach NegotiatedContentResult<T> Suchen, um alles zu sehen, was es tut. Da ist nicht viel dran.

1

Ich musste eine IHttpActionResult -Instanz im Hauptteil einer IExceptionHandler -Klasse erstellen, um die ExceptionHandlerContext.Result Eigentum. Ich wollte aber auch ein benutzerdefiniertes ReasonPhrase setzen.

Ich habe festgestellt, dass ein ResponseMessageResult ein HttpResponseMessage umbrechen kann (wodurch ReasonPhrase einfach festgelegt 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" });
        }
    }
}
1
Jono Job