APIs obsoletas – ASP.NET 2.0

Com a vinda do novo ASP.NET, o 2.0, muitas coisas que existiam nas versões 1.x acabaram ficando obsoletas, sendo uma delas, a velha conhecida de todos nós, a propriedade SmartNavigation dos WebForms. Ela tem a responsabilidade de manter a posição (scroll) da página quando um PostBack é causado na mesma.

Lendo a listagem de APIs obsoletas do .NET Framework 2.0 Beta 2, vi que a propriedade SmartNavigation foi substituída pela propriedade MaintainScrollPositionOnPostBack da classe Page.

Além disso, a Namespace Mail foi “transportado” para o Namespace System.Net.Mail. Uma outra que sofreu uma grande alteração foram os métodos da classe Page que utilizávamos para trabalhar com código Javascript no cliente (i.e. o método RegisterClientScriptBlock) que agora estão todas dentro de uma classe chamada ClientScriptManager e a classe Page, por sua vez, à expõe através de uma propriedade chamada ClientScript.

Para uma lista completa de todas as APIs obsoletas no .NET Framework 2.0, aconselho a baixar aqui, diretamente da Microsoft e analisar.

Analisando o Microsoft PetShop 3.0

Para quem não conhece, o Microsoft PetShop 3.0 é uma aplicação de uma loja virtual fictícia que vende pequenos animais que a Microsoft construiu com o propósito de um caso de estudo baseado no Java Pet Store da Sun, mas construindo-a sob a Plataforma .NET. Mas este artigo não tem o intuito de mostrar esta comparação e sim abordar a estrutura em que o mesmo foi construído. Para quem se interessar, poderar ver o estudo/análise desta comparação através deste link: http://www.middleware-company.com/buzz/buzz0703.jsp.

Microsoft PetShop 3.0 é uma aplicação Web, construída em ASP.NET, que comunica com componentes de negócios escritos em C#, quais compõem a Camada de Negócios (Business Logic Layer). ADO.NET é utilizado para acessar as Base de Dados, acesso qual está abstraído em uma camada de acesso a dados (Data Access Layer (DAL)), separando-se da camada de negócios.

Figura 1 – Microsoft PetShop 3.0.

Estrutura da Solução

Como podemos ver abaixo na Figura 2, a solução da Aplicação e constituída de diversos projetos, sendo 1 projeto do tipo WebApplication (User Interface), 8 projetos do tipo Class Library e 2 projetos “Build” para efetuar algumas configurações no ato da instalação. Veremos abaixo, a descrição de cada um deles.

Figura 2 – Estrutura da Solução.

 

Projeto Finalidade
BLL Contém as regras do negócio da aplicação.
ConfigTool Utilizado para Encriptar/Descriptar ConnectionString e fazer entradas no Event Log.
DALFactory Classe que determina qual Database será utilizada.
IDAL Interfaces que deverão ser implementadas para cada DAL.
Model Classes de dados ou objetos de negócio.
OracleDAL Implementação específica para acesso a fonte de dados Oracle.
Post-Build Responsável por adicionar os Assemblies no GAC e COM+.
Pre-Build Remove Assemblies do GAC e COM+.
SQLServerDAL Implementação específica para acesso a fonte de dados SQL Server 2000.
Utility Classes utilitárias.
Solution Items StrongKey para assinar os Assemblies.

Aplicação Distribuída

Para que a solução atenda o cenário de uma aplicação distrubuída, são criados duas Base de Dados (MSPetShop e MSPetShopOrders). Em uma delas, são armazenadas as informações referentes aos Produtos, Clientes, Banners, entre outros. Já na segunda Base de Dados, são armazenados os pedidos dos clientes e seus repectivos Items. Veremos abaixo a finalidade de cada uma das tabelas:

Tabela Descrição
Account Informações sobre o Cliente.
BannerData Armazena informações sobre Banners.
Category Categorias dos Produtos.
Inventory Estoque dos Produtos.
Item Detalhes dos Produtos.
LineItem Detalhes dos Items dos Pedidos.
Orders Pedidos realizados por um determinado Cliente.
OrderStatus Status do Pedido.
Product Catalogo dos Produtos.
Profile Perfil do Cliente.
Signon Login dos Usuários.
Supplier Informações dos Fornecedores.

OBS.: As linhas que estão com o fundo cinza, pertencem a Base de Dados “MSPetShopOrders”.

Arquitetura

Figura 3 – Arquitetura da Solução.

Como vemos na figura acima, a aplicação está dividida em três grandes camadas: Camada de Apresentação, Camada de Negócios e Camada de Acesso a Dados. O COM+ entra no jogo para garantir a integridade dos pedidos, ou seja, quando efetuamos uma compra, se algo falhar durante a transação, o mesmo será desfeito, mantendo assim a integridade dos dados.

Os componentes de negócios fazem as devidas manipulações para serem exibidas no Front-End, mas tudo começa a ficar interessante a partir daqui.

A idéia é de deixar a aplicação independente de Base de Dados. Isso não seria tão difícil, pois poderíamos utilizar OleDb para acessar qualquer uma das Bases de Dados, criando assim uma única classe genérica de acesso. Mas isso torna a aplicação pouco performática, pois não utiliza os Providers específicos para cada Base de Dados. Um dos requirementos ao construir esta aplicação era justamente obter alta performance, e em virtude disso, é indiscutível o uso dos Providers nativos: SqlClient e OracleClient.

Com essa meta, uma forma de simplicar o acesso aos dados, foi utilizar a Pattern Abstract Factory [Gof], juntamente com Reflection que cria e instancia o objeto correto em runtime. É criado uma Interface genérica, que contém os métodos necessários que deve ser implementada em cada classe de acesso a dados. Para cada classe de acesso a dados, você deve implementar esta Interface, utilizando as classes e o Provider específico para o Banco de Dados que está sendo requisitado.

Depois das classes de acesso a dados de Bases diferentes, cria-se uma Factory, que em runtime, utilizando Reflection, vai identificar qual das classes de acesso a dados irá instanciar, baseando-se em uma chave definida no arquivo Web.Config da aplicação Web. A figura abaixo, ilustra o processo:

Figura 4 – Escolhendo qual DB utilizar.

Se algum dia tivermos a necessidade de que a aplicação atenda a mais um tipo diferente de Base de Dados, por exemplo Microsoft Access, teríamos que seguir o seguinte procedimento:

1 – Criar classes de acesso a dados, utilizando OleDb e nela implementar as Interfaces IDAL;

2 – Compilar estas novas classes e fazer Deploy para o servidor;

3 – Alterar a configuração no arquivo Web.Config para chamá-la.

DAO – Data Access Objects

Data Access Objects ou DAO, é um padrão de projeto que é utilizado para abstrair o acesso a dados da camada de negócios (Business Layer), que por sua vez, acessa a Base de Dados através de Data Access Objects, tornando assim transparente o acesso. A DAO é uma DAL específica para um determinado objeto, fazendo com que a camada de negócios fique sem conhecimento da Base de Dados, pois isto está tudo encapsulado.

Esta abstração encapsula a implementação utilizada para acesso, gravação, gerenciamento da conexão referente a fonte de dados, que realmente não interessa a Business Layer qual o tipo de armazenamento de dados está sendo utilizado.

Figura 5 – Diagrama de Classe da Pattern DAO.

Abstract Factory

Abstract Factory fornece uma Interface ou Classe Abstrata para criar uma família de objetos relacionados dependentes sem especificar a classe concreta. Sendo assim, no projeto PetShop 3.0, é criado uma Interface chamada IDAL, que contém os métodos de manipulação de dados, qual é implementada em todas as classes concretas para accesso à diferentes Base de Dados.

Com isso, ao invés do Cliente instanciar a classe concreta, chama-se método de criação (Factory Method), que invoca a Abstract Factory, e utiliza este objeto para chamar os métodos e/ou propriedades da classe concreta. Ganhamos bastante flexibilidade, pois baseado em uma Key (no caso, chamada de WebDAL) dentro do arquivo Web.Config, em runtime ele saberá qual dos objetos concretos deverá instanciar.

Traduzindo em Código

Para que as coisas fiquem um pouco mais claras, veremos abaixo, como é feito isso. Sabendo que temos que implementar as Interfaces contidas na IDAL, veremos uma delas (Account):

1
2
3
4
5
6
7
 
public interface IAccount
{
    AccountInfo SignIn(string userId, string password);
    AddressInfo GetAddress(string userId);
    void Insert(AccountInfo account);
    void Update(AccountInfo account);
}
 
Código 1 – Interface IAccount.

Na camada de acesso a dados, deve-se implementar esta interface (IAccount) e codificar utilizando o Provider específico para aquela Base de Dados. No caso do PetShop, a implementação foi realizada utilizando o DAAB (Data Access Application Block) que utiliza SqlClient e também implementando para acesso a Base de Dados Oracle, utilizando OracleClient.

1
2
3
4
 
public class Account : IAccount
{
    //…
}
 
Código 2 – Implementado Interface IAccount.

Feito isso, a Factory se encarrega de verificar qual dos objetos instanciar, verificando no Web.Config qual a Base de Dados a ser utilizada, e através de Reflection, carrega a cria a instância da mesma através do Factory Method:

1
2
3
4
5
6
7
8
9
10
 
public class Account
{
    public static PetShop.IDAL.IAccount Create()
    {
        string path = System.Configuration.ConfigurationSettings.AppSettings[“WebDAL”];
        string className = path + “.Account”;
 
        return (PetShop.IDAL.IAccount) Assembly.Load(path).CreateInstance(className);
    }
}
 
Código 3 – Factory Method.

E finalmente em nossa camada de negócios, utilizamos nossa Factory para que ela gere a instância correta da classe de acesso e manipulação da Base de Dados a ser utilizada no Projeto:

1
2
3
4
5
6
7
8
9
10
11
 
public class Account
{
    public void Update(AccountInfo account)
    {
        if (account.UserId.Trim() == string.Empty)
            return;
        
        IAccount dal = PetShop.DALFactory.Account.Create();
        dal.Update(account);
    }
}
 
Código 4 – “Chamando” o Factory Method na Camada de Negócios.

Conclusão: Como vimos a utilização da Orientação à Objetos no Design de nossas aplicações, nos traz grande produtividade e facilidade em uma posterior manutenção. No caso do PetShop que vimos acima, é um ótimo exemplo, onde precisamos gerar independência de Base de Dados, mas existem vários cenários onde podemos utilizar Abstract Factory e principalmente DAO.

Configurando uma aplicação ASP.NET

Hoje temos dois arquivos bastante importantes quando trabalhamos com aplicações ASP.NET. São eles: Global.asax e o arquivo Web.Config. Esses arquivos tornam a Aplicação bastante flexível, pois reúnem as configurações e eventos em um único lugar.

