Israel Aece

Software Developer

Estendendo a classe MembershipUser

Uma das grandes questões que encontro por ai é com relação a API do Membership do ASP.NET 2.0. Muitos desenvolvedores precisam incrementá-la, ou melhor, adicionar novas propriedades para que a mesma ganhe novas características, e atendam a necessidade da aplicação que está sendo construída. Essas propriedades são provenientes de tabelas diferentes das criadas pelo utilitário aspnet_regsql.exe ou ainda, em alguns casos, novas colunas que foram adicionadas dentro das tabelas aspnet_Users e aspnet_Membership.

Já no código, o que geralmente se costuma fazer, é criar uma classe que herde diretamente de MembershipUser:

public class Usuario : MembershipUser { ... }

... e, dentro desta, adicionar as propriedades que estão sendo desejadas. Depois disso, será necessário customizar o provider que estamos utilizando que, na maioria das vezes é o SqlMembershipProvider, e sobrescrever alguns métodos para adicionar um trabalho extra, onde devemos buscar os dados adicionais. Eis abaixo um exemplo:

public class CustomSqlMembershipProvider : SqlMembershipProvider
{
    public override MembershipUser GetUser(string userName, bool userIsOnline)
    {
        MembershipUser user = base.GetUser(userName, userIsOnline);
        CustomUser custom = new CustomUser(user);
        custom.Telefone = "+55 (19) 5555-5555";
        return custom;
    }
}

Mas as mudanças não param por ai. Como a interface da classe Membership opera somente com o tipo MembershipUser, voce será obrigado em todos os momentos que precisar chamar um determinado método (como o GetUser acima), efetuar conversões para o seu tipo específico. Algo mais ou menos como:

CustomUser custom = Membership.GetUser("israelaece", false) as CustomUser;
if(custom != null)
    Response.Write(custom.Telefone);

Ainda para piorar as coisas, voce perderá toda um dos principais benefícios do Provider Model que é a reutilização de código pois, se amanhã voce precisar rodar a aplicação sob uma base de dados Access ou Oracle, voce terá que sobrescrever cada um dos respectivos providers para dar o devido suporte.

Ao meu ver, eu acredito que a classe MembershipUser deve ser utilizada única e exclusivamente para gerenciar a autenticação/identificação do usuário dentro da aplicação. Se voce precisar adicionar características para os usuários, então a minha recomendação é utilizar o Profile (que não deixa ser uma extensão do usuário). Caso isso não seja possível, crie uma API baseando-se no modelo de Provider Model, se desejar suportar os benefícios que ele proporciona. Se mesmo assim, nenhuma dessas alternativas forem possíveis, então voce poderia criar uma classe que expõe as novas propriedades do usuário, populando-a da forma tradicional, operando de forma independente a classe MembershipUser.

DTC e Virtualização

Recentemente tive um problema curioso devido a virtualização de servidores. O pessoal responsável pela infraestrutura utiliza a mesma base/imagem do sistema operacional quando um novo servidor é criado. Até então, sem nenhum problema aparente.

Como a aplicação que estava sendo instalada trata-se de um processo distribuído, com Message Queues, SQL Server e Windows Services, o DTC é necessário para coordenar todos os processos transacionais. Quando a aplicação roda, todas as transações estavam sendo abortadas.

Depois de uma busca aprofundada, encontrei o motivo do problema: há dentro do registro do Windows uma GUID que identifica o DTC. Justamente pelo fato de que todas as máquinas utilizam uma mesma base comum, essa GUID estava igual em todas elas. A solução para o problema, encontrada no final deste artigo, é criar uma nova GUID (via algum utilitário, como aquele do Visual Studio .NET) e renomear a chave que tem como Description MSDTC dentro de HKEY_CLASSES_ROOT\CID em cada máquina participante do processo.

Eis aqui maiores informações sobre ghosting, mas não me compete. ;)

