msdn09_capa.JPG

Clique aqui para ler todos os artigos desta edição

 

User Controls compartilhados via GAC
por Marcos Santos e Alexandre Santos

 

User Control é uma página ASP.NET logicamente convertida como um controle, ou seja, um componente. Uma das principais características dos user controls, é a capacidade de permitir a reutilização do mesmo conteúdo e da mesma lógica de programação em várias páginas de um mesmo projeto, além de poder ser manipulado como um objeto. Contudo, surgiu a necessidade de se reutilizar estes user controls em mais de uma aplicação ASP.NET, sendo o caminho mais conhecido, o de copiá-los para dentro da nova aplicação em questão. No entanto isso pode trazer problemas na hora da manutenção, ao precisarmos atualizar todas as réplicas existentes. Novas soluções foram aparecendo no intuito de fazer com que diferentes aplicações “enxergassem” a mesma cópia do user control, entretanto sempre sem a possibilidade da utilização do CodeBehind, fazendo com que o desenvolvedor perdesse as vantagens fornecidas por este conceito.

O objetivo deste artigo é mostrar a criação de um user control utilizando CodeBehind que possa ser compartilhado entre aplicações Web e permitir que o projeto consumidor descreva o comportamento dos eventos do user control. Para isto, será feita uma ligeira explanação sobre o GAC (Global Assembly Cache), que permite este compartilhamento.

Para aumentar a compreensão, simulemos que existam duas aplicações Web distintas que manipulam o mesmo banco de dados Northwind do SQL Server. A primeira seria uma aplicação de Relatórios (Report) e a outra de gerenciamento dos dados (Manager). Imagine que em ambas as aplicações exista a necessidade de um DataGrid com a listagem das Categorias, por exemplo, para a aplicação Report com uma opção de filtro de um relatório específico e na aplicação Manager um cadastro de Categorias. Seria interessante se criarmos apenas um único UserControl que faça o acesso ao banco de dados,  popule o DataGrid e ainda permita que a aplicação consumidora descreva o comportamento da ação ao clicar no botão que seleciona a categoria desejada pelo internauta.

Projeto

Crie um projeto ASP.NET Web Application chamado UserControlCompartilhado usando como linguagem o Visual C#. O Visual Studio .NET criou 4 arquivos, mas como este projeto conterá apenas o user control, algo semelhante a um componente, três destes arquivos devem ser apagados (Global.asax, Web.config e WebForm1.aspx) para que o mesmo não seja interpretado pelo serviço de Internet (IIS) como uma aplicação Web. Portanto, apenas o arquivo AssemblyInfo.cs permanecerá.

Insira um Web User Contol chamado ucGridCategorias.ascx. Note que a extensão do arquivo é .ascx, denotando ser um controle (componente) e que “roda” apenas quando adicionado num WebForm.aspx.

Através do menu Table / Insert / Table desenhe uma tabela com duas linhas, uma coluna e largura de 400 pixels. Para a primeira linha arraste um componente Label da ToolBox Web Forms, que será utilizado como informação ao internauta, com propriedades id = lbInformacao e Text em branco; Na segunda linha da tabela, arraste um componente DataGrid, com propriedades id = gridCategorias e AutoGenerateColumns = False.

Dica: Clique com o botão direito e selecione Auto Format para formatá-lo de acordo com a sua necessidade.

Como as colunas do DataGrid não serão montadas automaticamente é preciso informar quais colunas irão preenchê-lo. Para isso, pressione o botão direito no gridCategorias e selecione Property Builder. Na guia de Columns, localize a lista de Available Columns e selecione as seguintes colunas com as devidas propriedades:

 

Coluna

Header

Data Field

1 Button Column (tipo Select)

 

 

1 Bound Column

Código

CategoryID

1 Bound Column

Categoria

CategoryName

 

O Layout do User Control a ser utilizado está pronto, conforme Figura 1.

image002.jpg
Figura 1 – Layout do User Control em tempo de desenvolvimento.

 

