Documentação de APIs com Swagger

Há algum tempo eu escrevi aqui sobre o ApiExplorer, que é uma funcionalidade que nos permite expor uma documentação sobre a nossa API. Basicamente ela consiste em nos fornecer metadados dos controllers, ações, parâmetros e resultados para que assim possamos criar uma interface amigável com o usuário (leia-se outro desenvolvedor).

Apesar de termos certa flexibilidade na criação e exibição da documentação, o nosso foco sempre é desenvolvermos a funcionalidade proposta pela API, enquanto a documentação sempre fica em segundo plano, e que na maioria das vezes, nem sempre ganha a atenção que merece. Vale lembrar que é isso que o mundo externo irá utilizar para estudar antes de consumir.

Existe uma especificação chamada Swagger, que tenta padronizar as informações extraídas da APIs Web e, consequentemente, expor de uma forma intuitiva e com bastante riqueza ao mundo. Além do padrão, ainda existe um conjunto de ferramentas (consulte o site oficial), e entre elas, tenho a Swagger UI, que consiste em um conjunto de documentos CSS, HTML e Javascript, e que dinamicamente geram uma interface rica com todas as informações extraídas da API, incluindo o formato das mensagens de requisição e resposta, e ainda, uma opção para já invocar as ações que estão disponíveis, sem precisar recorrer à outras ferramentas, tais como Fiddler. Como um diferencial, os parâmetros já são mapeados e consistidos pela própria interface, garantido que os tipos estejam de acordo com o esperado pela API.

Vale lembrar que o Swagger é completamente independente de plataforma, e não é algo exclusivo do .NET ou da Microsoft. Felizmente existe uma pacote chamado Swashbuckle, que nada mais é que uma implementação desta especificação para Web APIs escritas com o ASP.NET, e que nos bastidores, recorre ao ApiExplorer para gerar a documentação neste – novo – padrão. Por fim, com uma simples linha de código, é possível fazer com que a nossa API tenha uma documentação extremamente rica. Para utiliza-la, basta adicionar o pacote através do Nuget, conforme vemos no comando abaixo:

PM> Install-Package Swashbuckle

Depois de instalado, podemos perceber a criação de uma arquivo chamado SwaggerConfig.cs, que já habilita as configurações mais básicas para o seu funcionamento. Abaixo temos a versão simplificada dela, mas se reparar na classe gerada pela instalação do pacote, há uma série de comentários que são bastante autoexplicativos em relação a cada funcionalidade que ela expõe.

public class SwaggerConfig
{
    public static void Register()
    {
        var thisAssembly = typeof(SwaggerConfig).Assembly;

        GlobalConfiguration.Configuration
            .EnableSwagger(c => c.SingleApiVersion(“v1”, “Minha API”))
            .EnableSwaggerUi();
    }
}

Com essa configuração realizada, ao rodar a aplicação e colocar /swagger depois do endereço (url) onde está rodando o serviço, teremos a interface com os métodos da nossa API sendo exibidos.

E ao clicar em um dos botões “Try It Out”, mais detalhes da requisição e da resposta serão apresentados, e também a ação será invocada na API. Note que se estivéssemos optando por testar uma ação que estivesse sendo exposta através do método HTTP POST, um textarea seria exibido para que você colocasse o corpo da requisição (em JSON ou XML, você escolhe) para assim enviar ao serviço.

Guardando-se as devidas proporções, para quem trabalha ou já trabalhou com SOAP, pode notar certa semelhança com o WSDL (Swagger) e com o SOAP UI (Swagger UI), onde o que vimos aqui, é voltando para o ambiente REST.

Anúncios

Testes com a classe TelemetryClient

No artigo anterior eu mostrei o uso da classe TelemetryClient para catalogar informações customizadas. O fato de utilizar a classe em sua configuração padrão, fará com que as informações sejam enviadas para o serviço (na nuvem) mesmo que você ainda esteja em ambiente de testes. Talvez isso não seja o que estamos querendo, pois espalhamos pelo código os pontos que são cruciais para enviar as informações, mas talvez só faça sentido quando estive em ambiente de produção.

