Israel Aece

Software Developer

Explorando Segurança do ASP.NET - Arquitetura

O ASP.NET 2.0 inclui uma porção de novos serviços de persistência de dados em um banco de dados. Essa nova arquitetura é chamada de Provider Model e nos dá uma enorme flexibilidade, onde podemos trocar a fonte de dados/persistência e a aplicação continua trabalhando normalmente. Este provider é um módulo do software que estamos desenvolvendo que fornece uma interface genérica para uma fonte de dados, onde abstraem a base de dados. Além disso ser flexível, a qualquer momento podemos trocar a base de dados, essa arquitetura também é extensível, ou seja, se mais tarde quisermos customizar algo, podemos fazer isso sem maiores problemas.

A idéia dentro deste padrão é ter uma classe abstrata, onde dentro dela teremos métodos e propriedades que devem ser implementados nas classes concretas e, através de configurações no arquivo Web.Config, definimos a classe concreta que iremos utilizar para a aplicação. A questão é que essa classe concreta é instanciada em runtime, pois o ASP.NET recupera isso do arquivo de configuração e se encarrega de criar a instância correta da classe para uma determinada funcionalidade.

Essa arquitetura já não é lá muito nova. Se analisarmos o ASP.NET Fóruns ou até mesmo o PetShop 3.0, veremos que eles utilizam uma técnica bem parecida, onde fazem o uso dos padrões Abstract Factory e Factory Method (padrões Criacionais) para garantir essa genericidade. Isso faz com que devemos ter uma classe abstrata por trás a qual as classes concretam as implementam e o runtime se encarrega de criar a instância correta da classe concreta baseando-se nas configurações do arquivo Web.Config.

Essa arquitetura é usada extensivamente dentro do .NET Framework 2.0 (ASP.NET), onde temos classes abstratas para cada situação diferente e, para um determinado repositório de dados, uma classe concreta já implementada. Através da tabela abaixo veremos o nome da funcionalidade, a classe base e as classes concretas que se enquandram dentro do escopo do artigo:

Funcionalidade Classe Base Classes Concretas Descrição
Membership MembershipProvider SqlMembershipProvider
ActiveDirectoryMembershipProvider
Responsável por gerenciar os usuários de uma aplicação ASP.NET.
Roles RoleProvider SqlRoleProvider
WindowsTokenRoleProvider
AuthorizationStoreRoleProvider
Utilizado para gerir os papéis dos usuários dentro da aplicação ASP.NET.


Reparem que para uma determinada classe abstrata, como por exemplo MembershipProvider, já temos, por padrão, algumas classes que a Microsoft implementou para já utilizarmos a funcionalidade. Um exemplo disso é a classe SqlMembershipProvider, a qual utiliza uma base de dados SQL Server 2000 ou superior para disponibilizar o recurso. Como foi dito acima, não se restringe somente à isso. Temos classes para gerencimento de estado, Web Parts, Site Map, Profile, etc., que não foram citados/explicados por não fazerem parte do escopo deste artigo. Se no futuro precisarmos customizar alguma das funcionalidades da tabela acima para uma base de dados, como por exemplo Oracle, basta criarmos uma classe herdando de MembershipProvider ou RoleProvider e implementar os métodos e propriedades exclusivamente para aquela base de dados. Finalmente, para ilustrar essa arquitetura, é mostrado através da figura abaixo o design das classes, já com as devidas denotações de herança entre elas:

Figura 1 - Design das classes utilizando Provider Model.

Além das classes que vimos acima, temos ainda duas classes estáticas (compartilhadas) que são, também, parte das principais. São estas classes que são expostas para nós, desenvolvedores, utilizarmos e fazermos a chamada aos métodos e propriedades de forma genérica. Essas classes são: Membership e Roles, as quais estão contidas dentro do Namespace System.Web.Security. No caso da classe Membership, existe um membro interno chamado s_Provider do tipo MembershipProvider (a classe base para as classes concretas de Membership, como por exemplo o SqlMembershipProvider), o qual receberá a instância da classe concreta. Essa inicialização acontece quando um método interno chamado Initialize é executado. Ele se encarrega de extrair os providers do arquivo Web.Config e instanciá-los para que, quando chamarmos os métodos e propriedades, já sejam efetivamente os métodos e propriedades da classe concreta que queremos utilizar. O funcionamento é também semelhante para a classe Roles.

