Autorização baseada em função (VB)

por Scott Mitchell

Observação

Desde que este artigo foi escrito, os provedores de associação de ASP.NET foram substituídos por ASP.NET Identity. É altamente recomendável atualizar aplicativos para usar a plataforma ASP.NET Identity em vez dos provedores de associação apresentados no momento em que este artigo foi escrito. ASP.NET Identity tem várias vantagens sobre o sistema de associação ASP.NET, incluindo :

  • Melhor desempenho
  • Extensibilidade e testabilidade aprimoradas
  • Suporte para OAuth, OpenID Connect e autenticação de dois fatores
  • Suporte à identidade baseada em declarações
  • Melhor interoperabilidade com ASP.Net Core

Baixar código ou baixar PDF

Este tutorial começa com uma visão de como a estrutura Funções associa as funções de um usuário ao contexto de segurança dele. Em seguida, ele examina como aplicar regras de autorização de URL baseadas em função. Depois disso, examinaremos o uso de meios declarativos e programáticos para alterar os dados exibidos e a funcionalidade oferecida por uma página ASP.NET.

Introdução

No tutorial Autorização Baseada no Usuário, vimos como usar a autorização de URL para especificar quais usuários poderiam visitar um determinado conjunto de páginas. Com apenas um pouco de marcação no Web.config, podemos instruir ASP.NET a permitir que apenas usuários autenticados visitem uma página. Ou poderíamos ditar que somente os usuários Tito e Bob eram permitidos ou indicar que todos os usuários autenticados, exceto Sam, eram permitidos.

Além da autorização de URL, também examinamos técnicas declarativas e programáticas para controlar os dados exibidos e a funcionalidade oferecida por uma página com base na visita do usuário. Em particular, criamos uma página que listava o conteúdo do diretório atual. Qualquer pessoa poderia visitar esta página, mas apenas usuários autenticados podiam exibir o conteúdo dos arquivos e apenas Tito poderia excluir os arquivos.

A aplicação de regras de autorização por usuário pode se transformar em um pesadelo de contabilidade. Uma abordagem mais mantenedível é usar a autorização baseada em função. A boa notícia é que as ferramentas à nossa disposição para aplicar regras de autorização funcionam igualmente bem com funções como funcionam para contas de usuário. As regras de autorização de URL podem especificar funções em vez de usuários. O controle LoginView, que renderiza saídas diferentes para usuários autenticados e anônimos, pode ser configurado para exibir conteúdo diferente com base nas funções do usuário conectado. E a API de Funções inclui métodos para determinar as funções do usuário conectado.

Este tutorial começa com uma visão de como a estrutura Funções associa as funções de um usuário ao contexto de segurança dele. Em seguida, ele examina como aplicar regras de autorização de URL baseadas em função. Depois disso, examinaremos o uso de meios declarativos e programáticos para alterar os dados exibidos e a funcionalidade oferecida por uma página ASP.NET. Vamos começar!

Noções básicas sobre como as funções são associadas ao contexto de segurança de um usuário

Sempre que uma solicitação insere o pipeline ASP.NET ele é associado a um contexto de segurança, que inclui informações que identificam o solicitante. Ao usar a autenticação de formulários, um tíquete de autenticação é usado como um token de identidade. Como discutimos no tutorial Uma Visão Geral da Autenticação de Formulários, o FormsAuthenticationModule é responsável por determinar a identidade do solicitante, o que ele faz durante o AuthenticateRequest evento.

Se um tíquete de autenticação válido e não expirado for encontrado, o o FormsAuthenticationModule decodificará para verificar a identidade do solicitante. Ele cria um novo GenericPrincipal objeto e atribui isso ao HttpContext.User objeto . A finalidade de uma entidade de segurança, como GenericPrincipal, é identificar o nome do usuário autenticado e a quais funções ela pertence. Essa finalidade é evidente pelo fato de que todos os objetos principais têm uma Identity propriedade e um IsInRole(roleName) método. O FormsAuthenticationModule, no entanto, não está interessado em gravar informações de função e o GenericPrincipal objeto que ele cria não especifica nenhuma função.

Se a estrutura Funções estiver habilitada, o RoleManagerModule Módulo HTTP será iniciado após o FormsAuthenticationModule e identificará as funções do usuário autenticado durante o PostAuthenticateRequest evento, que é acionado após o AuthenticateRequest evento. Se a solicitação for de um usuário autenticado, o RoleManagerModule substituirá o GenericPrincipal objeto criado pelo e o FormsAuthenticationModule substituirá por um RolePrincipal objeto . A RolePrincipal classe usa a API de Funções para determinar a quais funções o usuário pertence.

A Figura 1 ilustra o fluxo de trabalho de pipeline ASP.NET ao usar a autenticação de formulários e a estrutura Funções. O FormsAuthenticationModule é executado primeiro, identifica o usuário por meio de seu tíquete de autenticação e cria um novo GenericPrincipal objeto. Em seguida, as RoleManagerModule etapas em e substituem o GenericPrincipal objeto por um RolePrincipal objeto .

Se um usuário anônimo visitar o site, nem o FormsAuthenticationModule nem o RoleManagerModule criarão um objeto principal.

Os eventos de pipeline ASP.NET para um usuário autenticado ao usar a Autenticação de Formulários e a Estrutura de Funções

Figura 1: os eventos de pipeline ASP.NET para um usuário autenticado ao usar a autenticação de formulários e a Estrutura de Funções (clique para exibir a imagem em tamanho real)

O RolePrincipal método do IsInRole(roleName) objeto chama Roles.GetRolesForUser para obter as funções do usuário para determinar se o usuário é membro do roleName. Ao usar o SqlRoleProvider, isso resulta em uma consulta para o banco de dados do repositório de funções. Ao usar regras de autorização de URL baseadas em função, o RolePrincipalIsInRole método será chamado em cada solicitação para uma página protegida pelas regras de autorização de URL baseadas em função. Em vez de precisar pesquisar as informações de função no banco de dados em cada solicitação, a Roles estrutura inclui uma opção para armazenar em cache as funções do usuário em um cookie.

Se a estrutura Funções estiver configurada para armazenar em cache as funções do usuário em um cookie, o RoleManagerModule criará o cookie durante o evento do pipeline ASP.NETEndRequest. Esse cookie é usado em solicitações subsequentes no PostAuthenticateRequest, que é quando o RolePrincipal objeto é criado. Se o cookie for válido e não tiver expirado, os dados no cookie serão analisados e usados para preencher as funções do usuário, evitando assim que o RolePrincipal precise fazer uma chamada para a Roles classe para determinar as funções do usuário. A Figura 2 ilustra esse fluxo de trabalho.

