WebParts – Personalização e Provider

No decorrer do artigo vimos como configurar as WebParts, conectá-las, editá-las, etc.. Só que, até este momento, o principal gerenciador de tudo isso é o controle WebPartManager. Mas, até então, não sabemos como são mantidas as modificações entre as múltiplas sessões do usuário e, principalmente, como e quando esses processos acontecem. Neste capítulo veremos esses processos de “baixo-nível” um pouco mais detalhadamente, porém tudo isso já está encapsulado pela plataforma e não exige muita programação para configurá-lo. De qualquer forma, não deixa de ser interessante analisá-lo.

O primeiro conceito que temos que nos atentar se resume aos tipos de escopos de páginas possíveis: User e Shared. Veremos detalhadamente através da tabela abaixo os detalhes destes tipos de escopos:

Escopo Descrição
User

O escopo a nível de usuário significa que as mudanças e a personalização serão para um usuário específico.

Shared

O escopo compartilhado permitirá que todas as alterações feitas a nível de página sejam compartilhadas entre todos os usuários que a acessam.

Já o segundo conceito é a visibilidade dos controles. Isso irá determinar se um determinado controle está visível para um usuário ou para todos os usuários. Cada controle WebPart na página é um controle que é compartilhado, visível a todos os usuários que acessam a página; ou um controle per-user é visível somente a um usuário específico. A visibilidade é determinada de acordo com a forma que o controle é adicionado à página. Se o controle é adicionado via markup (diretamente no código HTML), ele sempre será um controle compartilhado. Se o controle é adicionado na aplicação via código ou se o usuário o seleciona em um catálogo (controles dinâmicos), a visibilidade é determinado pelo escopo corrente da personalização da página. Se a página estiver em escopo Shared, o controle dinamicamente criado será compartilhado; se a página estiver em escopo User, o controle será exclusivo daquele usuário que o criou.

O terceiro e último conceito é o escopo de propriedade. Quando você cria uma propriedade personalizada (denotada com o atribute Personalizable), você pode definir o escopo de personalização para a propriedade em questão para Shared ou User, sendo User o padrão. Isso fornecerá um controle detalhado sobre como podemos personalizar por todos os usuários e ainda podemos personalizar somente por usuários autorizados quando o escopo da página estiver definido como Shared.

Juntos, esses conceitos de escopo de personalização de página, visibilidade de controle e escopo de personalização de propriedade criam um conjunto de opções de como que os controles WebParts possam ser visualizados e personalizados pelos usuários. A tabela abaixo sumariza como estes controles se comportam quando os usuários personalizam esses controles em vários escopos:

Visibilidade de Controle Página em escopo compartilhado Página em escopo de usuário
Controle compartilhado

Um usuário autorizado pode personalizar propriedades em escopo User e Shared para todos os usuários.

No caso de controles adicionados dinamicamente, um usuário autorizado pode permanentemente fechar o controle para todos os usuários.

Já utilizando controles estáticos, eles não podem ser excluídos, embora possa ser fechado por um usuário autorizado para todos os usuários.

Usuários individuais não podem personalizar as propriedades definidas com o escopo compartilhado. Eles podem somente personalizar as propriedades definidas com o escopo de usuário.

Estes usuários podem fechar um controle compartilhado, que será adicionado à um Page Catalog, mas não poderá permanentemente excluí-lo.

Controle personalizado por usuário

Este controle não pode ser personalizado com a página em escopo compartilhado porque o controle não aparecerá toda vez na página, somente aparecerá quando a página estiver em escopo de usuário.

Usuários individualmente podem personalizar as propriedades dos controles em ambos escopos: User e Shared, porque a instância do controle é exclusiva/privada, podendo também excluir o controle permanentemente.

Depois de entendidos os conceitos, temos ainda dois componentes importantíssimos que possibilitam a personalização que são o WebPartManager e o WebPartPersonalization. O primeiro, WebPartManager, como já sabemos, gerencia todas as WebParts disponíveis na página, habilitando e gerenciando o ciclo de vida dos dados da personalização. Ele contém uma propriedade denominada Personalization, que expõe um objeto do tipo WebPartPersonalization, que disponibiliza as seguintes (auto-explicativas) propriedades: ProviderName, InitialScope e Enabled; já o WebPartPersonalization implementa a lógica necessária para realizar algumas ações referentes a personalização de “baixo-nível”.

A classe WebPartPersonalization armazena internamente um objeto do tipo PersonalizationProvider que, durante a execução, armazena uma referência ao provider especificado no arquivo Web.Config. Essa arquitetura faz parte de um padrão criado pela Microsoft denominado Provider Model, que já falamos sobre ele neste artigo. O ASP.NET fornece um provider para que os dados sejam salvos no banco de dados SQL Server. A classe concreta chama-se SqlPersonalizationProvider e, podemos ver através da imagem abaixo a hierarquia das classes, desde a abstrata ProviderBase até a classe concreta para SQL Server. Se quisermos criar um provider de personalização customizado basta herdarmos da classe abstrata PersonalizationProvider e customizarmos para o repositório desejado.

Figura 1 – Arquitetura de Provider Model para personalização.

Além das configurações que podem ser realizadas no controle WebPartManager, ainda há outras configurações, não menos importantes, a serem realizadas no arquivo Web.Config. Dentro do mesmo, mais especificamente dentro do elemento webParts, personalization há um sub-elemento chamado authorization, onde podemos especificar quais usuários (ou papéis) podem entrar e fazer parte de uma página que contém escopo compartilhado. Ainda no elemento authorization há um atributo denominado verbs, que permite definirmos dois valores:

Verbo Descrição
enterSharedScope

Indica se o usuário ou papel (role) pode acessar uma página definida com escopo compartilhado.

modifyState

Indica se o usuário ou papel (role) é capaz de modificar os dados de personalização para o escopo corrente.

Para exemplificar a configuração a ser realizada no arquivo Web.Config, analise o código abaixo. Repare que proibimos todos os usuários (deny) de entrar em um escopo compartilhado e de modificar os dados de personalização (note o atributo verbs). Depois disso, permitimos o acesso a estas funcionalidades apenas a usuários que estão contidos dentro do papel de Administradores.

<webParts>
  <personalization>
    <authorization>
      <allow roles="Administradores" verbs="enterSharedScope, modifyState" />
      <deny users="*" verbs="enterSharedScope, modifyState" />            
    </authorization>
  </personalization>
</webParts>

Definido no arquivo Web.Config os usuários ou papéis que terão as devidas permissões, podemos a qualquer momento fazer a transição entre o escopo de usuário (que é o padrão) para o escopo compartilhado. Tudo isso é possível através do método ToogleScope da classe WebPartPersonalization. Mas não podemos invocar este método sem antes verificar se o usuário corrente tem ou não permissão para entrar neste escopo compartilhado. O trecho de código abaixo ilustra esse processo de transição entre os escopos:

