Peguei uma aplicação esses dias que não tinha nada de tratamento de erros. Nada. Quando uma exceção era lançada, bum, ia parar direto naquela linda tela amarela do ASP.Net. No máximo tinha uma TC (Try Catch Finally) que fazia o seguinte:

    1 Dim componente As New AlgumComponente()

    2 Try

    3     componente.AlgumaFuncao()

    4 Catch ex As Exception

    5     Throw ex

    6 End Try

Esse tipo de código é perigosíssimo porque esconde o erro, que depois virará um pesadelo para encontrar. Mas depois eu entro neste assunto. A questão era: eu precisava sugerir uma solução de tratamento de erro em poucos minutos (a aplicação ia subir para homologação no mesmo dia).

Analisei algumas opções:

  1. Base de dados – Não dá tempo de criar os schema e os scripts, está fora.
  2. XML no file system – Estava fora das políticas escrever arquivos em disco, a não ser que fosse muito necessário. Fora.
  3. E-mail – não tinha endereço disponível, e não dava tempo para criar. Fora o trabalho de monitorar desse jeito. Fora.
  4. Algum legado de log ou log de erros. Não havia. Fora.
  5. Memória. Opa… memória!

Sim, guardar na memória resolveu o problema. É óbvio que é uma solução para quem está com pouco tempo, mas ia resolver. Depois implementaria algo mais sofisticado. A idéia era: quando acontecer uma exceção, pega o objeto de exceção e guarda ele em uma lista de acesso global. Perfeito. Criei algo assim:

    1 Public Class MeuLogger

    2

    3     Public Shared ReadOnly Property Errors() As List(Of Exception)

    4         Get

    5             Dim Exceptions As List(Of Exception) = TryCast(System.Web.HttpContext.Current.Application(“apperrors”), List(Of Exception))

    6             If Exceptions Is Nothing Then

    7                 Exceptions = New List(Of Exception)

    8                 System.Web.HttpContext.Current.Application(“apperrors”) = Exceptions

    9             End If

   10             If Exceptions.Count > 50 Then

   11                 Exceptions.RemoveRange(0, Exceptions.Count – 50)

   12             End If

   13             Return Exceptions

   14         End Get

   15     End Property

   16

   17 End Class

Não lembro se o código era exatamente esse, mas era mais ou menos esse. Nada mais é que uma List(Of Exception) guardada em uma variável de aplicação. Sempre que alguém solicitava a lista eu verificava se ela tinha mais que 50 itens, e se tinha eu removia os primeiros. Cinquenta exceções não é quase nada, a memória não vai sobrecarregar. Depois é só exibir. Vejam os códigos. No caso de erro:

    1 Try

    2     componente.AlgumaFuncao()

    3 Catch ex As Exception

    4     MeuLogger.Errors.Add(ex)

    5     Return

    6 End Try

E na hora de exibir criei outra página, joguei um gridview de nome gvErrors sem mudar nada, e fiz o binding, simples assim:

    1 Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load

    2     LoadErrors()

    3 End Sub

    4

    5 Private Sub LoadErrors()

    6     Dim errors As List(Of Exception) = MeuLogger.Errors

    7     gvErrors.DataSource = errors

    8     gvErrors.DataBind()

    9 End Sub

Para ver o erro era só chamar esta página. Pronto, resolveu. Faça você um teste, vai funcionar. Na chamada de componente.AlgumaFuncao(), lance uma exceção. E faça um “for” umas 70 vezes. Tudo certo até aqui.

Pois bem, mas o título do post não é “como criar um gerenciador de erros em 5 minutos”, mas “Como fazer databind com tipos herdados”. E por quê? Porque se as exceções registrada na lista de erros forem subtipos diferentes de Exception, você vai ter problemas. O gridview, quando começa a fazer o binding de uma List(Of T) na chamada de DataBind(), verifica o tipo da lista logo no primeiro item, e com esse tipo ele tentará montar as colunas dinamicamente. Ele não utiliza o “T” do generics, ele olha o tipo no primeiro objeto da lista. Só que o segundo objeto pode ser de um tipo diferente, mesmo que ambos herdem de Exception. Resultado: o gridview vai procurar por uma propriedade que não existe e… tela amarela de novo. Por exemplo: Se a primeira exceção a ser listada for uma ArgumentException, ela vai ter a propriedade ParamName e a coluna ParamName vai ser adicionada ao gridview. Se alguma outra exceção não for uma ArgumentException, ela não vai ter esta propriedade e teremos o problema.

Vejam o erro abaixo. Notem que temos lançada uma System.Reflection.TargetException, com a mensagem “Object does not match target type”, bem na linha do databind.

image

O problema não acontece com colunas estáticas, mas, de qualquer forma, há momentos em que queremos um gridview dinâmico mesmo. Para isso fui pesquisar. Das várias soluções que encontrei, a mais fácil e rápida de ser implementada foi criar um wrapper que envolve a classe de lista. Ficou assim:

   22 Private Class ListaDeErrosTipada

   23     Inherits List(Of Exception)

   24     Implements System.ComponentModel.ITypedList

   25

   26     Sub New(ByVal innerList As IList(Of Exception))

   27         MyBase.New(innerList)

   28     End Sub

   29

   30

   31     Public Function GetItemProperties(ByVal listAccessors() As PropertyDescriptor) As PropertyDescriptorCollection Implements ITypedList.GetItemProperties

   32         Return System.ComponentModel.TypeDescriptor.GetProperties(GetType(Exception))

   33     End Function

   34

   35     Public Function GetListName(ByVal listAccessors() As PropertyDescriptor) As String Implements ITypedList.GetListName

   36         Return “ExceptionList”

   37     End Function

   38 End Class

Veja que a classe herda de “List(Of Exception)”, recebe uma outra lista de exceções no construtor, e repassa esta lista ao construtor da classe base, para que ela já seja adicionada à coleção. Onde está o segredo? Na interface ITypedList, implementada pela lista customizada. Ela informa o tipo que a lista está utilizando. O método GetItemProperties é quem dá a brecha, com a ajuda do método estático GetProperties da classe TypeDescriptor. O outro método não faz diferença neste contexto.

Como fica então o código de databind? Fica assim:

    5 Private Sub LoadErrors()

    6     Dim errors As List(Of Exception) = MeuLogger.Errors

    7     gvErrors.DataSource = New ListaDeErrosTipada(errors)

    8     gvErrors.DataBind()

    9 End Sub

Somente a linha 7 foi alterada, onde pegamos a coleção e a repassamos para a classe de lista de erros tipada.

Com isso meu “log de erros em 5 minutos” ficou pronto.

Happy coding!

Giovanni Bassi

Arquiteto e desenvolvedor, agilista, escalador, provocador. É fundador e CSA da Lambda3. Programa porque gosta. Acredita que pessoas autogerenciadas funcionam melhor e por acreditar que heterarquia é mais eficiente que hierarquia. Foi reconhecido Microsoft MVP há mais de dez anos, dos mais de vinte que atua no mercado. Já palestrou sobre .NET, Rust, microsserviços, JavaScript, TypeScript, Ruby, Node.js, Frontend e Backend, Agile, etc, no Brasil, e no exterior. Liderou grupos de usuários em assuntos como arquitetura de software, Docker, e .NET.