Por questões de infraestrutura, utilizaremos no decorrer deste artigo o banco de dados SQL Server 2000. Mas então como prepará-lo para fazer o uso desta funcionalidade? Pois bem, para que o mesmo possa ser utilizado para isso, é necessário criarmos dentro do banco de dados a infraestrutura (Tabelas, Índices, Stored Procedures e Triggers) necessária para a utilização do recurso, que no caso serão Membership e Roles.

Quando instalamos no .NET Framework 2.0, em seu diretório %WinDir%\Microsoft.NET\Framework\v2.0.50727 é instalado um utilitário chamado aspnet_regsql.exe. Este utilitário, dados alguns parâmetros, faz todo o trabalho da criação da infraestrutura dentro de um determinado banco de dados SQL Server. Veremos na tabela abaixo os parâmetros que ele aceita:

Parâmetro Descrição
-? Exibe o Help do utilitário.
-W Roda o utilitário em modo Wizard. Esse é o padrão caso nenhum outro parâmetro seja informado. Com isso, você terá um wizard (que é mostrado abaixo) para acompanhá-lo durante o processo.

-C Especifica a ConnectionString para o servidor onde o banco de dados SQL Server está instalado. Se desejar, você pode informar isso separadamente (mostrado mais abaixo).
-S Nome do servidor onde o SQL Server está.
-U Login de acesso dentro do SQL Server (é necessário informar o password ou Integrated Security).
-P Password para o Login especificado através do parâmetro -U.
-E Autentica o usuário com as credenciais do usuário corrente logado no Windows.
-sqlexportonly Usado para criar um arquivo de script para adicionar ou remover tais funcionalidades.
-A all|m|r|p|c|w Adiciona a infraestrutura dentro da base de dados para uma determinada funcionalidade. Além do -A, ainda há o parâmetro complementar onde você deve informar qual será a funcionalidade que deseja adicionar:

all - Todas
m - Membership
r - Role management
p - Profile
c - Web Parts personalization
w - Web events
-R all|m|r|p|c|w Remove a infraestrutura dentro da base de dados para uma determinada funcionalidade. Além do -R, ainda há o parâmetro complementar onde você deve informar qual será a funcionalidade que deseja remover:

all - Todas
m - Membership
r - Role management
p - Profile
c - Web Parts personalization
w - Web events

Abaixo é mostrado (através do prompt de comando do Visual Studio .NET 2005) alguns exemplos do uso do utilitário aspnet_regsql.exe:

C:\aspnet_regsql.exe -S localhost -U NomeUsuario -P P@$$w0rd -d BancoDados -A mrpw
C:\aspnet_regsql.exe -S localhost -E -d BancoDados -A all
C:\aspnet_regsql.exe -S localhost -E -d BancoDados -R all

Na primeira linha adicionamos as funcionalidades de Membership, Roles, Profile e WebEvents no banco de dados chamado "BancoDados". Já na segunda opção adicionamos todas as funcionalidades, só que agora utilizando as credenciais do Windows. E, por último, estamos removendo toda a infraestrutura da base de dados. Quando criamos uma destas funcionalidades dentro da base de dados ele inclui uma porção de Tabelas, Stored Procedures, Triggers para que a mesma seja atendida. Se analisarmos o design da base de dados depois disso, veremos a seguinte estrutura:

Figura 2 - Design da base de dados para suportar as funcionalidades de Membership e Roles.

O Arquivo ASPNETDB.MDF

Quando utilizamos Membership ou qualquer uma destas funcionalidades, se não especificarmos um provider e você não tiver um banco de dados pré-definido para o uso das mesmas, o ASP.NET cria automaticamente dentro do diretório App_Data dentro da aplicação um banco de dados chamado ASPNETDB.MDF, que faz uso do SQL Server 2005 Express Edition. Essa criação se dá quando iniciamos o WSAT - Web Site Administration Tool, onde depois de iniciarmos o provider, o arquivo MDF é criado dentro da pasta App_Data.

Com isso não seria necessário você criar a infraestrutura em uma base de dados assim como mostramos acima, pois o arquivo ASPNETDB.MDF já terá todo o schema necessário para as funcionalidades. Se o volume de usuários não é tão alto e o sistema pouco complexo, ter o ASPNETDB.MDF já resolve a necessidade. Mas quando o nível de usuários aumenta e de alguma forma você precisa fazer o relacionamento da tabela de Usuários com outras tabelas do sistema, é mais interessante ter isso unificado, ou seja, dentro de uma única base de dados.

