Restituzione del codice di stato http dal controller Web Api


219

Sto cercando di restituire un codice di stato di 304 non modificato per un metodo GET in un controller API Web.

L'unico modo in cui sono riuscito è stato qualcosa del genere:

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

Il problema qui è che non è un'eccezione, non è stato modificato, quindi la cache del client è OK. Voglio anche che il tipo restituito sia un Utente (come mostrano tutti gli esempi di API Web con GET), non restituire HttpResponseMessage o qualcosa del genere.


Stai usando betao costruisci di notte ?
Aliostad,

@Aliostad Sto usando beta
ozba

quindi cosa c'è di sbagliato nel tornare new HttpResponseMessage(HttpStatusCode.NotModified)? Non funziona?
Aliostad,

@Aliostad Non riesco a restituire HttpResponseMessage quando il tipo restituito è Utente, non si sta compilando (ovviamente).
Ozba,

Risposte:


251

Non conoscevo la risposta, quindi ho chiesto al team ASP.NET qui .

Quindi il trucco è cambiare la firma HttpResponseMessagee usarla 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
Non viene compilato nella versione beta di ASP.NET MVC 4, poiché CreateResponse accetta solo il codice di stato come parametro. in secondo luogo volevo una soluzione senza HttpResponseMessage come valore di ritorno in quanto deprecato: aspnetwebstack.codeplex.com/discussions/350492
ozba

5
Nel caso in cui qualcuno ne abbia bisogno, per ottenere il valore dal metodo del controller sarebbe GetUser(request, id, lastModified).TryGetContentValue(out user), dove user(nel caso esempio) è un Useroggetto.
Grinn,

4
È ancora il metodo preferito nel 2015? MVC 5?
schiaccia il

4
La versione più moderna restituisce IHttpActionResult - non HttpResponseMessage (2017)
niico

8
Per aggiungere al suggerimento di Niico, quando il tipo restituito è IHttpActionResulte vuoi restituire l'Utente, puoi semplicemente farlo return Ok(user). Se devi restituire un altro codice di stato (diciamo, proibito) puoi semplicemente farlo return this.StatusCode(HttpStatusCode.Forbidden).
Estratto l'

68

È inoltre possibile effettuare le seguenti operazioni se si desidera conservare la firma dell'azione come Utente di ritorno:

public User GetUser(int userId, DateTime lastModifiedAtClient) 

Se si desidera restituire qualcosa di diverso da quello in cui 200si inserisce HttpResponseExceptionun'azione e si passa a quello che HttpResponseMessagesi desidera inviare al client.


9
Questa è una soluzione molto più elegante (risposta incompleta albiet). Perché tutti preferiscono farlo nel modo più duro?
nagytech,

4
@Geoist stackoverflow.com/questions/1282252/... . L'eccezione è costosa.
tia,

10
Sì, se stai progettando un'API occupata, usare un'eccezione per comunicare il caso più comune di NotModifiedè davvero dispendioso. Se tutte le tue API lo hanno fatto, il tuo server convertirà principalmente i watt in eccezioni.
Luke Puplett,

2
@nagytech perché non puoi restituire un messaggio di errore personalizzato se lanci un errore (come una risposta 400) ... anche lanciare eccezioni è sciocco per qualcosa che ti aspetti che faccia il codice. Costoso e verrà registrato quando non si desidera necessariamente che siano. Non sono davvero eccezioni.
Rocklan

40

In MVC 5, le cose sono diventate più semplici:

return new StatusCodeResult(HttpStatusCode.NotModified, this);

3
Non riesci a specificare un messaggio?
schiaccia il

1
L'uso di un messaggio è in realtà la risposta accettata. Questo è solo un po 'più difficile
Jon Bates,

39

Modificare il metodo API GetXxx per restituire HttpResponseMessage e quindi restituire una versione digitata per la risposta completa e la versione non tipizzata per la risposta 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);
        }
    }

* I dati ClientHasStale sono la mia estensione per il controllo delle intestazioni ETag e IfModifiedSince.

Il framework MVC dovrebbe comunque serializzare e restituire l'oggetto.

NOTA

Penso che la versione generica verrà rimossa in alcune versioni future dell'API Web.


4
Questa era la risposta esatta che stavo cercando, anche se come tipo di ritorno Task <HttpResponseMessage <T>>. Grazie!
xeb,

1
@xeb - sì, vale assolutamente la pena chiamarlo. Maggiori informazioni su async qui asp.net/mvc/tutorials/mvc-4/…
Luke Puplett

14

Odio sbattere vecchi articoli, ma questo è il primo risultato per questo nella ricerca di Google e ho avuto un sacco di tempo con questo problema (anche con il supporto di voi ragazzi). Quindi qui non va niente ...

Spero che la mia soluzione aiuti quelli che erano anche confusi.

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 quindi eredita dal BaseController

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

e poi usandolo

[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
Brillante. Mi ha salvato un sacco di tempo.
gls123,

10

.net core 2.2 che restituisce 304 codice di stato. Questo sta usando un ApiController.

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

Facoltativamente, è possibile restituire un oggetto con la risposta

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

7

Per ASP.NET Web Api 2, questo post di MS suggerisce di cambiare il tipo di ritorno del metodo in IHttpActionResult. È quindi possibile restituire un costruito in IHttpActionResultesecuzione come Ok, BadRequest, etc ( vedi qui ) o restituire una propria implementazione.

Per il tuo codice, potrebbe essere fatto come:

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

Un'altra opzione:

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

Se è necessario restituire un IHttpActionResult e si desidera restituire il codice di errore più un messaggio, utilizzare:

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

2

Non mi piace dover cambiare la mia firma per usare il tipo HttpCreateResponse, quindi ho trovato un po 'di una soluzione estesa per nasconderlo.

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

Puoi quindi aggiungere un metodo al tuo ApiController (o meglio al tuo controller di base) in questo modo:

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

Quindi puoi restituirlo proprio come uno dei metodi integrati:

[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

Un aggiornamento a @Aliostads risponde usando il più moden IHttpActionResult introdotto in 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

Prova questo :

return new ContentResult() { 
    StatusCode = 404, 
    Content = "Not found" 
};
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.