Lance HttpResponseException ou retorne Request.CreateErrorResponse?

172

Depois de revisar um artigo Tratamento de exceções na API da Web do ASP.NET, estou um pouco confuso sobre quando lançar uma exceção vs retornar uma resposta de erro. Também me pergunto se é possível modificar a resposta quando seu método retorna um modelo específico de domínio em vez de HttpResponseMessage...

Então, para recapitular aqui, minhas perguntas, seguidas de algum código com case #:

Questões

Perguntas sobre o Caso 1

  1. Devo sempre usar em HttpResponseMessagevez de um modelo de domínio concreto, para que a mensagem possa ser personalizada?
  2. A mensagem pode ser personalizada se você estiver retornando um modelo de domínio concreto?

Perguntas sobre o caso # 2,3,4

  1. Devo lançar uma exceção ou retornar uma resposta de erro? Se a resposta for "depende", você pode dar situações / exemplos de quando usar um vs o outro.
  2. Qual é a diferença entre throwing HttpResponseExceptionvs Request.CreateErrorResponse? A saída para o cliente parece idêntica ...
  3. Devo sempre usar HttpErrorpara "quebrar" as mensagens de resposta em erros (se a exceção é lançada ou a resposta de erro é retornada)?

Amostras de casos

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

Atualizar

Para ajudar a demonstrar ainda mais os casos # 2,3,4, o seguinte trecho de código destaca várias opções que "podem acontecer" quando um cliente não é encontrado ...

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
fonte
6
@ Mike Wasson Como autor do artigo vinculado, qual abordagem você adotaria?
Zam6ak 20/09/12

Respostas:

102

A abordagem adotada é lançar exceções das ações do controlador de API e ter um filtro de exceção registrado que processa a exceção e define uma resposta apropriada no contexto de execução da ação.

O filtro expõe uma interface fluente que fornece um meio de registrar manipuladores para tipos específicos de exceções antes de registrar o filtro na configuração global.

O uso desse filtro permite o tratamento centralizado de exceções, em vez de espalhá-lo pelas ações do controlador. No entanto, há casos em que capturarei exceções na ação do controlador e retornarei uma resposta específica se não fizer sentido centralizar o tratamento dessa exceção específica.

Exemplo de registro do filtro:

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

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

O código-fonte também pode ser encontrado aqui .

Oposição
fonte
Uau! :) Isso pode ser um pouco demais para projetos menores, mas ainda assim muito bom ... BTW, por que CreateResponse em vez de CreateErrorResponse no DefaultHandler?
Zam6ak
Eu estava tentando separar os detalhes do erro (serializados no corpo) da frase de razão; mas você certamente poderia usar CreateErrorResponse se isso fizesse mais sentido, como no caso de ligação de modelo.
Oppositional
1
Como você pode registrar o filtro com apenas uma linha de código, acho que é adequado para praticamente qualquer tipo de projeto. Temos o filtro em uma biblioteca de classes publicada em nosso feed interno do NuGet, para facilitar o uso dos desenvolvedores.
Oposição
O que você está usando para guardas (domésticos ou de terceiros)?
Zam6ak 26/09/12
Hoemgrown, removi seu uso no exemplo acima. A classe Guard fornece um conjunto de métodos estáticos que protegem ou verificam se uma afirmação foi atendida. Consulte codepaste.net/5oc1if (Guard) e codepaste.net/nsrsei (DelegateInfo) se você deseja a implementação.
oposição de oposição
23

Se você não está retornando HttpResponseMessage e está retornando classes de entidade / modelo diretamente, uma abordagem que eu achei útil é adicionar a seguinte função de utilitário ao meu controlador

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

e simplesmente chame-o com o código de status e a mensagem apropriados

Joe King
fonte
4
Esta é a resposta certa, pois vem com o formato "Mensagem" como um par de valores-chave no corpo. Normalmente, é assim que vejo outras estruturas e linguagens fazê-lo.
MobileMon
Eu tenho uma pergunta menor sobre essa abordagem. Estou consumindo a mensagem usando a sintaxe {{}} na página angularJS. Se eu deixar retornos de carro, eles aparecerão como n \ r \ na mensagem. Qual é o caminho certo para preservá-los?
Naomi
Eu tentei essa abordagem. Sim throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, "Invalid Request Format!")), mas no Fiddler, ele mostra o status 500 (não 400). Alguma idéia do porquê?
Sam
a diferença é a função pai do erro. Aqui está o ThrowResponseException para qualquer exceção no aplicativo. Mas deve ser a função real lançar a exceção ...
Serge
15

Caso 1

  1. Não necessariamente, há outros locais no pipeline para modificar a resposta (filtros de ação, manipuladores de mensagens).
  2. Veja acima - mas se a ação retornar um modelo de domínio, você não poderá modificar a resposta dentro da ação.

