Lempar HttpResponseException atau kembalikan Request.CreateErrorResponse?

172

Setelah meninjau artikel Penanganan Pengecualian dalam API Web ASP.NET, saya agak bingung kapan harus melempar pengecualian vs mengembalikan respons kesalahan. Saya juga bertanya-tanya apakah mungkin untuk mengubah respons ketika metode Anda mengembalikan model domain tertentu daripada HttpResponseMessage...

Jadi, untuk rekap di sini adalah pertanyaan saya diikuti oleh beberapa kode dengan case #s:

Pertanyaan

Pertanyaan tentang Kasus # 1

  1. Haruskah saya selalu menggunakan HttpResponseMessagemodel domain yang konkret, agar pesannya dapat dikustomisasi?
  2. Bisakah pesan dikustomisasi jika Anda mengembalikan model domain konkret?

Pertanyaan tentang Kasus # 2,3,4

  1. Haruskah saya melempar pengecualian atau mengembalikan respons kesalahan? Jika jawabannya "tergantung", dapatkah Anda memberikan situasi / contoh kapan harus menggunakan satu vs yang lain.
  2. Apa perbedaan antara melempar HttpResponseExceptionvs Request.CreateErrorResponse? Output ke klien tampaknya sama ...
  3. Haruskah saya selalu menggunakan HttpErroruntuk "membungkus" pesan respons dalam kesalahan (apakah pengecualian dilemparkan atau respons kesalahan dikembalikan)?

Sampel Kasus

// CASE #1
public Customer Get(string id)
{
    var customer = _customerService.GetById(id);
    if (customer == null)
    {
        var notFoundResponse = new HttpResponseMessage(HttpStatusCode.NotFound);
        throw new HttpResponseException(notFoundResponse);
    }
    //var response = Request.CreateResponse(HttpStatusCode.OK, customer);
    //response.Content.Headers.Expires = new DateTimeOffset(DateTime.Now.AddSeconds(300));
    return customer;
}        

// CASE #2
public HttpResponseMessage Get(string id)
{
    var customer = _customerService.GetById(id);
    if (customer == null)
    {
        var notFoundResponse = new HttpResponseMessage(HttpStatusCode.NotFound);
        throw new HttpResponseException(notFoundResponse);
    }
    var response = Request.CreateResponse(HttpStatusCode.OK, customer);
    response.Content.Headers.Expires = new DateTimeOffset(DateTime.Now.AddSeconds(300));
    return response;
}

// CASE #3
public HttpResponseMessage Get(string id)
{
    var customer = _customerService.GetById(id);
    if (customer == null)
    {
        var message = String.Format("customer with id: {0} was not found", id);
        var errorResponse = Request.CreateErrorResponse(HttpStatusCode.NotFound, message);
        throw new HttpResponseException(errorResponse);
    }
    var response = Request.CreateResponse(HttpStatusCode.OK, customer);
    response.Content.Headers.Expires = new DateTimeOffset(DateTime.Now.AddSeconds(300));
    return response;
}

// CASE #4
public HttpResponseMessage Get(string id)
{
    var customer = _customerService.GetById(id);
    if (customer == null)
    {
        var message = String.Format("customer with id: {0} was not found", id);
        var httpError = new HttpError(message);
        return Request.CreateErrorResponse(HttpStatusCode.NotFound, httpError);
    }
    var response = Request.CreateResponse(HttpStatusCode.OK, customer);
    response.Content.Headers.Expires = new DateTimeOffset(DateTime.Now.AddSeconds(300));
    return response;
}

Memperbarui

Untuk membantu lebih lanjut mendemonstrasikan kasus # 2,3,4 cuplikan kode berikut ini menyoroti beberapa opsi yang "dapat terjadi" ketika seorang pelanggan tidak ditemukan ...

