DefaultButton dentro do GridView

Uma das coisas que me perguntam é como implementar o DefaultButton em um Form/Panel quando estamos em uma edição dentro de um controle DataBound, como é o caso do GridView.

Para fazer isso funcionar, não tem lá muito segredo: basta interceptar a renderização do controle no evento PreRender e verificar se o mesmo está em modo de edição. Para isso, podemos utilizar a propriedade EditIndex, que retorna um valor inteiro indicando se o GridView está com algum linha em mode de edição. Caso esteja, voce acessa a linha em questão e procura pelo controle (button) que deverá ter seu evento Click disparado quando a tecla Enter for pressionada. Quando encontrado, voce recupera a propriedade UniqueID e atribui a mesma na propriedade DefaultButton do Form/Panel. Um exemplo simples é mostrado abaixo:

protected void GridView1_PreRender(object sender, EventArgs e)
{
    if (this.GridView1.EditIndex > -1)
    {
        this.form1.DefaultButton =
            this.GridView1.Rows[this.GridView1.EditIndex].Cells[0].Controls[0].UniqueID;
    }
}

A propriedade UniqueID retorna uma string representando o nome do controle (e, neste caso, do botão de edição) dentro da hierarquia de sub-controles dos controles DataBound.

Parâmetro customizado em controles DataSource

Há algum tempo eu falei aqui sobre controles Data Sources (ObjectDataSource, SqlDataSource, etc.). Esses controles possuem tipos de parametros pré-definidos, como é o caso do CookieParameter, ControlParameter, SessionParameter, etc..

Só que há situações em que precisamos customizar tais tipos de parametros, ou melhor, extrair a informação de um local em que os parametros pré-definidos não nos ajudam. Para este cenário, podemos criar um parametro customizado, herdando da classe base Parameter, como é mostrado abaixo:

public class UserNameParameter : Parameter
{
    public UserNameParameter() { }

    //outras implementações//membros

    protected override object Evaluate(HttpContext context, Control control)
    {
        if (context == null || !context.User.Identity.IsAuthenticated)
            return null;

        return context.User.Identity.Name;
    }
}

Com esse controle criado, podemos utilizá-lo no interior de um controle ObjectDataSource, como podemos visualizar no trecho de código abaixo:

<%@ Register TagPrefix=”ia” Assembly=”ClassLibrary1″ Namespace=”ClassLibrary1″ %>

<asp:ObjectDataSource ID=”ObjectDataSource1″ runat=”server”
    SelectMethod=”RecuperarUsuarios” TypeName=”ColecaoDeUsuarios”>
    <SelectParameters>
        <ia:UserNameParameter Name=”nome” />
    </SelectParameters>
</asp:ObjectDataSource>

//Classe ColecaoDeUsuarios:

public List<Usuario> RecuperarUsuarios(string nome) { }

É importante lembrar que o atributo Name é herdado da classe Parameter e voce deverá definí-lo com o nome do parametro especificado no método RecuperarUsuarios (método de Select). Para finalizar, voce não precisa se restringir as propriedades existentes na classe Parameter; voce pode estar criando as tuas próprias propriedades (se preocupando em guardar as informações no ViewState) e permitindo que o usuário (outro desenvolvedor) atributo valor para ela durante a configuração do controle Data Source.

ASP.NET MVC – Preview 2

Há algum tempo eu falei sobre algumas das funcionalidades disponibilizadas pelo ASP.NET MVC. Recentemente a Microsoft disponibilizou a versão 2, com algumas mudanças e melhorias. Abaixo estão listadas algumas delas:

  • Todos os tipos que compõem o Framework estavam dentro do assembly System.Web.Extensions.dll. Nesta versão a Microsoft desacoplou isso, “quebrando” em tres assemblies distintos (todos podendo ser rodados em partial trust):
    • System.Web.Mvc.dll: Este assembly é o core dos aplicativos MVC.
    • System.Web.Routing.dll: O roteamento consiste em ter URLs amigáveis ao invés de URLs que apontam para um recurso físico, como é o caso de uma página ASPX. Como essa característia não necessariamente é de aplicativos baseados em MVC, a Microsoft decidiu isolar isso em um assembly, permitindo que façamos o uso deste em aplicativos ASP.NET WebForms. O Phil Haack e o Luis Abreu escreveram sobre isso aqui e aqui.
    • System.Web.Abstractions.dll: Este assembly acomoda os tipos (para ambientes de testes) que serão utilizados para efetuar mocks dos objetos concretos.
  • Não há mais uma template de projeto para testes de aplicações baseadas em MVC. Agora, quando criamos um projeto MVC, uma pop-up é exibida, perguntando se voce deseja ou não criar um projeto de testes, dando suporte a qualquer runtime que queira utilizar (NUnit, xUnit, MSTest, etc.).
  • As rotas eram antes definidas como [ ]. Agora fazemos isso através de { }.
    • Ainda sobre rotas: na declaração das mesmas, é possível agora definirmos um “catchall” e, com isso, o runtime será capaz de pegar todos os parametros que são passados na URL. Este vídeo mostra a implementação desta técnica.
  • Os métodos que representam ações não precisam mais ser decorados com o atributo ControllerActionAttribute. Qualquer método público de um controller pode agora ser utilizado pelo runtime. Agora, o que voce precisa fazer é barrar o acesso, ou seja, se voce tem um método público mas não quer que este esteja disponível para que a aplicação possa consumí-lo, então voce deve decorar este método com o atributo NoActionAttribute.
  • Há um novo atributo chamado ActionFilterAttribute que voce poderá utilizar para interceptar a execução de uma determinada ação. Esse atributo, trata-se de uma classe base que deve ser herdada. Essa classe base fornece dois principais métodos: OnActionExecuting e OnActionExecuted que, de acordo com a nossa necessidade, sobrescrevemos cada um destes métodos e efetuamos a customização. Essa nova classe dará origem a um novo atributo que, por sua vez, deverá ser decorado nos métodos dos controllers que deverão invocá-los. Maiores informações e como proceder para criá-lo, consulte este vídeo.

