web-dev-qa-db-fra.com

Renvoi du code d'état http à partir du contrôleur Web Api

J'essaie de renvoyer un code d'état de 304 non modifié pour une méthode GET dans un contrôleur API Web.

La seule façon dont j'ai réussi était quelque chose comme ça:

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

Le problème ici est que ce n’est pas une exception, il n’est tout simplement pas modifié pour que le cache du client fonctionne correctement. Je souhaite également que le type de retour soit un utilisateur (comme le montrent tous les exemples d'API Web avec GET) et non pas HttpResponseMessage ou quelque chose comme ça.

207
ozba

Je ne connaissais pas la réponse alors demandé à l'équipe ASP.NET ici .

Le truc consiste donc à changer la signature en HttpResponseMessage et à utiliser 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);
}
239
Aliostad

Vous pouvez également effectuer les opérations suivantes si vous souhaitez conserver la signature d'action en tant qu'utilisateur renvoyé:

public User GetUser(int userId, DateTime lastModifiedAtClient) 

Si vous voulez renvoyer autre chose que 200, vous lancez un HttpResponseException dans votre action et transmettez le HttpResponseMessage que vous souhaitez envoyer au client.

67

Modifiez la méthode de l'API GetXxx pour renvoyer HttpResponseMessage, puis renvoyez une version typée pour la réponse complète et la version non typée pour la réponse 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);
        }
    }

* Les données ClientHasStale sont mon extension pour la vérification des en-têtes ETag et IfModifiedSince.

Le framework MVC doit quand même sérialiser et retourner votre objet.

NOTE

Je pense que la version générique est supprimée dans une version future de l'API Web.

39
Luke Puplett

Dans MVC 5, les choses se sont simplifiées:

return new StatusCodeResult(HttpStatusCode.NotModified, this);
37
Jon Bates

Je déteste écraser de vieux articles, mais c’est le premier résultat de cette recherche dans google et j’ai passé un sacré bout de temps à résoudre ce problème (même avec le soutien de vous les gars). Alors rien ne va ici ...

Espérons que ma solution aidera ceux qui ont également été confus.

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

et ensuite juste hériter du BaseController

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

et ensuite l'utiliser

[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);
}
12
Kenneth Garza

.net core 2.2 retournant le code d’état 304. Ceci utilise un ApiController.

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

Vous pouvez éventuellement renvoyer un objet avec la réponse

    [HttpGet]
    public ActionResult<YOUROBJECT> Get()
    {
        return StatusCode(304, YOUROBJECT); 
    }
5
Ives.me
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
Jo Smo

Pour ASP.NET Web Api 2, ce message de MS suggère de changer le type de résultat de la méthode en IHttpActionResult. Vous pouvez ensuite retourner une implémentation IHttpActionResult intégrée telle que Ok, BadRequest, etc. ( voir ici ) ou renvoyer votre propre implémentation.

Pour votre code, cela pourrait être fait comme:

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

Si vous devez renvoyer un IHttpActionResult et que vous voulez renvoyer le code d'erreur ainsi qu'un message, utilisez:

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

Une autre 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
Bora Aydın

Je n'aime pas avoir à changer ma signature pour utiliser le type HttpCreateResponse, alors j'ai proposé une solution un peu plus étendue pour le masquer.

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

Vous pouvez ensuite ajouter une méthode à votre ApiController (ou mieux à votre contrôleur de base) comme ceci:

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

Ensuite, vous pouvez le retourner comme n'importe quelle méthode intégrée:

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

Une mise à jour de @Aliostads répond en utilisant le plus moden IHttpActionResult introduit dans 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);
    }
}
0
Ogglas