Israel Aece

Software Developer

Explorando Segurança do ASP.NET - Membership e MembershipUser

A classe estática Membership, encontrada dentro do Namespace System.Web.Security, é usada pelo ASP.NET para validar as credenciais do usuário e gerenciar as configurações do mesmo. Esta classe faz uso da classe FormsAuthentication (já existente nas versões anteriores do ASP.NET) para criar e gerenciar a autenticação do usuário dentro da aplicação Web.

Falando um pouco sobre o seu funcionamento: existe um membro interno chamado s_Provider do tipo MembershipProvider (a classe base para as classes concretas de Membership, como por exemplo o SqlMembershipProvider), o qual receberá a instância da classe concreta. Essa inicialização acontece quando um método interno chamado Initialize é executado. Ele se encarrega de extrair os providers do arquivo Web.Config e instanciá-los para que, quando chamarmos os métodos e propriedades, já sejam efetivamente os métodos e propriedades da classe concreta que queremos utilizar.

Como esta classe encapsula o acesso ao provider específico, ela fornece facilidades como: criação de novos usuários, validação de usuários, gerenciamento de usuários em seu repositório (SQL Server, ActiveDirectory, etc). Como já vimos anteriormente o funcionamento interno desta classe, podemos perceber que o ASP.NET confia no provider especificado no Web.Config para se comunicar com a fonte de dados. Como já sabemos, o .NET Framework inclui a classe SqlMembershipProvider para acesso e persistência dos dados no SQL Server e é este que vamos utilizar no decorrer dos exemplos deste artigo.

Mas esta arquitetura é extensível, ou seja, a qualquer momento eu posso criar o meu próprio provider de Membership, onde eu queira customizá-lo para um outro repositório qualquer. E, para que isso seja possível, é necessário herdarmos da classe abstrata chamada MembershipProvider e customizá-la de acordo com o nosso repositório. Para ilustar o cenário, imaginemos que desejamos ter um provider de Membership sendo persistido em um arquivo XML:

public class XmlMembershipProvider : MembershipProvider
{
    // customizo aqui todos os métodos e
    // propriedades necessárias
}

Depois do provider criado, basta definí-lo nas configurações do Web.Config e começar a utilizá-lo.

A configuração no arquivo Web.Config

Por padrão, todas as aplicações ASP.NET 2.0 já utilizam o Membership. Isso porque dentro do arquivo de configuração machine.config já temos um provider chamado AspNetSqlProvider que utiliza o SqlMembershipProvider. Este provider aponta para um banco de dados chamado ASPNETDB.MDF que encontra-se dentro da pasta App_Data da aplicação e é habilitado quando marcamos a opção AspNetSqlProvider no Web Site Administration Tool. Como o intúito do artigo é mostrar como configurar isso manualmente, vamos especificar o provider em nossa aplicação e configurá-lo para atender as nossas necessidades e, para isso, vamos até o arquivo Web.Config da aplicação para efetuar tal customização.

Já que não queremos utilizar o provider e o banco de dados ASPNETDB.MDF fornecido por padrão pela plataforma, devemos recorrer ao arquivo Web.Config para configurar o provider da forma que precisarmos. Para esta configuração temos os seguintes elementos: membership e providers, que deverão estar declarados dentro do elemento system.web. Estes elementos, por sua vez, possuem uma série de atributos que definem as configurações e possíveis comportamentos e validações que o nosso provider irá desempenhar.

Para cada provider que é adicionado dentro do elemento providers especificamos os atributos de configuração deste provider e, através de atributos definidos no elemento membership, definimos configurações a nível "global de providers". Veremos cada um dos possíveis atributos (voltado para o uso do SqlMembershipProvider) e seus respectivos valores nas tabelas abaixo:

Elemento membership
Atributo Tipo Valor Padrão Opcional Descrição
defaultProvider String AspNetSqlProvider Sim

O nome do provider que você vai utilizar. Lembrando que no elemento providers você pode especificar uma lista deles e é através do defaultProvider que você especifica qual deles utilizar.

userIsOnlineTimeWindow Inteiro 15 Sim

Define o tempo em que usuário é considerado como online, que é utilizado para calcular o número aproximado de usuários online.

hashAlgorithmType String SHA1 Sim

Utilizado para especificar o algoritmo de hashing que será usado pelo provider, sempre quando o mesmo precisar efetuar hashing em dados sigilosos.

 