Finalização de Variáveis de Sessão

Muitos interceptam o fechamento do navegador para finalizar alguma tarefa no servidor. A mais comum delas é “matar” a sessão do usuário, evitando que a mesma espere o timeout para ser removida da memória. Sendo assim, é necessário que voce invoque um método server-side para isso.

Uma alternativa é, no evento onUnload do JavaScript, abrir uma pop-up, apontando para uma página ASPX que, em seu evento Load, chame o método Abandon, remover individualmente cada uma delas ou fazer qualquer outro tipo de finalização. O problema desta técnica é com relação aos bloqueadores de pop-up que muitos navegadores possuem e, com essa funcionalidade habilitada, a abertura da janela de finalização não seria possível.

Sendo assim, podemos utilizar o Client Side Callbacks do ASP.NET 2.0 para isso. O código para isso, ficaria mais ou menos como:

public partial class _Default : System.Web.UI.Page, ICallbackEventHandler
{
    protected void Page_Load(object sender, EventArgs e)
    {
        if (!Page.IsCallback && Request.Browser.SupportsCallback)
        {
            string funcaoJS = this.ClientScript.GetCallbackEventReference(
                this,
                “””,
                “””,
                “””,
                “””,
                true);

            body.Attributes.Add(“onUnload”, funcaoJS);
        }

        //Adicionando Informações a uma Dummy Session
        HttpContext.Current.Session[“Dummy”] = “IsraelAece”;
    }

    public string GetCallbackResult() { return “”; }

    public void RaiseCallbackEvent(string eventArgument)
    {
        HttpContext.Current.Session.Remove(“Dummy”);
    }
}

Isso fará com que um trecho de código JavaScript seja embutido no cliente e, quando o evento onUnload ocorrer, o método será disparado removendo/executando o código para remoção de informações pertinentes ao usuário corrente.

Duplex Services no ASP.NET

O WCF fornece uma funcionalidade chamada Duplex Services. Este tipo de serviço permite que o servidor invoque um método do lado do cliente através de callbacks.

Quando estamos trabalhando com Windows Forms, o consumo deste tipo de serviço não tem muito segredo, pois voce pode apontar para um método dentro do teu formulário ou até mesmo em uma classe do teu projeto que, quando chegar o momento, o runtime do WCF o invocará. No ASP.NET as coisas funcionam de forma diferente. Uma vez que voce requisita uma página ASPX, o ASP.NET cria o objeto correspondente a mesma, executa e, por fim, o descarta. Com isso, se apontarmos o callback de um serviço duplex para um método qualquer da página, muito provavelmente, quando chegar o momento do serviço executá-lo, ele já não existirá mais.

Para resolver, ou melhor, aguardar até que o callback seja disparado, voce pode utilizar um sincronizador, como o é o caso do ManualResetEvent que irá aguardar até que o callback seja disparado. O exemplo desta utilização é mostrado através do trecho de código abaixo:

public partial class _Default : Page, IServiceCallback
{
    private ManualResetEvent _mre = new ManualResetEvent(false);

    protected void Page_Load(object sender, EventArgs e)
    {
        using (ServiceClient proxy =
            new ServiceClient(new InstanceContext(this)))
        {
            proxy.Send(“Israel”);
            _mre.WaitOne();
        }
    }

    public void Notification(string message)
    {
        Response.Write(message);
        _mre.Set();
    }
}

Como podemos notar, o método WaitOne aguardará até que o método Set seja disparado que, por sua vez, está sendo invocado dentro do método de callback.

Instalação padrão do Membership, Roles e Profile