As informações de função do usuário podem ser armazenadas em um cookie para melhorar o desempenho

Figura 2: As informações de função do usuário podem ser armazenadas em um cookie para melhorar o desempenho (clique para exibir a imagem em tamanho real)

Por padrão, o mecanismo de cookie de cache de função está desabilitado. Ele pode ser habilitado por meio da <roleManager>marcação ; de configuração em Web.config. Discutimos o uso do <roleManager> elemento para especificar provedores de função no tutorial Criando e Gerenciando Funções, portanto, você já deve ter esse elemento no arquivo do Web.config aplicativo. As configurações de cookie de cache de função são especificadas como atributos do <roleManager>elemento ; e são resumidas na Tabela 1.

Observação

As configurações listadas na Tabela 1 especificam as propriedades do cookie de cache de função resultante. Para obter mais informações sobre cookies, como eles funcionam e suas várias propriedades, leia este tutorial cookies.

Propriedade Descrição
cacheRolesInCookie Um valor booliano que indica se o cache de cookie é usado. Assume o padrão de false.
cookieName O nome do cookie de cache de função. O valor padrão é ". ASPXROLES".
cookiePath O caminho para o cookie de nome de funções. O atributo path permite que um desenvolvedor limite o escopo de um cookie a uma hierarquia de diretório específica. O valor padrão é "/", que informa o navegador para enviar o cookie de tíquete de autenticação para qualquer solicitação feita ao domínio.
cookieProtection Indica quais técnicas são usadas para proteger o cookie de cache de função. Os valores permitidos são: All (o padrão); Encryption; ; Nonee Validation.md)

| cookieRequireSSL | Um valor booliano que indica se uma conexão SSL é necessária para transmitir o cookie de autenticação. O valor padrão é false cookieSlidingExpiration false createPersistentCookieis set totrue. | | cookieTimeout | Specifies the time, in minutes, after which the authentication ticket cookie expires. The default value is30. This value is only pertinent when createPersistentCookieis set totrue. | | createPersistentCookie | A Boolean value that specifies whether the role cache cookie is a session cookie or persistent cookie. Iffalse(the default), a session cookie is used, which is deleted when the browser is closed. Iftrue, a persistent cookie is used; it expires cookieTimeoutnumber of minutes after it has been created or after the previous visit, depending on the value ofcookieSlidingExpiration. | | domain| Specifies the cookie's domain value. The default value is an empty string, which causes the browser to use the domain from which it was issued (such as www.yourdomain.com). In this case, the cookie will <strong>not</strong> be sent when making requests to subdomains, such as admin.yourdomain.com. If you want the cookie to be passed to all subdomains you need to customize theattribute, setting it to "yourdomain.com". | | maxCachedResults | Specifies the maximum number of role names that are cached in the cookie. The default is 25. TheRoleManagerModuledoes not create a cookie for users that belong to more thanmaxCachedResultsroles. Consequently, theRolePrincipalobject'sIsInRolemethod will use theRolesclass to determine the user's roles. The reasonmaxCachedResultsexists is because many user agents do not permit cookies larger than 4,096 bytes. So this cap is meant to reduce the likelihood of exceeding this size limitation. If you have extremely long role names, you may want to consider specifying a smaller. This value is only pertinent when | A Boolean value that indicates whether the cookie's timeout is reset each time the user visits the site during a single session. The default value is. | | valor de maxCachedResults; contrariwise, se você tiver nomes de função extremamente curtos, provavelmente poderá aumentar esse valor. |

Tabela 1: as opções de configuração de cookie de cache de função

Vamos configurar nosso aplicativo para usar cookies de cache de função não persistentes. Para fazer isso, atualize o <roleManager> elemento em Web.config para incluir os seguintes atributos relacionados a cookie:

<roleManager enabled="true" 
          defaultProvider="SecurityTutorialsSqlRoleProvider"
          cacheRolesInCookie="true"
          createPersistentCookie="false"
          cookieProtection="All">

     <providers>
     ...
     </providers>
</roleManager>

Atualizei o elemento ; adicionando <roleManager>três atributos: cacheRolesInCookie, createPersistentCookiee cookieProtection. Ao definir cacheRolesInCookie como true, o RoleManagerModule agora armazenará automaticamente em cache as funções do usuário em um cookie, em vez de precisar pesquisar as informações de função do usuário em cada solicitação. Defina explicitamente os createPersistentCookie atributos e cookieProtection como false e All, respectivamente. Tecnicamente, não precisei especificar valores para esses atributos, pois acabei de atribuí-los a seus valores padrão, mas os coloquei aqui para deixar claro explicitamente que não estou usando cookies persistentes e que o cookie é criptografado e validado.

Isso é tudo! A partir daí, a estrutura Funções armazenará em cache as funções dos usuários em cookies. Se o navegador do usuário não der suporte a cookies ou se seus cookies forem excluídos ou perdidos, de alguma forma, não será importante – o RolePrincipal objeto simplesmente usará a Roles classe no caso de nenhum cookie (ou um inválido ou expirado) estar disponível.

Observação

O grupo Padrões & Práticas da Microsoft desencoraja o uso de cookies de cache de função persistente. Como a posse do cookie de cache de função é suficiente para provar a associação de função, se um hacker puder de alguma forma obter acesso ao cookie de um usuário válido, ele poderá representar esse usuário. A probabilidade de isso acontecer aumentará se o cookie for persistido no navegador do usuário. Para obter mais informações sobre essa recomendação de segurança, bem como outras preocupações de segurança, consulte a Lista de Perguntas de Segurança para ASP.NET 2.0.

Etapa 1: Definindo Role-Based regras de autorização de URL

Conforme discutido no tutorial Autorização Baseada no Usuário, a autorização de URL oferece um meio de restringir o acesso a um conjunto de páginas em uma base de usuário por usuário ou função por função. As regras de autorização de URL são escritas em Web.config usando o <authorization> elemento com <allow> elementos filho e <deny> . Além das regras de autorização relacionadas ao usuário discutidas em tutoriais anteriores, cada <allow> elemento filho também <deny> pode incluir:

  • Uma função específica
  • Uma lista delimitada por vírgulas de funções

