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)

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.

Transformando XML com XSLT

Quando temos um arquivo XML, podemos querer exibí-lo para o Usuário. Como seu formato é incompreensível para Usuários que não conhecem, pode-se estruturá-lo para exibir de uma forma mais amigável.

Para isso utilizamos XSLT para transformar o XML, mostrando assim ao Usuário algo compreensível, utilizando HTML para formatar e exibir estes valores.

Temos abaixo os arquivos XML e XSLT:

Arquivo XML (Categorias.xml):

 

Arquivo XSLT (Formatacao.xslt):

Vemos que utilizamos um for-each para percorrer os dados do XML baseando-se nas tags: “Dados/Registro”.

Agora em nosso arquivo ASPX, temos que criar a estrutura para resgatar o conteudo do Arquivo XML e carregar o arquivo XSLT, e assim aplicar este estilo no conteúdo do arquivo XML, e gerando o seu “output” em HTML, para que o browser possa compreender.

Temos que importar os seguintes Namespaces:

1
2
3
4
 
Imports System.IO
Imports System.Xml
Imports System.Xml.Xsl
Imports System.Text
 

Agora faremos essa tranformação no evento Page_Load do nosso WebForm. O código abaixo mostra como efetuar:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 
Private Sub Page_Load(ByVal sender As Object, ByVal e As _
    System.EventArgs) Handles MyBase.Load
 
    Dim arquivoXML As String = Server.MapPath(“Categorias.xml”)
    Dim arquivoXSLT As String = Server.MapPath(“Formatacao.xslt”)
    Dim xmlDOC As New XmlDocument
    Dim xsltDOC As New XslTransform
    Dim sb As New StringBuilder
    Dim sw As New StringWriter(sb)
 
    xmlDOC.Load(arquivoXML)
    xsltDOC.Load(arquivoXSLT)
    xsltDOC.Transform(xmlDOC, Nothing, sw, Nothing)
    Response.Write(sb.ToString)
End Sub
 

Criamos dois objetos, sendo um do tipo XmlDocument e o outro do tipo XslTransform e carregamos os respectivos arquivos em cada um desses objetos. Depois disso, chamamos o método “Transform”, que aplica o estilo do XSLT no XML e através de um objeto do tipo StringWriter recuperamos o “output” gerado e atribuímos à um StringBuilder, e este por sua vez armazena o conteúdo que por fim escrevemos na tela através do Response.Write.

Lendo XML de uma String

Em determinados momentos quando não temos um arquivo XML fisicamente criado, mas temos seu conteúdo, qual foi recuperado de algum método ou mesmo uma Query SQL em uma String e necessitamos carregar essa String (o conteúdo XML) em um DataSet, podemos utilizar a classe StringReader juntamente com o método ReadXml do Dataset para isso.

Esta classe está contida dentro do Namespace System.IO e implementa a classe TextReader para é utilizada para ler uma sequencia de caracteres.

Temos abaixo a estrutura do conteúdo XML que iremos carregar no nosso Dataset e posteriormente atribuí-lo à um DataGrid: 

Agora veremos o código necessário para ler a String e carregarmos no Dataset. Temos que importar o seguinte Namespace:

1
 
Imports System.IO
 

Dentro do evento Load do nosso WebForm fazermos:

1
2
3
4
5
6
7
8
9
10
11
12
13
 
Private Sub Page_Load(ByVal sender As Object, ByVal e As _
    System.EventArgs) Handles MyBase.Load
 
    Dim xml As String
    Dim ds As New Dataset
 
    xml = “<Pessoal><Funcionario><ID>00011</ID><Nome>Jose</Nome></Funcionario></Pessoal>”
 
    ds.ReadXml(New StringReader(xml), XmlReadMode.Auto)
 
    Me.DataGrid1.DataSource = ds
    Me.DataGrid1.DataBind()
End Sub
 

Chamo a atenção para o conteúdo atribuído à variável xml, que uma parte foi ocultada por questões de espaço. Através do método ReadXml do Dataset, passamos uma instância da classe StringReader que recebe no seu construtor a String XML que queremos ler. Informamos também no segundo parâmetro o XmlReadMode, que especifica como os dados em XML são lidos dentro do Dataset, e por fim, definimos a propriedade DataSource do DataGrid com o nosso Dataset já carregado e invocamos o método DataBind do DataGrid para carregá-lo.