O funcionamento ocorre da seguinte forma: na primeira vez que é carregada (!IsPostBack), é feita a verificação se há alguma informação (string) passada como parâmetro ao user control a ser mostrada ao internauta no Label lbInformação,  realiza a consulta ao banco de dados e preenche o DataGrid com os registros da tabela Categories. Posteriormente, quando o usuário clicar no botão Select será disparado o evento ItemCommand do DataGrid, e este por sua vez deverá invocar o evento da página consumidora, que terá descrito o comportamento desejado da ação do clique do botão. A vantagem deste formato é a maior reusabilidade, uma vez que se o comportamento fosse escrito uma única vez no próprio user control, em todas as páginas obter-se-ia sempre o mesmo resultado, independente de tipo de aplicação.

Pressione F7 para exibir a janela de códigos. Como usaremos o SqlServer, digite na lista Using o namespace:

 

using System.Data.SqlClient;

 

Para definir a string de conexão com o banco de dados digite na lista de Protected o respectivo código da sua conexão. Cabe ressaltar que esta conexão deve ser configurada de acordo com a instalação do SQL Server no servidor, podendo haver Password, segurança integrada, etc.

 

protected const string conexao = "database=northwind;server=(local);user id=sa";

 

Para permitir que cada página possa informar uma string de dados ao usuário, como um cabeçalho do user control, digite:

 

public string deInformacao = "";

 

Para expor o evento do clique do botão do DataGrid ao consumidor do user control, digite:

 

public event DataGridCommandEventHandler ItemCommandGridCategoria = null;

 

Note que através da linha de código acima, é possível à página .aspx consumidora informar qual o método da mesma que descreverá o comportamento do clique do botão do DataGrid do user control. O método em questão deverá ter todos os parâmetros e todos deverão ser do mesmo tipo declarados no delegate DataGridCommandEventHandler.

Digite o código da Listagem 1 no evento Page_Load para preencher o gridCategorias. Todos os códigos contidos no Page.IsPostBack serão executados apenas a primeira vez em que página for carregada.

 

Listagem 1 Preenchendo o gridCategorias

private void Page_Load(object sender, System.EventArgs e)

{

if (!Page.IsPostBack)

{

if (this.deInformacao != "")

//Inform. p/ internauta

lbInformacao.Text = this.deInformacao;

//método que preenche DataGrid

this.montaGrid();

}

}

 

Digite a rotina montaGrid (Listagem 2) que seleciona todas as categorias da tabela Categories e preenche

o DataGrid gridCategorias.

 

Listagem 2 Rotina montaGrid

//Preenche o DataGrid de Categorias//

private void montaGrid()

{

//cria conexão

SqlConnection conn = new SqlConnection(conexao);

//monta o sql

string sql = "Select CategoryID,CategoryName";

sql += " from Categories Order by CategoryName";

SqlDataAdapter da = new SqlDataAdapter(sql,conn);

//cria DataSet

//e o preenche com o resultado da consulta

DataSet ds = new DataSet();

da.Fill(ds,"Categorias");

 

//DataBind no DataGrid

gridCategorias.DataSource =ds.Tables["Categorias"];

gridCategorias.DataBind();

}

 

Até aqui os dados já estão sendo exibidos no gridCategorias, mas quando o Internauta quiser selecionar uma Categoria, ele precisa clicar no botão Select. Via programação, precisamos interceptar este evento e criar o código para que o user control invoque o método da página .aspx que o contém. Isto é possível através do seu atributo declarado anteriormente ItemCommandGridCategoria que contém a referência ao método. Portanto, selecione o gridCategorias, pressione F4 para exibir a janela de propriedades, clique no raio (Events), localize o evento ItemCommand, dê um duplo clique e digite o código da Listagem 3.

 

Listagem 3 Evento ItemCommand

//Evento qdo o usuário clica no botão "Select”//

private void gridCategorias_ItemCommand(object source,

System.Web.UI.WebControls.DataGridCommandEventArgs e)

{

gridCategorias.SelectedIndex = e.Item.ItemIndex;//marca Categ selecionada     

 

/*Se o consumidor do User Control relacionou um método, com a mesma assinatura que irá descrever o

comportamento deste evento (Clique do botão Select), o mesmo deve ser invocado. Possibilitando

maior reusabilidade do user control.*/

 

if (this.ItemCommandGridCategoria != null)

//Invoca método do consumidor

this.ItemCommandGridCategoria(source,e);

}

 

