Israel Aece

Software Developer

WebParts - Introdução

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:

Propriedade Descrição
CatalogIconImageUrl Define uma imagem que representará essa WebPart dentro de um catálogo de controles.
Description Especifica um texto que detalha superficialmente o que faz essa WebPart para ser usada como ToolTip dentro do catálogo de controles.
Subtitle Quando essa propriedade é definida, o valor desta é concatenado com o valor especificado na propriedade Title, formando assim um título mais completo/detalhado.
Title Define o título da WebPart.
TitleIconImageUrl Através desta propriedade, você pode definir o caminho até uma imagem, que será exibida na barra de título da WebPart, podendo ser a mesma que foi especificada na propriedade CatalogIconImageUrl.
TitleUrl Especifica uma URL contendo informações adicionais a respeito da sua WebPart.

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!");
    }
}

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.

 

Controle Descrição
WebPartManager

Este é um dos principais controles, já que gerencia todas as WebParts e WebPartZones da página. Entre as suas funcionalidades temos: lista e relacionamento entre as WebParts, adição e remoção de WebParts, conexões, personalização e manutenção de estado, define modos de visualização da página, entre outros. Esse controle não tem nenhuma aparência/visualização em runtime e deve existir apenas um controle por página.

ProxyWebPartManager

Este controle é utilizado para quando há conexões estáticas entre a Master Page e as Content Pages, ou seja, precisamos estabelecer conexões dentro das Content Pages com um controle WebPartManager declarado na Master Page. Como temos apenas um controle WebPartManager por página, é muito comum quando temos uma Master Page colocar o WebPartManager dentro dela e, como ela é mesclada com a Content Page em runtime, um único controle WebPartManager gerenciará todas as WebParts de todas as Content Pages.

WebPartZone

Este controle contém as WebParts em si. Os controles WebPartZone delimitam o local físico onde as WebParts serão armazenadas e exibidas. As WebPartZone ainda permitem configurar algumas propriedades, como por exemplo, a aparência que as WebParts terão quando forem colocadas dentro dela. Uma das principais propriedades é a ZoneTemplate. Essa propriedade recebe uma referência de uma classe que implementa a interface ITemplate, onde podemos definir controles estáticos que podem ser declarados ainda em design-time.

CatalogZone

Através deste controle conseguimos delimitar uma zona onde poderemos incluir dentro dela as zonas conhecidas como catálogos, que irão listar algumas WebParts que estarão disponíveis dentro da página e, conseqüentemente, permitirão a interação do usuário para personalizar a página, podendo escolher qualquer uma destas WebParts para visualizar/manipular. Este controle também é dotado de uma propriedade chamada ZoneTemplate do tipo ITemplate e, em seu interior, podemos fazer o uso dos seguintes controles de catálogo: DeclarativeCatalogPart, PageCatalogPart e ImportCatalogPart.

DeclarativeCatalogPart

Este catálogo permite-nos adicionar controles de forma declarativa, ou seja, via HTML ainda em design-time. Esse controle disponibiliza uma propriedade chamada WebPartsTemplate onde dentro dela definiremos as WebParts, que serão exibidas ao usuário final em uma listagem, possibilitando que ele adicione tais WebParts onde desejar.

PageCatalogPart

Este catálogo lista todas as WebParts que foram removidas da página pelo usuário, permitindo ao usuário trazer a WebPart de volta quando desejar. Sendo assim, só é viável o uso deste controle quando queira que o usuário tenha essa flexibilidade.

ImportCatalogPart

Através deste controle é permitido que o usuário envie um arquivo (via upload) com extensão *.webpart para que a mesma possa ser importada para dentro deste controle e, conseqüentemente, o usuário possa fazer o uso dela, adicionando-a na zona que desejar. Veremos o uso deste controle na íntegra e também da exportação de WebParts nas próximas seções.

EditorZone

Assim como o controle CatalogZone, o EditorZone também delimita uma zona onde dentro dela definiremos outros controles que permitirão a edição das WebParts existentes na página. Isso permitirá que o usuário altere a visualização e configuração das WebParts, salvando tais configurações para permitir que o mesmo acesse mais tarde e as modificações sejam mantidas. Essas modificações vão desde a aparência até o comportamento destas WebParts e, dentro deste controle, no interior da propriedade ZoneTemplate, podemos ter os seguintes editores: AppearanceEditorPart, BehaviorEditorPart, LayoutEditorPart e PropertyGridEditorPart.

AppearanceEditorPart

Este editor fornece propriedades para alterarmos a aparência de cada WebPart, como por exemplo, alterar o título apresentado pela WebPart.

BehaviorEditorPart

Este controle permite-nos configurar a utilização dos verbos expostos pelas WebParts, como por exemplo, AllowClose, AllowEdit, etc.

LayoutEditorPart

Este controle permite a configuração do estado e da localização da WebPart. Por estado, signifca 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.

PropertyGridEditorPart

Este controle 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

ConnectionsZone

