ASP .NET 4.5 Web Forms - Tratamento de erros e registro de log - IX (Final do curso)


Neste tutorial, vamos incluir o tratamento de erros e o registro de erros em nossa aplicação.

O Tratamento de erros vai permitir que o aplicativo lide de forma mais amigável com os erros exibindo mensagens de erros adequadas em cada situação. O registro de erros permitirá que possamos encontrar e corrigir os erros que ocorreram durante a execução da aplicação.

Obs: Acompanhe todas as partes do curso neste link: http://www.macoratti.net/Cursos/aspn_450.htm

O que você vai aprender:

Este artigo foi integralmente baseado no artigo original : http://www.asp.net/web-forms/tutorials/aspnet-45/getting-started-with-aspnet-45-web-forms/aspnet-error-handling (com algumas alterações e portado para a linguagem VB .NET)

Conceitos

As Aplicações ASP.NET devem ser capaz de lidar com os erros que ocorrem durante a execução de uma forma consistente. A ASP.NET usa o Common Language Runtime (CLR), que fornece uma maneira de notificar as aplicações de erros de uma maneira uniforme. Quando ocorre um erro, uma exceção é lançada. Uma exceção é qualquer erro, condição ou comportamento inesperado que um aplicativo encontra durante sua execução.

Na plataforma .NET, uma exceção é um objeto que herda da classe System.Exception. Uma exceção é lançada a partir de uma área de código onde ocorreu um problema. A exceção é passada para a pilha de chamadas um lugar onde a aplicação fornece código para manipular a exceção. Se o aplicativo não tratar a exceção, o navegador é forçado a exibir os detalhes do erro.

Como melhor prática, lidamos com os erros ao nível do código usando blocos try/catch /finally dentro do código. Geralmente colocamos esses blocos de modo que o usuário possa corrigir problemas no contexto em que eles ocorrem. Se os blocos de tratamento de erros estiverem muito longe de onde ocorreu o erro, torna-se mais difícil fornecer aos usuários a informação de que ele precisa para corrigir o problema.

Classe de Exceção

A classe Exception é a classe base da qual as exceções herdam. A maioria dos objetos de exceção são instâncias de alguma classe derivada da classe Exception, como a classe SystemException, a classe IndexOutOfRangeException, ou a classe ArgumentNullException. A classe Exception tem propriedades, tais como a propriedade StackTrace, a propriedade InnerExceptio e a propriedade Message, que fornecem informações específicas sobre o erro que ocorreu.

Hierarquia de herança de exceção

O runtime tem um conjunto base de exceções que derivam da classe SystemException que ele lança quando uma exceção for encontrada. A maioria das classes que herdam da classe de exceção, como a classe IndexOutOfRangeException e a classe ArgumentNullException, não implementam membros adicionais. Portanto, a informação mais importante para uma exceção pode ser encontrada na hierarquia das exceções, o nome da exceção, e as informações contidas no exceção.

Hierarquia manipulação de exceção

Em uma aplicação ASP.NET Web Forms, exceções podem ser tratadas com base em uma hierarquia de tratamento específico. Uma exceção pode ser tratada nos seguintes níveis:

  1. nível de aplicação
  2. nível de página
  3. nível de código

Quando um aplicativo lida com exceções, informações adicionais sobre a exceção que é herdada da classe Exception muitas vezes podem ser recuperadas e exibidas para o usuário. Além do nível de aplicativo, página e código, você também pode lidar com exceções no nível de módulo HTTP e/ou usando um manipulador personalizado IIS.

Tratamento de erros a nível de aplicação

Você pode lidar com erros padrão no nível do aplicativo quer seja modificando a configuração de seu aplicativo ou adicionando um manipulador Application_Error no arquivo Global.asax de sua aplicação.

Você pode lidar com erros padrão e erros de HTTP, adicionando uma seção customErrors no arquivo Web.config. A seção customErrors permite que você especifique uma página padrão para a qual os usuários serão redirecionados quando ocorrer um erro. Ele também permite que você especifique páginas individuais para erros de código específicos de status.

A seguir temos um exemplo de configuração feita no arquivo web.config que define o tratamento de erro neste escopo:

<configuration>
  <system.web>
    <customErrors mode="On" defaultRedirect="ErrorPage.aspx?handler=customErrors%20section%20-%20Web.config">
      <error statusCode="404" redirect="ErrorPage.aspx?msg=404&amp;handler=customErrors%20section%20-%20Web.config"/>
    </customErrors>
  </system.web>
</configuration>

Infelizmente, quando usamos a configuração para redirecionar o usuário para uma página diferente, não temos os detalhes do erro que ocorreu.No entanto, podemos capturar os erros que ocorrem em qualquer lugar na aplicação, adicionando código para o manipulador Application_Error no arquivo Global.asax conforme mostra o exemplo a seguir:

    Sub Application_Error(ByVal sender As Object, ByVal e As EventArgs)
        ' dispara quando um erro ocorrer
        Dim exc As Exception = Server.GetLastError()

        If TypeOf exc Is HttpUnhandledException Then
            ' Passa o erro para a página de erro.
            Server.Transfer("ErrorPage.aspx?handler=Application_Error%20-%20Global.asax", True)
        End If
    End Sub

Tratamento de erro a nível de página

Um manipulador a nível de página retorna o usuário para a página onde o erro ocorreu, mas como as instâncias dos controles não são mantidas, não haverá mais nada na página. Para fornecer os detalhes do erro para o usuário, devemos escrever especificamente os detalhes do erro na página.

Devemos usar um manipulador de erro a nível de página para registrar erros sem tratamento ou para levar o usuário para uma página que pode exibir informações úteis.

Este exemplo de código mostra um manipulador para o evento de erro em uma página ASP.NET. Este manipulador captura todas as exceções que não estão sendo tratadas dentro do bloco try/catch na página.

Private Sub Page_Error(sender As Object, e As EventArgs)
	
Dim exc As Exception = Server.GetLastError()
' Trata o erro
If TypeOf exc Is HttpUnhandledException Then
     ErrorMsgTextBox.Text = "Ocorreu um erro nesta página " 
End If
    ' LImpar ao erro a partir do servidor
    Server.ClearError()
End Sub

Depois de tratar um erro, você deve limpá-lo, chamando o método ClearError do objeto Server (classe HttpServerUtility), caso contrário, você vai ver um erro que tenha ocorrido anteriormente.

Tratamento de erro a nível de código

A instrução try-catch consiste em um bloco try seguido de uma ou mais cláusulas de captura, que especificam os manipuladores para exceções diferentes. Quando uma exceção é lançada, o Common Language Runtime(CLR) procura a instrução catch que trata esta exceção.

Se o método atualmente em execução não contém um bloco catch, a CLR olha para o método que chamou o método atual, e assim por diante, até a pilha de chamadas. Se nenhum bloco catch é encontrado, a CLR exibe uma mensagem de exceção não tratada para o usuário e para a execução do programa.

O exemplo de código a seguir mostra uma maneira comum de usar try/catch/finally para manipular erros.

Try
    file.ReadBlock(buffer, index, buffer.Length)
Catch e As FileNotFoundException
    Server.Transfer("NoFileErrorPage.aspx", True)
Catch e As System.IO.IOException
    Server.Transfer("IOErrorPage.aspx", True)
Finally
    If file IsNot Nothing Then
 	file.Close()
   End If
End Try

No código acima, o bloco try contém o código que precisa ser protegido contra uma possível exceção. O bloco é executado até que uma exceção é lançada ou o bloco é concluído com êxito. Se uma excepção FileNotFoundException ou uma exceção IOException ocorre, a execução é transferida para uma página diferente. Em seguida, o código contido no bloco finally é executado, se ocorreu um erro ou não. (O código do bloco finally sempre será executado)

Incluindo o suporte ao registro de erros

Antes de adicionar a manipulação de erros em nossa aplicação vamos adicionar o suporte ao registro de erros, adicionando uma classe ExceptionUtility na pasta de Logic do nosso projeto.

Ao fazer isso, cada vez que o aplicativo tratar um erro, os detalhes do erro serão adicionados ao arquivo de log de erros.

Clique com o botão direito do mouse sobre a pasta Logic e selecione Add -> New Item;

A seguir selecione Visual Basic -> Code e o template Class e informe o nome ExceptionUtility.vb e clique no botão Add;

A seguir digite o código abaixo neste arquivo:

Imports System
Imports System.Collections.Generic
Imports System.Linq
Imports System.Web
Imports System.IO

Namespace WingtipToys.Logic
    ' Creia o nosso utilitário de tratamenteo de exceptions
    Public NotInheritable Class ExceptionUtility
        ' Todos os métodos são estativiso 
        Private Sub New()
        End Sub

        ' Registra uma exceção
        Public Shared Sub LogException(exc As Exception, source As String)
            ' Inclui a logica para as registrar as exceções
            ' Pega o caminho absoluto para o arquivo de log
            Dim logFile As String = "App_Data/ErrorLog.txt"
            logFile = HttpContext.Current.Server.MapPath(logFile)

            ' Abre o arquivo de log para acnexar e escrever o log
            Dim sw As New StreamWriter(logFile, True)
            sw.WriteLine("********** {0} **********", DateTime.Now)
            If exc.InnerException IsNot Nothing Then
                sw.Write("Inner Exception Type: ")
                sw.WriteLine(exc.InnerException.[GetType]().ToString())
                sw.Write("Inner Exception: ")
                sw.WriteLine(exc.InnerException.Message)
                sw.Write("Inner Source: ")
                sw.WriteLine(exc.InnerException.Source)
                If exc.InnerException.StackTrace IsNot Nothing Then
                    sw.WriteLine("Inner Stack Trace: ")
                    sw.WriteLine(exc.InnerException.StackTrace)
                End If
            End If
            sw.Write("Exception Type: ")
            sw.WriteLine(exc.[GetType]().ToString())
            sw.WriteLine("Exception: " + exc.Message)
            sw.WriteLine("Source: " + source)
            sw.WriteLine("Stack Trace: ")
            If exc.StackTrace IsNot Nothing Then
                sw.WriteLine(exc.StackTrace)
                sw.WriteLine()
            End If
            sw.Close()
        End Sub
    End Class
End Namespace

Quando ocorre uma exceção,ela pode ser gravada em um arquivo de log de exceção chamando o método LogException. Este método tem dois parâmetros, o objeto da exceção e uma string contendo detalhes sobre a origem da exceção. O log de exceção é gravado no arquivo ErrorLog.txt na pasta App_Data.

Adicionando uma página de erro

Em nossa aplicação usaremos uma página para exibir erros. A página de erro é projetada para mostrar uma mensagem de erro segura para os usuários do site. No entanto, se o usuário for um desenvolvedor fazendo uma solicitação HTTP que está sendo servida localmente na máquina onde reside o código, iremos exibir detalhes de erro adicionais na página de erro que serão vistas apenas pelo desenvolvedor.

Clique com o botão direito do mouse sobre o nome do projeto e selecione Add -> New Item;

Selecione Visual Basic -> Web e a seguir o template Web Form using Master Page e informe o nome ErrorPage.aspx e clique no botão Add;

Selecione o arquivo Site.Master como a página principal e, em seguida, escolha OK.

A seguir substitua a marcação existente pelo código a seguir:

<%@ Page Title="" Language="vb" AutoEventWireup="false" MasterPageFile="~/Site.Master" CodeBehind="ErrorPage.aspx.vb" Inherits="WingTipToys.ErrorPage" %>
<asp:Content ID="Content1" ContentPlaceHolderID="HeadContent" runat="server">
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="FeaturedContent" runat="server">
     <h2>Erro:</h2>
    <p></p>
    <asp:Label ID="FriendlyErrorMsg" runat="server" Text="Label" Font-Size="Large" style="color: red"></asp:Label>

    <asp:Panel ID="DetailedErrorPanel" runat="server" Visible="false">
        <p>
            Erro Detalhado:
            <br />
            <asp:Label ID="ErrorDetailedMsg" runat="server" Font-Bold="true" Font-Size="Large" /><br />
        </p>
        <p>
            Tratamento Erro:
            <br />
            <asp:Label ID="ErrorHandler" runat="server" Font-Bold="true" Font-Size="Large" /><br />
        </p>
        <p>
            Mensagem de erro Detalhada:
            <br />
            <asp:Label ID="InnerMessage" runat="server" Font-Bold="true" Font-Size="Large" /><br />
        </p>
        <pre>
            <asp:Label ID="InnerTrace" runat="server"  />
        </pre>
    </asp:Panel>

