Ném httpResponseException hoặc trả lại Request.CreateErrorResponse?


172

Sau khi xem xét một bài viết Xử lý ngoại lệ trong API Web ASP.NET, tôi hơi bối rối khi phải đưa ra một ngoại lệ so với trả lời lỗi. Tôi cũng tự hỏi liệu có thể sửa đổi phản hồi khi phương thức của bạn trả về một mô hình cụ thể miền thay vì HttpResponseMessage...

Vì vậy, để tóm tắt lại ở đây là những câu hỏi của tôi được theo sau bởi một số mã với trường hợp #s:

Câu hỏi

Câu hỏi liên quan đến trường hợp số 1

  1. Tôi có nên luôn luôn sử dụng HttpResponseMessagethay vì một mô hình miền cụ thể để thông điệp có thể được tùy chỉnh không?
  2. Tin nhắn có thể được tùy chỉnh nếu bạn đang trả về mô hình miền cụ thể?

Các câu hỏi liên quan đến vụ án # 2,3,4

  1. Tôi có nên ném ngoại lệ hoặc trả lại lỗi không? Nếu câu trả lời là "nó phụ thuộc", bạn có thể đưa ra tình huống / ví dụ về thời điểm sử dụng cái này với cái kia không.
  2. Sự khác biệt giữa ném HttpResponseExceptionvs Request.CreateErrorResponse? Đầu ra cho máy khách có vẻ giống hệt ...
  3. Tôi có nên luôn luôn sử dụng HttpErrorđể "bọc" các thông báo phản hồi bị lỗi (cho dù ngoại lệ bị ném hoặc phản hồi lỗi được trả về)?

Mẫu trường hợp

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

Cập nhật

Để giúp chứng minh thêm các trường hợp # 2,3,4 đoạn mã sau đây nêu bật một số tùy chọn "có thể xảy ra" khi không tìm thấy khách hàng ...

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 Là tác giả của bài viết được liên kết, bạn sẽ thực hiện phương pháp nào?
zam6ak

Câu trả lời:


102

Cách tiếp cận tôi đã thực hiện là chỉ đưa ra các ngoại lệ từ các hành động của bộ điều khiển api và có một bộ lọc ngoại lệ được đăng ký để xử lý ngoại lệ và đặt phản hồi thích hợp cho bối cảnh thực thi hành động.

Bộ lọc hiển thị giao diện lưu loát cung cấp phương tiện đăng ký trình xử lý cho các loại ngoại lệ cụ thể trước khi đăng ký bộ lọc với cấu hình toàn cầu.

Việc sử dụng bộ lọc này cho phép xử lý ngoại lệ tập trung thay vì trải rộng nó qua các hành động của bộ điều khiển. Tuy nhiên, có những trường hợp tôi sẽ bắt ngoại lệ trong hành động của bộ điều khiển và trả về một phản hồi cụ thể nếu việc tập trung xử lý ngoại lệ cụ thể đó không có ý nghĩa gì.

Ví dụ đăng ký bộ lọc:

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

Lớp UnhandledExceptionFilterAttribution:

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

Mã nguồn cũng có thể được tìm thấy ở đây .


ồ :) Đây có thể là một chút nhiều cho các dự án nhỏ hơn, nhưng vẫn rất tốt đẹp ... BTW, tại sao CreatResponse thay vì CreatErrorResponse trong DefaultHandler?
zam6ak

Tôi đã cố tách các chi tiết lỗi (nối tiếp trong phần thân) khỏi cụm từ lý do; nhưng bạn chắc chắn có thể sử dụng CreatErrorResponse nếu điều đó có ý nghĩa hơn như trong trường hợp ràng buộc mô hình.
Đối lập

1
Vì bạn có thể đăng ký bộ lọc chỉ với một dòng mã, tôi nghĩ rằng nó phù hợp với hầu hết mọi loại dự án. Chúng tôi có bộ lọc trong thư viện lớp được xuất bản trên nguồn cấp dữ liệu NuGet nội bộ của chúng tôi, vì vậy rất dễ dàng cho các nhà phát triển sử dụng.
Đối lập

Bạn đang sử dụng gì cho lính canh (cây nhà lá vườn hoặc bên thứ 3)?
zam6ak

Hoemgrown, tôi đã loại bỏ việc sử dụng nó trong ví dụ trên. Lớp Guard cung cấp một tập hợp các phương thức tĩnh bảo vệ chống lại hoặc xác minh rằng một xác nhận đã được đáp ứng. Xem codepaste.net/5oc1if (Guard) và codepaste.net/nsrsei (DelegateInfo) nếu bạn muốn triển khai.
Đối lập

23

