Exception Handling and Shielding
O tratamento de erros é um dos aspetos cruciais das aplicações e também, tradicionalmente, um dos mais difíceis de implementar corretamente. Pela sua complexidade, pela multiplicidade de cenários que devem ser considerados e porque há um equilíbrio difícil entre os seguintes vectores:
- O detalhe e a pertinência das mensagens de erro apresentadas ao utilizador. Devem ajudá-lo a ultrapassar o problema de uma forma clara e sucinta.
- O detalhe e a pertinência da informação recolhida (para além da apresentada na aplicação) para assistir na resolução de problemas na aplicação (suporte e manutenção).
- A atenção e esforço necessários por parte do developer (espécie de mamífero pouco disponivel para trabalho pormenorizado como é tipicamente este).
Ainda assim, este é um dos aspetos transversais das aplicações que pode e deve ser endereçado na plataforma, tentando reduzir ao mínimo o trabalho necessário pelo developer, maximizando a informação recolhida sobre o erro e maximizando a qualidade da informação apresentada ao utilizador.
A Plataforma Elevation segue as seguintes orientações principais neste tema:
- Em circunstância alguma a aplicação pode simplesmente estourar com um erro. Deve tratá-los todos de alguma forma. Mesmo os erros mais inesperados como, por exemplo, a falta de uma dll, deve apresentar uma janela de erro do sistema (ver "yellow screen of hell").
- Os erros esperados devem ser apresentados ao utilizador dentro da aplicação. Da forma mais adequada a cada erro particular e com mensagens claras e simples que ajudem o utilizador a ultrapassar o problema.
- Os erros inesperados devem ser apresentados ao utilizador de uma forma especial, indicando-lhe que aconteceu uma situação inesperada que, à partida, ele não poderá resolver.
- Em circustância alguma os erros apresentados ao utilizador devem apresentar informação sensível ou informação sobre a forma como a aplicação está implementada.
Este último ponto diz respeito ao conceito de Exception Shielding a que a plataforma deve obedecer. A recolha de informação para a resolução de problemas (suporte) não justifica que o utilizador seja confrontado com dados técnicos que não o ajudam. São coisas distintas. Essa informação técnica é recolhida pela aplicação no back-end (tracing, etc.)
Erros Esperados versus Erros Inesperados
Um erro esperado (ExpectedException) corresponde a uma condição que faz parte do funcionamento normal da aplicação e por isso foi antecipada pelo programador. Eis alguns exemplos: - O cliente X não existe. - A condição de pagamento da fatura não está preenchida. - O ficheiro selecionado pelo utilizador não pode ser carregado.
Já um erro inesperado (Exception) será uma condição que não cumpra os requisitos anteriores. Pode ter sido antecipada pelo utilizador mas não é esperado que aconteça na operação normal da aplicação. Exemplos: - O argumento X do método é nulo. - Ocorreu um erro ao ler o ficheiro. - O servidor da base de dados não está disponível.
Qualquer erro esperado deve ser lançado no código através de uma exceção que derive do tipo Primavera.Core.Exceptions.ExpectedException (exemplo: Primavera.Core.Domain.Exceptions.DomainException).
Desse modo será possível distinguir os erros esperados dos inesperados no tratamento de erros:
try
{
// do something
}
catch (ExpectedException ex)
{
// show to the user
}
catch (Exception ex)
{
// show a default dialog for unexpected errors
}
Tratamento dos Erros
Na perspetiva do utilizador (aquilo que ele verá) a Plataforma Elevation tratará os erros da seguinte forma.
Sempre que ocorrer um erro inesperado será apresentado um diálogo semelhante ao seguinte:

Este diálogo genérico ocupará toda a página da aplicação quando o erro for, por assim dizer, irrecuperável (não permitir à partida continuar a operação que o causou), ou será apresentado num popup no caso contrário (por exemplo se ocorreu na gravação de um registo).
Todos os erros esperados serão apresentados no contexto da aplicação através do Message Service que apresenta um toast ou um popup consoante o tipo de erro.