</asp:Content>
<asp:Content ID="Content3" ContentPlaceHolderID="MainContent" runat="server">
</asp:Content>

No arquivo code-behind ErrorPage.aspx.vb inclua o código abaixo:

Imports System
Imports System.Collections.Generic
Imports System.Linq
Imports System.Web
Imports System.Web.UI
Imports System.Web.UI.WebControls
Imports WingTipToys.WingtipToys.Logic

Public Class ErrorPage
    Inherits System.Web.UI.Page

    Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
        ' Cria as mensagens de erro seguras
        Dim generalErrorMsg As String = "Ocorreu um problema na aplicação web. Tente novamente. " + "Se o ocorre persistir contacte o suporte técnico."
        Dim httpErrorMsg As String = "Ocorreu um erro HTTP. Página não encontrada. Tente novamente."
        Dim unhandledErrorMsg As String = "O erro não foi tratado pelo código da aplicação."

        ' exibe as mensagens de erro
        FriendlyErrorMsg.Text = generalErrorMsg

        ' Determina onde o erro foi tratado
        Dim errorHandler__1 As String = Request.QueryString("handler")
        If errorHandler__1 Is Nothing Then
            errorHandler__1 = "Error Page"
        End If

        ' pegao ultimo erro do servidor.
        Dim ex As Exception = Server.GetLastError()

        ' Pega o numero do erro passado como um valor querystring 
        Dim errorMsg As String = Request.QueryString("msg")
        If errorMsg = "404" Then
            ex = New HttpException(404, httpErrorMsg, ex)
            FriendlyErrorMsg.Text = ex.Message
        End If

        'Se a exceção não existe mais, cria uma exceção genérica
        If ex Is Nothing Then
            ex = New Exception(unhandledErrorMsg)
        End If

        ' exibe os detalhes de erro somente para o desenvolvedor.SOMENTE ACESSO LOCAL
        If Request.IsLocal Then
            'mensagem de erro detalhada
            ErrorDetailedMsg.Text = ex.Message

            ' mostra onde o erro foi tratado
            ErrorHandler.Text = errorHandler__1

            ' mostra o acesso local 
            DetailedErrorPanel.Visible = True

            If ex.InnerException IsNot Nothing Then
                InnerMessage.Text = ex.[GetType]().ToString() + "<br/>" + ex.InnerException.Message
                InnerTrace.Text = ex.InnerException.StackTrace
            Else
                InnerMessage.Text = ex.[GetType]().ToString()
                If ex.StackTrace IsNot Nothing Then
                    InnerTrace.Text = ex.StackTrace.ToString().TrimStart()
                End If
            End If
        End If

        ' registra a exceção
        ExceptionUtility.LogException(ex, errorHandler__1)

        ' limpa o erro do servidor
        Server.ClearError()
    End Sub

End Class

Quando a página de erro for exibida, o evento Page_Load é executado. No manipulador Page_Load, o local onde o erro foi primeiro tratado é determinado. Então, o último erro que ocorreu é obtido através do método GetLastError do objeto Server.

Se a exceção não existir mais, uma exceção genérica será criada. Então, se o pedido HTTP foi feita localmente, todos os detalhes do erro serão mostrados. Neste caso, apenas a máquina local executando a aplicação web vai ver esses detalhes do erro. Depois que as informações do erro foram exibidas, o erro é adicionado ao arquivo de log e o erro é eliminado do servidor.

Exibindo mensagens de erro não tratadas para a Aplicação

Ao adicionar uma seção customErrors no arquivo Web.config, você pode rapidamente tratar erros simples que ocorrem na aplicação. Você também pode especificar como lidar com erros com base no seu valor de código de status, como 404 - Arquivo não encontrado.

Abra o arquivo web.config da raiz do projeto e inclua a seção customErrors no interior do nó <system.web> conforme abaixo:

A seção customErrors especifica o modo, o que é definido como "On" (mode="On"). Também especifica o defaultRedirect, que diz à aplicação para qual página navegar quando ocorrer um erro.

Além disso, adicionamos um elemento de erro específico que define como lidar com um erro 404 quando uma página não for encontrada. Mais adinte, vamos adicionar o tratamento de erro adicional que irá capturar os detalhes de um erro a nível de aplicação.

