โยน HttpResponseException หรือส่งคืน Request สร้าง CreateErrorResponse ไหม?


172

หลังจากตรวจสอบข้อยกเว้นการจัดการบทความใน ASP.NET Web APIฉันสับสนนิดหน่อยว่าเมื่อใดที่จะโยนข้อยกเว้น vs ส่งคืนการตอบกลับข้อผิดพลาด ฉันยังสงสัยว่ามันเป็นไปได้ที่จะปรับเปลี่ยนการตอบสนองเมื่อวิธีการของคุณส่งคืนรูปแบบเฉพาะโดเมนแทนHttpResponseMessage...

ดังนั้นการสรุปที่นี่คำถามของฉันตามด้วยรหัสบางกรณี #s:

คำถาม

คำถามเกี่ยวกับ Case # 1

  1. ฉันควรใช้HttpResponseMessageแทนโมเดลโดเมนที่เป็นรูปธรรมเสมอเพื่อให้สามารถปรับแต่งข้อความได้หรือไม่
  2. สามารถปรับแต่งข้อความได้หรือไม่หากคุณส่งคืนโมเดลโดเมนที่เป็นรูปธรรม?

คำถามเกี่ยวกับ Case # 2,3,4

  1. ฉันควรจะโยนข้อยกเว้นหรือตอบกลับข้อผิดพลาด? หากคำตอบคือ "มันขึ้นอยู่กับ" คุณสามารถให้สถานการณ์ / ตัวอย่างเมื่อใดที่จะใช้หนึ่งเทียบกับอีก
  2. ความแตกต่างระหว่างการขว้างปาคืออะไรHttpResponseExceptionVS Request.CreateErrorResponse? ผลลัพธ์ไปยังลูกค้าดูเหมือนกัน ...
  3. ฉันควรจะใช้HttpErrorเพื่อ "ห่อ" ข้อความตอบสนองในข้อผิดพลาด (ไม่ว่าจะเป็นข้อยกเว้นโยนหรือการตอบกลับข้อผิดพลาด)?

ตัวอย่างกรณี

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

ปรับปรุง

เพื่อช่วยสาธิตเพิ่มเติมกรณี # 2,3,4 ข้อมูลโค้ดต่อไปนี้เน้นตัวเลือกหลายอย่างที่ "สามารถเกิดขึ้นได้" เมื่อไม่พบลูกค้า ...

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

6
@Mike Wasson ในฐานะผู้เขียนบทความที่เชื่อมโยงคุณจะใช้วิธีใด
zam6ak

คำตอบ:


102

วิธีที่ฉันได้ทำคือเพียงแค่โยนข้อยกเว้นจากการดำเนินการควบคุม api และมีการลงทะเบียนตัวกรองข้อยกเว้นที่ประมวลผลข้อยกเว้นและตั้งค่าการตอบสนองที่เหมาะสมในบริบทการดำเนินการการกระทำ

ตัวกรองแสดงอินเทอร์เฟซที่คล่องแคล่วที่ให้วิธีการลงทะเบียนตัวจัดการสำหรับชนิดของข้อยกเว้นเฉพาะก่อนที่จะลงทะเบียนตัวกรองด้วยการกำหนดค่าส่วนกลาง

การใช้ตัวกรองนี้ช่วยให้สามารถจัดการข้อยกเว้นจากศูนย์กลางแทนการแพร่กระจายผ่านการดำเนินการของคอนโทรลเลอร์ อย่างไรก็ตามมีบางกรณีที่ฉันจะตรวจจับข้อยกเว้นภายในแอคชั่นคอนโทรลเลอร์และส่งคืนการตอบกลับเฉพาะหากไม่เหมาะสมที่จะรวมการจัดการข้อยกเว้นนั้นไว้ที่ส่วนกลาง

ตัวอย่างการลงทะเบียนตัวกรอง:

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

คลาส 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
    }
}

รหัสที่มานอกจากนี้ยังสามารถพบได้ที่นี่