Escrevendo Imagens através de Stream

Algumas vezes temos um Stream que contém dados para imprimirmos na tela para o usuário. Um cenário muito usado é quando por exemplo precisamos exibir ao usuário um avatar, ou mesmo uma imagem que está armazenada em uma DB qualquer e para não interferir o processamento da página corrente, criamos uma segunda página, esta responsável por recuperar o arquivo e carregar em um objeto do tipo FileStream, e assim, percorrer este objeto e imprimi-lo no browser.

A classe FileStream está contida dentro do Namespace System.IO, qual temos que importá-lo para a utilização desta classe sem a necessidade de digitarmos o “path” completo da mesma. Apenas como exemplo, utilizaremos um arquivo que está no disco para exibição.

Em primeiro lugar, importamos o Namespace System.IO:

1
 
Imports System.IO
 

Abaixo o código no evento Page_Load do WebForm, que carrega a imagem para o Stream e en seguida, exibimos a imagem no browser:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
 
Private Sub Page_Load(ByVal sender As Object, ByVal e As _
    System.EventArgs) Handles MyBase.Load
 
    Response.Clear()
    Dim fs As FileStream
    Dim b(1024) As Byte
    Try
        fs = File.Open(Server.MapPath(“Images/Win2003.jpg”), _
            FileMode.Open, FileShare.Read, FileShare.None)
 
        While (fs.Read(b, 0, b.Length) > 0)
            Response.BinaryWrite(b)
        End While
    Finally
        If Not (fs Is Nothing) Then
            fs.Close()
        End If
    End Try
    Response.End()
End Sub
 

Analisando o código acima, através do método Open da Classe File (que também está contida no Namespace System.IO), que nos retorna um objeto do tipo FileStream, atribuímos o seu retorno à um objeto FileStream. Depois disso, percorremos o nosso Stream enquanto o seu conteúdo seja maior que 0, pois através do método Read lemos um bloco de bytes do Stream e escrevemos esses dados em um determinado buffer. E por fim, chamamos o método Response.End() para encerrar a execução, já que a página foi criada apenas para esta finalidade.

CollectionBase

É muito comum em runtime retornarmos valores da Base de Dados e armazenarmos em DataSets, mas com isso ocorre o “Weakly Typed” e o “Late Binding”, ou seja, não temos a segurança de tipos e o acesso à suas propriedades somente ocorre durante o tempo de execução.

Tendo esse problema, o que podemos fazer para termos nossos próprios objetos com suas respectivas propriedades e métodos ao invés de uma genérica? Então neste artigo explicarei como podemos criar uma coleção customizada, onde utilizaremos o CollectionBase.

Esta classe é fornecida através do Namespace System.Collections, criada justamente para essa finalidade. Criaremos uma coleção de Usuários e colocaremos dentro de um controle ASP.NET DataList.

O CollectionBase é uma classe onde podemos apenas herdá-la. Ela implementa três interfaces: IList, ICollection e IEnumerable que definem os métodos que aceitam tipos de System.Object. Além disso, ela ainda encapsula um ArrayList, onde ficarão armazenados os elementos da coleção.

Vamos nos dedicar aos seguintes métodos: Add, Contains, Insert, Item e Remove. Explicando cada método:


Function Add
Retorno: Integer
Adiciona um novo Item na Coleção e retorna o indíce (posição) que o objeto foi adicionado.

Function Contains
Retorno: Boolean
Verifica se já existe um determinado objeto dentro da Coleção e retorna um valor boleano indicando ou não sua existência.

Sub Insert
Insere um Item na Coleção em uma determinada posição.

Property Item
Retorna ou Recebe um objeto dado uma posição.

Sub Remove
Remove um objeto da Coleção.

Sub RemoveAt
Remove um objeto da Coleção dado uma posição.


