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.

ASP.NET Profile – Parte 2

Como já foi dito anteriormente, os Profiles, por default, são armazenados em um banco do dados do tipo Microsoft Access dentro da pasta App_Data da aplicação. Haverá casos, onde temos a nosso dispor, uma base de dados SQL Server, que é infinitamente mais robusta que um simples arquivo MDB do Microsoft Access, e com isso podemos utilizá-la para que a aplicação grave neste servidor SQL Server as informações/dados referentes aos Profiles da mesma.

Para que isso seja possível, temos no ASP.NET o que chamamos de um Profile Provider, que permitirá você especificar onde as informações serão salvas, definindo isso em seu arquivo de configuração (Web.Config ou Machine.Config).

O ASP.NET nos fornece, já intrinsicamente, um provider/classe chamado SqlProfileProvider. Esta classe deriva de uma classe base chamada ProfileProvider, ficando assim flexível para quando você precisar criar seu próprio Profile Provider para um banco de dados específico, como por exemplo MySQL, Oracle, SyBase, etc.

Mas antes de utilizarmos o SqlProfileProvider temos que fazer algumas configurações fora da nossa aplicação, ou seja, rodarmos um aplicativo “*.exe” que vem juntamente com o .NET Framework 2.0, chamado aspnet_regsql, que você poderá encontrá-lo no seguinte path: WindowsMicrosoft.NETFramework[versao]. Este processo se faz necessário porque através dele, é que são geradas as Stored Procedures dentro do servidor SQL Server. Stored Procedures que são executadas pelo SqlProfileProvider para a manipulação dos dados dos Profiles.

Feito este processo, o que se tem a fazer agora é configurar o Provider, no nosso caso, o SqlProfileProvider no arquivo Web.Config da aplicação. Abaixo um trecho do código do arquivo Web.Config onde é configurado o SqlProfileProvider a nível da nossa aplicação (se desejar definir este Provider para todas as aplicações, poderá fazer esta configuração no arquivo Machine.Config):

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
 
<configuration>
    <system.web>
        <connectionStrings>
            <add name=”CNProfiles”
                connectionString=”Server=localhost;Trusted_Connection=true;database=DBProfiles” />
        </connectionStrings>
 
        <authentication mode=”Forms” />
        <anonymousIdentification enabled=”true” />
        <profile defaultProvider=”SQLProvider” />
 
        <providers>
            <add name=”SQLProvider”
                type=”System.Web.Profile.SqlProfileProvider”
                connectionStringName=”CNProfiles” />
        </providers>
 
        <profile>
            <properties>
                <add name=”Nome” defaultValue=”” allowAnonymous=”true” />
                <add name=”Email” defaultValue=”” allowAnonymous=”true” />
            </properties>
        </profile>
    </system.web>
</configuration>
 
Código 1 – Configurando o Profile no arquivo Web.Config.

Como vemos, dentro do elemento providers temos o nome e o tipo (a classe) que no nosso caso é a SqlProfileProvider que fará o trabalho para gerir os Profiles dentro do SQL Server. Temos também um atributo chamado connectionStringName onde indicamos qual é a string de conexão, que informará o servidor e o banco de dados que será utilizado. Caso você tenha customizado uma classe para um banco de dados qualquer, o tipo (type) mudará, onde voce deverá informar o tipo da classe criada, inclusive o Namespace.

Agora basta utilizar os Profiles normalmente, como foi explicado na primeira parte deste artigo, e os dados já serão gravados dentro do SQL Server especificado na Connection String do arquivo Web.Config.

Gerenciando Profiles – Relatórios

Temos uma classe chamada ProfileManager que nos permite gerenciar os Profiles da nossa aplicação. É através desta classe, que nos é fornecido uma série de métodos e propriedades estáticos para as tarefas mais comuns, como por exemplo excluir, exibir, recuperar Profiles, etc.

Com isso, podemos criar uma aplicação Console ou mesmo um Windows Service que gerencie os Profiles da nossa aplicação, colocando-lá todas as manipulações que desejamos fazer com os Profiles. Abaixo os métodos e propriedades da classe ProfileManager:

