Zwracanie kodu stanu HTTP z kontrolera Web Api

219

Próbuję zwrócić kod stanu 304 niezmodyfikowany dla metody GET w kontrolerze interfejsu API sieci Web.

Jedynym sposobem, w jaki mi się udało, było coś takiego:

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

Problem polega na tym, że nie jest to wyjątek, po prostu nie jest modyfikowany, więc pamięć podręczna klienta jest OK. Chcę również, aby typem zwracanym był użytkownik (jak pokazują wszystkie przykłady interfejsu WWW z GET), nie zwracał HttpResponseMessage ani czegoś takiego.

ozba
źródło
Używasz betalub budujesz w nocy ?
Aliostad
@Aliostad Używam wersji beta
ozba
więc co jest złego w powrocie new HttpResponseMessage(HttpStatusCode.NotModified)? Nie działa?
Aliostad
@Aliostad Nie mogę zwrócić HttpResponseMessage, gdy typem zwracanym jest User, nie kompiluje się (oczywiście).
ozba

Odpowiedzi:

251

Nie znałem odpowiedzi, więc zapytałem tutaj zespół ASP.NET .

Zatem sztuczka polega na zmianie podpisu HttpResponseMessagei użyciu 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);
}
Aliostad
źródło
3
Nie kompiluje się w wersji ASP.NET MVC 4 beta, ponieważ CreateResponse przyjmuje tylko kod statusu jako parametr. po drugie chciałem rozwiązania bez HttpResponseMessage jako wartości zwracanej, ponieważ jest przestarzałe: aspnetwebstack.codeplex.com/discussions/350492
ozba
5
W przypadku, gdy ktoś tego potrzebuje, uzyskanie wartości z metody kontrolera byłoby GetUser(request, id, lastModified).TryGetContentValue(out user), gdzie user(w tym przypadku) jest Userobiektem.
Grinn
4
Czy nadal jest to preferowana metoda w 2015 r.? MVC 5?
zmiażdżyć
4
Nowocześniejsza wersja zwraca IHttpActionResult - nie HttpResponseMessage (2017)
niico
8
Aby dodać do sugestii niico, gdy typem zwrotu jest IHttpActionResulti chcesz zwrócić użytkownika, możesz to zrobić return Ok(user). Jeśli chcesz zwrócić inny kod stanu (powiedzmy zabronione), możesz to zrobić return this.StatusCode(HttpStatusCode.Forbidden).
Drew
68

Możesz również wykonać następujące czynności, jeśli chcesz zachować podpis akcji jako powracający użytkownik:

public User GetUser(int userId, DateTime lastModifiedAtClient) 

Jeśli chcesz zwrócić coś innego niż 200wtedy, wrzucisz HttpResponseExceptionakcję i przekazujesz wiadomość, HttpResponseMessagektórą chcesz wysłać do klienta.

Henrik Frystyk Nielsen
źródło
9
Jest to zdecydowanie bardziej eleganckie rozwiązanie (niepełna odpowiedź na pytania). Dlaczego wszyscy wolą robić to w trudny sposób?
nagytech
4
@Geoist stackoverflow.com/questions/1282252/... . Wyjątek dotyczący rzucania jest kosztowny.
tia
10
Tak, jeśli projektujesz zajęty interfejs API, użycie wyjątku do komunikowania najczęstszego przypadku NotModifiedjest naprawdę marnotrawstwem. Jeśli wszystkie interfejsy API to zrobiły, serwer będzie w większości konwertuje waty do wyjątków.
Luke Puplett
2
@nagytech, ponieważ nie możesz zwrócić niestandardowego komunikatu o błędzie, jeśli zgłaszasz błąd (np. odpowiedź 400) ... również zgłaszanie wyjątków jest głupie dla czegoś, czego oczekuje się od kodu. Drogie i zostanie zalogowany, gdy nie będzie to konieczne. Nie są tak naprawdę wyjątkami.
Rocklan,
40