if (customer == null)
{
    // which of these 4 options is the best strategy for Web API?

    // option 1 (throw)
    var notFoundMessage = new HttpResponseMessage(HttpStatusCode.NotFound);
    throw new HttpResponseException(notFoundMessage);

    // option 2 (throw w/ HttpError)
    var message = String.Format("Customer with id: {0} was not found", id);
    var httpError = new HttpError(message);
    var errorResponse = Request.CreateErrorResponse(HttpStatusCode.NotFound, httpError);
    throw new HttpResponseException(errorResponse);

    // option 3 (return)
    var message = String.Format("Customer with id: {0} was not found", id);
    return Request.CreateErrorResponse(HttpStatusCode.NotFound, message);
    // option 4 (return w/ HttpError)
    var message = String.Format("Customer with id: {0} was not found", id);
    var httpError = new HttpError(message);
    return Request.CreateErrorResponse(HttpStatusCode.NotFound, httpError);
}
zam6ak
sumber
6
@ Mike Wasson Sebagai penulis artikel yang ditautkan, pendekatan mana yang akan Anda ambil?
zam6ak

Jawaban:

102

Pendekatan yang saya ambil adalah dengan hanya melemparkan pengecualian dari tindakan pengontrol api dan mendaftarkan filter pengecualian yang memproses pengecualian dan menetapkan respons yang sesuai pada konteks eksekusi tindakan.

Filter memperlihatkan antarmuka yang lancar yang menyediakan sarana untuk mendaftarkan penangan untuk jenis pengecualian tertentu sebelum mendaftarkan filter dengan konfigurasi global.

Penggunaan filter ini memungkinkan penanganan pengecualian terpusat alih-alih menyebarkannya di seluruh tindakan pengontrol. Namun ada kasus-kasus di mana saya akan menangkap pengecualian dalam aksi pengontrol dan mengembalikan respons tertentu jika tidak masuk akal untuk memusatkan penanganan penanganan pengecualian khusus itu.

Contoh pendaftaran filter:

GlobalConfiguration.Configuration.Filters.Add(
    new UnhandledExceptionFilterAttribute()
    .Register<KeyNotFoundException>(HttpStatusCode.NotFound)

    .Register<SecurityException>(HttpStatusCode.Forbidden)

    .Register<SqlException>(
        (exception, request) =>
        {
            var sqlException = exception as SqlException;

            if (sqlException.Number > 50000)
            {
                var response            = request.CreateResponse(HttpStatusCode.BadRequest);
                response.ReasonPhrase   = sqlException.Message.Replace(Environment.NewLine, String.Empty);

                return response;
            }
            else
            {
                return request.CreateResponse(HttpStatusCode.InternalServerError);
            }
        }
    )
);

Kelas UnhandledExceptionFilterAttribute:

using System;
using System.Collections.Concurrent;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Web.Http.Filters;

namespace Sample
{
    /// <summary>
    /// Represents the an attribute that provides a filter for unhandled exceptions.
    /// </summary>
    public class UnhandledExceptionFilterAttribute : ExceptionFilterAttribute
    {
        #region UnhandledExceptionFilterAttribute()
        /// <summary>
        /// Initializes a new instance of the <see cref="UnhandledExceptionFilterAttribute"/> class.
        /// </summary>
        public UnhandledExceptionFilterAttribute() : base()
        {

        }
        #endregion

        #region DefaultHandler
        /// <summary>
        /// Gets a delegate method that returns an <see cref="HttpResponseMessage"/> 
        /// that describes the supplied exception.
        /// </summary>
        /// <value>
        /// A <see cref="Func{Exception, HttpRequestMessage, HttpResponseMessage}"/> delegate method that returns 
        /// an <see cref="HttpResponseMessage"/> that describes the supplied exception.
        /// </value>
        private static Func<Exception, HttpRequestMessage, HttpResponseMessage> DefaultHandler = (exception, request) =>
        {
            if(exception == null)
            {
                return null;
            }

            var response            = request.CreateResponse<string>(
                HttpStatusCode.InternalServerError, GetContentOf(exception)
            );
            response.ReasonPhrase   = exception.Message.Replace(Environment.NewLine, String.Empty);

            return response;
        };
        #endregion