No método do projeto consumidor do user control, muito provavelmente será necessário saber informações como o código e a descrição da categoria selecionada. Uma forma de expor estes dados é através de métodos Property públicos. Digite o código da Listagem 4.

 

Listagem 4 métodos Property públicos

//Retorna o valor da célula correspondente ao item selecionado, caso haja//

public int CdCategoria

{

get

{

if (gridCategorias.SelectedIndex == -1)

return 0;

else

//1=indice da coluna

return Convert.ToInt32(gridCategorias.SelectedItem.Cells[1].Text);

}

}

//Retorna o valor da célula correspondente ao item selecionado, caso haja//

public string DescricaoCategoria

{

get

{

if (gridCategorias.SelectedIndex == -1)

return "";

else

//2=indice da coluna

return gridCategorias.SelectedItem.Cells[2].Text;

}

}

 

O User Control já estaria pronto, caso não fosse necessário compartilhá-lo entre as aplicações Report e Manager, porém para cumprir este requisito, será necessária uma explanação sobre o GAC.

 

GAC (Global Assembly Cache)

Nas metodologias de programação anteriores ao .NET, quando se desejava reutilizar componentes, entre aplicações, construía-se DLLs. Entretanto, as aplicações precisavam conhecer estas DLLs que iriam utilizar em tempo de compilação. Surgiu então o padrão COM (Component Object Model), ainda em uso, mas com a limitação da necessidade de que informações sobre os componentes sejam adicionadas ao registro do sistema Windows, tornando mais complexa a sua instalação e remoção. Para exemplificar, vamos supor um programa X utilizando um componente COM versão 1.0, caso o usuário instale um outro programa Y que utilize este mesmo componente, porém com versão 2.0, haverá uma grande possibilidade do programa X parar de funcionar por incompatibilidade de versão do componente utilizado.

Na plataforma .NET um programa executável ou parte dele (como um arquivo .dll) é visto como um assembly. O conceito de assembly surgiu para facilitar o controle de publicação de aplicações e resolver o problema de conflito de versões de componentes em aplicações que os utilizam. O conflito de versões aqui é resolvido pela utilização de um strong name, que é composto pelo nome e versão do Assembly e uma chave criptográfica. Um assemby será considerado o mesmo que um outro apenas quando o strong name for o mesmo, permitindo assim que diferentes versões do mesmo componente rodem simultaneamente na mesma máquina, chamada de execução side-by-side.

Basicamente, um Assembly consiste em quatro elementos: manifest, type metadata, MSIL e um conjunto de recursos. O único elemento obrigatório é o manifest, que contém a identidade (strong name) do Assembly, que descreve os arquivos utilizados e inclusive possíveis Assemblies referenciados.

Dica: Através do utilitário ildasm.exe é possível ver o conteúdo de um Assembly.

Pelo fato de um Assembly conter informações sobre os arquivos utilizados, dependências e dados de versão, entre outros, não é necessário incluir informações no registro do Windows. Em várias situações, é possível instalar uma aplicação .NET apenas copiando os arquivos para o computador desejado.

No ambiente .NET foi construído um diretório especial, localizado em WinNT\Assembly, conhecido como GAC (Global Assembly Cache), com a finalidade de ser um recipiente de assemblies compartilhados. Portanto, é onde você deve disponibilizar o seu assembly UserControlCompartilhado.dll, mas para tornar isto possível, é obrigatório que este seja relacionado a um strong name.

Assim será necessário criar a chave criptográfica que compõe o strong name, além do nome e versão através de um utilitário chamado sn.exe, normalmente localizado no diretório Bin da instalação do seu Framework .NET. Digite a seguinte linha de comando no prompt do DOS:

 

sn –k c:\sn\UserControlCompartilhado.snk

 

Dica: Ao invés do diretório “c:\sn\” pode ser utilizado outro diretório de sua preferência, porém este já deverá existir.

 