Nếu bạn không trả về HttpResponseMessage và thay vào đó đang trả lại trực tiếp các lớp thực thể / mô hình, một cách tiếp cận mà tôi thấy hữu ích là thêm chức năng tiện ích sau vào bộ điều khiển của tôi

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

và chỉ cần gọi nó với mã trạng thái và thông báo phù hợp


4
Đây là câu trả lời đúng, nó đi kèm với định dạng "Tin nhắn" như một cặp giá trị chính trong phần thân. Đây thường là cách tôi thấy các khung và ngôn ngữ khác thực hiện
MobileMon

Tôi có một câu hỏi nhỏ về phương pháp này. Tôi đang sử dụng tin nhắn bằng cú pháp {{}} trong trang angularJS. Nếu tôi rời khỏi xe ngựa trở về, họ sẽ đến dưới dạng n \ r \ trong tin nhắn. Cách đúng để bảo quản chúng là gì?
Naomi

Tôi đã thử phương pháp này. Tôi đã làm throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, "Invalid Request Format!")), nhưng trong Fiddler, nó hiển thị trạng thái 500 (không phải 400). Bất cứ ý tưởng tại sao?
Sam

sự khác biệt là hàm cha của lỗi. Dưới đây là throwResponseException cho bất kỳ ngoại lệ nào trong ứng dụng. Nhưng phải là chức năng thực sự ném ngoại lệ ...
Serge

15

Trường hợp 1

  1. Không nhất thiết, có những nơi khác trong đường ống để sửa đổi phản hồi (bộ lọc hành động, xử lý tin nhắn).
  2. Xem ở trên - nhưng nếu hành động trả về một mô hình miền, thì bạn không thể sửa đổi phản hồi bên trong hành động.

Các trường hợp # 2-4

  1. Những lý do chính để ném httpResponseException là:
    • nếu bạn đang trả về một mô hình miền nhưng cần xử lý các trường hợp lỗi,
    • để đơn giản hóa logic bộ điều khiển của bạn bằng cách coi các lỗi là ngoại lệ
  2. Những điều này nên tương đương; HttpResponseException đóng gói một httpResponseMessage, đây là thứ được trả về dưới dạng phản hồi HTTP.

    ví dụ, trường hợp # 2 có thể được viết lại thành

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

    ... nhưng nếu logic bộ điều khiển của bạn phức tạp hơn, việc ném một ngoại lệ có thể đơn giản hóa luồng mã.

  3. HttpError cung cấp cho bạn một định dạng nhất quán cho phần phản hồi và có thể được tuần tự hóa thành JSON / XML / etc, nhưng không bắt buộc. ví dụ: bạn có thể không muốn đưa phần thân thực thể vào phản hồi hoặc bạn có thể muốn một số định dạng khác.


Cách tiếp cận tôi đã thực hiện là chỉ đưa ra các ngoại lệ từ các hành động của bộ điều khiển api và tôi đã đăng ký một bộ lọc ngoại lệ xử lý ngoại lệ và đặt phản hồi thích hợp cho bối cảnh thực thi hành động. Bộ lọc là 'có thể cắm' để tôi có thể đăng ký trình xử lý cho các loại ngoại lệ cụ thể trước khi đăng ký bộ lọc với cấu hình toàn cầu. Điều này cho phép tôi thực hiện xử lý ngoại lệ tập trung thay vì trải rộng nó trên các bộ điều khiển.
Đối lập

@Oppositable Bất kỳ cơ hội nào bạn sẽ sẵn sàng chia sẻ bộ lọc ngoại lệ của mình? Có lẽ là một Gist hoặc trên một trang web chia sẻ mã như CodePaste?
Paige Cook

@Mike Wasson bạn có nói rằng "phản hồi lỗi trả về" là cách tiếp cận phổ biến hơn so với "ngoại lệ ném" không? Tôi hiểu về mặt chức năng kết quả cuối cùng có thể giống nhau (nhưng?), Nhưng tôi tự hỏi tại sao không chỉ bao gồm toàn bộ logic bộ điều khiển trong thử / bắt và trả về lỗi phản hồi khi thích hợp?
zam6ak

15

Không ném một HTTPResponseException hoặc trả lại một HTTPResponesMessage cho các lỗi - ngoại trừ nếu mục đích là kết thúc yêu cầu với kết quả chính xác đó .

Các httpResponseException không được xử lý giống như các ngoại lệ khác . Chúng không bị bắt trong Bộ lọc ngoại lệ . Họ không bị bắt trong Handception Exception . Chúng là một cách ranh mãnh để trượt vào một HTTPResponseMessage trong khi chấm dứt luồng thực thi của mã hiện tại.

Trừ khi mã là mã cơ sở hạ tầng dựa trên việc không xử lý đặc biệt này, hãy tránh sử dụng loại HttpResponseException!