ว้าว! :) นี่อาจจะเล็กน้อยสำหรับโครงการขนาดเล็ก แต่ก็ยังดีมาก ... BTW ทำไม CreateResponse แทนที่จะเป็น CreateErrorResponse ใน DefaultHandler?
zam6ak

ฉันพยายามแยกรายละเอียดข้อผิดพลาด (เรียงลำดับตามเนื้อความ) ออกจากวลีเหตุผล แต่คุณสามารถใช้ CreateErrorResponse ได้อย่างแน่นอนถ้ามันสมเหตุสมผลมากกว่าในกรณีที่มีการเชื่อมโยงโมเดล
ตรงข้าม

1
เนื่องจากคุณสามารถลงทะเบียนตัวกรองได้ด้วยรหัสเพียงบรรทัดเดียวฉันคิดว่ามันเหมาะสำหรับเกือบทุกประเภทโครงการ เรามีตัวกรองในไลบรารีคลาสที่มีการเผยแพร่ในฟีด NuGet ภายในของเราดังนั้นจึงเป็นเรื่องง่ายสำหรับนักพัฒนาที่จะใช้
ตรงข้าม

สิ่งที่คุณใช้สำหรับยาม (พื้นบ้านหรือบุคคลที่สาม)?
zam6ak

Hoemgrown ฉันลบการใช้งานในตัวอย่างข้างต้น คลาส Guard จัดเตรียมชุดของวิธีการแบบคงที่ที่ป้องกันหรือตรวจสอบว่าได้รับการยืนยันแล้ว ดูcodepaste.net/5oc1if (Guard) และcodepaste.net/nsrsei (DelegateInfo) หากคุณต้องการใช้งาน
ตรงข้าม

23

หากคุณไม่ได้ส่งคืนHttpResponseMessageและส่งคืนคลาสเอนทิตี้ / โมเดลโดยตรงแนวทางที่ฉันพบว่ามีประโยชน์คือการเพิ่มฟังก์ชั่นยูทิลิตี้ต่อไปนี้ให้กับคอนโทรลเลอร์ของฉัน

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

และเพียงแค่เรียกมันด้วยรหัสสถานะและข้อความที่เหมาะสม


4
นี่คือคำตอบที่ถูกต้องมันมาพร้อมกับรูปแบบ "ข้อความ" เป็นคู่ของค่าคีย์ในเนื้อหา นี่เป็นวิธีที่ฉันเห็นกรอบและภาษาอื่น ๆ ทำ
MobileMon

ฉันมีคำถามเล็กน้อยเกี่ยวกับวิธีการนี้ ฉันใช้ข้อความโดยใช้ {{}} ไวยากรณ์ในหน้า angularJS ถ้าฉันปล่อยให้รถคืนพวกเขามาในฐานะ n \ r \ ในข้อความ วิธีที่เหมาะสมในการรักษาพวกเขาคืออะไร?
นาโอมิ

ฉันลองวิธีนี้ ฉันทำthrow new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, "Invalid Request Format!"))แต่ในพู้ทำเล่นมันแสดงสถานะ 500 (ไม่ใช่ 400) มีความคิดอะไรไหม
แซม

ข้อแตกต่างคือฟังก์ชั่นหลักของข้อผิดพลาด นี่คือ ThrowResponseException สำหรับข้อยกเว้นใด ๆ ในแอปพลิเคชัน แต่ควรเป็นหน้าที่จริงที่ทำให้เกิดข้อยกเว้น ...
Serge

15

กรณี # 1

  1. ไม่จำเป็นต้องมีสถานที่อื่นในไปป์ไลน์เพื่อแก้ไขการตอบสนอง (ตัวกรองการกระทำตัวจัดการข้อความ)
  2. ดูด้านบน - แต่หากการดำเนินการส่งคืนโมเดลโดเมนคุณจะไม่สามารถแก้ไขการตอบกลับภายในการดำเนินการได้