Membership: Geração/Reset de Password

Se voce está utilizando as funcionalidades de autenticação do ASP.NET 2.0, então é provável que voce já se deparou com a necessidade de recuperar/redefinir (PasswordRecovery) a senha de uma determinado usuário. Há um detalhe importante a ser observado quando voce habilita esta funcionalidade.

O comportamento deste controle merece uma atenção especial para os dois cenários suportados:

  • enablePasswordRetrieval = true: quando esta opção está definida como true, obriga voce também a definir a propriedade passwordFormat para um valor diferente de Hashed. Se esta combinação de valores for atendida, a senha será recuperada da base de dados e enviada para o e-mail do respectivo usuário.
  • enablePasswordRetrieval = false: esta configuração é geralmente utilizada quando a propriedade passwordFormat é definida como Hashed, ou seja, não será possível recuperar a senha da base de dados, pois ela foi hasheada. Neste caso, quando o usuário utilizar o controle PasswordRecovery, então ele gerará uma nova senha e, é neste momento que começa toda a confusão.

O processo para a geração da nova senha invoca, indiretamente, o método GeneratePassword do provider selecionado que, na maioria dos casos, é o SqlMembershipProvider. Dentro deste método há uma chamada para o método estático GeneratePassword da classe Membership. A confusão ocorre aqui por causa de dois atributos que são configurados no arquivo Web.Config: minRequiredPasswordLength e minRequiredNonalphanumericCharacters. Esses valores servem apenas para validação de senhas informadas pelo usuário mas, para o processo de geração automática (reset), são desprezados.

O SqlMembershipProvider tem um comportamento padrão que é: se o valor informado no atributo minRequiredPasswordLength for menor que 14, ele assume 14; o valor do atributo minRequiredNonalphanumericCharacters é sempre informado mas, pela lógica implementada, não mudará em nada.

O que voce precisa fazer é sobrescrever o método GeneratePassword da classe SqlMembershipProvider e, colocar a regra necessária para mudar o comportamento padrão. Eis aqui uma possível implementação:

using System.Security.Cryptography;

public class CustomMembershipProvider : SqlMembershipProvider
{
    private static char[] chars = "a0bc1d2efgh3i4jkl5m6n7o8p9qrstuvuyzw".ToCharArray();

    public override string GeneratePassword()
    {
        if (this.MinRequiredNonAlphanumericCharacters == 0)
        {
            byte[] randomBytes = new byte[this.MinRequiredPasswordLength];
            char[] result = new char[this.MinRequiredPasswordLength];
            new RNGCryptoServiceProvider().GetBytes(randomBytes);

            for (int i = 0; i < this.MinRequiredPasswordLength; i++)
            {
                int index = ((int)randomBytes[i]) % chars.Length;
                result[i] = chars[index];
            }

            return new string(result);
        }
        else
        {
            return base.GeneratePassword();
        }
    }
}

//Configuração (Web.Config):
<membership defaultProvider="SqlMembershipProvider">
    <providers>
        <clear/>
        <add
            name="SqlMembershipProvider"
            type="CustomMembershipProvider"
            minRequiredNonalphanumericCharacters="0"
            minRequiredPasswordLength="5"/>
    </providers>
</membership>

Comparativo: MVC vs. WebForms

Recentemente a Microsoft disponibilizou a implementação do padrão MVC. Esta versão encontra-se em CTP e está disponível para ser baixada neste link. A idéia deste post é mostrar uma comparação saudável e técnica do modelo tradicional do ASP.NET (WebForms) em comparação com o  MVC.

Introdução

Desde o lançamento da plataforma .NET até a versão mais recente – 3.5 – o ASP.NET é baseado em um modelo chamado de WebForms que, desde então, foi uma das grandes mudanças, talvez uma das mais impactantes, que a Microsoft fez em sua estrutura de desenvolvimento para aplicações Web. O ASP classico deixou de ser utilizado, devido a estrutura estável, performática, reutilizável e simples que é o ASP.NET.