Testando a aplicação

Execute a aplicação pressionando CTRL+F5 (ou clicando no botão do menu que executa a aplicação);

O navegador irá abrir e apresentar a página Default.aspx:

Informe a seguinte URL no navegador para provocar um erro: http://localhost:674846/NoPage.aspx (o número da porta para sua aplicação será diferente)

A página de erro ErrorPage.aspx será exibida conforme a figura a seguir:

Quando você solicita a página NoPage.aspx, que não existe, a página de erro irá mostrar a mensagem de erro simples e as informações de erro detalhadas, se detalhes adicionais estiverem disponíveis.

No entanto, se o usuário solicitou uma página inexistente a partir de um local remoto, a página de erro só vai mostrar a mensagem de erro em vermelho.

Incluindo uma exceção para testes

Para verificar como o aplicativo irá funcionar quando ocorre um erro, você pode deliberadamente criar condições de erro. Vamos lançar uma exceção de teste quando a página padrão for carregada para ver o que acontece.

Abra o arquivo code-behind Default.aspx.vb e no evento Load da página inclua o código abaixo:

Public Class _Default
    Inherits Page

    Protected Sub Page_Load(ByVal sender As Object, ByVal e As EventArgs) Handles Me.Load
        Throw New InvalidOperationException("O Erro : InvalidOperationException " + "õcorreu no evento Page_Load ná página Default.aspx.")
    End Sub
End Class

No código acima estamos criando uma exceção do tipo InvalidOperationException que irá ocorrer quando a página Default.aspx for carregada.

Testando a aplicação

Execute a aplicação novamente pressionando CTRL+F5 (ou clicando no botão do menu que executa a aplicação);

O navegador irá abrir e apresentar a página Default.aspx:

A página exibirá a mensagem de erro conforme abaixo:

Adicionando o tratamento de erros a nível de aplicativo

Em vez de capturar a exceção usando a seção customErrors no arquivo Web.config, onde você ganha pouca informação sobre a exceção, você pode interceptar o erro no nível da aplicação e recuperar detalhes do erro.

Abra o arquivo Global.asax e atualize o código do evento Application_Error conforme abaixo:

 Sub Application_Error(ByVal sender As Object, ByVal e As EventArgs)
        ' pega o último erro do servidor
        Dim exc As Exception = Server.GetLastError()

        If TypeOf exc Is HttpUnhandledException Then
            If exc.InnerException IsNot Nothing Then
                exc = New Exception(exc.InnerException.Message)
                Server.Transfer("ErrorPage.aspx?handler=Application_Error%20-%20Global.asax", True)
            End If
        End If

    End Sub

Quando ocorre um erro na aplicação, o manipulador Application_Error é chamado. Nesse manipulador, a última exceção é recuperada e revisada. Se a exceção ocorreu sem um tratamento e a exceção contém detalhes de uma exceção interior(InnerException não é nulo), a aplicação transfere a execução para a página de erro onde os detalhes da exceção são exibidos.

Testando a aplicação

Execute a aplicação novamente pressionando CTRL+F5 (ou clicando no botão do menu que executa a aplicação);

O navegador irá abrir e apresentar a página Default.aspx:

A página exibirá a mensagem de erro conforme abaixo:

Adicionando o tratamento de erros a nível de página

Podemos adicionar a manipulação de erros a nível de página para uma página quer adicionando um atributo ErrorPage na diretiva @Page da página, ou pela adição de um manipulador de eventos Page_Error ao código-behind da página. Vamos adicionar um manipulador de eventos Page_Error que irá transferir a execução para a página ErrorPage.aspx.

Abra o arquivo code-behind Default.aspx.vb e inclua o código para o evento Page_Error() conforme abaixo:

   Private Sub Page_Error(sender As Object, e As EventArgs)
        ' pega o ultimo erro do servidor.
        Dim exc As Exception = Server.GetLastError()

        ' trata o erro
        If TypeOf exc Is InvalidOperationException Then
            ' Passa o erro para pagina de erro
            Server.Transfer("ErrorPage.aspx?handler=Page_Error%20-%20Default.aspx", True)
        End If
    End Sub