Criada a chave, precisamos informar ao projeto UserControlCompartilhado as únicas duas informações que formam o strong name e que ainda não lhe foram passadas: a versão e a chave. O arquivo AssemblyInfo.cs, tem a finalidade de definir propriedades do elemento manifest do assembly. Abra o arquivo AssemblyInfo.cs e altere as propriedades como segue:

 

[assembly: AssemblyVersion("1.0.0.0")]

[assembly: AssemblyKeyFile(@"c:\sn\UserControlCompartilhado.snk")]

 

Compile o projeto. Por fim, abra o prompt do DOS e vá até o diretório de saída do seu projeto, onde está o arquivo UserControlCompartilhado.dll gerado. Como consta na especificação, o caminho é: %HOMEPATH%\VSWebCache\\\obj\, onde %HOMEPATH% comumente é o Document and Settings\”Nome do seu usuário de login”. Digite a seguinte linha de comando que copiará o Assembly com o strong name para o GAC:

 

Gacutil /i UserControlCompartilhado.dll

 

Projeto consumidor

Uma vez publicado o User control no GAC, resta criar o projeto consumidor. Crie um novo projeto ASP.NET Web Application chamado Report com um Web Form chamado RelatorioExemplo.aspx.

Para possibilitar aqui a utilização do CodeBehind do código do user control criado será necessário adicionar a sua referência. No Solution Explorer expanda o projeto Report e clique com o botão direito em References e Add Reference. Clique em Browse e selecione o arquivo UserControlCompartilhado.dll no mesmo caminho descrito anteriormente (..\VSWebCache\..) e clique em OK.

Desta forma é feita a referência ao código do Assembly contido no GAC, conforme pode ser visto na Figura 2. Perceba na guia de propriedades, que a propriedade Copy Local é igual a False e que há um Strong Name vinculado (Figura 3). Isto indica que não haverá replicas do user control, somente referências à existente no GAC.

 

 

image003.png

Figura 2 – Referência ao UserControlCompartilhado

 

image006.jpg

Figura 3 – Propriedades da Referência ao UserControlCompartilhado

 

Como visto, o acesso ao GAC está feito, agora será necessário acessar o arquivo .ascx existente em um diretório diferente de um projeto também diferente. Isto é resolvido através do IIS. Dentro do diretório virtual da aplicação Web “Report” deverá ser criado um subdiretório virtual que aponte para o caminho físico do arquivo .ascx.

Abra o IIS, selecione o diretório virtual Report e clique com o botão direito em Novo / Diretório Virtual. Quando solicitada a informação digite os dados conforme a Tabela 1.

 

Tabela 1 Informações de configuração do subdiretório virtual

Informação

Digite

Alias

UserControls

Diretório

Localize o diretório onde está o arquivo ucGridCategorias.ascx

Permissões

Deixe checado “Somente Leitura”. Remova as outras opções

 

Veja na Figura 4 a configuração do Subdiretório virtual.

 

image008.jpg

Figura 4 – Configuração do diretório virtual

 

Como agora já se sabe o caminho para encontrar o arquivo ucGridCategorias.ascx do user control, de que forma o arquivo RelatorioExemplo.aspx do projeto consumidor irá reconhecer o assembly UserControlCompartilhado no momento de carregá-lo? Uma solução para o nível de projeto é adicionar a referência no arquivo Web.config. Mas como neste artigo o foco é uma solução para vários projetos, adicionaremos a referência no arquivo machine.config

Machine.config é um arquivo de configuração existente em todas as máquinas com o Framework .NET instalado e pode ser encontrado em \WinNT\Microsoft.net\FrameWork\\CONFIG. Abra este arquivo e localize a sessão e adicione a seguinte linha:

 

code01.jpg

 

O valor do seu atributo PublicKeyToken provavelmente deverá ser outro. Vá até a pasta WinNT\Assembly e veja as propriedades do UserControlCompartilhado.dll ou digite no prompt do DOS:

 

gacutil /l UserControlCompartilhado e o verifique.

 

Para utilizar um user control numa página .aspx deve-se utilizar a directiva @Register. Abra o arquivo RelatorioExemplo.aspx e no modo HTML adicione:

 

 