Este controle permite que as WebParts sejam conectadas umas com as outras dinamicamente ou estaticamente, desde que as conexões estejam habilitadas nas WebParts. As conexões entre WebParts serão abordadas detalhadamente na seção Conexões, ainda neste artigo.

 WebParts.zip (293.75 kb)

Protegendo arquivos "não ASP.NET"

Nas versões 1.x do ASP.NET existe um problema quando nós requisitamos arquivos não ASP.NET, quais são "protegidos" através de Forms Authentication. O problema é que estes arquivos não passam pelos módulos de autenticação e autorização do ASP.NET, então independentemente das configurações no Web.Config, o recurso sempre será visível à todos os usuários, incluindo os usuários anonimos.

A solução para isso é mapear o arquivo protegido (extensão) usando o handler HttpForbiddenHandler no arquivo Web.Config da aplicação ou configurando o IIS diretamente, como eu mostrei neste post. Mas estas soluções são muito complicadas porque, no primeiro caso, o runtime do ASP.NET servirá todas as requisições e, consequentemente, a performance irá degradar; já a segunda solução, talvez é impossível porque o serviço de hospedagem não permite configurarmos o servidor deles.

O ASP.NET 2.0 resolveu este problema adicionando um novo handler chamado de DefaultHttpHandler (para os verbos: GET, HEAD e POST). Este handler será executado para todos os arquivos que não pertencem ao ASP.NET (como imagens, *.htm, *.asp, etc.), fazendo a validação do usuário e se o mesmo tem permissão para isso. Se for válido, o IIS devolverá a requisição ao responsável pelo processo deste recurso. Agora a performance é muito melhor e voce pode utilizar toda a infraestrutura (autenticação e autorização) do FormsAuthentication para proteger seus arquivos que não fazem parte do ASP.NET.

Segurança via Server.Transfer

Quando trabalhamos com acesso restrito à determinadas páginas/seções da aplicação ASP.NET, devemos ser muito cuidadoso com o uso do método Transfer da classe HttpServerUtility.

Imagine que o usuário com privilégios mínimos não tem acesso à uma determinada página por não pertencer a role "Administradores". Coloque um botão nessa sua página que todos tem acesso e chame o método Server.Transfer apontando para a página restrita. Rode a aplicação e clique no botão. Verá que mesmo que o usuário não tenha acesso aquela página específica, o mesmo conseguirá visualizá-la.

Isso acontece porque o processo de autenticação e autorização não acontece quando se usa o método Transfer. Isso na verdade já aconteceu, ou seja, essa validação já foi feita antes do ASP.NET chamar efetivamente o recurso (página) solicitado.

Para resolver esse problema voce tem duas formas; chamar o método Redirect ao invés do Transfer. Isso forçará uma requisição do browser/cliente, qual necessitará que o processo de autenticação e autorização novamente seja executado; já a segunda possibilidade é continuar utilizando o método Transfer e, na página de destino fazer a verificação se o usuário tem ou não acesso aquele recurso. Para isso, deve-se utilizar o método IsInRole.

EnableViewState e TextBox

Uma das grandes novidades do ASP.NET foi a introdução do Viewstate, o qual se encarrega de manter os estados dos controles entre os postbacks. Com isso, muitos pensam que a persistencia da propriedade Text está atrelada a propriedade EnableViewState do controle, ou seja, quando alguém não quer que a mesma seja mantida durante os postbacks, tenta definí-la como False, mas isso não é possível.

O que acontece é que o ASP.NET não usa o ViewState para isso e sim para uma outra finalidade. O segredo está na interface IPostBackDataHandler que o controle TextBox implementa. Ela contém um método chamado LoadPostData, que retorna um valor booleano indicando se o valor foi ou não alterado. É baseado neste retorno que é ou não disparado o evento TextChanged do TextBox. É passado como parametro para este método, a coleção de parametros enviados pelo post do formulário, qual internamente recupera o valor da propriedade value do respectivo controle. Podemos visualizar isso ao decompilar tal método:

protected virtual bool LoadPostData(string postDataKey, NameValueCollection postCollection)
{
      base.ValidateEvent(postDataKey);
      string text1 = this.Text;
      string text2 = postCollection[postDataKey];
      if (!this.ReadOnly && !text1.Equals(text2, StringComparison.Ordinal))
      {
            this.Text = text2;
            return true;
      }
      return false;
}

Se colocar o controle TextBox no Wacth do Visual Studio, verá que no momento do postback, o valor da propriedade Text não terá nada se a propriedade EnableViewState do controle estiver como False. Somente depois de passar pelo método LoadPostData da classe é que o valor da propriedade Text é definido/restaurado, já que faz sentido, pois o ViewState do controle está desabilitado e a propriedade Text retornará vazio, qual será diferente do valor vindo pela coleção de parametros do post do formulário. Logo, o valor da propriedade Text é mantido mesmo com a propriedade EnableViewState esteja False. Para ilustrar, veja o código decompilado desta propriedade:

public virtual string Text
{
      get
      {
            string text1 = (string) this.ViewState["Text"];
            if (text1 != null)
            {
                  return text1;
            }
            return string.Empty;
      }
      set
      {
            this.ViewState["Text"] = value;
      }
}

