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

219

J'essaie de retourner un code d'état de 304 non modifié pour une méthode GET dans un contrôleur web api.

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é donc le cache client est OK. Je veux é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.

ozba
la source
Utilisez-vous betaou construisez -vous tous les soirs ?
Aliostad
@Aliostad J'utilise la bêta
ozba
alors qu'est-ce qui ne va pas avec le retour new HttpResponseMessage(HttpStatusCode.NotModified)? Ça ne marche pas?
Aliostad
@Aliostad Je ne peux pas retourner HttpResponseMessage lorsque le type de retour est User, il ne compile pas (évidemment).
ozba

Réponses:

251

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

L'astuce consiste donc à modifier la signature HttpResponseMessageet à l'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);
}
Aliostad
la source
3
Il ne se compile pas dans la version bêta d'ASP.NET MVC 4, car CreateResponse ne prend que le code d'état comme paramètre. deuxièmement, je voulais une solution sans HttpResponseMessage comme valeur de retour car elle est obsolète: aspnetwebstack.codeplex.com/discussions/350492
ozba
5
Dans le cas où quelqu'un en aurait besoin, pour obtenir la valeur de la méthode du contrôleur, il faudrait GetUser(request, id, lastModified).TryGetContentValue(out user), où user(dans le cas de l'exemple) est un Userobjet.
Grinn
4
Est-ce toujours la méthode préférée en 2015? MVC 5?
écraser le
4
La version plus moderne renvoie IHttpActionResult - pas HttpResponseMessage (2017)
niico
8
Pour ajouter à la suggestion de niico, lorsque le type de retour est IHttpActionResultet que vous souhaitez renvoyer l'utilisateur, vous pouvez simplement le faire return Ok(user). Si vous devez renvoyer un autre code d'état (par exemple, interdit), vous pouvez le faire return this.StatusCode(HttpStatusCode.Forbidden).
Drew
68

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

public User GetUser(int userId, DateTime lastModifiedAtClient) 

Si vous voulez retourner autre chose que 200vous lancez un HttpResponseExceptiondans votre action et passez le que HttpResponseMessagevous voulez envoyer au client.

Henrik Frystyk Nielsen
la source
9
C'est une solution beaucoup plus élégante (réponse incomplète albiet). Pourquoi tout le monde préfère le faire à la dure?
nagytech
4
@Geoist stackoverflow.com/questions/1282252/… . Lancer une exception coûte cher.
tia
10
Oui, si vous concevez une API chargée, utiliser une exception pour communiquer le cas le plus courant NotModifiedest vraiment un gaspillage. Si toutes vos API l'ont fait, votre serveur convertira principalement les watts en exceptions.
Luke Puplett
2
@nagytech parce que vous ne pouvez pas retourner un message d'erreur personnalisé si vous lancez une erreur (comme une réponse 400) ... aussi, lever des exceptions est idiot pour quelque chose que vous attendez du code. Cher et sera enregistré lorsque vous ne le souhaitez pas nécessairement. Ce ne sont pas vraiment des exceptions.
Rocklan
40

Dans MVC 5, les choses sont devenues plus faciles:

return new StatusCodeResult(HttpStatusCode.NotModified, this);
Jon Bates
la source
3
Vous ne pouvez pas spécifier un message?
écraser
1
L'utilisation d'un message est en fait la réponse acceptée. Ceci est juste un petit terser
Jon Bates
39

Modifiez la méthode GetXxx API 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 vérifier les en-têtes ETag et IfModifiedSince.

Le framework MVC doit toujours sérialiser et renvoyer votre objet.

REMARQUE

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

Luke Puplett
la source
4
C'était la réponse exacte que je cherchais - bien qu'en tant que type de retour Task <HttpResponseMessage <T>>. Merci!
2012
1
@xeb - oui, cela vaut vraiment la peine d'être appelé. Plus d'informations sur async ici asp.net/mvc/tutorials/mvc-4/…
Luke Puplett
14

Je déteste heurter les anciens articles, mais c'est le premier résultat pour cela dans la recherche Google et j'ai eu beaucoup de mal avec ce problème (même avec le soutien de vous). Alors ça ne va rien ...

J'espère 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;
        }
    }
}

puis juste hériter du BaseController

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

puis 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);
}
Kenneth Garza
la source
1
Brillant. Cela m'a fait gagner une tonne de temps.
gls123
10

.net core 2.2 renvoyant le code de statut 304. Cela 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); 
    }
Ives.me
la source
7

Pour ASP.NET Web Api 2, ce message de MS suggère de changer le type de retour de la méthode en IHttpActionResult. Vous pouvez ensuite revenir construit dans la IHttpActionResultmise en œuvre comme Ok, BadRequest, etc ( voir ici ) ou retourner votre propre implémentation.

Pour votre code, cela pourrait se faire 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);
}
datchung
la source
3

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);
    }
}
Bora Aydın
la source
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
la source
2

Si vous devez renvoyer un IHttpActionResult et que vous souhaitez renvoyer le code d'erreur plus un message, utilisez:

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

Je n'aime pas avoir à changer ma signature pour utiliser le type HttpCreateResponse, j'ai donc proposé une petite solution étendue pour masquer cela.

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 renvoyer 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." 
                });
}
krillgar
la source
0

Une mise à jour de la réponse @Aliostads utilisant le plus de modens IHttpActionResultintroduit dans l'API Web 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
la source
0

Essaye ça :

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