Como dito anteriormente, vamos então criar nossa coleção customizada. Primeiramente devemos criar a nossa classe Usuario onde conterá as suas respectivas propriedades. Para isso, criarei uma WebApplication para criarmos e utilizarmos nossa coleção de exemplo. Depois de criado a WebApplication no Visual Studio .NET, adicione um novo arquivo *.vb (Class) e nomeie para Usuario.vb. Nossa classe deverá ficar conforme a estrutura abaixo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
 
Public Class Usuario
 
    Private _nome As String
    Private _email As String
 
    Public Property Nome() As String
        Get
            Return Me._nome
        End Get
        Set(ByVal Value As String)
            Me._nome = Value
        End Get
    End Property
 
    Public Property Email() As String
        Get
            Return Me._email
        End Get
        Set(ByVal Value As String)
            Me._email = Value
        End Get
    End Property
 
End Class
 
Código 1 – A Classe Usuário e suas propriedades.

Como podemos ver acima, criamos a Classe Usuario com as seguintes propriedades: Nome e Email. Agora devemos criar uma nova Classe chamada UsuarioColecao que será a nossa Coleção de Usuários. Para isso adicionarei mais um arquivo *.vb no projeto com o nome: UsuarioColecao.vb. Aqui devemos herdar o System.Collections.CollectionBase e devemos escrever os métodos e propriedades (Add, Contains, Insert, Item e Remove) para trabalharmos apenas com objetos do tipo Usuario, garantindo assim a “Segurança de Tipos”. Veja o código abaixo:

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
29
29
30
 
Public Class UsuarioColecao
 
    Inherits System.Collections.CollectionBase
 
    Public Function Add(ByVal objUsuario As Usuario) As Integer
        Return MyBase.List.Add(objUsuario)
    End Function
 
    Public Function Contains(ByVal objUsuario As Usuario) As Boolean
        Return MyBase.List.Contains(objUsuario)
    End Function
 
    Public Sub Insert(ByVal index As Integer, ByVal objUsuario As Usuario)
        MyBase.List.Insert(index, objUsuario)
    End Sub
 
    Default Public Property Item(ByVal index As Integer) As Usuario
        Get
            Return CType(MyBase.List(index), Usuario)
        End Get
        Set(ByVal Value As Usuario)
            MyBase.List(index) = Value
        End Get
    End Property
 
    Public Sub Remove(ByVal objUsuario As Usuario)
        MyBase.List.Remove(objUsuario)
    End Sub
 
End Class
 
Código 2 – Coleção de Usuários.

Acima podemos ver na linha 3 que herdamos a System.Collections.CollectionBase na nossa classe UsuarioColecao. E para garantirmos a segurança de tipos da coleção, ou seja, para que todos os métodos trabalhem apenas com um determinado tipo de objeto (no nosso caso, Usuario), devemos especificar isso para cada método e propriedade. Para os fãs de coleções, uma das grandes inovações do Visual Studio .NET Whidbey serão as coleções genéricas (Generics). Para maiores informações sobre o Generics: http://download.microsoft.com/download/c/7/f/c7f7a575-79ac-4399-9535-3ed57bc292f2/generics.doc.

Bem, agora nossa Classe UsuarioColecao já está pronta para trabalhar/aceitar apenas objetos do tipo Usuario, criado anteriormente. Vamos então à um exemplo prático de como usá-las:

Figura 1 – Intellisense já reconhece as propriedades da nossa Classe Usuario.

Como podem ver na figura 1, criamos um objeto do tipo Usuario e na medida que vamos precisando dele, instanciamos o mesmo (usuario = New Usuario()). Depois de cada objeto criado e atribuído a eles seus respectivos valores, você deve adicioná-lo à coleção de Usuario, qual também já criamos e instanciamos no início do código. Abaixo o código completo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 
Dim usuario As Usuario
Dim usuarioColecao As New UsuarioColecao()
 
usuario = New Usuario()
With usuario
    .Email = “israel@projetando.net”
    .Nome = “Israel Aéce”
End With
usuarioColecao.Add(usuario)
 
usuario = New Usuario()
With usuario
    .Email = “teste@servidor.net”
    .Nome = “José Augusto”
End With
usuarioColecao.Add(usuario)
 
Código 3 – Coleção de Usuários.