As funcionalidades Membership, Roles e Profile do ASP.NET 2.0 vem por padrão habilitadas. A questão é que essa configuração padrão demanda ter instalado na máquina onde corre a aplicação (geralmente a máquina do desenvolvedor), o SQL Server Express.

Em uma aplicação recém criada não haverá nenhuma configuração no arquivo Web.Config. Se analisarmos algumas seções que estão presentes do arquivo machine.config, temos:

<connectionStrings>
    <add name=”LocalSqlServer” connectionString=”data source=.SQLEXPRESS;Integrated Security=SSPI;AttachDBFilename=|DataDirectory|aspnetdb.mdf;User Instance=true” providerName=”System.Data.SqlClient”/>
</connectionStrings>

<membership>
    <providers>
        <add name=”AspNetSqlMembershipProvider” type=”System.Web.Security.SqlMembershipProvider, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a” connectionStringName=”LocalSqlServer” …. />
    </providers>
</membership>

Com isso, quando iniciamos o ASP.NET Configuration, ele irá tentar acessar (ou criar) esta base de dados. Se não tiver o SQL Server Express instalado, esse processo resultará em um erro. Para resolver, voce tem duas alternativas:

  1. Instalar o SQL Server Express.
  2. Configurar para um servidor SQL Server existente. Se optar por essa alternativa, voce deve configurar o seu arquivo Web.Config da seguinte forma:

<?xml version=”1.0″?>
<configuration>
  <connectionStrings>
    <clear/>
    <add
      name=”SqlConnectionString”
      connectionString=”Data Source=local;Initial Catalog=BaseDeDados;Integrated Security=True;”/>
  </connectionStrings>
  <system.web>
    <membership defaultProvider=”SqlMembershipProvider”>
      <providers>
        <clear/>
        <add
          name=”SqlMembershipProvider”
          type=”System.Web.Security.SqlMembershipProvider”
          connectionStringName=”SqlConnectionString”
          ….. />
      </providers>
    </membership>
  </system.web>
</configuration>

É importante notar que o elemento <clear /> remove a conexão e o provider do membership que são configurados por padrão. Além dessa configuração prévia, voce precisará também criar os objetos (tabelas, stored procedures e views) necessários para que essas funcionalidades trabalhem. Para isso, voce utilizará o utilitário aspnet_regsql.exe e pode encontrar maiores informações aqui.

Auto-Save do Profile

A utilização do Profile em aplicações escritas sob o ASP.NET 2.0 tem crescido bastante e, com isso, há um detalhe importante que deve ser analisado quando utilizamos esta funcionalidade.

O que me refiro é com relação ao auto-save do mesmo. Por padrão, quando a página finaliza a sua execução, o módulo ProfileModule invocará o evento ProfileAutoSaving durante o evento EndRequest, chamando o método Save da classe ProfileBase, responsável por persistir os dados do mesmo na DB.

A questão é que, na maioria dos casos, as informações do Profile são alteradas em apenas uma página (página de configurações) e, nas demais, é apenas lido essas informações. Com isso, o resto das páginas invocam todas essas rotinas desnecessariamente. Se este é o seu cenário, ou ainda, se em algumas páginas altera o conteúdo, então o que pode fazer é desabilitar o auto-save e, explicitamente, salvar quando for necessário para ter uma melhor performance.

Essa configuração é definida no arquivo Web.Config, através do atributo automaticSaveEnabled do elemento profile. Para desabilitar, basta definir o atributo automaticSaveEnabled para false, como é mostrado abaixo:

<profile defaultProvider=”SqlProfileProvider” automaticSaveEnabled=”false”>
   ….
</profile>

… depois, quando em alguma página precisar alterar o conteúdo, faz:

Profile.CurrentLanguage = “pt-BR”;
Profile.Save();

Adicionando Mensagens no ValidationSummary

Uma pergunta que fizeram no fórum e que achei interessante é como adicionar uma mensagem customizado no controle ValidationSummary sem ter um validator control vinculado. Pois bem, os controles de validação implementam direta ou indiretamente a interface IValidator. Essa interface fornece 3 membros: ErrorMessage { get; set; }, IsValid { get;set } e Validate().

A classe Page possui uma propriedade chamada Validators e que a mesma expõe a coleção de validadores contidos na página. Sendo assim, basta criarmos uma classe e implementarmos a interface IValidator. Algo mais ou menos como:

using System;
using System.Web.UI;

public class CustomErrorMessage : IValidator
{
    public CustomErrorMessage(string errorMessage)
    {
        this.ErrorMessage = errorMessage;
    }

    public string ErrorMessage { get; set; }

    public bool IsValid
    {
        get { return false; }
        set { }
    }

    public void Validate() { }
}

Depois da classe criada, basta utilizarmos em qualquer página da aplicação:

int valor = 0;

try
{
    int resultado = 123 / valor;
}
catch(DivideByZeroException)
{
    this.Validators.Add(new CustomErrorMessage(“Divisão por 0.”));
}