Membro Descrição
 DeleteInactiveProfiles Exclui os Profiles inativos, baseando-se em uma data que lhe é informada.
 DeleteProfile Exclui um Profile específico baseando-se no username.
 DeleteProfiles Exclui vários Profiles baseando-se em um Array de usernames.
 FindInactiveProfilesByUserName Retorna uma coleção de objetos do tipo ProfileInfo que estão inativos desde uma data e username específico.
 FindProfilesByUserName Retorna uma coleção de objetos do tipo ProfileInfo de acordo com um username específico.
 GetAllInactiveProfiles Retorna uma coleção de objetos do tipo ProfileInfo com Profiles inativos baseando-se em uma data.
 GetAllProfiles Retorna uma coleção de objetos do tipo ProfileInfo com todos os Profiles.
 GetNumberOfInactiveProfiles Retorna um número inteiro que indica a quantidade de Profiles inativos baseando-se em uma data específica.
 GetNumberOfProfiles Retorna um número inteiro que indica a quantidade de Profiles.
 ApplicationName Nome da aplicação que armazena o Profile.
 AutomaticSaveEnabled Indica que o Profile será salvo automaticamente no final da execução da página.
 Enabled Resgata um valor booleano que indica se o Profile está ou não ativo na aplicação.
 Provider Resgata o provider padrão da aplicação.
 Providers Resgata uma coleção de providers de uma aplicação.

Ainda temos um objeto bastante importante chamado ProfileInfo, que contém informações de um Profile específico. Veremos na tabela abaixo as propriedades desta classe:

Membro Descrição
 IsAnonymous Indica se é ou não um Profile anônimo.
 LastActivityDate Indica última data que o Profile foi acessado.
 LastUpdatedDate Indica última data que o Profile foi atualizado.
 Size Resgata o tamanho do Profile.
 UserName Resgata o username do Profile.

Abaixo um exemplo de como utilizar a classe ProfileManager dentro de uma aplicação qualquer:

1
2
3
4
 
Sub Page_Load()
    Me.GridView1.DataSource = ProfileManager.GetAllProfiles(ProfileAuthenticationOption.All)
    Me.GridView1.DataBind()
End Sub
 
Código 2 – Utilizando a classe ProfileManager.

Como o método GetAllProfiles retorna uma coleção de objetos do tipo ProfileInfo, podemos tranquilamente definí-lo como DataSource de um container de dados para exibi-lo para o usuário assim como é mostrado no código 2, logo acima.

CONCLUSÃO: Nesta situação, os providers e os Profiles são bastante úteis, onde é encapsulado uma porção de códigos para nos ajudar a gerenciar essas informações que temos em aplicações Web/ASP.NET. Claro que se quiser utilizar uma outra base de dados, não SQL Server, terá mesmo que customizar um Provider para esta, herdando da classe base ProfileProvider. E com o uso da classe ProfileManager, temos acesso e gerenciamento dos Profiles da nossa aplicação, evitando trabalhar diretamente com código SQL.

Validation Groups

O problema é quando pressionamos qualquer um destes botões, todos os validadores são disparados, impossibilitando o usuário de continuar o processo, obrigando-o a preencher todo o formulário para prosseguir.

Isso foi pensado melhor e resolvido na versão 2.0 do ASP.NET e a Microsoft incluio os chamados Validation Groups que é exatamente o tema deste artigo. Como podemos ver na Figura 1 logo abaixo, o problema que acontece na versão 1.x, todos os validators são disparados, mesmo os que supostamente não deveriam ocorrer.

Figura 1 – O problema dos validadores da versão 1.x.

Como podemos reparar, quando pressionamos qualquer um dos botões, o formulário como um todo é tratato, e todos os validadores onde a consistência falha, são disparados.

Agora os controles tem uma nova propriedade, chamada ValidationGroup que receberá uma string que identificará o “Grupo” a ser validado pelo botão que desejar, e assim, tratar independentemente “ilhas” de controles nos formulários. Abaixo veremos o código HTML que exemplifica o uso dos Validation Groups:

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
38
39
40
41
42
43
 
 
Nome 1:
<asp:TextBox
    id=”TextBox1″
    runat=”server”>