Figura 2 – Aceitando apenas objetos do tipo Usuario.

A figura 2 nos mostra algo bem interessante, pois reparem a o método Add qual utilizamos para adicionar um novo objeto Usuario em nossa coleção. Ele só aceita um objeto do tipo Usuario. Se tentarmos colocar uma String por exemplo, ele dará erro de compilação.

Como vimos, para todos os métodos (Add, Contains, Insert, Item e Remove) eles apenas irão trabalhar com objetos do tipo Usuario. Como disse no inicio do artigo, vou explicar como popular um controle DataList através de uma Coleção.

Logo após de criar e popular os objetos conforme o código 3, apenas devemos atribuir a Coleção à propriedade DataSource do DataList, e também colocar no HTML as propriedades que deseja exibir. O código abaixo carrega o controle DataList no evento Page_Load do WebForm:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 
Dim usuario As Usuario
Dim usuarioColecao As New UsuarioColecao()
 
usuario = New Usuario()
With usuario
    .Email = “israel@projetando.net”
    .Nome = “Israel Aéce”
End With
usuarioColecao.Add(usuario)
 
usuario = New Usuario()
With usuario
    .Email = “teste@servidor.net”
    .Nome = “José Augusto”
End With
usuarioColecao.Add(usuario)
 
With Me.dtlColecao
    .DataSource = usuarioColecao
    .DataBind()
End With
 
Código 4 – Carregando o controle DataList (Linha 18 à 21).

Ainda temos que colocar os campos que desejamos exibir no HTML. Para isso você deve utilizar as áreas ItemTemplate e AlternatingItemTemplate. No código abaixo estou exibindo as duas propriedades (Nome e Email) do objeto Usuario:

1
2
3
4
5
6
7
8
9
10
 
<asp:DataList…>
    <ItemTemplate>
        Nome: <%# DataBinder.Eval(Container.DataItem, “Nome”)%><br>
        Email: <%# DataBinder.Eval(Container.DataItem, “Email”)%>
    </ItemTemplate>
    <AlternatingItemTemplate>
        Nome: <%# DataBinder.Eval(Container.DataItem, “Nome”)%><br>
        Email: <%# DataBinder.Eval(Container.DataItem, “Email”)%>
    </AlternatingItemTemplate>
</asp:DataList>
 
Código 5 – Código HTML do DataList com as propriedades do objeto Usuario.

Como podem ver abaixo, o controle DataList carregado com os dados e exibindo as propriedades do objeto Usuario:

Figura 3DataList já carregado.

E quando utilizamos DataGrid não há problemas com relação à paginação como acontece com o DataReader. E também quando for definir as colunas de um DataGrid, ao invés de no campo Data Field do Property Builder, você informar o nome do campo da Base de Dados, você apenas atribui à ele o nome da propriedade que deseja exibir.

E ainda você pode optar por escrever os itens da coleção. Para isso basta executar um laço For…Next e percorrer a coleção e escrever os seus itens. O código abaixo explica como fazer isso:

1
2
3
4
5
 
Dim i As Integer
For i = 0 To usuarioColecao.Count – 1
    Response.Write(“Nome: ” & usuarioColecao.Item(i).Nome & “<br>”)
    Response.Write(“Email: ” & usuarioColecao.Item(i).Email & “<br><br>”)
Next
 
Código 6 – Percorrendo e escrevendo os itens da Coleção.

E o resultado ficará algo como:

Figura 4Escrevendo os Itens da Coleção.

Conclusão: Neste artigo vimos como é simples construirmos Coleções customizadas derivadas do System.Collections.CollectionBase. Apesar de que para cada objeto, temos que ter uma Coleção específica, onde temos que reescrever cada método para que aceite um determinado objeto. Mas com o Generics as coisas ficaram mais fáceis, mas de qualquer forma, vimos a facilidade de escrever Coleções customizadas e atribuirmos à um controle.

Console Application vs. Windows Services

Estamos prestes a automatizar um serviço de envio e recebimento de arquivos para um dos clientes da empresa onde trabalho. Essa automação consistirá em enviar um arquivo contendo uma relação de documentos para serem cadastrados na base de dados interna.