Com isso vemos o porque os valores são mantidos. Se a propriedade EnableViewState estiver como False, retornará string.Empty que é diferente do valor vindo pela coleção (postCollection). Seguindo o fluxo do método LoadPostData, veremos que a condicional if é atentida e o valor do Text mudado, ou melhor, mantido. Para finalizar, verá que o evento TextChanged SEMPRE SERÁ DISPARADO, a menos que a propriedade Text seja igual a string.Empty e, o valor armazenado no ViewState["Text"] é somente utilizado para verificar se o evento TextChanged deve ou não ser disparado.

Server.Transfer é limitado?

Eu estou trabalhando em um projeto ASP.NET e estou criando um handler, que obviamente implementa a Interface IHttpHandler, para que processe e gere um arquivo binário para forçar o download do mesmo.

Depois que configurei o arquivo Web.Config, a requisição para arquivos com extensão "*.abc" serão agora interceptados por este handler. Mas existe um grande problema aqui, porque eu estou utilizando o método Server.Transfer, então eu não posso enviar para um dos overloads deste método uma instancia deste handler que criei ou chamar diretamente o "caminho virtual", como "Pagina.abc". Voce pode confirmar essa informação decompilando o método Transfer utilizando o Reflector:

[ --- Suprimido --- ]
else if (!(handler is Page))
{
   error = new HttpException(0x194, string.Empty);
}
[ --- Suprimido --- ]
if (error != null)
{
   [ --- Suprimido --- ]
   throw new HttpException(SR.GetString("Error_executing_child_request_for_handler",
      new object[] { handler.GetType().ToString() }), error);
}

Independentemente do overload do método Transfer que voce use, a mensagem de erro é a mesma: "Error executing child request for [handler | Pagina.abc].". A razão porque eu não utilizo o método Response.Redirect é que eu preciso enviar parametros através do coleção de Context.Items por questões de segurança.

A solução temporária para isso é herdar a classe Page ao invés de implementar a interface IHttpHandler no meu handler, mas eu acredito que isso não seja lá muito elegante.

Control State

Eu estou finalizando a leitura do livro de ASP.NET 2.0 do Luis Abreu e lá ele fala sobre a funcionalidade Control State. Ela é bem legal porque os controles de servidor não mais salvam informações importantes sobre o seu funcionamento no ViewState.

Nas versões 1.x do ASP.NET, essas informações são armazenadas no ViewState então, quando voce desabilitava o ViewState, alguns controles não funcionavam corretamente. Por exemplo, desabilite o ViewState e tente mudar o índice de paginação de um controle DataGrid. Voce verá que o controle desaparecerá.

Com o ControlState, essas informações são armazenadas em outro local e, se você desabilitar o ViewState, o controle continuará trabalhando normalmente. Desabilitar o ViewState é muito importante quando você não precisa manter o estado dos controle durante os postbacks ou quando a performance da sua aplicação está baixa.

Security Trimming - Visualização de Items

A Microsoft implementou na versão 2.0 do ASP.NET uma opção para criar uma forma de navegação dentro de um determinado WebSite. Isso possibilita a definição dos items de menus via arquivo XML (*.sitemap), onde definimos a hierarquia dos items que serão utilizandos e, consequentemente, apresentados pelos mais diversos controles de navegação que o ASP.NET 2.0 prove.

Para aqueles que não sabem, os arquivos *.sitemap permite-nos definir (através de um atributo chamado roles do elemento siteMapNode) os papéis que o usuário deverá pertencer para que sejam possíveis a visualização ou não destes itens nos controles que os carregarem.

Há uma certa confusão quando desejamos trabalhar com "Security Trimming" dentro desta aplicação, pois não basta apenas definirmos o atributo securityTrimmingEnabled do provider para True e as roles que serão permitidas no elemento siteMapNode dentro do arquivo *.sitemap. Além disso, é necessário que, as roles definidas no arquivo *.sitemap, precisam estar em sincronia com os elementos authorization no arquivo Web.Config, pois o ASP.NET analisa as roles do usuário corrente e o elemento authorization no arquivo de configuração antes de exibir ou não o item no controle.

Mas tudo isso AINDA NÃO É SUFICIENTE. A questão é que, por default, o elemento authorization é permitido para todos os usuários (allow users="*"), independentemente das roles, logo, se não se atentar a isso, os items continuam sendo visíveis, porém não acessíveis. Para solucionar isso, voce precisa antes de ir definindo os recursos que cada role terá, negar o acesso a todos os usuários, assim como é mostrado no trecho de código logo abaixo:

<configuration>
  <system.web>
    <authorization>
      <deny users="*" />
    </authorization>
  </system.web>
  <location path="Pagamentos.aspx">
    <system.web>
      <authorization>
        <allow roles="Gerente" />
      </authorization
    </system.web>
  </location>
  <location path="Extrato.aspx" >
    <system.web>
      <authorization>
        <allow roles="Funcionario" />
      </authorization>
    </system.web>
  </location>
<configuration>