Por exemplo, as regras de autorização de URL concedem acesso a esses usuários nas funções Administradores e Supervisores, mas negam acesso a todos os outros:

<authorization>

     <allow roles="Administrators, Supervisors" />
     <deny users="*" />
</authorization>

O <allow> elemento na marcação acima indica que as funções Administradores e Supervisores são permitidas; o <deny>elemento ; instrui que todos os usuários são negados.

Vamos configurar nosso aplicativo para que as ManageRoles.aspxpáginas , UsersAndRoles.aspxe CreateUserWizardWithRoles.aspx só sejam acessíveis para esses usuários na função Administradores, enquanto a RoleBasedAuthorization.aspx página permanece acessível a todos os visitantes.

Para fazer isso, comece adicionando um Web.config arquivo à Roles pasta .

Adicionar um arquivo Web.config ao diretório Funções

Figura 3: Adicionar um Web.config arquivo ao Roles diretório (Clique para exibir a imagem em tamanho real)

Em seguida, adicione a seguinte marcação de configuração a Web.config:

<?xml version="1.0"?>

<configuration>
     <system.web>
          <authorization>
               <allow roles="Administrators" />
               <deny users="*"/>
          </authorization>

     </system.web>

     <!-- Allow all users to visit RoleBasedAuthorization.aspx -->
     <location path="RoleBasedAuthorization.aspx">
          <system.web>
               <authorization>
                    <allow users="*" />

               </authorization>
          </system.web>
     </location>
</configuration>

O <authorization> elemento na <system.web> seção indica que somente os usuários na função Administradores podem acessar os recursos ASP.NET no Roles diretório. O <location> elemento define um conjunto alternativo de regras de autorização de URL para a RoleBasedAuthorization.aspx página, permitindo que todos os usuários visitem a página.

Depois de salvar suas alterações Web.configno , faça logon como um usuário que não esteja na função Administradores e tente visitar uma das páginas protegidas. O UrlAuthorizationModule detectará que você não tem permissão para visitar o recurso solicitado; consequentemente, o FormsAuthenticationModule redirecionará você para a página de logon. Em seguida, a página de logon redirecionará você para a UnauthorizedAccess.aspx página (consulte Figura 4). Esse redirecionamento final da página de logon para ocorre devido ao UnauthorizedAccess.aspx código que adicionamos à página de logon na Etapa 2 do tutorial autorização baseada no usuário. Em particular, a página de logon redireciona automaticamente qualquer usuário autenticado para UnauthorizedAccess.aspx se a querystring contiver um ReturnUrl parâmetro, pois esse parâmetro indica que o usuário chegou à página de logon depois de tentar exibir uma página que ele não estava autorizado a exibir.

Somente os usuários na função Administradores podem exibir as páginas protegidas

Figura 4: somente os usuários na função Administradores podem exibir as páginas protegidas (clique para exibir a imagem em tamanho real)

Faça logoff e faça logon como um usuário que esteja na função Administradores. Agora você deve ser capaz de exibir as três páginas protegidas.

Tito pode visitar a página UsersAndRoles.aspx porque ele está na função administradores

Figura 5: Tito pode visitar a UsersAndRoles.aspx página porque ele está na função Administradores (clique para exibir a imagem em tamanho real)

Observação

Ao especificar regras de autorização de URL – para funções ou usuários – é importante ter em mente que as regras são analisadas uma de cada vez, de cima para baixo. Assim que uma correspondência for encontrada, o usuário receberá ou negará acesso, dependendo se a correspondência tiver sido encontrada em um <allow> elemento ou <deny> . Se nenhuma correspondência for encontrada, o usuário receberá acesso. Consequentemente, se você quiser restringir o acesso a uma ou mais contas de usuário, é imperativo que você use um <deny> elemento como o último elemento na configuração de autorização de URL. Se suas regras de autorização de URL não incluirem um<deny>elemento , todos os usuários receberão acesso. Para obter uma discussão mais detalhada sobre como as regras de autorização de URL são analisadas, consulte a seção "Uma olhada em como as UrlAuthorizationModule regras de autorização usam para conceder ou negar acesso" do tutorial autorização baseada no usuário.

Etapa 2: limitando a funcionalidade com base nas funções do usuário conectado no momento

A autorização de URL facilita a especificação de regras de autorização grosseiras que declaram quais identidades são permitidas e quais são negadas de exibir uma página específica (ou todas as páginas em uma pasta e suas subpastas). No entanto, em determinados casos, talvez queiramos permitir que todos os usuários acessem uma página, mas limitem a funcionalidade da página com base nas funções do usuário visitante. Isso pode implicar mostrar ou ocultar dados com base na função do usuário ou oferecer funcionalidade adicional aos usuários que pertencem a uma função específica.

Essas regras de autorização baseadas em função de granularidade fina podem ser implementadas declarativamente ou programaticamente (ou por meio de alguma combinação dos dois). Na próxima seção, veremos como implementar a autorização declarativa de granularidade fina por meio do controle LoginView. Depois disso, exploraremos técnicas programáticas. Antes de podermos examinar a aplicação de regras de autorização de granularidade finas, no entanto, primeiro precisamos criar uma página cuja funcionalidade depende da função do usuário que a visita.

Vamos criar uma página que lista todas as contas de usuário no sistema em um GridView. O GridView incluirá o nome de usuário, o endereço de email, a última data de logon e os comentários sobre o usuário. Além de exibir as informações de cada usuário, o GridView incluirá recursos de edição e exclusão. Inicialmente, criaremos esta página com a funcionalidade de edição e exclusão disponível para todos os usuários. Nas seções "Usando o controle LoginView" e "Limitando programaticamente a funcionalidade", veremos como habilitar ou desabilitar esses recursos com base na função do usuário visitante.

Observação

A página ASP.NET que estamos prestes a criar usa um controle GridView para exibir as contas de usuário. Como esta série de tutoriais se concentra na autenticação de formulários, autorização, contas de usuário e funções, não quero gastar muito tempo discutindo o funcionamento interno do controle GridView. Embora este tutorial forneça instruções passo a passo específicas para configurar esta página, ele não se aprofunda nos detalhes de por que determinadas escolhas foram feitas ou que efeito determinadas propriedades têm na saída renderizada. Para um exame detalhado do controle GridView, marcar minha série de tutoriais Trabalhando com Dados no ASP.NET 2.0.

