Retornando o Código de Status HTTP do Controlador da API da Web

219

Estou tentando retornar um código de status 304 não modificado para um método GET em um controlador de API da web.

A única maneira de obter sucesso foi algo assim:

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

O problema aqui é que não é uma exceção, apenas não é modificado para que o cache do cliente esteja OK. Também quero que o tipo de retorno seja um Usuário (como todos os exemplos de API da Web mostram com GET) não retorne HttpResponseMessage ou algo assim.

ozba
fonte
Você está usando betaou construção noturna ?
Aliostad
@Aliostad eu estou usando beta
ozba
então, o que há de errado em retornar new HttpResponseMessage(HttpStatusCode.NotModified)? Isso não funciona?
Aliostad
@ Aliostad Não consigo retornar HttpResponseMessage quando o tipo de retorno é Usuário, não está compilando (obviamente).
Ozba 18/05

Respostas:

251

Eu não sabia a resposta, então perguntei à equipe do ASP.NET aqui .

Portanto, o truque é alterar a assinatura HttpResponseMessagee usá-la 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
fonte
3
Ele não é compilado na versão beta do ASP.NET MVC 4, pois o CreateResponse usa apenas o código de status como parâmetro. em segundo lugar, eu queria uma solução sem HttpResponseMessage como o valor de retorno como ele está sendo preterido: aspnetwebstack.codeplex.com/discussions/350492
ozba
5
Caso alguém precise, para obter o valor do método do controlador GetUser(request, id, lastModified).TryGetContentValue(out user), onde user(no caso de exemplo) é um Userobjeto.
18713 Grinn
4
Ainda é o método preferido em 2015? MVC 5?
esmagar
4
A versão mais moderna retorna IHttpActionResult - não HttpResponseMessage (2017)
niico
8
Para adicionar à sugestão do niico, quando o tipo de retorno é IHttpActionResulte você deseja devolver o usuário, basta fazê-lo return Ok(user). Se você precisar retornar outro código de status (por exemplo, proibido), basta fazê-lo return this.StatusCode(HttpStatusCode.Forbidden).
Tirou
68

Você também pode fazer o seguinte se desejar preservar a assinatura da ação como Usuário que retorna:

public User GetUser(int userId, DateTime lastModifiedAtClient) 

Se você deseja retornar algo diferente, 200então você lança um HttpResponseExceptionem sua ação e passa o HttpResponseMessageque deseja enviar ao cliente.

Henrik Frystyk Nielsen
fonte
9
Essa é uma solução muito mais elegante (embora a resposta esteja incompleta). Por que todo mundo prefere fazer da maneira mais difícil?
Nagytech 16/05
4
@Geoist stackoverflow.com/questions/1282252/… . Lançar exceção é caro.
Tia
10
Sim, se você estiver criando uma API ocupada, usar uma exceção para comunicar o caso mais comum NotModifiedé realmente um desperdício. Se todas as suas APIs fizeram isso, o servidor estará convertendo principalmente watts em exceções.
Luke Puplett
2
@ Nagytech, porque você não pode retornar uma mensagem de erro personalizada se você lançar um erro (como uma resposta de 400) ... também lançar exceções é bobagem para algo que você espera que o código faça. Caro e será registrado quando você não desejar que eles sejam. Eles não são realmente exceções.
Rocklan
40

No MVC 5, as coisas ficaram mais fáceis:

return new StatusCodeResult(HttpStatusCode.NotModified, this);
Jon Bates
fonte
3
Não consegue especificar uma mensagem?
esmaga
1
Usar uma mensagem é realmente a resposta aceita. Este é apenas um pouco terser
Jon Bates
39

Altere o método da API GetXxx para retornar HttpResponseMessage e, em seguida, retorne uma versão digitada para a resposta completa e a versão não digitada para a resposta NotModified.

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

* Os dados do ClientHasStale são minha extensão para verificar os cabeçalhos ETag e IfModifiedSince.

A estrutura MVC ainda deve serializar e retornar seu objeto.

NOTA

Eu acho que a versão genérica está sendo removida em alguma versão futura da API da Web.

Luke Puplett
fonte
4
Esta era a resposta exata que eu estava procurando - embora como um tipo de retorno Task <HttpResponseMessage <T>>. Obrigado!
xeb 31/07
1
@ xeb - sim, isso vale totalmente a pena chamar. Mais informações sobre assíncrono aqui asp.net/mvc/tutorials/mvc-4/...
Luke Puplett
14

Eu odeio esbarrar em artigos antigos, mas este é o primeiro resultado disso na pesquisa do google e passei um tempão com esse problema (mesmo com o apoio de vocês). Então aqui não vai nada ...

Espero que minha solução ajude aqueles que também estavam confusos.

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

e depois apenas herdar do BaseController

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

e depois usá-lo

[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
fonte
1
Brilhante. Economizou-me uma tonelada de tempo.
precisa
10

.net core 2.2 retornando código de status 304. Isso está usando um ApiController.

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

Opcionalmente, você pode retornar um objeto com a resposta

    [HttpGet]
    public ActionResult<YOUROBJECT> Get()
    {
        return StatusCode(304, YOUROBJECT); 
    }
Ives.me
fonte
7

Para o ASP.NET Web Api 2, esta postagem da MS sugere alterar o tipo de retorno do método para IHttpActionResult. Você pode então voltar um construído em IHttpActionResultimplementação, como Ok, BadRequest, etc ( ver aqui ) ou retornar a sua própria implementação.

Para o seu código, isso pode ser feito como:

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
fonte
3

Outra opção:

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
fonte
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
fonte
2

Se você precisar retornar um IHttpActionResult e desejar retornar o código de erro mais uma mensagem, use:

return ResponseMessage(Request.CreateErrorResponse(HttpStatusCode.NotModified, "Error message here"));
Chris Halcrow
fonte
2

Não gosto de alterar minha assinatura para usar o tipo HttpCreateResponse, então criei um pouco de uma solução estendida para ocultar isso.

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

Você pode adicionar um método ao seu ApiController (ou melhor, ao seu controlador de base) assim:

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

Em seguida, você pode devolvê-lo como qualquer um dos métodos incorporados:

[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
fonte
0

Uma atualização para o @Aliostads responde usando o moden mais IHttpActionResult introduzido na 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
fonte
0

Tente o seguinte:

return new ContentResult() { 
    StatusCode = 404, 
    Content = "Not found" 
};
don_mega
fonte