        #region GetContentOf
        /// <summary>
        /// Gets a delegate method that extracts information from the specified exception.
        /// </summary>
        /// <value>
        /// A <see cref="Func{Exception, String}"/> delegate method that extracts information 
        /// from the specified exception.
        /// </value>
        private static Func<Exception, string> GetContentOf = (exception) =>
        {
            if (exception == null)
            {
                return String.Empty;
            }

            var result  = new StringBuilder();

            result.AppendLine(exception.Message);
            result.AppendLine();

            Exception innerException = exception.InnerException;
            while (innerException != null)
            {
                result.AppendLine(innerException.Message);
                result.AppendLine();
                innerException = innerException.InnerException;
            }

            #if DEBUG
            result.AppendLine(exception.StackTrace);
            #endif

            return result.ToString();
        };
        #endregion

        #region Handlers
        /// <summary>
        /// Gets the exception handlers registered with this filter.
        /// </summary>
        /// <value>
        /// A <see cref="ConcurrentDictionary{Type, Tuple}"/> collection that contains 
        /// the exception handlers registered with this filter.
        /// </value>
        protected ConcurrentDictionary<Type, Tuple<HttpStatusCode?, Func<Exception, HttpRequestMessage, HttpResponseMessage>>> Handlers
        {
            get
            {
                return _filterHandlers;
            }
        }
        private readonly ConcurrentDictionary<Type, Tuple<HttpStatusCode?, Func<Exception, HttpRequestMessage, HttpResponseMessage>>> _filterHandlers = new ConcurrentDictionary<Type, Tuple<HttpStatusCode?, Func<Exception, HttpRequestMessage, HttpResponseMessage>>>();
        #endregion

        #region OnException(HttpActionExecutedContext actionExecutedContext)
        /// <summary>
        /// Raises the exception event.
        /// </summary>
        /// <param name="actionExecutedContext">The context for the action.</param>
        public override void OnException(HttpActionExecutedContext actionExecutedContext)
        {
            if(actionExecutedContext == null || actionExecutedContext.Exception == null)
            {
                return;
            }

            var type    = actionExecutedContext.Exception.GetType();

            Tuple<HttpStatusCode?, Func<Exception, HttpRequestMessage, HttpResponseMessage>> registration = null;

            if (this.Handlers.TryGetValue(type, out registration))
            {
                var statusCode  = registration.Item1;
                var handler     = registration.Item2;

                var response    = handler(
                    actionExecutedContext.Exception.GetBaseException(), 
                    actionExecutedContext.Request
                );

                // Use registered status code if available
                if (statusCode.HasValue)
                {
                    response.StatusCode = statusCode.Value;
                }

                actionExecutedContext.Response  = response;
            }
            else
            {
                // If no exception handler registered for the exception type, fallback to default handler
                actionExecutedContext.Response  = DefaultHandler(
                    actionExecutedContext.Exception.GetBaseException(), actionExecutedContext.Request
                );
            }
        }
        #endregion

        #region Register<TException>(HttpStatusCode statusCode)
        /// <summary>
        /// Registers an exception handler that returns the specified status code for exceptions of type <typeparamref name="TException"/>.
        /// </summary>
        /// <typeparam name="TException">The type of exception to register a handler for.</typeparam>
        /// <param name="statusCode">The HTTP status code to return for exceptions of type <typeparamref name="TException"/>.</param>
        /// <returns>
        /// This <see cref="UnhandledExceptionFilterAttribute"/> after the exception handler has been added.
        /// </returns>
        public UnhandledExceptionFilterAttribute Register<TException>(HttpStatusCode statusCode) 
            where TException : Exception
        {

            var type    = typeof(TException);
            var item    = new Tuple<HttpStatusCode?, Func<Exception, HttpRequestMessage, HttpResponseMessage>>(
                statusCode, DefaultHandler
            );

            if (!this.Handlers.TryAdd(type, item))
            {
                Tuple<HttpStatusCode?, Func<Exception, HttpRequestMessage, HttpResponseMessage>> oldItem = null;

                if (this.Handlers.TryRemove(type, out oldItem))
                {
                    this.Handlers.TryAdd(type, item);
                }
            }

            return this;
        }
        #endregion

