Bagaimana cara mengembalikan NotFound () IHttpActionResult dengan pesan kesalahan atau pengecualian?


98

Saya mengembalikan NotFound IHttpActionResult, ketika sesuatu tidak ditemukan dalam tindakan GET WebApi saya. Bersamaan dengan tanggapan ini, saya ingin mengirim pesan khusus dan / atau pesan pengecualian (jika ada). Arus ApiController's NotFound()metode tidak memberikan kelebihan untuk lulus pesan.

Apakah ada cara untuk melakukan ini? atau saya harus menulis kebiasaan saya sendiri IHttpActionResult?


Apakah Anda ingin mengembalikan pesan yang sama untuk semua hasil Tidak Ditemukan?
Nikolai Samteladze

@NikolaiSamteladze Tidak, ini bisa menjadi pesan yang berbeda tergantung pada situasinya.
Ajay Jadhav

Jawaban:


84

Anda perlu menulis hasil tindakan Anda sendiri jika Anda ingin menyesuaikan bentuk pesan respons.

Kami ingin memberikan bentuk pesan respons yang paling umum di luar kotak untuk hal-hal seperti 404 kosong sederhana, tetapi kami juga ingin membuat hasil ini sesederhana mungkin; salah satu keuntungan utama menggunakan hasil tindakan adalah membuat metode tindakan Anda lebih mudah untuk pengujian unit. Semakin banyak properti yang kami tempatkan pada hasil tindakan, semakin banyak hal yang perlu dipertimbangkan pengujian unit Anda untuk memastikan metode tindakan melakukan apa yang Anda harapkan.

Saya sering menginginkan kemampuan untuk memberikan pesan khusus juga, jadi jangan ragu untuk mencatat bug agar kami dapat mempertimbangkan untuk mendukung hasil tindakan tersebut di rilis mendatang: https://aspnetwebstack.codeplex.com/workitem/list/advanced

Namun, satu hal yang menyenangkan tentang hasil tindakan adalah Anda selalu dapat menulis sendiri dengan cukup mudah jika Anda ingin melakukan sesuatu yang sedikit berbeda. Inilah cara Anda melakukannya dalam kasus Anda (dengan asumsi Anda menginginkan pesan kesalahan dalam teks / biasa; jika Anda menginginkan JSON, Anda akan melakukan sesuatu yang sedikit berbeda dengan konten):

public class NotFoundTextPlainActionResult : IHttpActionResult
{
    public NotFoundTextPlainActionResult(string message, HttpRequestMessage request)
    {
        if (message == null)
        {
            throw new ArgumentNullException("message");
        }

        if (request == null)
        {
            throw new ArgumentNullException("request");
        }

        Message = message;
        Request = request;
    }

    public string Message { get; private set; }

    public HttpRequestMessage Request { get; private set; }

    public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
    {
        return Task.FromResult(Execute());
    }

    public HttpResponseMessage Execute()
    {
        HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.NotFound);
        response.Content = new StringContent(Message); // Put the message in the response body (text/plain content).
        response.RequestMessage = Request;
        return response;
    }
}

public static class ApiControllerExtensions
{
    public static NotFoundTextPlainActionResult NotFound(this ApiController controller, string message)
    {
        return new NotFoundTextPlainActionResult(message, controller.Request);
    }
}

Kemudian, dalam metode tindakan Anda, Anda bisa melakukan sesuatu seperti ini:

public class TestController : ApiController
{
    public IHttpActionResult Get()
    {
        return this.NotFound("These are not the droids you're looking for.");
    }
}

Jika Anda menggunakan kelas dasar pengontrol kustom (alih-alih mewarisi langsung dari ApiController), Anda juga bisa menghilangkan "this." bagian (yang sayangnya diperlukan saat memanggil metode ekstensi):

public class CustomApiController : ApiController
{
    protected NotFoundTextPlainActionResult NotFound(string message)
    {
        return new NotFoundTextPlainActionResult(message, Request);
    }
}

public class TestController : CustomApiController
{
    public IHttpActionResult Get()
    {
        return NotFound("These are not the droids you're looking for.");
    }
}

1
Saya menulis implementasi yang persis sama dari 'IHttpActionResult', tetapi tidak spesifik untuk hasil 'NotFound'. Ini mungkin akan bekerja untuk Semua 'HttpStatusCodes'. Kode CustomActionResult saya terlihat seperti ini Dan tindakan 'Get ()' dari kontroler saya terlihat seperti ini: 'public IHttpActionResult Get () {return CustomNotFoundResult ("Meessage to Return."); } 'Juga, saya mencatat bug di CodePlex untuk mempertimbangkan ini di rilis mendatang.
Ajay Jadhav

Saya menggunakan ODataControllers dan saya harus menggunakan this.NotFound ("blah");
Jerther

1
Pos yang sangat bagus, tetapi saya hanya ingin merekomendasikan agar tidak terkena tip warisan. Tim saya memutuskan untuk melakukan hal itu sejak lama, dan hal itu membengkak banyak kelas dengan melakukannya. Saya baru-baru ini memfaktorkan ulang semuanya menjadi metode ekstensi dan menjauh dari rantai warisan. Saya sangat merekomendasikan orang untuk mempertimbangkan dengan cermat kapan mereka harus menggunakan warisan seperti ini. Biasanya, komposisinya jauh lebih baik, karena jauh lebih terpisah.
julealgon