</asp:TextBox>
 
<asp:RequiredFieldValidator
    id=”RequiredFieldValidator1″
    runat=”server”
    ErrorMessage=”Digite o Nome 1″
    ControlToValidate=”TextBox1″
    SetFocusOnError=”True”
    ValidationGroup=”Grupo1″>Digite o Nome 1</asp:RequiredFieldValidator>
 
<asp:Button
    id=”Button1″
    runat=”server”
    Text=”Grupo 1″
    ValidationGroup=”Grupo1″>
</asp:Button>
 
<HR>
 
Nome 2:
<asp:TextBox
    id=”Textbox2″
    runat=”server”>
</asp:TextBox>
 
<asp:RequiredFieldValidator
    id=”Requiredfieldvalidator2″
    runat=”server”
    ErrorMessage=”Digite o Nome 2″
    ControlToValidate=”TextBox2″
    SetFocusOnError=”True”
    ValidationGroup=”Grupo2″>Digite o Nome 2</asp:RequiredFieldValidator>
 
<asp:Button
    id=”Button2″
    runat=”server”
    Text=”Grupo 2″
    ValidationGroup=”Grupo2″>
</asp:Button>
 
Código 1 – Definindo o ValidationGroup.

Podemos reparar que para “amarrar” os controles, inclusive os validadores à serem validados por um botão qualquer, definimos uma string que baseado nela, o botão apenas verificará a consistência dos validadores que também são do mesmo grupo.

Outra característica da versão 2.0 do ASP.NET, é a propriedade SetFocusOnError. Quando esta propriedade esta definida como True e a consistência desse validador falhar, o foco é direcionado automaticamente para o controle que está associado ao mesmo.

CONCLUSÃO: Os validadores são uma forma elegante de tratarmos formulários em aplicações ASP.NET evitando assim, códigos Javascript que seríamos obrigados a digitar para obter o mesmo resultado. Apesar disso, na versão 1.x tínhamos o problema que vimos acima, e que, nesta nova versão foi solucionado com a adição da propriedade ValidationGroup.

Compressão de Arquivos

Já agora nesta nova versão do .NET Framework, 2.0, temos um novo namespace, dentro do System.IO, chamado System.IO.Compression. Este namespace contém 2 classes chamadas DeflateStream e GZipStream. Elas tem uma interface semelhante, mas uma delas, a DeflateStream utiliza o algoritmo Deflate para comprimir e descomprimir os arquivos. Temos também um enumerador dentro deste namespace chamado CompressionMode, que indica o que será feito (Compressão ou Descompressão) com o Stream.

Para ilustrarmos isso, vamos fazer um teste utilizando a classe GZipStream, comprimindo um arquivo de 527 KB, analisando os códigos abaixo:

1
2
 
Imports System.IO
Imports System.IO.Compression
 
Código 1 – Importando os Namespaces necessários.

 

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
 
Public Sub Comprime(ByVal arquivo As String)
    Dim stream As FileStream = Nothing
    Try
        stream = New FileStream(arquivo, FileMode.Open, FileAccess.Read, FileShare.Read)
 
        Dim buffer(stream.Length – 1) As Byte
        Dim count As Integer = stream.Read(buffer, 0, buffer.Length)
        stream.Close()
 
        Dim ms As New MemoryStream()
        Dim zip As New GZipStream(ms, CompressionMode.Compress, True)
        zip.Write(buffer, 0, buffer.Length)
        zip.Close()
        WriteFile(“ArquivoCompact.gz”, ms.ToArray, ms.Length)
    Finally
        If Not (stream Is Nothing) Then
            stream.Close()
        End If
    End Try
End Sub
 