        #region Register<TException>(Func<Exception, HttpRequestMessage, HttpResponseMessage> handler)
        /// <summary>
        /// Registers the specified exception <paramref name="handler"/> for exceptions of type <typeparamref name="TException"/>.
        /// </summary>
        /// <typeparam name="TException">The type of exception to register the <paramref name="handler"/> for.</typeparam>
        /// <param name="handler">The exception handler responsible for exceptions of type <typeparamref name="TException"/>.</param>
        /// <returns>
        /// This <see cref="UnhandledExceptionFilterAttribute"/> after the exception <paramref name="handler"/> 
        /// has been added.
        /// </returns>
        /// <exception cref="ArgumentNullException">The <paramref name="handler"/> is <see langword="null"/>.</exception>
        public UnhandledExceptionFilterAttribute Register<TException>(Func<Exception, HttpRequestMessage, HttpResponseMessage> handler) 
            where TException : Exception
        {
            if(handler == null)
            {
              throw new ArgumentNullException("handler");
            }

            var type    = typeof(TException);
            var item    = new Tuple<HttpStatusCode?, Func<Exception, HttpRequestMessage, HttpResponseMessage>>(
                null, handler
            );

            if (!this.Handlers.TryAdd(type, item))
            {
                Tuple<HttpStatusCode?, Func<Exception, HttpRequestMessage, HttpResponseMessage>> oldItem = null;

                if (this.Handlers.TryRemove(type, out oldItem))
                {
                    this.Handlers.TryAdd(type, item);
                }
            }

            return this;
        }
        #endregion

        #region Unregister<TException>()
        /// <summary>
        /// Unregisters the exception handler for exceptions of type <typeparamref name="TException"/>.
        /// </summary>
        /// <typeparam name="TException">The type of exception to unregister handlers for.</typeparam>
        /// <returns>
        /// This <see cref="UnhandledExceptionFilterAttribute"/> after the exception handler 
        /// for exceptions of type <typeparamref name="TException"/> has been removed.
        /// </returns>
        public UnhandledExceptionFilterAttribute Unregister<TException>()
            where TException : Exception
        {
            Tuple<HttpStatusCode?, Func<Exception, HttpRequestMessage, HttpResponseMessage>> item = null;

            this.Handlers.TryRemove(typeof(TException), out item);

            return this;
        }
        #endregion
    }
}

Kode sumber juga dapat ditemukan di sini .

Oposisi
sumber
Wow! :) Ini mungkin sedikit banyak untuk proyek yang lebih kecil, tetapi masih sangat bagus ... BTW, mengapa CreateResponse bukannya CreateErrorResponse di DefaultHandler?
zam6ak
Saya mencoba memisahkan detail kesalahan (bersambung dalam badan) dari frasa alasan; tetapi Anda tentu bisa menggunakan CreateErrorResponse jika itu lebih masuk akal seperti dalam kasus pengikatan model.
Oposisi
1
Karena Anda dapat mendaftarkan filter hanya dengan satu baris kode, saya pikir ini cocok untuk hampir semua jenis proyek. Kami memiliki filter di perpustakaan kelas yang dipublikasikan di umpan NuGet internal kami, sehingga mudah bagi pengembang untuk menggunakannya.
Oposisi
Apa yang Anda gunakan untuk penjaga (buatan sendiri atau pihak ketiga)?
zam6ak
Hoemgrown, saya menghapus penggunaannya pada contoh di atas. Kelas penjaga menyediakan seperangkat metode statis yang menjaga atau memverifikasi bahwa pernyataan telah dipenuhi. Lihat codepaste.net/5oc1if (Penjaga) dan codepaste.net/nsrsei (DelegateInfo) jika Anda menginginkan implementasinya.
Oposisi
23

Jika Anda tidak mengembalikan HttpResponseMessage dan sebagai gantinya mengembalikan kelas entitas / model secara langsung, pendekatan yang menurut saya bermanfaat adalah menambahkan fungsi utilitas berikut ke controller saya.

