Mengembalikan kode status http dari pengontrol Web Api

219

Saya mencoba mengembalikan kode status 304 yang tidak dimodifikasi untuk metode GET di pengontrol api web.

Satu-satunya cara saya berhasil adalah sesuatu seperti ini:

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

Masalahnya di sini adalah itu bukan pengecualian, hanya saja tidak dimodifikasi sehingga cache klien OK. Saya juga ingin jenis kembali menjadi Pengguna (karena semua contoh api web menunjukkan dengan GET) tidak mengembalikan HttpResponseMessage atau sesuatu seperti ini.

ozba
sumber
Apakah Anda menggunakan betaatau membangun malam ?
Aliostad
@ Aliliad Saya menggunakan beta
ozba
jadi apa yang salah dengan kembali new HttpResponseMessage(HttpStatusCode.NotModified)? Apakah itu tidak berhasil?
Aliostad
@Aliostad Saya tidak bisa mengembalikan HttpResponseMessage ketika jenis kembali adalah Pengguna, itu tidak mengkompilasi (jelas).
Ozba

Jawaban:

251

Saya tidak tahu jawabannya jadi bertanya pada tim ASP.NET di sini .

Jadi triknya adalah mengubah tanda tangan HttpResponseMessagedan menggunakannya 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
sumber
3
Itu tidak dikompilasi dalam rilis beta ASP.NET MVC 4, karena CreateResponse hanya mengambil kode status sebagai parameter. kedua saya menginginkan solusi tanpa HttpResponseMessage sebagai nilai pengembalian karena sudah usang: aspnetwebstack.codeplex.com/discussions/350492
ozba
5
Dalam hal ada yang membutuhkannya, untuk mendapatkan nilai dari metode controller akan GetUser(request, id, lastModified).TryGetContentValue(out user), di mana user(dalam contoh kasus) adalah Userobjek.
Grinn
4
Apakah ini masih merupakan metode yang disukai pada tahun 2015? MVC 5?
naksir
4
Versi yang lebih modern mengembalikan IHttpActionResult - bukan HttpResponseMessage (2017)
niico
8
Untuk menambah saran niico, ketika jenis kembali adalah IHttpActionResultdan Anda ingin mengembalikan Pengguna, Anda bisa melakukannya return Ok(user). Jika Anda perlu mengembalikan kode status lain (katakanlah, dilarang), Anda bisa melakukannya return this.StatusCode(HttpStatusCode.Forbidden).
Drew
68

Anda juga dapat melakukan hal berikut jika Anda ingin mempertahankan tanda tangan tindakan sebagai Pengguna yang kembali:

public User GetUser(int userId, DateTime lastModifiedAtClient) 

Jika Anda ingin mengembalikan sesuatu selain dari 200itu Anda melemparkan HttpResponseExceptiondalam tindakan Anda dan meneruskan HttpResponseMessageAnda ingin mengirim ke klien.

Henrik Frystyk Nielsen
sumber
9
Ini adalah solusi yang jauh lebih elegan (jawaban tidak lengkap albiet). Mengapa semua orang lebih suka melakukannya dengan cara yang sulit?
nagytech
4
@Geoist stackoverflow.com/questions/1282252/… . Melempar pengecualian itu mahal.
tia
10
Ya, jika Anda merancang API yang sibuk, menggunakan pengecualian untuk mengomunikasikan kasus yang paling umum NotModifiedbenar-benar boros. Jika semua API Anda melakukan ini, maka server Anda sebagian besar akan mengkonversi watt menjadi pengecualian.
Luke Puplett
2
@nagytech karena Anda tidak dapat mengembalikan pesan kesalahan khusus jika Anda melemparkan kesalahan (seperti respons 400) ... juga melempar pengecualian adalah konyol untuk sesuatu yang Anda harapkan dilakukan oleh kode. Mahal dan akan dicatat ketika Anda tidak perlu menginginkannya. Itu bukan pengecualian.
Rocklan
40

Di MVC 5, segalanya menjadi lebih mudah:

return new StatusCodeResult(HttpStatusCode.NotModified, this);
Jon Bates
sumber
3
Tidak dapat menentukan pesan?
naksir
1
Menggunakan pesan sebenarnya adalah jawaban yang diterima. Ini hanya sedikit terser
Jon Bates
39

Ubah metode GetXxx API untuk mengembalikan HttpResponseMessage dan kemudian mengembalikan versi yang diketik untuk respons penuh dan versi yang tidak diketik untuk respons 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);
        }
    }

* Data ClientHasStale adalah ekstensi saya untuk memeriksa header ETag dan IfModifiedSince.

Kerangka MVC masih harus bersambung dan mengembalikan objek Anda.

CATATAN

Saya pikir versi generik sedang dihapus di beberapa versi Web API di masa depan.

Luke Puplett
sumber
4
Ini adalah jawaban tepat yang saya cari - walaupun sebagai jenis pengembalian <TttpResponseMessage <T>> Tugas. Terima kasih!
xeb
1
@ xeb - ya, itu benar-benar layak disebut. Info lebih lanjut tentang async di sini asp.net/mvc/tutorials/mvc-4/…
Luke Puplett
14

Saya benci menabrak artikel lama tapi ini adalah hasil pertama untuk ini dalam pencarian google dan saya bersenang-senang dengan masalah ini (bahkan dengan dukungan kalian). Jadi begini ...

Semoga solusi saya akan membantu mereka yang juga bingung.

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

dan kemudian hanya mewarisi dari BaseController

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

dan kemudian menggunakannya

[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
sumber
1
Cemerlang. Menghemat saya banyak waktu.
gls123
10

.net core 2.2 mengembalikan 304 kode status. Ini menggunakan ApiController.

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

Secara opsional, Anda dapat mengembalikan objek dengan respons

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

Untuk ASP.NET Web Api 2, posting ini dari MS menyarankan untuk mengubah tipe pengembalian metode menjadi IHttpActionResult. Anda kemudian dapat kembali dibangun di IHttpActionResultimplementasi seperti Ok, BadRequest, dll ( lihat di sini ) atau kembali implementasi sendiri.

Untuk kode Anda, itu bisa dilakukan seperti:

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

Pilihan lain:

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

Jika Anda perlu mengembalikan IHttpActionResult dan ingin mengembalikan kode kesalahan plus pesan, gunakan:

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

Saya tidak suka harus mengubah tanda tangan saya untuk menggunakan tipe HttpCreateResponse, jadi saya datang dengan sedikit solusi tambahan untuk menyembunyikannya.

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

Anda kemudian dapat menambahkan metode ke ApiController Anda (atau lebih baik pengendali basis Anda) seperti ini:

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

Kemudian Anda dapat mengembalikannya sama seperti metode bawaan apa pun:

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

Pembaruan untuk jawaban @Aliostads menggunakan lebih banyak moden yang IHttpActionResultdiperkenalkan di 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
sumber
0

Coba ini :

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