Public Sub Descomprime(ByVal arquivo As String)
    Dim stream As FileStream = Nothing
    Try
        stream = New FileStream(arquivo, FileMode.Open, FileAccess.Read, FileShare.Read)
        Dim ms As New MemoryStream(stream.Length)
        Dim unzip As New GZipStream(ms, CompressionMode.Decompress, True)
 
        Dim data(stream.Length – 1) As Byte
        stream.Read(data, 0, data.Length)
        ms.Write(data, 0, data.Length)
        ms.Position = 0
 
        Dim dataOutput() As Byte = ReadBytes(unzip)
        WriteFile(“ArquivoOriginal.exe”, dataOutput, dataOutput.Length)
        unzip.Close()
    Finally
        If Not (stream Is Nothing) Then
            stream.Close()
        End If
    End Try
End Sub
 
Public Function ReadBytes(ByVal s As Stream) As Byte()
    Dim buffer(10000) As Byte
    Dim bytesLidos As Integer = 0
    Using ms As New MemoryStream()
        Do
            bytesLidos = s.Read(buffer, 0, 10000)
            ms.Write(buffer, 0, bytesLidos)
        Loop Until bytesLidos <= 0
        Return ms.ToArray()
    End Using
End Function
 
Código 2 – Função que comprime de descomprime o arquivo informado.

Como podemos ver no código 2 logo acima, utilizamos a classe GZipStream tanto para comprimir quanto para descomprimir o arquivo do nosso exemplo. Para o nome do arquivo que é passado para a função “Comprime”, resgatamos o mesmo para um FileStream. Com isso, conseguimos manipulá-lo e passarmos para a classe de compressão fazer o trabalho e gerar o arquivo em menor tamanho. Com um objeto do tipo MemoryStream, recuperamos o conteúdo que é gerado pelo GZipStream e assim salvamos/criamos o arquivo com extensão “*.gz”, já comprimido.

O Método WriteFile tem apenas a finalidade de gerar os arquivos fisicamente no disco, dado um nome do arquivo e o conteúdo que será salvo para este. Abaixo o código deste método para análise:

1
2
3
4
5
6
7
8
 
Public Sub WriteFile(ByVal fileName As String, _
    ByVal data() As Byte, _
    ByVal count As Integer)
 
    Dim f As New FileStream(fileName, FileMode.Create, FileAccess.Write)
    f.Write(data, 0, count)
    f.Close()
End Sub
 
Código 3 – Método auxiliar que gera os arquivos fisicamente.

Agora já analisando a outra parte do código 2, ou melhor, o método “Descomprime”, ele faz basicamente da mesma forma que anteriormente, ou seja, carrega o arquivo comprimido para um FileStream e depois passa a instância de um MemoryStream para a classe fazer o trabalho de descompressão do arquivo informado. Um método auxiliar, chamado “ReadBytes”, é criado para recuperarmos o conteúdo extraído/original do arquivo através do GZipStream.

Depois de executar o código, o arquivo EXE de 527 KB, diminuiu para 225 KB, e com certeza é bem considerável.

Utilizando a Compreensão no ASP.NET

Mas claro que a única utilidade deste namespace não é somente para comprimir e descomprimir arquivos em disco. No ASP.NET, ele terá um papel bastante importante, ou seja, poderemos através dele, comprimirmos o conteúdo/output de páginas ASPX antes de enviar ao cliente (clientes que suportam este tipo de compressão), para termos uma melhor performance e consequentemente diminuimos o tráfego de dados na rede.

Para aqueles que utilizam IIS 6.0, pode ir até as propriedades da aplicação, na Tab “Service” e marcar a compressão de arquivos, já que a mesma, por padrão, vem desabilitada e assim não seria necessário nenhuma espécie de programação, pois o IIS cuidaria disso.

Mas há casos em que não temos o IIS 6.0 ou o acesso a ele. Isso ocorre quando temos a nossa aplicação hospedada em um host qualquer, e em quase 100% dos casos, o cliente não tem acesso ao IIS. Nesta situação é que o ASP.NET 2.0 + .NET Framework 2.0 nos ajudam, pois podemos comprimir os “arquivos” programaticamente através de HttpModules e fazendo também o uso das classes de compressão que vimos no início do artigo.

