Problemas com Sessão no ASP.NET 4.0

10/06/2011

Há diversas causas possíveis para perda de sessão no ASP.Net/IIS e recentemente encontrei mais uma. No processo de migração de uma aplicação do ASP.Net 2.0 para o ASP.Net 4.0 precisamos montar um Web.config compatível com o IIS6 e IIS7 simultaneamente. Uma das alterações nessa configuração foi definir a página padrão da aplicação como mostrado abaixo:

<system.webServer>
    <validation validateIntegratedModeConfiguration="false" />
        <defaultDocument enabled="true">
            <files>
                <clear/>
                <add value="index.aspx" />
            </files>
        </defaultDocument>
</system.webServer>

Durante o tempo em que o elemento defaultDocument não existiu, a aplicação rodou normalmente tanto em Debug quanto em Release. Após a colocação do elemento defaultDocument a aplicação começou a apresentar problemas de perda de sessão a partir de uma página específica. De início não atribuí o problema ao novo elemento de configuração mas como não foi possível depurar o erro, retirei o elemento da configuração e a aplicação voltou a funcionar. Assim, procurei mais informações sobre o elemento defaultDocument e lendo o artigo Default Document <defaultDocument>, compreendi melhor o seu comportamento. O que de fato ocorreu é que essa página continha um controle Image com a Url vazia, pois estava aguardando a imagem final que seria colocada ali. Durante a requisição, como não havia uma Url para a imagem, a página usada como defaultDocument era executada em seu lugar, resetando os valores da variável de sessão original. O real problema portanto não era a perda da sessão mas o fato de que a página padrão estava sendo executada indevidamente. Após colocarmos uma Url para a imagem, o problema cessou.

Navegadores

Há uma questão interessante nesse problema e para o qual ainda não consegui uma resposta. Mesmo quando a imagem não possuía uma Url, a aplicação gerava erros no IE6, IE7 e IE8, mas funcionava normalmente no IE9 e FireFox. Com a colocação final da imagem a aplicação passou a funcionar em todos os navegadores. Assim que conseguir alguma informação sobre esse comportamento diferenciado entre os navegadores, atualizarei esse artigo. Até lá, muita atenção com Urls vazias…


A validação de requisições no ASP.NET e o Cross-Site Scripting (XSS)

06/06/2011

Ataques de HTML Injection e Cross-Site Scripting (XSS) são frequentes em ambiente Web. Muitas aplicações porém, não estão preparadas para evitar nem ao menos os tipos de ataques mais comuns e/ou mais simples. O ASP.Net possui desde a versão 1.1 um recurso próprio que ajuda a mitigar esse tipo de ataque, chamado Page Request Validation. Esse mecanismo é formado por filtros que analisam a requisição procurando por sequências suspeitas de Injection, e bloqueiam a sua ação quando encontradas. Por exemplo, suponha que em uma entrada de dados seja lançado algo como <script>alert(‘código suspeito’)</script> e essa informação seja gravada em sua base. Se não houver nenhum tratamento dessa informação ou o mecanismo de validação do ASP.Net estiver desabilitado, ao executar novamente essa tela o navegador irá executar o script:

Exemplo de Script Injection
Porém, se o mecanismo de validação estiver ativo, um erro será disparado na tentativa de envio do código:

Erro de validação

A validação do ASP.Net está habilitada por padrão para toda a aplicação, mas pode ser desabilitada totalmente e controlada página por página. Para habilitar ou desabilitar a validação para toda a aplicação, altere o Web.config:

<configuration>
  <system.web>
    <pages validateRequest="true|false" />
  </system.web>
</configuration>

Para controlar a configuração por página, altere a diretiva @Page:

<%@ Page validateRequest="true|false" %>

Em qualquer caso, se a validação automática for desabilitada, informações suspeitas serão enviadas ao seu aplicativo facilitando a execução de código malicioso através da sua aplicação. Nesses casos se torna ainda mais urgente que o desenvolvedor trate diretamente as informações recebidas e manipuladas pela aplicação.

Alterações para o .Net Framework 4.0

O que vimos acima é válido para aplicações voltadas para o .NET Framework 2.0. Mas essas aplicações ASP.Net 2.0 podem encontrar problemas com a validação se forem migradas para a versão 4.0 porque até o ASP.Net 2.0 a validação era executada apenas para os arquivos .aspx e suas classes e agora, como explicado em ASP.NET 4 Breaking Changes, a partir do ASP.Net 4.0 a validação é executada para qualquer tipo de requisição HTTP. O problema é que esse novo modelo impede o controle de validação por página, ou seja, o elemento validateRequest é ignorado e agora podem ocorrer erros em pontos da aplicação migrada onde antes nenhuma validação ocorria. A solução para ambos os pontos é retornar o mecanismo de validação à sua forma de atuação do ASP.Net 2.0 através de uma configuração no Web.config:

<httpRuntime requestValidationMode="2.0" />

Integração com o Internet Explorer

