Rückgabe des http-Statuscodes vom Web Api-Controller


219

Ich versuche, einen Statuscode von 304 zurückzugeben, der für eine GET-Methode in einem Web-API-Controller nicht geändert wurde.

Der einzige Weg, auf dem ich Erfolg hatte, war ungefähr so:

public class TryController : ApiController
{
    public User GetUser(int userId, DateTime lastModifiedAtClient)
    {
        var user = new DataEntities().Users.First(p => p.Id == userId);
        if (user.LastModified <= lastModifiedAtClient)
        {
             throw new HttpResponseException(HttpStatusCode.NotModified);
        }
        return user;
    }
}

Das Problem hierbei ist, dass es keine Ausnahme ist. Es wird nur nicht geändert, sodass der Client-Cache in Ordnung ist. Ich möchte auch, dass der Rückgabetyp ein Benutzer ist (wie alle Beispiele für Web-APIs mit GET zeigen) und nicht HttpResponseMessage oder ähnliches zurückgibt.


Verwenden Sie betaoder bauen Sie jede Nacht ?
Aliostad

@Aliostad Ich benutze Beta
Ozba

Was ist also falsch an der Rückkehr new HttpResponseMessage(HttpStatusCode.NotModified)? Funktioniert es nicht
Aliostad

@Aliostad Ich kann HttpResponseMessage nicht zurückgeben, wenn der Rückgabetyp Benutzer ist, er wird (offensichtlich) nicht kompiliert.
Ozba

Antworten:


251

Ich kannte die Antwort nicht und fragte das ASP.NET-Team hier .

Der Trick besteht also darin, die Signatur zu ändern HttpResponseMessageund zu verwenden Request.CreateResponse.

[ResponseType(typeof(User))]
public HttpResponseMessage GetUser(HttpRequestMessage request, int userId, DateTime lastModifiedAtClient)
{
    var user = new DataEntities().Users.First(p => p.Id == userId);
    if (user.LastModified <= lastModifiedAtClient)
    {
         return new HttpResponseMessage(HttpStatusCode.NotModified);
    }
    return request.CreateResponse(HttpStatusCode.OK, user);
}

3
Es wird in der Beta-Version von ASP.NET MVC 4 nicht kompiliert, da CreateResponse nur Statuscode als Parameter verwendet. zweitens wollte ich eine Lösung ohne HttpResponseMessage als Rückgabewert, da sie veraltet ist: aspnetwebstack.codeplex.com/discussions/350492
ozba

5
Falls jemand es benötigt, wäre der Wert von der Controller-Methode zu erhalten GetUser(request, id, lastModified).TryGetContentValue(out user), wobei user(im Beispielfall) ein UserObjekt ist.
Grinn

4
Ist dies auch 2015 die bevorzugte Methode? MVC 5?
Crush

4
Die modernere Version gibt IHttpActionResult zurück - nicht HttpResponseMessage (2017)
Niico

8
Um den Vorschlag von niico zu ergänzen IHttpActionResult, können Sie dies einfach tun , wenn der Rückgabetyp lautet und Sie den Benutzer zurückgeben möchten return Ok(user). Wenn Sie einen anderen Statuscode (z. B. verboten) zurückgeben müssen, können Sie dies einfach tun return this.StatusCode(HttpStatusCode.Forbidden).
Drew

68

Sie können auch Folgendes tun, wenn Sie die Aktionssignatur als wiederkehrender Benutzer beibehalten möchten:

public User GetUser(int userId, DateTime lastModifiedAtClient) 

Wenn Sie etwas anderes als das zurückgeben möchten, 200werfen Sie ein HttpResponseExceptionin Ihre Aktion und übergeben das, das HttpResponseMessageSie an den Client senden möchten.


9
Dies ist eine viel elegantere Lösung (albiet unvollständige Antwort). Warum ziehen es alle vor, es auf die harte Tour zu machen?
Nagytech

4
@Geoist stackoverflow.com/questions/1282252/… . Das Auslösen einer Ausnahme ist kostspielig.
Tia

10
Ja, wenn Sie eine ausgelastete API entwerfen, NotModifiedist es wirklich verschwenderisch , eine Ausnahme zu verwenden, um den häufigsten Fall zu kommunizieren . Wenn alle Ihre APIs dies getan haben, konvertiert Ihr Server meistens Watt in Ausnahmen.
Luke Puplett