A classe TelemetryClient confia nas configurações que são disponibilizadas pela classe TelemetryConfiguration. Ela, por sua vez, possui uma propriedade estática chamada Active que retorna as configurações globais (para a aplicação) do Application Insights. A partir daí podemos recorrer a propriedade DisableTelemetry que quando definida como true, não enviará as informações ao serviço. Para utiliza-la podemos também condicionar à diretiva de DEBUG para somente desabilitar enquanto estivermos em desenvolvimento:

#if DEBUG
            TelemetryConfiguration.Active.DisableTelemetry = true;
#endif

Ainda com o exemplo utilizado no artigo anterior, não estávamos nos preocupando com os testes unitários. É uma preocupação que também devemos ter para não reportar os eventos quando estamos rodando os testes. Apesar da opção acima resolver, as vezes podemos querer validar se o nosso código está ou não reportando quando necessário e se as informações que foram levadas estão de acordo com a nossa expectativa.

A classe de configuração também nos permite configurar o canal de comunicação, e com isso customizar (sobrescrever) onde e como queremos armazenar os logs. Só que antes de visualizarmos o código necessário para alcançar isso, vamos entender a dependência entre a classe TelemetryClient e o restante da configuração.

A classe TelemetryClient possui dois overloads, onde um deles espera a instância da classe TelemetryConfiguration, que se for omitida, o Application Insights utiliza o valor fornecido pela propriedade Active. A classe TelemetryConfiguration possui uma propriedade chamada TelemetryChannel, onde podemos customizar o canal de comunicação através da implementação da interface ITelemetryChannel. Para o exemplo, vou optar por armazenar os logs em uma coleção interna, conforme é possível ver no código abaixo:

public class TestChannel : ITelemetryChannel
{
    private readonly IList<ITelemetry> itens;

    public TestChannel()
    {
        this.itens = new List<ITelemetry>();
    }

    public void Send(ITelemetry item)
    {
        this.itens.Add(item);
    }

    public IList<ITelemetry> Itens
    {
        get
        {
            return this.itens;
        }
    }

    //outros membros, omitidos por questão de espaço
}

Depois da classe implementada, precisamos criar a instância e associa-la à propriedade da configuração para que ela passe a ser utilizada quando chamarmos os métodos para catalogar as informações (Track*). O código que foi criado para calcular o frete não foi alterado em nada. Mesmo que a configuração tenha sido realizada externamente, a classe TelemetryClient irá utilizar esta classe via TelemetryConfiguration (via propriedade Active). O teste ficaria da seguinte forma:

[TestClass]
public class CalculadoraDeFrete
{
    private TestChannel channel;

    [TestInitialize]
    public void Inicializar()
    {
        this.channel = new TestChannel();
        TelemetryConfiguration.Active.TelemetryChannel = channel;
    }

    [TestMethod]
    public void DeveCalcularFreteParaValinhos()
    {
        var cep = “13273-047”;
        var valorEsperado = 12.38M;

        var calculadora = new CalculadoraDeFrete();
        var resultado = calculadora.Calcular(cep);

        Assert.AreEqual(valorEsperado, resultado);

        var evento = this.channel.Itens.Single() as EventTelemetry;

        Assert.AreEqual(“CalculoDeFrete”, evento.Name);
        Assert.AreEqual(cep, evento.Properties[“Cep”]);
        Assert.AreEqual(resultado.ToString(), evento.Properties[“Resultado”]);
    }
}

ApplicationInsights – Utilizando a classe TelemetryClient

A Microsoft está disponibilizando um serviço de telemetria de aplicações chamado de Application Insights. É um serviço que roda no Azure (nuvem) e que permite integrar nas aplicações que desenvolvemos para que seja possível extrair informações, armazenar e, consequentemente, analisar os dados gerados para tentar identificar algum problema de performance ou de qualquer outra natureza.