private void ThrowResponseException(HttpStatusCode statusCode, string message)
{
    var errorResponse = Request.CreateErrorResponse(statusCode, message);
    throw new HttpResponseException(errorResponse);
}

dan cukup menyebutnya dengan kode status dan pesan yang sesuai

Joe King
sumber
4
Ini adalah jawaban yang tepat, ia datang dengan format "Pesan" sebagai pasangan nilai kunci dalam tubuh. Ini biasanya bagaimana saya melihat kerangka kerja dan bahasa lain melakukannya
MobileMon
Saya punya pertanyaan kecil tentang pendekatan ini. Saya mengonsumsi pesan menggunakan sintaks {{}} di halaman angularJS. Jika saya meninggalkan carriage return, mereka datang sebagai n \ r \ dalam pesan. Apa cara yang benar untuk melestarikan mereka?
Naomi
Saya mencoba pendekatan ini. Saya lakukan throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, "Invalid Request Format!")), tetapi di Fiddler, itu menunjukkan status 500 (bukan 400). Ada yang tahu kenapa?
Sam
perbedaannya adalah fungsi induk dari kesalahan. Ini adalah ThrowResponseException untuk pengecualian dalam aplikasi. Tetapi seharusnya fungsi yang sebenarnya melempar pengecualian ...
Serge
15

Kasus 1

  1. Belum tentu, ada tempat lain dalam pipa untuk memodifikasi respons (filter tindakan, penangan pesan).
  2. Lihat di atas - tetapi jika tindakan mengembalikan model domain, maka Anda tidak dapat mengubah respons di dalam tindakan.

Kasing # 2-4

  1. Alasan utama untuk melempar HttpResponseException adalah:
    • jika Anda mengembalikan model domain tetapi perlu menangani kasus kesalahan,
    • untuk menyederhanakan logika pengontrol Anda dengan memperlakukan kesalahan sebagai pengecualian
  2. Ini harus setara; HttpResponseException merangkum HttpResponseMessage, yang akan dikembalikan sebagai respons HTTP.

    misalnya, case # 2 dapat ditulis ulang sebagai

    public HttpResponseMessage Get(string id)
    {
        HttpResponseMessage response;
        var customer = _customerService.GetById(id);
        if (customer == null)
        {
            response = new HttpResponseMessage(HttpStatusCode.NotFound);
        }
        else
        {
            response = Request.CreateResponse(HttpStatusCode.OK, customer);
            response.Content.Headers.Expires = new DateTimeOffset(DateTime.Now.AddSeconds(300));
        }
        return response;
    }

    ... tetapi jika logika pengontrol Anda lebih rumit, melempar pengecualian mungkin menyederhanakan aliran kode.

  3. HttpError memberi Anda format yang konsisten untuk badan respons dan dapat diserialisasi ke JSON / XML / etc, tetapi itu tidak diperlukan. misalnya, Anda mungkin tidak ingin memasukkan entitas-badan dalam respons, atau Anda mungkin menginginkan beberapa format lain.

Mike Wasson
sumber
Pendekatan yang saya ambil adalah dengan hanya membuang pengecualian dari tindakan pengontrol api dan saya telah mendaftarkan filter pengecualian yang memproses pengecualian dan menetapkan respons yang sesuai pada konteks eksekusi tindakan. Filter ini 'pluggable' sehingga saya bisa mendaftarkan penangan untuk jenis pengecualian tertentu sebelum mendaftarkan filter dengan konfigurasi global. Ini memungkinkan saya untuk melakukan penanganan pengecualian terpusat alih-alih menyebarkannya di seluruh pengontrol.
Oposisi
@Opposisi Apakah Anda berkesempatan berbagi filter pengecualian? Mungkin sebagai Intisari atau di situs kode berbagi seperti CodePaste?
Paige Cook
@ Mike Wasson akankah Anda mengatakan bahwa "return error response" adalah pendekatan yang lebih umum vs "throw exception"? Saya mengerti secara fungsional hasil akhirnya mungkin sama, tetapi saya bertanya-tanya mengapa tidak hanya mencakup seluruh logika pengontrol dalam mencoba / menangkap dan mengembalikan respons kesalahan yang sesuai?
zam6ak
15