protected override void OnInit(EventArgs e)
{
    base.OnInit(e);
    if (this.WebPartManager1.Personalization.Scope == PersonalizationScope.User
        && WebPartManager1.Personalization.CanEnterSharedScope)
    {
        this.WebPartManager1.Personalization.ToggleScope();
    }
}

Como falamos acima, é importante verificar se o usuário tem ou não permissão para alternar entre os escopos. E para possibilitar essa verificação, a propriedade CanEnterSharedScope retorna um valor booleano indicando se o usuário está autorizado a entrar no escopo compartilhado.

Personalização de Propriedades

Como você já deve ter reparado, em um dos capítulos anteriores fizemos o uso de um atributo chamado PersonalizableAttribute em uma determinada propriedade de um UserControl. Esse atributo permite-nos especificar propriedades que devem ter seus valores persistidos através da personalização. Este atributo tem uma sobrecarga em seu construtor que permite informarmos um enumerador do tipo PersonalizationScope, indicando qual é o escopo da personalização. Este enumerador provê duas opções: Shared e User (padrão), acima explicadas. Abaixo é mostrado um exemplo de como criar um propriedade e denotá-la com o atributo Personalizable:

[
    WebBrowsable,
    WebDisplayName("Cor da Barra de Navegação"),
    WebDescription("Cor que aparecerá como Background da Barra de Navgegação"),
    Personalizable(PersonalizationScope.Shared)
]
public string CorBarraNavegacao
{
    get
    {
        return this.BarraNavegacao.BgColor;
    }
    set
    {
        this.BarraNavegacao.BgColor = value;
    }
}

Interface IPersonalizable

Se falarmos de personalização de WebParts, jamais poderíamos deixar de abordar a interface IPersonalizable. Esta interface fornece dois métodos e uma propriedade que nos permitirá ter um maior controle durante a persistência e o carregamento de dados a serem personalizados.

Como dissemos, esta interface nos fornece os seguintes membros: Load, Save e IsDirty. O método Load é invocado quando os dados de personalização são extraídos do repositório para a aplicação; o método Save permite-nos interceptar o processo de persistência dos dados no repositório; finalmente, a propriedade IsDirty retorna um valor booleano indicando se os dados do controle sofreram ou não alguma alteração.

Os métodos Load e Save tem em sua assinatura um parâmetro do tipo PersonalizationDictionary, que nada mais é do que uma coleção (key/value) de objetos do tipo PersonalizationEntry. Com isso podemos, em qualquer um dos momentos, acessar a coleção de dados a serem persistidos ou carregados e manipular da maneira que desejarmos. O uso desta técnica é geralmente utilizado quando não precisamos expor a propriedade com o atribute Personalizable já que o valor é definido internamente, ou seja, dentro do próprio controle. Através do exemplo abaixo, veremos como implementar esta interface em um UserControl:

private bool _isDirty;

public bool IsDirty
{
    get
    {
        return this._isDirty;
    }
}

public void Load(PersonalizationDictionary state)
{
    PersonalizationEntry entry = state["TextBoxValue"] as PersonalizationEntry;
    if (entry != null)
    {
        this.txtValor.Text = entry.Value.ToString();
    }
}

public void Save(PersonalizationDictionary state)
{
    state["TextBoxValue"] = 
        new PersonalizationEntry(
            this.txtValor.Text.Trim(), 
            PersonalizationScope.User);
}

protected void txtValor_TextChanged(object sender, EventArgs e)
{
    this._isDirty = true;
}

Analisando o código acima, podemos ver que no evento TextChanged de um determinado controle TextBox que está com a propriedade AutoPostBack definido como True, é definido True para o membro _isDirty, que é exposto pela propriedade IsDirty. O valor desta propriedade irá determinar se o método Save deverá ou não ser disparado para que possamos interceptar o processo de persistência e definirmos anexar valores na coleção fornecida como parâmetro para tal método.

Reinicializando o Estado da Página

A infra-estrutura ainda possibilita a reinicialização do estado das WebParts de uma determinada página, ou seja, permite que você volte ao ponto inicial, como ela foi inicialmente definida. Geralmente os métodos que possibilitam isso são baseados em escopos, onde você pode determinar qual dos escopos quer reinicializar. Para analisar os métodos disponíveis para reinicialização de estado, analise a classe estática PersonalizationAdministration e seus respectivos métodos.

WebParts.zip (293.75 kb)

WebParts – Segurança

Assim como era de se esperar, as WebParts também fornecem um recurso bastante flexível para tratarmos da segurança das WebParts. Pode ocorrer, em algumas situações, que determinadas WebParts estarão visíveis somente aos usuários que se enquadrarem em um determinado papel, ou ainda, somente usuários específicos poderão visualizá-las.

Para podermos manipular a segurança das WebParts, temos: o evento AuthorizeWebPart da classe WebPartManager e o argumento WebPartAuthorizationEventArgs, que manda para o evento anteriormente citado informações para a customização da autorização. Além disso, ainda há uma propriedade, não menos importante, chamada AuthorizationFilter da classe WebPart, onde definimos em cada WebPart os usuários ou os papéis que terão acesso à WebPart em questão. A propriedade AuthorizationFilter e o evento AuthorizeWebPart trabalham em conjunto, ou seja, de nada adiantará se você apenas definir a propriedade AuthorizationFilter com os papéis ou os usuários que nada acontecerá. Além disso, você precisa adicionar a lógica necessária para tratar isso no evento AuthorizeWebPart.

Antes de vermos como codificar esse evento, vamos analisar as propriedades do argumento WebPartAuthorizationEventArgs que é passado para o evento AuthorizeWebPart:

Propriedade Descrição
AuthorizationFilter

Passa para o evento o valor que está definido nesta mesma propriedade da WebPart.

IsAuthorized

É através desta propriedade que informaremos se o usuário terá ou não permissão para a visualização da WebPart. Ela recebe um valor booleano, que você deverá informar através da lógica que implementar dentro do evento AuthorizeWebPart.

IsShared

Propriedade de somente leitura que especifica se a WebPart é visível/compartilhada em todos os usuários da aplicação.

Path

Propriedade de somente leitura que retorna o path do controle, caso ele seja um User Control – ASCX.

Type

Retorna o tipo do controle que está sendo autorizado.

Depois de entendermos cada uma das propriedades do argumento WebPartAuthorizationEventArgs, vamos ver como funciona isso na prática, ou seja, como devemos efetivamente validar o usuário para que ele possa ou não visualizar a WebPart. Em princípio, voltando um pouco atrás, na arquitetura das WebParts, sabemos que um server-control que está contido no interior de uma ZoneTemplate será sempre envolvido por uma GenericWebPart e, conseqüentemente, teremos a propriedade AuthorizationFilter à nossa disposição para definirmos os filtros necessários. Com isso, podemos analisar o código abaixo que ilustra esse ponto:

<html>
<body>
    <form id="form1" runat="server">
        <asp:WebPartManager ID="WebPartManager1" runat="server">
        </asp:WebPartManager>
        <table width="100%" align="center" cellspacing="0">
            <tr>
                <td width="75%" colspan="2" valign="top">
                    <asp:WebPartZone ID="WebPartZone1" runat="server">
                        <ZoneTemplate>
                            <uc1:Photos
                                ID="Photos1"
                                runat="server"
                                Title="Galeria"
                                AuthorizationFilter="Admins,Gerentes" />
                            <asp:Label
                                ID="lblNome"
                                runat="server"
                                Text="Seja bem-vindo Sr(a). Gerente."
                                AuthorizationFilter="Gerentes" />
                        </ZoneTemplate>
                    </asp:WebPartZone>
                </td>
            </tr>
        </table>
    </form>
</body>
</html>

Assim como podemos ver, ao fazer o uso do ASCX Photos definimos na propriedade AuthorizationFilter os papéis de Admins e Gerentes. Mas como já falamos anteriormente, somente isso não faz com que a autorização seja executada. É necessário que você crie essa validação através do evento AuthorizeWebPart, que veremos mais tarde. O idéia de colocar o controle Label é somente para ilustrar que há a possibilidade de acessar a propriedade AuthorizationFilter para um server-control sem ele tê-la explicitamente.

Finalmente temos que codificar o evento AuthorizeWebPart para concluirmos o processo de autorização de uma determinada WebPart. Como no exemplo definimos papéis na propriedade AuthorizationFilter, é justamente isso que devemos validar, ou seja, teremos que verificar se o usuário corrente está ou não contido dentro de um dos papéis especificados. O código abaixo demonstra essa verificação:

protected void WebPartManager1_AuthorizeWebPart(object sender,
    WebPartAuthorizationEventArgs e)
{
    if (!string.IsNullOrEmpty(e.AuthorizationFilter))
    {
        string[] roles = e.AuthorizationFilter.Split(',');
        e.IsAuthorized = Array.Exists(roles, delegate(string role)
        {
            return User.IsInRole(role);
        });
    }
    else
    {
        e.IsAuthorized = true;
    }
}

Observação: o código muda ligeramente entre VB.NET e C# porque o VB.NET não suporta métodos anônimos que são uma característica exclusiva do C#.

WebParts.zip (293.75 kb)

WebParts – Exportação e Importação

Um recurso bastante interessante que as WebParts fornecem é a Importação e Exportação de WebParts. Esse recurso utiliza um arquivo com extensão *.webpart onde, em seu interior, o conteúdo é XML onde teremos informações a respeito de uma determinada WebPart. Este arquivo conterá as propriedades, tipos e valores de uma WebPart.

Para efetuar a exportação, há duas formas: através da utilização do verbo Export ou através de um método chamado ExportWebPart da classe WebPartManager. Já a importação podemos fazer através do catálogo ImportCatalogPart ou também programaticamente, através do método Import da classe WebPartManager. Veremos no decorrer desta seção essas duas opções.

Exportação

Antes de mais nada, necessitamos habilitar a possibilidade de exportação de WebParts no arquivo Web.Config:

<webParts enableExport="true">

Depois desta configuração realizada no arquivo Web.Config é necessário definirmos a propriedade ExportMode das WebParts que pretendemos exportar com um dos valores fornecidos pelo enumerador WebPartExportMode que, por sua vez, fornece três opções, a saber: None (padrão), All e NonSensitiveData. O primeiro deles, None faz com que os dados de uma WebPart não sejam exportados. Já o segundo, All, como o próprio nome diz, permite que todas as propriedades, sem excessão, de uma determinada WebPart sejam exportadas e, finalmente, a opção NonSensitiveData faz com que dados personalizáveis (propriedades denotas com o atributo Personalizable e que também definem o valor True à propriedade IsSensitive) não sejam exportados. A propriedade CorBarraNavegacao que criamos anteriormente mostra como configurar, ou seja, como definir a propriedade IsSensitive para que a mesma possa ser ocultada na exportação da WebPart:

[
    WebBrowsable,
    WebDisplayName("Cor da Barra de Navegação"),
    WebDescription("Cor que aparecerá como Background da Barra de Navgegação"),
    Personalizable(PersonalizationScope.User, true)
]
public string CorBarraNavegacao
{
    get
    {
        return this.BarraNavegacao.BgColor;
    }
    set
    {
        this.BarraNavegacao.BgColor = value;
    }
}

Para exemplificar, veremos abaixo o conteúdo de um arquivo que contém a WebPart e, por motivos de testes, vou permitir a visualização da propriedade acima neste arquivo:

<?xml version="1.0" encoding="utf-8"?>
<webParts>
  <webPart xmlns="http://schemas.microsoft.com/WebPart/v3">
    <metaData>
      <type src="~/UserControls/Personagens.ascx" />
      <importErrorMessage>Cannot import this Web Part.</importErrorMessage>
    </metaData>
    <data>
      <properties>
        <property name="CorBarraNavegacao" type="string" />
      </properties>
      <genericWebPartProperties>
        <property name="AllowClose" type="bool">True</property>
        <property name="Width" type="unit" />
        <property name="AllowMinimize" type="bool">True</property>
        <property name="AllowConnect" type="bool">True</property>
        <property name="ChromeType" type="chrometype">Default</property>
        <property name="TitleIconImageUrl" type="string" />
        <property name="Description" type="string" />
        <property name="Hidden" type="bool">False</property>
        <property name="TitleUrl" type="string" />
        <property name="AllowEdit" type="bool">True</property>
        <property name="Height" type="unit" />
        <property name="HelpUrl" type="string" />
        <property name="Title" type="string">Personagens</property>
        <property name="CatalogIconImageUrl" type="string" />
        <property name="Direction" type="direction">NotSet</property>
        <property name="ChromeState" type="chromestate">Normal</property>
        <property name="AllowZoneChange" type="bool">True</property>
        <property name="AllowHide" type="bool">True</property>
        <property name="HelpMode" type="helpmode">Navigate</property>
        <property name="ExportMode" type="exportmode">All</property>
      </genericWebPartProperties>
    </data>
  </webPart>
</webParts>

Analisando o arquivo XML gerado acima, podemos destacar algumas seções (elementos e atributos) do arquivo:

Elemento/Atributo Descrição
webParts

O elemeto root do arquivo. Pode existir somente um por arquivo.

webPart

Define informações do controle que será exportado.

metaData

Contém informações sobre o tipo da WebPart e também define uma mensagem de erro que será exibida aos usuários caso algum erro ocorra durante o processo de importação desta WebPart.

type

Lista os tipos, incluindo o full-name do controle e, no caso de um arquivo, terá o path até o controle.

importErrorMessage

Mensagem de erro que será exibida aos usuários caso algum erro ocorra durante o processo de importação desta WebPart.

data

Contém as propriedades e os respectivos valores.

properties

Contém as propriedades customizadas da WebPart.

genericWebPartProperties

Contém as propriedades de uma WebPart quando o controle não herda da classe WebPart.

Além desta forma declarativa, temos a possibilidade de efetuar a exportação programaticamente. Para isso, como já vimos acima, devemos utilizar o método ExportWebPart da classe WebPartManager em conjunto com um objeto do tipo XmlTextWriter, devidamente inicializado, que será responsável por recuperar e, através de algumas classes auxiliares, salvar fisicamente o conteúdo XML gerado pelo método ExportWebPart. O código abaixo mostra um exemplo:

using System.IO;
using System.Text;

//

WebPart tempWebPart = this.WebPartManager1.WebParts[2];

StringBuilder sb = new StringBuilder();
XmlTextWriter xmlWriter = new XmlTextWriter(new StringWriter(sb));

xmlWriter.Indentation = 4;
xmlWriter.Formatting = Formatting.Indented;
this.WebPartManager1.ExportWebPart(tempWebPart, xmlWriter);

using (StreamWriter writer = new StreamWriter("C:\Personagens.WebPart"))
{
    writer.Write(sb.ToString());
    writer.Close();
}

Importação

Depois da exportação realizada, precisamos saber como devemos proceder para que seja possível importarmos um determinado arquivo de WebPart para a página. Para isso também temos duas opções: através do catálogo ImportCatalogPart e programaticamente.

Como já sabemos, para adicionar um catálogo do tipo ImportCatalogPart na página é necessário incluir, primeiramente, uma zona do tipo CatalogZone que, por sua vez, serve de PlaceHolder para todos os tipos de catálogos, assim como vimos anteriormente. É importante dizer também que este catálogo somente aparecerá se o estado da página estiver definido como CatalogDisplayMode.

Depois do catálogo devidamente configurado, ao rodar a aplicação teremos o mesmo sendo exibido, pronto para ser utilizado. A partir deste momento, o usuário pode selecionar um arquivo com extensão *.WebPart, que conterá o conteúdo XML referente a WebPart a ser importada pelo controle. Quando clicar no botão Upload, o arquivo é carregado para dentro do catálogo, porém ainda não é adicionado a coleção de WebParts da página. A título de curiosidade, o arquivo não é salvo fisicamente no disco após o Upload. O ASP.NET apenas lê o seu conteúdo através de um Stream, como já foi mostrado aqui. Note na imagem abaixo os dois estágios do catálogo, ou seja, a do lado esquerdo contém o catálogo em seu estágio inicial; já a do lado direito é exibida após o upload e, como pode ver, já consta o controle que importamos do arquivo.

Figura 1 – Utilizando o catálogo ImportCatalogPart.

A outra alternativa que temos para importar uma WebPart é via programação, onde utilizamos o método ImportWebPart da classe WebPartManager. Dado um arquivo *.WebPart – XML, este método recupera o conteúdo do arquivo e gera um server control, retornando um objeto do tipo WebPart. Informamos a este método um objeto do tipo XmlTextReader, devidamente inicializado com o arquivo *.WebPart e um outro parâmetro obrigatório do tipo string, que armazenará a mensagem de erro de importação, caso ela venha a acontecer. O trecho de código abaixo ilustra o processo de importação via programação:

XmlTextReader reader = new XmlTextReader("C:\Personagens.WebPart");
string mensagemErro;
WebPart part = this.WebPartManager1.ImportWebPart(reader, out mensagemErro);

if (string.IsNullOrEmpty(mensagemErro) && part != null)
{
    // ...
}

A importação de WebParts pode trazer alguns riscos à aplicação como, por exemplo, um usuário malicioso pode incluir em uma propriedade do tipo string um script que pode ser executado durante a importação da mesma e, para evitar esse problema, todas as strings devem ser codificadas.

WebParts.zip (293.75 kb)

WebParts – Conexões

As conexões entre as WebParts permitem-nos estabelecer ligações entre elas e, conseqüentemente, gerar um conteúdo baseado em um valor que a mesma receberá como uma espécie de parâmetro. Essas ligações utilizam um conceito de produtor e consumidor. O produtor é quem disponibiliza o valor para que as WebParts possam fazer o uso; já o consumidor são as WebParts que consumirão o conteúdo e, baseando-se nele, gerará um conteúdo ou tratará da forma que achar conveniente.

Existem dois tipos de conexões: estáticas e dinâmicas. A primeira delas, estáticas, são criadas em tempo de desenvolvimento e colocadas dentro da seção StaticConnections do controle WebPartManager, ou seja, temos as conexões já pré-definidas; as conexões dinâmicas permitem ao usuário final da aplicação criar as conexões automaticamente, através de um catálogo do tipo ConnectionsZone, que veremos mais tarde, ainda nesta seção. O controle WebPartManager também é responsável por gerenciar todas as conexões, estáticas e dinâmicas.

Para que seja possível termos qualquer uma dessas conexões, precisamos definir as WebParts que serão as produtoras e as WebParts que serão as consumidoras. Para efetuarmos essa “amarração”, é necessário criar uma Interface pública em que o produtor deverá implementá-la. Essa Interface será utilizada para aqueles consumidores que estão interessados em resgatar os dados do produtor.

Podemos ver em nosso exemplo que há uma WebPart em que dentro dela há um controle do tipo DropDownList que dispõe todas as temporadas disponíveis da série 24 Horas. A idéia aqui é, selecionada uma dessas temporadas, todas as WebParts da página (DVD, Personagens e Galeria de Fotos) deverão customizar seu conteúdo baseando-se na temporada escolhida. Como já falamos acima, devemos inicialmente criar a Interface pública que será utilizada para a troca de informações:

public interface IConnection
{
    int TemporadaId { get; }
}

Com a Interface criada precisamos, neste momento, customizar o produtor e os consumidores. Inicialmente, devemos implementar essa Interface dentro da WebPart que disponibilizará o conteúdo. Como temos um WebUserControl (ASCX) que disponibiliza a temporada selecionada, vamos implementar essa Interface nele e expor o valor selecionado do controle DropDownList. O resultado dessa implementação ficará da seguinte forma:

public partial class UserControls_Episodios : 
    System.Web.UI.UserControl, IConnection 
{
    // removido para poupar espaço

    public int TemporadaId
    {
        get
        {
            return Convert.ToInt32(this.ddlTemporadas.SelectedValue);
        }
    }

    [ConnectionProvider("Identificação da Temporada")]
    public IConnection GetInterfaceConnection()
    {
        return this;
    }
}

Analisando o código acima, podemos reparar que além da propriedade TemporadaId exposta pela Interface também precisamos criar um método denotado com o atributo ConnectionProvider que retornará uma referência do produtor para que o controle WebPartManager possa extrair os dados e, conseqüentemente, mandar aos consumidores do mesmo.

Com o produtor finalizado, precisamos, neste momento construir o(s) consumidor(es) que fará uso do valor exposto pelo produtor. Nas WebParts, que serão as consumidoras desses valores, apenas deverá conter um método, denotado com o atributo ConnectionConsumer e, como parâmetro obrigatório, deverá receber um elemento do tipo IConnection, que é a Interface utilizada para estabelecer a conexão. A implementação é mostrada através do código abaixo onde, depois de verificado se não é uma instância nula, passamos o Id da temporada para se efetuar uma busca na base de dados para retornar o DVD correspondente:

public partial class UserControls_DVDs : 
    System.Web.UI.UserControl
{
    // removido para poupar espaço

    <ConnectionConsumer("Identificação da Temporada")]
    public void DefineTemporada(IConnection temporada)
    {
        if(temporada != null)
        {
             this.FindDVDsInDB(temporada.TemporadaId);
        }
    }
}

Conexões Estáticas

Como falamos um pouco acima, a conexão estática é definida no interior da seção StaticConnections do controle WebPartManager. Para exemplificar essa conexão, abaixo iremos configurar o controle WebPartManager para possibilitar a conexão entre as WebParts que configuramos acima:

<asp:WebPartManager ID="WebPartManager1" runat="server">
    <StaticConnections>
        <asp:WebPartConnection 
            ID="Conexao1" 
            ProviderID="Episodios1" 
            ConsumerID="DVDs1" />
        <asp:WebPartConnection 
            ID="Conexao2" 
            ProviderID="Episodios1" 
            ConsumerID="Personagens1" />
    </StaticConnections>
</asp:WebPartManager>

Como podemos ver, definimos nos atributos ProviderID e ConsumerID o ID do produtor e consumidor respectivamente. Neste tipo de conexão, tudo é feito de forma automática, ou seja, assim que o valor for alterado, automaticamente o ASP.NET notifica os consumidores para que eles possam se adequar de acordo com o valor selecionado.

Conexões Dinâmicas

As conexões dinâmicas permitem ao usuário final da aplicação criar as conexões quando achar conveniente, ou seja, ao invés de você deixar as conexões explícitas no código, dentro da seção StaticConnections do controle WebPartManager, você adiciona na sua página um controle do tipo ConnectionsZone. É importante lembrar que mesmo nas conexões dinâmicas é necessário a criação do produtor e consumidor acima descritos.

Um outro ponto importante é que esta zona/controle somente estará disponível quando a propriedade DisplayMode do controle WebPartManager estiver definida como ConnectDisplayMode, como é mostrado na seção de Manipulação e Configuração. Depois do controle adicionado à página e a propriedade DisplayMode devidamente configurada, quando rodarmos a aplicação um verbo chamado Connect (Imagem 1) estará disponível para as WebParts produtoras e consumidoras. Sendo assim, ao clicar em algum deles, o controle ConnectionsZone será exibido para você optar em qual WebPart quer se conectar.

Figura 1 – Verbo Connect.

Quando o controle ConnectionsZone é exibido são exibidas duas possibilidades, onde uma delas permite criar a conexão para um produtor e a outra permite criar a conexão para um consumidor. Quando clicamos no verbo Connect de um determinado consumidor são listados dentro do controle ConnectionsZone todos produtores em que o consumidor pode se conectar; já quando clicamos no mesmo verbo, agora de um produtor, teremos a disposição no controle ConnectionsZone todos os consumidores que podem conectar-se a este produtor. A imagem abaixo mostra o passo-à-passo de como gerar uma conexão entre o consumidor e o produtor:

Figura 2 – Gerando as conexões a partir do controle ConnectionsZone.

Depois de gerada a conexão, o controle ConnectionsZone exibe a lista das conexões efetuadas, como é mostrado na imagem abaixo:

Figura 3 – Conexões geradas.

 WebParts.zip (293.75 kb)

WebParts – Manipulação e Configuração

Depois de entender a arquitetura de classes exposta pelo Framework de WebParts, chega o momento de vermos na prática essas funcionalidades. Essa seção está dividida em cinco partes: configuração, uso de verbos, manipulação (drag-and-drop), catálogos e edição das WebParts. Na primeira dessas quatro partes, veremos a configuração básica para que as páginas ASPX comportem as WebParts; já quando falamos de manipulação, veremos como habilitar a possibilidade de alterar os locais (zonas) onde as WebParts se encontram. Já em catálogos, veremos como catalogamos as nossas WebParts e, finalmente, a edição das zonas e, conseqüentemente, das WebParts contidas dentro delas.

Como já discutimos anteriormente, o principal controle que gerencia todas as manipulação feitas sob as WebParts e as zonas é o WebPartManager. Esse controle não tem nenhuma aparência visual e deve sempre existir um – e somente um – para cada página ASPX que fizer o uso das WebParts. Além disso, é importante dizer que as WebParts só trabalham com usuários devidamente autenticados, ou seja, usuários anônimos não terão permissão para alterar entre zonas (drag-and-drop), editar as propriedades, aparência e comportamento das WebParts. Entre as principais funcionalidades deste controle podemos destacar algumas delas:

  • Relação das WebParts e suas zonas: internamente o controle WebPartManager gerencia todas as WebParts presentes na página. Essas WebParts estão adicionadas dentro de uma coleção do tipo WebPartCollection e exposta através de uma propriedade denominada WebParts. Além desta coleção, ainda existe uma outra não menos importante chamada Zones. Esta coleção, do tipo WebPartZoneCollection, mantém as zonas que a página contém. Sendo assim, internamente ele conseguirá manter a relação de uma determinada WebPart com a WebZone, sabendo em qual zona a WebPart encontra-se e também qual a posição dela dentro desta zona.

  • Gerencia o estado de visualização das páginas: quando trabalhamos com WebParts é comum ouvirmos falar sobre o estado de visualização da página. Esse estado de visualização é que vai permitir e habilitar certas funcionalidades disponibilizadas por elas. Esses estados são representados por classes do tipo WebPartDisplayMode, onde para cada estado é criado um membro público de somente leitura (read-only), que deverá ser definido na propriedade DisplayMode do WebPartManager para que ele se encarregue de alterar o layout de acordo com a visualização escolhida pelo usuário. Veremos mais abaixo os modos de visualização disponíveis.

  • Armazena as conexões entre as WebParts: este controle também é responsável por estabelecer e monitorar a comunicação entre uma ou mais WebParts existentes em uma página. Veremos mais sobre conexões mais tarde, ainda neste artigo.

  • Personalização e persistência de estado através de um provider: possibilita o usuário mover as WebParts para a zona que desejar; permite ainda alterar a aparência, propriedades e comportamentos de cada uma das WebParts. Com as modificações efetuadas é necessário armazenar isso em algum lugar para mais tarde, quando o usuário voltar, não precisar fazer todas as alterações novamente. Nesta altura é que fazemos o uso da arquitetura de providers para podermos persistir tais alterações em algum repositório. A personalização será abordada em uma futura seção, ainda neste artigo.

  • Encarregado de tratar a autorização para cada WebPart: apesar de possuir um grande número de eventos, o controle WebPartManager fornece um evento importante chamado AuthorizeWebPart. Este evento ocorre quando o método IsAuthorized é chamado para determinar se o usuário tem ou não permissão para visualizar uma determinada WebPart.

Configuração

Antes de efetivamente vermos o código responsável pela adição e configuração do controle WebPartManager na página ASPX, temos ainda que esclarecer uma questão que ficou pendente: os estados de visualização de uma página que contém WebParts. Quando fazemos o uso de WebParts, podemos ter vários estados de visualização da página corrente, ou seja, para cada um desses estados é necessário informarmos ao WebPartManager qual o estado que queremos e, para isso, utilizamos a propriedade DisplayMode.