Comece abrindo a RoleBasedAuthorization.aspx página na Roles pasta . Arraste um GridView da página para o Designer e defina como IDUserGrid. Em um momento, escreveremos um código que chama o Membership.GetAllUsers e associa o objeto resultante MembershipUserCollection ao GridView. O MembershipUserCollection contém um MembershipUser objeto para cada conta de usuário no sistema; MembershipUser os objetos têm propriedades como UserName,LastLoginDateEmail e assim por diante.

Antes de escrevermos o código que associa as contas de usuário à grade, vamos primeiro definir os campos do GridView. Na Marca Inteligente do GridView, clique no link "Editar Colunas" para iniciar a caixa de diálogo Campos (consulte Figura 6). A partir daqui, desmarque a caixa de seleção "Gerar campos automaticamente" no canto inferior esquerdo. Como queremos que esse GridView inclua recursos de edição e exclusão, adicione um CommandField e defina suas ShowEditButton propriedades e ShowDeleteButton como True. Em seguida, adicione quatro campos para exibir as UserNamepropriedades , Email, LastLoginDatee Comment . Use um BoundField para as duas propriedades somente leitura (UserName e LastLoginDate) e TemplateFields para os dois campos editáveis (Email e Comment).

Fazer com que o primeiro BoundField exiba a UserName propriedade ; defina suas HeaderText propriedades e DataField como "UserName". Esse campo não será editável, portanto, defina sua ReadOnly propriedade como True. Configure o BoundField definindo-o LastLoginDateHeaderText como "Último Logon" e como DataField "LastLoginDate". Vamos formatar a saída desse BoundField para que apenas a data seja exibida (em vez da data e hora). Para fazer isso, defina a propriedade deste BoundField HtmlEncode como False e sua DataFormatString propriedade como "{0:d}". Defina também a ReadOnly propriedade como True.

Defina as HeaderText propriedades dos dois TemplateFields como "Email" e "Comment".

Os campos do GridView podem ser configurados por meio da caixa de diálogo Campos

Figura 6: Os campos do GridView podem ser configurados por meio da caixa de diálogo Campos (clique para exibir a imagem em tamanho real)

Agora precisamos definir o ItemTemplate e EditItemTemplate para os TemplateFields "Email" e "Comment". Adicione um controle Web label a cada um dos ItemTemplates e associe suas Text propriedades às Email propriedades e Comment , respectivamente.

Para o TemplateField "Email", adicione um TextBox chamado Email a ele EditItemTemplate e associe sua Text propriedade à propriedade usando a Email vinculação de dados bidirecional. Adicione um RequiredFieldValidator e RegularExpressionValidator ao EditItemTemplate para garantir que um visitante que edite a propriedade Email inseriu um endereço de email válido. Para o TemplateField "Comment", adicione uma Caixa de Texto de várias linhas chamada Comment a seu EditItemTemplate. Defina as propriedades e Rows do Columns TextBox como 40 e 4, respectivamente, e, em seguida, associe sua Text propriedade à propriedade usando a Comment vinculação de dados bidirecional.

Depois de configurar esses TemplateFields, sua marcação declarativa deve ser semelhante à seguinte:

<asp:TemplateField HeaderText="Email">
     <ItemTemplate>
          <asp:Label runat="server" ID="Label1" Text='<%# Eval("Email")%>'></asp:Label>

     </ItemTemplate>
     <EditItemTemplate>
          <asp:TextBox runat="server" ID="Email" Text='<%# Bind("Email")%>'></asp:TextBox>

          <asp:RequiredFieldValidator ID="RequiredFieldValidator1" runat="server" 
               ControlToValidate="Email" Display="Dynamic"
               ErrorMessage="You must provide an email address."
               SetFocusOnError="True">*</asp:RequiredFieldValidator>

          <asp:RegularExpressionValidator ID="RegularExpressionValidator1" runat="server"
               ControlToValidate="Email" Display="Dynamic"
               ErrorMessage="The email address you have entered is not valid. Please fix 
               this and try again."
               SetFocusOnError="True"

               ValidationExpression="\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*">*
          </asp:RegularExpressionValidator>
     </EditItemTemplate>
</asp:TemplateField>

<asp:TemplateField HeaderText="Comment">
     <ItemTemplate>
          <asp:Label runat="server" ID="Label2" Text='<%# Eval("Comment")%>'></asp:Label>

     </ItemTemplate>
     <EditItemTemplate>
          <asp:TextBox runat="server" ID="Comment" TextMode="MultiLine"
               Columns="40" Rows="4" Text='<%# Bind("Comment")%>'>

          </asp:TextBox>
     </EditItemTemplate>
</asp:TemplateField>

Ao editar ou excluir uma conta de usuário, precisaremos saber o valor da propriedade desse UserName usuário. Defina a propriedade do DataKeyNames GridView como "UserName" para que essas informações fiquem disponíveis por meio da coleção do DataKeys GridView.

Por fim, adicione um controle ValidationSummary à página e defina sua ShowMessageBox propriedade como True e sua ShowSummary propriedade como False. Com essas configurações, o ValidationSummary exibirá um alerta do lado do cliente se o usuário tentar editar uma conta de usuário com um endereço de email ausente ou inválido.

<asp:ValidationSummary ID="ValidationSummary1"
               runat="server"
               ShowMessageBox="True"
               ShowSummary="False" />

Concluímos a marcação declarativa desta página. Nossa próxima tarefa é associar o conjunto de contas de usuário ao GridView. Adicione um método chamado BindUserGrid à RoleBasedAuthorization.aspx classe code-behind da página que associa o MembershipUserCollection retornado por Membership.GetAllUsers ao UserGrid GridView. Chame esse método do Page_Load manipulador de eventos na primeira visita à página.

Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
     If Not Page.IsPostBack Then
          BindUserGrid()
     End If
End Sub

Private Sub BindUserGrid()
     Dim allUsers As MembershipUserCollection = Membership.GetAllUsers()
     UserGrid.DataSource = allUsers
     UserGrid.DataBind()
End Sub

Com esse código em vigor, visite a página por meio de um navegador. Como mostra a Figura 7, você deve ver um GridView listando informações sobre cada conta de usuário no sistema.

O UserGrid GridView lista informações sobre cada usuário no sistema

Figura 7: o UserGrid GridView lista informações sobre cada usuário no sistema (clique para exibir a imagem em tamanho real)

Observação