Jangan melempar HttpResponseException atau mengembalikan HttpResponesMessage untuk kesalahan - kecuali jika tujuannya adalah untuk mengakhiri permintaan dengan hasil yang tepat .

HttpResponseException tidak ditangani sama dengan pengecualian lainnya . Mereka tidak terperangkap dalam Filter Pengecualian . Mereka tidak terperangkap dalam Exception Handler . Mereka adalah cara licik untuk memasukkan HttpResponseMessage sambil menghentikan aliran eksekusi kode saat ini.

Kecuali jika kode tersebut adalah kode infrastruktur yang mengandalkan penanganan khusus yang tidak ditangani ini, hindari menggunakan jenis HttpResponseException!

HttpResponseMessage bukan pengecualian. Mereka tidak menghentikan aliran eksekusi kode saat ini. Mereka tidak bisa difilter sebagai pengecualian. Mereka tidak dapat dicatat sebagai pengecualian. Mereka mewakili hasil yang valid - bahkan respons 500 adalah "respons non-pengecualian yang valid"!


Jadikan hidup lebih sederhana:

Ketika ada kasus luar biasa / kesalahan, lanjutkan dan lemparkan pengecualian .NET yang normal - atau jenis pengecualian aplikasi yang disesuaikan ( tidak berasal dari HttpResponseException) dengan properti 'kesalahan / respons' yang diinginkan seperti kode status - sesuai dengan pengecualian normal penanganan .

Gunakan Pengecualian Filter / Pengecualian Pengecualian / Pencatat Eksepsi untuk melakukan sesuatu yang sesuai dengan kasus luar biasa ini: mengubah / menambah kode status? tambahkan pengidentifikasi pelacakan? termasuk jejak tumpukan? catatan?

Dengan menghindari HttpResponseException , penanganan 'kasus luar biasa' dibuat seragam dan dapat ditangani sebagai bagian dari pipa yang terbuka! Misalnya seseorang dapat mengubah 'NotFound' menjadi 404 dan 'ArgumentException' menjadi 400 dan 'NullReference' menjadi 500 dengan mudah dan seragam dengan pengecualian tingkat aplikasi - sambil memungkinkan ekstensibilitas untuk memberikan "dasar-dasar" seperti kesalahan logging.

pengguna2864740
sumber
2
Saya mengerti mengapa ArgumentExceptions di controller secara logis menjadi 400, tapi bagaimana dengan ArgumentExceptions lebih dalam stack? Itu tidak selalu benar untuk mengubah ini menjadi 400, namun jika Anda memiliki filter yang mengubah selimut semua ArgumentExceptionmenjadi 400, satu-satunya cara untuk menghindari itu adalah dengan menangkap pengecualian di controller dan melempar kembali sesuatu yang lain, yang tampaknya untuk mengalahkan tujuan penanganan pengecualian seragam dalam filter atau serupa.
cmeeren
@ cmeeren Dalam kode yang saya tangani, sebagian besar menangkap pengecualian dan mengubahnya menjadi HttpResponse [Pengecualian / Pesan] di setiap metode web. Kedua kasus adalah sama , dalam hal ini jika perhatiannya adalah untuk melakukan sesuatu yang berbeda dengan pengecualian internal yang dilakukan melakukan "sesuatu" dengan pengecualian internal yang tertangkap: Saya merekomendasikan hasilnya adalah untuk melemparkan pengecualian pembungkus yang sesuai yang masih ditangani lebih lanjut. tumpukan.
user2864740
@cmeeren Setelah pembaruan, sebagian besar titik entri web kami melempar turunan khusus (non-HttpResponseException, yang memiliki dan atau dipetakan ke kode respons yang sesuai) terhadap kesalahan penggunaan. Penangan yang seragam dapat melakukan beberapa inspeksi tumpukan (icky, tetapi ia bekerja dengan hati-hati) untuk menentukan pada tingkat mana pengecualian itu berasal - yaitu. mencakup 99% dari kasus yang tidak memiliki penanganan yang lebih halus - atau hanya merespons dengan 500 untuk kesalahan internal. Inti dari HttpResponseException adalah bahwa ia mem-bypass pemrosesan pipa yang berguna.
user2864740
9

