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)

Verificando autorização através de Roles

Em aplicações ASP.NET ou qualquer outro tipo de aplicação, é comum verificar se um determinado usuário tem permissão para algum recurso, como por exemplo uma página, botão, comando, etc.. Nos últimos dias, eu recebi um e-mail onde um rapaz tinha problemas de performance neste ponto.

Na página dele, existe um botão de exclusão para cada linha do controle GridView. Então, no evento RowDataBound do mesmo, ele verifica se o usuário corrente está ou não em uma role específica. Mas o problema é que ele chama o método IsUserInRole para cada linha/registro. Em centenas de linhas/registros no GridView, este método será sempre chamado e, para cada chamada, todo o código deste método será executado. Abaixo o código antigo e o código melhorado:

[ Antigo ]
if(e.Row.RowType == DataControlRowType.DataRow) {
     e.Row.FindControl(“btnDelete”).Visible = Roles.IsUserInRole(“Administrator”);
}

[ Novo ]
private bool _isInAdministratorRole;
//….
this._isInAdministratorRole = Roles.IsUserInRole(“Administrator”);
//….
if(e.Row.RowType == DataControlRowType.DataRow) {
     e.Row.FindControl(“btnDelete”).Visible = this._isInAdministratorRole;
}

Pequenas técnicas como esta podem aumentar a performance da aplicação. “Caching code” é explicado no livro Code Complete – Segunda Edição, no capítulo 26, páginas 628 e 629.

Existem outras técnicas relacionadas com roles em uma aplicação ASP.NET. Estas técnicas permitem armazenar as roles em cookies do lado do cliente através do atributo cacheRolesInCookie. Ele previne em cada requisição, fazer uma nova query no banco de dados (ou outro repositório), para procurar pelas roles específicas do usuário que está logado no momento e anexar no objeto HttpContext.

Armazeando roles no cliente podem aumentar a performance se sua aplicação tem uma lentidão no acesso a dados ou um número grande de roles. Mas essa técnica tem problemas de segurança e voce precisa ser bastante cuidadoso em alguns pontos:

  1. Cookies com informações de autorização serão persistidos no profile do usuário e, um hacker que tiver acesso físico a máquina deste usuário poderá acessá-lo e, conseqüentemente, alterá-lo. Isso também causará problemas para aqueles usuários que esquecem a aplicação aberta (esquecendo de fazer o logout) em máquinas compartilhadas entre vários usuários.
  2. Enviar cookies sem SSL também pode compromenter a segurança, já que hackers podem pegar uma cópia do cookie e emular uma determinada role e elevar os privilégios dentro do sistema.

Se o caching de roles do lado do cliente for necessário (cookies), então habilite o atributo cookieRequireSSL no elemento roleManager por questões de segurança. Mas SSL exige um certificado (se voce não tiver, é necessário comprá-lo) e a performance pode ser prejudicada. É muito importante analisar o cenário e adotar a melhor estratégia.

Client-Side Callbacks e Exceptions

Para aqueles que já trabalharam com Client-Side Callbacks sabem que quando adicionamos a referencia para ele dentro de um evento cliente de um determinado controle, podemos definir uma função, também cliente, que será disparada caso dentro do evento server-side que será executado atire uma excessão.

Só que há um problema neste cenário: se a função server-side que será executada disparar a Exception e o atributo mode do elemento customErrors que está no Web.Config estiver como On ou RemoteOnly (que é o padrão), teu callback nunca trabalhará corretamente, e a seguinte mensagem será passada como argumento para a função de tratamento de erro no cliente:

There was an error in the callback.

Isso ocorre porque quando uma excessão acontece no servidor, a página é modificada, porque o customErrors intercepta o processo e, consequentemente, não devolve ao cliente o que é esperado, ou seja, a mensagem de erro da Exception.