O UserGrid GridView lista todos os usuários em uma interface não paginada. Essa interface de grade simples não é adequada para cenários em que há várias dezenas ou mais usuários. Uma opção é configurar o GridView para habilitar a paginação. O Membership.GetAllUsers método tem duas sobrecargas: uma que não aceita parâmetros de entrada e retorna todos os usuários e outra que recebe valores inteiros para o índice de página e o tamanho da página e retorna apenas o subconjunto especificado dos usuários. A segunda sobrecarga pode ser usada para usar a página de forma mais eficiente por meio dos usuários, pois retorna apenas o subconjunto preciso das contas de usuário em vez de todas elas. Se você tiver milhares de contas de usuário, convém considerar uma interface baseada em filtro, que mostra apenas os usuários cujo Nome de Usuário começa com um caractere selecionado, por exemplo. O Membership.FindUsersByName método é ideal para criar uma interface do usuário baseada em filtro. Examinaremos a criação dessa interface em um tutorial futuro.

O controle GridView oferece suporte interno de edição e exclusão quando o controle está associado a um controle de fonte de dados configurado corretamente, como SqlDataSource ou ObjectDataSource. O UserGrid GridView, no entanto, tem seus dados associados programaticamente; portanto, devemos escrever código para executar essas duas tarefas. Em particular, precisaremos criar manipuladores de eventos para os eventos , RowCancelingEdit, RowUpdatinge RowDeleting do RowEditingGridView, que são acionados quando um visitante clica nos botões Editar, Cancelar, Atualizar ou Excluir do GridView.

Comece criando os manipuladores de eventos para os eventos , RowCancelingEdite do RowEditingGridView e RowUpdating adicione o seguinte código:

Protected Sub UserGrid_RowEditing(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.GridViewEditEventArgs) Handles UserGrid.RowEditing
     ' Set the grid's EditIndex and rebind the data

     UserGrid.EditIndex = e.NewEditIndex
     BindUserGrid()
End Sub

Protected Sub UserGrid_RowCancelingEdit(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.GridViewCancelEditEventArgs) Handles UserGrid.RowCancelingEdit
     ' Revert the grid's EditIndex to -1 and rebind the data
     UserGrid.EditIndex = -1
     BindUserGrid()
End Sub
    
Protected Sub UserGrid_RowUpdating(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.GridViewUpdateEventArgs) Handles UserGrid.RowUpdating
     ' Exit if the page is not valid
     If Not Page.IsValid Then
          Exit Sub
     End If

     ' Determine the username of the user we are editing
     Dim UserName As String = UserGrid.DataKeys(e.RowIndex).Value.ToString()

     ' Read in the entered information and update the user
     Dim EmailTextBox As TextBox = CType(UserGrid.Rows(e.RowIndex).FindControl("Email"),TextBox)
     Dim CommentTextBox As TextBox= CType(UserGrid.Rows(e.RowIndex).FindControl("Comment"),TextBox)

     ' Return information about the user
     Dim UserInfo As MembershipUser = Membership.GetUser(UserName)

     ' Update the User account information
     UserInfo.Email = EmailTextBox.Text.Trim()
     UserInfo.Comment = CommentTextBox.Text.Trim()

     Membership.UpdateUser(UserInfo)

     ' Revert the grid's EditIndex to -1 and rebind the data
     UserGrid.EditIndex = -1
     BindUserGrid()
End Sub

Os RowEditing manipuladores de eventos e RowCancelingEdit simplesmente definem a propriedade do EditIndex GridView e associam novamente a lista de contas de usuário à grade. As coisas interessantes acontecem no RowUpdating manipulador de eventos. Esse manipulador de eventos começa garantindo que os dados sejam válidos e, em seguida, captura o UserName valor da conta de usuário editada da DataKeys coleção. As Email caixas de texto e Comment nos dois TemplateFields EditItemTemplate são então referenciadas programaticamente. Suas Text propriedades contêm o endereço de email e o comentário editados.

Para atualizar uma conta de usuário por meio da API de Associação, precisamos primeiro obter as informações do usuário, o que fazemos por meio de uma chamada para Membership.GetUser(userName). As propriedades e Comment do Email objeto retornado MembershipUser são atualizadas com os valores inseridos nas duas TextBoxes da interface de edição. Por fim, essas modificações são salvas com uma chamada para Membership.UpdateUser. O RowUpdating manipulador de eventos é concluído revertendo o GridView para sua interface de pré-edição.

Em seguida, crie o RowDeleting manipulador de eventos RowDeleting e adicione o seguinte código:

Protected Sub UserGrid_RowDeleting(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.GridViewDeleteEventArgs) Handles UserGrid.RowDeleting

     ' Determine the username of the user we are editing
     Dim UserName As String = UserGrid.DataKeys(e.RowIndex).Value.ToString()

     ' Delete the user
     Membership.DeleteUser(UserName)

     ' Revert the grid's EditIndex to -1 and rebind the data
     UserGrid.EditIndex = -1
     BindUserGrid()
End Sub

O manipulador de eventos acima começa pegando o UserName valor da coleção do DataKeys GridView; esse UserName valor é então passado para o método da DeleteUserclasse Membership. O DeleteUser método exclui a conta de usuário do sistema, incluindo dados de associação relacionados (como a quais funções esse usuário pertence). Depois de excluir o usuário, a grade é EditIndex definida como -1 (caso o usuário tenha clicado em Excluir enquanto outra linha estava no modo de edição) e o BindUserGrid método é chamado.

Observação

O botão Excluir não requer nenhum tipo de confirmação do usuário antes de excluir a conta de usuário. Encorajo você a adicionar alguma forma de confirmação do usuário para diminuir a chance de uma conta ser excluída acidentalmente. Uma das maneiras mais fáceis de confirmar uma ação é por meio de uma caixa de diálogo de confirmação do lado do cliente. Para obter mais informações sobre essa técnica, consulte Adicionando Client-Side confirmação ao excluir.

Verifique se essa página funciona conforme o esperado. Você deve ser capaz de editar o endereço de email e o comentário de qualquer usuário, bem como excluir qualquer conta de usuário. Como a RoleBasedAuthorization.aspx página é acessível a todos os usuários, qualquer usuário , até mesmo visitantes anônimos, pode visitar esta página e editar e excluir contas de usuário! Vamos atualizar esta página para que somente os usuários nas funções Supervisores e Administradores possam editar o endereço de email e o comentário de um usuário, e somente os Administradores possam excluir uma conta de usuário.