Elemento add - providers [ "filho" do membership ]
Atributo Tipo Valor Padrão Opcional Descrição
applicationName String ApplicationVirtualPath Sim

Especifica o nome da aplicação em que o Membership está sendo utilizado, possibilitando desta forma, você trabalhar com múltiplas aplicações utilizando a mesma base de dados, mas também podendo utilizar o mesmo nome da aplicação para outras aplicações ASP.NET.

O provider que faz o uso do ActiveDirectory ignora esta propriedade.

Obs.: Atente-se quando você define como nome da aplicação o caracter /. Este caracter pega o nome do diretório virtual da aplicação corrente e atribui como nome da aplicação. Isso pode não refletir exatamente o seu ambiente de produção e, conseqüentemente, ter problemas em não encontrar os registros vinculados à aplicação.

commandTimeout Inteiro 30 (ADO.NET) Sim

Especifica o número em segundos para a configuração do SqlCommand. É através dele que definimos o tempo de espera entre a execução do comando e o erro.

connectionStringName String   Não

Especifica o nome da ConnectionString que irá ser utilizada pelo provider. Esta Connection String é especificada no elemento connectionStrings.

description String   Sim

Uma breve descrição do provider.

enablePasswordRetrieval Boolean False Sim

Especifica se será ou não permitido a recuperação do password do usuário. Se True, o provider permitirá a recuperação.

enablePasswordReset Boolean True Sim

Especifica se será ou não permitido reinicialização da senha do usuário. Se True, o provider permitirá a reinicialização.

maxInvalidPasswordAttempts Inteiro 5 Sim

Define o número de tentativas de acesso até que a conta do usuário seja bloqueada. O resultado dá-se da soma de tentativas inválidas de recuperação da palavra-chave ou senha.

minRequiredNonalphanumericCharacters Inteiro 1 Sim

Define o número de caracteres especiais que a senha deverá conter, não podendo ser definido com um valor menor que 0 ou maior que 128 ou ainda maior que o valor estipulado no atributo minRequiredPasswordLength.

minRequiredPasswordLength Inteiro 1 Sim

Define o número mínimo de caracteres para a senha. Também não pode ser definido com um valor menor que 0 ou maior que 128.

name String   Não

Define o nome do provider, que deverá ser informado no atributo defaultProvider do elemento membership quando você quiser utilizá-lo.

passwordAttemptWindow Inteiro 10 Sim

Define o intervalo de tempo em que é efetuada a contabilidade de tentativas de login falhadas. Quando chegamos ao fim do intervalo especificado neste atributo e o número de tentativas falhadas não atingiu o valor estipulado no atributo maxInvalidPasswordAttempts, os contadores são reinicializados.

passwordFormat String Hashed Sim

Define o formato de armazenamento de senhas na fonte de dados. Os possíveis valores para este atributo estão definidos no enumerador MembershipPasswordFormat e são exibidos abaixo:

  • Clear: As senhas não são criptografadas.
  • Encrypted: Senhas são criptografadas usando as configurações especificadas no elemento de configuração machineKey.
  • Hashed: As senhas são criptografadas utilizando o algoritmo de hash SHA1.

Obs.: Como algoritmos hash são irreversíveis, somente conseguiremos recuperar a senha se este atributo for diferente de Hashed.

passwordStrengthRegularExpression String   Sim

Especifica uma expressão regular para validar uma senha e esta expressão deverá funcionar com a classe RegEx.

requiresQuestionAndAnswer Boolean True Sim

Indica se é ou não necessário informar uma pergunta e sua respectiva palavra-chave para que isso funcione como uma espécie de "lembrete" para o usuário.

requiresUniqueEmail Boolean True Sim

Especifica se o endereço de e-mail do usuário deve ser único.

type String   Não

Indica o tipo que contém a implementação deste provider, ou seja, a classe concreta que implementa a classe abstrata MembershipProvider.

Agora que já conhecemos todos os parâmetros possíveis dos elementos acima, veremos abaixo um exemplo de como estar configurando isso dentro do arquivo Web.Config da aplicação ASP.NET:

<?xml version="1.0"?>
<configuration>
  <connectionStrings>
    <clear/>
    <add 
      name="SqlConnectionString"
      connectionString="Data Source=local;Initial Catalog=DBTest;Integrated Security=True;"/>
  </connectionStrings>
  <system.web>
    <membership defaultProvider="SqlMembershipProvider">
      <providers>
        <clear/>
        <add
          name="SqlMembershipProvider" 
          type="System.Web.Security.SqlMembershipProvider" 
          connectionStringName="SqlConnectionString" 
          applicationName="NomeAplicacao" 
          enablePasswordRetrieval="false" 
          enablePasswordReset="true" 
          requiresQuestionAndAnswer="false" 
          requiresUniqueEmail="false" 
          passwordFormat="Hashed" 
          minRequiredNonalphanumericCharacters="0" 
          minRequiredPasswordLength="8" />
      </providers>
    </membership>
  </system.web>
</configuration>

Algumas considerações do código acima: temos uma coleção de connectionStrings, onde através do elemento connectionStrings as informamos e no atributo connectionStringName especificamos o nome do conexão que iremos utilizar; outro ponto importante é o elemento add que é "filho" do elemento providers: como podemos ter uma coleção de providers, podemos adicionar quantos desejarmos e sempre fazemos isso adicionando um novo elemento add e as suas respectivas configurações.

Agora que já temos conhecimento suficiente para criar a infraestrutura e configurar um determinado provider, vamos analisar a classe Membership e seus respectivos membros. Veremos agora como manipular e gerir usuários dentro de uma base de dados SQL Server e, mais tarde, quando estivermos falando sobre os controles que o ASP.NET fornece, veremos como implementar a segurança em uma aplicação utilizando FormsAuthentication.

Não vou me preocupar aqui em mostrar e explicar as propriedades da classe Membership justamente porque são propriedades de somente leitura e apenas devolvem os valores que configuramos no Web.Config para o provider. Nos concentraremos apenas nos métodos, pois são o mais importante e nos dão todas as funcionalidades de manutenção e criação de usuários na base de dados.

Método Descrição
CreateUser

Adiciona um novo usuário na base de dados. Este método é sobrecarregado.

Como o próprio nome diz, ele adiciona um novo usuário na base de dados e retorna um objeto do tipo MembershipUser (veremos sobre ele mais abaixo) se o usuário for criado com sucesso. Se, por algum motivo, a criação do usuário falhar, uma Exception do tipo MembershipCreateUserException é atirada. Além disso, você também pode, através de um parâmetro de saída do tipo MembershipCreateStatus, recuperar o motivo pelo qual a criação falhou.

DeleteUser

Exclui um usuário na base de dados dado um nome de usuário. Este método é sobrecarregado.

Este método retorna um parâmetro booleano, indicando se o usuário foi ou não excluído com sucesso. Também é possível passar como parâmetro para este método um valor booleano indicando se os dados relacionados com este usuário serão também excluídos. O padrão é True.

FindUsersByEmail

Dado um e-mail para ser procurado, o método retornará (paginando) uma coleção do tipo MembershipUserCollection, onde cada elemento desta coleção é do tipo MembershipUser.

O SQL Server executa a busca utilizando o operador LIKE e você pode passar para o mesmo os caracteres de busca para que possa ser analisado de acordo com a sua necessidade. Lembrando também que há um overload para este método onde você pode especificar os parâmetros pageIndex, pageSize e totalRecords para retornar os dados paginados para a sua aplicação e ter uma melhor performance se houver uma quantidade considerável de registros na base de dados.

FindUsersByName

Dado um nome de usuário para ser procurado, o método retornará (paginando) uma coleção do tipo MembershipUserCollection, onde cada elemento desta coleção é do tipo MembershipUser.

O SQL Server executa a busca utilizando o operador LIKE e você pode passar para o mesmo os caracteres de busca para que possa ser analisado de acordo com a sua necessidade. Lembrando também que há um overload para este método que você pode especificar os parâmetros pageIndex, pageSize e totalRecords para retornar os dados paginados para a sua aplicação e, ter uma melhor performance se houver uma quantidade considerável de registros na base de dados.

GeneratePassword

Gera uma senha randômica dado um tamanho máximo de caracteres e o número de caracteres não alfanuméricos.

Este método é comumente utilizado para gerar senhas randômicas e também é chamado pelo método ResetPassword para reinicializar a senha do usuário, ou seja, criar uma espécie de "senha temporária". Se existir no mínimo um caracter alfanumérico, eles não poderão ser caracteres não "imprimíveis" ou caracteres ocultos. Eis aqui as possibilidades: !@#$%^&*()_-+=[{]};:<>|./?