Existe um serviço de terceiro de faz o papel intermediário, ou seja, é um serviço (client (feito em Java)) que fica instalado em uma máquina qualquer e, dentro deste fazemos um agendamento de quanto em quanto tempo queremos que ele corra e assim, ele vai até um servidor, recupera o arquivo com a relação de documentos e deposita em um diretório local na máquina (em nossa empresa) em que o mesmo está instalado/configurado.

Meu papel entra agora, ou seja, teremos que criar algo para também de tempo em tempo ficar verificando se existe ou não um novo arquivo neste diretório e, caso existir, tenho que consistí-lo e consequentemente move-lo para uma outra máquina que irá processar este arquivo e fazer todas as entradas dentro da DB. Bem, não se tem muito segredo em como “ouvir” um determinado diretório do disco, ou seja, podemos, por exemplo, utilizar a classe FileSystemWatcher para alcançarmos isso.

A questão é como “hospedar” isso, ou seja, que tipo de aplicação desenvolver para alcançar essa funcionalidade: Windows Service ou uma Console Application? Com um Windows Service, poderia no evento OnStart deste iniciar o Watcher para “escutar” o diretório. Poderia também criar uma Thread que corra de X em X tempo executar; e quando ela executar, utilizaria o método GetFiles da classe Directory para verificar a existencia de arquivos.

É também perfeitamente possível criar uma Console Application que, também poderia utilizar o FileSystemWatcher para “escutar” o diretório e processar os arquivos.

A guerra entre estes dois tipos de aplicação é qual é melhor usar. Windows Services tem um ponto positivo, onde podemos definir para ele iniciar sozinho quando o sistema operacional “subir”, mas requer a instalação através do utilitário installutil.exe e também exige que o desenvolvedor conheça um pouco mais sobre essa tecnologia. Já as Console Applications não necessitam de uma instalação (claro, o .NET Framework deve existir onde vai correr a aplicação), mas seu ponto negativo é que necessitam de uma intervenção humana para inicializá-la.

Podemos de certa forma transformar uma Console Application com as mesmas características de um Windows Services, ou seja, no Iniciar do Windows poderia definí-la para inicializá-la automaticamente e como a mesma ficará “eternamente ouvindo” o diretório pré-definido, conseguiria alcançar o meu ideal. Além disso, ainda poderia utilizar o agendador do Windows para estipular quando e de quanto em quanto tempo quero que ele execute.

Pois bem, como essa automatização ainda estamos iniciando e estudando o processo, vai dar analisada para saber qual deles escolher e, também se alguém tiver alguma solução e/ou comentário, deixe-me saber por favor.

Extensibilidade vs. Melhor Performance

Este post se deve à um email que recebi de um colega me perguntando se utilizar Reflection para instanciar as classes de nosso sistema/projeto não perdemos em performance. Não cheguei a fazer testes precisos sobre isso, mas matérias que eu já li a respeito, dizem que instanciar os objetos via Reflection é mesmo mais lento que invocar diretamente o objeto.

Claro que se tivessemos já conhecimento de todas as variações que poderemos vir a ter, não precisaríamos utilizar Reflection, fazendo simplesmente um Select … Case pelas nossas opções e assim instanciar a classe concreta correta através de nosso Factory Method.

Já se utilizarmos o Reflection, o nosso Factory Method ficaria mais ou menos como:

     Public Shared Function Create() As IAccount
          Dim path As String = GetConfigDAL()
          Return DirectCast(Assembly.Load(path).CreateInstance(path  & “.Account”), IAccount)
     End Function

E no código que instancia o objeto:

     Dim account As IAccount = MinhaDALFactory.Account.Create()

Queria mesmo colher opiniões, procurando saber de vossa parte qual é o mais interessante, ou seja, optar por uma melhor performance ou extensibilidade?

Nota 1: Os exemplos foram baseados no Microsoft Pet Shop 3.0, que foi desenvolvimento como um case-study da  Microsoft.

Nota 2: A Microsoft, neste case-study, levou em consideração tanto performance quanto extensibilidade (utilizando Reflection), e nem por isso a performance foi degradada.