เคส # 2-4

  1. เหตุผลหลักในการขว้าง HttpResponseException คือ:
    • หากคุณส่งคืนโมเดลโดเมน แต่จำเป็นต้องจัดการกับกรณีที่มีข้อผิดพลาด
    • เพื่อทำให้ตรรกะคอนโทรลเลอร์ของคุณง่ายขึ้นโดยถือข้อผิดพลาดเป็นข้อยกเว้น
  2. สิ่งเหล่านี้ควรจะเทียบเท่า HttpResponseException สรุป HttpResponseMessage ซึ่งเป็นสิ่งที่ได้รับคืนกลับมาเป็น HTTP การตอบสนอง

    เช่นกรณีที่ # 2 สามารถเขียนใหม่เป็น

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

    ... แต่ถ้าลอจิกตัวควบคุมของคุณมีความซับซ้อนมากขึ้นการโยนข้อยกเว้นอาจทำให้การไหลของรหัสง่ายขึ้น

  3. HttpError ให้รูปแบบที่สอดคล้องกันสำหรับเนื้อความการตอบสนองและสามารถต่อเนื่องเป็น JSON / XML / etc แต่ไม่จำเป็น เช่นคุณอาจไม่ต้องการรวมนิติบุคคลในการตอบกลับหรือคุณอาจต้องการรูปแบบอื่น


วิธีที่ฉันได้ทำคือเพียงแค่โยนข้อยกเว้นจากการกระทำของตัวควบคุม api และฉันได้ลงทะเบียนตัวกรองข้อยกเว้นที่ประมวลผลข้อยกเว้นและตั้งค่าการตอบสนองที่เหมาะสมในบริบทการดำเนินการการกระทำ ตัวกรองนั้น 'เสียบปลั๊กได้' ซึ่งฉันสามารถลงทะเบียนตัวจัดการสำหรับชนิดของข้อยกเว้นเฉพาะก่อนที่จะลงทะเบียนตัวกรองด้วยการกำหนดค่าส่วนกลาง สิ่งนี้ทำให้ฉันสามารถจัดการข้อยกเว้นจากศูนย์กลางแทนการแพร่กระจายข้ามคอนโทรลเลอร์
ตรงข้าม

@Oppositional โอกาสใด ๆ ที่คุณยินดีที่จะแชร์ตัวกรองข้อยกเว้นของคุณ? อาจจะเป็นส่วนสำคัญหรือในเว็บไซต์แบ่งปันรหัสเช่น CodePaste?
Paige Cook

@ ไมค์ Wasson คุณจะบอกว่า "การตอบกลับข้อผิดพลาด" เป็นวิธีการทั่วไปมากขึ้นเทียบกับ "โยนข้อยกเว้น"? ฉันเข้าใจการใช้งานจริงผลลัพธ์ที่ได้อาจเป็นเหมือนกัน แต่ฉันสงสัยว่าทำไมไม่รวมตรรกะตัวควบคุมทั้งหมดในการลอง / จับและการตอบกลับข้อผิดพลาดตามความเหมาะสม?
zam6ak

15

อย่าส่ง HttpResponseException หรือส่งกลับ HttpResponesMessage เพื่อหาข้อผิดพลาด - ยกเว้นว่าเจตนาที่จะยุติการร้องขอด้วยผลลัพธ์ที่แน่นอนนั้น

HttpResponseException จะไม่จัดการเช่นเดียวกับข้อยกเว้นอื่นพวกเขาจะไม่ได้ติดอยู่ในตัวกรองข้อยกเว้น พวกเขาจะไม่ได้ถูกจับในการจัดการข้อยกเว้น พวกเขาเป็นวิธีหลบหลีกใน HttpResponseMessage ในขณะที่การยกเลิกการไหลของการดำเนินการของรหัสปัจจุบัน

ยกเว้นว่ารหัสนั้นเป็นรหัสโครงสร้างพื้นฐานที่ใช้การจัดการแบบพิเศษนี้ให้หลีกเลี่ยงการใช้ประเภท HttpResponseException!

HttpResponseMessage ไม่ใช่ข้อยกเว้น พวกเขาไม่ได้ยุติการไหลของการดำเนินการของรหัสปัจจุบัน พวกเขาสามารถไม่ถูกกรองเป็นข้อยกเว้น พวกเขาไม่สามารถเข้าสู่ระบบเป็นข้อยกเว้น สิ่งเหล่านี้แสดงถึงผลลัพธ์ที่ถูกต้องแม้การตอบสนอง 500 ครั้งก็เป็น "การตอบกลับที่ไม่ใช่ข้อยกเว้นที่ถูกต้อง"!


