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
- Devo sempre usar em
HttpResponseMessage
vez de um modelo de domínio concreto, para que a mensagem possa ser personalizada? - A mensagem pode ser personalizada se você estiver retornando um modelo de domínio concreto?
Perguntas sobre o caso # 2,3,4
- 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.
- Qual é a diferença entre throwing
HttpResponseException
vsRequest.CreateErrorResponse
? A saída para o cliente parece idêntica ... - Devo sempre usar
HttpError
para "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);
}
Respostas:
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:
Classe UnhandledExceptionFilterAttribute:
O código-fonte também pode ser encontrado aqui .
fonte
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
e simplesmente chame-o com o código de status e a mensagem apropriados
fonte
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ê?Caso 1
Casos # 2-4
Estes devem ser equivalentes; HttpResponseException encapsula um HttpResponseMessage, que é o que é retornado como resposta HTTP.
por exemplo, o caso 2 poderia ser reescrito como
... mas se a lógica do seu controlador for mais complicada, lançar uma exceção poderá simplificar o fluxo do código.
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.
fonte
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.
fonte
ArgumentException
s em um controlador seria logicamente 400, mas e quantoArgumentException
mais s na pilha? Não seria necessariamente correto transformá-los em 400, mas se você tiver um filtro que converta todos osArgumentException
s 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.Outro caso para quando usar em
HttpResponseException
vez deResponse.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.CreateResponse
não reverterá a transação, enquanto a exceção será lançada.fonte
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
atributo ao método para que a ajuda seja gerada corretamente. Então
ou por erro
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.fonte
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
fonte
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
Result
é uma classe que contém detalhes do erro, enquantoProviderCollection
é o meu resultado do caminho feliz.fonte
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
O núcleo é esse loop, onde verifico se o tipo de exceção é uma subclasse de um tipo registrado.
my2cents
fonte