Tracing
Em ambos os casos é recolhida informação sobre os erros através do Tracing. Os erros inesperados são registados como Error ou Fatal, enquanto os esperados são registados apenas no nível Verbose.
Implementação
Este comportamento é assegurado pelos seguintes componentes da Plataforma.

Application_Error e GlobalApplicationEventsHandler
No Global.asax do host da aplicação é configurado o evento Application_Error da seguinte forma:
protected void Application_Error(object sender, EventArgs e)
{
// Notify the global application events handler
GlobalApplicationEventsHandler.OnError(this);
}
Este evento ocorre sempre que acontecer uma exceção não tratada na aplicação servidor.
O GlobalApplicationEventsHandler trata então destes casos assegurando a apresentação do diálogo
genérico de erro.
CustomError e Error.Html
Ao nível do ASP.NET os erros estão configurados pelo elemento de configuração customError no web.config:
<customErrors mode="On" defaultRedirect="Error.html" />
A página Error.html (no Host da aplicação) é uma página HTML estática que apresenta o diálogo genérico de erro.
A página Error.html só será utilizada quando ocorrer um erro na aplicação até ao ponto em que os application services não estão carregados. Isso pode acontecer, por exemplo, se o deployment da aplicação estiver errado. Depois de carregados os application services, o tratamento de erros será assegurado pelo serviço ICustomErrorService.
ICustomErrorService
Este serviço é responsável por apresentar o diálogo genérico de erro configurado de acordo com o erro inesperado em
causa. Para isso utilizará o controller DefaultErrorController para apresentar a view.
Neste momento são tratados todos os códigos de erro HTTP, sendo que os seguintes têm tratamento particular (apresentando ao utilizador uma mensagem diferente para cada um):
- 401 - Unauthorized
- 403 - Forbidden
- 404 - NotFound
- 500 - InternalServerError
DefaultErrorController e Error.cshtml
Este controller é responsável por apresentar a view Error.cshtml (do módulo Shell). Essa view é o diálogo genérico de erro.
UnhandledExceptionFilterAttribute
Qualquer exceção ocorrida na camada de serviços (Services Controllers) é tratada por um filtro HTTP associado às ações Web API, sejam as da Web API pública ou as da Web API interna (a usada pelas aplicações).
Esse filtro é definido pela classe Primavera.Core.Services.UnhandledExceptionFilterAttribute e é registado
dinamicamente na classe Primavera.Core.Services.HttpServiceConfiguration.
Esta classe é responsável por traduzir exceções não tratadas em mensagens de resposta HTTP segundo as seguintes regras:
BaseEntityNotFoundException- traduzida no status code404 NotFound.EntityNotFoundException- traduzida no status code404 NotFound.DeletedEntityException- traduzida no status code405 MethodNotAllowed.DomainValidationException- traduzida no status code400 BadRequest.ForbiddenException- traduzida no status code403 Forbidden.ConflictException- traduzida no status code409 Conflict.ExpectedException- traduzida no status code400 BadRequest.- Qualquer outra - traduzida no status code
500 InternalServerError.
Note-se, neste ponto, que o filtro trata também erros esperados e torna-os em respostas HTTP que o cliente sabe tratar devidamente. Este aspeto é muito importante para o funcionamento da aplicação - o cliente (JS) que processa as respostas e este componente devem estar devidamente alinhados para assegurar o tratamento correto de cada condição de erro.
No caso das exceções ocorridas na Web API pública, o filtro trata também de fazer exception shielding, ou seja, de não apresentar
na resposta dados detalhados das exceções. Nesse cenário apenas será incluido na resposta a mensagem de erro e, dependendo do erro,
um código de erro (numa propriedade com o nome %ERRORCODE%).
Esse comportamento depende do setting ApplicationContext.Settings.HideExceptionsInPublicWebApi que pode
ser modificado na secção de configuração Primavera.Core.Web.ApplicationWebConfigurationSection. Esse setting tem o valor true
por defeito.
Outro aspeto muito importante desta implementação é que, uma vez que o filtro trata os erros esperados de uma forma standardizada, não é necessário que o código dos service controllers faça qualquer tratamento especial das exceções (para além daquele que fizer parte da sua própria lógica de negócio).
Note-se que este filtro se aplica apenas a ações Web API e não tem efeito no MVC. Neste último caso o tratamento de erros é assegurado pelo evento Application_Error como qualquer outro erro HTTP.
exceptionHandler.service.js e app.js
No cliente são estes dois componentes que fazem o tratamento dos códigos de erro recebidos do servidor.
exceptionHandler.service.js: trata os erros recebidos das ações Web API. Todos os erros com o código 500 são tratados automaticamente de forma genérica, não sendo nunca necessário no código que invoca um serviço fazer qualquer tipo controlo.app.js: trata os outros erros vindos do Application_Error como um HTML com a página de erro.
BusinessExceptionsProcessor
Este componente é responsável pelo processamento de exceções na camada de domínio de acordo com as seguintes regras:
ExpectedException- retorna a mesma exceção.- Qualquer outra - é encapsulada numa
DomainUnexpectedExceptioncom uma mensagem de erro genérica.
DataExceptionsProcessor
Este componente é responsável pelo processamento de exceções na camada de dados de acordo com as seguintes regras:
ExpectedException- retorna a mesma exceção.SqlException- o código de erro SQL é traduzido numa mensagem preparada para o utilizador e a exceção é encapsulada numaDataException.- Qualquer outra - é encapsulada numa
DataUnexpectedExceptioncom uma mensagem de erro genérica.
Regras de Programação
Os programadores das aplicações devem assegurar as seguintes regras no código custom da aplicação. Essas regras dependem do layer onde estiver o código custom.
Data Layer
Na camada de dados é crucial assegurar a invocação da classe DataExceptionsProcessor da seguinte forma:
public void MyMethod()
{
try
{
// Do something
}
catch (Exception ex)
{
throw DataExceptionsProcessor.Process(ex);
}
}
Domain Layer
Na camada de domínio o comportamento deve ser semelhante:
public void MyMethod()
{
try
{
// Do something
}
catch (Exception ex)
{
throw BusinessExceptionsProcessor.Process(ex);
}
}
Services Layer
Finalmente nos services controllers devem verificar-se as seguintes normas:
- Não deve ser programado nenhum tratamento genérico de exceções. Esse é assegurado pelo
UnhandledExceptionFilterAttribute. - Os erros devem ser lançados como exceções normais. Não é necessário usar a
HttpErrorResponseExceptionnem o métodoRequest.CreateErrorResponse.
Diagnóstico
Os erros não tratados incluem um identificador ("Correlation Id") que permite relacionar a mensagem apresentada na aplicação no diálogo de erro com o respetivo registo de tracing.
Este identificador só é registado no trace pelo tracing listener para o AppInsights (
TelemetryTracingListener).
Note-se na seguinte mensagem de erro:

O identificador assinalado a vermelho inclui informação adicional sobre o erro:
- O primeiro número (500) corresponde ao status code HTTP associado ao erro.
- O segundo identificador é o CorrelationId.
As exceções são registadas pelo tracing listener tanto nos traces como nas exceptions disponíveis no AppInsights.
A seguinte query permite, por exemplo, consultar as exceções ocorridas na última hora:
exceptions
| where timestamp > ago(1h)
| order by timestamp desc

A coluna customDimensions inclui as seguintes propriedades adicionais sobre a exceção:
- O Correlation Id.
- A Source, que corresponde ao handler da aplicação que provocou a recolha da exceção não tratada.

NOTA IMPORTANTE Como já foi referido as exceções são recolhidas na tabela
exceptionse na tabelatracesdo AppInsights. No entanto, as exceções registadas dependem do nível de tracing configurado, ou seja, se o nível configurado forError, então as exceções recolhidas no tracing em níveis inferiores (Verbosepor exemplo) não serão registadas de todo. No caso das exceções não tratadas recolhidas pelos default handlers, elas são sempre registadas com os níveis de traceErrorouFatal.