HttpResponseMessage không phải là trường hợp ngoại lệ. Họ không chấm dứt luồng thực thi của mã hiện tại. Chúng không thể được lọc như ngoại lệ. Chúng không thể được ghi lại như ngoại lệ. Chúng đại diện cho một kết quả hợp lệ - thậm chí 500 phản hồi là "phản hồi không ngoại lệ hợp lệ"!


Làm cho cuộc sống đơn giản hơn:

Khi có trường hợp ngoại lệ / lỗi, hãy tiếp tục và ném ngoại lệ .NET bình thường - hoặc loại ngoại lệ ứng dụng được tùy chỉnh ( không xuất phát từ HttpResponseException) với các thuộc tính 'lỗi http / phản hồi' mong muốn như mã trạng thái - như ngoại lệ thông thường xử lý .

Sử dụng Bộ lọc ngoại lệ / Trình xử lý ngoại lệ / Trình ghi ngoại lệ để làm điều gì đó phù hợp với các trường hợp ngoại lệ này: thay đổi / thêm mã trạng thái? thêm định danh theo dõi? bao gồm dấu vết ngăn xếp? đăng nhập?

Bằng cách tránh httpResponseException, việc xử lý 'trường hợp đặc biệt' được thực hiện thống nhất và có thể được xử lý như một phần của đường ống tiếp xúc! Ví dụ: người ta có thể biến 'NotFound' thành 404 và 'ArgumentException' thành 400 và 'NullReference' thành 500 một cách dễ dàng và thống nhất với các ngoại lệ ở cấp ứng dụng - trong khi cho phép mở rộng để cung cấp "cơ bản" như ghi nhật ký lỗi.


2
Tôi hiểu tại sao ArgumentExceptions trong bộ điều khiển sẽ hợp lý là 400, nhưng còn ArgumentExceptionsâu hơn trong ngăn xếp thì sao? Sẽ không nhất thiết phải biến những thứ này thành 400, nhưng nếu bạn có một bộ lọc chuyển đổi tất cả ArgumentExceptionthành 400, cách duy nhất để tránh điều đó là bắt ngoại lệ trong bộ điều khiển và ném lại thứ khác, dường như để đánh bại mục đích xử lý ngoại lệ thống nhất trong bộ lọc hoặc tương tự.
cmeeren

@cmeeren Trong mã tôi đang xử lý nó, hầu hết mã này đã bắt ngoại lệ và biến nó thành httpResponse [Ngoại lệ / Thông báo] trong mỗi phương thức web. Cả hai trường hợp đều giống nhau , trong đó nếu mối quan tâm là làm một cái gì đó khác với các ngoại lệ bên trong được thực hiện "một cái gì đó" với ngoại lệ bị bắt: Tôi khuyên kết quả là ném một ngoại lệ gói thích hợp vẫn được xử lý thêm cây rơm.
dùng2864740

@cmeeren Sau khi cập nhật, hầu hết các điểm truy cập web của chúng tôi đưa ra một dẫn xuất đặc biệt (không phải là httpResponseException, có hoặc được ánh xạ tới mã phản hồi phù hợp) cho các lỗi sử dụng. Trình xử lý thống nhất có thể thực hiện một số kiểm tra ngăn xếp (icky, nhưng nó hoạt động một cách cẩn thận) để xác định mức độ ngoại lệ đến từ mức nào - tức là. bao gồm 99% các trường hợp không có xử lý tinh tế hơn - hoặc đơn giản là trả lời với 500 cho các lỗi nội bộ. Điểm mấu chốt với HttpResponseException là nó bỏ qua việc xử lý đường ống hữu ích.
dùng2864740

9

Một trường hợp khác khi sử dụng HttpResponseExceptionthay vì Response.CreateResponse(HttpStatusCode.NotFound)hoặc mã trạng thái lỗi khác là nếu bạn có giao dịch trong các bộ lọc hành động và bạn muốn các giao dịch được khôi phục khi trả về phản hồi lỗi cho khách hàng.

Sử dụng Response.CreateResponsesẽ không đẩy giao dịch trở lại, trong khi ném một ngoại lệ sẽ.


3

Tôi muốn chỉ ra rằng đó là kinh nghiệm của tôi rằng nếu ném một HTTPResponseException thay vì trả lại một HTTPResponseMessage trong phương thức webapi 2, rằng nếu một cuộc gọi được thực hiện ngay lập tức cho IIS Express thì nó sẽ hết thời gian hoặc trả lại 200 nhưng có lỗi html Phản hồi. Cách dễ nhất để kiểm tra điều này là thực hiện cuộc gọi $ .ajax đến một phương thức ném một HTTPResponseException và trong errorCallBack trong ajax thực hiện cuộc gọi ngay lập tức đến một phương thức khác hoặc thậm chí là một trang http đơn giản. Bạn sẽ nhận thấy cuộc gọi imediate sẽ thất bại. Nếu bạn thêm điểm dừng hoặc giải quyết () trong lỗi gọi lại để trì hoãn cuộc gọi thứ hai một hoặc hai giây cho máy chủ thời gian để khôi phục thì nó hoạt động chính xác.