Chamo a atenção aqui que é extremamente necessário que o cliente tenha habilitado no seu browser o suporte ao HTTP 1.1, pois a compressão é uma “feature” disponibilizada nesta versão. Caso ela não esteja configurada, então o processo não funcionará. Inclusive você poderá identificar isso programaticamente no próprio ASP.NET, ou mesmo, através do Trace.

Para habilitar o suporte ao HTTP 1.1 no seu browser (Internet Explorer), que por padrão já vem habilitado, deve-se ir até o menu Tools (Ferramentas), Internet Options (Opções da Internet), tab Advanced (Avançado), e habilitar a opção “Use HTTP 1.1”.

Para o nosso teste, infelizmente não tenho no momento ferramentas de Monitoring (como por exemplo a Network Monitor Tools, disponibilizada no Windows 2003 server), justamente por estar desenvolvendo este artigo em Windows XP Professional. Apenas para exemplo, vamos verificar através do Trace da página qual dos tipos de compressão foi usado. Vale lembrar que isso não diminui o tamanho final do output da página, ou seja, a finalidade é que o servidor compacta o retorno e envia ao cliente, e este, por sua vez, recebe este conteúdo e automaticamente descompacta e exibe ao usuário e por esta questão é que temos que ter o suporte ao HTTP 1.1.

O projeto constituirá em um Class Library que criaremos uma classe que implementará a interface IHttpModule. Esse processo é bem semelhante o que já fazemos atualmente, ou seja, no evento Init anexamos o procedimento que será disparado quando o evento BeginRequest for invocado. A diferença está na codificação deste procedimento, onde será nele que verificaremos se a requisição que solicitou a página suporta a compressão e assim compactamos o retorno de acordo com os algoritmos suportados pelos browser. Veremos abaixo o componente já implementado:

1
2
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
 
Imports Microsoft.VisualBasic
Imports System.IO
Imports System.IO.Compression
Imports System.Web
 
Public Class Compress
 
    Implements IHttpModule
 
    Public Sub Dispose() Implements System.Web.IHttpModule.Dispose
    End Sub
 
    Public Sub Init(ByVal context As HttpApplication) Implements System.Web.IHttpModule.Init
        AddHandler context.BeginRequest, AddressOf BeginRequest
    End Sub
 
    Private Sub BeginRequest(ByVal sender As Object, ByVal e As EventArgs)
        Dim app As HttpApplication = DirectCast(sender, HttpApplication)
        Dim headers As String = app.Request.Headers.Get(“Accept-Encoding”)
        If Not headers = String.Empty Then
            Dim stream As Stream = app.Response.Filter
            If headers.ToUpper.IndexOf(“GZIP”) > -1 Then
                app.Response.Filter = New GZipStream(stream, CompressionMode.Compress)
                app.Response.AppendHeader(“Content-Encoding”, “gzip”)
                app.Context.Trace.Warn(“Compressao via GZIP”)
            ElseIf headers.ToUpper.IndexOf(“DEFLATE”) > -1 Then
                app.Response.Filter = New DeflateStream(stream, CompressionMode.Compress)
                app.Response.AppendHeader(“Content-Encoding”, “deflate”)
                app.Context.Trace.Warn(“Compressao via DeflateStream”)
            End If
        End If
    End Sub
 
End Class
 
Código 4 – Implementando a interface IHttpModule para compreesão.

Analisando o código acima, no procedimento BeginRequest recuperamos a instância do objeto HttpApplication e com este, fazemos as devidas consistências, recuperamos e definimos o tipo de retorno. Através do método Get que é fornecida pela propriedade Headers (que contém o cabeçalho da requisição HTTP), recuperamos o Accept-Encoding, que nos informará se o cliente tem ou não suporte aos tipos de compactação do .NET (GZipStream e DeflateStream).

Baseado neste valor, criamos a instância do “compactador” que será utilizado, e com isso, atribuímos ao construtor da classe responsável pela compactação o Stream que é devolvido para o cliente, justamente para internamente, ela criar a versão compactada desta página e devolver para o cliente.