Casos # 2-4

  1. Os principais motivos para lançar HttpResponseException são:
    • se você estiver retornando um modelo de domínio, mas precisar lidar com casos de erro,
    • para simplificar a lógica do seu controlador tratando erros como exceções
  2. Estes devem ser equivalentes; HttpResponseException encapsula um HttpResponseMessage, que é o que é retornado como resposta HTTP.

    por exemplo, o caso 2 poderia ser reescrito como

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

    ... mas se a lógica do seu controlador for mais complicada, lançar uma exceção poderá simplificar o fluxo do código.

  3. O HttpError fornece um formato consistente para o corpo da resposta e pode ser serializado para JSON / XML / etc, mas não é necessário. por exemplo, você pode não querer incluir um corpo de entidade na resposta ou pode querer outro formato.

Mike Wasson
fonte
A abordagem adotada é lançar exceções das ações do controlador de API e registrei um filtro de exceção que processa a exceção e define uma resposta apropriada no contexto de execução da ação. O filtro é 'plugável', para que eu possa registrar manipuladores para tipos específicos de exceções antes de registrar o filtro na configuração global. Isso permite que eu manipule exceções centralizadas em vez de espalhá-las pelos controladores.
Oppositional
@Opositivo Alguma chance de você estar disposto a compartilhar seu filtro de exceção? Talvez como um Gist ou em um site de compartilhamento de código como o CodePaste?
Paige Cook
@ Mike Wasson, você diria que "retornar resposta ao erro" é uma abordagem mais comum vs "lançar exceção"? Entendo que funcionalmente o resultado final pode ser (é?) O mesmo, mas estou me perguntando por que não apenas abranger toda a lógica do controlador na resposta de erro de tentativa / captura e retorno, conforme apropriado?
Zam6ak
15

Não lance um HttpResponseException ou retorne um HttpResponesMessage por erros - exceto se a intenção é terminar a solicitação com o resultado exato .

HttpResponseException não são tratados da mesma forma que outras exceções . Eles não são capturados nos filtros de exceção . Eles não são capturados no manipulador de exceções . Eles são uma maneira astuta de inserir um HttpResponseMessage enquanto encerram o fluxo de execução do código atual.

A menos que o código seja o código de infraestrutura que depende dessa remoção especial, evite usar o tipo HttpResponseException!

HttpResponseMessage não são exceções. Eles não encerram o fluxo de execução do código atual. Eles não podem ser filtrados como exceções. Eles não podem ser registrados como exceções. Eles representam um resultado válido - mesmo uma resposta de 500 é "uma resposta de não exceção válida"!


Simplifique a vida:

Quando houver um caso excepcional / erro, vá em frente e lance uma exceção .NET normal - ou um tipo de exceção de aplicativo personalizado ( não derivado de HttpResponseException) com as propriedades desejadas 'http error / response', como um código de status - como exceção normal manipulação .

Use Filtros de exceção / Manipuladores de exceção / Registradores de exceção para fazer algo apropriado com esses casos excepcionais: alterar / adicionar códigos de status? adicionar identificadores de rastreamento? incluir rastreamentos de pilha? registro?

Ao evitar o HttpResponseException, o manuseio 'caso excepcional' é uniformizado e pode ser tratado como parte do pipeline exposto! Por exemplo, pode-se transformar um 'NotFound' em 404 e 'ArgumentException' em 400 e 'NullReference' em 500, fácil e uniformemente em 500, com exceções no nível do aplicativo - enquanto permite extensibilidade para fornecer "conceitos básicos", como registro de erros.

user2864740
fonte
2
Eu entendo por que ArgumentExceptions em um controlador seria logicamente 400, mas e quanto ArgumentExceptionmais s na pilha? Não seria necessariamente correto transformá-los em 400, mas se você tiver um filtro que converta todos os ArgumentExceptions para 400, a única maneira de evitar isso é capturar a exceção no controlador e lançar novamente outra coisa, o que parece anular o objetivo do tratamento uniforme de exceções em um filtro ou similar.
cmeeren
@cmeeren No código em que estava lidando, a maioria capturou a exceção e a transformou no HttpResponse [Exception / Message] em cada método da web. Ambos os casos são os mesmos , pois se a preocupação é fazer algo diferente com exceções internas, é feito "algo" com a exceção interna capturada: eu recomendo que o resultado seja lançar uma exceção de empacotamento apropriada que ainda seja tratada mais adiante. pilha.
user2864740
@cmeeren Após as atualizações, a maioria dos nossos pontos de entrada na Web lança um derivado especial (não HttpResponseException, que tem e ou é mapeado para os códigos de resposta apropriados) a erros de uso. O manipulador de uniformes poderia fazer alguma inspeção na pilha (nojenta, mas funciona com algum cuidado) para determinar em que nível a exceção veio - ie. cobrir 99% dos casos que não têm tratamento mais refinado - ou simplesmente responder com 500 por erros internos. O ponto crucial do HttpResponseException é que ele ignora o processamento útil do pipeline.
user2864740
9