Essa propriedade aceita um objeto do tipo WebPartDisplayMode que, por sua vez, definirá o comportamento que a página terá quando esse modo estiver ativo. Atualmente temos 5 modos/estados de visualização, que são explicados através da tabela abaixo:

Estado Descrição
BrowseDisplayMode

É a visualização normal de uma página. Além de ser a visualização padrão, é dessa forma que um usuário anônimo visualizará a página. Esse modo de visualização não permite qualquer alteração a nível de layout, mas permite-nos acessar os verbos de cada uma das WebParts quando estão disponíveis.

DesignDisplayMode

Esta visualização permite aos usuários (autenticados) customizar a posição e em qual zona as WebParts devem ficar. Além disso, ainda é permitido você excluir WebParts existentes na página corrente.

EditDisplayMode

Este estado de visualização permite-nos editar a aparência, propriedades e comportamentos das WebParts que estão visíveis no modo normal. Geralmente neste estado, fazemos o uso dos seguintes controles: AppearanceEditorPart, BehaviorEditorPart, LayoutEditorPart e PropertyGridEditorPart.

CatalogDisplayMode

Através deste estado os usuários podem adicionar WebParts que estão disponíveis em algum catálogo também disponibilizado na página.

ConnectDisplayMode

Neste estado os usuários podem estabelecer conexões entre WebParts, desconectá-las e ainda gerenciar essas conexões.

E como esses modos de visualização são representados? Esses objetos são declarados como estáticos e de somente leitura dentro da classe WebPartManager. Como já falamos acima, esses objetos são do tipo WebPartDisplayMode, que encontra-se também declarado dentro do namespace System.Web.UI.WebControls.WebParts. Cada um dos estados tem uma classe que herda da classe base WebPartDisplayMode. Essas classes são declaradas no interior da classe WebPartManager e customiza as propriedades e métodos para os diferentes tipos de visualização. Como essas são privadas, não há informações sobre elas na ajuda do Visual Studio .NET, mas podemos visualizar isso através do Reflector, assim como é mostrado na imagem abaixo:

Figura 1 – Classes internas que representam os estados de visualização.

Figura 2 – Membros da classe abstrata WebPartDisplayMode.

Agora que já conhecemos a arquitetura superficial do WebPartManager vamos começar a trabalhar efetivamente em cima de um projeto para vermos as WebParts em ação. Como já sabemos, o primeiro controle a termos na página é o WebPartManager. Como ele não tem nenhuma aparência em runtime, então poderá colocá-lo em qualquer lugar da sua página. Depois deste controle, é o momento de declararmos as zonas que desejamos ter em nossa página. Também não há a necessidade de amarrar as zonas especificando o ID do controle WebPartManager, já que esse vínculo será feito em runtime. Abaixo é exibido a configuração padrão para uma página que fará o uso das WebParts, tendo 4 zonas posicionadas de duas em duas em cada linha de uma tabela:

<html>
<body>
    <form id="form1" runat="server">
        <asp:WebPartManager ID="WebPartManager1" runat="server">
        </asp:WebPartManager>
        <table width="100%" align="center" cellspacing="0">
            <tr>
                <td width="75%" colspan="2" valign="top">
                    <asp:WebPartZone ID="WebPartZone1" runat="server">
                        <ZoneTemplate>

                        </ZoneTemplate>
                    </asp:WebPartZone>
                </td>
                <td width="25%" valign="top">
                    <asp:WebPartZone ID="WebPartZone3" runat="server">
                        <ZoneTemplate>

                        </ZoneTemplate>
                    </asp:WebPartZone>
                </td>
            </tr>
            <tr>
                <td width="75%" colspan="2" valign="top">
                    <asp:WebPartZone ID="WebPartZone2" runat="server">
                        <ZoneTemplate>

                        </ZoneTemplate>
                    </asp:WebPartZone>
                </td>
                <td width="25%" valign="top">
                    <asp:WebPartZone ID="WebPartZone4" runat="server">
                        <ZoneTemplate>

                        </ZoneTemplate>
                    </asp:WebPartZone>
                </td>
            </tr>
        </table>
    </form>
</body>
</html>

Reparem que cada uma das zonas possui um elemento ZoneTemplate, onde dentro dele será armazenado os controles que desejamos que apareça. É também para dentro desta seção que os controles são colocados quando utilizamos o recurso de drag-and-drop. No nosso caso, essa página deverá “nascer” com quatro User Controls (arquivos ASCX), onde cada uma das seções armazenará um deles.

A declaração dos arquivos ASCX dentro destes templates não muda nada com relação ao que já fazemos atualmente quando queremos colocá-lo no interior de uma página ASPX. O único detalhe aqui é que podemos, na declaração do controle ASCX que está contido dentro da ZoneTemplate, já especificar as propriedades que são expostas pela classe GenericWebPart. Mesmo que o editor de HTML “reclamar”, dizendo que a propriedade não faz parte do controle, quando a aplicação é executada, o erro não acontecerá, pois como vimos anteriormente, os controles colocados dentro deste template são encapsulados por essa classe genérica.

Finalmente temos a página sendo exibida com as WebParts em suas posições iniciais. Lembre-se que neste momento o usuário ainda não está logado na aplicação e, conseqüentemente, não poderá modificar o layout (drag-and-drop). Quando a página é carrega inicialmente ela estará com o seu modo de visualização definido como BrowseDisplayMode e o drag-and-drop não resultará, porém o uso dos verbos (veremos a seguir) poderá ser utilizado sem problemas.

Verbos

Cada WebPart pode conter diversos verbos. Verbos são ações que o usuário pode executar em cima de uma determinada WebPart e eles podem aparecer como links, botões ou imagens (isso é definido através da propriedade VerbButtonType do controle WebPartZone). Por padrão temos esses verbos em um dropdown menu, que automaticamente aparecem no canto superior direito das WebParts. A imagem abaixo ilustra esses verbos:

Figura 3 – Menu com os verbos padrões.

A configuração destes verbos é definida exclusivamente para cada uma das zonas contidas na página. Essas zonas expõem propriedades do tipo WebPartVerb para cada um dos verbos existentes dentro do controle WebPartZone. Esse controle por sua vez, já possui vários verbos (propriedades), que podemos utilizar dentro das aplicações. Essas propriedades, por convenção, adotam a seguinte nomenclatura: NomeVerboVerb. A listagem abaixo mostra os verbos fornecidos atualmente pela classe/controle WebPartZone:

Verbo Descrição
Minimize Minimiza a WebPart, ficando apenas visível a barra de título e o menu de verbos exposto pela WebPart.
Restore Quando a WebPart estiver minimizada e queremos voltá-la em sua visualização normal, ou seja, com todo o conteúdo, podemos optar por restaurar.
Close Este verbo permite-nos fechar a WebPart. Quando fazemos isso, a mesma não ficará mais visível e somente conseguiremos acessá-la novamente quando acessarmos o catálogo. As WerbParts que são fechadas pelo usuário voltam ao catálogo para, mais tarde, se o usuário desejar, torná-la visível novamente.
Connect Este verbo permite-nos conectar uma WebPart em outra. Veremos isso mais detalhadamente na próxima seção deste artigo.
Delete Este verbo somente estará disponível para WebParts adicionadas programaticamente na página, ou seja, controles declarados como markup na página não terão acesso a este verbo. Ele somente estará disponível quando o estado de visualização do WebPartManager estiver definido como CatalogDisplayMode ou DesignDisplayMode. Ele ainda é capaz de excluir a instância definitivamente e qualquer dados de personalização vinculados a ele.
Edit Se a página estiver com o seu estado de visualização definido como EditDisplayMode e a mesma permitir a edição de controles, este verbo, ao ser clicado, permitirá a edição da WebPart corrente.
Export Disponível quando a aplicação permitir a exportação, onde uma determinada WebPart pode ser serializada e exportada para um arquivo físico. Veremos isso detalhadamente mais adiante, ainda neste artigo.
Help Este verbo possibilita incluirmos um arquivo de ajuda para uma determinada WebPart. O que faz este verbo ficar disponível é definir a propriedade HelpUrl da WebPart.

Caso nenhum dos verbos atender a necessidade, ainda há a possibilidade de criar o seu próprio verbo. Se não quiser suporte visual, basta você criar uma instância da classe WebPartVerb ou até mesmo criar uma classe customizada, onde terá essa classe (WebPartZone) como base, configurá-la de acordo com a sua necessidade e, em seguida, adicionar na coleção de verbos da(s) zona(s) ou de uma determinada WebPart. Se desejar ter um suporte visual para alteração das propriedades, então terá que criar uma zona customizada (herdando de WebPartZone) e adicionando uma nova propriedade expondo esse novo verbo (esse cenário é válido quando você quiser que todas as WebParts contidas dentro desta zona tenha esse verbo customizado, caso contrário, deverá adicionar esse verbo customizado na coleção de verbos da sua WebPart). Há duas propriedades interessantes que vale a pena chamar a atenção: ClientClickHandler e ServerClickHandler. A primeira delas nós podemos especificar uma string contendo um nome de método em código cliente (Javascript) que será executado quando o usuário clicar. Já a segunda, especificamos um delegate do tipo WebPartEventHandler, que referenciará um método do lado do servidor a ser executado.

O controle WebPartZone fornece um evento chamado CreateVerbs, que é disparado quando os verbos são criados para uma determinada zona, logo, podemos interceptar a criação a partir deste evento e, neste momento, adicionarmos verbos customizados. O código abaixo mostra um exemplo de como criar um verbo customizado e adicionar a coleção de verbos de uma zona através do evento CreateVerbs:

protected void WebPartZone4_CreateVerbs(object sender, WebPartVerbsEventArgs e)
{
    WebPartVerb c = 
        new WebPartVerb(
            "Anuncio", 
            new WebPartEventHandler(ProcessoServidor),
            "return confirm('Deseja mesmo continuar?');");

    c.Text = "Comprovar";
    c.Description = "Uma descrição qualquer.";

    List newVerbs = new List();
    newVerbs.Add(c);
    e.Verbs = new WebPartVerbCollection(e.Verbs, newVerbs);
}

private void ProcessoServidor(object sender, WebPartEventArgs e)
{
    Response.Write(e.WebPart.Title);
}

Como podemos ver no código acima, optamos por criar um objeto do tipo WebPartVerb e customizá-lo. Reparem que o construtor desta classe fornece diversos overloads, onde um deles permite passarmos uma string que representa o código cliente que será executado quando o usuário clicar. Esse valor é guardado dentro da propriedade ClientClickHandler e, como era de se esperar, há também o overload que recebe o delegate que aponta para a função do lado do servidor que será executada se o usuário clicar neste verbo. Logo em seguida, criamos uma nova lista que armazenará os novos verbos e adicionamos o verbo recém criado dentro desta coleção. Finalmente, criamos uma nova instância da classe WebPartVerbCollection, que é responsável por armazenar em seu interior uma coleção de verbos e, em seu construtor, informamos a lista atual de verbos (exposta através da propriedade Verbs do argumento WebPartVerbsEventArgs) e a lista contendo os novos verbos. A imagem abaixo mostra a WebParts de DVDs incluindo este novo verbo:

Figura 4 – Novo verbo adicionado.

Importante: Quando você opta por herdar da classe WebPart, é necessário que você sobrescreva a propriedade Verbs. Essa propriedade é definida através de uma interface chamada IWebActionable, que retorna uma coleção read-only (WebPartVerbCollection) contendo todos os verbos da WebPart em questão.

Manipulação

Quando queremos habilitar ao usuário a possibilidade de alterar o layout, ou seja, permitir o drag-and-drop das WebParts para as zonas que temos na página, além do usuário precisar estar devidamente logado, ainda é necessário que a propriedade DisplayMode, ou melhor, o modo de visualização do controle WebPartManager esteja definido como DesignDisplayMode. Com este estado de visualização definido, agora é permitido efetuar o drag-and-drop das WebParts e assim customizar o layout da página.

De acordo com o layout da nossa aplicação, há um controle DropDownList no canto superior direito da página que contém os possíveis estados da página. Para exemplificar, defini o mesmo como Editar (valor que eu mesmo customizei) e no evento SelectedIndexChanged faço a verificação e, dependendo do valor selecionado, a rotina define o respectivo estado ao controle WebPartManager. Logo abaixo é mostrado o código que faz essa verificação (ele será modificado nas próximas seções) e logo em seguida a página em seu estado de Design, onde posso, via drag-and-drop, modificar as WebParts.

protected void ddlOpcoes_SelectedIndexChanged(object sender, EventArgs e)
{
    string selectedValue = ((DropDownList)sender).SelectedValue;
    if (!string.IsNullOrEmpty(selectedValue))
    {
        switch (selectedValue)
        {
            case "Design":
                this.WebPartManager1.DisplayMode = WebPartManager.DesignDisplayMode;
                break;
            case "Catalog":
                this.WebPartManager1.DisplayMode = WebPartManager.CatalogDisplayMode;
                break;
            case "Edit":
                this.WebPartManager1.DisplayMode = WebPartManager.EditDisplayMode;
                break;
            case "Browse":
                this.WebPartManager1.DisplayMode = WebPartManager.BrowseDisplayMode;
                break;
            case "Connect":
                this.WebPartManager1.DisplayMode = WebPartManager.ConnectDisplayMode;
                break;
        }
    }
}

Observação: O motivo de estar fazendo o cast é porque o DropDownList está dentro de um controle LoginView.

Figura 5Drag-and-Drop em ação.

Catálogos

Como já vimos anteriormente, os catálogos servem para armazenar as WebParts quando elas não estão em uma determinada zona da página. Imagine a seguinte situação: você remove temporariamente uma determinada WebPart da página e, mais tarde, quer trazê-la de volta. Precisamos ter um repostório para que possamos selecionar/escolher as WebParts e, em um clique, podemos adicioná-la(s) em uma zona qualquer da página corrente.