Kasus lain kapan harus digunakan HttpResponseExceptionalih-alih Response.CreateResponse(HttpStatusCode.NotFound), atau kode status kesalahan lainnya, adalah jika Anda memiliki transaksi dalam filter tindakan dan Anda ingin transaksi tersebut dibatalkan saat mengembalikan respons kesalahan ke klien.

Menggunakan Response.CreateResponsetidak akan mengembalikan transaksi, sedangkan melemparkan pengecualian akan.

Rob Gray
sumber
3

Saya ingin menunjukkan bahwa sudah pengalaman saya bahwa jika melempar HttpResponseException alih-alih mengembalikan HttpResponseMessage dalam metode webapi 2, bahwa jika panggilan dilakukan segera ke IIS Express, ia akan kehabisan waktu atau mengembalikan 200 tetapi dengan kesalahan html di responnya. Cara termudah untuk menguji ini adalah dengan membuat $ .ajax panggilan ke metode yang melempar HttpResponseException dan di errorCallBack di ajax membuat panggilan langsung ke metode lain atau bahkan halaman http sederhana. Anda akan melihat panggilan langsung akan gagal. Jika Anda menambahkan break point atau settimeout () di panggilan kesalahan kembali untuk menunda panggilan kedua satu atau dua detik, memberikan waktu server untuk memulihkannya berfungsi dengan benar.

Memperbarui:Akar penyebab timeout koneksi Ajax aneh adalah jika panggilan ajax dibuat cukup cepat koneksi tcp yang sama digunakan. Saya meningkatkan 401 kesalahan eter dengan mengembalikan HttpResonseMessage atau melempar HTTPResponseException yang dikembalikan ke panggilan browser ajax. Tetapi bersamaan dengan panggilan itu, MS mengembalikan Objek Tidak Ditemukan Kesalahan karena dalam aplikasi Startup.Auth.vb.UserCookieAuthentication diaktifkan sehingga berusaha untuk kembali mencegat respons dan menambahkan arahan ulang tetapi kesalahan dengan Obyek bukan Instance dari Obyek. Kesalahan ini adalah html tetapi ditambahkan ke respons setelah fakta jadi hanya jika panggilan ajax dibuat cukup cepat dan koneksi tcp yang sama digunakan kembali ke browser dan kemudian ditambahkan ke depan panggilan berikutnya. Untuk beberapa alasan Chrome hanya batas waktu, Fiddler memilih karena campuran json dan htm tetapi firefox membalik kesalahan sebenarnya. Sangat aneh tetapi packet sniffer atau firefox adalah satu-satunya cara untuk melacak yang satu ini.

Juga harus dicatat bahwa jika Anda menggunakan bantuan Web API untuk menghasilkan bantuan otomatis dan Anda mengembalikan HttpResponseMessage maka Anda harus menambahkan

[System.Web.Http.Description.ResponseType(typeof(CustomReturnedType))] 

atribut ke metode ini sehingga bantuan menghasilkan dengan benar. Kemudian

return Request.CreateResponse<CustomReturnedType>(objCustomeReturnedType) 

atau kesalahan

return Request.CreateErrorResponse( System.Net.HttpStatusCode.InternalServerError, new Exception("An Error Ocurred"));

Semoga ini bisa membantu orang lain yang mungkin mendapatkan batas waktu acak atau server tidak tersedia segera setelah melempar HttpResponseException.

Juga mengembalikan HttpResponseException memiliki manfaat tambahan yaitu tidak menyebabkan Visual Studio merusak pengecualian yang tidak ditangani, berguna ketika kesalahan dikembalikan adalah AuthToken perlu di-refresh dalam satu halaman aplikasi.