Por sua vez, o ASP.NET possui uma série de características que o tornam simples de utilizar, mas não menos robusto. A quantidade de controles ricos que o mesmo fornece é extremamente grande, onde precisaríamos de uma série de linhas de código HTML + CSS para ter um resultado igual. Mesmo com essa facilidade e toda sua extensibilidade (como é caso dos Control Adapters), muitos desenvolvedores sentem a necessidade de ter um maior controle sobre como o conteúdo está sendo renderizado para o cliente e como os dados que o cliente informa são enviados para a aplicação.

Devido a estrutura do ASP.NET e seus controles intrínsicos, a renderização final acaba sendo delegada ao runtime do ASP.NET e é neste momento que desenvolvedores sentem a necessidade de uma maior flexibilidade. O padrão MVC (Model-View-Controller) garante isso. Este padrão não nasceu agora. Já existem vários Frameworks que você pode acoplar ao ASP.NET e desenvolver aplicações baseadas neste modelo. O que vemos neste momento é uma implementação deste padrão pela própria Microsoft, integrando fortemente à infraestrutura do ASP.NET.

Arquitetura

O ASP.NET WebForms é baseado em um padrão chamado Page Controller.  Já o modelo MVC baseia-se em um outro padrão, chamado de Front Controller. Cada um desses padrões tem suas vantagens e desvantagens que podem ser analisadas e comparadas através do link definido acima.

Além dos padrões, o modelo WebForms possui algumas características muito interessantes, que tornaram o ASP.NET muito fácil de desenvolver, acabando com aquele abismo que existia entre programadores Windows e Web. Para que isso fosse possível, funcionalidades como o ViewState, CodeBehind, Server Controls foram essenciais, mas que, no mundo MVC, acabam não sendo utilizados.

Páginas vs. Ações

Quando uma requisição chega para uma aplicação ASP.NET baseada no modelo tradicional, a página requisitada é mapeada para uma página (*.aspx). Esta página contém código HTML que representa a visualização da mesma e que será processada pelo ASP.NET e, em seguida, gerado um output e enviado ao usuário que a requisitou.

No modelo MVC, isso mudará drasticamente. Ao invés de você fazer requisição para uma página, você requisitará uma ação. Esta ação nada mais é do que um método que estará dentro de um determinado Controller. O Controller é responsável por capturar as informações fornecidas pelo protocolo HTTP ou pelo usuário, manipular essas informações, acessar o Model e, finalmente, renderizar o conteúdo, através de uma View, para o usuário.

Runtime

Quando uma requisição chega para uma aplicação ASP.NET tradicional, o runtime primeiramente executará vários de passos até que a página ASPX seja efetivamente executada. Muitos deles serão omitidos neste post ou serão comentados superficialmente (para maiores detalhes, consulte este artigo). Durante os passos que são omitidos nestes posts, estão a construção do AppDomain e a criação de objetos extremamente importantes para a execução da mesma, como o HttpContext, HttpApplication, etc. Depois que estes objetos estão devidamente criados, em algum momento, o ASP.NET determina qual será o handler responsável por executar a página; assim que este handler é determinado, o método ProcessRequest da página é executado e o ciclo de vida da mesma inicia, executando os métodos que criam os controles na página (baseando-se no HTML), os eventos da própria página, como Init, Load, etc., e eventos de controles que, possivelmente, podem acontecer.

No modelo MVC esse comportamento é radicalmente diferente. O motivo é porque as requisições para aplicações MVC são feitas para ações e não para páginas. Devido a isso, um módulo chamado UrlRoutingModule é adicionado a esta aplicação que intercepta a requisição no evento PostResolveRequestCache e PostMapRequestHandler. Este módulo é responsável por efetuar o parser da requisição e encontrar uma rota que se encaixa com a requisição. O mapeamento das requisições para o Controller/Ação é realizado no arquivo Global.asax e será tema de um futuro post/artigo.
 