Se você definir o customErrors como Off, o processo resultará com sucesso, mas dependendo da sua estratégia de tratamento de erros, poderá comprometer a aplicação, pois o Off devolve os detalhes do erro para o cliente. Para resolver, talvez será necessário isolar a página que faz o uso do callback em um diretório e, dentro dele, adicionar um novo arquivo Web.Config e sobrescrever o customErrors para Off e tratando todos os pontos da(s) página(s) que podem ocasionar erros com Try.Catch.Finally.

Escrevendo Texto em Imagens

O .NET Framework fornece uma série de classes, que estão contidas dentro da DLL System.Drawing.dll (Namespace System.Drawing) para a manipulação de imagens. Utilizaremos algumas dessas classes para isso, mas antes de efetivamente entrarmos nesta análise, vamos entender a forma que vamos dispor isso ao usuário. Primeiramente vamos criar uma classe chamada GenerateDynamicImage que implmentará a interface IHttpHandler, e consequentemente vamos adicioná-la no arquivo Web.Config para que a mesma possa interpretar as requisições efetuadas à ela, que gerará a imagem do texto que vamos informar.

Abaixo é exibida o código da classe:

using System.Web;
using System.Drawing;
using System.Drawing.Text;
using System.Drawing.Imaging;

public class GenerateDynamicImage : IHttpHandler
{
     public void ProcessRequest(HttpContext context) 
     {
          //....
          string text = context.Request.QueryString["Text"];
          if(text != string.Empty){
               Bitmap bmp = null;
               Graphics graphic = null;
               try
               {
                    bmp = new Bitmap(200, 30);
                    graphic = Graphics.FromImage(bmp);
                    graphic.Clear(Color.LightBlue);
                    graphic.DrawString(text,
                         new Font("Verdana", 14, FontStyle.Bold),
                         Brushes.Black, 3, 3);

                    context.Response.ContentType = "image/GIF";
                    bmp.Save(context.Response.OutputStream, ImageFormat.Gif);
               }
               catch{}
               finally{
                    if(graphic != null) graphic.Dispose();
                    if(bmp != null) bmp.Dispose();
               }
          }
     }
}

Como podemos reparar, a classe implementa a interface IHttpHandler e através do método ProcessRequest recuperamos o string que será escrita através da QueryString (fornecida pelo HttpContext que é passado como parâmetro) e, através das classes Bitmap e Graphics configuramos a imagem, ajustando a cor, fonte, tamanho, etc., e mudamos o ContentType da requisição para que o ouput seja em formato de imagem. Finalmente, através da propriedade OutputStream, salvamos a imagem neste Stream que é enviado ao cliente.

Depois desta classe pronta, resta registrá-la no arquivo Web.Config da aplicação na seção de httpHandlers, como é mostrado abaixo:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
     <system.web> 
          <httpHandlers>
               <add 
                    verb="*" 
                    path="GenerateImage.ashx" 
                    type="WebApplication1.GenerateDynamicImage, WebApplication1"/>
          </httpHandlers>
     </system.web>
</configuration>

Finalmente para testarmos esse handler, criarmos uma página ASPX e colocamos nela um controle Label, TextBox, Button e um do tipo Image. No evento Click do Button adicionaremos o seguinte código:

this.Image1.ImageUrl = string.Concat("GenerateImage.ashx?Text=", this.TextBox1.Text.Trim());

Validação de QueryString

Pelo que noto em grande parte dos sites que visito é que poucos se preocupam em tratar a QueryString. Nem que ao menos crie uma página de erro customizada e a defina dentro do elemento customErrors no arquivo Web.Config da aplicação. E olha que nem as grandes corporações salvam: 🙂

[ Correto ]: http://ww16.itau.com.br/atendimento/faq/resp_topicos.aspx?assunto=5
[ Erro1 (string) ]: http://ww16.itau.com.br/atendimento/faq/resp_topicos.aspx?assunto=broken
[ Erro2 (overflow) ]: http://ww16.itau.com.br/atendimento/faq/resp_topicos.aspx?assunto=9999999999999

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.