Com o arquivo Global.asax, você executa um determinado código quando sua Aplicação é iniciada/finalizada ou até mesmo quando qualquer página da Aplicação for requisitada. Já o arquivo Web.Config, armazena as configurações da Aplicação, tais como: Estado de Sessão, Segurança, Globalização, etc.

Quando se trabalha com o Visual Studio .NET, ao criar uma nova Aplicação do tipo WebApplication, automaticamente ele se encarrega de adicioná-los por padrão è ela.

Figura 1 – Os arquivos Global.asax e Web.Config são criados automaticamente.

O fato de termos várias páginas ASP.NET agrupadas você até pode chamar de Aplicativo, mas isso vai muito além. Um Aplicativo ASP.NET tem além de todas as páginas, handlers de evento, módulos e código executável que é executado dentro de um Diretório Virtual no servidor Web (IIS).

O Diretório bin

Dentro da raiz do Aplicativo há um diretório chamado “bin”. Dentro dele são armazenados arquivos em formato binário compilados utilizados por seu Aplicativo (um exemplo são os arquivos com extensão *.dll).

Os assemblies colocados aqui estão automaticamente disponíveis para seus arquivos *.aspx, e com isso não precisamos nos preocupar com os procedimentos complexos de criação.

O Arquivo Global.asax

O Diretório Virtual no IIS é grande parte do Aplicativo ASP.NET. Independente da página, o aplicativo é iniciado na primeira vez que ela é solicitada. Enquanto os usuários navegam pelas páginas, o processamento ocorre em segundo plano para tratar do Aplicativo.

O Aplicativo pode cair e ser reiniciado da mesma forma que qualquer Aplicativo tradicional, com a seguinte excessão: enquanto um Aplicativo tradicional é iniciado e executado em um computador desktop permitindo a interação direta com o usuário, um Aplicativo ASP.NET é iniciado e executado em um servidor Web, e o usuário utiliza um browser para acessá-lo.

Lembrando que tudo em .NET Framework é um objeto, temos um objeto chamado HttpApplication, que nos fornece métodos e eventos. Com isso podemos deduzir que sempre que uma página em seu Diretório Virtual for solicitada pela primeira vez, um objeto do tipo HttpApplication é instanciado.

Como as páginas ASP.NET realizamos uma ou mais tarefas individualmente, elas não controlam o Aplicativo de modo geral, não podendo uma página afetar diretamente a outra. Deve existir uma localização central que controla a execução do Aplicativo. Tendo este cenário, entra em cena o arquivo Global.asax.

Conhecido como Arquivo de Aplicação de ASP.NET, o Global.asax permite-nos programar no lugar do objeto HttpApplication e com isso você poderá controlar o seu Aplicativo ASP.NET como faz com qualquer outro objeto por meio de métodos e eventos.

OBS.: Apesar do Visual Studio .NET incluir o arquivo Global.asax por default, ele é totalmente opcional. Se seu Aplicativo não conter um arquivo desse tipo, o programa opera de forma padrão. Desejando adicionar funcionalidades, a sua utilização torna-se essencial.