Enquanto uma forma simples e rápida de agregar segurança à sua aplicação, a validação intrínseca do ASP.Net não é infalível. Ataques de XSS possuem formas diferentes e podem explorar bugs dos próprios navegadores. O Internet Explorer a partir da versão 8 possui um filtro anti-XSS próprio, habilitado por padrão que traz um nível de segurança adicional às aplicações. O filtro XSS do IE8 foi muito criticado em seu lançamento por suas falhas, inclusive de segurança, como descrito em Major IE8 flaw makes ‘safe’ sites unsafe mas em testes que fiz para uma aplicação ele se mostrou eficaz, neutralizando ataques que em versões anteriores do navegador tiveram sucesso. Usuários finais podem configurar o filtro do IE através de suas configurações de segurança:

Filtro Anti XSS

A nível de aplicações ASP.Net, podemos interagir com o filtro XSS do IE através de HTTP Headers. Por exemplo, se houver necessidade de ligarmos ou desligarmos o filtro XSS para uma aplicação, podemos fazer isto através do Web.config usando 0 para desligar e 1 para ligar:

<system.webServer>
    <httpProtocol>
      <customHeaders>
        <clear />
        <add name="X-XSS-Protection" value="0" />
      </customHeaders>
    </httpProtocol>
</system.webServer>

Entendendo que não se deve confiar inteiramente nos dados digitados pelos usuários, lançar mão de mecanismos de proteção nativos é útil mas não é a solução de todos os problemas. Há vários tipos de ataque por Injection conhecidos e outros que ainda deverão surgir. O ideal é usar esses mecanismos como elementos de auxílio na proteção da sua aplicação e trabalhar em formas de sanitizar os dados manipulados pela aplicação. Técnicas frequentes envolvem Encoding e Decoding das entradas e o uso de expressões regulares, mas todas devem ser pensadas dentro do contexto dos dados que o aplicativo manipula.


Usando o SqlPersonalizationProvider para Acesso à Web Parts

30/04/2011

No meu artigo O Utilitário Aspnet_regsql e os Schemas do SQL Server vimos como instalar a base de dados dos Providers ASP.NET em um Schema diferente do dbo. Porém, isso por si só não é suficiente uma vez que os Providers continuarão a procurar pelos objetos no dbo. A solução para resolver a questão do aceso, é personalizar os Providers de forma que procurem pelos objetos da base no Schema correto.

O Web Part Personalization Provider

O ASP.NET utiliza uma série de Providers para se comunicar com a base de dados de suporte e atender a serviços como Membership e Web Parts. Esses Providers possuem uma estrutura padrão de execução baseada no arquivo Web.config para localizar a base de dados de suporte. Embora a utilização do Web.config signifique que alguns elementos usados pelos Providers possam ser parametrizados, isso não é verdade para qualquer informação. Por exemplo, dois elementos importantes que não podem ser parametrizados sem personalização são justamente o nome do Schema associado a base de dados e a possibilidade de usarmos conexões dinâmicas. No exemplo abaixo vemos uma típica seção de configuração para utilização dos Web Parts em uma aplicação. Note que não há um parâmetro que indique o nome do Schema na base de dados:


<webParts>
<personalization defaultProvider=AspNetSqlPersonalizationProvider>
<providers>
<add name=AspNetSqlPersonalizationProvider> type=System.Web.UI.WebControls.WebParts.SqlPersonalizationProviderconnectionStringName=LocalSqlServerapplicationName=//>
</providers
<authorization>
<deny users=*verbs=enterSharedScope/>
<allow users=*verbs=modifyState/>
</authorization
</personalization
</webParts

Agora vejamos a mesma seção de configuração utilizando o Provider personalizado, contendo o nome do Schema em uso e sem uma String de conexão fixa:

<webParts>
<personalization defaultProvider=AppWebPartProvider>
<providers>
<clear />
<add name=SitePersonalizationProvider> type=SiteApp.SitePersonalizationProviderdatabaseSchema=ConfiguracoesapplicationName=SiteApp/>
</providers
<authorization>
<deny users=*verbs=enterSharedScope/>
<allow users=*verbs=modifyState/>
</authorization
</personalization
</webParts

Para demonstrar como chegar a essa personalização, falarei especificamente sobre o SqlPersonalizationProvider, embora os princípios sirvam para os demais Providers. O primeiro passo na personalização dos Providers ASP.NET é baixar o ProviderToolkitSamples aqui, que contém um projeto C# (aparentemente, não há uma versão em VB.NET do Toolkit) com exemplos de personalização desses Providers. Instale o Toolkit e em seguida abra o projeto ProviderToolkitSampleProviders.csproj. No conjunto de arquivos há o fonte de cada Provider e rotinas de apoio. Para trabalharmos as Web Parts especificamente precisaremos apenas dos arquivos:

  • SqlPersonalizationProvider.cs
  • PersonalizationProviderHelper.cs
  • SecUtil.cs
  • SqlConnectionHelper.cs
  • SR.cs

Você pode criar um novo projeto C# e adicionar apenas os arquivos citados, caso queira manter o projeto original intacto. Nossa primeira alteração será no arquivo SqlPersonalizationProvider.cs. Vamos declarar uma nova variável no escopo da classe chamada _databaseSchemaName, que terá o objetivo de armazenar o nome correto do Schema da base de dados:

private const int maxStringLength = 256;

private string _applicationName;
private int _commandTimeout;
private string _connectionString;
private int _SchemaVersionCheck;
private string _databaseSchemaName;

Em seguida, localize no código todas as chamadas ao Schema dbo. Em cada linha encontrada substituiremos dbo pela variável que vai conter o nome correto do Schema, por exemplo:

String strSchemaCommand = _databaseSchemaName + “.aspnet_PersonalizationAdministration_FindState“;
SqlCommand command = new SqlCommand(strSchemaCommand, connection);

O objetivo da nova variável é trazer o nome correto do Schema, que informaremos no arquivo Web.config da aplicação através de um novo parâmetro chamado databaseSchema. Nos Providers, o método Initialize é responsável por ler as configurações do Web.config logo, é nesse método que vamos verificar a presença do atributo contendo o nome do Schema que vamos utilizar. Acrescente a seguinte verificação ao método Initialize:


//Se o schema do banco não for informado, assume dbo como padrão.
//O atributo databaseSchema é personalizado, não existindo no elemento de configuração padrão.

if (String.IsNullOrEmpty(configSettings[“databaseSchema“]))
{
configSettings.Add(“databaseSchema“,“dbo“);
}

Em seguida, acrescente ao final do método Initialize a linha de código que recupera o nome do Schema no arquivo de configuração:


_databaseSchemaName = configSettings[“databaseSchema“];

Por fim, altere o método CheckSchemaVersion passando um parâmetro adicional, que criaremos na sequência:

private void CheckSchemaVersion( SqlConnection connection )
{
string[] features = { “Personalization“ };
string version = “1“;

SecUtility.CheckSchemaVersion( this,
connection,
features,
version,
_databaseSchemaName,
ref _SchemaVersionCheck );
}

Quando todas as chamadas aos objetos do banco estiverem recuperando o Schema correto, teremos agora que alterar o arquivo SecUtil.cs. Nesse arquivo, localize o método CheckSchemaVersion e altere a sua assinatura para conter um novo parâmetro de tipo String chamado databaseSchema. A nova assinatura deve estar como abaixo:


internal static void CheckSchemaVersion(ProviderBase provider, SqlConnection connection, string[] features, string version, string databaseSchema, ref int schemaVersionCheck)

Em seguida na mesma rotina, localize a chamada pela procedure aspnet_CheckSchemaVersion e altere o código para que fique como abaixo:


String strSchemaCommand = databaseSchema + “.aspnet_CheckSchemaVersion“;
cmd = new SqlCommand(strSchemaCommand, connection);

As alterações feitas até o momento permitem determinarmos um Schema diferente do dbo. Para permitir o uso de uma conexão informada dinâmicamente vamos criar uma propriedade no arquivo SqlPersonalizationProvider.cs como abaixo:


//Propriedade para manter a string de conexão
public string ConnectionString
{
get { return _connectionString; }
set { _connectionString = value; }
}

E voltando ao método Initialize vamos verificar se a String de conexão foi informada pelo Web.config ou dinâmicamente:


//Verifica se a conexão foi informada no arquivo de configuração. Se não foi, deve ser informada através da propriedade ConnectionString.
string connectionStringName = configSettings[“connectionStringName“];
if (!String.IsNullOrEmpty(connectionStringName))
{
configSettings.Remove(“connectionStringName“);

string connectionString = SqlConnectionHelper.GetConnectionString(connectionStringName, true, true);
if (String.IsNullOrEmpty(connectionString))
{
throw new ProviderException(SR.GetString(SR.PersonalizationProvider_BadConnection, connectionStringName));
}
_connectionString = connectionString;
}

A Chamada em uma Aplicação Web VB.Net

O que fizemos até o momento foi criar um Provider personalizado. O passo seguinte é chamar esse Provider na aplicação Web e claro, o VB.Net não pode faltar 🙂 Para isso crie um novo projeto Web e adicione uma classe com o nome que você queira dar ao seu Provider de acesso. Nessa classe coloque o seguinte código:


Public Class SitePersonalizationProvider
    Inherits AppWebPartProvider

    Private strApplicationName As String = “SiteApp“

    Public Overrides Sub Initialize(name As String, config As System.Collections.Specialized.NameValueCollection)

    ‘Verifica se a seção de configuração dos Web Parts existe no arquivo de configuração
    If config Is Nothing Then
        Throw New ArgumentNullException(“config“, “A configuração dos Web Parts não está definida no arquivo de configurações“)
    End If

    ‘Se o nome do Provider não estiver configurado, associamos o nome padrão
    If String.IsNullOrEmpty(name) Then
        name = “SitePersonalizationProvider“
    End If

    ‘Atribui a descrição do Provider se não houver um definido no arquivo de configuração
    If String.IsNullOrEmpty(config(“description“)) Then
        config.Remove(“description“)
        config.Add(“description“, “Provider de personalização de Web Parts“)
    End If

    MyBase.Initialize(name, config)

    Dim strCn As String = “A string de conexão entra aqui“
    MyBase.ConnectionString = strCn

    End Sub
End Class

Vimos então um exemplo de personalização em um dos Providers ASP.NET para acessar de maneira diferente a base de dados, mas muito mais pode ser feito dependendo dos requisitos da aplicação. Espero que o artigo seja útil na necessidade de customizações com os Providers do ASP.NET.


O Utilitário Aspnet_regsql e os Schemas do SQL Server

08/04/2011