Nesta altura o componente já está criado e com a DLL gerada. Temos agora que adicionar a referência ao nosso projeto ASP.NET Application. Depois de feita a referência, temos que, no arquivo Web.Config desta aplicação ASP.NET, adicionar o HttpModule para que ele possa ser executado nas requisições das páginas.

No exemplo deste artigo, o projeto Class Library foi chamado de “Component” e a classe responsável pela compressão de “Compress”. Então adicionamos este módulo no arquivo Web.Config da aplicação, assim como já fazíamos anteriormente, ou seja, dentro da seção “<system.web></system.web>”, como é mostrado abaixo:

1
2
3
 
<httpModules>
    <add name=”Compress” type=”Component.Compress, Component”/>
</httpModules>
 
Código 5 – Configurando o Módulo no arquivo Web.Config.

Depois disso, ao rodar a aplicação a compactação passa a ser realizada pelo ASP.NET, assim como podemos ver na Figura 1 logo abaixo onde o Trace nos aponta qual dos “compactadores” foi utilizado.

Figura 1 – Verificando qual compactador foi utilizado.

CONCLUSÃO: Através destas classes agora disponíveis nesta nova versão do .NET Framework, podemos criar diversas funcionalidades para as nossas aplicações, sem a necessidade de adquirir um componente de terceiros. Este novo namespace, System.IO.Compression fornece todo o suporte necessário para atingir os objetivos quando se trata de compressão e descompressão de arquivos em .NET.

Nova arquitetura de Segurança

Ao contrário do ASP.NET 1.x, o ASP.NET 2.0 nos fornece uma grande infraestrutura, incluindo classes e controles, para trabalharmos com segurança e gerencimento de usuários das nossas aplicações Web. Nas versões anteriores, tínhamos que construir nossas próprias classes para gerir os usuários da nossa aplicação em nossa Base de Dados ou mesmo no Active Directory e, sendo assim, tínhamos que escrever muito mais códigos para atingirmos este nosso objetivo, isso ainda sem levar em consideração a parte de designer (ASPX).

Na versão 2.0 do ASP.NET, com a grande gama de controles a nosso dispor para melhor trabalharmos o front-end (UI), temos ainda classes para manipular os usuários em nossa Base de Dados. Classes quais utilizam o Provider Model Design Pattern, e assim conseguímos ter um código genérico, idenpendentemente de que Banco de Dados (ou qualquer outro tipo de repositório) estamos utilizando em nossa aplicação.

A Infraestrutura

Assim como o Profile, o ASP.NET também utiliza o Providers para gerenciar a segurança, sendo eles, dois providers: MembershipProvider e o RoleProvider. O MembershipProvider é utilizado para gerir os usuários (armazenando, criando, excluindo e alterando) e passwords. O RoleProvider é utilizado para gerenciar as Roles (Regras) dos usuários dentro da aplicação.

Para versão Beta 1, o Provider padrão é o AccessMembershipProvider. Como o Microsoft Access não suporta um grande volume de usuários “pendurados”, ele foi substituído pelo SqlMembershipProvider na versão Beta 2 do Visual Studio .NET 2005, e como o próprio nome diz, é voltado para o Banco de Dados SQL Server. Se ainda há alguém que pretende utilizar o AccessMembershipProvider, é possível encontrá-lo neste endereço.

Os dois providers são descendentes de uma mesma classe abstrata, chamada MemberShipProvider, deixando assim, flexível para extender a funcionalidade para outros tipos de Banco de Dados, como por exemplo Oracle, Sybase, ou até mesmo arquivos XML, bastando apenas herdar da classe base, MemberShipProvider, e implementar os métodos e propriedades para a Base de Dados que deseja utilizar.

Para utilizar o SqlMembershipProvider, é necessário termos dentro do SQL Server os objetos (Tabelas e Stored Procedures) necessários para que seja possível o uso deste Provider. Junto ao Microsoft .NET que é instalado na máquina, existe uma ferramenta chamada “aspnet_regsql.exe”, que faz todo este processo automaticamente, onde apenas precisamos informar o servidor e o Banco de Dados onde serão instalados estes objetos.