Dependendo do tipo de aplicações onde este serviço é instalado, ele utiliza o recurso exposto pela aplicação para interceptar a requisição e catalogar as informações. Por exemplo, se este serviço é instalado em uma aplicação ASP.NET, então o módulo (IHttpModule) ApplicationInsightsHttpModule é incluído para interceptar as requisições que chegam à aplicação; já para aplicações ASP.NET 5, acopla-se através do método UseApplicationInsightsRequestTelemetry. Em ambos os casos, automaticamente o Application Insights começa a receber informações sobre as requisições que chegam para a aplicação, duração da requisição, cabeçalho, etc.

A identificação da aplicação para o serviço é uma chave chamada instrumentation key (GUID) que é gerada pelo Azure, e deve ser configurada na aplicação para que o Application Insights possa enviar ao serviço e ele, por sua vez, saiba qual aplicação está gerando os dados. E, novamente, dependendo da tecnologia, o arquivo utilizado para armazenar a chave é o ApplicationInsights.config para os projetos ASP.NET tradicionais ou aplicações Windows, e o config.json para o ASP.NET 5.

[ApplicationInsights.config]
<?xml version=”1.0″ encoding=”utf-8″?>
<ApplicationInsights>
  …
  <InstrumentationKey>bab34a9e-f1d9-4cf9-8a09-5f33b8f4ebed</InstrumentationKey>
</ApplicationInsights>

[config.json]
{
  “ApplicationInsights”: {
    “InstrumentationKey”: “bab34a9e-f1d9-4cf9-8a09-5f33b8f4ebed”
  },
  …
  }
}

Mesmo que ocorra essa mágica, o Application Insights fornece uma API que podemos recorrer para customizar os dados que são enviados para o serviço. Isso pode ser feito através da classe TelemetryClient, que expõe alguns métodos específicos ou de mais baixo nível para customizar diversas informações que podem ser levadas para o serviço. Para fazer uso desta classe, primeiramente precisamos utilizar o Nuget para adicionar o seguinte pacote:

Install-Package Microsoft.ApplicationInsights

É importante dizer que é possível fazer uso desta classe em projetos que não sejam do tipo ASP.NET. Como exemplo, vou utilizar uma aplicação Console para catalogar os mais diversos tipos de informações no Application Insights. No caso de projetos ASP.NET, já há uma opção durante a criação do projeto para habilita-lo, assim como é possível vermos na imagem abaixo, e a instalação explícita do pacote não é necessária.

>

Independente da forma como você adiciona o suporte para o Application Insights na aplicação, a classe TelemetryClient pode ser utilizada. Como já era de se esperar, esta classe fornece uma propriedade chamada InstrumentationKey, que é onde devemos configurar a chave gerado pelo Azure, mas em casos de projetos do tipo Windows, podemos criar o arquivo ApplicationInsights.config e lá ter o elemento InstrumentationKey, o que facilita a alteração sem precisar recompilar a aplicação.

var tc = new TelemetryClient()
{
    InstrumentationKey = “bab34a9e-f1d9-4cf9-8a09-5f33b8f4ebed”
};

A partir da instância da classe TelemetryClient criada, temos alguns métodos para enviar informações para o serviço. O Application Insights possui diversos tipos de informações (“categorias”) que possamos enviar os dados: Trace, Request, Page View, Expcetion, Dependency e Custom Event. Para cada uma destas informações, há um método correspondente: TrackTrace, TrackRequest, TrackPageView, TrackException, TrackDependency e TrackEvent.

Para exemplificar, considere o código abaixo. Estamos utilizando o método TrackEvent para indicar ao serviço que trata-se de um evento que vamos gerar diversas entradas e que corresponde à uma atividade de rotina da nossa aplicação. Além do nome, também podemos, opcionalmente, informar um dicionário de dados contendo propriedades que queremos anexar aquele evento. No exemplo, estou optando por incluir o parâmetro, o resultado e o tempo que levou para realizar o cálculo.