O utilitário Aspnet_regsql.exe é parte integrante do .NET Framework a partir da versão 2.0 e pode ser utilizado tanto na forma de um assistente como através de linha de comando. Seu objetivo é criar uma base de dados SQL Server, que atende aos Providers do ASP.NET usados para controle de acesso, perfilização, personalização de Web Parts e monitoramento de sistema. Quando não há nenhum detalhe mais específico sobre a criação dessa base de dados, a utilização do utilitário através do assistente gráfico é suficiente para criar a estrutura necessária aos Providers. Mas, quando há detalhes adicionais sobre a criação da base de dados, o assistente peca em permitir customizações. Nesse artigo, veremos um requisito bastante comum quanto à criação de bases de dados que é a criação de um Schema próprio para manter os objetos do banco de dados.

Parâmetros de linha de comando

O utilitário Aspnet_regsql cria por padrão, uma base de dados chamada Aspnetdb associada ao Schema dbo. Porém, não é raro que políticas de segurança restrinjam o acesso aos objetos da base por meio do Schema dbo. Infelizmente, o assistente gráfico não permite que o Schema seja alterado para criação da base, o que pode ser um impedimento para a sua criação. Porém, através da utilização de parâmetros de linha de comando que podem ser consultados no MSDN e algum trabalho manual, é possível contornar essa limitação. Portanto, para podermos alterar o Schema no qual os objetos de suporte devem ser criados, podemos executar o utilitário Aspnet_regsql da seguinte forma:

  1. Clique em Iniciar – Executar
  2. Digite cmd para abrir o prompt de comandos
  3. Digite cd\Windows\Microsoft.NET\Framework\<VersãoFramework>
  4. Digite aspnet_regsql -sqlexportlonly c:\SuporteProviders.sql -d BaseExemplo -a c

Execução do Aspnet_regsql por linha de comando

No exemplo acima, <VersãoFramework> refere-se à versão do .NET Framework que seu aplicativo está usando. O parâmetro -sqlexportonly indica que deve ser criado um script para ser executado manualmente. O parâmetro -d indica que os objetos de suporte aos Providers ASP.NET (Tabelas, Views, Procedures etc.) devem ser criados no banco já existente BaseExemplo, ao invés de criar um novo banco. Já o parâmetro -a indica que serão criados apenas os objetos necessários para o suporte à Web Parts. Mas e o nome do novo Schema? Bem, essa é a parte ruim. Não há parâmetro que permita a troca do nome do Schema, portanto, a troca deve ser feita substituindo-se no script gerado as ocorrências da palavra dbo pelo nome do Schema desejado, exceto nas linhas abaixo:
Localização da base de dados

Execução dos Objetos no Schema
Após as substituições o script pode ser executado e os objetos de suporte aos Providers ASP.NET serão criados no Schema indicado. Mas isso ainda não basta pois os Providers de acesso do ASP.NET vão continuar tentando localizar seus respectivos objetos de suporte no Schema dbo. Para complementar esse assunto, leia meu artigo sobre a personalização de um dos Providers ASP.NET para procurar os objetos de suporte no Schema correto. Até lá!


Embutindo Arquivos JavaScript em Assemblies .NET

09/02/2011

Uma característica pouco utilizada no desenvolvimento ASP.NET é a colocação de arquivos como recursos da aplicação. Nesse procedimento, bibliotecas JavaScript, arquivos de imagens, css e outros podem ser adicionados diretamente ao Assembly da aplicação, não sendo necessária a sua distribuição em separado e ocultando esses recursos do mundo externo, o que pode ser particularmente útil no caso de bibliotecas JavaScript. Se você criou por exemplo um controle ASP.NET que precisa de código cliente e que pode ser usado em vários sites, deixar esse código em um arquivo externo pode tornar a distribuição do controle mais problemática e deixar o código aberto a mudanças indevidas. Ao embutir o arquivo JavaScript no seu Assembly, você garante uma distribuição mais simples e impede a alteração indevida do seu fonte.

Há diferentes maneiras de se adicionar e utilizar uma biblioteca JavaScript como recurso de uma aplicação, desde emitir o código JavaScript a partir do código .NET até embutir o arquivo inteiro no Assembly. Nesse post veremos como embutir o arquivo no Assembly e utilizá-lo com a ajuda do ScriptManager.

A Criação do Recurso

Como nosso objetivo é armazenar um arquivo de scripts, vamos adicionar uma biblioteca ao projeto seguindo as etapas abaixo:

1. Adicione um arquivo de tipo JScript File ao projeto e crie suas rotinas JavaScript. Por questão de organização é interessante manter uma pasta para as bibliotecas JavaScript. No exemplo essa pasta chama-se Scripts e o nome do arquivo é Client.js:

Janela Add Item

2. Selecione o arquivo .js e altere sua propriedade Build Action para Embedded Resource:

Propriedades de arquivo

3. Abra a pasta My Project e em seguida clique o botão direito do mouse sobre o arquivo AssemblyInfo.vb e clique em Open. Se o arquivo não estiver visível clique no botão Show All Files no topo do Solution Explorer:

Configuração do arquivo AssemblyInfo.vb