ทำให้ชีวิตง่ายขึ้น:

เมื่อมีกรณีพิเศษ / ข้อผิดพลาดไปข้างหน้าและโยนข้อยกเว้น. NET ปกติ - หรือประเภทข้อยกเว้นแอปพลิเคชันที่กำหนดเอง ( ไม่ได้มาจาก HttpResponseException) พร้อมคุณสมบัติ 'ข้อผิดพลาด / ตอบกลับ' ที่ต้องการเช่นรหัสสถานะ - ตามข้อยกเว้นปกติ การจัดการ

ใช้ตัวกรองข้อยกเว้น / ตัวจัดการข้อยกเว้น / ตัวบันทึกข้อยกเว้นเพื่อทำสิ่งที่เหมาะสมกับกรณีพิเศษเหล่านี้: เปลี่ยน / เพิ่มรหัสสถานะ? เพิ่มตัวระบุการติดตามหรือไม่ รวมการติดตามสแต็กหรือไม่ เข้าสู่ระบบ?

ด้วยการหลีกเลี่ยง HttpResponseException การจัดการ 'กรณีพิเศษ' จึงเป็นรูปแบบที่เหมือนกันและสามารถจัดการได้เป็นส่วนหนึ่งของระบบท่อที่เปิดเผย! ตัวอย่างเช่นหนึ่งสามารถเปลี่ยน 'NotFound' เป็น 404 และ 'ArgumentException' เป็น 400 และ 'NullReference' เป็น 500 ได้อย่างง่ายดายและสม่ำเสมอด้วยข้อยกเว้นระดับแอปพลิเคชัน - ในขณะที่อนุญาตให้ขยายได้เพื่อให้ "พื้นฐาน" เช่นการบันทึกข้อผิดพลาด


2
ฉันเข้าใจว่าทำไมArgumentExceptionในตัวควบคุมจะมีเหตุผลเป็น 400 แต่สิ่งที่ArgumentExceptionลึกลงไปในกองซ้อน? มันไม่ถูกต้องที่จะเปลี่ยนสิ่งเหล่านี้เป็น 400 แต่ถ้าคุณมีตัวกรองที่ครอบคลุมการแปลงทั้งหมดArgumentExceptionเป็น 400 วิธีเดียวที่จะหลีกเลี่ยงได้คือการจับข้อยกเว้นในตัวควบคุมและโยนสิ่งอื่นซึ่งดูเหมือนว่า เพื่อเอาชนะวัตถุประสงค์ของการจัดการข้อยกเว้นอย่างสม่ำเสมอในตัวกรองหรือคล้ายกัน
cmeeren

@cmeeren ในรหัสที่ฉันจัดการมันส่วนใหญ่จะจับข้อยกเว้นและเปลี่ยนเป็น HttpResponse [ยกเว้น / ข้อความ] ในแต่ละวิธีการเว็บ ทั้งสองกรณีเหมือนกันคือในกรณีที่ข้อกังวลคือการทำสิ่งที่แตกต่างกับข้อยกเว้นภายในที่ทำ "สิ่ง" ที่มีข้อยกเว้นภายในที่ถูกจับ: ฉันขอแนะนำผลที่ได้คือการโยนข้อยกเว้นการตัดที่เหมาะสม ซ้อนกัน.
user2864740

@cmeeren หลังจากอัปเดตจุดเข้าใช้งานเว็บส่วนใหญ่ของเราจะได้รับพิเศษ (ไม่ใช่ HttpResponseException ซึ่งมีและหรือถูกแมปกับรหัสตอบกลับที่เหมาะสม) กับข้อผิดพลาดในการใช้งาน ตัวจัดการเครื่องแบบสามารถทำการตรวจสอบสแต็ก (icky แต่ทำงานด้วยความระมัดระวัง) เพื่อกำหนดระดับที่ข้อยกเว้นมาจาก - เช่น ครอบคลุม 99% ของกรณีที่ไม่มีการจัดการที่ละเอียดมากขึ้นหรือเพียงแค่ตอบสนองต่อข้อผิดพลาดภายใน 500 ข้อ crux พร้อม HttpResponseException คือว่ามันข้ามการประมวลผลไปป์ไลน์ที่มีประโยชน์
user2864740