2
@nagytech, weil Sie keine benutzerdefinierte Fehlermeldung zurückgeben können, wenn Sie einen Fehler auslösen (wie eine 400-Antwort) ... auch das Auslösen von Ausnahmen ist dumm für etwas, das Sie vom Code erwarten. Teuer und wird protokolliert, wenn Sie dies nicht unbedingt möchten. Das sind keine wirklichen Ausnahmen.
Rocklan

40

In MVC 5 wurde es einfacher:

return new StatusCodeResult(HttpStatusCode.NotModified, this);

3
Sie können keine Nachricht angeben?
Crush

1
Die Verwendung einer Nachricht ist tatsächlich die akzeptierte Antwort. Dies ist nur ein kleiner Terser
Jon Bates

39

Ändern Sie die GetXxx-API-Methode so, dass HttpResponseMessage zurückgegeben wird, und geben Sie dann eine typisierte Version für die vollständige Antwort und die untypisierte Version für die NotModified-Antwort zurück.

    public HttpResponseMessage GetComputingDevice(string id)
    {
        ComputingDevice computingDevice =
            _db.Devices.OfType<ComputingDevice>()
                .SingleOrDefault(c => c.AssetId == id);

        if (computingDevice == null)
        {
            return this.Request.CreateResponse(HttpStatusCode.NotFound);
        }

        if (this.Request.ClientHasStaleData(computingDevice.ModifiedDate))
        {
            return this.Request.CreateResponse<ComputingDevice>(
                HttpStatusCode.OK, computingDevice);
        }
        else
        {
            return this.Request.CreateResponse(HttpStatusCode.NotModified);
        }
    }

* Die ClientHasStale-Daten sind meine Erweiterung zum Überprüfen von ETag- und IfModifiedSince-Headern.

Das MVC-Framework sollte Ihr Objekt weiterhin serialisieren und zurückgeben.

HINWEIS

Ich denke, die generische Version wird in einer zukünftigen Version der Web-API entfernt.


4
Dies war genau die Antwort, nach der ich gesucht habe - allerdings als Task <HttpResponseMessage <T>> Rückgabetyp. Vielen Dank!
xeb

1
@xeb - ja, das ist einen Anruf wert. Weitere Informationen zu Async finden Sie hier asp.net/mvc/tutorials/mvc-4/…
Luke Puplett

14

Ich hasse es, alte Artikel zu stoßen, aber dies ist das erste Ergebnis in der Google-Suche und ich hatte verdammt viel Zeit mit diesem Problem (sogar mit der Unterstützung von euch). Also hier geht nichts ...

Hoffentlich hilft meine Lösung denen, die auch verwirrt waren.

namespace MyApplication.WebAPI.Controllers
{
    public class BaseController : ApiController
    {
        public T SendResponse<T>(T response, HttpStatusCode statusCode = HttpStatusCode.OK)
        {
            if (statusCode != HttpStatusCode.OK)
            {
                // leave it up to microsoft to make this way more complicated than it needs to be
                // seriously i used to be able to just set the status and leave it at that but nooo... now 
                // i need to throw an exception 
                var badResponse =
                    new HttpResponseMessage(statusCode)
                    {
                        Content =  new StringContent(JsonConvert.SerializeObject(response), Encoding.UTF8, "application/json")
                    };

                throw new HttpResponseException(badResponse);
            }
            return response;
        }
    }
}

und dann einfach vom BaseController erben

[RoutePrefix("api/devicemanagement")]
public class DeviceManagementController : BaseController
{...

und dann mit ihm

[HttpGet]
[Route("device/search/{property}/{value}")]
public SearchForDeviceResponse SearchForDevice(string property, string value)
{
    //todo: limit search property here?
    var response = new SearchForDeviceResponse();

    var results = _deviceManagementBusiness.SearchForDevices(property, value);

    response.Success = true;
    response.Data = results;

    var statusCode = results == null || !results.Any() ? HttpStatusCode.NoContent : HttpStatusCode.OK;

    return SendResponse(response, statusCode);
}

1
Brillant. Hat mir eine Menge Zeit gespart.
gls123

10

.net Core 2.2 gibt 304 Statuscode zurück. Dies verwendet einen ApiController.