No arquivo AssemblyInfo.vb colocaremos um atributo WebResource indicando o tipo de recurso que ficará disponível para a aplicação. Esse atributo é parte do Namespace System.Web.UI e deve conter o nome do arquivo de recurso e seu tipo. O nome é motivo comum de falha na chamada dos scripts e deve estar no formato [Namespace].[Arquivo].[Extensão] para evitar essas falhas. Seguindo esse padrão, vemos que o recurso é adicionado como DemoScript.Client.js e o contentType como text/javascript:

Configuração do WebResource

A Chamada do Script

Executando os passos acima teremos a biblioteca JavaScript embutida no Assembly mas como nas páginas precisaremos chamar as rotinas em pontos distintos, usaremos o controle ScriptManager. Vamos portanto entrar em uma página simples, que contenha apenas um botão e vamos adicionar à essa página um controle ScriptManager a partir da Toolbox. Tendo adicionado o ScriptManager vamos selecionar a sua propriedade Scripts, como mostrado abaixo:

Janela de coleção de scripts
A janela de Scripts cria um elemento ScriptReference que permite indicar quais scripts devem ser gerenciados pelo ScriptManager. No caso de arquivos como recursos devemos indicar na propriedade Assembly o nome do projeto e na propriedade Name o nome do recurso como especificado no arquivo AssemblyInfo.vb.

A partir daí é possível chamar as rotinas JavaScript normalmente no código HTML. Se quisermos por exemplo chamar uma rotina a partir do click do botão basta definir o valor da propriedade OnClientClick como a chamada de umas das funções JavaScript da sua biblioteca:

<asp:Button ID=”Button1″ runat=”server” OnClientClick=”exibeAlerta(‘Processamento iniciado…’)″ />

O ScriptManager simplifica a chamada dinâmica de código cliente e o uso de arquivos de script embutidos pela aplicação, permitindo rapidamente a inclusão desses arquivos em seus projetos.


Combinando o Controle ASP.NET ImageMap e o Namespace System.Drawing

16/11/2010

Estamos todos acostumados a lidar com imagens, figuras e demais elementos gráficos em ambiente Web. Na maioria das vezes, recorremos à criação de imagens em outros aplicativos ou reutilização de imagens já existentes. Porém, o .NET Framework possui o namespace System.Drawing, que possui excelentes recursos para manipulação de imagens dinamicamente. Nesse artigo pretendo mostrar um exemplo prático de uso desse namespace, para criar uma linha de tempo dinâmica.

O Controle ImageMap

O Visual Studio fornece nativamente o controle ImageMap com o ASP.NET 2.0, que permite utilizar regiões de uma imagem (HotSpots) para navegação ou algum outro procedimento de código baseado nessas regiões. Para um exemplo específico de uso do controle ImageMap consulte a documentação da Microsoft em ASP.NET Quickstart Tutorials. Nesse artigo, o controle ImageMap será utilizado para definir as regiões da linha de tempo em que devem ser exibidos ToolTips, como na imagem abaixo:


Linha de tempo com System.Drawing
Cada HotSpot é criado dinamicamente e ao mover o mouse sobre cada ponto da linha de tempo, um ToolTip diferente é exibido no HotSpot correspondente.

Gerando a Linha de Tempo

Para chegarmos ao efeito acima, o primeiro passo é adicionar à sua página um controle ImageMap. Arrastando-o da Toolbox para a página teremos a seguinte entrada:

<asp:ImageMapID=”ImageMap1″ runat=”server” />

A partir daí, precisaremos de dados para serem usados nas regiões do controle ImageMap, que podem vir de diferentes fontes. Para o exemplo, usaremos classe para manter os dados a serem apresentados no controle ImageMap, e uma lista desses dados:

Public Class TempoPontos

    Private intAno As Integer
    Private strDescricao As String = String.Empty

    Public Sub New(ByVal ano As Integer, ByVal descricao As String)
        intAno = ano
        strDescricao = descricao
    End Sub

    Public Property Ano() As Integer
        Get
            Return intAno
        End Get
        Set(ByVal value As Integer)
            intAno = value
        End Set
    End Property

    Public Property Descricao() As String
        Get
            Return strDescricao
        End Get
        Set(ByVal value As String)
            strDescricao = value
        End Set
    End Property

End Class

A idéia é que cada instância da classe acima forneça os pontos de dados para a geração da linha de tempo. Esses dados serão combinados com a imagem criada dinâmicamente e a imagem será então, associada ao controle ImageMap. Para fins de exemplo, a lógica de criação da lista de pontos e a imagem é colocada em um botão na página:

Imports System.Drawing
Imports System.Drawing.Drawing2D
Imports System.Drawing.Imaging