Cập nhật:Nguyên nhân sâu xa của thời gian chờ kết nối Ajax wierd là nếu một cuộc gọi ajax được thực hiện đủ nhanh, cùng một kết nối tcp được sử dụng. Tôi đã tăng ether lỗi 401 bằng cách trả lại một HTTPResonseMessage hoặc ném HTTPResponseException đã được trả về cuộc gọi ajax của trình duyệt. Nhưng cùng với cuộc gọi đó, MS đã trả về Lỗi Không tìm thấy đối tượng vì trong ứng dụng Startup.Auth.vb.UserCookieAuthentication đã được bật nên nó đã cố gắng trả lại chặn phản hồi và thêm chuyển hướng nhưng nó bị lỗi với Object không phải là Instance của Object. Lỗi này là html nhưng sau đó đã được thêm vào phản hồi vì vậy chỉ khi cuộc gọi ajax được thực hiện đủ nhanh và cùng một kết nối tcp được sử dụng thì nó mới được đưa trở lại trình duyệt và sau đó nó được thêm vào phía trước của cuộc gọi tiếp theo. Vì một số lý do, Chrome chỉ hết thời gian, fiddler pucked becasue của sự pha trộn của json và htm nhưng firefox đã đưa ra lỗi thực sự. Vì vậy, wierd nhưng gói sniffer hoặc firefox là cách duy nhất để theo dõi cái này.

Ngoài ra, cần lưu ý rằng nếu bạn đang sử dụng trợ giúp API Web để tạo trợ giúp tự động và bạn trả lại httpResponseMessage thì bạn nên thêm một

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

thuộc tính cho phương thức để giúp tạo ra chính xác. Sau đó

return Request.CreateResponse<CustomReturnedType>(objCustomeReturnedType) 

hoặc có lỗi

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

Hy vọng điều này sẽ giúp những người khác có thể nhận được thời gian chờ ngẫu nhiên hoặc máy chủ không khả dụng ngay lập tức sau khi ném một HTTPResponseException.

Ngoài ra, việc trả lại một HTTPResponseException có thêm lợi ích là không khiến Visual Studio phá vỡ một ngoại lệ Không được xử lý hữu ích khi lỗi được trả về là AuthToken cần được làm mới trong một ứng dụng trang duy nhất.

Update cả hai đều trả lại lời hứa nhưng không phải cùng một lời hứa bị xiềng xích sau đó () trả lại một lời hứa mới khiến cho lệnh thi hành bị sai. Vì vậy, khi lời hứa sau đó () hoàn thành, đó là thời gian chờ tập lệnh. Gotcha kỳ lạ nhưng không phải là vấn đề IIS Express xảy ra sự cố giữa Bàn phím và ghế.


0

Theo như tôi có thể nói, cho dù bạn ném ngoại lệ hay bạn trả về Request.CreateErrorResponse, kết quả vẫn như nhau. Nếu bạn nhìn vào mã nguồn cho System.Web.Http.dll, bạn sẽ thấy nhiều như vậy. Hãy xem tóm tắt chung này và một giải pháp rất giống mà tôi đã thực hiện: Web Api, HttpError và hành vi của các ngoại lệ


0

Trong các tình huống lỗi, tôi muốn trả về một lớp chi tiết lỗi cụ thể, ở bất kỳ định dạng nào mà máy khách yêu cầu thay vì đối tượng đường dẫn hạnh phúc.

Tôi muốn có các phương thức điều khiển của tôi trả về đối tượng đường dẫn hạnh phúc cụ thể của miền và đưa ra một ngoại lệ khác.

Vấn đề tôi gặp phải là các hàm tạo của httpResponseException không cho phép các đối tượng miền.

Đây là những gì tôi cuối cùng đã đưa ra

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

Resultlà một lớp có chứa các chi tiết lỗi, trong khi đó ProviderCollectionlà kết quả đường dẫn hạnh phúc của tôi.


0

Tôi thích câu trả lời đối lập

Dù sao, tôi cần một cách để bắt Ngoại lệ được kế thừa và giải pháp đó không thỏa mãn tất cả các nhu cầu của tôi.

Vì vậy, cuối cùng tôi đã thay đổi cách anh ấy xử lý OnException và đây là phiên bản của tôi

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

Cốt lõi là vòng lặp này nơi tôi kiểm tra xem loại ngoại lệ có phải là lớp con của loại đã đăng ký hay không.

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

my2cents

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.