Onde TagPrefix é semelhante a um namespace, possibilitando que se registre diferentes user controls com o mesmo nome de classe (TagName) numa mesma página .aspx. Perceba que o caminho (atributo src) é dado através do subdiretório virtual criado anteriormente.

Cabe ressaltar que o projeto UserControlCompartilhado utiliza CodeBehind e no momento em que é carregado no projeto Report é feita uma tentativa de localizar a classe UserControlCompartilhado.ucGridCategorias, referenciada no HTML do arquivo .ascx. Isto só é possível porque o user control  está compartilhado no GAC e está na lista de assemblies do arquivo machine.config.

Para exemplificar a construção da aplicação consumidora, será feita uma tela simples onde será adicionado o user control. Quando o internauta clicar no botão contido no DataGrid, será invocado o método existente no projeto Report que irá popular três Labels com o código e a descrição da Categoria selecionada e uma string de exemplo.

Através do menu Table / Insert / Table desenhe uma tabela com duas linhas, uma coluna e largura de 500 pixels. Na primeira linha digite “aqui” e na segunda linha adicione mais três Labels, nomeados lbAcao, lbCategoria e lbDeCategoria, respectivamente.

Vá novamente ao código HTML e localize a string “aqui” e substitua por:

 

 

O atributo id fornece o nome do objeto e o atributo deInformacao é aquele que foi declarado público para possibilitar a passagem de alguma informação ao user control que será mostrada ao internauta.

Para que seja possível tratar o user control como um objeto dentro do CodeBehind da página .aspx deve ser adicionado as seguintes instruções na lista using e protected:

 

//na lista de Using

using UserControlCompartilhado;

//na lista de protected

protected ucGridCategorias ucGridCategorias1;

 

Note que ucGridCategorias é o nome da classe dentro do CodeBehind do aquivo ucGridCategorias.ascx.

A seguir deve ser criado o evento que irá interceptar e descrever o comportamento do evento do botão contido no DataGrid do UserControlCompartilhado, conforme consta na Listagem 5.

 

Listagem 5 Evento que intercepta e descreve o comportamento do evento do botão

/* Método que descreve o comportamento da ação do clique do botão dentro do user control*/

private void DataGridCategoria_ItemCommand(object source,

System.Web.UI.WebControls.DataGridCommandEventArgs e)

{

string strAcao = "Evento do Botão do \"User Control compartilhado via GAC\",";

strAcao += " utilizando CodeBehind, interceptado na aplicação consumidora!!!";

lbAcao.Text = strAcao;

lbCategoria.Text = "Código: "+ucGridCategorias1.CdCategoria.ToString();

lbDeCategoria.Text = "Categoria: "+

ucGridCategorias1.DescricaoCategoria.ToString();

}

        

Por último, é preciso informar ao objeto do UserControlCompartilhado que o método DataGridCategoria_ItemCommand é quem descreverá o comportamento do evento do botão que seleciona a categoria. É feita a relação no método InitializeComponent() já existente no arquivo RelatorioExemplo.aspx.cs, como segue:

 

private void InitializeComponent()

{

this.Load += new System.EventHandler(this.Page_Load);

//método que intercepta evendo do UserControl

this.ucGridCategorias1.ItemCommandGridCategoria +=

new DataGridCommandEventHandler(this.DataGridCategoria_ItemCommand);        

}

 

Execute a aplicação e veja o resultado na Figura 5.

 

image010.jpg

Figura 5 – Resultado aplicação Report.

 

Para desenvolver a outra aplicação Web, a Manager, basta repetir os passos desde a criação da aplicação Report, excluindo a alteração no arquivo machine.config que já está feita.

 

Conclusão

Através deste artigo, foi possível visualizar a importância dos User Control, através da sua capacidade de reutilização e componentização. O uso de atributos event fornece um excelente recurso de notificação das classes publicadoras às classes que as subscrevem. Pôde-se perceber também que ao se pensar em compartilhamento de Assemblies, estamos nos referindo ao GAC  e que um assembly deve estar fortemente nomeado (strong name).