    [HttpGet]
    public ActionResult<YOUROBJECT> Get()
    {
        return StatusCode(304);
    }

Optional können Sie ein Objekt mit der Antwort zurückgeben

    [HttpGet]
    public ActionResult<YOUROBJECT> Get()
    {
        return StatusCode(304, YOUROBJECT); 
    }

7

Für ASP.NET Web Api 2 wird in diesem Beitrag von MS vorgeschlagen , den Rückgabetyp der Methode in zu ändern IHttpActionResult. Sie können dann eine eingebaute Rück IHttpActionResultUmsetzung wie Ok, BadRequestusw. ( siehe hier ) oder eine eigene Implementierung zurück.

Für Ihren Code könnte dies wie folgt erfolgen:

public IHttpActionResult GetUser(int userId, DateTime lastModifiedAtClient)
{
    var user = new DataEntities().Users.First(p => p.Id == userId);
    if (user.LastModified <= lastModifiedAtClient)
    {
        return StatusCode(HttpStatusCode.NotModified);
    }
    return Ok(user);
}

3

Andere Option:

return new NotModified();

public class NotModified : IHttpActionResult
{
    public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
    {
        var response = new HttpResponseMessage(HttpStatusCode.NotModified);
        return Task.FromResult(response);
    }
}

2
public HttpResponseMessage Post(Article article)
{
    HttpResponseMessage response = Request.CreateResponse<Article>(HttpStatusCode.Created, article);

    string uriToTheCreatedItem = Url.Route(null, new { id = article.Id });
    response.Headers.Location = new Uri(Request.RequestUri, uriToTheCreatedItem);

    return response;
}

2

Wenn Sie ein IHttpActionResult zurückgeben müssen und den Fehlercode plus eine Nachricht zurückgeben möchten, verwenden Sie:

return ResponseMessage(Request.CreateErrorResponse(HttpStatusCode.NotModified, "Error message here"));

2

Ich mag es nicht, meine Signatur ändern zu müssen, um den Typ HttpCreateResponse zu verwenden, deshalb habe ich mir eine erweiterte Lösung ausgedacht, um das zu verbergen.

public class HttpActionResult : IHttpActionResult
{
    public HttpActionResult(HttpRequestMessage request) : this(request, HttpStatusCode.OK)
    {
    }

    public HttpActionResult(HttpRequestMessage request, HttpStatusCode code) : this(request, code, null)
    {
    }

    public HttpActionResult(HttpRequestMessage request, HttpStatusCode code, object result)
    {
        Request = request;
        Code = code;
        Result = result;
    }

    public HttpRequestMessage Request { get; }
    public HttpStatusCode Code { get; }
    public object Result { get; }

    public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
    {
        return Task.FromResult(Request.CreateResponse(Code, Result));
    }
}

Sie können dann Ihrem ApiController (oder besser Ihrem Basis-Controller) eine Methode wie folgt hinzufügen:

protected IHttpActionResult CustomResult(HttpStatusCode code, object data) 
{
    // Request here is the property on the controller.
    return new HttpActionResult(Request, code, data);
}

Dann können Sie es wie jede der integrierten Methoden zurückgeben:

[HttpPost]
public IHttpActionResult Post(Model model)
{
    return model.Id == 1 ?
                Ok() :
                CustomResult(HttpStatusCode.NotAcceptable, new { 
                    data = model, 
                    error = "The ID needs to be 1." 
                });
}

0

Ein Update für @Aliostads Antwort unter Verwendung des IHttpActionResultin Web API 2 eingeführten mehr Moden .

https://docs.microsoft.com/en-us/aspnet/web-api/overview/getting-started-with-aspnet-web-api/action-results#ihttpactionresult

public class TryController : ApiController
{
    public IHttpActionResult GetUser(int userId, DateTime lastModifiedAtClient)
    {
        var user = new DataEntities().Users.First(p => p.Id == userId);
        if (user.LastModified <= lastModifiedAtClient)
        {
            return StatusCode(HttpStatusCode.NotModified);
            // If you would like to return a Http Status code with any object instead:
            // return Content(HttpStatusCode.InternalServerError, "My Message");
        }
        return Ok(user);
    }
}

0

Versuche dies :

return new ContentResult() { 
    StatusCode = 404, 
    Content = "Not found" 
};
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.