Depois disso, um handler chamado MvcHandler é disparado. Ele é responsável por encontrar e instanciar o Controller correspondente e executar a respectiva ação requisitada pelo usuário. A idéia dentro desta ação é acessar o Model para extrair informações, definir possíveis regras de exibição e renderizar a visualização através do método RenderView. Este método recebe como parâmetro uma string, que representa a página a ser exibida; a responsabilidade deste método é justamente encontrar tal página ASPX e executá-la.

Todos os Controllers das aplicações herdam direta ou indiretamente da classe Controller que fornece toda essa infraestrutra. Não diferente, as páginas que servem como View, devem herdar da classe ViewPage ou ViewPage<TViewData>. Apesar de ser um Framework ainda bem novo, já existe a possibilidade de você customizar alguns funcionalidades, como a criação do Controller (ControllerBuilder) e também da geração da View (ViewFactory) e que podem ser facilmente plugáveis à infraestrutura do MVC, mas isso exige um post/artigo mais detalhado.

Segurança

Funcionalidades como o Membership e Roles continuam sendo suportadas normalmente, com apenas um detalhe com relação a restrição de páginas por grupos/usuários. Até então, quando queremos barrar o acesso a um determinado grupo/usuário à uma página, especificamos isso no Web.Config da aplicação, como pode ser visto através do trecho de código abaixo:

<location path="Contas/ContaCorrente.aspx">
  <system.web>
    <authorization>
      <allow roles="Gerentes" />
      <deny users="*" />
    </authorization>
  </system.web>
</location>

No modelo MVC isso não funcionará. Como falamos acima, o modelo MVC se baseia na execução de ações e não em páginas. Isso quer dizer que você terá que, ao invés de especificar a página, informar a ação e qual grupo/usuário poderá acessá-la. No modelo MVC deveríamos ter algo mais ou menos como:

<location path="Contas/ExibirContaCorrente">
  <system.web>
    <authorization>
      <allow roles="Gerentes" />
      <deny users="*" />
    </authorization>
  </system.web>
</location>

[ControllerAction]
public void ExibirContaCorrente()
{
     //regras
     RenderView(“ContaCorrente”);  //ContaCorrente.aspx
}

Em alternativa a isso, podemos recorrer a programação declarativa e utilizar atributos para especificar a autorização. Com a segurança declarativa, podemos decorar o método de ação com o atributo PrincipalPermissionAttribute e especificar o(s) grupo(s) que poderão ter acesso ao respectivo método. Fredrik Normén explica maiores detalhes desta implementação neste post.

Um outro exemplo é com relação a página de autenticação (Login) da aplicação. Ela também será uma ação de algum Controller que deverá ser executada quando o usuário desejar efetuar o login. Com isso, o atributo loginUrl do elemento <forms /> não apontará mais para uma página, mas sim para uma ação que se encarregará de exibir a página de autenticação para o usuário. Troy Goode mostra como aplicar essa técnica através deste post.

Testes

A partir do momento que você vê os testes como algo benéfico para o bom funcionamento das aplicações, você irá querer implementar isso em qualquer tipo de aplicação, mesmo em aplicações ASP.NET. Infelizmente, aplicações ASP.NET desenvolvidas em sua estrutura tradicional (WebForms), tornam a realização dos testes extremamente difícil, já que uma única classe/página é responsável por tratar da exibição dos dados, exibir os dados e capturar as informações fornecidas pelo usuário e enviar para a aplicação novamente. Esse modelo nos obrigará a instanciar essa classe e, para isso, será necessário que a mesma esteja sendo executada dentro do IIS, criando um forte acoplamento.