6
Fungsionalitas ini seharusnya sudah out-of-the-box. Menyertakan parameter opsional "ResponseBody" seharusnya tidak memengaruhi pengujian unit.
Theodore Zographos

230

Berikut ini satu baris untuk mengembalikan IHttpActionResult NotFound dengan pesan sederhana:

return Content(HttpStatusCode.NotFound, "Foo does not exist.");

24
Orang harus memilih jawaban ini. Itu bagus dan mudah!
Jess

2
Ketahuilah bahwa solusi ini tidak menyetel status header HTTP ke "404 Not Found".
Kasper Halvas Jensen

4
@KasperHalvasJen Kode status http dari server adalah 404, apakah Anda memerlukan sesuatu yang lebih?
Anthony F

4
@AnonyF Anda benar. Saya menggunakan Controller.Content (...). Haruskah menggunakan ApiController.Content (...) - My bad.
Kasper Halvas Jensen

Terima kasih sobat, inilah yang saya cari
Kaptein Babbalas

28

Anda bisa menggunakan ResponseMessageResultjika Anda suka:

var myCustomMessage = "your custom message which would be sent as a content-negotiated response"; 
return ResponseMessage(
    Request.CreateResponse(
        HttpStatusCode.NotFound, 
        myCustomMessage
    )
);

ya, jika Anda membutuhkan versi yang jauh lebih pendek, maka saya rasa Anda perlu menerapkan hasil tindakan kustom Anda.


Saya menggunakan metode ini karena kelihatannya rapi. Saya baru saja mendefinisikan pesan khusus di tempat lain dan kode pengembalian menjorokkan.
ozzy432836

Saya lebih suka ini daripada Konten karena ini benar-benar mengembalikan objek yang bisa saya parse dengan properti Pesan seperti metode standar BadRequest.
pengguna1568891

7

Anda dapat menggunakan properti ReasonPhrase dari kelas HttpResponseMessage

catch (Exception exception)
{
  throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.NotFound)
  {
    ReasonPhrase = exception.Message
  });
}

Terima kasih. Yah .. ini seharusnya berhasil, tetapi kemudian saya harus membangun HttpResponseException saya sendiri dalam setiap tindakan. Untuk mengurangi kodenya, saya berpikir apakah saya bisa menggunakan fitur WebApi 2 (seperti metode NotFount () , Ok () yang sudah jadi) dan meneruskan pesan ReasonPhrase ke sana.
Ajay Jadhav

Anda dapat membuat metode ekstensi Anda sendiri NotFound (pengecualian pengecualian), yang akan menampilkan HttpResponseException yang benar
Dmytro Rudenko

@DmytroRudenko: hasil tindakan diperkenalkan untuk meningkatkan kemampuan untuk diuji. Dengan menampilkan HttpResponseException di sini Anda akan membahayakannya. Juga di sini kami tidak memiliki pengecualian, tetapi OP sedang mencari untuk mengirim kembali pesan.
Kiran Challa

Oke, jika Anda tidak ingin menggunakan NUint untuk pengujian, Anda dapat menulis implementasi NotFoundResult Anda sendiri dan menulis ulang ExecuteAsync-nya untuk mengembalikan data pesan Anda. Dan kembalikan instance kelas ini sebagai hasil dari permintaan tindakan Anda.
Dmytro Rudenko

1
Perhatikan bahwa sekarang Anda dapat mengirimkan kode status secara langsung, misalnya HttpResponseException (HttpStatusCode.NotFound)
Mark Sowul

3

Anda dapat membuat hasil konten kustom yang dinegosiasikan seperti yang disarankan d3m3t3er. Bagaimanapun saya akan mewarisi. Selain itu, jika Anda hanya membutuhkannya untuk menampilkan NotFound, Anda tidak perlu menginisialisasi status http dari konstruktor.

public class NotFoundNegotiatedContentResult<T> : NegotiatedContentResult<T>
{
    public NotFoundNegotiatedContentResult(T content, ApiController controller)
        : base(HttpStatusCode.NotFound, content, controller)
    {
    }

    public override Task<HttpResponseMessage> ExecuteAsync(
        CancellationToken cancellationToken)
    {
        return base.ExecuteAsync(cancellationToken).ContinueWith(
            task => task.Result, cancellationToken);
    }
}

2

Saya menyelesaikannya hanya dengan mengambil dari OkNegotiatedContentResultdan menimpa kode HTTP dalam pesan respons yang dihasilkan. Kelas ini memungkinkan Anda mengembalikan isi konten dengan kode respons HTTP apa pun.

public class CustomNegotiatedContentResult<T> : OkNegotiatedContentResult<T>
{
    public HttpStatusCode HttpStatusCode;

    public CustomNegotiatedContentResult(
        HttpStatusCode httpStatusCode, T content, ApiController controller)
        : base(content, controller)
    {
        HttpStatusCode = httpStatusCode;
    }