Outputcache - CacheProfile

Estou trabalhando em um projeto ASP.NET 2.0 e, como algumas páginas estavam requerendo o uso de Outputcache, para cada uma dessas páginas estava ajustando o mesmo valor de segundos no atributo Duration e também especificando algumas outras propriedades desta mesma diretiva, customizando assim, o cache da forma que necessitava.

Mas em um certo momento me perguntei: e se esses valores e condições mudarem? Sim, terei que ir a cada página e especificar tudo novamente. Foi nesse momento que recorri a documentação do .NET Framework e encontrei o elemento outputCacheProfiles que é especificado dentro do arquivo Web.Config.

Com isso, ao invés de especificar os mesmos valores em cada página ASPX, eu criei uma "profile" dentro do Web.Config e passo a utilizar este "profile" nas páginas ASPX. Um exemplo do uso é mostrado abaixo:

[ Web.Config ]
<outputCacheSettings>
  <outputCacheProfiles>
    <add name = "PaginasEmCache"
      varyByParam = "TituloID"
      enabled = "true"
      duration = "180" />
  </outputCacheProfiles>
</outputCacheSettings>

[ *.ASPX ]
<%@ OutputCache CacheProfile="PaginasEmCache" %>

Recuperando a linha do GridView

Muitas pessoas me perguntam como acessar o conteúdo de uma linha do GridView dentro do evento RowCommand (sim, elas NÃO ESTÃO fazendo o uso do DataKeys). Pois bem, nas versões 1.x do ASP.NET, o evento ItemCommand do DataGrid tinha um argumento do tipo DataGridCommandEventArgs e, dentro deste, uma propriedade chamada Item que retorna a instancia da linha corrente.

Isso não existe mais no GridView e, se ainda desejar acessar os dados da linha clicada no evento RowCommand do GridView, terá que proceder da seguinte forma: no evento RowCreated do GridView, terá que definir à propriedade CommandArgument do controle LinkButton (Button, ImageButton, etc) responsável por disparar o comando, a propriedade RowIndex que vem como parametro para este evento. Esta propriedade retorna um valor inteiro contendo o índice da linha e, como o evento RowCommand te fornece uma propriedade chamada CommandArgument, conseguirá recuperar o índice da linha e, consequentemente, acessar os valores da mesma.