GetAllUsers

O método retornará (paginando) uma coleção do tipo MembershipUserCollection contendo todos os usuários da base de dados, onde cada elemento desta coleção é do tipo MembershipUser.

Lembrando também que há um overload para este método que você pode especificar os parâmetros pageIndex, pageSize e totalRecords para retornar os dados paginados para a sua aplicação e, ter uma melhor performance se houver uma quantidade considerável de registros na base de dados.

GetNumberOfUsersOnline

Retorna o número de usuários online para a aplicação corrente, ou seja, a qual está definida na configuração do provider.

O critério para o usuário ser considerado como online é a data da última atividade ser maior que a hora corrente menos o valor especificado no atributo UserIsOnlineTimeWindow. A data da última atividade é atualizada para a data corrente quando as credenciais do usuário são passadas para o método ValidateUser ou UpdateUser ou quando você chama o método GetUser ou o mesmo método sobrecarregado, passando o parâmetro userIsOnline como True.

GetUser

Retorna um objeto do tipo MembershipUser que representa um usuário.

Se chamar o método GetUser sem nenhum parâmetro, o método retornará o usuário logado. Se optar por passar o nome do usuário para este mesmo método, mas utilizando uma sobrecarga dele, ele retornará os dados deste usuário especificado.

GetUserNameByEmail

Retorna um objeto do tipo MembershipUser baseando-se em um e-mail informado como parâmetro.

UpdateUser

Atualiza na base de dados as informações de um determinado usuário.

Esse método recebe um objeto do tipo MembershipUser. Este objeto pode ser um que recuperamos anteriormente da base de dados ou até mesmo um novo que acabamos de instanciar e definir seus valores.

ValidateUser

Dado um nome de usuário e senha, este método retorna um valor booleano indicando se é ou não um usuário válido.

Em alguns dos métodos acima comentamos sobre o objeto chamado MembershipUser. Veremos abaixo a lista de métodos e propriedades do mesmo com uma breve descrição.

Propriedade Descrição
Comment

Comentários adicionais sobre o usuário.

CreationDate

Data/hora em que o usuário foi criado dentro da base de dados.

Email

E-mail do usuário.

IsApproved

Indica se o usuário pode ou não se autenticar.

Mesmo informando o nome de usuário e senha válidos e esta propriedade estiver definida como False, o método ValidateUser retornará False.

IsLockedOut

Indica se o usuário está barrado devido a tentativas incorretas de acessar a aplicação.

IsOnline

Indica se o usuário está ou não online.

LastActivityDate

Recupera a data/hora do último acesso/atividade dentro da aplicação.

LastLockoutDate

Recupera a data/hora do último vez que esse usuário foi barrado.

LastLoginDate

Recupera a data/hora da última vez em que o usuário fez o login com sucesso na aplicação.

LastPasswordChangedDate

Recupera a data/hora da última vez em que o usuário alterou a sua senha.

PasswordQuestion

Recupera uma determinada questão para servir como "lembrete" da senha do usuário.

ProviderName

Recupera o nome do provider que o mesmo está utilizando.

ProviderUserKey

Recupera o identificador do usuário dentro da base de dados.

Na base de dados, o identificador é criado e armazenado como um tipo de dado chamado uniqueidentifier. Como a propriedade ProviderUserKey retorna um Object se quiser ter algo mais tipado, terá que convertê-lo para o objeto Guid, que corresponde ao tipo de dado uniqueidentifier da base de dados.

UserName

Recupera o nome do usuário.

 

Método Descrição
ChangePassword

Altera a senha do usuário na base de dados.

Este método recebe dois parâmetros: senha atual e a nova senha e, retorna um valor booleano indicando se a alterção aconteceu com sucesso. Vale lembrar que as senhas informadas neste método devem estar de acordo com as especificações informadas na configuração do provider no arquivo Web.Config.

ChangePasswordQuestionAndAnswer

Altera a pergunta e resposta que ajudam o usuário a lembrar a senha.

GetPassword

Recupera a senha de um determinado usuário.