9

อีกกรณีหนึ่งสำหรับเมื่อใช้HttpResponseExceptionแทนResponse.CreateResponse(HttpStatusCode.NotFound)หรือรหัสสถานะข้อผิดพลาดอื่นคือถ้าคุณมีธุรกรรมในตัวกรองการดำเนินการและคุณต้องการให้ธุรกรรมถูกย้อนกลับเมื่อส่งกลับการตอบสนองข้อผิดพลาดไปยังไคลเอนต์

การใช้Response.CreateResponseจะไม่ย้อนกลับธุรกรรมในขณะที่โยนข้อยกเว้นจะ


3

ฉันต้องการชี้ให้เห็นว่ามันเป็นประสบการณ์ของฉันว่าถ้าทิ้ง HttpResponseException แทนที่จะส่งกลับ HttpResponseMessage ในวิธี webapi 2 ว่าถ้ามีการโทรทันทีไปยัง IIS Express มันจะหมดเวลาหรือส่งคืน 200 แต่มีข้อผิดพลาด html ใน การตอบสนอง. วิธีที่ง่ายที่สุดในการทดสอบนี้คือการโทร $ .ajax ไปยังวิธีการที่เรียกใช้ HttpResponseException และ errorCallBack ใน ajax ทำการโทรหาวิธีอื่นทันทีหรือแม้แต่หน้า http ง่ายๆ คุณจะสังเกตเห็นว่าการโทรเลียนแบบจะล้มเหลว หากคุณเพิ่มจุดพักหรือ settimeout () ในข้อผิดพลาดการโทรกลับเพื่อชะลอการโทรครั้งที่สองหรือสองให้เวลาเซิร์ฟเวอร์เพื่อกู้คืนการทำงานอย่างถูกต้อง

ปรับปรุง:สาเหตุหลักของการหมดเวลาการเชื่อมต่อ Ajax ที่แปลกประหลาดคือหากมีการโทร ajax อย่างรวดเร็วเพียงพอที่จะใช้การเชื่อมต่อ tcp เดียวกัน ฉันกำลังเพิ่มอีเธอร์ข้อผิดพลาด 401 โดยส่งคืน HttpResonseMessage หรือส่ง HTTPResponseException ซึ่งกลับไปที่การโทร ajax ของเบราว์เซอร์ แต่พร้อมกับการเรียก MS นั้นส่งคืนข้อผิดพลาด Object Not Found เนื่องจากใน Startup.Auth.vb app.UserCookieAuthentication ถูกเปิดใช้งานดังนั้นจึงพยายามคืนการสกัดกั้นการตอบสนองและเพิ่มการเปลี่ยนเส้นทาง แต่เกิดข้อผิดพลาดกับ Object ไม่ใช่อินสแตนซ์ของวัตถุ ข้อผิดพลาดนี้เป็น html แต่ถูกผนวกเข้ากับการตอบสนองหลังจากข้อเท็จจริงดังนั้นหากการโทร ajax นั้นเร็วพอและการเชื่อมต่อ tcp เดียวกับที่ใช้มันส่งกลับไปยังเบราว์เซอร์แล้วต่อท้ายด้านหน้าของการโทรครั้งถัดไป ด้วยเหตุผลบางอย่าง Chrome เพิ่งหมดเวลา นักไวโอลินซุกซนเนื่องจากการผสมของ json และ htm แต่ firefox rturned ข้อผิดพลาดจริง ดังนั้นแปลก แต่แพ็คเก็ตดมกลิ่นหรือ firefox เป็นวิธีเดียวที่จะติดตามอันนี้ลง