public class CalculadoraDeFrete
{
    private static TelemetryClient tc = new TelemetryClient()
    {
        InstrumentationKey = “bab34a9e-f1d9-4cf9-8a09-5f33b8f4ebed”
    };

    public static decimal Calcular(string cep)
    {
        var sw = Stopwatch.StartNew();

        //cálculo do frete
        var resultado = 12.38M;

        tc.TrackEvent(
            “CalculoDeFrete”,
            new Dictionary<string, string>()
            {
                { “Cep”, cep },
                { “Resultado”, resultado.ToString() },
                { “Tempo”, sw.Elapsed.ToString() }
            });
        tc.Flush();

        return resultado;
    }
}

Ainda em relação as propriedades que podemos anexar ao log no momento em que o evento ocorre, podemos também definir valores globais que serão enviados em todas as requisições, independentemente se está catalogando um evento, uma exceção, ou qualquer outra informação. E para isso, basta recorrer ao dicionário que está exposto através do contexto da classe TelemetryClient:

static CalculadoraDeFrete()
{
    tc.Context.Properties.Add(“Usuario”, “Israel Aece”);
}

Internamente a classe mantém um buffer com as entradas e periodicamente envia as requisições para o serviço. Utilizamos o método Flush para adiantar esse processo. Se consultarmos o portal, já podemos visualizar o evento adicionado e as respectivas propriedades que enviamos (incluindo a propriedade global Usuario). A partir deste nome do evento, eles sempre serão agrupados para uma melhor visualização e análise.

Como foi comentado acima, também é possível realizar o log de uma exceção. Para isso, podemos envolver o código inseguro em um bloco try/catch e utilizar o método TrackException. Estou optando por utilizar a versão mais simples do método, mas há parâmetros opcionais que permitem incluir um dicionário com valores customizados, assim como fizemos no código acima.

public static decimal Calcular(string cep)
{
    var resultado = 0M;

    try
    {
        throw new ArgumentException(“O CEP informado está fora da área de cobertura da transportadora.”);
        //cálculo do frete
    }
    catch (Exception e)
    {
        tc.TrackException(e);
        tc.Flush();
        throw;
    }

    return resultado;
}

Agora se atualizarmos o portal do Azure, irmos até a seção de Failures, o contador foi incrementado e é possível visualizar o erro que ocorreu, incluindo além da mensagem, toda a stack trace, útil para nós desenvolvedores, identificar onde o problema exatamente aconteceu.

Como é um exemplo simplista, estou fazendo o tratando localizado da exceção. Dependendo da estratégia de tratamento de erros da aplicação, é possível centralizar o envio das informações do erro em um ponto global, e dependendo da tecnologia utilizada, você pode recorrer ao suporte que ela dá para isso. Em aplicações ASP.NET tradicionais, você pode concentrar isso no evento Application_Error no arquivo Global.asax, no ASP.NET MVC em um filtro, no ASP.NET Web API no ExceptionLogger ou, no WCF, através da interface IErrorHandler.

Como podemos notar, o Application Insights eleva o “simples log de aplicações” para um outro nível. Podemos gerar diversas informações e temos a certeza que por trás existe uma grande infraestrutura para armazenamento e processamento delas. Terceirizando isso nos permite ainda mais focar no que de fato importa: o desenvolvimento da regra de negócio para qual a aplicação está sendo construída.

Tracking/Auditoria através do atributo ping

Em algumas situações precisamos, de alguma forma, catalogar as seções que são acessadas em uma aplicação Web para fins de auditoria ou até mesmo log de segurança. Uma técnica que é comumente utilizada é ter uma página ou ação que receba toda a demanda para qualquer link que se clique, e a partir de parâmetros que são colocadas em querystrings, este centralizador será capaz de realizar o log, e em seguida, redirecionar o usuário para a página solicitada.