Para ilustar o processo, o código (em C#) é mostrado abaixo:

private void GridView1_RowCommand(Object sender, GridViewCommandEventArgs e)
{
   if(e.CommandName=="Add")
   {
      int index = Convert.ToInt32(e.CommandArgument);
      GridViewRow row = GridView1.Rows[index];
      Response.Write(row.Cells[2].Text);
   }
}

private void GridView1_RowCreated(Object sender, GridViewRowEventArgs e)
{
   if(e.Row.RowType == DataControlRowType.DataRow)
   {
      LinkButton addButton = (LinkButton)e.Row.Cells[0].Controls[0];
      addButton.CommandArgument = e.Row.RowIndex.ToString();
   }
}

ListItem

Um dos grandes problemas (bug?) que temos nas versões 1.x do ASP.NET é que os atributos do objeto ListItem (usado para preencher controles como DropDownLists, ListBoxes, etc.) não são renderizados, logo, se quisésemos definir uma cor de background em um item do DropDownList, não era possível, a menos que fizessemos algo como o Scott Mitchell mostra neste artigo.

A boa notícia é que isso foi resolvido na versão 2.0 do ASP.NET e agora podemos fazer o que já cansamos de tentar nas versões anteriores:

foreach (ListItem item in this.DropDownList1.Items)
{
    item.Attributes.Add("style", "background-color: Red");
}

Atributo debug

O ScottG fala neste post sobre o atributo debug do elemento compilation do arquivo Web.Config e me fez lembrar o meu primeiro projeto em ASP.NET. A performance estava muito baixa e depois de uma checagem antes de mandar ao servidor de produção, alterei o atributo, definindo-o para false. A performance aumenta bastante, já que muitas coisas que devem ser usadas somente em tempo de desenvolvimento, deixam de ser utilizadas. Só vale lembrar que isso não é uma característica exclusiva do ASP.NET 2.0.

Uma das outras técnicas interessantes, esta somente para ASP.NET 2.0, é definir no arquivo machine.config do servidor de aplicações, o atributo retail do elemento deployment para true, para evitar que, pessoas despercebidas façam o deployment de aplicações sem mudar/definir o atributo debug para false do arquivo Web.Config da respectiva aplicação.

Acessar dados do Arquivo do Upload

Muitas vezes, quando precisamos fazer um upload e, antes que o mesmo seja efetivamente salvo no disco, devemos aplicar algumas validações onde, serão estas validações que irão dizer se o arquivo é ou não um arquivo válido para a nossa necessidade e, consequentemente, salvar no disco.

O que muito gente não sabe (devido ao número de e-mails que recebo com esse, até então, problema) é que há a possibilidade de analisar o arquivo antes de salvá-lo. É possível graças a propriedade chamada InputStream da classe HttpPostedFile (acessível através do controle FileUpload). Com isso, podemos tranquilamente acessar o arquivo, ler e, se for um arquivo válido, o persistimos fisicamente. Para exemplificar, o código abaixo ilustra isso:

if (Page.IsValid && this.FileUpload1.HasFile){
     StreamReader sr =
          new StreamReader(this.FileUpload1.PostedFile.InputStream);
     //....
}

Obviamente que poupei o error-handling por questões de espaço. Como há um construtor na classe StreamReader que permite passarmos um Stream, podemos passar o Stream que vem quando fazemos o upload de um arquivo. Depois, se necessário, podemos utilizar o método SaveAs para salvar o arquivo fisicamente no disco, mas a idéia com este post, é mostrar que não é necessário salvarmos o arquivo fisicamente para análisá-lo.

Health Monitoring

Nas versões anteriores do ASP.NET tínhamos algumas funcionalidades/serviços que nos permitiam monitorar a vida de uma aplicação ASP.NET. Podemos considerar como estes serviços os Traces e os contadores de performance. O ASP.NET 2.0 introduz um novo conceito, chamado de Health Monitoring, que trata-se de uma infraestrutura completa para monitoramento da aplicação ASP.NET, permitindo-nos analisar posteriormente o comportamento de tal aplicação, analisando a performance, diagnosticar falhas da aplicação e também eventos significativos durante a vida da aplicação em questão.

Neste novo cenário, devemos nos atentar para dois principais conceitos que temos que ter em mente: events e providers. Um event é responsável por armazenar informações de um determinado acontecimento dentro da aplicação. Já o provider é o responsável por tratar e persistir (quando necessário) o evento. Este provider pode, por exemplo, fazer entrada dentro do Event Log do Windows, salvar em uma base de dados qualquer, mandar o mesmo por e-mail, etc. A amarração entre o evento e o provider é feita através de regras que definimos no arquivo de configuração - Web.Config - da aplicação.

O .NET Framework 2.0 já nos fornece uma porção de providers capazes de tratar estes eventos, podendo inclusive, criar os nossos próprios events e providers (veremos isso mais tarde, ainda neste artigo). Essa infraestrutura pode ser exibida através da imagem abaixo:

Figura 1 - Estrutura dos events e providers em funcionamento.

Partindo para o lado técnico e de design das classes, os eventos nada mais são do que classes que herdam a maioria das funcionalidades de uma classe base chamada de WebBaseEvent, sendo esta necessária para criarmos os nossos eventos customizados. Todos estes eventos devem, depois de criados, ser registrados no arquivo de configuração para que a aplicação e o runtime do ASP.NET possa fazer o uso dos mesmos. O registro pode ser a nível de Web.Config, ficando estes eventos limitados a aplicação ou, se desejar compartilhar os eventos para que todas as aplicações o utilizem, pode optar por registrá-los no arquivo Machine.config. Quando você registra este evento em um arquivo de configuração (seja ele qual for), é necessário que você defina um nome que representará a categoria associada a este evento. Para ter uma idéia dos eventos já existentes dentro do .NET Framework, consulte a tabela abaixo:

Evento Descrição
All Events Captura qualquer evento criado.
HeartBeats Permite efetuar a notificação de eventos.
Application Lifetime Events Fornece informações sobre o início, término e recompilação da aplicação.
All Errors Captura todos os erros gerados pela aplicação.
Infraestructure Errors Captura os erros relacionados com o funcionamento do IIS e do ASP.NET.
Request Processing Errors Excessões ocorridas durante o pedido de um determinado recurso.
All Audits Utilizado para efetuar auditorias.
Failure Audits Efetua a auditoria de tentativas falhas de Login.
Success Audits Ao contrário do anterior, efetua a auditoria de tentativas com sucesso de Login.

Para comprovar que estes eventos já estão pré-definidos, podemos analisar o arquivo Web.Config (que corresponde as configurações padrões de todas as aplicações Web), sendo exibidos através da imagem abaixo:

Figura 2 - Eventos já definidos dentro do .NET Framework 2.0.

Assim como os eventos, já existem também providers pré-definidos dentro do .NET Framework. São eles:

Provider Descrição
SqlWebEventProvider Utilizando este provider, o evento é armazenado dentro de uma base de dados SQL Server. É importante dizer que é necessário executar o utilitário aspnet_regsql.exe dentro do banco de dados para que seja criada a infraestrutura de tabelas necessárias. Este provider suporta buffer1.
SimpleMailWebEventProvider Permite configurar o envio de e-mails com as informações contidas dentro de um evento. Este provider suporta buffer1.
TemplatedMailWebEventProvider Semelhante ao anterior, mas neste caso podemos definir uma mensagem customizada a ser enviada.
EventLogProvider Efetua as entradas dentro da categoria Application do Event Log do Windows.
WmiWebEventProvider Através deste provider é possível vincular os eventos aos eventos WMI e, conseqüentemente, podem ser consumidos por listeners WMI.

1 - Quando falamos que um provider suporta buffering, isso quer dizer que ele espera este buffer completar para "descarregar" todo o conteúdo em memória em seu local de destino. Isso pode nos gerar um maior ganho de performance, pois evita de a cada evento ocorrido dentro da aplicação ter que perder tempo armazenando isso. Estes buffers são definidos dentro do elemento bufferModes.

Apesar de existirem todos estes providers dentro do .NET Framework, no arquivo de configuração só temos adicionados o SqlWebEventProvider, EventLogProvider e WmiWebEventProvider. Se desejarmos utilizar um dos outros providers devemos registrar o mesmo dentro do elemento providers do arquivo Web.Config local.

Dando seqüencia, vamos analisar as definições de regras, que é onde "amarramos" o provider com os events. É com estas regras que definimos por qual provider o nosso evento vai ser tratado. Isso permite a independência de providers, ou seja, podemos ter qualquer evento sendo tratado por qualquer provider. Felizmente, a infraestrutura não me obriga a tratar todos os eventos com apenas um único provider. Para exemplificar, vamos analisar como devemos fazer para habilitar o health monitoring e utilizar um evento e provider:

<system.web>
  <healthMonitoring enabled="true">
    <rules>
      <clear />
      <add 
        name="All Errors Default" 
        eventName="All Errors" 
        provider="EventLogProvider" 
        profile="Default" 
        minInterval="00:01:00"/>
    </rules>
  </healthMonitoring>
</system.web>

Como podemos analisar no código acima, apenas com algumas linhas de configurações dentro do arquivo Web.Config, o health monitoring já está habilitado. Antes de ver isso em funcionamento, vamos analisar os atributos: name identifica a regra; eventName refere-se ao evento que deverá ser tratado; provider é o nome do provider que iremos utilizar para persistir o evento; profile permite-nos associar um perfil a esta regra; e finalmente o atributo minInterval, qual tem um funcionamento interessante, ou seja, se uma excessão acontecer mais de uma vez dentro do período estipulado nele, apenas será logado/persistido uma vez o evento no EventLog (neste caso). Para poder testar, apenas vou criar um código .NET que gere uma excessão, como por exemplo uma divisão por 0 (zero):

Figura 3 - Health Monitoring em funcionamento.

Criação de um Event e Provider customizado

Mesmo com todas as classes que o .NET Framework 2.0 traz para trabalhar com Health Monitoring, ainda temos a flexibilidade de criar os nossos próprios events e providers, se assim desejarmos. Para isso, basta trabalharmos com as classes base (tanto para event quanto para provider) que já trazem as funcionalidades principais para operar os dados e apenas customizamos o que e para onde direcionaremos estes eventos.

Como já vimos no começo do artigo, utilizamos a classe base WebBaseEvent para criarmos o nosso próprio evento. O cenário aqui será criar um event para utilizarmos no decorrer da aplicação e um provider para persistirmos os eventos em arquivos de texto (txt). O código abaixo mostra como devemos proceder para criarmos o nosso próprio event:

using System.Web.Management;

public class CodeEvent : WebBaseEvent
{
    private static readonly int _eventCode = WebEventCodes.WebExtendedBase + 1;

    public CodeEvent(string msg, object eventSource, int eventCode)
        : base(msg, eventSource, CodeEvent._eventCode) {}

    public CodeEvent(string msg, object eventSource, int eventCode, int eventDetails)
        : base(msg, eventSource, CodeEvent._eventCode, eventDetails) {}
}

A única coisa que devemos ter atenção é no código interno do event. Ele está definido como _eventCode e é responsável por gerar um código para distinguir os eventos desta classe. Depois do evento criado, antes de ver como fica a configuração no arquivo Web.Config, analisaremos como proceder para a criação do provider para o arquivo texto:

using System.IO;
using System.Web.Management;

public class TextEventProvider : WebEventProvider
{
    private string _providerName = "TextEventProvider";
    private string _path = string.Empty;

    public override void Initialize(string name, 
        System.Collections.Specialized.NameValueCollection config)
    {
        if (string.IsNullOrEmpty(config["path"]))
            throw new ArgumentException("Caminho inválido/inexistente.");

        this._path = config["path"];
        base.Initialize(name, config);
    }

    public override void ProcessEvent(WebBaseEvent raisedEvent)
    {
        if (raisedEvent is CodeEvent)
        {
            StreamWriter sw = File.AppendText(this._path);
            sw.WriteLine(
                string.Format("{0}\t{1}\t{2}\t{3}\t",
                    raisedEvent.EventCode,
                    raisedEvent.EventID,
                    raisedEvent.EventTime,
                    raisedEvent.Message));
            sw.Close();
        }
    }

    public override void Shutdown(){}
    public override void Flush(){}
}

Através do método Initialize recuperamos informações do arquivo de configuração que, neste nosso caso, devemos recuperar o caminho do arquivo através do atributo path. Temos também o método ProcessEvent que temos acesso ao evento gerado e assim podemos tratá-lo como quisermos. Como vamos armazenar isso em um arquivo TXT, utilizaremos o conjunto de caracteres "\t" para incluirmos o TAB entre os valores dos campos do evento. O método AppendText da classe File fará com que o conteúdo sempre será adicionado no TXT, sem perder o que já temos lá gravado.Com estas classes prontas, agora basta configurarmos o event e o provider dentro do Web.Config criando uma nova regra e "amarrando" o evento ao provider:

<system.web>
  <healthMonitoring enabled="true">
    <providers>
      <add 
          name="TextEventProvider" 
          type="TextEventProvider" 
          path="C:\EventLog.txt"/>
    </providers>
    <eventMappings>
      <add 
          name="Code Event" 
          type="CodeEvent"/>
    </eventMappings>
    <rules>
      <add 
          name="Text Event Rule" 
          eventName="Code Event" 
          provider="TextEventProvider" 
          profile="Default" 
          minInterval="00:00:00"/>
    </rules>
  </healthMonitoring>
</system.web>

Aqui vale chamar a atenção para o atributo type. Como eu estou criando as classes internamente, ou seja, dentro do diretório App_Code do meu projeto ASP.NET, eu não precisei definir o nome do Assembly, informação que seria necessária se estivesse com esse código dentro de uma DLL a parte.

Da mesma forma que fizemos anteriormente, vamos criar uma situação e forçar a divisão por zero para que uma excessão seja atirada e, com isso, salvamos o evento que criamos com o uso do nosso provider, definido na regra Text Event Rule. O notificação é gerada quando invocamos o método Raise (que é herdado da classe base). O código abaixo exibe isso:

try
{
    int i = 0;
    int resultado = 10 / i;
}
catch (DivideByZeroException ex)
{
    CodeEvent codeEvent = new CodeEvent("Divisão por zero.", sender, 0);
    codeEvent.Raise();
}

 

Figura 4 - O arquivo TXT gerado com o Log.

CONCLUSÃO: Como vimos no decorrer deste artigo, o Health Monitoring traz uma grande quantidade de features e classes que nos permitem trabalhar com o monitoramento da vida da aplicação sem precisarmos recorrer a componentes e/ou ferramentas de terceiros para isso. Finalmente, ratifico a simplicidade que temos quando precisamos criar algo mais customizado, onde os events e os providers que estão intrínsicos e fornecidos pelo .NET Framework não nos atende.