Depois do provider escolhido, ainda é necessário definirmos no arquivo Web.Config qual será o provider a ser utilizado. Abaixo um exemplo de como definí-lo:

1
2
3
4
5
 
<configuration>
    <system.web>
        <membership defaultProvider=”AspNetSqlProvider” />
    </system.web>
</configuration>
 
Código 1 – Definindo a estrutura do Provider de segurança no arquivo Web.Config.

Como vemos, no atributo defaultProvider definimos o nome do provider que utilizaremos na aplicação. Este provider está definido dentro da coleção de providers que podemos também, definir dentro do arquivo de configuração (Machine.Config ou Web.Config) os providers que podemos ou não utilizar.

Quando criamos o nosso próprio provider, precisamos registrá-lo na coleção de providers e assim, definimos uma séria de atributos, sendo um deles o type, que informará a classe que será utilizada. Nada impedirá de ter mais de um provider definido, mas obrigatoriamente, no atributo defaultProvider, devemos especificar o provider que iremos utilizar na aplicação. Abaixo temos uma lista completa:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 
<configuration>
    <system.web>
        <membership defaultProvider=”AspNetSqlProvider”>
            <providers>
                <add name=”AspNetSqlProvider”
                    type=”System.Web.Security.SqlMembershipProvider”
                    connectionStringName=”Connection_String”
                    enablePasswordRetrieval=”false”
                    enablePasswordReset=”true”
                    requiresQuestionAndAnswer=”true”
                    passwordFormat=”Hashed”
                    applicationName=”/” />
            </providers>
        </membership>
    </system.web>
</configuration>
 
Código 2 – Configurando os providers.

 

Atributo Descrição
name Nome do Provider.
type Tipo do objeto, incluindo o Namespace.
connectionStringName Connection String com a Base de Dados.
enablePasswordRetrieval Possibilita ou não recuperar o password de um usuário.
enablePasswordReset Possibilita ou não que usuários possam reiniciar seu password com um outro aleatório.
requiresQuestionAndAnswer Indica se o usuário deverá ou não responder a questão antes de recuperar ou reiniciar o seu password.
passwordFormat Opções: Clear, Encrypted e Hashed.
applicationName Nome da Aplicação. Utilizado quando você precisa isolar os usuários em aplicações diferentes, utilizando esta mesma estrutura.

Nota: É importante mencionar que dentro do .NET Framework, também já temos implementada a classe ActiveDirectoryMembershipProvider, que faz o mesmo trabalho, mas agora, utilizando o Active Directory como repositório.

Os Controles

O ASP.NET 2.0 contém um conjunto completo de controles para trabalharmos com a segurança, contendo inclusive uma Tab chamada Security dentro da ToolBox do Visual Studio .NET 2005 para armazenar estes controles. Vamos neste artigo, analisar superficialmente alguns destes novos controles, como por exemplo o ChangePassword, CreateUser, LogIn, LoginView e o PasswordRecovery. Estes controles também tem a grande vantagem de trabalharem diretamente com o Provider que definimos no arquivo Web.Config, e assim, faz todo o processo, inclusive as queries necessárias para manipular os dados na Base de Dados.

ChangePassword

Figura 1 – Controle que permite a mudança de password.

O controle ChangePassword tem a finalidade de alterar o password de um determinado usuário, onde temos propriedades para configurar, como por exemplo a propriedade MailDefinition que permite você definir um email para quando o usuário alterar o password, uma mensagem é enviada a ele informando a mudança e o seu novo password.

CreateUser

Figura 2 – Controle que permite a criação de novos usuários.

Este controle, do tipo Wizard, permite a criação de novos usuários dentro na Base de Dados, permitindo também o envio de um email de confirmação ao usuário que foi cadastrado. Este controle também possui uma propriedade chamada AutoGeneratePassword, que é responsável por gerar um password randômico para o novo usuário.

LogIn

Figura 3 – Controle para efetuar o Login.