Podemos eleger uma ação que será a centralizadora e todos os links serão renderizados apontando para ela incluindo o parâmetro o caminho da ação real a ser executada. No exemplo abaixo, o método responsável para fazer tudo isso será o RedirectUser.

public class TesteController : Controller
{
    public ActionResult Index()
    {
        return View();
    }

    public ActionResult Executar()
    {
        return View();
    }

    public ActionResult RedirectUser(string path)
    {
        //realiza o log

        return Redirect(path);
    }
}

Se acompanharmos o fluxo através de algum monitor de tráfego HTTP, veremos o redirecionamento sendo realizado. Vale lembrar que os redirecionamentos que estão sendo feitos aqui são realizados pelo navegados, exigindo o round-trip (código 302) entre o cliente e o serviço. Esse round-trip pode ser visualizado na imagem abaixo, através do ícone branco que está no segundo item da listagem.

Para facilitar estes cenários, o HTML 5 introduziu um novo atributo no elemetro <a /> chamando ping. Enquanto o atributo href deve ser o link de destino para o recurso desejado, o atributo ping servirá para apontar a ação que será utilizada para realizar o log quando o usuário clicar no respectivo link. A finalidade deste log vai ser, de fato, apenas catalogar o clique sem a necessidade de redirecionar o usuário para o local desejado/solicitado. Abaixo o exemplo de como configurar este atributo:

<a ping=”/Teste/Log” href=”/Teste/Executar”>Executar</a>

A imagem abaixo ilustra o procedimento em execução, postando assincronamente para o método Log enquanto também já encaminha o usuário para o destino clicado.

O interessante é que é realizado um POST para o método Log, incluindo várias informações extremamente relevantes, tais como a origem e o destino do ping, que podem ser capturadas pela ação de log para catalogar as informações das seções que foram solicitadas/acessadas. Abaixo temos as mensagens extraídas do Fiddler contendo os headers que foram enviados para o método Log neste exemplo, que faz uso também de um novo MIME que é o text/ping. O ponto negativo é que nem todos os navegadores implementaram este recurso. Infelizmente ele está somente implementando no Chrome e no Safari. Já há uma requisição aberta para que o time do Internet Explorer faça a adequação para suportar este atributo.

[ Requisição ]

