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 3Health 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.

Formatando Colunas do GridView

Muitas vezes queremos formatar os valores vindos da fonte de dados antes de exibí-los nas colunas do controle GridView. Como já sabemos, basta aplicarmos o padrão que já conhecemos na propriedade DataFormatString, como por exemplo: {0:dd/MM/yyyy} que isso é aplicado ao valor da coluna e, consequentemente, exibida da forma como esperamos.

Neste momento, apresento uma nova propriedade (também da classe BoundField) chamada de HtmlEncode que, quando definida como True, o valor da coluna é codificado antes de ser exibido, para evitar assim, problemas com o XSS (Cross Site Scripting) e algum possível código malicioso.

O problema é que quando o valor da propriedade HtmlEncode está definido como True (é o default), a formatação não é aplicada, assim como podemos ver diretamente na documentação:

“When the HtmlEncode property is true, the value of the field is HTML encoded to its string representation before the formatting string is applied. For some objects, such as dates, you might want to control how the object is displayed with a formatting string. In those cases, you must set the HtmlEncode property to false.”

Com isso, quando os tipos de dados são datas ou valores monetários e precisa aplicar uma formtação na propriedade DataFormatString, voce deve definir a propriedade HtmlEncode para False para que consiga chegar ao resultado desejado.

Limitando o conteúdo de uma coluna no DataGrid

A solução apresentada neste artigo é criar uma coluna customizada para que não se precise alterar a query SQL e também evitar de tratar isso em todo o evento ItemDataBound. A idéia é criar uma coluna que herde diretamente da classe BoundColumn que tem as funcionalidades básicas de uma coluna do DataGrid.

Nesta classe customizada que será criada, uma propriedade chamada QuantidadeCaracteres é criada para receber um número inteiro que será a quantidade de caracteres que a coluna deverá ter. Pelo fato de herdarmos da classe BoundColumn temos acesso e devemos sobrescrever o método FormatDataValue que, é este que recupera o dado da fonte de dados e devolve para o runtime do ASP.NET exibir em seu devido lugar dentro do DataGrid. E para finalizar, a criação de um método adicional chamada Truncar fará o trabalho para “cortar” a string que representa o valor da fonte de dados. Dado esta explicação, veremos abaixo como fica a implementação desta nova classe:

using System;
using System.Web.UI.WebControls;

namespace IAWebControls
{
    public class LimitedColumn : BoundColumn
    {
        private int _quantidadeCaracteres;

        public int QuantidadeCaracteres
        {
            get
            {
                return this._quantidadeCaracteres;
            }
            set
            {
                this._quantidadeCaracteres = value;
            }
        }

        protected override string FormatDataValue(object dataValue)
        {
            return this.Trunca(dataValue.ToString());
        }

        private string Trunca(string originalValue)
        {
            if(originalValue.Length <= this._quantidadeCaracteres)
                return originalValue;
            else
                return string.Format("{0}...", originalValue.Substring(0, this._quantidadeCaracteres));
        }
    }
}

Depois da classe feita, resta utilizá-la no HTML, dentro do DataGrid que iremos colocar na página. Mas vale lembrar que é necessário registrar a classe para que ela funcione corretamente, através da diretiva Register. Finalmente criamos uma coluna dentro do DataGrid que será está coluna que apresentará os dados limitados ao número de caracteres que iremos definir através da propriedade QuantidadeCaracteres. O código abaixo ilustra como isso é configurado.

<%@ Register TagPrefix="IAControls" Namespace="IAWebControls" Assembly="IAWebControls" %>

...

<asp:DataGrid 
    id="DataGrid1" 
    runat="server" 
    AutoGenerateColumns="False">
    <Columns>
        <asp:BoundColumn 
            DataField="CategoryID" 
            HeaderText="CategoryID" />
        <IAControls:LimitedColumn 
            DataField="Description" 
            QuantidadeCaracteres="10" />
    </Columns>
</asp:DataGrid>

 

Microsoft Anti-Cross Site Scripting Library V1.0

Há poucos dias atrás, a Microsoft lançou o “Anti-Cross Site Scripting Library V1.0”. Trata-se de uma biblioteca que substitui os métodos HttpUtility.HtmlEncode e HttpUtility.UrlEncode e, com a mesma finalidade, de evitar ataques XSS nas aplicações Web/ASP.NET.

A diferença é que com esta nova biblioteca, os métodos trabalham com os caracteres permitidos “white-listing” ao invés dos caracteres não permitidos “black-listing”, como é o caso dos métodos da classe HttpUtility. Isso fará com que, ao chamar os métodos, o que não estiver contido dentro deste “conjunto” de caracteres válidos, será codificado. Para maiores informações a respeito, façam o download e analisem o documento que vem junto com o pacote.