Protected Sub Button1_Click(ByVal sender As Object, ByVal e As EventArgs) Handles Button1.Click
    Dim fnfFamilia As FontFamily = Nothing
    Dim fnTexto As Font = Nothing
    Dim ptInicio As Point = Nothing
    Dim szInicio As Size = Nothing
    Dim lstPontos As List(Of TempoPontos) = Nothing
    Dim pCor As Pen = Nothing
    Dim bmpPonto As Bitmap = Nothing
    Dim grLinha As Graphics = Nothing

    Try

        ‘Define os valores iniciais dos elementos usados na criação da imagem
        fnfFamilia = New FontFamily(“Arial”)
        fnTexto = New Font(fnfFamilia, 10.0F, FontStyle.Regular)
        ptInicio = New Point(10, 10)
        szInicio = New Size(16, 16)
        pCor = New Pen(Color.Black, 2)
        bmpPonto = New Bitmap(800, 80)

        ‘Preenche a lista de pontos para a linha de tempo
        lstPontos = New List(Of TempoPontos)
        With lstPontos
            .Add(New TempoPontos(2005, “1º e 2º períodos”))
            .Add(New TempoPontos(2006, “3º e 4º períodos”))
            .Add(New TempoPontos(2007, “5º e 6º períodos”))
            .Add(New TempoPontos(2008, “7º e 8º períodos”))
            .Add(New TempoPontos(2009, “Mestrado”))
            .Add(New TempoPontos(2010, “Tese e apresentação”))
        End With

        ‘Gera um gráfico em memória e prenche seu fundo com branco
        grLinha = Graphics.FromImage(bmpPonto)
        grLinha.SmoothingMode = SmoothingMode.AntiAlias
        grLinha.FillRectangle(Brushes.White, New Rectangle(0, 0, lstPontos.Count * 65, 120))

        ‘Cria a linha de tempo com o tamanho relativo a quantidade de pontos
        grLinha.DrawLine(pCor, 0, 18, lstPontos.Count * 60, 18)

        Dim sgInicio As Single = ptInicio.X
        For Each t As TempoPontos In lstPontos

            ‘Desenha o círculo sem preenchimento
            grLinha.DrawEllipse(Pens.Black, New Rectangle(sgInicio, ptInicio.Y, szInicio.Width, szInicio.Height))

            ‘Gera a cor de fundo com degrade para o preechimento do círculo
            Dim ln As New LinearGradientBrush(New Rectangle(sgInicio, ptInicio.Y, szInicio.Width, szInicio.Height), _
Color.AliceBlue, Color.DarkBlue, LinearGradientMode.ForwardDiagonal)

            ‘Preenche o círculo com a cor definida acima
            grLinha.FillEllipse(ln, New Rectangle(sgInicio, ptInicio.Y, szInicio.Width, szInicio.Height))

            ‘Escreve na imagem o ano referente ao círculo
            grLinha.DrawString(t.Ano.ToString, fnTexto, Brushes.Black, sgInicio, ptInicio.Y + 25)

            ‘Avança a posição do próximo círculo
            sgInicio += 60
        Next

        ‘Salva a imagem criada em disco e vincula a imagem ao controle ImageMap
        bmpPonto.Save(Server.MapPath(“LinhaTempo.gif”))
        ImageMap1.ImageUrl = “~/LinhaTempo.gif”

        ‘Cria as áreas referentes a cada ponto no ImageMap e associa os Tooltips
        sgInicio = ptInicio.X
        For Each t As TempoPontos In lstPontos
            ImageMap1.HotSpots.Add(New RectangleHotSpot() With {.Top = ptInicio.Y, .Left = sgInicio, _
            .Bottom = ptInicio.Y + 25, .Right = sgInicio + 15, _
            .AlternateText = t.Descricao})
            sgInicio += 60
        Next

        Catch ex As Exception
            Throw
        End Try

End Sub

No código acima, utilizamos várias das classes do namespace System.Drawing para criar a imagem da linha de tempo dinamicamente, inclusive criando texto através da classe Font. Assim, não foi necessário recorrer a nenhum controle de terceiros ou imagens pré-definidas, o que traz uma maior liberdade na geração do código.


Integração entre Trace e diagnóstico no ASP.NET

29/08/2010

Um elemento importante no arsenal de um desenvolvedor é a capacidade de gerar diagnósticos acerca da aplicação e do seu ambiente. No ASP.NET desde sua primeira versão, temos um mecanismo de Tracing capaz de gerar informações em tempo de execução sobre o funcionamento da aplicação e que permite ao desenvolvedor inclusive, adicionar suas próprias informações ao resultado do Tracing. O mecanismo de Tracing padrão do ASP.NET porém, está associado à classe HttpContext, que mantém informações sobre as requisições HTTP, fazendo com que o Tracing padrão não seja útil fora do ambiente Web. Imagine por exemplo, que você possui algumas classes fora da aplicação ASP.NET e das quais você deseja obter informações de Tracing. As classes não poderiam enviar informações diretamente ao ASP.NET Tracing por estarem fora do contexto HTTP.  

A partir do .NET Framework 2.0 essa limitação é resolvida através da integração opcional entre o ASP.NET Tracing e o Diagnostics Tracing, que pode ser utilizado fora do ambiente Web. Para testar essa integração, coloque no evento Load de uma página ASP.NET as linhas abaixo:  

Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
    Trace.Write(“Entrada de Trace”, “Entrada escrita no objeto TraceContext nativo.”)
  

    System.Diagnostics.Trace.Write(“Entrada escrita no objeto TraceContext nativo e no objeto TraceContext de System.Diagnostics.”, “Entrada de Trace”)  

End Sub  

Em seguida, adicione ou altere a configuração padrão do ASP.NET Tracing para habilitado no arquivo Web.config:  

<trace enabled =truerequestLimit =20pageOutput =true/>  

Nessa configuração, a mensagem emitida pelo System.Diagnostics.Trace seria ignorada pelo ASP.NET Tracing ao executarmos a página. Porém, com o ASP.NET 2.0 podemos configurar um Listener do tipo WebPageTraceListener, que direciona as mensagens do System.Diagnostics para a saída de Trace das páginas ASP.NET. Para isso, mude sua configuração de Trace para:  