Quando ocorre um erro na página, o manipulador de eventos Page_Error é chamado. Nesse manipulador, a última exceção é recuperada e revisada. Se um um erro do tipo InvalidOperationException ocorrer, o manipulador de eventos Page_Error transfere a execução para a página de erro onde os detalhes da exceção são exibidos.

Testando a aplicação

Execute a aplicação novamente pressionando CTRL+F5 (ou clicando no botão do menu que executa a aplicação);

O navegador irá abrir e apresentar a página Default.aspx:

A página exibirá a mensagem de erro conforme abaixo:

Após essa bateria de testes remova ou comente o código usado no evento Load da página Default.aspx para lançar deliberadamente um erro.

Usando ELMAH

ELMAH (módulos de registro de erros e manipuladores) é uma unidade de registro de erros que você conecta em sua aplicação ASP.NET como um pacote NuGet. A ELMAH oferece os seguintes recursos:

Antes de trabalhar com o ELMAH, temos que instalá-lo. Isto é fácil usando o instalador de pacotes NuGet.

Obs:O NuGet é uma extensão do Visual Studio que faz com que seja fácil de instalar e atualizar bibliotecas de código aberto e ferramentas no Visual Studio.

Com o projeto aberto no Visual Web Developer for Web clique no menu TOOLS  e a seguir selecione Library Package Manager -> Manage Nuget Packages for Solution...;

A janela - Manage Nuget Packages - será exibida;

Selecione o item OnLine e digite na caixa de busca a palavra ELMAH;

O pacote ELMAH deverá ser exibido no topo da lista de itens encontrados. Clique no botão Install para instalar o pacote ELMAH;

A instalação apresentará uma tela solicitando a confirmação para instalar o pacote no projeto. Confirme clicando em OK.

Obs: Você deve estar conectado para poder baixar o pacote.

Se for solicitado a você para recarregar os arquivos abertos, selecione "Sim para Todos".(Yes to All).

O pacote ELMAH adiciona entradas na seção <modules> do nó <system.webServer> no arquivo Web.config. Se o sistema solicitar se você deseja recarregar o arquivo Web.config modificado, clique em Sim(Yes).

Com essa etapa concluída estamos pronto para usar o pacote ELMAH.

Visualizando o log ELMAH

Ver o log ELMAH é fácil, mas primeiro vamos criar uma exceção não tratada que será gravada no log ELMAH.

Pressione CTRL + F5 para executar nossa aplicação

A seguir digite a seguinte URL (usando o seu número de porta) no navegador para provocar uma exceção não tratada no log ELMAH:

localhost:64786/NoPage.aspx

A página de erro será exibida conforme abaixo:

Para visualizar o log de erros ELMAH digite a url no navegador: http://localhost:64786/elmah.axd

O log de erros ELMAH será exibido indicando informações sobre a ocorrência

Se você clicar no link Details... irá obter os detalhes da exceção:

Atenção !!! Cuidado ao usar o recurso ELMAH pois qualquer um pode ler o código de erro acessando a página, e existem exploits (Um exploit, é um programa de computador ou uma sequência de comandos que se aproveita das vulnerabilidades de um sistema) que exploram isso.

Veja como hackear Elmah aqui: http://www.troyhunt.com/2012/01/aspnet-session-hijacking-with-google.html

Para implementar seguro (nao testei): http://haacked.com/archive/2007/07/24/securely-implement-elmah-for-plug-and-play-error-logging.aspx

colaboração de Vitor Hugo

Neste tutorial aprendemos como manipular erros ao nível da aplicação, a nível da página, e do nível de código. Aprendem os também a registrar erros tratados e sem tratamento para posterior análise.   Além disso, aprendemos  sobre a importância das mensagens de erro seguras.

E com isso encerramos essa série de tutoriais onde acompanhamos a criação de uma aplicação ASP .NET Web Forms usando o Visual Studio Express For Web 2012.

Embora a aplicação possa ser melhorada em muitos aspectos o objetivo foi mostrar como podemos criar aplicações dinâmicas com acesso a dados usando recursos integrados do Visual Studio.

Espero que esses tutoriais tenham contribuído para aguçar o seu desejo em aprender mais sobre a tecnologia ASP .NET pois o que vimos foi só a ponta do Iceberg. 

Bom estudo !!!

O projeto completo também esta no Super DVD .NET

Referências:


José Carlos Macoratti