Desenvolvimento - ASP. NET
ASP.NET: WebParts
Dentre todas as novidades que foram incluídas dentro do ASP.NET 2.0, não podemos descartar as chamadas Web Parts. O uso delas permitem que os usuários finais da aplicação possam modificar o conteúdo da página, a aparência e também o comportamento, sem recorrer a códigos e/ou operações mais complexas.
por Israel AéceEssas modificações que o usuário tem direito de fazer podem ser aplicadas somente a ele ou a todos os usuários da aplicação. Isso dependerá muito das regras da sua aplicação e, além disso, você ainda pode persistir as configurações realizadas e, conseqüentemente, terá os valores entre múltiplas sessões do usuário.
As Web Parts permitem ao desenvolvedor criar aplicações mais ricas e ao mesmo tempo deixar o usuário customizar como ele desejar. No decorrer deste artigo, vamos conhecer as principais funcionalidades disponibilizadas pela arquitetura de Web Parts.
Conteúdo
Introdução e Arquitetura
Através desta seção veremos uma introdução a respeito das Web Parts e também alguns conceitos. Além disso, abordaremos a arquitetura das classes que compõem o namespace System.Web.UI.WebControls.WebParts, entendendo como funcionam as hierarquias e como extendê-las quando necessitamos de algo mais customizado.
Manipulação e Configuração
A utilização e manipulação das Web Parts se dá pelo uso de controles que foram adicionados à ToolBox do Visual Studio .NET 2005. Através do uso destes controles, temos grande partes das funcionalidades disponibilizadas pela tecnologia. Veremos no decorrer desta seção a maior partes destes controles e também os editores e os catálogos, que nos permitem editar cada uma das Web Parts e catalogá-las.
Conexões
As conexões são utilizadas para estabelecer uma comunicação entre duas Web Parts. Existem dois tipos de conexões: as estáticas e as dinâmicas. A partir desta seção iremos entender como criar e configurar as conexões estáticas e dinâmicas.
Exportação e Importação
Dentro das Web Parts temos a possibilidade de exportar as Web Parts existentes na página e restaurá-las em algum outro momento. A importação é realizada justamente para fazer o processo inverso, ou seja, permitir que o usuário traga para dentro da sua aplicação uma Web Part existente e o mais importante: isso tudo é feito pelo usuário final.
Segurança
A Microsoft também se preocupou com a segurança de uma Web Part. Para isso, ela incluiu um evento chamado AuthorizeWebPart da classe WebPartManager, que permite-nos tratar se o usuário corrente tem ou não direitos de acessar a Web Part em questão.
Personalização e Provider
O foco desta seção é demonstrar como persistir as modificações que o usuário fez durante o seu tempo dentro da aplicação. Isso irá permitir que as configurações serão mantidas entre múltiplas sessões do usuário. Veremos também como essas informações são persistidas e quais as possibilidades e repositórios possíveis para armazenar tais informações.
Introdução e Arquitetura
Com a disseminação cada vez maior da internet, as aplicações Web tendem cada vez mais serem customizadas para atender um determinado usuário/cenário. Os usuários estão a cada dia mais exigentes e com isso nossas aplicações devem estar preparadas para isso. Essas customizações vão desde o que o usuário quer realmente ver até em que posição da tela ele quer que essa informação apareça. Já há vários sites que possibilitam essas customizações e um dos mais conhecidos é o já citado Windows Live Spaces, que fornece-nos uma gama de funcionalidades a nível de adição de conteúdo, customização das informações e aparência.
Na versão 2.0 do ASP.NET a Microsoft introduziu novos controles, denominados WebParts. A intenção é possibilitar a nós desenvolvedores criarmos aplicações mais customizavéis e, conseqüentemente, mais agradáveis aos nossos consumidores. Esses controles estão contidos dentro da ToolBox do Visual Studio .NET 2005 e sua respectivas classes e membros (enumeradores, estruturas, etc) constam dentro do namespace System.Web.UI.WebControls.WebParts.
Arquitetura das Classes
Antes de começarmos a analisar os controles já fornecidos pelo ASP.NET, vamos primeiramente entender como funciona a hierarquia das classes que provêm as funcionalidades das WebParts. Existem duas formas de estarmos criando uma WebPart. Você pode utilizar qualquer controle ASP.NET (TextBox, Calendar, User Control, GridView, etc.) como uma WebPart ou, se desejar um controle ainda mais customizado, pode herdar da classe abstrata chamada WebPart ou até mesmo implementar (direta ou indiretamente) a interface IWebPart.
Quando utilizamos algum controle ASP.NET como uma WebPart (que por sua vez não herda a classe WebPart e nem implementa a IWebPart), ele é encapsulado por uma classe denominada GenericWebPart que, por sua vez, implementa diretamente a interface IWebPart. Essa classe serve como um wrapper para o seu controle ASP.NET, provendo a ele todas as funcionalidades de uma WebParts, funcionalidades que veremos mais adiante. Para ilustramos melhor essa hierarquia, vejamos a imagem abaixo:
Figura 1 - Hierarquia das WebParts. |
Analisando a imagem acima podemos ver a interface IWebPart, que nos fornece várias propriedades que definem algumas características de UI (User Interface) comuns entre todas as WebParts. Através da listagem abaixo será possível entender qual a finalidade de cada uma dessas propriedades:
|
Ainda analisando a imagem acima, podemos notar três outras classes: Part, WebPart e GenericWebPart. Essas classes são extremamente importantes dentro da arquitetura do Framework de WebParts e veremos a utilidade de cada uma delas abaixo.
A classe Part define propriedades comuns para todos os controles do tipo parts que tem uma aparência consistente, fornecendo propriedades para a customização da part dentro da página. Para exemplificar, podemos citar dois tipos de parts: a WebPart, que falaremos mais abaixo e o controle EditorPart, que fornece-nos uma UI para modificar (personalizar) uma determinada WebPart.
Já a classe WebPart serve como classe base para todos os controles WebParts, pois herda diretamente da classe Part, adicionando ainda funcionalidades para criar conexões entre WebParts, personalização e interações com o usuário. Se quiser criar uma WebPart customizada e fazer uso de todas as funcionalidades fornecidas pelas WebParts, então é desta classe que você deverá herdar e customizar de acordo com a sua necessidade. E, quando herdá-la, atente-se a sobrescrever o método RenderContents, que é o responsável pela exibição do conteúdo que a sua WebPart irá apresentar dentro da página. O trecho de código abaixo mostra um pequeno exemplo de como implementar essa classe concreta:
using System; using System.Web.UI; using System.Web.UI.WebControls.WebParts; public class CustomWebPart : WebPart { public CustomWebPart() { this.Title = "Minha WebPart"; } protected override void RenderContents(HtmlTextWriter writer) { writer.Write("O conteúdo deve ser colocado aqui!"); } } Imports System Imports System.Web.UI Imports System.Web.UI.WebControls.WebParts Public Class CustomerWebPart Inherits WebPart Public Sub New() Me.Title = "Minha WebPart" End Sub Protected Overrides Sub RenderContents(writer As HtmlTextWriter) writer.Write("O conteúdo deve ser colocado aqui!") End Sub End Class |
|||
C# | VB.NET |
Finalmente a classe GenericWebPart, como já citamos anteriormente, herda diretamente da classe WebPart e serve como wrapper para controles que não tem suporte intrínsico as funcionalidades de WebParts, comportando-se como uma verdadeira WebPart em runtime. Como a utilidade desta classe é servir de wrapper para o controle, ela nunca será declarada dentro do HTML da página ASPX. Como essa classe só existirá em runtime, é possível acessá-la através de um método chamado GetGenericWebPart da classe WebPartManager. Esta, por sua vez, possui uma propriedade chamada ChildControl, que retorna uma referência ao controle que foi encapsulado pela GenericWebPart.
Controles de WebParts
Antes de efetivamente entrarmos na definição dos controles, é importante termos em mente um conceito bastante importante: as Zonas. As zonas são basicamente o local físico dentro da página onde podemos colocar uma ou mais WebParts. Como era de se esperar, vamos falar um pouco sobre a hierarquia de zonas que o Framework de WebParts nos fornece. Para isso, vejamos a figura abaixo:
Figura 2 - Hierarquia das Zonas. |
Para iniciarmos nossa análise, iniciaremos com a classe WebZone. Essa classe abstrata provê grande parte das funcionalidades para os controles que servem como containers, ou seja, para os controles (parts) que estão contidos dentro da zona, sendo a classe base para todas as zonas fornecidas pelo Framework de WebParts. Isso inclui controles Web Parts, controles de servidor e User Controls. Como já era de se esperar, uma zona pode armazenar vários controles internamente, já que o mesmo herda diretamente da classe CompositeControl e a sua renderização é gerada em tabelas HTML. Como essas zonas permitem a configuração da aparência das mesmas, logo, qualquer WebPart colocada dentro dela herdará essas configurações.
Derivadas desta classe, existem dois tipos: WebPartZoneBase e ToolZone. A primeira delas, WebPartZoneBase, além de herdar todas as funcionalidades da classe WebZone, adiciona código cliente (Javascript) para permitir o Drag and Drop das WebParts contidas dentro da zona, coloca em funcionamento os verbos (veremos mais tarde a respeito), e outros detalhes a nível de aparência, como por exemplo bordas e menus. Um detalhe importante é que essa classe fornece duas propriedades interessantes; a primeira delas chamanda WebParts do tipo WebPartCollection, que retorna uma coleção de objetos do tipo WebPart, representando todas as WebParts que estão contidas dentro daquela zona; a segunda, LayoutOrientation, é onde especificamos em que sentido as WebParts serão posicionadas dentro da zona, ou seja, Vertical ou Horizontal. Já a outra classe, ToolZone, tem a finalidade de servir como classe base para as zonas que somente aparecerão em determinados modos de visualização, contendo controles especiais que permitem os usuários modificar a aparência e propriedades que foram herdadas, e assim criar zonas mais customizadas. Ainda analisando o diagrama de classes, podemos ver que da classe base WebZone surgem algumas outras zonas, como por exemplo EditorZone, CatalogZone, ConnectionsZone e WebPartZone, mas estas já são controles que estão dentro da ToolBox do Visual Studio .NET 2005, queveremos mais abaixo.
Para ilustrar as zonas e as WebParts, onde e como elas se encaixam, a imagem abaixo mostra no primeiro quadro, as seções da página que contém zonas disponíveis que permitem o drag-and-drop de WebParts. Já o segundo quadro exibe as mesmas zonas, só que agora com uma WebPart diferente dentro de cada zona. Vale lembrar que nada impede de termos todas as WebParts dentro de uma única zona e, como já podemos deduzir, é necessário que nossa página tenha, no mínimo, uma zona para que possa armazenar as WebParts.
Figura 3 - Zonas e WebParts. |
Agora que já vimos como é a hierarquia e estrutura das classes que fornecem as WebParts e WebZones, vamos analisar os controles que o próprio ASP.NET nos fornece para usarmos dentro da página ASPX. Os controles serão aqui apresentados, porém veremos o uso deles no decorrer deste artigo, analisando detalhadamente cada um deles quando forem realmente utilizados.
Figura 4 - Controles de WebParts - ToolBox - VS.NET 2005. |
|
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.
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:
|
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> |
|||
WebParts - ASPX |
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:
|
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); } Protected Sub WebPartZone4_CreateVerbs(sender As Object, e As WebPartVerbsEventArgs) Dim c As New WebPartVerb( "Anuncio", new WebPartEventHandler(ProcessoServidor), "return confirm("Deseja mesmo continuar?");") c.Text = "Comprovar" c.Description = "Uma descrição qualquer." Dim newVerbs As New List(Of WebPartVerb)() newVerbs.Add(c) e.Verbs = New WebPartVerbCollection(e.Verbs, newVerbs) End Sub Private Sub ProcessoServidor(sender As Object, e As WebPartEventArgs) Response.Write(e.WebPart.Title) End Sub |
|||
C# | VB.NET |
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; } } } Protected Sub ddlOpcoes_SelectedIndexChanged(sender As Object, e As EventArgs) Dim selectedValue As String = DirectCast(sender, DropDownList).SelectedValue If Not String.IsNullOrEmpty(selectedValue) Then Select Case selectedValue Case "Design" Me.WebPartManager1.DisplayMode = WebPartManager.DesignDisplayMode Case "Catalog" Me.WebPartManager1.DisplayMode = WebPartManager.CatalogDisplayMode Case "Edit" Me.WebPartManager1.DisplayMode = WebPartManager.EditDisplayMode Case "Browse" Me.WebPartManager1.DisplayMode = WebPartManager.BrowseDisplayMode Case "Connect" Me.WebPartManager1.DisplayMode = WebPartManager.ConnectDisplayMode End Select End If End Sub |
|||
C# | VB.NET |
Observação: O motivo de estar fazendo o cast é porque o DropDownList está dentro de um controle LoginView.
Figura 5 - Drag-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> |
|||
CatalogZone - ASPX |
É 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:
|
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; } } < WebBrowsable, WebDisplayName("Cor da Barra de Navegação"), WebDescription("Cor que aparecerá como Background da Barra de Navgegação"), Personalizable > Public Property CorBarraNavegacao As String Get Return Me.BarraNavegacao.BgColor End Get Set(Value As String) Me.BarraNavegacao.BgColor = Value End Set End Property |
|||
C# | VB.NET |
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; } } Public Interface IConnection ReadOnly Property TemporadaId() As Integer End Interface |
|||
C# | VB.NET |
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; } } Public Partial Class UserControls_Episodios Inherits System.Web.UI.UserControl Implements IConnection " removido para poupar espaço Public ReadOnly Property TemporadaId As Integer Get Return Convert.ToInt32(this.ddlTemporadas.SelectedValue) End Get End Property <ConnectionProvider("Identificação da Temporada")> Public Function GetInterfaceConnection() As IConnection Return Me End Function End Class |
|||
C# | VB.NET |
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); } } } Public Partial Class UserControls_DVDs Inherits System.Web.UI.UserControl " removido para poupar espaço <ConnectionConsumer("Identificação da Temporada")> Public Sub DefineTemporada(temporada As IConnection) If Not IsNothing(temporada) Then Me.FindDVDsInDB(temporada.TemporadaId) End If End Sub End Class |
|||
C# | VB.NET |
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> |
|||
StaticConnections - ASPX |
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. |
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"> |
|||
Web.Config |
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; } } < WebBrowsable, WebDisplayName("Cor da Barra de Navegação"), WebDescription("Cor que aparecerá como Background da Barra de Navgegação"), Personalizable(PersonalizationScope.User, True) > Public Property CorBarraNavegacao As String Get Return Me.BarraNavegacao.BgColor End Get Set(Value As String) Me.BarraNavegacao.BgColor = Value End Set End Property |
|||
C# | VB.NET |
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> |
|||
Personagens.WebPart |
Analisando o arquivo XML gerado acima, podemos destacar algumas seções (elementos e atributos) do arquivo:
|
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(); } Imports System.IO Imports System.Text "" Dim tempWebPart As WebPart = Me.WebPartManager1.WebParts(2) Dim sb As New StringBuilder() Dim xmlWriter As New XmlTextWriter(New StringWriter(sb)) xmlWriter.Indentation = 4 xmlWriter.Formatting = Formatting.Indented Me.WebPartManager1.ExportWebPart(tempWebPart, xmlWriter) Using writer As New StreamWriter("C:\Personagens.WebPart") writer.Write(sb.ToString()) writer.Close() End Using |
|||
C# | VB.NET |
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) { // ... } Dim reader As New XmlTextReader("C:\Personagens.WebPart") Dim mensagemErro As String Dim part As WebPart = Me.WebPartManager1.ImportWebPart(reader, mensagemErro) If String.IsNullOrEmpty(mensagemErro) AndAlso Not IsNothing(part) Then " ... End If |
|||
C# | VB.NET |
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.
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:
|
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> |
|||
WebParts - ASPX |
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; } } Protected Sub WebPartManager1_AuthorizeWebPart(sender As Object, e As WebPartAuthorizationEventArgs) If Not String.IsNullOrEmpty(e.AuthorizationFilter) Then Dim roles As String() = e.AuthorizationFilter.Split(",") e.IsAuthorized = Me.CheckRoleUser(roles) Else e.IsAuthorized = True End If End Sub Private Function CheckRoleUser(allowedRoles As String()) As Boolean For Each role As String In allowedRoles If User.IsInRole(role) Then Return True End If Next Return False End Function |
|||
C# | VB.NET |
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#. function doClick(index, numTabs, id) { document.all("tab" + id, index).className = "tab"; for (var i=1; i 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:
|
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:
|
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:
|
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> |
|||
Web.Config |
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(); } } Protected Overrides Sub OnInit(e As EventArgs) MyBase.OnInit(e) If Me.WebPartManager1.Personalization.Scope = PersonalizationScope.User AndAlso WebPartManager1.Personalization.CanEnterSharedScope) Me.WebPartManager1.Personalization.ToggleScope() End if End Sub |
|||
C# | VB.NET |
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; } } < WebBrowsable, WebDisplayName("Cor da Barra de Navegação"), WebDescription("Cor que aparecerá como Background da Barra de Navgegação"), Personalizable(PersonalizationScope.Shared) > Public Property CorBarraNavegacao As String Get Return Me.BarraNavegacao.BgColor End Get Set(Value As String) Me.BarraNavegacao.BgColor = Value End Set End Property |
|||
C# | VB.NET |
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; } Private _isDirty As Boolean; Public ReadOnly Property IsDirty As Booelan Get Return Me._isDirty End Get End Property Public Sub Load(state As PersonalizationDictionary) Dim entry As PersonalizationEntry = DirectCast(state("TextBoxValue"), PersonalizationEntry) If Not IsNothing(entry) Then Me.txtValor.Text = entry.Value.ToString() End If End Sub Public Sub Save(state As PersonalizationDictionary) state("TextBoxValue") = New PersonalizationEntry( Me.txtValor.Text.Trim(), PersonalizationScope.User) End Sub Protected Sub txtValor_TextChanged(sender As Object, e As EventArgs) Me._isDirty = True; End Sub |
|||
C# | VB.NET |
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.
Conclusão:
Como vimos através deste artigo, as Web Parts disponibilizam uma grande gama de controles e funcionalidades para simplificar e tornar mais ricas as nossas aplicações, que anteriormente necessitavam escrever muito código (principalmente código cliente (Javascript e DHTML)) para alcançar o mesmo resultado.