<trace enabled =truerequestLimit =20pageOutput =truewriteToDiagnosticsTrace =true/>  

E adicione uma seção de configuração para o Listener:  

<system.diagnostics>
  <trace>
    <listeners>
      <add name=WebPageTraceListener
type=System.Web.WebPageTraceListener, System.Web, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a/>
    </listeners>
  </trace>
</system.diagnostics> 

Repare então que na saída do Trace, estão as duas mensagens de Trace destacadas em vermelho: 
 

Resultado de Trace

Resultado de Trace

Visto o funcionamento da integração do Trace, podemos então chamar o Diagnostics.Trace em um componente externo e suas entradas serão direcionadas ao Tracing do ASP.NET. A classe abaixo pode estar em um outro componente e se for chamada em uma página ASP.NET, terá seu registro inserido no Tracing:

Imports System.Diagnostics

Public Class LogAplicacao
     Public Sub Gravar(ByVal categoria As String, ByVal mensagem As String)
        Trace.Write(mensagem, categoria)
    End Sub
End Class

Com as configurações vistas aqui, experimente chamar um componente similar com chamadas de Trace em suas páginas e se beneficie de informações mais completas em suas aplicações.


A Influência do Atributo AutoEventWireup

04/07/2010

Um atributo das páginas ASP.NET nem sempre compreendido, é o atributo AutoEventWireup. Esse atributo tem o objetivo de associar automaticamente os eventos de página com métodos escritos para esses fins. Por exemplo, o evento Load da página será utomaticamente associado ao método Page_Load se o atributo AutoEventWireup estiver definido como True (que é o valor default) e o seu método correspondente esteja no formato Page_NomeEvento, no nosso caso, Page_Load.
O atributo pode ser configurado por página:
<%@ Page Language=“vb” AutoEventWireup=“false” %>

Ou para todas as páginas, no aquivo Web.config:
<configuration>
   <system.web>
       <pages autoEventWireup=“true|false” />
   </system.web>
</configuration>

Um detalhe importante é que o atributo tem seu valor definido de forma diferente, de acodo com a linguagem utilizada, VB.NET ou C#:

  • No VB.NET o atributo está desabilitado por padrão: AutoEventWireup = false
  • No C# o atributo está habilitado por padrão: AutoEventWireup = true

No VB.NET o atributo se encontra desligado porque a sintaxe da linguagem permite associar os eventos de página aos seus métodos correspondentes através da palavra-chave Handles:

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

End Sub

No C#, o compilador encontra automaticamente a rotina Page_Load e a associa ao evento Load. Portanto, se o atributo for desligado, será necessário associar a rotina ao evento manualmente:

public partial class _Default : Page
{
    public _Default()
    {
        Load += new EventHandler(Page_Load);
    }

    protected void Page_Load(object sender, EventArgs e)
    {

    }
}

Experimente colocar um BreakPoint no evento Load de sua página e alterar o atributo AutoEventWireup. Você perceberá de forma simples a influência do atributo na execução dos eventos de página.

Impacto

O uso do atributo AutoEventWireup parece inocente, mas possui efeitos colaterais. Um desses efeitos é a obrigação de que os métodos associados aos eventos de página tenham nomes pré-definidos. Um outro efeito adverso está na performance de execução das páginas. Como o Visual Studio gera o código necessário para associar os eventos de página aos métodos e o Framework automaticamente invoca os eventos de acordo com seus nomes prédefinidos, essa dupla de efeitos tem o potencial de fazer com que os eventos de página sejam executados duas vezes. O artigo ASP.NET Server Control Event Model do MSDN comenta sobre esses efeitos. Já o impacto efetivo em termos de performance é pequeno pois uma vez tendo as páginas sido compiladas, essa verificação não ocorre novamente.


Ordenação em Controles DropDownList

05/05/2010

Uma operação muito comum e muito provavelmente cotidiana, é a ordenação de diferentes tipos de lista. E também muito comum, é a necessidade de exibir os resultados dessas ordenações aos usuários. Controles DropDown estão normalmente ligados a esse tipo de funcionalidade, uma vez que os seus usuários podem precisar justamente selecionar um elemento de uma lista ordenada.

Os mecanismos de ordenação são vários, partindo do longamente conhecido Order By em cláusulas SQL. A questão é que a ordenação direta no banco pode criar uma dependência direta com a base de dados e comunicações desnecessárias com a mesma. Podemos então, usar as alternativas presentes no .NET Framework para gerar as listas ordenadas de forma mais independente.

Considere uma DropDownList em uma página ASP.NET. Independente da origem dos dados exibidos na lista, você gostaria de reordenar esses elementos. Um dos métodos mais comuns usa um ArrayList para executar a ordenação:

Dim ar As New ArrayList

For Each li As ListItem In DropDownList1.Items
ar.Add(li.Text)
Next

ar.Sort()

DropDownList1.Items.Clear()
DropDownList1.DataSource = ar
DropDownList1.DataBind()

O trecho acima ordena os elementos da DropDownList de forma ascendente. Se você quiser uma ordenação descendente, basta trocar o método Sort por Reverse, como abaixo:

Dim ar As New ArrayList

For Each li As ListItem In DropDownList1.Items
ar.Add(li.Text)
Next

ar.Reverse()

DropDownList1.Items.Clear()
DropDownList1.DataSource = ar
DropDownList1.DataBind()

Ordenações Personalizadas

O método Sort do ArrayList implementa o algoritmo QuickSort, que não garante que a ordem original dos elementos será preservada em caso de igualdade na comparação. Assim, para uma ordenação mais exata, podemos lançar mão de uma ordenação personalizada através da Interface IComparer que pode ser usada em conjunto com o método Sort do ArrayList entre outros. Vejamos um exemplo de implementação do IComparer:

Public Class ListaAscendente
Implements IComparer

Public Function Compare(ByVal x As Object, ByVal y As Object) As Integer Implements System.Collections.IComparer.Compare
Dim itemx As ListItem = DirectCast(x, ListItem)
Dim itemy As ListItem = DirectCast(y, ListItem)

Dim ci As CaseInsensitiveComparer = New CaseInsensitiveComparer
Return ci.Compare(itemx.Text, itemy.Text)
End Function
End Class

Repare que temos uma classe que implementa a Interface IComparer, que por sua vez fornece o método Compare, onde devemos implementar a lógica de ordenação. A comparação retorna um valor que indica quando um valor é menor que outro, igual ao outro ou maior que o outro. No exemplo acima, comparamos textualmente o valor de cada elemento de uma lista para uma ordenação ascendente. Para uma ordenação descendente, basta inverter a ordem dos objetos em comparação como abaixo:

Public Class ListaDescendente
Implements IComparer

Public Function Compare(ByVal x As Object, ByVal y As Object) As Integer Implements System.Collections.IComparer.Compare
Dim itemx As ListItem = DirectCast(x, ListItem)
Dim itemy As ListItem = DirectCast(y, ListItem)

Dim ci As CaseInsensitiveComparer = New CaseInsensitiveComparer
Return ci.Compare(itemy.Text, itemx.Text)
End Function
End Class

Implementadas as classes, elas agora podem ser usadas em qualquer ArrayList que armazene elementos do tipo ListItem, como os de uma DropDowList, sem a necessidade do Loop do primeiro exemplo. Veja um exemplo de utilização das classes criadas acima:

Dim ar As New ArrayList(DropDownList1.Items)
ar.Sort(New ListaAscendente)

DropDownList1.Items.Clear()
DropDownList1.DataSource = ar
DropDownList1.DataBind()

Entra em Cena o Linq

Embora funcionais, percebemos que são necessárias algumas linhas de código para conseguirmos nossa ordenação independente da base de dados. Com o advento do Linq a partir do .NET Framework 3.0, tarefas que precisavam de muita escrita passaram a precisar de pouca escrita, como é o caso da ordenação. Para ordenar a nossa DropDownList de forma Ascendente com o Linq, usamos:

Dim liEnum As IEnumerable(Of ListItem) = Nothing
liEnum = DropDownList1.Items.Cast(Of ListItem)().OrderBy(Function(item) item.Text).ToList()

DropDownList1.DataSource = liEnum
DropDownList1.DataBind()

E de forma Descendente, usamos:

Dim liEnum As IEnumerable(Of ListItem) = Nothing
liEnum = DropDownList1.Items.Cast(Of ListItem)().OrderByDescending(Function(item) item.Text).ToList()

DropDownList1.DataSource = liEnum
DropDownList1.DataBind()

O operador Cast converte os elementos para tipos ListItem e a partir daí, os operadores OrderBy e OrderByDescending se encarregam de fazer a ordenação pelo texto de cada item. Cada um desses operadores informa uma expressão Lambda… mas isso é assunto para outro post 🙂


Desabilitando o Trace Viewer

17/01/2010

O Trace Viewer é um recurso muito útil para detectar problemas em suas aplicações quando estas estão em produção.  Quando habilitado à nível de aplicação, pode ser acessado através da URL:

http://site/trace.axd

Mas permitir o acesso à informações detalhadas sobre uma aplicação pode trazer riscos de segurança. Na parametrização do Trace Viewer podemos impedir que suas informações sejam acessadas remotamente da seguinte forma:

<trace enabled=”true” pageOutput=”false” localOnly=”true”/>

O atributo localOnly determina que o Trace Viewer só pode ser exibido na máquina local, evitando assim, o acesso a partir de outras máquinas.

Mas caso seja necessário uma política de segurança mais restritiva, há ainda outras alternativas. O Trace Viewer pode ser inteiramente desligado ou ter o seu nome padrão trocado de Trace.axd para um outro nome qualquer, de conhecimento apenas de pessoas que devam ter acesso às informações do Trace. Para isso é necessário uma alteração no arquivo Machine.config como mostrado abaixo:

<httpHandlers>
<add verb=”*” path=”trace.axd type=”System.Web.Handlers.TraceHandler”/>
</httpHandlers>

Para desabilitar totalmente o Trace Viewer, simplesmente deixe vazio o atributo path, ou troque o nome padrão para um nome diferente se quiser manter o acesso ao Trace.

Você pode ainda alterar ou desligar o Trace para sites individuais, configurando o elemento <httpHandlers> em seu arquivo Web.config.