Isso já será bem mais flexível no modelo MVC, pois as três partes são independentes/desacopladas, permitindo que os testes sejam realizados de forma isolada ao resto do Framework. Somente um detalhe importante aqui é com relação a utilização de recursos específicos do protocolo HTTP (como QueryStrings, Forms, Headers, etc.); ao utilizar isso dentro da classe Controller você amarra isso ao ASP.NET e, segundo Phil Haack, eles estarão disponibilizando Interfaces que poderemos efetuar o mock destes objetos.

Server Controls (runat=”server”)

Acredito que aqueles que recorrem ao uso do modelo MVC provavelmente querem também ter o controle da criação dos controles e dificilmente deverão utilizar os controles fornecidos pelo ASP.NET.

Mesmo assim, felizmente a Microsoft se preocupou com a gama de controles que ela disponibiliza na ToolBox do Visual Studio .NET, e permite que utilizamos tais controles em aplicações que utilizam o modelo MVC, mas com algumas limitações:

• A forma que você popula os controles muda ligeiramente para se adequar a este modelo, mas ganhamos a facilidade do drag-and-drop e a geração automática do respectivo HTML. E além de tudo isso, a Microsoft garante que os Server Controls continuarão evoluindo com as futuras versões da plataforma e, conseqüentemente, suportados em ambos modelos.

• Apesar dos Server Controls serem suportados, o MVC não utiliza o conceito de Postback para se comunicar com o servidor, justamente porque tudo será baseado em ações, e o mesmo deverá ser redirecionado para uma ação específica dentro de algum Controller.

Outras Funcionalidades

Algumas funcionalidades do ASP.NET provavelmente perderão o sentido dentro deste modelo, como é o caso dos Control Adapters (já mencionado acima). Outras funcionalidades, que já utilizamos tradicionalmente, continuam sendo suportadas dentro deste modelo, como por exemplo ASCX, Master Pages, Session State, Caching, Url Authorization, etc..

Outras funcionalidades ainda não se sabe se serão suportadas dentro deste modelo, como é o caso das páginas assíncronas. Com este modelo, quem executa a parte “pesada” é o Controller e não mais a página. Sendo assim, não temos que fazer a página executar assincronamente já que, neste momento, o processo custoso já foi realizado. Imaginei que a Microsoft pudesse fazer algo como:

[ControllerAction]
[ExecuteInBackground]
public void CalcularPorcentagemDeComissao(int? gerenteId)
{
     //processo custoso
     RenderView(“Resultado”, null);
}

… e assim o processo seria executado em uma worker thread ao invés de utilizar uma thread retirada do ThreadPool que poderia servir páginas mais simples/leves.

Futuro

Este post foi baseado na versão CTP do ASP.NET MVC Framework. Provavelmente este Framework evoluirá bastante até sua versão final. Acredito que em breve, a Microsoft lançará uma nova versão, com possíveis melhorias no que diz respeito a execução e renderização dos controles. Até lá, muitas informações serão disponibilizadas pela comunidade, como é o caso do MVC Toolkit.

Para aqueles que pretendem estudar mais sobre este Framework, aconselho que assinem os blogs do Scott Guthrie, Fredrik Normém, Phil Haack, Rob Conery, Scott Hanselman e Luis Abreu que, sob-demanda, publicarão informações importantes sobre o assunto e o blog do Eduardo Miranda, que fala muito sobre testes.

Conclusão

Particularmente, gosto bastante do modelo WebForms e, até o presente momento, sempre consegui extrair bons resultados deste modelo. Vejo o MVC como uma alternativa bastante interessante ao modelo tradicional e que acredito que será bem aceito pela comunidade. Mesmo que o código C# ou VB.NET que consta no arquivo ASPX é referente a renderização, não gosto muito desta mistura (um pouco confuso?), e acho que isso é talvez por estar habituado ao code-behind. O Framework vai evoluir muito e, acredito que em pouco tempo, teremos várias novidades e, conseqüentemente, poderemos tirar maiores conclusões a respeito destes dois modelos.