Para isso a Microsoft adicionou três controles/catálogos: DeclarativeCatalogPart, PageCatalogPart e ImportCatalogPart. O primeiro delas, DeclarativeCatalogPart, é responsável por armazenar WebParts que inicialmente não aparecem na página, ou seja, ficam ali de stand-by para, a qualquer momento, adicioná-las na página e sua configuração ainda é realizada em design-time. Já o segundo, PageCatalogPart, irá armazenar as WebParts que são removidas da página pelo usuário final. O última deles veremos mais detalhadamente na seção de Exportação e Importação de WebParts. Abaixo veremos a configuração de uma zona de catálogo (que é a zona que armazena os catálogos descritos anteriormente):

<asp:CatalogZone ID="CatalogZone1" runat="server">
    <ZoneTemplate>
        <asp:PageCatalogPart ID="PageCatalogPart1" runat="server" />
        <asp:DeclarativeCatalogPart ID="DeclarativeCatalogPart1" runat="server">
            <WebPartsTemplate>
                <asp:Calendar ID="Calendar1" runat="server" Title="Calendário" />
            </WebPartsTemplate>
        </asp:DeclarativeCatalogPart>
    </ZoneTemplate>
    <!-- Propriedades ocultadas para poupar espaço -->
</asp:CatalogZone>

É importante reparar que, nesta zona, a seção ZoneTemplate serve para adicionar os catálogos que desejamos ter na página. Para o exemplo decidi utilizar dois dos três catálogos mencionados acima, e aproveito para apresentar a seção WebPartsTemplate do catálogo DeclarativeCatalogPart que recebe as WebParts que inicialmente não estarão visíveis na página, ou seja, poderão ser adicionadas pelo usuário final. É importante dizer que, para que os catálogos estejam disponíveis, é necessário definirmos a propriedade DisplayMode do controle WebPartManager como CatalogDisplayMode (consultar o código do evento SelectedIndexChanged do DropDownList um pouco mais acima).

Através do comparativo que a imagem abaixo mostra, vemos que a zona que contém os catálogos cria links para os catálogos que temos em seu interior, ou seja, como temos o DeclarativeCatalogPart e PageCatalogPart há dois links em que, quando clicados, listam as respectivas WebParts. No rodapé da zona de catálogos temos um controle do tipo DropDownList que contém todas as zonas da página corrente em que podemos adicionar as WebParts que estão nestes catálogos. Depois de selecionada a zona e a(s) WebPart(s) (através do CheckBox) que desejamos adicionar, basta clicar no botão Adicionar.

Figura 6 – Catálogos de WebParts.

Edição

Como já mencionamos anteriormente, as WebParts ainda contém alguns controles que permitem a sua edição de aparência, comportamento e layout. Para que isso seja possível, é disponibilizado um controle chamado de EditorZone onde, em seu interior (dentro da seção ZoneTemplate), são colocados os seguintes controles de edição: AppearanceEditorPart, BehaviorEditorPart, LayoutEditorPart e o PropertyGridEditorPart. É Importante lembrar que os controles responsáveis pela edição das WebParts somente estarão disponíveis se o estado da propriedade DisplayMode do controle WebPartManager da página estiver definido como EditDisplayMode (para ver como definir isso, consulte o evento SelectedIndexChanged do DropDownList). Quando este estado é definido, um verbo chamado Edit é adicionado às WebParts para que, quando o usuário clicar, a mesma entre em modo de edição.

O primeiro deles, AppearanceEditorPart, fornece propriedades para alterarmos a aparência de cada WebPart, como por exemplo, alterar o título apresentado pela WebPart. A imagem abaixo ilustra o uso dele:

Figura 7 – O editor AppearanceEditorPart sendo utilizado.

O segundo editor, BehaviorEditorPart, permite-nos configurar a utilização dos verbos expostos pelas WebParts, como por exemplo, AllowClose, AllowEdit, etc. Além disso, ainda é possível alterar a propriedade AuthorizationFilter, porém você deve analisar até que ponto isso é viável. A imagem abaixo ilustra o controle em funcionamento:

Figura 8 – O editor BehaviorEditorPart sendo utilizado.

Em terceiro lugar, o editor LayoutEditorPart permite a configuração do estado e da localização da WebPart. Por estado, significa que se ele deve ser minimizado, fechado, etc. Já a localização diz respeito a zona e a posição dentro dela que a WebPart terá. Esse editor permitirá a alteração das seguintes propriedades das WebParts: ChromeState, Zone e ZoneIndex. A imagem abaixo ilustra o controle em funcionamento:

Figura 9 – O editor LayoutEditorPart sendo utilizado.

Finalmente o quarto e último editor, o PropertyGridEditorPart, que permite a configuração de propriedades customizadas das WebParts, contrariando os outros editores, que editam apenas as propriedades relacionadas com a UI (User Interface). Este controle irá exibir todas as propriedades da WebPart denotadas com o atributo WebBrowsable. Para cada tipo dessas propriedades, um controle ASP.NET específico é criado para satisfazer as alterações. Na listagem abaixo é mostrado a relação entre tipo e controle:

Tipo Controle
String

TextBox

Int, Float, Unit

TextBox

Boolean

CheckBox

Enum

DropDownList (contendo os valores)

DateTime

TextBox

Para exemplificarmos o uso deste editor, vamos adicionar uma propriedade no UserControl (ASCX) que recebe/retorna uma string. Como já vimos, o atributo WebBrowsable é obrigatório. Além dele, ainda há alguns outros atributos que você pode utilizar para customizar ainda mais, como é o caso dos atributos WebDisplayName, WebDescription e Personalizable.

O atributo WebDisplayName definirá o nome (label) da propriedade que será exibido que, por padrão, é o próprio nome da propriedade. Já o WebDescription serve para definirmos uma pequena mensagem (tooltip) quando o usuário posiciona o mouse em cima do controle responsável por alterar a propriedade. Finalmente o atributo Personalizable, que garantirá que o valor atribuído a propriedade será persistido no repositório de dados especificado através do provider. É importante dizer que somente a propriedade de escrita e leitura são permitidas, e qualquer outro tipo de propriedade, incluindo as parametrizadas, resultarão em uma excessão do tipo HttpException. A imagem abaixo ilustra o controle em funcionamento:

Figura 10 – O editor PropertyGridEditorPart sendo utilizado.

Depois de visualizado o resultado, é necessário vermos como ficou a implementação da propriedade dentro do UserControl que é editado pelo controle PropertyGridEditorPart:

[
    WebBrowsable,
    WebDisplayName("Cor da Barra de Navegação"),
    WebDescription("Cor que aparecerá como Background da Barra de Navgegação"),
    Personalizable
]
public string CorBarraNavegacao
{
    get
    {
        return this.BarraNavegacao.BgColor;
    }
    set
    {
        this.BarraNavegacao.BgColor = value;
    }
}

WebParts.zip (293.75 kb)