Outro caso para quando usar em HttpResponseExceptionvez de Response.CreateResponse(HttpStatusCode.NotFound), ou outro código de status de erro, é se você possui transações em filtros de ação e deseja que as transações sejam revertidas ao retornar uma resposta de erro ao cliente.

O uso Response.CreateResponsenão reverterá a transação, enquanto a exceção será lançada.

Rob Gray
fonte
3

Quero ressaltar que foi minha experiência que, ao lançar uma HttpResponseException em vez de retornar uma HttpResponseMessage em um método webapi 2, que, se uma chamada for feita imediatamente para o IIS Express, ela atingirá o tempo limite ou retornará 200, mas com um erro html em a resposta. A maneira mais fácil de testar isso é fazer a chamada $ .ajax para um método que lança uma HttpResponseException e no errorCallBack no ajax fazer uma chamada imediata para outro método ou mesmo para uma simples página http. Você notará que a chamada imediata falhará. Se você adicionar um ponto de interrupção ou um settimeout () na chamada de erro de volta para atrasar a segunda chamada, um segundo ou dois, dando tempo ao servidor para recuperá-lo, funcionará corretamente.

Atualizar:A causa raiz do tempo limite estranho da conexão Ajax é que, se uma chamada ajax for feita com rapidez suficiente, a mesma conexão tcp será usada. Eu estava criando um éter de erro 401 retornando um HttpResonseMessage ou lançando uma HTTPResponseException que foi retornada à chamada de ajax do navegador. Mas, juntamente com essa chamada, a MS estava retornando um erro Objeto não encontrado, porque em Startup.Auth.vb app.UserCookieAuthentication estava habilitado; portanto, ele estava tentando retornar a interceptação da resposta e adicionar um redirecionamento, mas com o objeto não Instância de um objeto. Este erro era html, mas foi anexado à resposta após o fato; somente se a chamada ajax for rápida o suficiente e a mesma conexão tcp usada for retornada ao navegador e depois anexada à frente da próxima chamada. Por alguma razão, o Chrome acabou o tempo limite, O violinista pegou por causa da mistura de json e htm, mas o Firefox transformou o erro real. Tão estranho, mas sniffer de pacotes ou firefox era a única maneira de rastrear este.

Além disso, deve-se observar que, se você estiver usando a ajuda da API da Web para gerar ajuda automática e retornar HttpResponseMessage, deverá adicionar um

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

atributo ao método para que a ajuda seja gerada corretamente. Então

return Request.CreateResponse<CustomReturnedType>(objCustomeReturnedType) 

ou por erro

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

Espero que isso ajude alguém que pode estar recebendo um tempo limite aleatório ou um servidor que não está disponível imediatamente após lançar uma HttpResponseException.

Também retornar um HttpResponseException tem o benefício adicional de não causar o Visual Studio quebrar em uma exceção não tratada, útil quando o erro retornado é o AuthToken precisa ser atualizado em um aplicativo de página única.

Atualização: estou retirando minha declaração sobre o tempo limite do IIS Express; isso aconteceu com um erro no retorno de chamada ajax do lado do cliente, pois desde o Ajax 1.8 retornando $ .ajax () e retornando $ .ajax. (). Then () ambos retornam promessa, mas não a mesma promessa encadeada, então () retorna uma nova promessa que causou um erro na ordem de execução. Então, quando a promessa then () foi concluída, foi um tempo limite para o script. Um problema estranho, mas não um problema expresso do IIS, ocorre um problema entre o teclado e a cadeira.

Andrew DeVries
fonte
0

Pelo que sei, se você lança uma exceção ou retorna Request.CreateErrorResponse, o resultado é o mesmo. Se você olhar o código-fonte do System.Web.Http.dll, verá o mesmo. Dê uma olhada neste resumo geral e em uma solução muito semelhante que eu criei : API da Web, HttpError e o comportamento das exceções

Andy Cohen
fonte
0

Em situações de erro, eu queria retornar uma classe de detalhes de erro específica, em qualquer formato que o cliente solicitasse, em vez do objeto de caminho feliz.

Desejo que meus métodos de controlador retornem o objeto de caminho feliz específico do domínio e, ao contrário, lançem uma exceção.

O problema que tive foi que os construtores HttpResponseException não permitem objetos de domínio.

Isso é o que eu acabei inventando

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

Resulté uma classe que contém detalhes do erro, enquanto ProviderCollectioné o meu resultado do caminho feliz.

NickBeaugié
fonte
0

Eu gosto de resposta de oposição

De qualquer forma, eu precisava de uma maneira de capturar a exceção herdada e essa solução não atende a todas as minhas necessidades.

Acabei mudando como ele lida com o OnException e esta é a minha versão

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

O núcleo é esse loop, onde verifico se o tipo de exceção é uma subclasse de um tipo registrado.

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

my2cents

Não é importante
fonte