POST http://localhost:17479/Teste/Log HTTP/1.1
Host: localhost:17479
Connection: keep-alive
Content-Length: 4
Cache-Control: max-age=0
Origin: null
Ping-From: http://localhost:17479/Teste/Index
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.117 Safari/537.36
Ping-To: http://localhost:17479/Teste/Executar
Content-Type: text/ping
Accept: */*
Accept-Encoding: gzip,deflate,sdch
Accept-Language: pt-BR,pt;q=0.8,en-US;q=0.6,en;q=0.4

PING

[ Resposta ]

HTTP/1.1 200 OK
Cache-Control: private
Server: Microsoft-IIS/8.0
X-AspNetMvc-Version: 5.0
X-AspNet-Version: 4.0.30319
X-SourceFiles: =?UTF-8?B?QzpcVXNlcnNcSXNyYWVsXERlc2t0b3BcV2ViQXBwbGljYXRpb24xXFdlYkFwcGxpY2F0aW9uMVxUZXN0ZVxMb2c=?=
X-Powered-By: ASP.NET
Date: Wed, 26 Feb 2014 02:01:12 GMT
Content-Length: 0

Tratamento e Log Global de Exceções – ASP.NET Web API

Para todo e qualquer tipo de aplicação, uma necessidade que temos é a captura dos erros não tratados pela aplicação e o log destes, para que seja possível a depuração e análise depois que o problema acontecesse, sendo necessário incluir o máximo de informações relevantes para essa apuração.

Apesar de existir algumas técnicas já bastante conhecidas no ASP.NET, a versão 2.1 do ASP.NET Web API, a Microsoft criou um novo namespace chamado System.Web.Http.ExceptionHandling e colocou ali alguns recursos específicos para a interceptação e log das exceções disparadas e não tratadas em qualquer nível da API, ou seja, tanto para as exceções que ocorrem no interior do código dos controllers, bem como os códigos de infraestrutura (formatadores, handlers, etc.).

Para utilizar este serviço de log, devemos recorrer à interface IExceptionLogger, que define um único método chamado LogAsync, que como o próprio nome sugere, já traz suporte para realizar esta tarefa de forma assíncrona, passando como parâmetro para ele uma instância da classe ExceptionLoggerContext, qual contém toda informação pertinente ao problema que ocorreu e onde ocorreu (ExceptionContext). Para facilitar as implementações, também já existe uma classe chamada ExceptionLogger, que implementa esta interface define um método chamado Log, que é onde customizeremos o log da exceção que ocorreu.

Para exemplificar, o código abaixo captura a exceção que ocorreu e armazena em um arquivo texto toda a stack trace. Só que criar a classe não é suficiente, pois precisamos acoplá-la à exceção, e para isso, devemos recorrer novamente ao arquivo Global.asax, que está logo no próximo trecho de código.

public class ExceptionTextLogger : ExceptionLogger
{
    private readonly string filePath;

    public ExceptionTextLogger(string filePath)
    {
        this.filePath = filePath;
    }

    public override void Log(ExceptionLoggerContext context)
    {
        File.AppendAllText(filePath, context.Exception.ToString());
    }
}

config.Services.Add(typeof(IExceptionLogger), 
    new ExceptionTextLogger(@”C:TempLog.txt”));

Quando as exceções ocorrem, lembrando que não são somente aquelas exceções referentes ao código que escrevemos no interior da API, mas incluem também os erros que ocorrem dentro das partes referente à infraestrutura, todos eles são repassados para os loggers configurados na aplicação.

Depois que o log é realizado, entra em cena um novo recurso chamado de exception handler. Este recurso que a Microsoft criou nos permitirá avaliar o erro que ocorreu e determinar se a exceção deverá ou não ser disparada. Caso o handler opte por não tratar o erro que ocorreu, a exceção será lançada. Aqui é feito o uso da classe ExceptionDispatchInfo, que captura o contexto onde ocorreu a exceção e posterga o disparo até que o handler faça a análise da mesma.

Seguindo a mesma estrutura do logger, temos uma interface chamada IExceptionHandler que fornece um método assíncrono chamado HandleAsync, recebendo como parâmetro uma classe chamada ExceptionHandlerContext, que informa os detalhes sobre o problema ocorrido através da propriedade ExceptionContext. E, novamente, temos uma classe base chamada ExceptionHandler com a implementação básica da interface IExceptionHandler, qual já podemos utilizar em nosso código:

public class ExceptionMessageHandler : ExceptionHandler
{
    public override void Handle(ExceptionHandlerContext context)
    {
        if (context.Exception is BusinessException)
            context.Result = new BusinessBadRequest(context);
    }

    private class BusinessBadRequest : IHttpActionResult
    {
        private readonly ExceptionHandlerContext exceptionContext;

        public BusinessBadRequest(ExceptionHandlerContext exceptionContext)
        {
            this.exceptionContext = exceptionContext;
        }

        public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
        {
            return Task.FromResult(
                this.exceptionContext.Request.CreateErrorResponse(
                    HttpStatusCode.BadRequest, 
                    new HttpError(this.exceptionContext.Exception, true)));
        }
    }
}

O código acima implementa o método Handle e avalia se a exceção que ocorreu foi do tipo BusinessException. Caso tenha sido, ele cria uma mensagem de retorno específica, atribuindo à propriedade Result. Caso essa propriedade não seja abastecida (no nosso caso, se não for uma BusinessException), então o ASP.NET Web API irá disparar o erro. E, finalmente, para colocá-lo em execução, vamos recorrer ao arquivo Global.asax:

config.Services.Replace(typeof(IExceptionHandler), new BusinessExceptionHandler());

Postergando o Disparo de Exceções

Utilizamos o bloco try/catch para conseguir tratar erro onde ele acontece, ou seja, envolvemos no bloco try o trecho de código que pode dar erro, e se ele acontecer, o bloco catch, opcionalmente, pode capturar o erro e o tratar, dando uma mensagem customizada, realizando o log, etc.

Na grande maioria das vezes quando trabalhamos na construção de uma biblioteca, não devemos tratar o erro no local, pois o ideal é deixar que ele seja disparado e propagado, para que assim o consumidor tenha a chance de saber o porque aquele erro aconteceu. E, se desejar interceptar a exceção dentro da biblioteca, é necessário redisparar a exceção preservando a stack trace, que é onde é armazenado todo o caminho (classes e métodos) até onde o erro de fato ocorreu. O código abaixo exibe como fazer isso:

try
{
    //Algum Código
}
catch (Exception ex)
{
    //Log, Tratamento

    throw;
}

Quando o throw for executado, o .NET dispara a exceção que ocorreu mantendo toda a stack trace, o que é essencial para que o consumidor possa entender o que houve. Só que se quisermos executar mais algum código a partir desta linha e postergar o disparo da exceção, não é possível, pois essa keyword tem uma tratativa especial pelo .NET Framework e aborta a execução das linhas que estão na sequência.

Isso inclusive era uma dificuldade do time do .NET Framework para conseguir fazer com que as exceções que ocorriam dentro dos métodos assíncronos fossem propagados sem perder as informações (stack trace). Para resolver isso, foi criado uma classe chamada ExceptionDispatchInfo (namespace System.Runtime.ExceptionServices), que através do método estático Capture, recebe como parâmetro e armazena a exceção que ocorreu, e mais tarde, quando desejar, podemos redispará-la através do método Throw, conforme é mostrado abaixo:

private static string LerConteudo(string nomeDoArquivo)
{
    var conteudo = string.Empty;
    ExceptionDispatchInfo erro = null;

    try
    {
        conteudo = File.ReadAllText(nomeDoArquivo);
    }
    catch (Exception ex)
    {
        erro = ExceptionDispatchInfo.Capture(ex);
    }

    //Algum outro código aqui

    if (erro != null) erro.Throw();

    return conteudo;
}

Unhandled Exception: System.IO.FileNotFoundException: Could not find file ‘C:UsersIsraelDesktopConsoleApplication1ConsoleApplication1binDebugTeste.txt’.
   at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)
   at System.IO.FileStream.Init(String path, FileMode mode, FileAccess access, Int32 rights, Boolean useRights, FileShare share, Int32 bufferSize, FileOptions options, SECURITY_ATTRIBUTES secAttrs, String msgPath, Boolean bFromProxy, Boolean useLongPath, Boolean checkHost)
   at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize, FileOptions options, String msgPath, Boolean bFromProxy, Boolean useLongPath, Boolean checkHost)
   at System.IO.StreamReader..ctor(String path, Encoding encoding, Boolean detectEncodingFromByteOrderMarks, Int32 bufferSize, Boolean checkHost)
   at System.IO.File.InternalReadAllText(String path, Encoding encoding, Boolean checkHost)
   at System.IO.File.ReadAllText(String path)
   at ConsoleApplication1.Program.LerConteudo(String nomeDoArquivo) in c:UsersIsraelDesktopConsoleApplication1ConsoleApplication1Program.cs:line 27
— End of stack trace from previous location where exception was thrown —
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at ConsoleApplication1.Program.LerConteudo(String nomeDoArquivo) in c:UsersIsraelDesktopConsoleApplication1ConsoleApplication1Program.cs:line 34
   at ConsoleApplication1.Program.Main(String[] args) in c:UsersIsraelDesktopConsoleApplication1ConsoleApplication1Program.cs:line 15

Detalhes sobre Tratamento de Erros no ASP.NET MVC

Desde as primeiras versões do ASP.NET MVC, a Microsoft criou um atributo chamado HandleErrorAttribute, que nada mais do que um filtro (de exceção) e tem a finalidade de interceptar todas as exceções que não são tratadas pelas ações “in-place”. E este filtro pode ser aplicado em nível de ação, de controller ou globalmente, no arquivo Global.asax e, este último nível, já vem configurado por padrão nos projetos ASP.NET MVC.

Este atributo garante com que qualquer exceção não seja exibida no navegador, apresentando aquela tela com detalhes do erro e que o usuário final dificilmente entende. Em sua configuração padrão, quando este atributo é acionado, ele redireciona o usuário para uma view chamada Error. Mas tanto o acionamento quanto a view a ser exibida pode ser customizada em qualquer nível, recorrendo às propriedades ExceptionType e View, conforme é mostrado no trecho de código abaixo. É importante dizer que este atributo pode ser decorado múltiplas vezes, possibilitando a seleção da view de acordo com a exceção que foi disparada.

[HandleError(ExceptionType = typeof(DivideByZeroException), View = “DivideByZeroException”)]
public class SiteController : Controller { }

Simplesmente o fato de existir este atributo decorado na ação, na classe ou globalmente, não é suficiente para acioná-lo, a não ser que configuremos a seção de customErrors no arquivo Web.Config. Esta seção está presente desde a primeira versão do ASP.NET, e agora pode e deve ser usada em conjunto com o atributo HandleErrorAttribute para garantir ou não o redirecionamento do usuário para a view desejada. A configuração mínima para o correto funcionamento deste atributo deve ser:

<configuration>
  <system.web>
    <customErrors mode=”On” />
  </system.web>
</configuration>

E uma vez que o atributo é adicionado, o ASP.NET passará a redirecionar o usuário para a view que exibirá a mensagem amigável. Só que além disso, o ASP.NET MVC faz captura o controller, da ação e da exceção que foi disparada, e configura a instância da classe HandleErrorInfo com todas essas informações referente ao erro que ocorreu, e a adiciona na propriedade ViewData da view que será exibida. Isso irá permitir capturar as informações para exibir na tela caso seja necessário informar ao usuário os detalhes do erro que ocorreu.

@model System.Web.Mvc.HandleErrorInfo

<html>
<head>
    <title>Error</title>
</head>
<body>
   

       

DivideByZeroException

       

            Controller: @Model.ControllerName

            Action: @Model.ActionName

            Exception: @Model.Exception.GetType().FullName

            Message: @Model.Exception.Message

       

   

</body>
</html>

É importante mencionar que o atributo HandlerErrorAttribute somente é acionado, por padrão, quando há erros de número 500 do HTTP. Para outros casos, como 401 (Unauthorized), ele não é acionado e, consequentemente, o erro é exibido para o usuário. Para garantir que seja apresentado, também de forma amigável, outros erros que não sejam do tipo 500, podemos recorrer ao customErrors especificando o erro e a respectiva página de erro que deve ser apresentada quando o mesmo ocorrer.

<customErrors mode=”On”>
  <error statusCode=”401″ redirect=”/401.htm” />
  <error statusCode=”404″ redirect=”/404.htm” />
</customErrors>

Para finalizar, é importante se atentar em casos onde uma determinada ação (que faz uso deste atributo) é utilizada também por uma aplicação Javascript, pois quando um erro ocorrer, neste cenário o que o cliente está esperando é uma mensagem do erro e não um redirecionamento para uma view. A ideia é que o erro seja tratado pelo próprio cliente (Javascript), que exibirá alguma informação para informar o erro. Uma opção aqui seria herdar da classe HandleErrorAttribute e avaliar se a ação está ou não sendo invocada por uma aplicação Javascript, e para isso, podemos recorrer ao header X-Requested-With, verificando se é igual à XMLHttpRequest, retornando um objeto do tipo JsonResult devidamente configurado.