นอกจากนี้ควรสังเกตว่าถ้าคุณใช้ Web API ช่วยในการสร้างวิธีใช้อัตโนมัติและคุณกลับ HttpResponseMessage คุณควรเพิ่ม

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

คุณลักษณะของวิธีการเพื่อช่วยสร้างอย่างถูกต้อง แล้วก็

return Request.CreateResponse<CustomReturnedType>(objCustomeReturnedType) 

หรือผิดพลาด

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

หวังว่าสิ่งนี้จะช่วยให้คนอื่นที่อาจได้รับการหมดเวลาแบบสุ่มหรือเซิร์ฟเวอร์ไม่สามารถใช้งานได้ทันทีหลังจากการขว้าง HttpResponseException

การส่งคืน HttpResponseException นั้นมีประโยชน์เพิ่มเติมที่จะไม่ทำให้ Visual Studio ทำลายข้อยกเว้นที่ไม่ได้รับการจัดการที่มีประโยชน์เมื่อข้อผิดพลาดที่ส่งคืนคือ AuthToken จำเป็นต้องรีเฟรชในแอปหน้าเดียว

อัปเดต: ฉันกำลังถอนคำสั่งของฉันเกี่ยวกับการหมดเวลาของ IIS Express นี่เป็นความผิดพลาดในฝั่งไคลเอ็นต์ของฉัน ajax โทรกลับมันปรากฏว่าตั้งแต่ Ajax 1.8 ส่งคืน $ .ajax () และส่งกลับ $ .ajax. () แล้ว () ทั้งสัญญาส่งคืน แต่ไม่เหมือนสัญญาที่ถูกล่ามโซ่เดียวกัน () ส่งคืนสัญญาใหม่ซึ่งทำให้คำสั่งของการดำเนินการผิด ดังนั้นเมื่อสัญญาในตอนนั้นเสร็จสิ้นจึงเป็นการหมดเวลาของสคริปต์ Gotcha แปลก ๆ แต่ไม่ใช่ IIS ที่แสดงปัญหาระหว่าง Keyboard กับ chair


0

เท่าที่ฉันบอกได้ไม่ว่าคุณจะมีข้อยกเว้นหรือคุณส่งคำขอคืนสร้าง CreateErrorResponse ผลลัพธ์จะเหมือนกัน ถ้าคุณดูซอร์สโค้ดสำหรับ System.Web.Http.dll คุณจะเห็นมาก ลองดูที่สรุปทั่วไปนี้และวิธีแก้ไขปัญหาที่คล้ายกันมากที่ฉันทำ: Web Api, HttpError และพฤติกรรมของข้อยกเว้น


0

ในสถานการณ์ที่มีข้อผิดพลาดฉันต้องการส่งคืนคลาสรายละเอียดข้อผิดพลาดเฉพาะในรูปแบบใดก็ตามที่ลูกค้าร้องขอแทนที่จะเป็นวัตถุเส้นทางที่มีความสุข

ฉันต้องการให้เมธอดคอนโทรลเลอร์ของฉันส่งคืนอ็อบเจ็กต์พา ธ ความสุขที่ระบุโดเมนและส่งข้อยกเว้นเป็นอย่างอื่น

ปัญหาที่ฉันมีคือตัวสร้าง HttpResponseException ไม่อนุญาตให้มีวัตถุโดเมน

นี่คือสิ่งที่ฉันมาในที่สุด

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

Resultเป็นคลาสที่มีรายละเอียดข้อผิดพลาดขณะที่ProviderCollectionผลลัพธ์เส้นทางที่มีความสุขของฉันคือ


0

ฉันชอบคำตอบที่ตรงข้าม

อย่างไรก็ตามฉันต้องการวิธีในการรับข้อยกเว้นที่สืบทอดมาและโซลูชันนั้นไม่ตอบสนองความต้องการทั้งหมดของฉัน

ดังนั้นฉันจึงเปลี่ยนวิธีจัดการ OnException และนี่คือรุ่นของฉัน

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

แกนกลางเป็นวงนี้ที่ฉันตรวจสอบว่าประเภทยกเว้นเป็นประเภทย่อยของประเภทที่ลงทะเบียน

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

my2cents

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.