    public override Task<HttpResponseMessage> ExecuteAsync(
        CancellationToken cancellationToken)
    {
        return base.ExecuteAsync(cancellationToken).ContinueWith(
            task => { 
                // override OK HTTP status code with our own
                task.Result.StatusCode = HttpStatusCode;
                return task.Result;
            },
            cancellationToken);
    }
}

1

Jika Anda mewarisi dari basis NegotitatedContentResult<T>, seperti yang disebutkan, dan Anda tidak perlu mengubah content(misalnya Anda hanya ingin mengembalikan string), maka Anda tidak perlu mengganti ExecuteAsyncmetode ini.

Yang perlu Anda lakukan adalah memberikan definisi tipe yang sesuai dan konstruktor yang memberi tahu basis Kode Status HTTP mana yang akan dikembalikan. Segala sesuatu yang lain hanya berfungsi.

Berikut adalah contoh untuk keduanya NotFounddan InternalServerError:

public class NotFoundNegotiatedContentResult : NegotiatedContentResult<string>
{
    public NotFoundNegotiatedContentResult(string content, ApiController controller)
        : base(HttpStatusCode.NotFound, content, controller) { }
}

public class InternalServerErrorNegotiatedContentResult : NegotiatedContentResult<string>
{
    public InternalServerErrorNegotiatedContentResult(string content, ApiController controller)
        : base(HttpStatusCode.InternalServerError, content, controller) { }
}

Dan kemudian Anda dapat membuat metode ekstensi yang sesuai untuk ApiController(atau melakukannya di kelas dasar jika Anda memilikinya):

public static NotFoundNegotiatedContentResult NotFound(this ApiController controller, string message)
{
    return new NotFoundNegotiatedContentResult(message, controller);
}

public static InternalServerErrorNegotiatedContentResult InternalServerError(this ApiController controller, string message)
{
    return new InternalServerErrorNegotiatedContentResult(message, controller);
}

Dan kemudian mereka bekerja seperti metode bawaan. Anda dapat memanggil yang sudah ada NotFound()atau Anda dapat memanggil kebiasaan baru Anda NotFound(myErrorMessage).

Dan tentu saja, Anda dapat menyingkirkan jenis string "hard-code" dalam definisi jenis kustom dan membiarkannya generik jika Anda mau, tetapi Anda mungkin harus mengkhawatirkan ExecuteAsynchal - hal tersebut, bergantung pada apa <T>sebenarnya Anda .

Anda dapat melihat di atas kode sumber untuk NegotiatedContentResult<T>melihat semua yang dilakukannya. Tidak banyak untuk itu.


1

Saya perlu membuat IHttpActionResultinstance di badan IExceptionHandlerkelas, untuk mengatur ExceptionHandlerContext.Resultproperti. Namun saya juga ingin mengatur kebiasaan ReasonPhrase.

Saya menemukan bahwa a ResponseMessageResultcould wrap a HttpResponseMessage(yang memungkinkan ReasonPhrase disetel dengan mudah).

Sebagai contoh:

public class MyExceptionHandler : ExceptionHandler
{
    public override void Handle(ExceptionHandlerContext context)
    {
        var ex = context.Exception as IRecordNotFoundException;
        if (ex != null)
        {
            context.Result = new ResponseMessageResult(new HttpResponseMessage(HttpStatusCode.NotFound) { ReasonPhrase = $"{ex.EntityName} not found" });
        }
    }
}

0

Iknow PO bertanya dengan teks pesan, tetapi opsi lain untuk mengembalikan 404 adalah membuat metode mengembalikan IHttpActionResult dan menggunakan fungsi StatusCode

    public async Task<IHttpActionResult> Get([FromUri]string id)
    {
       var item = await _service.GetItem(id);
       if(item == null)
       {
           StatusCode(HttpStatusCode.NotFound);
       }
       return Ok(item);
    }

0

Jawaban di sini kehilangan sedikit masalah cerita pengembang. The ApiControllerkelas masih memperlihatkan NotFound()metode yang pengembang dapat menggunakan. Ini akan menyebabkan sekitar 404 respon mengandung hasil tubuh yang tidak terkontrol.

Saya menyajikan di sini beberapa bagian kode " metode ApiController NotFound yang lebih baik " yang akan memberikan metode rawan kesalahan yang lebih sedikit yang tidak mengharuskan pengembang untuk mengetahui "cara yang lebih baik untuk mengirim 404".

  • buat kelas yang mewarisi dari yangApiController dipanggilApiController
    • Saya menggunakan teknik ini untuk mencegah pengembang menggunakan kelas asli
  • ganti NotFoundmetodenya agar pengembang dapat menggunakan api pertama yang tersedia
  • jika Anda ingin mencegah hal ini, tandai ini sebagai [Obsolete("Use overload instead")]
  • tambahkan ekstra protected NotFoundResult NotFound(string message)yang ingin Anda dorong
  • Masalah: hasilnya tidak mendukung merespon dengan tubuh. solusi: mewarisi dan menggunakan NegotiatedContentResult. lihat terlampir kelas NotFoundResult yang lebih baik .
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.