Como podemos perceber, este controle, dado um nome de usuário e password, possibilita-nos a verificar se um determinado usuário encontra-se ou não cadastrado em nossa Base de Dados. Temos também uma propriedade interessante neste controle, chamada VisibleWhenLoggedIn. Esta por sua vez, recebe um valor booleano, indicando se este controle será visível ou não quando o usuário já estiver logado na aplicação.

LoginView

Figura 4 – Controle LoginView.

Particularmente um dos controles mais interessantes. Ele exibe vários templates para diferentes situações (chamamos isso de ContentTemplate), dependendo de Roles de usuários e também tendo opção para definirmos o que vamos exibir quando o usuário ainda não está autenticado em nossa aplicação. Um cenário bastante utilizado, é quando temos áreas dentro da nossa aplicação, onde este conteúdo varia de acordo com o tipo de usuário, ou seja, quando o usuário é do tipo “Gerente”, exibe os valores das operações. Já quando o usuário é do tipo “Operador”, a quantidade de pendências é exibida.

Outro cenário muito comum, é quando o usuário ainda não encontra-se autenticado, e assim exibimos a seguinte mensagem: “Seja bem vindo ao nosso site.”. Quando o usuário se identifica, efetuando o Login, passamos a exibir uma mensagem mais customizada: “Olá José, seja bem vindo.”.

PasswordRecovery

Figura 5 – Controle PasswordRecovery.

Finalmente o controle PasswordRecovery. Este controle é responsável por enviar o password ao usuário. Mas isso terá um comportamento diferente, dependendo de algumas opções que definimos na configuração do provider. Se o atributo enablePasswordRetrieval estiver definido como True e o atributo passwordFormat não for Hashed, então o password do usuário é enviado em “clean text” para o usuário, caso contrário, é gerado automaticamente um novo password aleatório e posteriormente enviado ao respectivo usuário.

A classe Membership

Como já vimos anterioramente, está é uma classe abstrata, contendo uma série de métodos compartilhados. Esta classe recebe em runtime a instância da classe concreta, ou seja, do provider que definimos o arquivo Web.Config. Claro que estes métodos serão invocados da classe concreta, pois é onde se encontram os métodos implementados para uma Base de Dados específica.

Abaixo é exibido uma lista com os principais métodos desta classe:

Membro Descrição
 CreateUser Cria um novo usuário.
 DeleteUser Exclui um usuário.
 FindUsersByEmail Resgata os usuários baseando-se em um endereço de email.
 FindUsersByName Resgata os usuários baseando-se em um userName.
 GeneratePassword Gera um password aleatório.
 GetAllUsers Retorna todos os usuários.
 GetNumberOfUsersOnline Retorna um número inteiro representando todos os usuários online.
 GetUser Resgata um determinado usuário baseando-se em um userName.
 GetUsernameByEmail Resgata um determinado usuário baseando-se em um endereço de email.
 UpdateUser Atualiza as informações de um determinado usuário.
 ValidateUser Retorna um valor booleano indicando se o usuário existe ou não.

Para utilizar esta API, não há a necessidade que criar a sua instância, por motivos que já vimos acima. Basta apenas chamar os métodos compartilhados da classe Membership e já estará usufruindo de todo o Membership, e também, já refletindo as alterações na Base de Dados. Abaixo alguns exemplos:

1
2
3
4
5
 
‘Criando usuário
Membership.CreateUser(“IsraelAece”, “123456”)
 
‘Excluindo usuário
Membership.DeleteUser(“IsraelAece”)
 
Código 3 – Utilizando o Provider na aplicação.

CONCLUSÃO: Os Providers Models vieram em hora certa, onde agora, podemos trabalhar mais facilmente com código genérico, deixando assim a aplicação bastante flexível. Já que contamos com este design também na parte de segurança do ASP.NET, isso evitará de escrevermos muitos códigos, assim como fazíamos nas versões anteriores. Estas funcionalidades já encontram-se encapsuladas dentro dos Providers e controles disponibilizados para a nossa utilização.