A seção "Usando o controle LoginView" analisa o uso do controle LoginView para mostrar instruções específicas para a função do usuário. Se uma pessoa na função Administradores visitar esta página, mostraremos instruções sobre como editar e excluir usuários. Se um usuário na função Supervisores atingir esta página, mostraremos instruções sobre como editar usuários. E se o visitante for anônimo ou não estiver na função Supervisores ou Administradores, exibiremos uma mensagem explicando que eles não podem editar ou excluir informações da conta de usuário. Na seção "Limitando programaticamente a funcionalidade", escreveremos um código que mostra ou oculta programaticamente os botões Editar e Excluir com base na função do usuário.

Usando o controle LoginView

Como vimos em tutoriais anteriores, o controle LoginView é útil para exibir interfaces diferentes para usuários autenticados e anônimos, mas o controle LoginView também pode ser usado para exibir marcação diferente com base nas funções do usuário. Vamos usar um controle LoginView para exibir instruções diferentes com base na função do usuário visitante.

Comece adicionando um LoginView acima do UserGrid GridView. Como discutimos anteriormente, o controle LoginView tem dois modelos internos: AnonymousTemplate e LoggedInTemplate. Insira uma breve mensagem em ambos os modelos que informe ao usuário que ele não pode editar ou excluir nenhuma informação do usuário.

<asp:LoginView ID="LoginView1" runat="server">
     <LoggedInTemplate>
          You are not a member of the Supervisors or Administrators roles. Therefore you
           cannot edit or delete any user information.
     </LoggedInTemplate>
     <AnonymousTemplate>

          You are not logged into the system. Therefore you cannot edit or delete any user
           information.
     </AnonymousTemplate>
</asp:LoginView>

Além do AnonymousTemplate e LoggedInTemplatedo , o controle LoginView pode incluir RoleGroups, que são modelos específicos de função. Cada RoleGroup contém uma única propriedade, Roles, que especifica a quais funções o RoleGroup se aplica. A Roles propriedade pode ser definida como uma única função (como "Administradores") ou para uma lista delimitada por vírgulas de funções (como "Administradores, Supervisores").

Para gerenciar os RoleGroups, clique no link "Editar RoleGroups" da Marca Inteligente do controle para abrir o Editor de Coleção RoleGroup. Adicione dois novos RoleGroups. Defina a propriedade do Roles primeiro RoleGroup como "Administradores" e a segunda como "Supervisores".

Gerenciar os modelos de Role-Specific do LoginView por meio do editor de coleção RoleGroup

Figura 8: Gerenciar os modelos de Role-Specific do LoginView por meio do Editor de Coleções RoleGroup (clique para exibir a imagem em tamanho real)

Clique em OK para fechar o Editor de Coleção RoleGroup; isso atualiza a marcação declarativa do LoginView para incluir uma seção com um <RoleGroups><asp:RoleGroup> elemento filho para cada RoleGroup definido no Editor de Coleção RoleGroup. Além disso, a lista suspensa "Exibições" na Marca Inteligente do LoginView , que inicialmente listava apenas o AnonymousTemplate e LoggedInTemplate , agora inclui os RoleGroups adicionados também.

Edite os RoleGroups para que os usuários na função Supervisores sejam exibidos instruções sobre como editar contas de usuário, enquanto os usuários na função Administradores são mostradas instruções para edição e exclusão. Depois de fazer essas alterações, a marcação declarativa do LoginView deve ser semelhante à seguinte.

<asp:LoginView ID="LoginView1" runat="server">
     <RoleGroups>
          <asp:RoleGroup Roles="Administrators">

               <ContentTemplate>
                    As an Administrator, you may edit and delete user accounts. 
                    Remember: With great power comes great responsibility!
               </ContentTemplate>
          </asp:RoleGroup>
          <asp:RoleGroup Roles="Supervisors">
               <ContentTemplate>
                    As a Supervisor, you may edit users&#39; Email and Comment information. 
                    Simply click the Edit button, make your changes, and then click Update.
               </ContentTemplate>

          </asp:RoleGroup>
     </RoleGroups>
     <LoggedInTemplate>
          You are not a member of the Supervisors or Administrators roles. 
          Therefore you cannot edit or delete any user information.
     </LoggedInTemplate>
     </AnonymousTemplate>
          You are not logged into the system. 
          Therefore you cannot edit or delete any user information.
     </AnonymousTemplate>
</asp:LoginView>

Depois de fazer essas alterações, salve a página e visite-a por meio de um navegador. Primeiro visite a página como um usuário anônimo. Você deverá receber a mensagem "Você não está conectado ao sistema. Portanto, você não pode editar ou excluir nenhuma informação do usuário." Em seguida, faça logon como um usuário autenticado, mas que não esteja na função Supervisores nem Administradores. Desta vez, você deverá ver a mensagem "Você não é membro das funções Supervisores ou Administradores. Portanto, você não pode editar ou excluir nenhuma informação do usuário."

Em seguida, faça logon como um usuário que seja membro da função Supervisores. Desta vez, você deverá ver a mensagem específica da função Supervisores (consulte a Figura 9). E se você fizer logon como um usuário na função Administradores, deverá ver a mensagem Específica da função Administradores (consulte a Figura 10).

Bruce é mostrado os supervisores Role-Specific mensagem

Figura 9: Bruce mostra a mensagem de Role-Specific supervisores (clique para exibir a imagem em tamanho real)

Tito é Mostrado o Administradores Role-Specific Mensagem

Figura 10: Tito mostra a mensagem de Role-Specific administradores (clique para exibir a imagem em tamanho real)

Como as capturas de tela nos Números 9 e 10 mostram, o LoginView renderiza apenas um modelo, mesmo que vários modelos se apliquem. Bruce e Tito são usuários conectados, mas o LoginView renderiza apenas o RoleGroup correspondente e não o LoggedInTemplate. Além disso, Tito pertence às funções Administradores e Supervisores, mas o controle LoginView renderiza o modelo específico da função Administradores em vez do supervisor.

A Figura 11 ilustra o fluxo de trabalho usado pelo controle LoginView para determinar qual modelo renderizar. Observe que, se houver mais de um RoleGroup especificado, o modelo LoginView renderizará o primeiro RoleGroup correspondente. Em outras palavras, se tivéssemos colocado o Grupo de Supervisores como o primeiro RoleGroup e os Administradores como o segundo, quando Tito visitou essa página, ele veria a mensagem Supervisores.