Pembaruan: Saya mencabut pernyataan saya tentang waktu habis IIS Express, ini kebetulan merupakan kesalahan di sisi klien saya ajax panggilan balik ternyata sejak Ajax 1,8 mengembalikan $ .ajax () dan mengembalikan $ .ajax. (). Then () keduanya mengembalikan janji tetapi tidak dirantai janji yang sama kemudian () mengembalikan janji baru yang menyebabkan urutan eksekusi salah. Jadi, ketika janji () selesai, itu adalah batas waktu skrip. Gotcha aneh tapi bukan masalah IIS express masalah antara Keyboard dan kursi.

Andrew DeVries
sumber
0

Sejauh yang saya tahu, apakah Anda melempar pengecualian, atau Anda mengembalikan Request.CreateErrorResponse, hasilnya sama. Jika Anda melihat kode sumber untuk System.Web.Http.dll, Anda akan melihat sebanyak itu. Lihatlah ringkasan umum ini, dan solusi yang sangat mirip yang telah saya buat: Web Api, HttpError, dan perilaku pengecualian

Andy Cohen
sumber
0

Dalam situasi kesalahan, saya ingin mengembalikan kelas detail kesalahan tertentu, dalam format apa pun yang diminta klien alih-alih objek happy path.

Saya ingin agar metode pengontrol saya mengembalikan objek path domain khusus dan melemparkan pengecualian.

Masalah yang saya miliki adalah bahwa konstruktor HttpResponseException tidak mengizinkan objek domain.

Inilah yang akhirnya saya temukan

public ProviderCollection GetProviders(string providerName)
{
   try
   {
      return _providerPresenter.GetProviders(providerName);
   }
   catch (BadInputValidationException badInputValidationException)
   {
     throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.BadRequest,
                                          badInputValidationException.Result));
   }
}

Resultadalah kelas yang berisi detail kesalahan, sementara itu ProviderCollectionadalah hasil jalur bahagia saya.

NickBeaugié
sumber
0

saya suka jawaban oposisi

Lagi pula, saya membutuhkan cara untuk menangkap Pengecualian yang diwarisi dan solusi itu tidak memenuhi semua kebutuhan saya.

Jadi saya akhirnya mengubah cara dia menangani OnException dan ini adalah versi saya

public override void OnException(HttpActionExecutedContext actionExecutedContext) {
   if (actionExecutedContext == null || actionExecutedContext.Exception == null) {
      return;
   }

   var type = actionExecutedContext.Exception.GetType();

   Tuple<HttpStatusCode?, Func<Exception, HttpRequestMessage, HttpResponseMessage>> registration = null;

   if (!this.Handlers.TryGetValue(type, out registration)) {
      //tento di vedere se ho registrato qualche eccezione che eredita dal tipo di eccezione sollevata (in ordine di registrazione)
      foreach (var item in this.Handlers.Keys) {
         if (type.IsSubclassOf(item)) {
            registration = this.Handlers[item];
            break;
         }
      }
   }

   //se ho trovato un tipo compatibile, uso la sua gestione
   if (registration != null) {
      var statusCode = registration.Item1;
      var handler = registration.Item2;

      var response = handler(
         actionExecutedContext.Exception.GetBaseException(),
         actionExecutedContext.Request
      );

      // Use registered status code if available
      if (statusCode.HasValue) {
         response.StatusCode = statusCode.Value;
      }

      actionExecutedContext.Response = response;
   }
   else {
      // If no exception handler registered for the exception type, fallback to default handler
      actionExecutedContext.Response = DefaultHandler(actionExecutedContext.Exception.GetBaseException(), actionExecutedContext.Request
      );
   }
}

Inti adalah loop ini di mana saya memeriksa apakah tipe pengecualian adalah subkelas dari tipe terdaftar.

foreach (var item in this.Handlers.Keys) {
    if (type.IsSubclassOf(item)) {
        registration = this.Handlers[item];
        break;
    }
}

my2cents

Tidak penting
sumber