Se a propriedade EnablePasswordRetrieval estiver definida como False, uma Exception será atirada. Se o atributo passwordFormat do provider estiver definido como Hashed, a senha antiga não será recuperada, mas uma nova senha será gerada e retornada através do método ResetPassword.

ResetPassword

Reinicializa a senha do usuário e a retorna ao chamador.

Se o atributo enablePasswordReset estiver definido como False e o método ResetPassword for chamado, uma Exception será atirada. Agora, se o atributo requiresQuestionAndAnswer estiver definido como True, você pode utilizar o método ResetPassword, desde que utilize a sobrecarga do mesmo, onde você deve fornecer a palavra-chave para o usuário.

UnlockUser

Desbloqueia um usuário para o mesmo poder voltar a acessar a aplicação. Um valor booleano é retornado indicando se o desbloqueio foi ou não realizado com sucesso.

Depois da teoria veremos abaixo um trecho curto que mostra como chamar esses métodos e propriedades via código:

// Criando Usuário
MembershipCreateStatus status = MembershipCreateStatus.UserRejected;
MembershipUser user =
    Membership.CreateUser(
        "IsraelAece",
        "P@$$w0rd",
        "israel@projetando.net",
        "Questão Password",
        "Resposta Password",
        true,
        out status);
Response.Write(string.Format("Status Criação: {0}", status.ToString());

// Recuperando Usuário
MembershipUser user = Membership.GetUser("IsraelAece");
if(user != null)
{
    Response.Write(string.Format("E-mail: {0}", user.Email));
}

// Excluindo Usuário
if(Membership.Delete("IsraelAece"))
{
    Response.Write("Usuário excluído com sucesso.");
}

// Populando um GridView
this.GridView1.AutoGenerateColumns = true;
this.GridView1.DataSource = Membership.GetAllUsers();
this.GridView1.DataBind();

Integração com outras tabelas

Umas das principais dúvidas é como integrar a tabela de aspnet_Membership com outras tabelas do banco de dados, como por exemplo, uma tabela com o endereço, R.G., C.P.F., entre outros dados. Neste caso cria-se uma coluna chamada UserId do tipo uniqueidentifier nesta tabela "filha" que será uma ForeignKey da tabela aspnet_Membership. Claro que o objeto MembershipUser ainda continuará com as mesmas propriedades e, apesar de um pouco complicado, você teria que sobrescrever o provider para poder contemplar esse novo design do seu objeto.

Para ilustrar, analise a imagem abaixo. Foi criada a relação entre as tabelas através do aspnet_Membership.UserId x Colaboradores.ColaboradorID para firmar o relacionamento entre as tabelas:

Figura 5 - Relacionamento com a tabela aspnet_Membership.

 

Explorando Segurança do ASP.NET - Arquitetura

O ASP.NET 2.0 inclui uma porção de novos serviços de persistência de dados em um banco de dados. Essa nova arquitetura é chamada de Provider Model e nos dá uma enorme flexibilidade, onde podemos trocar a fonte de dados/persistência e a aplicação continua trabalhando normalmente. Este provider é um módulo do software que estamos desenvolvendo que fornece uma interface genérica para uma fonte de dados, onde abstraem a base de dados. Além disso ser flexível, a qualquer momento podemos trocar a base de dados, essa arquitetura também é extensível, ou seja, se mais tarde quisermos customizar algo, podemos fazer isso sem maiores problemas.

A idéia dentro deste padrão é ter uma classe abstrata, onde dentro dela teremos métodos e propriedades que devem ser implementados nas classes concretas e, através de configurações no arquivo Web.Config, definimos a classe concreta que iremos utilizar para a aplicação. A questão é que essa classe concreta é instanciada em runtime, pois o ASP.NET recupera isso do arquivo de configuração e se encarrega de criar a instância correta da classe para uma determinada funcionalidade.

Essa arquitetura já não é lá muito nova. Se analisarmos o ASP.NET Fóruns ou até mesmo o PetShop 3.0, veremos que eles utilizam uma técnica bem parecida, onde fazem o uso dos padrões Abstract Factory e Factory Method (padrões Criacionais) para garantir essa genericidade. Isso faz com que devemos ter uma classe abstrata por trás a qual as classes concretam as implementam e o runtime se encarrega de criar a instância correta da classe concreta baseando-se nas configurações do arquivo Web.Config.

Essa arquitetura é usada extensivamente dentro do .NET Framework 2.0 (ASP.NET), onde temos classes abstratas para cada situação diferente e, para um determinado repositório de dados, uma classe concreta já implementada. Através da tabela abaixo veremos o nome da funcionalidade, a classe base e as classes concretas que se enquandram dentro do escopo do artigo:

Funcionalidade Classe Base Classes Concretas Descrição
Membership MembershipProvider SqlMembershipProvider
ActiveDirectoryMembershipProvider
Responsável por gerenciar os usuários de uma aplicação ASP.NET.
Roles RoleProvider SqlRoleProvider
WindowsTokenRoleProvider
AuthorizationStoreRoleProvider
Utilizado para gerir os papéis dos usuários dentro da aplicação ASP.NET.


Reparem que para uma determinada classe abstrata, como por exemplo MembershipProvider, já temos, por padrão, algumas classes que a Microsoft implementou para já utilizarmos a funcionalidade. Um exemplo disso é a classe SqlMembershipProvider, a qual utiliza uma base de dados SQL Server 2000 ou superior para disponibilizar o recurso. Como foi dito acima, não se restringe somente à isso. Temos classes para gerencimento de estado, Web Parts, Site Map, Profile, etc., que não foram citados/explicados por não fazerem parte do escopo deste artigo. Se no futuro precisarmos customizar alguma das funcionalidades da tabela acima para uma base de dados, como por exemplo Oracle, basta criarmos uma classe herdando de MembershipProvider ou RoleProvider e implementar os métodos e propriedades exclusivamente para aquela base de dados. Finalmente, para ilustrar essa arquitetura, é mostrado através da figura abaixo o design das classes, já com as devidas denotações de herança entre elas:

Figura 1 - Design das classes utilizando Provider Model.

Além das classes que vimos acima, temos ainda duas classes estáticas (compartilhadas) que são, também, parte das principais. São estas classes que são expostas para nós, desenvolvedores, utilizarmos e fazermos a chamada aos métodos e propriedades de forma genérica. Essas classes são: Membership e Roles, as quais estão contidas dentro do Namespace System.Web.Security. No caso da classe Membership, existe um membro interno chamado s_Provider do tipo MembershipProvider (a classe base para as classes concretas de Membership, como por exemplo o SqlMembershipProvider), o qual receberá a instância da classe concreta. Essa inicialização acontece quando um método interno chamado Initialize é executado. Ele se encarrega de extrair os providers do arquivo Web.Config e instanciá-los para que, quando chamarmos os métodos e propriedades, já sejam efetivamente os métodos e propriedades da classe concreta que queremos utilizar. O funcionamento é também semelhante para a classe Roles.

Por questões de infraestrutura, utilizaremos no decorrer deste artigo o banco de dados SQL Server 2000. Mas então como prepará-lo para fazer o uso desta funcionalidade? Pois bem, para que o mesmo possa ser utilizado para isso, é necessário criarmos dentro do banco de dados a infraestrutura (Tabelas, Índices, Stored Procedures e Triggers) necessária para a utilização do recurso, que no caso serão Membership e Roles.

Quando instalamos no .NET Framework 2.0, em seu diretório %WinDir%\Microsoft.NET\Framework\v2.0.50727 é instalado um utilitário chamado aspnet_regsql.exe. Este utilitário, dados alguns parâmetros, faz todo o trabalho da criação da infraestrutura dentro de um determinado banco de dados SQL Server. Veremos na tabela abaixo os parâmetros que ele aceita:

Parâmetro Descrição
-? Exibe o Help do utilitário.
-W Roda o utilitário em modo Wizard. Esse é o padrão caso nenhum outro parâmetro seja informado. Com isso, você terá um wizard (que é mostrado abaixo) para acompanhá-lo durante o processo.

-C Especifica a ConnectionString para o servidor onde o banco de dados SQL Server está instalado. Se desejar, você pode informar isso separadamente (mostrado mais abaixo).
-S Nome do servidor onde o SQL Server está.
-U Login de acesso dentro do SQL Server (é necessário informar o password ou Integrated Security).
-P Password para o Login especificado através do parâmetro -U.
-E Autentica o usuário com as credenciais do usuário corrente logado no Windows.
-sqlexportonly Usado para criar um arquivo de script para adicionar ou remover tais funcionalidades.
-A all|m|r|p|c|w Adiciona a infraestrutura dentro da base de dados para uma determinada funcionalidade. Além do -A, ainda há o parâmetro complementar onde você deve informar qual será a funcionalidade que deseja adicionar:

all - Todas
m - Membership
r - Role management
p - Profile
c - Web Parts personalization
w - Web events
-R all|m|r|p|c|w Remove a infraestrutura dentro da base de dados para uma determinada funcionalidade. Além do -R, ainda há o parâmetro complementar onde você deve informar qual será a funcionalidade que deseja remover:

all - Todas
m - Membership
r - Role management
p - Profile
c - Web Parts personalization
w - Web events

Abaixo é mostrado (através do prompt de comando do Visual Studio .NET 2005) alguns exemplos do uso do utilitário aspnet_regsql.exe:

C:\aspnet_regsql.exe -S localhost -U NomeUsuario -P P@$$w0rd -d BancoDados -A mrpw
C:\aspnet_regsql.exe -S localhost -E -d BancoDados -A all
C:\aspnet_regsql.exe -S localhost -E -d BancoDados -R all

Na primeira linha adicionamos as funcionalidades de Membership, Roles, Profile e WebEvents no banco de dados chamado "BancoDados". Já na segunda opção adicionamos todas as funcionalidades, só que agora utilizando as credenciais do Windows. E, por último, estamos removendo toda a infraestrutura da base de dados. Quando criamos uma destas funcionalidades dentro da base de dados ele inclui uma porção de Tabelas, Stored Procedures, Triggers para que a mesma seja atendida. Se analisarmos o design da base de dados depois disso, veremos a seguinte estrutura:

Figura 2 - Design da base de dados para suportar as funcionalidades de Membership e Roles.

O Arquivo ASPNETDB.MDF

Quando utilizamos Membership ou qualquer uma destas funcionalidades, se não especificarmos um provider e você não tiver um banco de dados pré-definido para o uso das mesmas, o ASP.NET cria automaticamente dentro do diretório App_Data dentro da aplicação um banco de dados chamado ASPNETDB.MDF, que faz uso do SQL Server 2005 Express Edition. Essa criação se dá quando iniciamos o WSAT - Web Site Administration Tool, onde depois de iniciarmos o provider, o arquivo MDF é criado dentro da pasta App_Data.

Com isso não seria necessário você criar a infraestrutura em uma base de dados assim como mostramos acima, pois o arquivo ASPNETDB.MDF já terá todo o schema necessário para as funcionalidades. Se o volume de usuários não é tão alto e o sistema pouco complexo, ter o ASPNETDB.MDF já resolve a necessidade. Mas quando o nível de usuários aumenta e de alguma forma você precisa fazer o relacionamento da tabela de Usuários com outras tabelas do sistema, é mais interessante ter isso unificado, ou seja, dentro de uma única base de dados.

Outputcache - CacheProfile

Estou trabalhando em um projeto ASP.NET 2.0 e, como algumas páginas estavam requerendo o uso de Outputcache, para cada uma dessas páginas estava ajustando o mesmo valor de segundos no atributo Duration e também especificando algumas outras propriedades desta mesma diretiva, customizando assim, o cache da forma que necessitava.

Mas em um certo momento me perguntei: e se esses valores e condições mudarem? Sim, terei que ir a cada página e especificar tudo novamente. Foi nesse momento que recorri a documentação do .NET Framework e encontrei o elemento outputCacheProfiles que é especificado dentro do arquivo Web.Config.

Com isso, ao invés de especificar os mesmos valores em cada página ASPX, eu criei uma "profile" dentro do Web.Config e passo a utilizar este "profile" nas páginas ASPX. Um exemplo do uso é mostrado abaixo:

[ Web.Config ]
<outputCacheSettings>
  <outputCacheProfiles>
    <add name = "PaginasEmCache"
      varyByParam = "TituloID"
      enabled = "true"
      duration = "180" />
  </outputCacheProfiles>
</outputCacheSettings>

[ *.ASPX ]
<%@ OutputCache CacheProfile="PaginasEmCache" %>

Recuperando a linha do GridView

Muitas pessoas me perguntam como acessar o conteúdo de uma linha do GridView dentro do evento RowCommand (sim, elas NÃO ESTÃO fazendo o uso do DataKeys). Pois bem, nas versões 1.x do ASP.NET, o evento ItemCommand do DataGrid tinha um argumento do tipo DataGridCommandEventArgs e, dentro deste, uma propriedade chamada Item que retorna a instancia da linha corrente.

Isso não existe mais no GridView e, se ainda desejar acessar os dados da linha clicada no evento RowCommand do GridView, terá que proceder da seguinte forma: no evento RowCreated do GridView, terá que definir à propriedade CommandArgument do controle LinkButton (Button, ImageButton, etc) responsável por disparar o comando, a propriedade RowIndex que vem como parametro para este evento. Esta propriedade retorna um valor inteiro contendo o índice da linha e, como o evento RowCommand te fornece uma propriedade chamada CommandArgument, conseguirá recuperar o índice da linha e, consequentemente, acessar os valores da mesma.

Para ilustar o processo, o código (em C#) é mostrado abaixo:

private void GridView1_RowCommand(Object sender, GridViewCommandEventArgs e)
{
   if(e.CommandName=="Add")
   {
      int index = Convert.ToInt32(e.CommandArgument);
      GridViewRow row = GridView1.Rows[index];
      Response.Write(row.Cells[2].Text);
   }
}

private void GridView1_RowCreated(Object sender, GridViewRowEventArgs e)
{
   if(e.Row.RowType == DataControlRowType.DataRow)
   {
      LinkButton addButton = (LinkButton)e.Row.Cells[0].Controls[0];
      addButton.CommandArgument = e.Row.RowIndex.ToString();
   }
}

ListItem

Um dos grandes problemas (bug?) que temos nas versões 1.x do ASP.NET é que os atributos do objeto ListItem (usado para preencher controles como DropDownLists, ListBoxes, etc.) não são renderizados, logo, se quisésemos definir uma cor de background em um item do DropDownList, não era possível, a menos que fizessemos algo como o Scott Mitchell mostra neste artigo.

A boa notícia é que isso foi resolvido na versão 2.0 do ASP.NET e agora podemos fazer o que já cansamos de tentar nas versões anteriores:

foreach (ListItem item in this.DropDownList1.Items)
{
    item.Attributes.Add("style", "background-color: Red");
}

Atributo debug

O ScottG fala neste post sobre o atributo debug do elemento compilation do arquivo Web.Config e me fez lembrar o meu primeiro projeto em ASP.NET. A performance estava muito baixa e depois de uma checagem antes de mandar ao servidor de produção, alterei o atributo, definindo-o para false. A performance aumenta bastante, já que muitas coisas que devem ser usadas somente em tempo de desenvolvimento, deixam de ser utilizadas. Só vale lembrar que isso não é uma característica exclusiva do ASP.NET 2.0.

Uma das outras técnicas interessantes, esta somente para ASP.NET 2.0, é definir no arquivo machine.config do servidor de aplicações, o atributo retail do elemento deployment para true, para evitar que, pessoas despercebidas façam o deployment de aplicações sem mudar/definir o atributo debug para false do arquivo Web.Config da respectiva aplicação.

Acessar dados do Arquivo do Upload

Muitas vezes, quando precisamos fazer um upload e, antes que o mesmo seja efetivamente salvo no disco, devemos aplicar algumas validações onde, serão estas validações que irão dizer se o arquivo é ou não um arquivo válido para a nossa necessidade e, consequentemente, salvar no disco.

O que muito gente não sabe (devido ao número de e-mails que recebo com esse, até então, problema) é que há a possibilidade de analisar o arquivo antes de salvá-lo. É possível graças a propriedade chamada InputStream da classe HttpPostedFile (acessível através do controle FileUpload). Com isso, podemos tranquilamente acessar o arquivo, ler e, se for um arquivo válido, o persistimos fisicamente. Para exemplificar, o código abaixo ilustra isso:

if (Page.IsValid && this.FileUpload1.HasFile){
     StreamReader sr =
          new StreamReader(this.FileUpload1.PostedFile.InputStream);
     //....
}

Obviamente que poupei o error-handling por questões de espaço. Como há um construtor na classe StreamReader que permite passarmos um Stream, podemos passar o Stream que vem quando fazemos o upload de um arquivo. Depois, se necessário, podemos utilizar o método SaveAs para salvar o arquivo fisicamente no disco, mas a idéia com este post, é mostrar que não é necessário salvarmos o arquivo fisicamente para análisá-lo.