O fluxo de trabalho do controle LoginView para determinar qual modelo renderizar

Figura 11: o fluxo de trabalho do controle LoginView para determinar qual modelo renderizar (clique para exibir a imagem em tamanho real)

Limitando programaticamente a funcionalidade

Embora o controle LoginView exiba instruções diferentes com base na função do usuário que está visitando a página, os botões Editar e Cancelar permanecem visíveis para todos. Precisamos ocultar programaticamente os botões Editar e Excluir para visitantes anônimos e usuários que não estão na função Supervisores nem Administradores. Precisamos ocultar o botão Excluir para todos que não são administradores. Para fazer isso, escreveremos um pouco de código que referencia programaticamente os LinkButtons Edit e Delete do CommandField e define suas Visible propriedades como False, se necessário.

A maneira mais fácil de referenciar programaticamente controles em um CommandField é primeiro convertê-lo em um modelo. Para fazer isso, clique no link "Editar Colunas" na Marca Inteligente do GridView, selecione CommandField na lista de campos atuais e clique no link "Converter este campo em um TemplateField". Isso transforma o CommandField em um TemplateField com um ItemTemplate e EditItemTemplate. O ItemTemplate contém o LinkButtons Editar e Excluir enquanto o EditItemTemplate abriga o LinkButtons Update e Cancel.

Converter o CommandField em um TemplateField

Figura 12: Converter o CommandField em um TemplateField (clique para exibir a imagem em tamanho real)

Atualize o Editar e Excluir LinkButtons no ItemTemplate, definindo suas ID propriedades como valores de EditButton e DeleteButton, respectivamente.

<asp:TemplateField ShowHeader="False">
     <EditItemTemplate>
          <asp:LinkButton ID="LinkButton1" runat="server" CausesValidation="True" 
               CommandName="Update" Text="Update"></asp:LinkButton>

           <asp:LinkButton ID="LinkButton2" runat="server" CausesValidation="False"
               CommandName="Cancel" Text="Cancel"></asp:LinkButton>

     </EditItemTemplate>
     <ItemTemplate>
          <asp:LinkButton ID="EditButton" runat="server" CausesValidation="False" 
               CommandName="Edit" Text="Edit"></asp:LinkButton>

           <asp:LinkButton ID="DeleteButton" runat="server" CausesValidation="False"
               CommandName="Delete" Text="Delete"></asp:LinkButton>

     </ItemTemplate>
</asp:TemplateField>

Sempre que os dados são associados ao GridView, o GridView enumera os registros em sua DataSource propriedade e gera um objeto correspondente GridViewRow . À medida que cada GridViewRow objeto é criado, o RowCreated evento é acionado. Para ocultar os botões Editar e Excluir para usuários não autorizados, precisamos criar um manipulador de eventos para esse evento e referenciar programaticamente o LinkButtons Editar e Excluir, definindo suas Visible propriedades adequadamente.

Crie um manipulador de eventos do RowCreated evento e adicione o seguinte código:

Protected Sub UserGrid_RowCreated(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.GridViewRowEventArgs) Handles UserGrid.RowCreated
     If e.Row.RowType = DataControlRowType.DataRow AndAlso e.Row.RowIndex <> UserGrid.EditIndex Then
          ' Programmatically reference the Edit and Delete LinkButtons
          Dim EditButton As LinkButton = CType(e.Row.FindControl("EditButton"), LinkButton)

          Dim DeleteButton As LinkButton = CType(e.Row.FindControl("DeleteButton"), LinkButton)

          EditButton.Visible = (User.IsInRole("Administrators") OrElse User.IsInRole("Supervisors"))
          DeleteButton.Visible = User.IsInRole("Administrators")
     End If
End Sub

Tenha em mente que o RowCreated evento é acionado para todas as linhas gridView, incluindo o cabeçalho, o rodapé, a interface do pager e assim por diante. Só queremos referenciar programaticamente os botões Editar e Excluir LinkButtons se estivermos lidando com uma linha de dados que não está no modo de edição (já que a linha no modo de edição tem botões Atualizar e Cancelar em vez de Editar e Excluir). Esse marcar é tratado pela instrução If .

Se estivermos lidando com uma linha de dados que não está no modo de edição, os LinkButtons Editar e Excluir serão referenciados e suas Visible propriedades serão definidas com base nos valores boolianos retornados pelo User método do IsInRole(roleName) objeto. O User objeto faz referência à entidade de segurança criada pelo RoleManagerModule; consequentemente, o IsInRole(roleName) método usa a API roles para determinar se o visitante atual pertence a roleName.

Observação

Poderíamos ter usado a classe Roles diretamente, substituindo a chamada para User.IsInRole(roleName) por uma chamada para o Roles.IsUserInRole(roleName) método . Decidi usar o método do IsInRole(roleName) objeto principal neste exemplo porque ele é mais eficiente do que usar a API de Funções diretamente. Anteriormente neste tutorial, configuramos o gerenciador de funções para armazenar em cache as funções do usuário em um cookie. Esses dados de cookie armazenados em cache só são utilizados quando o método da IsInRole(roleName) entidade de segurança é chamado; chamadas diretas para a API de Funções sempre envolvem uma viagem para o repositório de funções. Mesmo que as funções não sejam armazenadas em cache em um cookie, chamar o método do IsInRole(roleName) objeto principal geralmente é mais eficiente porque quando ele é chamado pela primeira vez durante uma solicitação, ele armazena os resultados em cache. A API de Funções, por outro lado, não executa nenhum cache. Como o RowCreated evento é acionado uma vez para cada linha no GridView, usar User.IsInRole(roleName) envolve apenas uma viagem ao repositório de funções, enquanto Roles.IsUserInRole(roleName) requer N viagens, em que N é o número de contas de usuário exibidas na grade.

A propriedade do Visible botão Editar será definida True como se o usuário que estiver visitando esta página estiver na função Administradores ou Supervisores; caso contrário, ela será definida Falsecomo . A propriedade do Visible botão Excluir será definida True como somente se o usuário estiver na função Administradores.

Teste esta página por meio de um navegador. Se você visitar a página como um visitante anônimo ou como um usuário que não seja um Supervisor nem um Administrador, o CommandField estará vazio; ele ainda existe, mas como uma lasca fina sem os botões Editar ou Excluir.

Observação

É possível ocultar o CommandField completamente quando um não supervisor e não administrador estiver visitando a página. Eu deixo isso como um exercício para o leitor.

