Autorización basada en roles (C#)

por Scott Mitchell

Descargar código o Descargar PDF

En este tutorial se empieza con una visión de cómo el marco de trabajo de roles asocia los roles de un usuario con su contexto de seguridad. A continuación, examina cómo aplicar reglas de autorización de URL basadas en roles. A continuación, veremos el uso de métodos declarativos y de programación para modificar los datos mostrados y la funcionalidad que ofrece una página de ASP.NET.

Introducción

En el tutorial de autorización basada en el usuario vimos cómo usar la autorización de URL para especificar qué usuarios pueden visitar un conjunto determinado de páginas. Con tan solo un poco de marcado en Web.config , podríamos indicar a ASP.net que solo permitiera a los usuarios autenticados visitar una página. O bien, podríamos obligar a que solo se permitieran los usuarios Tito y Bob, o que indiquen que se permitían todos los usuarios autenticados excepto Sam.

Además de la autorización de URL, también hemos examinado las técnicas declarativas y de programación para controlar los datos que se muestran y la funcionalidad que ofrece una página basada en el usuario que visita. En concreto, hemos creado una página que muestra el contenido del directorio actual. Cualquier persona podría visitar esta página, pero solo los usuarios autenticados podrían ver el contenido de los archivos y solo Tito podrían eliminarlos.

La aplicación de reglas de autorización para cada usuario puede aumentar la pesadilla. Un enfoque más fácil de mantener es usar la autorización basada en roles. La buena noticia es que las herramientas de nuestra disposición para aplicar reglas de autorización funcionan igual de bien con roles que para las cuentas de usuario. Las reglas de autorización de URL pueden especificar roles en lugar de usuarios. El control LoginView, que representa una salida diferente para los usuarios autenticados y anónimos, se puede configurar para mostrar contenido diferente en función de los roles del usuario que ha iniciado sesión. Y la API de roles incluye métodos para determinar los roles del usuario que ha iniciado sesión.

En este tutorial se empieza con una visión de cómo el marco de trabajo de roles asocia los roles de un usuario con su contexto de seguridad. A continuación, examina cómo aplicar reglas de autorización de URL basadas en roles. A continuación, veremos el uso de métodos declarativos y de programación para modificar los datos mostrados y la funcionalidad que ofrece una página de ASP.NET. Comencemos.

Descripción de cómo se asocian los roles con el contexto de seguridad de un usuario

Cada vez que una solicitud entra en la canalización ASP.NET, se asocia a un contexto de seguridad, que incluye información que identifica al solicitante. Al utilizar la autenticación de formularios, se usa un vale de autenticación como un token de identidad. Como se explicó en el tutorial de Introducción a la autenticación de formularios , el FormsAuthenticationModule es responsable de determinar la identidad del solicitante, lo que hace durante el AuthenticateRequest evento.

Si se encuentra un vale de autenticación no expirado válido, lo FormsAuthenticationModule descodifica para determinar la identidad del solicitante. Crea un nuevo GenericPrincipal objeto y lo asigna al HttpContext.User objeto. El propósito de una entidad de seguridad, como GenericPrincipal , es identificar el nombre del usuario autenticado y los roles a los que pertenece. Este propósito es evidente por el hecho de que todos los objetos de entidad de seguridad tienen una Identity propiedad y un IsInRole(roleName) método. FormsAuthenticationModuleSin embargo, no está interesado en registrar información de roles y el GenericPrincipal objeto que crea no especifica ningún rol.

Si el marco de trabajo de roles está habilitado, el RoleManagerModule módulo HTTP se ejecuta en después de FormsAuthenticationModule y identifica los roles del usuario autenticado durante el PostAuthenticateRequest evento, que se desencadena después del AuthenticateRequest evento. Si la solicitud proviene de un usuario autenticado, RoleManagerModule sobrescribe el GenericPrincipal objeto creado por FormsAuthenticationModule y lo reemplaza por un RolePrincipal objeto. La RolePrincipal clase usa la API de roles para determinar los roles a los que pertenece el usuario.

La figura 1 muestra el flujo de trabajo de la canalización ASP.NET cuando se usa la autenticación de formularios y el marco de trabajo de roles. El FormsAuthenticationModule primero se ejecuta, identifica al usuario a través de su vale de autenticación y crea un nuevo GenericPrincipal objeto. A continuación, los RoleManagerModule pasos de y sobrescriben el GenericPrincipal objeto con un RolePrincipal objeto.

Si un usuario anónimo visita el sitio, FormsAuthenticationModule ni RoleManagerModule crea un objeto de entidad de seguridad.

Eventos de canalización ASP.NET para un usuario autenticado cuando se usa la autenticación de formularios y el marco de trabajo de roles

Figura 1: eventos de canalización ASP.net para un usuario autenticado cuando se usa la autenticación de formularios y el marco de trabajo de roles (haga clic para ver la imagen de tamaño completo)

El RolePrincipal método del objeto IsInRole(roleName) llama Roles.GetRolesForUser a para obtener los roles para el usuario con el fin de determinar si el usuario es un miembro de roleName. Al usar SqlRoleProvider , esto da como resultado una consulta a la base de datos del almacén de roles. Al usar las reglas de autorización de URL basada en roles RolePrincipal IsInRole , se llamará al método de en cada solicitud a una página protegida por las reglas de autorización de la dirección URL basada en roles. En lugar de tener que buscar la información de roles en la base de datos en cada solicitud, el marco de trabajo de roles incluye una opción para almacenar en caché los roles del usuario en una cookie.

Si el marco de trabajo de roles está configurado para almacenar en caché los roles del usuario en una cookie, RoleManagerModule crea la cookie durante el EndRequest eventode la canalización ASP.net. Esta cookie se usa en las solicitudes posteriores de PostAuthenticateRequest , que es cuando RolePrincipal se crea el objeto. Si la cookie es válida y no ha expirado, los datos de la cookie se analizan y se utilizan para rellenar los roles del usuario, con lo que se RolePrincipal evita que tenga que realizar una llamada a la Roles clase para determinar los roles del usuario. En la ilustración 2 se muestra este flujo de trabajo.

La información del rol del usuario se puede almacenar en una cookie para mejorar el rendimiento.

Figura 2: la información de rol del usuario se puede almacenar en una cookie para mejorar el rendimiento (haga clic para ver la imagen de tamaño completo)

De forma predeterminada, el mecanismo de cookies de la caché de rol está deshabilitado. Se puede habilitar mediante el <roleManager> marcado de configuración en Web.config . Hemos explicado cómo usar el <roleManager> elemento para especificar los proveedores de roles en el tutorial creación y administración de roles , por lo que ya debe tener este elemento en el archivo de la aplicación Web.config . La configuración de cookies de la caché de roles se especifica como atributos del <roleManager> elemento y se resumen en la tabla 1.

Note

Los valores de configuración que aparecen en la tabla 1 especifican las propiedades de la cookie de caché de rol resultante. Para obtener más información sobre las cookies, cómo funcionan y sus distintas propiedades, lea este tutorial de cookies.

Propiedad Descripción
cacheRolesInCookie Valor booleano que indica si se utiliza el almacenamiento en caché de cookies. Tiene como valor predeterminado false.
cookieName Nombre de la cookie de caché de rol. El valor predeterminado es ". ASPXROLES ".
cookiePath La ruta de acceso de la cookie de nombre de roles. El atributo path permite a un programador limitar el ámbito de una cookie a una jerarquía de directorios determinada. El valor predeterminado es "/", que informa al explorador de que envíe la cookie de vale de autenticación a cualquier solicitud realizada al dominio.
cookieProtection Indica las técnicas que se usan para proteger la cookie de la caché de roles. Los valores permitidos son: All (el valor predeterminado); Encryption ; None ; y Validation .
cookieRequireSSL Valor booleano que indica si se requiere una conexión SSL para transmitir la cookie de autenticación. El valor predeterminado es false.
cookieSlidingExpiration Valor booleano que indica si el tiempo de espera de la cookie se restablece cada vez que el usuario visita el sitio durante una sola sesión. El valor predeterminado es false. Este valor solo es relevante cuando createPersistentCookie se establece en true .
cookieTimeout Especifica el tiempo, en minutos, después del cual expira la cookie del vale de autenticación. El valor predeterminado es 30. Este valor solo es relevante cuando createPersistentCookie se establece en true .
createPersistentCookie Valor booleano que especifica si la cookie de caché de rol es una cookie de sesión o una cookie persistente. Si es false (valor predeterminado), se usa una cookie de sesión, que se elimina cuando se cierra el explorador. Si true es, se usa una cookie persistente; expira el cookieTimeout número de minutos después de que se haya creado o después de la visita anterior, dependiendo del valor de cookieSlidingExpiration .
domain Especifica el valor de dominio de la cookie. El valor predeterminado es una cadena vacía, que hace que el explorador use el dominio desde el que se emitió (por ejemplo, www.yourdomain.com). En este caso, la cookie no se enviará al realizar solicitudes a subdominios, como admin.yourdomain.com. Si desea que la cookie se pase a todos los subdominios, debe personalizar el domain atributo, estableciéndolo en "yourdomain.com".
maxCachedResults Especifica el número máximo de nombres de rol que se almacenan en caché en la cookie. El valor predeterminado es 25. No RoleManagerModule crea una cookie para los usuarios que pertenecen a más de maxCachedResults roles. Por consiguiente, el RolePrincipal método del objeto usará IsInRole la Roles clase para determinar los roles del usuario. La razón maxCachedResults existe porque muchos agentes de usuario no admiten cookies de más de 4.096 bytes. Por lo tanto, este extremo está pensado para reducir la probabilidad de superar este límite de tamaño. Si tiene nombres de rol muy largos, puede que desee considerar la posibilidad de especificar un maxCachedResults valor menor; contrariwise, si tiene nombres de rol muy cortos, probablemente pueda aumentar este valor.

Tabla 1: Opciones de configuración de cookies de caché de rol

Vamos a configurar nuestra aplicación para que use cookies de caché de rol no persistente. Para ello, actualice el <roleManager> elemento de Web.config para incluir los siguientes atributos relacionados con las cookies:

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

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

He actualizado el <roleManager> elemento agregando tres atributos: cacheRolesInCookie , createPersistentCookie y cookieProtection . Al establecer cacheRolesInCookie en true , RoleManagerModule ahora almacenará automáticamente en caché los roles del usuario en una cookie en lugar de tener que buscar la información de roles del usuario en cada solicitud. Configuro explícitamente createPersistentCookie los cookieProtection atributos y en false y All , respectivamente. Técnicamente, no era necesario especificar valores para estos atributos, ya que simplemente se les ha asignado a sus valores predeterminados, pero los pongo aquí para que queden claramente claros que no utilizo cookies persistentes y que la cookie se cifra y valida.

Así de simple. En adelante, el marco de trabajo de roles almacenará en la caché los roles de los usuarios en cookies. Si el explorador del usuario no admite cookies, o si las cookies se eliminan o se pierden, de algún modo no es importante: el RolePrincipal objeto simplemente usará la Roles clase en el caso de que no haya ninguna cookie (o una no válida o expirada).

Note

El grupo de prácticas de patrones de Microsoft no & recomienda el uso de cookies de caché de rol persistentes. Puesto que la posesión de la cookie de caché de rol es suficiente para demostrar la pertenencia al rol, si un pirata informático puede obtener acceso de algún modo a la cookie de un usuario válido, puede suplantar a ese usuario. La probabilidad de que esto suceda aumenta si la cookie se conserva en el explorador del usuario. Para obtener más información sobre esta recomendación de seguridad, así como otros problemas de seguridad, consulte la lista de preguntas de seguridad de ASP.NET 2,0.

Paso 1: definición de las reglas de autorización de URL de Role-Based

Tal y como se describe en el tutorial de autorización basada en el usuario, la autorización de direcciones URL ofrece un medio para restringir el acceso a un conjunto de páginas para cada usuario o función. Las reglas de autorización de URL se escriben en Web.config con el <authorization> elemento con los <allow> <deny> elementos secundarios y. Además de las reglas de autorización relacionadas con el usuario descritas en los tutoriales anteriores, cada <allow> <deny> elemento secundario y también puede incluir:

  • Un rol determinado
  • Una lista delimitada por comas de roles

Por ejemplo, las reglas de autorización de URL conceden acceso a los usuarios de los roles administradores y supervisores, pero deniegan el acceso a todos los demás:

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

El <allow> elemento en el marcado anterior indica que se permiten los roles administradores y supervisores; el <deny> elemento indica que se deniegan todos los usuarios.

Vamos a configurar nuestra aplicación para que las ManageRoles.aspx UsersAndRoles.aspx páginas, y CreateUserWizardWithRoles.aspx solo sean accesibles para los usuarios del rol administradores, mientras que la RoleBasedAuthorization.aspx Página permanece accesible para todos los visitantes.

Para ello, empiece agregando un Web.config archivo a la Roles carpeta.

Agregar un archivo de Web.config al directorio de roles

Figura 3: agregar un Web.config archivo al Roles directorio (haga clic para ver la imagen de tamaño completo)

A continuación, agregue el siguiente marcado de configuración 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>

El <authorization> elemento de la <system.web> sección indica que solo los usuarios del rol administradores pueden acceder a los recursos de ASP.net en el Roles directorio. El <location> elemento define un conjunto alternativo de reglas de autorización de URL para la RoleBasedAuthorization.aspx página, lo que permite que todos los usuarios visiten la página.

Después de guardar los cambios en Web.config , inicie sesión como un usuario que no está en el rol administradores y, a continuación, intente visitar una de las páginas protegidas. UrlAuthorizationModuleDetectará que no tiene permiso para visitar el recurso solicitado; por lo tanto, FormsAuthenticationModule se le redirigirá a la página de inicio de sesión. La página de inicio de sesión le redirigirá a la UnauthorizedAccess.aspx página (vea la figura 4). Este redireccionamiento final de la página de inicio de sesión de UnauthorizedAccess.aspx se produce debido al código que se agregó a la página de inicio de sesión en el paso 2 del tutorial de autorización basada en el usuario. En concreto, la página de inicio de sesión redirige automáticamente cualquier usuario autenticado a UnauthorizedAccess.aspx si QueryString contiene un ReturnUrl parámetro, ya que este parámetro indica que el usuario llegó a la página de inicio de sesión después de intentar ver una página que no estaba autorizado a ver.

Solo los usuarios del rol administradores pueden ver las páginas protegidas

Figura 4: solo los usuarios del rol administradores pueden ver las páginas protegidas (haga clic para ver la imagen a tamaño completo)

Cierre la sesión y, a continuación, inicie sesión como un usuario con el rol administradores. Ahora debería poder ver las tres páginas protegidas.

Tito puede visitar la página UsersAndRoles. aspx porque tiene el rol administradores

Figura 5: Tito puede visitar la UsersAndRoles.aspx Página porque tiene el rol administradores (haga clic para ver la imagen a tamaño completo)

Note

Al especificar las reglas de autorización de URL (para roles o usuarios) es importante tener en cuenta que las reglas se analizan de una en una, de arriba abajo. En cuanto se encuentra una coincidencia, se concede o se deniega el acceso al usuario, en función de si se encontró la coincidencia en un <allow> <deny> elemento o. Si no se encuentra ninguna coincidencia, se concede al usuario acceso. Por lo tanto, si desea restringir el acceso a una o varias cuentas de usuario, es imperativo que use un <deny> elemento como último elemento en la configuración de autorización de la dirección URL. Si las reglas de autorización de URL no incluyen un <deny> , se concederá acceso a todos los usuarios. Para obtener una explicación más detallada sobre cómo se analizan las reglas de autorización de URL, consulte la sección "un vistazo a cómo UrlAuthorizationModule utiliza las reglas de autorización para conceder o denegar el acceso" del tutorial de autorización basada en el usuario .

Paso 2: limitar la funcionalidad en función de los roles del usuario que ha iniciado sesión actualmente

La autorización de direcciones URL facilita la especificación de reglas de autorización generales que indican qué identidades se permiten y cuáles se deniegan para ver una página determinada (o todas las páginas de una carpeta y sus subcarpetas). Sin embargo, en algunos casos, es posible que desee permitir que todos los usuarios visiten una página, pero limiten la funcionalidad de la página en función de los roles del usuario visitante. Esto puede implicar la visualización u ocultación de los datos en función del rol del usuario o la oferta de funcionalidad adicional a los usuarios que pertenecen a un rol determinado.

Estas reglas de autorización basadas en roles de grano se pueden implementar de forma declarativa o mediante programación (o a través de alguna combinación de los dos). En la sección siguiente, veremos cómo implementar la autorización específica declarativo a través del control LoginView. A continuación, exploraremos las técnicas de programación. Sin embargo, antes de que podamos ver la aplicación de reglas de autorización de grano fino, primero tenemos que crear una página cuya funcionalidad depende del rol del usuario que la visita.

Vamos a crear una página que enumera todas las cuentas de usuario del sistema en un control GridView. GridView incluirá el nombre de usuario, la dirección de correo electrónico, la fecha del último inicio de sesión y los comentarios del usuario. Además de mostrar la información de cada usuario, el control GridView incluirá capacidades de edición y eliminación. Esta página se creará inicialmente con la funcionalidad de edición y eliminación disponible para todos los usuarios. En las secciones "uso del control LoginView" y "limitar la funcionalidad mediante programación", veremos cómo habilitar o deshabilitar estas características en función del rol del usuario que ha visitado.

Note

La página ASP.NET que se va a compilar usa un control GridView para mostrar las cuentas de usuario. Dado que esta serie de tutoriales se centra en la autenticación de formularios, la autorización, las cuentas de usuario y los roles, no deseo gastar demasiado tiempo en hablar sobre el funcionamiento interno del control GridView. Aunque en este tutorial se proporcionan instrucciones paso a paso específicas para configurar esta página, no profundiza en los detalles del motivo por el que se realizaron ciertas opciones o el efecto que tienen determinadas propiedades en la salida representada. Para un examen exhaustivo del control GridView, consulte mi trabajo con datos en la serie de tutoriales de ASP.NET 2,0 .

Para empezar, abra la RoleBasedAuthorization.aspx página en la Roles carpeta. Arrastre un control GridView desde la página hasta el diseñador y establezca su ID en UserGrid . En un momento, se escribirá código que llama al Membership.GetAllUsers método y enlaza el MembershipUserCollection objeto resultante a GridView. MembershipUserCollectionContiene un MembershipUser objeto para cada cuenta de usuario del sistema; los MembershipUser objetos tienen propiedades como UserName , Email , LastLoginDate , etc.

Antes de escribir el código que enlaza las cuentas de usuario a la cuadrícula, vamos a definir primero los campos de GridView. En la etiqueta inteligente de GridView, haga clic en el vínculo "editar columnas" para iniciar el cuadro de diálogo campos (vea la figura 6). Desde aquí, desactive la casilla "generar automáticamente campos" en la esquina inferior izquierda. Como queremos que este GridView incluya funcionalidades de edición y eliminación, agregue CommandField y establezca sus ShowEditButton ShowDeleteButton propiedades y en true. A continuación, agregue cuatro campos para mostrar UserName las Email propiedades,, LastLoginDate y Comment . Use un BoundField para las dos propiedades de solo lectura ( UserName y LastLoginDate ) y TemplateFields para los dos campos modificables ( Email y Comment ).

Haga que el primer BoundField muestre la UserName propiedad; establezca HeaderText sus DataField propiedades y en "username". Este campo no se podrá modificar, así que establezca su ReadOnly propiedad en true. Configure LastLoginDate BoundField estableciendo su HeaderText en "último inicio de sesión" y su DataField en "LastLoginDate". Vamos a dar formato a la salida de este BoundField para que solo se muestre la fecha (en lugar de la fecha y la hora). Para ello, establezca la propiedad de BoundField HtmlEncode en false y su DataFormatString propiedad en " {0:d} ". Establezca también la ReadOnly propiedad en true.

Establezca las HeaderText propiedades de los dos TemplateFields en "email" y "comment".

Los campos de GridView se pueden configurar mediante el cuadro de diálogo campos.

Figura 6: los campos de GridView se pueden configurar mediante el cuadro de diálogo campos (haga clic para ver la imagen a tamaño completo)

Ahora tenemos que definir el ItemTemplate y EditItemTemplate el de "email" y "comment" TemplateFields. Agregue un control Web Label a cada uno de los ItemTemplate s y enlace sus Text propiedades a Email las Comment propiedades y, respectivamente.

En el caso de "email", agregue un cuadro de texto denominado Email a su EditItemTemplate y enlace su Text propiedad a la Email propiedad mediante el enlace de los dos sentidos. Agregue un RequiredFieldValidator y un RegularExpressionValidator a EditItemTemplate para asegurarse de que un visitante que edita la propiedad de correo electrónico ha escrito una dirección de correo electrónico válida. En el caso del "comentario", agregue un cuadro de texto de varias líneas denominado Comment a su EditItemTemplate . Establezca las propiedades y del cuadro de texto Columns Rows en 40 y 4, respectivamente, y, a continuación, enlace su Text propiedad a la Comment propiedad mediante el enlace de los dos sentidos.

Después de configurar estos TemplateFields, su marcado declarativo debe ser similar al siguiente:

<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>

Al editar o eliminar una cuenta de usuario, es necesario conocer el valor de la UserName propiedad del usuario. Establezca la propiedad de GridView DataKeyNames en "username" para que esta información esté disponible a través de la DataKeys colección de GridView.

Por último, agregue un control ValidationSummary a la página y establezca su ShowMessageBox propiedad en true y su ShowSummary propiedad en false. Con esta configuración, ValidationSummary mostrará una alerta del lado cliente si el usuario intenta editar una cuenta de usuario con una dirección de correo electrónico que falta o no es válida.

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

Ahora hemos completado el marcado declarativo de esta página. La siguiente tarea consiste en enlazar el conjunto de cuentas de usuario a GridView. Agregue un método denominado BindUserGrid a la RoleBasedAuthorization.aspx clase de código subyacente de la página que enlaza el MembershipUserCollection devuelto por Membership.GetAllUsers a UserGrid GridView. Llame a este método desde el Page_Load controlador de eventos en la primera visita de la página.

protected void Page_Load(object sender, EventArgs e)    
{    
     if (!Page.IsPostBack)    
          BindUserGrid();    
}

private void BindUserGrid()    
{    
     MembershipUserCollection allUsers = Membership.GetAllUsers();    
     UserGrid.DataSource = allUsers;    
     UserGrid.DataBind();    
}

Con este código en su lugar, visite la página a través de un explorador. Como se muestra en la figura 7, debería ver una información de lista de GridView acerca de cada cuenta de usuario del sistema.

La UserGrid GridView muestra información sobre cada usuario del sistema.

Figura 7: el control UserGrid GridView muestra información sobre cada usuario del sistema (haga clic para ver la imagen de tamaño completo)

Note

UserGridGridView muestra todos los usuarios en una interfaz no paginada. Esta interfaz de cuadrícula simple no es adecuada para escenarios en los que hay varias docenas o más usuarios. Una opción consiste en configurar GridView para habilitar la paginación. El Membership.GetAllUsers método tiene dos sobrecargas: una que no acepta parámetros de entrada y devuelve todos los usuarios y uno que toma valores enteros para el índice de página y el tamaño de página, y devuelve solo el subconjunto especificado de los usuarios. La segunda sobrecarga se puede utilizar para paginar más eficazmente a través de los usuarios, ya que devuelve solo el subconjunto preciso de cuentas de usuario en lugar de todas ellas. Si tiene miles de cuentas de usuario, puede considerar la posibilidad de usar una interfaz basada en filtros, una que solo muestra los usuarios cuyo nombre de usuario comienza por un carácter seleccionado, por ejemplo. Membership.FindUsersByName methodEs ideal para crear una interfaz de usuario basada en filtros. Veremos cómo crear una interfaz de este tipo en un tutorial futuro.

El control GridView ofrece compatibilidad integrada de edición y eliminación cuando el control está enlazado a un control de origen de datos configurado correctamente, como SqlDataSource o ObjectDataSource. UserGridSin embargo, GridView tiene sus datos enlazados mediante programación; por lo tanto, se debe escribir código para realizar estas dos tareas. En concreto, es necesario crear controladores de eventos para los RowEditing RowCancelingEdit eventos,, y de GridView RowUpdating RowDeleting , que se desencadenan cuando un visitante hace clic en los botones de edición, cancelación, actualización o eliminación de GridView.

Empiece por crear los controladores de eventos para los RowEditing eventos, y de GridView RowCancelingEdit RowUpdating y, a continuación, agregue el código siguiente:

protected void UserGrid_RowEditing(object sender, GridViewEditEventArgs e)
{
     // Set the grid's EditIndex and rebind the data

     UserGrid.EditIndex = e.NewEditIndex;
     BindUserGrid();
}

protected void UserGrid_RowCancelingEdit(object sender, GridViewCancelEditEventArgs e)
{
     // Revert the grid's EditIndex to -1 and rebind the data
     UserGrid.EditIndex = -1;
     BindUserGrid();
}

protected void UserGrid_RowUpdating(object sender, GridViewUpdateEventArgs e)
{
     // Exit if the page is not valid
     if (!Page.IsValid)
          return;

     // Determine the username of the user we are editing
     string UserName = UserGrid.DataKeys[e.RowIndex].Value.ToString();

     // Read in the entered information and update the user
     TextBox EmailTextBox = UserGrid.Rows[e.RowIndex].FindControl("Email") as TextBox;
     TextBox CommentTextBox = UserGrid.Rows[e.RowIndex].FindControl("Comment") as TextBox;

     // Return information about the user
     MembershipUser UserInfo = 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();
}

Los RowEditing RowCancelingEdit controladores de eventos y simplemente establecen la propiedad de GridView EditIndex y, a continuación, vuelve a enlazar la lista de cuentas de usuario a la cuadrícula. Las cosas interesantes se producen en el RowUpdating controlador de eventos. Este controlador de eventos se inicia asegurándose de que los datos son válidos y, a continuación, obtiene el UserName valor de la cuenta de usuario modificada de la DataKeys colección. Email Comment EditItemTemplate A continuación, se hace referencia mediante programación a los cuadros de texto y de los dos TemplateFields. Sus Text propiedades contienen la dirección de correo electrónico y el comentario editados.

Para actualizar una cuenta de usuario a través de la API de pertenencia, necesitamos obtener primero la información del usuario, que hacemos a través de una llamada a Membership.GetUser(userName) . Las MembershipUser propiedades y del objeto devuelto Email Comment se actualizan con los valores especificados en los dos cuadros de texto de la interfaz de edición. Por último, estas modificaciones se guardan con una llamada a Membership.UpdateUser . El RowUpdating controlador de eventos se completa revirtiendo GridView a su interfaz de edición previa.

A continuación, cree el RowDeleting controlador de eventos y, a continuación, agregue el código siguiente:

protected void UserGrid_RowDeleting(object sender, GridViewDeleteEventArgs e)
{
     // Determine the username of the user we are editing
     string UserName = 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();
}

El controlador de eventos anterior comienza capturando el UserName valor de la colección de GridView DataKeys ; este UserName valor se pasa a continuación en el DeleteUser métodode la clase de pertenencia. El DeleteUser método elimina la cuenta de usuario del sistema, incluidos los datos de pertenencia relacionados (como los roles a los que pertenece este usuario). Después de eliminar el usuario, el valor de la cuadrícula EditIndex se establece en-1 (en caso de que el usuario haga clic en eliminar mientras otra fila estaba en modo de edición) y BindUserGrid se llama al método.

Note

El botón eliminar no requiere ningún tipo de confirmación por parte del usuario antes de eliminar la cuenta de usuario. Le recomiendo que agregue algún tipo de confirmación de usuario para reducir la posibilidad de que una cuenta se elimine accidentalmente. Una de las formas más sencillas de confirmar una acción es a través de un cuadro de diálogo de confirmación en el lado cliente. Para obtener más información sobre esta técnica, consulte agregar Client-Side confirmación al eliminar.

Compruebe que esta página funciona según lo previsto. Debe poder editar la dirección de correo electrónico y el comentario de cualquier usuario, así como eliminar cualquier cuenta de usuario. Como la RoleBasedAuthorization.aspx página es accesible para todos los usuarios, cualquier usuario, incluso los visitantes anónimos, puede visitar esta página y editar y eliminar cuentas de usuario. Vamos a actualizar esta página para que solo los usuarios de los roles supervisores y administradores puedan editar la dirección de correo electrónico y el comentario de un usuario, y solo los administradores puedan eliminar una cuenta de usuario.

En la sección "usar el control LoginView" se examina el uso del control LoginView para mostrar instrucciones específicas del rol del usuario. Si una persona del rol administradores visita esta página, se mostrarán instrucciones sobre cómo editar y eliminar usuarios. Si un usuario del rol supervisores llega a esta página, se mostrarán instrucciones sobre cómo editar usuarios. Y si el visitante es anónimo o no está en el rol supervisores o administradores, se mostrará un mensaje que explica que no puede editar ni eliminar la información de la cuenta de usuario. En la sección "limitar la funcionalidad mediante programación", se escribirá código que muestra u oculta mediante programación los botones editar y eliminar según el rol del usuario.

Usar el control LoginView

Como hemos visto en los tutoriales anteriores, el control LoginView es útil para mostrar diferentes interfaces para usuarios autenticados y anónimos, pero el control LoginView también se puede usar para mostrar un marcado diferente en función de los roles del usuario. Vamos a usar un control LoginView para mostrar diferentes instrucciones basadas en el rol del usuario que ha visitado.

Empiece agregando un LoginView sobre UserGrid GridView. Como hemos explicado anteriormente, el control LoginView tiene dos plantillas integradas: AnonymousTemplate y LoggedInTemplate . Escriba un mensaje breve en ambas plantillas que informe al usuario de que no puede editar ni eliminar información de usuario.

<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>

Además de AnonymousTemplate y LoggedInTemplate , el control LoginView puede incluir RoleGroups, que son plantillas específicas del rol. Cada RoleGroup contiene una propiedad única, Roles , que especifica los roles a los que se aplica el RoleGroup. La Roles propiedad se puede establecer en un único rol (por ejemplo, "administradores") o en una lista delimitada por comas de roles (como "administradores, supervisores").

Para administrar el RoleGroups, haga clic en el vínculo "editar RoleGroups" de la etiqueta inteligente del control para abrir el editor de la colección RoleGroup. Agregue dos nuevos RoleGroups. Establezca la propiedad de la primera RoleGroup Roles en "Administrators" y la segunda en "supervisores".

Administrar plantillas de Role-Specific de LoginView mediante el editor de colección RoleGroup

Figura 8: administración de las plantillas de Role-Specific de LoginView mediante el editor de la colección RoleGroup (haga clic para ver la imagen a tamaño completo)

Haga clic en Aceptar para cerrar el editor de la colección RoleGroup; Esto actualiza el marcado declarativo de LoginView para incluir una <RoleGroups> sección con un <asp:RoleGroup> elemento secundario para cada RoleGroup definido en el editor de la colección RoleGroup. Además, la lista desplegable "vistas" de la etiqueta inteligente de LoginView, que inicialmente se enumera solo AnonymousTemplate y LoggedInTemplate , ahora incluye también el RoleGroups agregado.

Edite RoleGroups para que los usuarios del rol supervisores muestren instrucciones sobre cómo editar cuentas de usuario, mientras que los usuarios del rol administradores reciben instrucciones para editar y eliminar. Después de realizar estos cambios, el marcado declarativo de LoginView debe ser similar al siguiente.

<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>

Después de realizar estos cambios, guarde la página y, a continuación, visítela a través de un explorador. En primer lugar, visite la página como usuario anónimo. Debe mostrarse el mensaje "no ha iniciado sesión en el sistema. Por lo tanto, no puede editar ni eliminar información de usuario ". A continuación, inicie sesión como usuario autenticado, pero uno que no esté en el rol supervisores ni administradores. Esta vez debería ver el mensaje "no es miembro de los roles supervisores o administradores. Por lo tanto, no puede editar ni eliminar información de usuario ".

A continuación, inicie sesión como un usuario que sea miembro del rol supervisores. Esta vez debería ver el mensaje específico del rol supervisores (consulte la figura 9). Y si inicia sesión como usuario en el rol administradores, debería ver el mensaje específico del rol administradores (consulte la figura 10).

Bruce se muestra los supervisores Role-Specific mensaje

Figura 9: Bruce muestra los supervisores Role-Specific mensaje (haga clic para ver la imagen de tamaño completo)

Tito se muestra el mensaje administradores Role-Specific

Figura 10: Tito se muestra el mensaje Administrators Role-Specific (haga clic para ver la imagen de tamaño completo)

Como muestran las capturas de pantalla en las figuras 9 y 10, la LoginView solo representa una plantilla, incluso si se aplican varias plantillas. Bruce y Tito están registrados en los usuarios, pero el LoginView solo representa el RoleGroup coincidente y no el LoggedInTemplate . Además, Tito pertenece a los roles Administrators y supervisores, pero el control LoginView representa la plantilla específica de la función Administrators en lugar de los supervisores.

En la figura 11 se muestra el flujo de trabajo que usa el control LoginView para determinar la plantilla que se va a representar. Tenga en cuenta que si se especifica más de un RoleGroup, la plantilla LoginView representa el primer RoleGroup que coincide con. En otras palabras, si se hubieran colocado los supervisores RoleGroup como primer RoleGroup y los administradores como segundo, cuando Tito visite esta página, verá el mensaje supervisores.

El flujo de trabajo del control LoginView para determinar qué plantilla se va a representar

Figura 11: flujo de trabajo del control LoginView para determinar qué plantilla se va a representar (haga clic para ver la imagen de tamaño completo)

Limitar la funcionalidad mediante programación

Aunque el control LoginView muestra distintas instrucciones basadas en el rol del usuario que visita la página, los botones editar y cancelar permanecen visibles para todo. Es necesario ocultar mediante programación los botones editar y eliminar para los visitantes anónimos y los usuarios que no están en el rol supervisores ni administradores. Necesitamos ocultar el botón Eliminar para todos los usuarios que no sean administradores. Para lograr esto, escribiremos un poco de código que haga referencia mediante programación a la LinkButtons de edición y eliminación de CommandField y establezca sus Visible propiedades en false , si es necesario.

La manera más sencilla de hacer referencia mediante programación a los controles en un CommandField es convertirlo primero en una plantilla. Para ello, haga clic en el vínculo "editar columnas" de la etiqueta inteligente de GridView, seleccione CommandField en la lista de campos actuales y haga clic en el vínculo "convertir este campo en TemplateField". Esto convierte CommandField en TemplateField con ItemTemplate y EditItemTemplate . El ItemTemplate contiene el LinkButtons de edición y eliminación mientras EditItemTemplate hospeda la actualización y cancela LinkButtons.

Convertir CommandField en TemplateField

Figura 12: conversión de CommandField en TemplateField (haga clic para ver la imagen de tamaño completo)

Actualice el LinkButtons de edición y eliminación en el ItemTemplate , estableciendo sus ID propiedades en los valores de EditButton y 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>

Cada vez que se enlazan datos a GridView, GridView enumera los registros de su DataSource propiedad y genera un GridViewRow objeto correspondiente. A medida que GridViewRow se crea cada objeto, RowCreated se desencadena el evento. Para ocultar los botones de edición y eliminación de usuarios no autorizados, es necesario crear un controlador de eventos para este evento y hacer referencia mediante programación a las LinkButtons de edición y eliminación, estableciendo sus Visible propiedades en consecuencia.

Cree un controlador de eventos para el RowCreated evento y, a continuación, agregue el código siguiente:

protected void UserGrid_RowCreated(object sender, GridViewRowEventArgs e)
{
     if (e.Row.RowType == DataControlRowType.DataRow && e.Row.RowIndex != UserGrid.EditIndex)
     {
          // Programmatically reference the Edit and Delete LinkButtons
          LinkButton EditButton = e.Row.FindControl("EditButton") as LinkButton;

          LinkButton DeleteButton = e.Row.FindControl("DeleteButton") as LinkButton;

          EditButton.Visible = (User.IsInRole("Administrators") || User.IsInRole("Supervisors"));
          DeleteButton.Visible = User.IsInRole("Administrators");
     }
}

Tenga en cuenta que el RowCreated evento se desencadena para todas las filas de GridView, incluidos el encabezado, el pie de página, la interfaz de buscapersonas, etc. Solo queremos hacer referencia mediante programación a las LinkButtons de edición y eliminación si se trata de una fila de datos que no está en modo de edición (puesto que la fila del modo de edición tiene botones actualizar y cancelar en lugar de editar y eliminar). Esta comprobación se controla mediante la if instrucción.

Si se trata de una fila de datos que no está en modo de edición, se hace referencia a los LinkButtons de edición y eliminación, y sus Visible propiedades se establecen en función de los valores booleanos devueltos por el User método del objeto IsInRole(roleName) . El objeto de usuario hace referencia a la entidad de seguridad creada por RoleManagerModule ; por lo tanto, el IsInRole(roleName) método usa la API de roles para determinar si el visitante actual pertenece a roleName.

Note

Podríamos haber usado la clase de roles directamente, reemplazando la llamada a User.IsInRole(roleName) por una llamada al Roles.IsUserInRole(roleName) método. Decidí usar el método del objeto principal IsInRole(roleName) en este ejemplo porque es más eficaz que usar directamente la API de roles. Anteriormente en este tutorial, se configuró el administrador de roles para almacenar en caché los roles del usuario en una cookie. Estos datos de cookies almacenados en caché solo se usan cuando se llama al método de la entidad de seguridad IsInRole(roleName) ; las llamadas directas a la API de roles siempre implican un viaje al almacén de roles. Aunque los roles no estén almacenados en la memoria caché en una cookie, la llamada al método del objeto principal IsInRole(roleName) suele ser más eficaz porque cuando se llama por primera vez durante una solicitud, almacena en caché los resultados. Por otro lado, la API de roles no realiza ningún almacenamiento en caché. Dado que el RowCreated evento se desencadena una vez para cada fila de GridView, el uso de User.IsInRole(roleName) implica un solo viaje al almacén de roles, mientras que Roles.IsUserInRole(roleName) requiere n viajes, donde N es el número de cuentas de usuario que se muestran en la cuadrícula.

La propiedad del botón Editar Visible se establece en true si el usuario que visita esta página está en el rol administradores o supervisores; de lo contrario, se establece en false . La propiedad del botón eliminar Visible está establecida en true solo si el usuario tiene el rol administradores.

Pruebe esta página a través de un explorador. Si visita la página como visitante anónimo o como usuario que no es supervisor ni administrador, el CommandField está vacío. todavía existe, pero como una astilla fina sin los botones editar o eliminar.

Note

Es posible ocultar por completo el CommandField cuando un usuario que no es supervisor y no administrador visita la página. Dejo esto como un ejercicio para el lector.

Los botones editar y eliminar están ocultos para los usuarios que no son supervisores y no son administradores

Figura 13: los botones editar y eliminar están ocultos para los usuarios que no son supervisores y no son administradores (haga clic para ver la imagen a tamaño completo)

Si un usuario que pertenece al rol supervisores (pero no al rol administradores) visita, solo ve el botón Editar.

Cuando el botón Editar está disponible para los supervisores, el botón Eliminar está oculto.

Figura 14: mientras el botón Editar está disponible para los supervisores, el botón Eliminar está oculto (haga clic para ver la imagen de tamaño completo)

Y si un administrador visita, tiene acceso a los botones editar y eliminar.

Los botones editar y eliminar solo están disponibles para los administradores

Figura 15: los botones editar y eliminar solo están disponibles para los administradores (haga clic para ver la imagen a tamaño completo)

Paso 3: aplicar reglas de autorización de Role-Based a clases y métodos

En el paso 2, limitamos las capacidades de edición a los usuarios de los roles supervisores y administradores, y eliminamos las capacidades solo para los administradores. Esto se logra ocultando los elementos de la interfaz de usuario asociados a usuarios no autorizados mediante técnicas de programación. Estas medidas no garantizan que un usuario no autorizado no pueda realizar una acción privilegiada. Puede haber elementos de la interfaz de usuario que se agreguen más adelante o que se haya olvidado de ocultar a usuarios no autorizados. O bien, un pirata informático puede detectar otra manera de obtener la página ASP.NET para ejecutar el método deseado.

Una manera sencilla de asegurarse de que un usuario no autorizado no pueda acceder a una parte determinada de la funcionalidad es decorar esa clase o método con el PrincipalPermission atributo. Cuando el tiempo de ejecución de .NET usa una clase o ejecuta uno de sus métodos, comprueba para asegurarse de que el contexto de seguridad actual tiene permiso. El PrincipalPermission atributo proporciona un mecanismo a través del cual se pueden definir estas reglas.

Analizamos el uso del PrincipalPermission atributo de nuevo en el tutorial de autorización basada en el usuario. En concreto, vimos cómo decorar el controlador de SelectedIndexChanged eventos y de GridView RowDeleting para que solo los puedan ejecutar usuarios autenticados y Tito, respectivamente. El PrincipalPermission atributo funciona también con roles.

Vamos a mostrar cómo usar el PrincipalPermission atributo en los RowUpdating controladores de eventos y de GridView RowDeleting para prohibir la ejecución de usuarios no autorizados. Lo único que debemos hacer es agregar el atributo adecuado sobre cada definición de función:

[PrincipalPermission(SecurityAction.Demand, Role = "Administrators")]
[PrincipalPermission(SecurityAction.Demand, Role = "Supervisors")]
protected void UserGrid_RowUpdating(object sender, GridViewUpdateEventArgs e)
{
     ...
}

[PrincipalPermission(SecurityAction.Demand, Role = "Administrators")]
protected void UserGrid_RowDeleting(object sender, GridViewDeleteEventArgs e)
{
     ...
}

El atributo del RowUpdating controlador de eventos determina que solo los usuarios de los roles administradores o supervisores pueden ejecutar el controlador de eventos, donde como el atributo del RowDeleting controlador de eventos limita la ejecución a los usuarios del rol administradores.

Note

El PrincipalPermission atributo se representa como una clase en el System.Security.Permissions espacio de nombres. Asegúrese de agregar una using System.Security.Permissions instrucción en la parte superior del archivo de clase de código subyacente para importar este espacio de nombres.

Si, de algún modo, un usuario no administrador intenta ejecutar el RowDeleting controlador de eventos o si un usuario que no es supervisor o no es un administrador intenta ejecutar el RowUpdating controlador de eventos, el tiempo de ejecución de .net generará una SecurityException .

Si el contexto de seguridad no está autorizado para ejecutar el método, se produce una excepción SecurityException.

Figura 16: Si el contexto de seguridad no está autorizado para ejecutar el método, SecurityException se produce una excepción (haga clic para ver la imagen de tamaño completo)

Además de las páginas de ASP.NET, muchas aplicaciones también tienen una arquitectura que incluye varias capas, como las capas de lógica de negocios y de acceso a datos. Estas capas se implementan normalmente como bibliotecas de clases y ofrecen clases y métodos para realizar la lógica empresarial y la funcionalidad relacionada con los datos. El PrincipalPermission atributo es útil para aplicar reglas de autorización a estas capas también.

Para obtener más información sobre el uso del PrincipalPermission atributo para definir reglas de autorización en clases y métodos, consulte la entrada de blog de Scott Guthriepara agregar reglas de autorización PrincipalPermissionAttributes a las capas de negocio y de datos mediante .

Resumen

En este tutorial, hemos examinado cómo especificar reglas de autorización generales y granulares basadas en los roles del usuario. ASP. La característica de autorización de URL de la red permite a un desarrollador de páginas especificar las identidades a las que se les permite o deniega el acceso a las páginas. Como hemos visto en el tutorial de autorización basada en el usuario, las reglas de autorización de URL se pueden aplicar de forma individual para cada usuario. También se pueden aplicar roles por rol, como vimos en el paso 1 de este tutorial.

Las reglas de autorización de grano fino se pueden aplicar de forma declarativa o mediante programación. En el paso 2, analizamos el uso de la característica RoleGroups del control LoginView para representar una salida diferente en función de los roles del usuario visitante. También hemos analizado las maneras de determinar mediante programación si un usuario pertenece a un rol específico y cómo ajustar la funcionalidad de la página en consecuencia.

¡ Feliz programación!

Lecturas adicionales

Para obtener más información sobre los temas descritos en este tutorial, consulte los siguientes recursos:

Acerca del autor

Scott Mitchell, autor de varios libros de ASP/ASP. NET y fundador de 4GuysFromRolla.com, ha estado trabajando con las tecnologías Web de Microsoft desde 1998. Scott funciona como consultor, profesor y redactor independiente. Su último libro se enseña a ASP.NET 2,0 en 24 horas. Se puede acceder a Scott en mitchell@4guysfromrolla.com o a través de su blog en http://ScottOnWriting.NET .

Agradecimiento especial...

Muchos revisores útiles revisaron esta serie de tutoriales. Los revisores responsables de este tutorial incluyen Banerjee y Teresa Murphy. ¿Está interesado en revisar los próximos artículos de MSDN? Si es así, suéltelo una línea en mitchell@4GuysFromRolla.com