O arquivo Global.asax é colocado no Diretório raiz do Aplicativo (Exemplo: http://servidor/site/ – c:inetpubwwwwrootsite). O ASP.NET controla o acesso à esse arquivo, de modo que ele não é acessível através do browser, o que garante a segurança. Se alguém tentar executar a URL: “http://www.site.com.br/global.asax” o receberá a seguinte notificação de erro:

Figura 2 – Acessando o arquivo Global.asax através do browser.

Este arquivo é gerenciado pela .NET Framework. Sempre que ele for modificado, o CLR detecta isso e faz com que o Aplicativo seja reiniciado para que as novas mudanças tenham efeito. Isso pode ocasionar transtornos para os usuários finais. Modifique este arquivo somente o necessário.

Programando o arquivo Global.asax

O arquivo Global.asax opera de maneira semelhante as páginas *.aspx. Você utiliza o Global.asax para sincronizar qualquer evento exposto pela classe HttpApplication. Eventos quais veremos abaixo:

Evento Descrição
AcquireRequestState Acionado quando o Aplicativo obtém o cache para a solicitação.
AuthenticateRequest Acionado quando o Aplicativo tenta autenticar a solicitação de HTTP.
AuthorizeRequest Acionado quando o Aplicativo tenta autorizar a solicitação de HTTP.
BeginRequest Acionado quando a solicitação de HTTP é iniciada.
EndRequest Acionado quando a solicitação de HTTP é concluída.
Error Acionado quando surge um erro.
PostRequestHandlerExecute Acionado imediatamente depois do handler de HTTP processar a solicitação.
PreRequestHandlerExecute Acionado imediatamente antes do handler de HTTP processar a solicitação.
PreSenderRequestContent Se a solicitação tiver conteúdo adicional (QueryString, Variáveis de Formulário, etc.), esse evento é acionado imediatamente antes daquele conteúdo ser recebido.
PreSenderRequestHeaders Acionado imediatamente antes de os cabeçalhos de solicitação serem recebidos.
ReleaseRequestState Acionado quando o Aplicativo libera o estado de sessão para a solicitação.
ResolveRequestCache Acionado quando o Aplicativo determina o cache para a solicitação.
UpdateRequestCache Acionado quando o Aplicativo autaliza e libera o cache para a solicitação.

Abaixo o arquivo Global.asax padrão:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
 
Imports System.Web
Imports System.Web.SessionState
 
Public Class Global
    Inherits System.Web.HttpApplication
 
    Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
        ‘…
    End Sub
 
    Sub Session_Start(ByVal sender As Object, ByVal e As EventArgs)
        ‘…
    End Sub
 
    Sub Application_BeginRequest(ByVal sender As Object, ByVal e As EventArgs)
        ‘…
    End Sub
 
    Sub Application_AuthenticateRequest(ByVal sender As Object, ByVal e As EventArgs)
        ‘…
    End Sub
 
    Sub Application_Error(ByVal sender As Object, ByVal e As EventArgs)
        ‘…
    End Sub
 
    Sub Session_End(ByVal sender As Object, ByVal e As EventArgs)
        ‘…
    End Sub
 
    Sub Application_End(ByVal sender As Object, ByVal e As EventArgs)
        ‘…
    End Sub
 
End Class
 
Código 1 – O arquivo Global.asax.

Repare que na Linha 5 herdamos a classe HttpApplication dentro do Global.asax, como expliquei acima.

O arquivo mostrado no Código 1 esta apenas com o Eventos que o próprio Visual Studio .NET deixa criado. Você ainda poderá trabalhar com os Eventos mostrados na tabela acima, e como vemos na Figura 3, logo abaixo:

Figura 3 – Os Eventos do arquivo Global.asax.

Agora veremos abaixo qual a ordem de execução dos Eventos do arquivo Global.asax:

1.  Application_Start
2.  Application_BeginRequest
3.  Application_AuthenticateRequest
4.  Application_AuthorizeRequest
5.  Application_ResolveRequestCache
6.  Session_Start
7.  Application_AcquireRequestState
8.  Application_PreRequestHandlerExecute
9.  Page_Load (arquivo *.aspx) ou qualquer outra saída de página
10. Application_PostRequestHandlerExecute
11. Application_ReleaseRequestState
12. Application_UpdateRequestCache
13. Application_EndRequest
14. Application_PreSendRequestHeaders

OBS.: Vale lembrar que alguns eventos são executados de acordo com alguma circunstância. Um exemplo disso é o caso do Evento Session_Start, que somente é executado quando qualquer página é solicitada por aquele usuário, a partir da segunda vez/página, o Evento não ocorre novamente.

Configurando a Aplicação ASP.NET

Da mesma forma que é importante controlar o processamento do Aplicativo, é necessário configurá-lo. Controle de Acesso, Segurança, Estado de Sessão e até mesmo configurações personalizadas. Para isso o ASP.NET nos fornece um arquivo baseado em texto, que nos dá extensibilidade e fácil configuração.

Além disso, a configuração é hierárquica, ou seja, as informações de configuração de aplicativos são aplicadas de acordo com a estrutura de Diretórios Virtuais do seu site. Os sub-diretórios podem herdar ou anular opções de configuração de seus diretórios-pai.

Por padrão, todos os diretório são herdados de um arquivo de configuração padrão de sistema chamado de machine.config, (localizado em: “WinNTMicrosoft.NETFrameworkVersãoCONFIG).

O arquivo que é responsável pela configuração é o Web.Config, que é um arquivo XML, qual veremos à seguir.

O Arquivo Web.Config

Dentro deste arquivo não há nada de especial, à não ser que ele contém chaves e valores que são reconhecidos pelo ASP.NET. Tais valores são facilmente modificáveis, e como mencionei anteriormente, você pode adicionar suas próprias chaves, para controlar outras operações que o ASP.NET não faz para você.

A estrutura básica de um arquivo Web.Config é essa:

1
2
3
4
5
6
7
8
9
10
11
 
<configuration>
    <configSections>
 
    </configSections>
    <system.web>
 
    </system.web>
    <system.net>
 
    </system.net>
</configuration>
 
Código 2 – Estrutura básica do arquivo Web.Config.

Como vemos, é somente XML. Entre as tags <configuration> há dois elementos diferentes: handlers de seção de configuração e configurações da seção de configurações.

As configurações que configuram seu Aplicativo são pares de chave/valor. Há dois tipos destas seções: system.net e system.web. A primeira seção é para configurar o tempo de execução da .NET em si, portanto, não se preocupe muito com isso. Já a segunda é para controlar o ASP.NET. Suas configurações serão colocadas nas tags <system.web>.

OBS.: O arquivo Web.Config faz distinção entre letras maiúsculas e minúsculas, e sem o formato correto, seu Aplicativo poderá gerar erros.

Abaixo uma tabela com as configurações disponíveis para a utilização no arquivo Web.Config:

Seção Descrição
<appSettings> Utilizada para armazenar suas próprias configurações personalizadas de Aplicativo.
<authentication> Configura como o ASP.NET autentica seus usuários.
<authorization> Configura a autorização de recursos no ASP.NET.
<browsercaps> Responsável por controlar as configurações do componente de capacidades do navegador.
<compilation> Responsável por todas as configurações de compilação.
<customErrors> Indica como exibir erros no navegador.
<globalization> Responsável por configurar as opções de globalização.
<httpHandlers> Responsável pelo mapeamento de URLs de entrada em classes IHttpHandler.
<httpModules> Responsável por configurar Módulos de HTTP dentro de um aplicativo.
<identity> Controla como o ASP.NET acessa seus recursos.
<location> Controla como as configurações se aplicam a um diretório.
<pages> Controla configurações de páginas.
<processModel> Configura as configurações de modelo de processo do ASP.NET em Sistemas de Servidor da Web do IIS.
<sessionState> Configura o Estado de Sessão.
<trace> Configura o Trace (Rastreamento).
<webServices> Controla as configurações dos Serviços da Web.

Como podem ver, o arquivo Web.Config ajuda a tornar a Aplicação bastante flexível, ou seja, podemos definir funcionalidades globais em um único lugar. Além disso, uma das vantagens é que se houver a necessidade de mudar algo dentro do arquivo Web.Config, não há a necessidade de recompilar a Aplicação.

Com tantas tags e atributos, que podem nos deixar confusos, infelizmente o Visual Studio .NET em suas versões 2002 e 2003 não implementou o Intellisense em arquivos *.config. Mas há uma forma de obtermos de terceiros tal funcionalidade no seguinte endereço:

http://www.radsoftware.com.au/web/CodeZone/Articles/IntellisenseWebConfig.aspx

Conclusão: Vimos que o arquivo Global.asax nos permite controlar quase todos os aspectos do processamento de uma página ASP.NET. Você pode utilizar os Eventos do objeto HttpApplication para realizar operações imperceptíveis para o usuário, tornando sua Aplicação muito mais robusta e eficiente. Além disso podemos tornar a nossa Aplicação bastante flexível utilizando o arquivo de configuração Web.Config, fazendo com que a mesma possa reagir rapidamente à qualquer mudança.

Formatando valores em colunas do DataGrid

Frequentemente quando utilizamos um controle do tipo DataGrid, e inserimos valores do tipo Data, Dinheiro, Inteiros ou Decimais, precisamos formatar esse valor de acordo com a finalidade desse Campo. Nesse artigo apresentarei a propriedade DataFormatString da BoundColumn de um DataGrid.

Existem dois tipos de formatação: Standard Formats e Custom Formats. Como o objetivo do artigo é mostrar as formatações mais utilizadas em aplicativos para serem executados nos padrões brasileiros, então deixarei claro o seguinte: O padrão para valores numéricos será adotado o Stardand Format. Já a formatação para datas, será utilizado o Custom Format.

A propriedade DataFormatString fornece uma formatação customizada para o valor inserido na BoundColumn. Esta propriedade consiste em duas partes separadas por dois pontos estando dentro de um par de chaves da seguinte forma: {:}. Isso é válido apenas quando estiver inserindo na BoundColumn valores numéricos ou do tipo data.

A sintaxe é a seguinte: {0:[Formato][Qtde. Casas Decimais]}. O caracter que vem após os dois pontos, é o formato em que o valor será exibido. Você também poderá optar por definir a quantidade de casas decimais da seguinte forma: {0:C2}. A seguir uma lista com os valores possíveis:

Standard Format Descrição
C Exibe o valor no formato de moeda.
D Exibe o valor em formato decimal.
E Exibe o valor no formato cientìfico (exponencial).
F Exibe o valor no formato fixo.
G Exibe o valor no formato geral.
N Exibe o valor no formato numérico.
P Exibe o valor no formato de porcentagem.
X Exibe o valor no formato hexadecimal.

Observações: Os caracteres acima que especificam o formato a ser exibido não são case-sensitive, exceto para o X, pois se ele for minúsculo, os valores serão apresentados em minúsculo, do contrário, serão exibidos em maiúsculo.

Para configurar os valores no DataGrid, clique com o botão direito do mouse em cima do mesmo, e selecione Property Builder. Em seguida, vá até a aba Columns e ao incluir uma nova BoundColumn, a propriedade DataFormatString será habilitada para que você possa definir a formatação customizada. A imagem abaixo ilustra o processo:

Figura 1 – Configurando a propriedade DataFormatString do DataGrid.

A figura abaixo exibe os valores no DataGrid de acordo com a formatação pré-definida na propriedade DataFormatString.

Figura 2 – Configurando a propriedade DataFormatString do DataGrid.

Aqui chamo a atenção para a coluna onde é exibido o valor no formato moeda e o separador de casas decimais. Como não foi definido nenhuma cultura no arquivo Web.Config, por padrão ele adota as configurações regionais definidas no servidor. Se acrescentar a cultura pt-BR nas configurações de nossa aplicação, verão que os valores passarão a serem exibidos no formato brasileiro. Abaixo a ilustrução deixará claro:

1
 
<globalization requestEncoding=”utf-8″ responseEncoding=”utf-8″ culture=”pt-br” />
 
Código 1 – Alterando a cultura no arquivo Web.Config.

Com essa mudança, agora temos os valores sendo exibidos no padrão brasileiro, conforme mostrado na figura 3:

Figura 3 – Valores sendo exibidos no formato brasileiro.

Além das configurações para valores numéricos, ainda podemos utilizar a propriedade DataFormatString para formatarmos datas que são inseridas no DataGrid. Abaixo uma tabela as as possibilidades de formatação para datas:

Custom Format Descrição
MM/dd/yyyy Formato Mês/Dia/Ano
dd/MM/yyyy Formato Dia/Mês/Ano
hh:mm Formato Hora:Minuto
hh:mm:ss Formato Hora:Minuto:Segundo
dd/MM/yyyy hh:mm:ss Formato Dia/Mês/Ano Hora:Minuto:Segundo

OBSERVAÇÕES: Devemos nos atentarmos para o MM e para o mm, pois maiúsculo significa Mês, já o minúsculo significa Minutos.

Há ainda vários outros padrões para a formatação de datas, quais não optei por colocar aqui por que utilizamos na maioria das vezes o formato brasileiro. Mas para quem se interessar pode encontrar maiores informações no link direto da fonte da Microsoft: Standard DateTime Format Strings.

Como dito anteriormente, a configuração da formatação para data, funciona da mesma forma que a formatação para valores numéricos, ou seja, você define na propriedade DataFormatString da BoundColumn do DataGrid, como por exemplo: {0:dd/MM/yyyy hh:mm:ss}. A Figura 4 ilustra os possíveis formatos para datas:

Figura 4 – Formatando Datas.

IMPORTANTE: Você poderia também ao invés de barras “/” utilizar o hífen “-” como separador para as Datas, ficando a String de formatação da seguinte forma: {0:dd-MM-yyyy hh:mm:ss}.

Poderá também fazer a formatação diretamente no HTML, utilizando a propriedade DataItem em conjunto com a método Format. Exemplo:

1
2
3
 
<asp:TemplateColumn>
    <%# String.Format(“{0:c}”, Convert.ToInt32(Container.DataItem(“NomeDaColuna”))) %>
</asp:TemplateColumn>
 
Código 2 – Formatando valores diretamente no código HTML.

CONCLUSÃO: Com este artigo mostrei as possíveis formas de formatação para valores do tipo numéricos de datas que são inseridos nas BoundColumns do DataGrid. Aconselho a darem uma olhada também no link que coloquei um pouco mais acima que diz respeito à outros tipos de formatação bastante utilizados pelas aplicações.

FormatandoValores.zip (19.08 kb)

Construindo uma Área Restrita

Sempre que construímos uma aplicação Web, é muito comum criarmos áreas administrativas e restritas, onde os usuários poderão gerenciar o conteúdo das páginas, pedidos, clientes, entre muitas outras coisas. Neste artigo explicarei como fazermos para criarmos uma área restrita em sua aplicação.

Devemos criar uma aplicação ASP.NET no Visual Studio. Inicialmente, criaremos uma pasta chamada “Administracao” onde ficaram contidas as páginas restritas. Dentro dela uma página “Default.aspx” e fora dela, devemos ter outro WebForm que será responsável para que usuário faça o Login. Esta página chamará “Login.aspx”. A estrutura de arquivos de nosso projeto ficará algo como:

Figura 1 – Estrutura de Arquivos da Aplicação.

Depois disso, devemos informar qual será a página de “Start”. Para isso informaremos que a página inicial será a “Default.aspx”, dentro da pasta “Administracao”. Explico: Quando a aplicação é iniciada, a página “Default.aspx” será chamada, ao chamá-la será verificado se o usuário corrente encontra-se ou não logado. Caso não esteja, ele será redirecionado para a página “Login.aspx”, onde ele será obrigado à se autenticar.

Figura 2 – Definindo a página inicial.

Bem, antes de mais nada, devemos construir a página “Login.aspx”, onde devemos ter o formulário para que o usuário possa digitar seu Login e Senha para se identificar. O formulário ficará da seguinte forma:

Figura 3 – Página de Login.

Em primeiro lugar, quando iniciamos a aplicação, reparem que ele requisitou a página “Default.aspx” dentro do diretório “Administracao”. Como foi verificado que o Usuário não está logado, fui direcionado para a página de Login (“Login.aspx”) e anexado na URL uma QueryString chamada “ReturnURL”, que será a página que o usuário será redirecionado após efetuar a Login (caso seja válido). Outro ponto que gostaria de chamar a atenção é para o CheckBox “Lembrar”. Ele serve para definirmos se o Cookie será ou não persistente, ou seja, se quando o usuário fechar o Browser e abri-lo novamente, já ficará ou não autenticado.

Antes de codificarmos o evento Click do botão de Login, vamos ver as configurações que devemos fazer no arquivo Web.Config:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 
<?xml version=“1.0” encoding=”utf-8″ ?>
<configuration>
    <system.web>
        <authentication mode=”Forms”>
            <forms name=”Administracao” loginUrl=“Login.aspx” />
        </authentication>
    </system.web>
    <location path=”Administracao”>
        <system.web>
            <authorization>
                <deny users=”?” />
            </authorization>
        </system.web>
    </location>
</configuration>
 
Código 1 – Configurações no arquivo Web.Config.

Informamos no arquivo Web.Config a página que será efetuado o Login e também informamos que a pasta “Administração” será restrita. A partir disso o usuário deverá efetuar o Login antes de visualizar o conteúdo da pasta “Administracao”.

Agora vamos criar uma tabela no SQL Server chamada “Administradores” onde armazenará o cadastro dos Administradores do Sistema. Depois de criada, no evento Click do Botão de Login, devemos ir até o SQL Server e verificar se esse usuário existe na Base de Dados. Caso positivo, ele será redirecionado para a página que encontra-se na QueryString “ReturnURL”, caso contrário, não podemos permitir o acesso.

A Tabela Administradores terá apenas o seguintes campos: Nome (Varchar(50)), Login(Varchar(10)) e Senha(Varchar(6)). E o evento Click ficará da seguinte forma:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
 
Imports System.Web.Security
Imports System.Data.SqlClient
 
Private Sub btnLogin_Click(ByVal sender As System.Object, _
    ByVal e As System.EventArgs) Handles btnLogin.Click
 
    Dim conn As SqlConnection = New SqlConnection(“CONNECTION_STRING”)
    Dim strQuery As String
    Dim strRetorno As String
    strQuery = “SELECT Nome FROM Administradores “
    strQuery &= “WHERE Login = @Login AND Senha = @Senha”
    Dim cmd As SqlCommand = New SqlCommand(strQuery, conn)
 
    Dim Login As SqlParameter = New SqlParameter(“@Login”, SqlDbType.VarChar, 10)
    Login.Value = Me.txtLogin.Text.Trim
    cmd.Parameters.Add(Login)
 
    Dim Senha As SqlParameter = New SqlParameter(“@Senha”, SqlDbType.VarChar, 6)
    Senha.Value = Me.txtSenha.Text.Trim
    cmd.Parameters.Add(Senha)
 
    Try
        conn.Open()
        strRetorno = Convert.ToString(cmd.ExecuteScalar())
        conn.Close()
    Catch ex As Exception
        Response.Write(“Ocorreu uma falha.”)
    Finally
        If Not strRetorno = String.Empty Then
            FormsAuthentication.RedirectFromLoginPage(Me.txtLogin.Text.Trim, _
                Me.chkLembrar.Checked)
        Else
            Me.lblMensagem.Visible = True
        End If
    End Try
 
End Sub
 
Código 2 – Validando o Usuário.

Como podem ver, em primeiro lugar importamos os Namespaces necessários, no caso: System.Data.SqlClient e System.Web.Security. Depois, abrimos conexão com a Base de Dados e através de uma query, passamos como parâmetros o Login e Senha, verificamos se existe algum registro que atenda à essa condição. Caso exista, o usuário é redirecionado para a página “Default.aspx” dentro da pasta “Administracao”, caso contrário uma mensagem será exibida para ele, informando que o acesso foi negado. Lembrando que o CheckBox é passado como o segundo parâmetro para a Sub RedirectFromLoginPage (Linha 31), informando se é ou não para manter o Cookie persistente.

Importante: Não há necessidade de nos preocuparmos em tratarmos os apóstrofos “‘” e ponto-e-vírgula “;” para aqueles que tentarem a “SQL Injection”, pois quando trabalhamos com Parameters, eles ficam encarregados de tratarem isso automaticamente.

Depois de validado, somos direcionados para a página requisitada anteriormente. Veja abaixo:

Figura 4 – Página restrita sendo exibida.

Como passamos para a Sub RedirectFromLoginPage o Login do Usuário (Linha 30 do Código 2), é possível recuperarmos isso durante a sua sessão. Para isso pode fazer:

1
2
3
4
5
6
7
8
 
Imports System.Web.Security
 
Private Sub Page_Load(ByVal sender As System.Object, _
    ByVal e As System.EventArgs) Handles MyBase.Load
 
    Me.lblMensagem.Text = “Bem Vindo à Área Restrita ” & _
        HttpContext.Current.User.Identity.Name
End Sub
 
Código 3 – Podemos ver na Linha 5, como recuperar o Login do Usuário.

O que nos restou agora é fazermos o “Logout” do Usuário. Para isso, utilizaremos o LinkButton “Logout” e assim, ao clicar, a sessão do usuário será finalizada. Veja o exemplo abaixo:

9
10
11
12
13
14
 
Private Sub lnkLogout_Click(ByVal sender As System.Object, _
    ByVal e As System.EventArgs) Handles lnkLogout.Click
 
    FormsAuthentication.SignOut()
    Response.Redirect(“Default.aspx”, True)
End Sub
 
Código 4 – Efetuando o Logout do Usuário.

Utilizamos a Sub SignOut da classe FormsAuthentication para finalizar a sessão do Usuário, e assim ele será novamente redirecionado para a página de Login (“Login.aspx”).

Conclusão: Com o ASP.NET a segurança em nossas aplicações Web ficaram bem mais fáceis de serem implementadas e bem mais seguras. Este artigo explica a parte superficial. Há muitas “features” que possam ser utilizadas para garantirmos o máximo de segurança possível, mas é claro que tudo dependerá de cada Aplicação.

Trabalhando com Arquivos

Frequentemente necessitamos trabalhar com arquivos, sejam eles “*.txt”, “*.xml”, “.csv”, etc. Nesse artigo mostrarei como fazemos para criar, excluir, ler arquivos e diretórios a partir de uma Aplicação Web (ASP.NET).

Separarei por tópicos, onde cada um abordará sobre um assunto. Começaremos pelo Diretórios:

Criando um Diretório


1
2
 
Dim dir As System.IO.Directory
dir.CreateDirectory(“C:Artigos Linha de Codigo”)
 
Código 1 – Criando um Diretório.

Basta criarmos uma variável do tipo System.IO.Directory e executar a função CreateDirectory passando o caminho e nome do diretório a ser criado.

Excluindo um Diretório


1
2
3
4
 
Dim dir As System.IO.Directory
If dir.Exists(“C:Artigos Linha de Codigo”) Then
    dir.Delete(“C:Artigos Linha de Codigo”)
End If
 
Código 2 – Excluindo um Diretório.

É sempre boa prática antes de chamarmos a Sub Delete verificarmos se o diretório existe ou não. Para isso utilize a Function Exists do objeto Directory, que retornará um valor boleano indicando se existe ou não o diretório especificado.

Listando Arquivos e SubDiretórios


1
2
3
4
5
6
7
8
9
10
11
 
Dim dir As New System.IO.DirectoryInfo(“C:Artigos Linha de Codigo”)
Dim subdir As System.IO.DirectoryInfo
Dim arq As System.IO.FileInfo
 
For Each subdir In dir.GetDirectories
    Response.Write(subdir.Name & “<br>”)
Next
 
For Each arq In dir.GetFiles(“*.*”)
    Response.Write(arq.Name & “<br>”)
Next
 
Código 3 – Listando Arquivos e SubDiretórios.

No Código 3 chamo a atenção para a Linha 9. Como o método GetFiles é sobrecarregado, podemos especificar qual o tipo de arquivos queremos listar. No exemplo acima, optei por mostrar todos os arquivos. Se quisessemos exibir apenas arquivos “*.txt”, poderíamos fazer: GetFiles(“*.txt*”).

Vamos agora as operações que podemos realizar com os arquivos.

Criando um Arquivo


1
2
 
Dim arq As New System.IO.FileInfo(“C:Artigos Linha de CodigoNovoArquivo.txt”)
arq.Create()
 
Código 4 – Criando um arquivo.

Escrevendo em um Arquivo


1
2
3
4
5
6
 
Dim strTexto As String = “Israel Aéce.” & System.Environment.NewLine
strTexto =& “Email: israel@projetando.net”
 
Dim stream As New System.IO.StreamWriter(“C:Artigos Linha de CodigoNovoArquivo.txt”)
stream.WriteLine(strTexto)
stream.Close()
 
Código 5 – Escrevendo em um arquivo.

Na instância do StreamWriter já informamos o caminho e o nome do arquivo que deverá ser escrito. Depois chamamos o método WriteLine e passamos a variável “strTexto” para que que ela seja escrita no arquivo.

Excluindo um Arquivo


1
2
3
4
 
Dim arq As New System.IO.FileInfo(“C:Artigos Linha de CodigoNovoArquivo.txt”)
If arq.Exists Then
    arq.Delete()
End If
 
Código 6 – Excluindo um arquivo.

Aqui fazemos da mesma forma qual fizemos para os Diretórios, ou seja, antes de excluirmos verificamos se o arquivo existe. Caso positivo, chamamos o método Delete().

Lendo Arquivos


1
2
3
 
Dim reader As New StreamReader(“C:Artigos Linha de CodigoNovoArquivo.txt”)
Response.Write(reader.ReadToEnd)
reader.Close()
 
Código 7 – Lendo Arquivos.

Utilizando o método ReadToEnd da classe StreamReader não precisamos fazer um loop para percorrer todo o arquivo.

Bem, é importante lembrar que nesse artigo quero apenas dar exemplos de “como fazer para…”. É extremamente importante utilizarmos os tratamentos de erros (Try…Catch…Finally) para executar as rotinas acima, pois não sabemos se os arquivos e/ou diretórios existem ou não, se temos permissão para executar, etc.

CONCLUSÃO: A Plataforma .NET nos trouxe uma grande facilidade na manipulação de arquivos. Sejam eles “*.txt”, “*.xml”, etc. Além de prático as classes como StreamReader e StreamWriter fornece aos desenvolvedores grandes recursos em poucas linhas de código. E para quem está “encantado” na facilidade, espere até conhecer as “My” do Visual Studio .NET Whidbey (http://download.microsoft.com/download/c/7/f/c7f7a575-79ac-4399-9535-3ed57bc292f2/mynamespace.doc).

Behavioral Pattern – Template Method

Em 1995 um grupo de quatro profissionais escreveram um livro chamado “Design Patterns: Elements of Reusable Object-Oriented Software”, cujo os autores ficaram conhecidos como: “A Gangue dos Quatro” (“Gang of Four” ou GoF), sendo eles: Erich Gamma, Ralph Johnson, John Vlissides e Richard Helm.

As Design Patterns contém 23 padrões que estão divididos em três seções:

  • Creational (Criacional)
  • Structural (Estrutural)
  • Behavioral (Comportamental)

Neste artigo abordaremos sobre a Pattern Template Method que está contida dentro da seção Behavioral (Comportamental).

Segundo o GoF, Template Method define um esqueleto de algum algoritmo em um método, adiando a implementação dos passos deste algoritmo para as sub-classes. Esses métodos que são criados nesta classe são chamados de Primitive Operations (Operações Primitivas), e o método em que este algoritmo é implementado o chamamos de Template Method.

Nesta Pattern temos no mínimo dois participantes envolvidos que veremos abaixo:

  • Classe Abstrata: Define as operações primitivas (como métodos abstratos), e o Template Method que definirá o esqueleto do algoritmo.
  • Classe Concreta: Qual implementa as operações primitivas.

O diagrama abaixo ilustrará estes participantes:

Figura 1 – Participantes do Template Method.

Como podemos ver no diagrama acima, a AbstractClass define os métodos abstratos (Primitive Operations) que devem ser implementados nas sub-classes. Além disso, a AbstractClass ainda define um Método Template, que por sua vez irá estruturar as chamadas aos métodos (Primitive Operations), criando assim o esqueleto do algoritmo.

Vamos transformar este Pattern em um exemplo do mundo real. O cenário será: uma classe Autenticacao qual define os seguintes métodos primitivos: VerificaSintaxe e VerificarExistencia e um Template Method chamado Login. A classe não poderá ser instanciada, somente herdá-la. Ficará a cargo das classes que herdarem esta classe, implementarem estes métodos primitivos:

1
2
3
4
5
6
7
8
9
10
11
12
13
 
Public MustInherit Class Autenticacao
 
    Protected MustOverride Function VerificaSintaxe(ByVal password As String) As Boolean
    Protected MustOverride Function VerificarExistencia(ByVal password As String) As Boolean
 
    Public Function Login(ByVal password As String) As Boolean
        If VerificaSintaxe(password) Then
            Return VerificarExistencia(password)
        End If
        Return False
    End Function
 
End Class
 
Código 1 – Classe Base que implementa o Template Method.

Como podemos ver a classe Autenticacao (AbstractClass) criamos os métodos abstratos e o Template Method. Notem que no Template Method (Login) definimos o esqueleto do algoritmo, fazendo chamadas as nossas operações primitivas.

A keyword MustInherit impossibilita da classe Autenticacao ser instanciada, podendo somente ser herdada. Já a keyword MustOverride obriga implementar o método nas sub-classes. Chamo a atenção aqui que por padrão em VB.NET, os métodos são NotOverridable, ou seja, não podem ser sobreescritos nas sub-classes, garantindo assim que o método Login não será alterado (sobreescrito).

Agora nos resta implementar esta classe:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 
Public Class Cliente
 
    Inherits Autenticacao
 
    Protected Overrides Function VerificarExistencia(ByVal password As String) As Boolean
        Select Case password
            Case “Daniel”, “Luciano”, “Rodrigo”, “Israel”
                Return True
            Case Else
                Return False
        End Case
    End Function
 
    Protected Overrides Function VerificaSintaxe(ByVal password As String) As Boolean
        Return (password.Length > 5)
    End Function
 
End Class
 
Código 2 – Classe Filha que herda da Classe “Autenticacao”.

Como dito anteriormente, na classe Cliente herdamos a classe Autenticacao e implementamos os métodos primitivos VerificarExistencia e VerificaSintaxe de acordo com a nossa necessidade ou com as regras de negócio. Podemos aqui reparar a flexibilidade, pois poderíamos implementar de forma diferente para a uma outra classe, por exemplo, Usuarios.

E finalmente, verificando a existência de um Cliente na aplicação:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 
Sub Main()
    Dim x As New Cliente
    
    Console.WriteLine(x.Login(“Joao”))
    ‘Output: False – Sintaxe incorreta
    
    Console.WriteLine(x.Login(“Israel”))
    ‘Output: True – Usuário encontrado
    
    Console.WriteLine(x.Login(“Juliano”))
    ‘Output: False – Usuário inválido
    
    Console.ReadLine()
End Sub
 
Código 3 – Consumindo a classe “Cliente”.

Ainda poderá existir ocasiões onde será necessário a criação de mais um passo dentro do seu algoritmo. Com isso basta inserir uma Operação Gancho (Hook Operation) no método Template:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 
    ‘…
    
    Public Function Login(ByVal password As String) As Boolean
        If VerificaSintaxe(password) Then
            LogAcesso(password)
            Return VerificarExistencia(password)
        End If
        Return False
    End Function
    
    Protected Overridable Sub LogAcesso(ByVal password As String)
        Console.WriteLine(“Usuario: ” & password)
    End Sub
    
    ‘…
 
Código 4 – Adicionando um metodo “Hook”.

Quando adicionamos um método “Hook”, devemos possibilitar que ele seja sobreescrito, daí o “Gancho”, pois com isso podemos reescrever o método para uma classe específica, tendo assim maior flexibilidade. A keyword Overridable permite que o método seja sobreescrito nas sub-classes (para sobreescrever, devemos utilizar a keyword Overrides)(VB.NET).

CONCLUSÃO: Com este artigo vimos a simplicidade e flexibilidade de como construirmos aplicações Orientadas à Objetos, ganhando facilidade na manutenção devido ao encapsulamento e abstração. Há ainda mais 22 outras Patterns, que quando não nos fornece a solução por completa, ao menos nos mostra o caminho dela.

OOTemplateMethod.rar (14.15 kb)

Creational pattern – Builder

A pattern Builder tem por finalidade isolar a construção de um objeto complexo da sua representação, levando em consideração que o mesmo processo de construção possa criar diferentes representações. Sendo assim, o algoritmo para a construção de um objeto deve ser independente das partes que realmente compõem o objeto e também de como eles são montados.

Utilizando esta pattern, o que temos a fazer é criar uma classe, qual especifica uma interface abstrata para a criação das partes de um objeto-produto. Esta classe abstrata deverá ser herdada pelos objetos concretos que implementaram os métodos de construção para aquele objeto. Esta classe concreta nos fornecerá uma forma de recuperarmos o produto, retornando-o de alguma forma para o cliente que o solicitou.

Vejamos abaixo os participantes envolvidos nesta pattern:

  • Director: Constrói um determinado objeto, utilizando a interface de Builder (classe abstrata).
  • Builder: Define uma interface abstrata para a criação das partes de um objeto-produto.
  • ConcreteBuilder: Implementa os métodos de construção da classe abstrata e também mantém a representação do objeto que cria. Fornece ao cliente um método para a recuperação do produto.
  • Product: Representa o objeto complexo em construção, incluindo as interfacces para a montagem das partes no resultado final.

O diagrama abaixo ilustrará estes participantes:

Figura 1 – Participantes da Pattern Builder.

Antes de começarmos a analisar o código da pattern, vamos primeiramente entender o cenário: Teremos dois tipos de objetos complexos, um chamado “Apartamento” e outro chamado “Casa”, pois cada um desses objetos tem particularidades em sua criação, ou seja, implementam diferentemente os métodos de sua construção. Abaixo a classe ConstrucaoBuilder que define a interface necessária para os objetos concretos:

1
2
3
4
5
6
7
8
9
 
Public MustInherit Class ConstrucaoBuilder
 
    Protected _dados As Hashtable
 
    Public MustOverride Sub ConstroiParedes()
    Public MustOverride Sub ConstroiJanelas()
    Public MustOverride Sub DefineNumero()
 
End Class
 
Código 1 – Classe Base que contém a Interface para a criação dos objetos.

Confrontando o código acima com o modelo estrutural da pattern, esta classe é a que chamamos de “Builder”, que define a interface abstrata. Depois disso, o que temos à fazer é criar uma classe (“Director”) que terá receberá como parâmetro em um método construtor um objeto do tipo da classe abstrata, e internamente será invocado seus métodos para a construção do objeto. O código abaixo ilustra a classe “Director”:

1
2
3
4
5
6
7
8
9
 
Public Class Construtora
 
    Public Sub Construir(ByVal construcao As ConstrucaoBuilder)
        construcao.ConstroiJanelas()
        construcao.ConstroiParedes()
        construcao.DefineNumero()
    End Sub
 
End Class
 
Código 2 – Classe “Director”.

Como podemos ver, recebemos no parâmetro do método “Construir” um objeto do tipo “ConstrucaoBuilder”, que é o nosso objeto “Builder”. Podemos ver que internamente são invocados os métodos de criação do objeto, definindo assim uma ordem de criação do objeto que está sendo informado e reutilizando o algoritmo (os passos) de criação para todos os objetos.

Feito isso, nos resta implementarmos os nossos objetos complexos (Casa e Apartamento), que obrigatóriamente devem derivar da classe “ConstrucaoBuilder”, implementando os seus métodos de criação e retorno para o cliente. Abaixo o código relacionado ao nosso objeto “Apartamento”:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
 
Public Class Apartamento
 
    Inherits ConstrucaoBuilder
 
    Public Overrides Sub ConstroiJanelas
        _dados = New Hashtable
        _dados.Add(“Janelas”, “2”)
    End Sub
 
    Public Overrides Sub ConstroiParedes
        _dados.Add(“Paredes”, “12”)
    End Sub
 
    Public Overrides Sub DefineNumero
        _dados.Add(“Número”, “A-56”)
    End Sub
 
    Public Sub VisualizarConstrucao
        Console.WriteLine(“APARTAMENTO:”)
        For Each d As DictionaryEntry In _dados
            Console.WriteLine(“{0}: {1}”, d.Key, d.Value)
        Next
        Console.WriteLine()
    End Sub
 
End Class
 
Código 3 – Classe Apartamento (“ConcreteBuilder”).

Vemos que ao herdar a classe “ConstrucaoBuilder” os métodos “ConstroiJanelas”, “ConstroiParedes”, “DefineNumero” e criamos um método chamado “VisualizarConstrucao” para devolver ao cliente o resultado gerado. Por questões de exemplo, a classe “Casa” tem a mesma estrutura interna em seus métodos de construção e sendo assim, vamos ocultá-la aqui por questões de espaço, mas pode consultá-la no código fonte do artigo.

E finalmente, a chamada no cliente fica:

1
2
3
4
5
6
7
8
9
10
11
12
13
 
Sub Main()
    
    Dim construtora As New Construtora
    Dim apto As New Apartamento
    Dim casa As New Casa
    
    construtora.Construir(apto)
    construtora.Construir(casa)
    
    apto.VisualizarConstrucao()
    casa.VisualizarConstrucao()
    
End Sub
 
Código 4 – Consumindo as classes no cliente.

Vemos que criamos/configuramos a instância de uma classe do tipo “Construtora” que é o nosso “Director”, que envia as solicitações ao Builder, que este por sua vez fará o seu trabalho de construção de uma determinada parte do objeto concreto. Criamos também mais dois objetos, sendo um do tipo “Apartamento” e outro do tipo “Casa”, quais são posteriormente passados para o método “Construir” do nosso Director, e este executa os métodos de construção respectivos do objeto em questão.

Pode-se reparar, que independentemente do objeto passado para o método construtor de nosso “Director”, o processo de criação do objeto será executado – baseando-se na instância do mesmo – e assim percebemos que separamos a criação dos objetos complexos da sua representação mas utilizando o mesmo processo, ou melhor, os mesmo passos, possibilitando diferentes representações.

Aplica-se esta pattern quando o algoritmo de criação de um objeto complexo deve ser independente das partes que o compõem das quais são montadas e também quando deverá permitir diferentes representações para o objeto que é construído.

CONCLUSÃO: Apesar de ser uma pattern de utilização 3 em uma escala de 0 à 5, é útil quando necessitamos separar a construção de um objeto complexo da sua representação, criando assim, diversas representações deste objeto.

OOBuilder.zip (17.17 kb)

Criando objetos SQL-CLR

Como sabemos, utilizamos o T-SQL (Transact Structure Query Language) para acessar e manipular dados em um Banco de Dados SQL Server. Como o T-SQL é uma linguagem de Banco, ela é bastante limitada além de procedural; muitas vezes precisamos fazer algo dentro da Base de Dados onde ficamos impossibilitados, tendo que trazer os dados para a aplicação e assim fazer as devidas manipulações e/ou consistências ali.

Nesta nova versão do SQL Server e do Visual Studio .NET (SQL Server 2005 e Visual Studio 2005, respectivamente), a Microsoft integrou o CLR (Common Language Runtime) do .NET com o SQL Server 2005, podendo assim desenvolver Stored Procedures, Triggers, User-Defined Functions, User-Defined Types utilizando uma linguagem .NET como, por exemplo, Visual Basic .NET ou Visual C# .NET, ou seja, em código gerenciado (managed code).

A integração com o Common Language Runtime (CLR) traz uma série de benefícios, pois agora os desenvolvedores não estarão mais limitados a utilizar o T-SQL quando quiserem fazer interações com o SQL Server, onde podem escrever códigos em linguagens .NET e ter toda a riqueza que estas nos oferecem, como por exemplo: tratamento de erros estruturados, arrays, coleções fortemente tipadas, laços For…Next e For Each e até mesmo utilizarmos uma Regular Expression para validar um determinado campo. Podemos também usufruir do CLR, indicando nos em compile-time erros de sintaxe até mesmo buffer overflows que possam vir a acontecer. O mais interessante ainda é que, para termos boa performance, o runtime do .NET é lazy loading para o usuário do SQL Server, ou seja, somente carregará quando for realmente necessário, portanto, quando invocar pela primeira vez uma Stored Procedure ou qualquer outro objeto que lá dentro se encontra.

Vamos ver no decorrer deste artigo como fazer para criar esses tipos de objetos utilizando uma linguagem .NET. O artigo se baseia na versão Beta 2 do SQL Server 2005 e versão Beta 1 do Visual Studio .NET 2005. Vale lembrar que, pelo fato destes softwares estarem ainda em suas versões betas, é possível que até a sua versão final alguma característica possa vir a mudar.

No Visual Studio .NET foi criado uma série de novos Templates para projetos. Um deles é o SQL Server Project , que é justamente para esta finalidade: criar objetos para o SQL Server. Para isso, ao iniciar o Visual Studio .NET 2005, basta criar um novo projeto e, ao selecionar a linguagem desejada, terá os templates para projetos para Base de Dados (Database). A Figura 1 ilustra este processo.

Quando o projeto é criado, é apresentada uma caixa de diálogo para informar o servidor de Banco de Dados pelo qual queremos criar os objetos. Neste momento temos que informar os dados para acesso, como: Nome do Servidor, Login e Password e a Base de Dados em si.

Criado o projeto e configurado a conexão com a Base de Dados, podemos já iniciar a construção dos objetos. Mas antes disso, vamos entender um pouco mais sobre cada um destes objetos:

Objeto  Descrição
Stored Procedures  Uma Stored Procedure (ou Procedimento Armazenado) é uma coleção de instruções T-SQL que é armazenada no servidor de Banco de Dados. A Stored Procedure nada mais é que um método qual encapsula a lógica de uma tarefa repetitiva, fornecendo suporte à declaração de variáveis, condicionais, laços e outros recursos de programação.
Triggers  Uma Trigger é um Procedimento Armazenado que é executado quando os dados de uma tabela específica são modificados. É bastante utilizado para impor integridade referencial ou consistência entre dados relacionados (claro, em tabelas diferentes).
User-Defined Functions Com o SQL Server você pode criar suas próprias funções para estender as funcionalidades do mesmo. Ele pode conter um ou mais parâmetros de entrada, podendo retornar um valor escalar ou uma tabela.
User-Defined Types O SQL Server fornece vários tipos de dados de sistema, mas não se limita a eles. Você pode criar tipos de dados específicos para a sua aplicação, baseando-se obrigatóriamente nestes tipos de dados do sistema. 

Criando objetos no .NET – Stored Procedures

Depois de entendermos o que é cada um desses objetos veremos como criá-los no Visual Studio .NET 2005. Para criar um novo objeto do tipo Stored Procedure dentro do projeto SQL Server devemos clicar com o botão direito em cima do Projeto no Solution Explorer >> Add >> Strored Procedure. Um arquivo é criado com a extensão *.vb (ou *.cs se o projeto tiver como linguagem o Visual C# .NET). Depois de adicionado, podemos ver que a IDE criou uma Partial Class, chamada StoredProcedures. Na medida que você for criando objetos deste tipo, outras Partial Class são também criadas e, quando compilado o projeto, estas, por sua vez são mescladas (merge) em uma única classe chamada StoredProcedures. Ao adicionar um novo objeto do tipo Stored Procedure, terá o código semelhante ao abaixo:

1
2
3
4
5
6
7
8
9
10
11
 
Imports System
Imports System.Data
Imports System.Data.Sql
Imports System.Data.SqlServer
Imports System.Data.SqlTypes
 
Partial Public Class StoredProcedures
    <SqlProcedure()> Public Shared Sub StoredProcedure1()
        ‘ Add your code here
    End Sub
End Class
 

Código 1 – Código gerado pela IDE quando solicitamos uma nova Stored Procedure.

O Atributo <SqlProcedure()> determina que o procedimento é uma Stored Procedure. O Visual Studio .NET usa esta determinação para criar a Stored Procedure dentro do SQL Server quando o Deployment é feito. Como já vimos acima, uma Partial Class chamada StoredProcedures é criada e dentro dela colocamos uma ou mais Stored Procedures. O interessante é colocar uma Stored Procedure por arquivo para facilitar a manutenção, mas isso não é uma regra obrigatória.

Vale ressaltar também que, independentemente se criarmos as Stored Procedures em um mesmo arquivo ou em arquivos separados, ao compilar, as Partial Class são mescladas e, com isso, as Stored Procedures ficam todas dentro de uma mesma classe, como já mencionamos acima. Para certificarmos que isso realmente acontece, podemos visualizar através do Class View do Visual Studio .NET, como vemos na Figura 4 abaixo:

 

Figura 4 – Class View do Visual Studio .NET 2005.

Depois de entendido a estrutura de como isso funciona, vamos então ao código que mostrará realmente a construção da Stored Procedure. O nosso cenário consiste em três tabelas, onde na primeira são armazenados os Fabricantes de Veículos. Já a segunda se encarrega de armazenar os Veículos dos respectivos Fabricantes e, por fim, temos uma tabela chamada Log que guardará os Logs de inserção, deleção e atualização efetuados na tabela Veiculo. A nossa tabela já está pré-configurada com os fabricantes. Um “select” na mesma nos retornará aos seguintes dados:

1
2
3
4
5
6
7
8
9
10
11
 
SELECT * FROM Fabricante
 
FabricanteID Nome
————– ————————————————–
1                 Audi
2                 Fiat
3                 Ford
4                 Volkswagen
5                 Chevrolet
 
(5 row(s) affected)
 

Código 2 – Dados pré-configurados na Tabela Fabricante para os exemplos do artigo.

Para complementar temos ainda a tabela de Veiculos, qual contém os veículos relacionados com os seus respectivos fabricantes, qual também já temos uma pré-carga com alguns dados:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 
SELECT
  Fabricante.Nome As Fabricante
  , Veiculo.Nome As Veiculo
FROM Veiculo
INNER JOIN Fabricante ON
  Fabricante.FabricanteID = Veiculo.FabricanteID
 
Fabricante             Veiculo
——————-   ————————————————–
Audi                     A3
Fiat                      Palio
Ford                     Fiesta
Volkswagen           Golf
Chevrolet              Corsa
 
(5 row(s) affected)
 

Código 3 – Dados pré-configurados na Tabela Veiculo para os exemplos do artigo.

Portanto, agora vamos transpor a última query, que retorna os Veículos e seus respectivos Fabricantes para uma Stored Procedure em .NET. A mesma vai chamar-se RetornaVeiculos. O código abaixo exemplifica isso:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 
Imports System
Imports System.Data
Imports System.Data.Sql
Imports System.Data.SqlServer
Imports System.Data.SqlTypes
 
Partial Public Class StoredProcedures
    <SqlProcedure()> Public Shared Sub RetornaVeiculos()
        Dim cmd As SqlCommand = SqlContext.GetCommand()
        Dim pipe As SqlPipe = SqlContext.GetPipe()
        
        Dim query As String = “SELECT “
        query &= “Fabricante.Nome, Veiculo.Nome “
        query &= “FROM Veiculo “
        query &= “INNER JOIN Fabricante ON “
        query &= “Fabricante.FabricanteID = Veiculo.FabricanteID”
        
        cmd.CommandText = query
        pipe.Send(cmd.ExecuteReader())
    End Sub
End Class
 

Código 4 – Stored Procedure RetornaVeiculos().

Analisando o código acima, temos algums novos objetos. No caso do exemplo do código 4 nos é apresentado o SqlContext e o SqlPipe. Veremos a funcionalidade de cada um desses objetos logo abaixo:

  • SqlContext: Através da classe SqlContext você consegue interagir com o Banco de Dados daquele contexto/conexão do ambiente em que está executando-o, resgatando as informações e objetos necessários para a execução de um determinado comando no Banco de Dados como, por exemplo, GetConnection(), GetCommand(), GetTransaction() , etc.. 

  • SqlPipe:  Este por sua vez envia mensagens, dados tabulares ou até mesmo erros que possam vir a ocorrer durante a execução para o cliente. O conceito deste objeto é bastante similar ao objeto HttpResponse (Response) do ASP.NET, que envia informações para quem o está requisitando (Browser).

O método GetPipe() resgata o canal de comunicação entre o objeto e a aplicação cliente que o chama, ou seja, é a comunicação entre o cliente e o servidor. Através do método Send deste objeto você envia as informações para o cliente. Este mesmo método tem vários overloads (sobrecargas) que recebe diversos tipos de objetos para retornar ao cliente. Já a classe SqlContext representa o contexto corrente e, com isso, elimina a necessidade de abrir outra conexão com a Base de Dados. Com exceção dessas novidades, o resto do código é bastante parecido com o que temos hoje ao desenvolvermos através de código ADO.NET.

Adicionando o Assembly no SQL Server 2005

Depois de criado a(s) Stored Procedure(s), ou qualquer outro objeto, é necessário catalogar o Assembly gerado pelo Visual Studio .NET dentro do SQL Server 2005. Este processo consiste em dois passos: no primeiro deles você deve catalogar o Assembly, ou seja, “inserí-lo” dentro do SQL Server. No segundo passo você precisa mapear os objetos (Stored Procedures, Triggers, etc) para os métodos que estão dentro do Assembly que, nesta altura, já estará catalogado. Veremos como funciona o processo de catálogo de Assemblies através da DDL (Data Definition Language):

1
2
3
4
 
USE BetaTester
  CREATE ASSEMBLY  ObjetosSQL 
    FROM ‘C:SQLCLR.NETObjetosSQLbinObjetosSQL.dll’
    WITH PERMISSION_SET = SAFE
 

Código 5 – Catalogando o Assembly dentro do SQL Server 2005.

No SQL Server 2005 temos agora uma nova instrução dentro da linguagem DDL que chamamos de “CREATE ASSEMBLY”. Esta instrução é utilizada para adicionarmos o Assembly dentro da Base de Dados. Como podemos ver, não existem muitos segredos: informamos um nome qualquer que identificará o Assembly e, na cláusula FROM, informarmos o path completo até o arquivo DLL gerado pelo Visual Studio .NET. Já a cláusula PERMISSION_SET permite especificar o nível de segurança em que seu código será executado/acessado. Existem três opções para esta cláusula, como veremos abaixo:

Tipo de Permissão  Descrição
SAFE É o default. Neste modo o Assembly somente poderá rodar no contexto local, mas não através do SqlClient. Previne também o acesso através de recursos externos e de código não gerenciado.
EXTERNAL_ACCESS  É o mesmo que SAFE, somente habilitando o acesso aos recursos externos. 
UNSAFE  Acesso irrestrito, desde que o Assembly seja assinado e catalogado por um usuário que seja membro do grupo sql_admins.

Depois de catalogado o Assembly dentro do nosso Banco de Dados, o que temos que realizar agora é a definição da Stored Procedure, mapeando o método RetornaVeiculos() que está dentro do Assembly. Para isso, utilizamos o código semelhante ao que se utiliza atualmente, ou seja, utilizando a linguagem DDL. Exemplo:

1
2
3
4
 
USE BetaTester
  CREATE PROCEDURE RetornaVeiculos 
    AS EXTERNAL NAME
    ObjetosSQL.[ObjetosSQL.StoredProcedures].RetornaVeiculos
 

Código 6 – Mapeando os métodos/objetos dentro do SQL Server 2005.

Uma observação importante é que, se a Stored Procedure tiver parâmetros, você deverá também específicá-los quando for criá-la dentro do SQL Server. Depois de mapead, podemos executar a Stored Procedure normalmente. Se tudo ocorrer corretamente o resultado será o mesmo que está sendo exibido no código 3 deste artigo. Para executar a Stored Procedure pode-se fazer como já era feito nas versões anteriores do SQL Server:

1
2
 
USE BetaTester
  EXECUTE RetornaVeiculos 
 

Código 7 – Executando a Stored Procedure.

Se analisarmos agora o Object Browser que encontra-se dentro do SQL Server Management Studio, veremos lá a Stored Procedure criada e o Assembly também já catalogado. Através da tabela sys.assembly_files você pode recuperar os Assemblies que estão catalogados dentro de um determinado Banco de Dados. Além dessa forma que foi explicado acima para catalogar o Assembly no SQL Server, pode ser feito automaticamente pelo Visual Studio .NET onde, depois de compilar o projeto, faz o Deployment do mesmo e com isso, os passos que foram efetuados acima ele faz automaticamente, incluindo todos os objetos dentro da Base de Dados em questão. Para realizar isso dentro do Visual Studio .NET 2005 vá até o menu Build e clique na opção “Build <Project>”.

Triggers

Como já sabemos, Triggers são disparadas quando uma ação de INSERT, UPDATE ou DELETE é executada em uma determinada tabela e as utilizamos quando necessitamos criar integridade ou mesmo fazer consistências dos dados da Base de Dados. Mas os eventos (ações, como são tratados em .NET) não se restringem somente à estas opções, tendo inclusive ações que detectam, por exemplo, a criação de tabelas dentro da Base de Dados. Para o uso das CLR Triggers, quando você cria uma Trigger no Visual Studio, o nome da Partial Class é Triggers.

No restante é bem semelhante, mesmo a construção das Stored Procedures como já vimos acima. A principal diferença está na recuperação do Contexto onde o comando corrente é executado, ou seja, dentro da Trigger você recupera o contexto através do método GetTriggerContext do objeto SqlContext. Este método fornece as mesmas informações do anterior, incluindo o acesso às tabelas virtuais que são criadas durante a execução da Trigger, tabelas quais armazenam os dados que causaram o disparo da mesma.

Outra diferença também é que o atributo para o método também muda. Agora temos que utilizar o seguinte atributo: <SqlTrigger(…)>, o qual veremos detalhadamente mais abaixo. No cenário dos nossos testes criaremos uma Trigger vinculada à tabela Veiculo onde, a cada Inserção, Atualização ou Deleção um Log deve ser gerado na tabela Log. Abaixo o código da mesma para analisarmos:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
 
Imports System
Imports System.Data
Imports System.Data.Sql
Imports System.Data.SqlServer
Imports System.Data.SqlTypes
 
Partial Public Class Triggers
     <SqlTrigger(Event:=”FOR INSERT, UPDATE, DELETE”, Name:=”GeraLog”, Target:=”Veiculo”)> _
     Public Shared Sub GeraLog()
        Dim context As SqlTriggerContext = SqlContext.GetTriggerContext()
        Dim cmd As SqlCommand = SqlContext.GetCommand()
        
        Select Case context.TriggerAction
            Case TriggerAction.Insert
                cmd.CommandText = “INSERT INTO [Log] (Descricao) VALUES (‘INSERÇÃO.’)”
            Case TriggerAction.Delete
                cmd.CommandText = “INSERT INTO [Log] (Descricao) VALUES (‘DELEÇÃO.’)”
            Case TriggerAction.Update
                cmd.CommandText = “INSERT INTO [Log] (Descricao) VALUES (‘ATUALIZAÇÃO.’)”
        End Select
        cmd.ExecuteNonQuery()
    End Sub
End Class
 

Código 8 – Trigger GeraLog().

Analisando o código acima vemos que o construtor do atributo SqlTrigger têm alguns parâmetros:

  • Event:  É o evento em que a Trigger vai ser disparada caso o mesmo aconteça. Se quisermos que a Trigger seja disparada toda vez em que uma inserção de um novo registro seja efetuada na Tabela, definimos como “FOR INSERT”. O mesmo acontece para atualização e deleção de registros. É importante lembrar que a cláusula “FOR” não é necessária para cada uma dessas ocasiões. 

  • Name:  O nome em si da Trigger.

  • Target: A tabela que será o “alvo”, ou melhor, a tabela em que a Trigger será anexada. A tabela em que é detectada alguma ação e esta estiver sendo capturada.

Vale lembrar que é perfeitamente possível acessar as tabelas virtuais “INSERTED” e “DELETED”, que são criadas pela Triggers quando a mesma é executada. Recuperando o contexto da Trigger através do método GetTriggerContext e através da ação da Trigger (verificado com o enumerador System.Data.Sql.TriggerAction) executamos um código específico para aquele processo. No caso do exemplo que é exibido no Código 8 inserimos uma nova linha na tabela Log informando a ação que foi executada.

O processo de criação dentro do SQL Server funciona da mesma forma que é feito para a Stored Procedure, ou seja, utilizando a linguagem DDL (Data Definition Language). O que muda é apenas o nome, agora sendo TRIGGER e tendo que informar em qual evento o mesmo será disparado. O exemplo abaixo exemplifica como a criação é realizada:

1
2
3
4
5
6
 
USE BetaTester
  CREATE TRIGGER GeraLog 
    ON Veiculo
    FOR INSERT, UPDATE, DELETE
    AS EXTERNAL NAME
    ObjetosSQL.[ObjetosSQL.StoredProcedures].GeraLog
 

Código 9 – Criando a Trigger dentro do SQL Server 2005.

User-Defined Functions (UDFs)

A construção e deploymet de User-Defined Functions é da mesma forma que as explicadas anteriormente. Como é necessário, o atributo para o método agora é o <SqlFunction(…)>. Vamos ver abaixo o código para termos um exemplo de uma UDF criada pela linguagem .NET:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 
Imports System
Imports System.Data
Imports System.Data.Sql
Imports System.Data.SqlServer
Imports System.Data.SqlTypes
 
Partial Public Class UserDefinedFunctions
     <SqlFunction(DataAccess:=DataAccessKind.Read)> _
     Public Shared Function RetornaQtdeVeiculos() As Integer
        Dim cmd As SqlCommand = SqlContext.GetCommand()
        cmd.CommandText = “SELECT COUNT(*) FROM Veiculo”
        Return Convert.ToInt32(cmd.ExecuteScalar())
    End Function
End Class
 

Código 10 – UDF – User-Defined Function RetornaQtdeVeiculos().

Como vemos, agora o procedimento é do tipo Function, justamente porque um valor é retornado. Utilizando a função COUNT(*) para retornar a quantidade de registros e através do método ExecuteScalar(), recuperamos este valor da Base de Dados e utilizando o Return, retornamos o valor ao cliente.

T-SQL vs. Managed Code

Uma das grandes dúvidas, com este novo recurso que será disponibilizado nas próximas versões do Visual Studio .NET e SQL Server 2005, é saber quando utilizar código gerenciado e quando utilizar o T-SQL. Com esta questão temos que analisar alguns fatores para a escolha. O T-SQL é interessante utilizar onde o código executará em sua maior parte diretamente acessando os dados, sem nenhuma lógica complexa ou procedural; já a utilização do código gerenciado facilita quando você necessita tirar proveito ao máximo das capacidades de linguagens de programação como, por exemplo, Visual Basic .NET ou Visual C# .NET, inclusive conceitos de orientação à objetos, integrando assim com recursos que dificilmente conseguimos em uma linguagem de Banco de Dados, que é muito limitada em relação à estas linguagens genuínas. Temos, além disso, a vantagem de usufruirmos da biblioteca do .NET Framework para trabalharmos.


Conclusão
: Neste artigo foram apresentadas algumas das novas features do novo SQL Server. Para que não ficasse muito extenso, inúmeros outros recursos não foram abordados neste artigo como, por exemplo, suporte à chamadas assíncronas, métodos para paginação de dados que retornam SqlDataReaders. Inclusive uma nova característica muito interessante, pois não precisamos mais de várias conexões com a base de dados para múltiplos comandos, podendo compartilhar a mesma conexão entre os comandos, fornecendo um grande ganho de performance e escalabilidade. E, como vimos no decorrer do artigo, o código é muito semelhante ao que já é utilizado atualmente em aplicações .NET que fazem acesso aos dados utilizando o ADO.NET. Agora temos mais de uma opção quando quisermos escrever códigos de acesso e manipulação de dados, ou seja, podemos escolher .NET (managed code) ou ainda continuar utilizando o T-SQL.

SQLCLR.NET.zip (208.69 kb)

ASP.NET Profile – Parte 1

Neste artigo apresentarei um overview do novo objeto do ASP.NET 2.0, chamado Profile qual tem a finalidade específica de armazenar informações de um determinado usuário em seu contexto.

O Profile é bem semelhante ao objeto Session atual, onde para cada usuário que temos em nossa aplicação Web é criado um Profile para ele. Até então funciona da mesma forma que o objeto Session. Mas o mais interessante está por vir, ou seja, quando fechamos o browser, a Session é perdida. Já utilizando o Profile ele se mantém persistente mesmo depois do usuário fechar o browser.

Isso se deve graças ao uso, por default, do Microsoft Access, onde são armazenadas estas informações, que fica dentro de uma pasta chamada Data, dentro da sua aplicação. Vale lembrar que é perfeitamente possível o uso de uma outra Base de Dados para termos uma melhor performance, como por exemplo SQL Server ou Oracle. Veremos este tópico um pouco mais adiante, em um futuro artigo.

Além da vantagem de se manter persistente mesmo depois de fechado o Browser, o Profile ainda tem uma vantagem, que particularmente considero fantástica, que é fortemente tipado (strongly typed), ao contrário da Session, que por sua vez aceitava um Object. Além disso, o IntelliSense já reconhece as propriedades, tornando assim o desenvolvimento mais rápido e menos propício a erros.

Definindo o Profile

Você deve utilizar o arquivo Web.Config para gerar a estrutura que o teu objeto Profile irá ter. Temos à nossa disposição o elemento profile, onde definimos as propriedades que vamos disponibilizar. O cenário é termos dentro do Profile, o Nome e Email do Usuário. Abaixo o código do arquivo Web.Config que define o Profile:

1
2
3
4
5
6
7
8
9
10
11
12
13
 
<configuration>
    <system.web>
        <authentication mode=”Forms” />
        <anonymousIdentification enabled=”true” />
 
        <profile>
            <properties>
                <add name=”Nome” defaultValue=”” allowAnonymous=”true” />
                <add name=”Email” defaultValue=”” allowAnonymous=”true” />
            </properties>
        </profile>
    </system.web>
</configuration>
 
Código 1 – Definindo a estrutura do Profile no arquivo Web.Config.

Analisando o código acima, vemos o elemento anonymousIdentification que especificará que o Profile será criado para usuários anônimos ou autenticados. Se definí-lo com False e tentar atribuir algum valor as propriedades em runtime e o usuário não estiver autenticado, uma Exception será lançada. Já o atributo allowAnonymous é requerido quando as propriedades são usadas com usuários anônimos.

O código para atribuir um valor as propriedades, fica da seguinte forma:

1
2
 
Profile.Email = “israel@projetando.net”
Profile.Nome = “Israel Aéce”
 
Código 2 – Acessando as propriedades do Profile.

Como já podemos ver na Figura 1 logo abaixo, o Intellisense já passa a interpretar as propriedades que definimos no Web.Config:

Figura 1 – Intellisense já reconhecendo as propriedades definidas no Web.Config.

Repare que o tipo do dado não é definido. Isso porque o default, quando não informamos é System.String. Caso queria definir um tipo Inteiro ou qualquer outro tipo, tem o atributo type, onde define-se o tipo de dado que queira para aquela propriedade.

Profile Groups

Quando começamos a criar várias propriedades dentro do arquivo Web.Config para utilizarmos no Profile, começamos a ter a necessidade de separar algumas delas em grupos para que facilite a compreensão e organização. Para isso temos o elemento group, onde dentro dele colocamos as propriedades pertinentes ao mesmo. Se quisermos adicionar um grupo chamando Endereco, contendo Rua, Cidade, Estado, fazemos:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
 
<configuration>
    <system.web>
        <authentication mode=”Forms” />
        <anonymousIdentification enabled=”true” />
 
        <profile>
            <properties>
                <add name=”Nome” defaultValue=”” allowAnonymous=”true” />
                <add name=”Email” defaultValue=”” allowAnonymous=”true” />
                <group name=”Endereco” />
                    <add name=”Rua” defaultValue=”” allowAnonymous=”true” />
                    <add name=”Cidade” defaultValue=”” allowAnonymous=”true” />
                </group>
            </properties>
        </profile>
    </system.web>
</configuration>
 
Código 3 – Definindo grupos do Profile no arquivo Web.Config.

E para acessá-los, o código fica:

1
2
3
4
 
Profile.Email = “israel@projetando.net”
Profile.Nome = “Israel Aéce”
Profile.Endereco.Rua = “Magnólias, das”
Profile.Endereco.Cidade = “Valinhos”
 
Código 4 – Acessando as propriedades com grupos do Profile.

Tipos Complexos

Muitas vezes, criamos objetos complexos que encapsulam alguma lógica e funcionalidades específicas. Um exemplo prático disso é quando criamos um objeto do tipo Carrinho de Compras para aplicações de comércio eletrônico. Neste caso, o ideal é colocarmos este tipo de objeto no Profile e assim fazer o uso destas funcionalidades. O Profile permite definir estes tipos complexos para ser gerenciado/armazenado, como por exemplo uma classe.

Para exemplificar como isso funciona realmente, criaremos uma classe (que é um tipo complexo) onde teremos um objeto que será o Carrinho de Compras do nosso Cliente. Esta classe armazenará os produtos que o cliente desejar incluir no Carrinho além de nos retornar o valor total do produtos e claro, podemos dar muito mais funcionalidades a ela se desejarmos. Primeiramente o código da classe que contém as propriedades para a estrutura do Produto:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
 
<Serializable()> Public Class Produto
 
    Private _nome As String
    Private _valor As Double
    Private _qtde As Integer
 
    Public Property Nome() As String
        Get
            Return Me._nome
        End Get
        Set(ByVal value As String)
            Me._nome = value
        End Set
    End Property
 
    Public Property Valor() As Double
        Get
            Return Me._valor
        End Get
        Set(ByVal value As Double)
            Me._valor = value
        End Set
    End Property
 
    Public Property Qtde() As Integer
        Get
            Return Me._qtde
        End Get
        Set(ByVal value As Integer)
            Me._qtde = value
        End Set
    End Property
 
End Class
 
Código 5 – Classe Produto.

Depois de criada a classe que tem a estrutura do nosso Produto, criaremos a seguir a classe que conterá as funcionalidades do nosso Carrinho de Compras, incluindo dentro dela também a capacidade de armazenar os produtos que são adicionados ao carrinho pelo usuário:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
 
<Serializable()> Public Class CarrinhoCompras
 
    Private _items As New Generic.List(Of Produto)
 
    Public ReadOnly Property Total() As Double
        Get
            Dim tot As Double
            For Each prod As Produto In _items
                tot += prod.Valor * prod.Qtde
            Next
            Return tot
        End Get
    End Property
 
    Public ReadOnly Property Produtos() As Generic.List(Of Produto)
        Get
            Return Me._items
        End Get
    End Property
 
    Public Sub AdicionarProduto(ByVal p As Produto)
        Me._items.Add(p)
    End Sub
 
End Class
 
Código 6 – Carrinho de Compras.

Como podemos ver, é um código simples, onde temos um método chamado AdicionarProduto que é responsável por adicionar um novo produto a coleção de produtos. Uma propriedade que nos retorna o valor total dos Produtos e outra para resgatar a coleção de produtos que o usuário selecionou.

O que é importante reparar é que as duas classes necessitam do atributo Serializable, que se faz necessário para o armazenamento/persistência do objeto pelo Profile.

Da mesma forma que criamos, um pouco mais acima, as simples propriedades e seus grupos no arquivo Web.Config, devemos fazer o mesmo neste caso de tipos complexos. A forma com qual é declarada muda um pouco, tendo que definir alguns atributos como é mostrado abaixo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 
<configuration>
    <system.web>
        <authentication mode=”Forms” />
 
        <anonymousIdentification enabled=”true” />
 
        <profile>
            <properties>
                <add
                        name=”CarrinhoCompras”
                        type=”CarrinhoCompras”
                        serializeAs=”Binary”
                        allowAnonymous=”true” />
            </properties>
        </profile>
    </system.web>
</configuration>
 
Código 7 – Definindo tipos complexos no Web.Config.

O que notamos de novidade na declaração de tipos complexos no Web.Config é o elemento serializeAs que define qual o tipo de serialização do objeto. Feito isso, agora já poderemos utilizar o nosso Carrinho de Compras pelo site, apenas tendo o cuidado de, quando for utilizá-lo pela primeira vez, instanciá-lo para poder inicializá-lo. Veremos abaixo um simples exemplo de como adicionar novos produtos ao Carrinho e como exibir o seu conteúdo em um controle ASP.NET qualquer:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 
Sub Button1_Click(ByVal sender As Object, ByVal e As System.EventArgs)
    If (Profile.CarrinhoCompras Is Nothing) Then
        Profile.CarrinhoCompras = New CarrinhoCompras
    End If
 
    Dim p As New Produto
    p.Nome = “Produto 1”
    p.Qtde = 2
    p.Valor = 2.9
    Profile.CarrinhoCompras.AdicionarProduto(p)
 
    Dim p1 As New Produto
    p1.Nome = “Produto 44”
    p1.Qtde = 3
    p1.Valor = 14
    Profile.CarrinhoCompras.AdicionarProduto(p1)
 
    Me.Label1.Text = “Total: ” & Profile.CarrinhoCompras.Total.ToString(“C2”)
    Me.GridView1.DataSource = Profile.CarrinhoCompras.Produtos
    Me.GridView1.DataBind()
End Sub
 
Código 8 – Utilizando o Carrinho de Compras.

Como vemos, verificamos se existe alguma instância do nosso objeto CarrinhoCompras em nosso Profile. Em seguida, criamos dois produtos e adicionamos neste carrinho. Através da propriedade Produtos resgatamos todos os produtos que estão dentro do Carrinho e preenchemos nosso controle GridView e um Label com a propriedade Total, assim como é ilustrado na figura abaixo:

Figura 2 – Carrinho de Compras sendo exibido.

CONCLUSÃO: Vimos aqui a nova forma de armazenarmos objetos pertencentes à um determinado usuário que chamamos de Profile. Há ainda outras funcionalidades como por exemplo a configuração e utilização de outros Providers e também como gerenciar e exibir (relatórios) de Profiles. Funcionalidades quais veremos em um futuro artigo desta série para não ficar muito desgastante.