W MVC 5 stało się łatwiej:

return new StatusCodeResult(HttpStatusCode.NotModified, this);
Jon Bates
źródło
3
Nie możesz podać wiadomości?
zmiażdżyć
1
Użycie wiadomości jest w rzeczywistości zaakceptowaną odpowiedzią. To tylko trochę krótszy
Jon Bates
39

Zmień metodę API GetXxx, aby zwracała HttpResponseMessage, a następnie zwróć wersję napisaną dla pełnej odpowiedzi i wersję nietypową dla odpowiedzi Niezmodyfikowanej.

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

* Dane ClientHasStale to moje rozszerzenie do sprawdzania nagłówków ETag i IfModifiedSince.

Struktura MVC powinna nadal serializować i zwracać obiekt.

UWAGA

Myślę, że wersja ogólna jest usuwana w niektórych przyszłych wersjach interfejsu API sieci Web.

Luke Puplett
źródło
4
To była dokładna odpowiedź, której szukałem - aczkolwiek jako typ zwrotu Zadanie <HttpResponseMessage <T>>. Dzięki!
xeb
1
@xeb - tak, to jest całkowicie warte połączenia. Więcej informacji na temat async tutaj asp.net/mvc/tutorials/mvc-4/…
Luke Puplett
14

Nienawidzę podrzucać starych artykułów, ale jest to pierwszy wynik tego w wyszukiwarce Google i świetnie się bawiłem z tym problemem (nawet przy wsparciu was). Więc tutaj nie ma nic ...

Mam nadzieję, że moje rozwiązanie pomoże tym, którzy również byli zdezorientowani.

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

a następnie po prostu odziedziczyć od BaseController

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

a następnie używając go

[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);
}
Kenneth Garza
źródło
1
Znakomity. Zaoszczędził mi mnóstwo czasu.
gls123
10

.net core 2.2 zwraca kod statusu 304. To używa ApiController.

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

Opcjonalnie możesz zwrócić obiekt z odpowiedzią

    [HttpGet]
    public ActionResult<YOUROBJECT> Get()
    {
        return StatusCode(304, YOUROBJECT); 
    }
Ives.me
źródło
7

W przypadku ASP.NET Web Api 2 ten post od MS sugeruje zmianę typu zwracanej metody na IHttpActionResult. Następnie można powrócić wbudowany w IHttpActionResultrealizacji podobnego Ok, BadRequestitp ( patrz tutaj ) lub zwrócić swoją realizację.

W przypadku kodu można to zrobić w następujący sposób:

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);
}
datchung
źródło
3

Inna opcja:

return new NotModified();

public class NotModified : IHttpActionResult
{
    public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
    {
        var response = new HttpResponseMessage(HttpStatusCode.NotModified);
        return Task.FromResult(response);
    }
}
Bora Aydın
źródło
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;
}
Jo Smo
źródło
2

Jeśli chcesz zwrócić IHttpActionResult i chcesz zwrócić kod błędu plus komunikat, użyj:

return ResponseMessage(Request.CreateErrorResponse(HttpStatusCode.NotModified, "Error message here"));
Chris Halcrow
źródło
2

Nie lubię zmieniać podpisu, aby użyć typu HttpCreateResponse, więc wymyśliłem trochę rozszerzonego rozwiązania, aby to ukryć.

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

Następnie możesz dodać metodę do ApiController (lub lepiej kontrolera podstawowego) w następujący sposób:

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

Następnie możesz zwrócić go tak jak każdą z wbudowanych metod:

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

Aktualizacja odpowiedzi @Aliostads przy użyciu większej ilości modów IHttpActionResultwprowadzonych w Web API 2.

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);
    }
}
Ogglas
źródło
0

Spróbuj tego :

return new ContentResult() { 
    StatusCode = 404, 
    Content = "Not found" 
};
don_mega
źródło