Os botões Editar e Excluir ficam ocultos para não supervisores e não administradores

Figura 13: os botões Editar e Excluir estão ocultos para não supervisores e não administradores (clique para exibir a imagem em tamanho real)

Se um usuário que pertence à função Supervisores (mas não à função Administradores), ele verá apenas o botão Editar.

Enquanto o botão Editar está disponível para supervisores, o botão Excluir fica oculto

Figura 14: enquanto o botão Editar está disponível para supervisores, o Botão Excluir está Oculto (Clique para exibir a imagem em tamanho real)

E se um administrador visitar, ela terá acesso aos botões Editar e Excluir.

Os botões Editar e Excluir estão disponíveis somente para administradores

Figura 15: Os botões Editar e Excluir estão disponíveis somente para administradores (clique para exibir imagem em tamanho real)

Etapa 3: Aplicar regras de autorização Role-Based a classes e métodos

Na Etapa 2, limitamos os recursos de edição aos usuários nas funções Supervisores e Administradores e excluímos recursos somente para Administradores. Isso foi feito ocultando os elementos de interface do usuário associados para usuários não autorizados por meio de técnicas programáticas. Essas medidas não garantem que um usuário não autorizado não poderá executar uma ação privilegiada. Pode haver elementos de interface do usuário adicionados posteriormente ou que esquecemos de ocultar para usuários não autorizados. Ou um hacker pode descobrir outra maneira de obter a página ASP.NET para executar o método desejado.

Uma maneira fácil de garantir que uma determinada funcionalidade não possa ser acessada por um usuário não autorizado é decorar essa classe ou método com o PrincipalPermission atributo . Quando o runtime do .NET usa uma classe ou executa um de seus métodos, ele verifica se o contexto de segurança atual tem permissão. O PrincipalPermission atributo fornece um mecanismo por meio do qual podemos definir essas regras.

Analisamos o uso do PrincipalPermission atributo de volta no tutorial Autorização Baseada no Usuário. Especificamente, vimos como decorar o manipulador de eventos e RowDeleting gridview SelectedIndexChanged para que eles só pudessem ser executados por usuários autenticados e Tito, respectivamente. O PrincipalPermission atributo funciona tão bem com funções.

Vamos demonstrar o uso do PrincipalPermission atributo nos manipuladores de eventos e RowDeleting gridview RowUpdating para proibir a execução para usuários não autorizados. Tudo o que precisamos fazer é adicionar o atributo apropriado em cima de cada definição de função:

<PrincipalPermission(SecurityAction.Demand, Role:="Administrators")>_
<PrincipalPermission(SecurityAction.Demand, Role:="Supervisors")>_
Protected Sub UserGrid_RowUpdating(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.GridViewUpdateEventArgs) Handles UserGrid.RowUpdating
     ...
End Sub

<PrincipalPermission(SecurityAction.Demand, Role:="Administrators")>_
Protected Sub UserGrid_RowDeleting(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.GridViewDeleteEventArgs) Handles UserGrid.RowDeleting
     ...
End Sub

O atributo para o RowUpdating manipulador de eventos determina que somente os usuários nas funções Administradores ou Supervisores podem executar o manipulador de eventos, em que como o atributo no RowDeleting manipulador de eventos limita a execução aos usuários na função Administradores.

Observação

O PrincipalPermission atributo é representado como uma classe no System.Security.Permissions namespace. Adicione uma Imports System.Security.Permissions instrução na parte superior do arquivo de classe code-behind para importar esse namespace.

Se, de alguma forma, um não administrador tentar executar o RowDeleting manipulador de eventos ou se um não supervisor ou não administrador tentar executar o RowUpdating manipulador de eventos, o runtime do .NET gerará um SecurityException.

Se o contexto de segurança não estiver autorizado a executar o método, uma SecurityException será gerada

Figura 16: se o contexto de segurança não estiver autorizado a executar o método, um SecurityException será gerado (clique para exibir a imagem em tamanho real)

Além de ASP.NET páginas, muitos aplicativos também têm uma arquitetura que inclui várias camadas, como lógica de negócios e camadas de acesso a dados. Normalmente, essas camadas são implementadas como Bibliotecas de Classes e oferecem classes e métodos para executar funcionalidades relacionadas à lógica de negócios e aos dados. O PrincipalPermission atributo também é útil para aplicar regras de autorização a essas camadas.

Para obter mais informações sobre como usar o PrincipalPermission atributo para definir regras de autorização em classes e métodos, consulte a entrada de blog de Scott Guthrieadicionando regras de autorização a camadas de dados e comerciais usando PrincipalPermissionAttributes.

Resumo

Neste tutorial, analisamos como especificar regras de autorização de granularidade grosseiras e finas com base nas funções do usuário. ASP. O recurso de autorização de URL do NET permite que um desenvolvedor de páginas especifique quais identidades são permitidas ou têm acesso negado a quais páginas. Como vimos no tutorial autorização baseada no usuário, as regras de autorização de URL podem ser aplicadas de acordo com o usuário. Eles também podem ser aplicados função por função, como vimos na Etapa 1 deste tutorial.

As regras de autorização de granularidade fina podem ser aplicadas declarativamente ou programaticamente. Na Etapa 2, examinamos o uso do recurso RoleGroups do controle LoginView para renderizar uma saída diferente com base nas funções do usuário visitante. Também analisamos maneiras de determinar programaticamente se um usuário pertence a uma função específica e como ajustar a funcionalidade da página adequadamente.

Programação feliz!

Leitura Adicional

Para obter mais informações sobre os tópicos discutidos neste tutorial, consulte os seguintes recursos:

Sobre o autor

Scott Mitchell, autor de vários livros do ASP/ASP.NET e fundador da 4GuysFromRolla.com, trabalha com tecnologias da Microsoft Web desde 1998. Scott trabalha como consultor independente, treinador e escritor. Seu último livro é Sams Teach Yourself ASP.NET 2.0 em 24 Horas. Scott pode ser contatado em mitchell@4guysfromrolla.com ou através de seu blog em http://ScottOnWriting.NET.

Agradecimentos especiais a...

Esta série de tutoriais foi revisada por muitos revisores úteis. Os principais revisores deste tutorial incluem Suchi Banerjee e Teresa Murphy. Interessado em revisar meus próximos artigos do MSDN? Nesse caso, solte-me uma linha em mitchell@4GuysFromRolla.com