Autorización basada en roles (C#)
por Scott Mitchell
Nota:
Desde que se escribió este artículo, los proveedores de ASP.NET Membership han sido reemplazados por ASP.NET Identity. Se recomienda encarecidamente actualizar las aplicaciones para usar la plataformaASP.NET Identity en lugar de los proveedores de pertenencia destacados en el momento en que se escribió este artículo. ASP.NET Identity tiene una serie de ventajas sobre el sistema de pertenencia ASP.NET, incluido :
- Mejor rendimiento
- Extensibilidad y capacidad de prueba mejoradas
- Compatibilidad con OAuth, OpenID Connect y autenticación en dos fases
- Compatibilidad con identidades basadas en notificaciones
- Mejor interoperabilidad con ASP.Net Core
Descargar código o descargar PDF
Este tutorial comienza con un vistazo a cómo el marco roles asocia los roles de un usuario con su contexto de seguridad. A continuación, examina cómo aplicar reglas de autorización de direcciones URL basadas en roles. Después, veremos el uso de medios declarativos y mediante programación para modificar los datos mostrados y la funcionalidad que ofrece una página de ASP.NET.
Introducción
En el Tutorial autorización basada en usuarios hemos visto cómo usar la autorización de dirección URL para especificar qué usuarios podrían visitar un conjunto determinado de páginas. Con un poco de marcado en Web.config
, podríamos indicar a ASP.NET permitir que solo los usuarios autenticados visiten una página. O podríamos dictar que solo se permitían los usuarios Tito y Bob, o indicar que todos los usuarios autenticados excepto Sam estaban permitidos.
Además de la autorización de direcciones URL, también hemos examinado técnicas declarativas y programáticas para controlar los datos mostrados y la funcionalidad que ofrece una página basada en la visita del usuario. En concreto, hemos creado una página que enumera el contenido del directorio actual. Cualquiera podría visitar esta página, pero solo los usuarios autenticados podían ver el contenido de los archivos y solo Tito podía eliminar los archivos.
La aplicación de reglas de autorización por usuario puede crecer en una pesadilla de contabilidad. Un enfoque más fácil de mantener es usar la autorización basada en roles. La buena noticia es que las herramientas a nuestra disposición para aplicar reglas de autorización funcionan igualmente bien con roles como lo hacen para las cuentas de usuario. Las reglas de autorización de direcciones 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.
Este tutorial comienza con un vistazo a cómo el marco roles asocia los roles de un usuario con su contexto de seguridad. A continuación, examina cómo aplicar reglas de autorización de direcciones URL basadas en roles. Después, veremos el uso de medios declarativos y mediante programación para modificar los datos mostrados y la funcionalidad que ofrece una página de ASP.NET. Comencemos.
Descripción de cómo están asociados los roles con el contexto de seguridad de un usuario
Cada vez que una solicitud entra en la canalización de ASP.NET está asociada a un contexto de seguridad, lo que incluye información que identifica al solicitante. Al usar la autenticación de formularios, se usa un vale de autenticación como token de identidad. Como se explicó en el tutorial Introducción a la autenticación de formularios, FormsAuthenticationModule
es responsable de determinar la identidad del solicitante, que hace durante el AuthenticateRequest
evento.
Si se encuentra un vale de autenticación no expirado válido, el FormsAuthenticationModule
descodifica para determinar la identidad del solicitante. Crea un nuevo objeto GenericPrincipal
y lo asigna al objeto HttpContext.User
. 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 propiedad Identity
y un método IsInRole(roleName)
. FormsAuthenticationModule
Sin embargo, el objeto que crea no está interesado en registrar la información del rol y el objeto GenericPrincipal
que crea no especifica ningún rol.
Si el marco roles está habilitado, el módulo HTTP realiza los RoleManagerModule
pasos después de FormsAuthenticationModule
e identifica los roles del usuario autenticado durante el PostAuthenticateRequest
evento, que se desencadena después del evento AuthenticateRequest
. Si la solicitud procede de un usuario autenticado, RoleManagerModule
sobrescribe el objeto creado GenericPrincipal
por y lo reemplaza por FormsAuthenticationModule
un RolePrincipal
objeto. La RolePrincipal
clase usa la API roles para determinar a qué roles pertenece el usuario.
En la figura 1 se muestra el flujo de trabajo de canalización de ASP.NET al usar la autenticación de formularios y el marco roles. El FormsAuthenticationModule
se ejecuta primero, identifica al usuario a través de su vale de autenticación y crea un nuevo objeto GenericPrincipal
. A continuación, el paso RoleManagerModule
y sobrescribe el objeto GenericPrincipal
con un objeto RolePrincipal
.
Si un usuario anónimo visita el sitio, ni el FormsAuthenticationModule
ni el RoleManagerModule
crea un objeto principal.
Figura 1: Los eventos de canalización de ASP.NET para un usuario autenticado al usar la autenticación de formularios y el marco de roles (haga clic para ver la imagen de tamaño completo)
Almacenamiento en caché de información de rol en una cookie
El método RolePrincipal
del objeto IsInRole(roleName)
llama a Roles.GetRolesForUser
para obtener los roles del usuario con el fin de determinar si el usuario es miembro de roleName. Cuando se usa SqlRoleProvider
, se produce una consulta en la base de datos del almacén de roles. Al usar reglas de autorización de direcciones URL basadas en rol, se llamará a los métodos RolePrincipal
y IsInRole
de cada solicitud a una página protegida por las reglas de autorización de direcciones URL basadas en roles. En lugar de tener que buscar la información de rol en la base de datos en cada solicitud, el marco roles incluye una opción para almacenar en caché los roles del usuario en una cookie.
Si el marco roles está configurado para almacenar en caché los roles del usuario en una cookie, RoleManagerModule
crea la cookie durante el EndRequest
evento de la canalización de ASP.NET. Esta cookie se usa en las solicitudes posteriores de PostAuthenticateRequest
, que es cuando se crea el objeto RolePrincipal
. Si la cookie es válida y no ha expirado, los datos de la cookie se analizan y se usan para rellenar los roles del usuario, lo que evita RolePrincipal
tener que realizar una llamada a la clase Roles
para determinar los roles del usuario. En la figura 2 se muestra este flujo de trabajo.
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 manera predeterminada, el mecanismo de cookies de caché de roles está deshabilitado. Se puede habilitar mediante el marcado de configuración <roleManager>
en Web.config
. Hemos analizado el uso del <roleManager>
elemento para especificar proveedores de roles en el tutorial Creación y administración de roles, por lo que ya debería tener este elemento en el archivo Web.config
de la aplicación. La configuración de cookies de caché de roles se especifica como atributos del elemento <roleManager>
, y son resumidas en la tabla 1.
Nota:
Las opciones de configuración enumeradas en la tabla 1 especifican las propiedades de la cookie de caché de roles resultante. Para obtener más información sobre las cookies, cómo funcionan y sus diversas propiedades, lea este tutorial sobre cookies.
Propiedad | Descripción |
---|---|
cacheRolesInCookie |
Valor booleano que indica si se usa el almacenamiento en caché de cookies. Tiene como valor predeterminado false . |
cookieName |
Nombre de la cookie de caché de roles. El valor predeterminado es ". ASPXROLES". |
cookiePath |
Ruta de acceso de la cookie de nombre de roles. El atributo ruta permite a un desarrollador limitar el ámbito de una cookie a una jerarquía de directorios determinada. El valor predeterminado es "/", que informa al explorador para enviar la cookie de vale de autenticación a cualquier solicitud realizada al dominio. |
cookieProtection |
Indica qué técnicas se usan para proteger la cookie de caché de roles. Los valores permitidos son: All (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 pertinente cuando createPersistentCookie se establece en true . |
cookieTimeout |
Especifica el tiempo, en minutos, después del cual expira la cookie de vale de autenticación. El valor predeterminado es 30 . Este valor solo es pertinente cuando createPersistentCookie se establece en true . |
createPersistentCookie |
Valor booleano que especifica si la cookie de caché de roles es una cookie de sesión o una cookie persistente. Si false (valor predeterminado), se usa una cookie de sesión, que se elimina cuando se cierra el explorador. Si true , se usa una cookie persistente; expira el número de minutos cookieTimeout después de que se haya creado o después de la visita anterior, en función del valor de cookieSlidingExpiration . |
domain |
Especifica el valor de dominio de la cookie. El valor predeterminado es una cadena vacía, lo que hace que el explorador use el dominio desde el que se emitió (por ejemplowww.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 atributo domain , estando establecida 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. RoleManagerModule no crea una cookie para los usuarios que pertenecen a más de maxCachedResults roles. Por lo tanto, el método RolePrincipal del objeto IsInRole usará la clase Roles para determinar los roles del usuario. El motivo maxCachedResults es que muchos agentes de usuario no permiten cookies de más de 4096 bytes. Por lo tanto, este límite está diseñado para reducir la probabilidad de superar esta limitación de tamaño. Si tiene nombres de rol extremadamente largos, puede considerar la posibilidad de especificar un valor más pequeño maxCachedResults ; de lo contrario, si tiene nombres de rol extremadamente cortos, probablemente puede aumentar este valor. |
Tabla 1: Opciones de configuración de cookies de caché de roles
Vamos a configurar nuestra aplicación para que use cookies de caché de roles no persistentes. Para ello, actualice el elemento <roleManager>
en Web.config
para incluir los siguientes atributos relacionados con cookies:
<roleManager enabled="true"
defaultProvider="SecurityTutorialsSqlRoleProvider"
cacheRolesInCookie="true"
createPersistentCookie="false"
cookieProtection="All">
<providers>
...
</providers>
</roleManager>
He actualizado el elemento <roleManager>
agregando tres atributos: cacheRolesInCookie
, createPersistentCookie
, y cookieProtection
. Al establecer cacheRolesInCookie
en true
, el RoleManagerModule
almacenará automáticamente en caché los roles del usuario en una cookie en lugar de tener que buscar la información de rol del usuario en cada solicitud. He establecido explícitamente los atributos createPersistentCookie
y cookieProtection
en false
y All
, respectivamente, Técnicamente, no necesito especificar valores para estos atributos, ya que acabo de asignarlos a sus valores predeterminados, pero los puse aquí para aclarar explícitamente que no estoy usando cookies persistentes y que la cookie está cifrada y validada.
Eso es todo. Por lo tanto, el marco roles almacenará en caché los roles de los usuarios en las cookies. Si el explorador del usuario no admite cookies, o si sus cookies se eliminan o pierden, de alguna manera, no es importante, el objeto RolePrincipal
simplemente usará la clase Roles
en caso de que ninguna cookie (o una no válida o expirada) esté disponible.
Nota:
El grupo Patrones y prácticas de Microsoft desaconseja el uso de cookies de caché de roles persistentes. Dado que la posesión de la cookie de caché de roles es suficiente para demostrar la pertenencia a roles, si un hacker puede obtener acceso 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 para ASP.NET 2.0.
Paso 1: Definir reglas de autorización de direcciones URL basadas en roles
Como se describe en el tutorial Autorización basada en usuarios, la autorización de direcciones URL ofrece un medio para restringir el acceso a un conjunto de páginas por usuario o rol por rol. Las reglas de autorización de dirección URL se describen mediante Web.config
el elemento <authorization>
con elementos secundarios <allow>
y <deny>
. Además de las reglas de autorización relacionadas con el usuario descritas en los tutoriales anteriores, cada elemento secundario<allow>
y <deny>
también puede incluir:
- Un rol determinado
- Una lista delimitada por comas de roles
Por ejemplo, las reglas de autorización de direcciones URL conceden acceso a esos usuarios en los roles Administradores y Supervisores, pero deniegan el acceso a todos los demás:
<authorization>
<allow roles="Administrators, Supervisors" />
<deny users="*" />
</authorization>
El elemento <allow>
del marcado anterior indica que se permiten los roles Administradores y Supervisores; el elemento <deny>
indica a todos los usuarios que se deniegan.
Vamos a configurar nuestra aplicación para que las páginasManageRoles.aspx
, UsersAndRoles.aspx
, y CreateUserWizardWithRoles.aspx
solo sean accesibles para los usuarios del rol Administradores, mientras que la página RoleBasedAuthorization.aspx
sigue siendo accesible para todos los visitantes.
Para ello, empiece agregando un archivo Web.config
a la carpeta Roles
.
Figura 3: Agregar un archivo Web.config
al directorioRoles
(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 elemento <authorization>
de la sección <system.web>
indica que solo los usuarios del rol Administradores pueden tener acceso a los recursos de ASP.NET en el directorio Roles
. El elemento <location>
define un conjunto alternativo de reglas de autorización de direcciones URL para la página RoleBasedAuthorization.aspx
, lo que permite a todos los usuarios visitar la página.
Después de guardar los cambios en Web.config
, registrarse como usuario que no esté en el rol Administradores y a continuación, intente visitar una de las páginas protegidas. UrlAuthorizationModule
Detectará que no tiene permiso para visitar el recurso solicitado; por lo tanto, FormsAuthenticationModule
le redirigirá a la página de inicio de sesión. A continuación, la página de registro le redirigirá a la página (consulte la UnauthorizedAccess.aspx
figura 4). Esta redirección final desde la página de inicio de sesión para UnauthorizedAccess.aspx
que se produzca debido al código que hemos agregado a la página de inicio de sesión en el paso 2 del tutorial de autorización basada en usuario. En concreto, la página de inicio de sesión redirige automáticamente a cualquier usuario autenticado a UnauthorizedAccess.aspx
si la cadena de consultas contiene un parámetro ReturnUrl
, 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 autorizada para ver.
Figura 4: Solo los usuarios del rol Administradores pueden ver las páginas protegidas (Haga clic para ver la imagen de tamaño completo)
Cerrar sesión y a continuación, regístrese como usuario que esté en el rol Administradores. Ahora debería poder ver las tres páginas protegidas.
Figura 5: Tito puede visitar la página UsersAndRoles.aspx
Porque está en el rol administradores (Haga clic para ver la imagen de tamaño completo)
Nota:
Al especificar reglas de autorización de direcciones URL (para roles o usuarios), es importante tener en cuenta que las reglas se analizan de una en una, desde la parte superior hacia abajo. Tan pronto como se encuentre una coincidencia, se concede o deniega el acceso al usuario, en función de si la coincidencia se encontró en un elemento <allow>
o <deny>
. Si no se encuentra ninguna coincidencia, se concede acceso al usuario. Por lo tanto, si desea restringir el acceso a una o varias cuentas de usuario, es imperativo usar un elemento <deny>
como último elemento en la configuración de autorización de dirección URL. Si las reglas de autorización de direcciones URL no incluyen un elemento<deny>
, se concederá acceso a todos los usuarios. Para un análisis más detallado de cómo se analizan las reglas de autorización de URL, consulte la sección "Cómo UrlAuthorizationModule
utiliza las reglas de autorización para conceder o denegar el acceso" del tutorial Autorización basada en usuarios.
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 gruesas que indiquen qué identidades se permiten y cuáles se deniegan de ver una página determinada (o todas las páginas de una carpeta y sus subcarpetas). Sin embargo, en determinados casos es posible que deseemos permitir que todos los usuarios visiten una página, pero limite la funcionalidad de la página en función de los roles del usuario que visita. Esto puede implicar mostrar u ocultar datos en función del rol del usuario o ofrecer funcionalidad adicional a los usuarios que pertenecen a un rol determinado.
Estas reglas de autorización específicas basadas en roles se pueden implementar mediante declaración o mediante programación (o mediante alguna combinación de los dos). En la sección siguiente veremos cómo implementar la autorización detallada declarativa mediante el control LoginView. Después, exploraremos técnicas de programación. Sin embargo, para poder examinar la aplicación de reglas de autorización específicas, primero es necesario crear una página cuya funcionalidad depende del rol del usuario que lo visite.
Vamos a crear una página que muestre todas las cuentas de usuario del sistema en GridView. GridView incluirá el nombre de usuario, la dirección de correo electrónico de cada usuario, la fecha de último inicio de sesión y los comentarios sobre el usuario. Además de mostrar la información de cada usuario, GridView incluirá funcionalidades de edición y eliminación. Inicialmente, crearemos esta página con la funcionalidad de edición y eliminación disponible para todos los usuarios. En las secciones "Usar el control LoginView" y "Funcionalidad de limitación mediante programación", veremos cómo habilitar o deshabilitar estas características en función del rol del usuario que visita.
Nota:
La página de ASP.NET que estamos a punto de compilar usa un control GridView para mostrar las cuentas de usuario. Dado que esta serie de tutoriales se centra en la autenticación, autorización, cuentas de usuario y roles de formularios, no quiero dedicar demasiado tiempo a analizar los trabajos internos 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 de por qué se realizaron determinadas opciones o qué efecto tienen las propiedades concretas en la salida representada. Para obtener un examen exhaustivo del control GridView, consulte mi serie de tutoriales Trabajar con datos en ASP.NET 2.0.
Para empezar, abra la página RoleBasedAuthorization.aspx
en la carpeta Roles
. Arrastre un Control GridView desde la página hasta el Diseñador y establezca su ID
en UserGrid
. En un momento escribiremos código que llama al método Membership.GetAllUsers
y enlaza el objeto resultante MembershipUserCollection
al GridView. MembershipUserCollection
contiene un objeto MembershipUser
para cada cuenta de usuario del sistema; los objetos MembershipUser
tienen propiedades como UserName
, Email
, LastLoginDate
, etc.
Antes de escribir el código que enlaza las cuentas de usuario a la cuadrícula, primero definiremos 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 campos automáticamente" en la esquina inferior izquierda. Puesto que queremos que GridView incluya funcionalidades de edición y eliminación, agregue un CommandField y establezca sus propiedades ShowEditButton
y ShowDeleteButton
en True. A continuación, agregue cuatro campos para mostrar las propiedades UserName
, Email
, LastLoginDate
, y Comment
. Use un BoundField para las dos propiedades de solo lectura (UserName
y LastLoginDate
) y TemplateFields para los dos campos editables (Email
y Comment
).
Haga que el primer BoundField muestre la propiedadUserName
; establezca sus propiedades HeaderText
y DataField
en "UserName". Este campo no se podrá editar, por lo que establezca su propiedad ReadOnly
en True. Configure BoundField LastLoginDate
estableciendo su HeaderText
a "Último inicio de sesión" y su DataField
a "LastLoginDate". Vamos a dar formato a la salida de este BoundField para que solo se muestre la fecha (en lugar de la fecha y hora). Para ello, establezca la propiedad de BoundField HtmlEncode
en False y su propiedad DataFormatString
en "{0:d}". Establezca también la propiedad ReadOnly
en True.
Establezca las propiedades HeaderText
de los dos TemplateFields en "Email" y "Comentario".
Figura 6: Los campos de GridView se pueden configurar a través del cuadro de diálogo Campos (haga clic para ver la imagen de tamaño completo)
Ahora es necesario definir ItemTemplate
y EditItemTemplate
para los TemplateFields "Email" y "Comentario". Añada un control Label Web a cada uno de los ItemTemplate
y enlace sus propiedades Text
a las propiedades Email
y Comment
, respectivamente.
Para TemplateField de "Correo electrónico", agregue un TextBox denominado Email
a su EditItemTemplate
y enlace su propiedad Text
a la propiedad Email
mediante el enlace de datos bidireccional. Agregue RequiredFieldValidator y RegularExpressionValidator a EditItemTemplate
para asegurarse de que un visitante que edite la propiedad Email haya escrito una dirección de correo electrónico válida. Para TemplateField de "Comentario", agregue un TextBox de varias líneas denominado Comment
a su EditItemTemplate
. Establezca las propiedades Columns
y Rows
de TextBox en 40 y 4, respectivamente, y luego enlace su propiedad Text
a la propiedad Comment
mediante el enlace de datos bidireccional.
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 saber el valor de propiedad UserName
del usuario. Establezca la propiedad DataKeyNames
de GridView en "UserName" para que esta información esté disponible a través de la colección DataKeys
de GridView.
Por último, agregue un control ValidationSummary a la página y establezca su propiedad ShowMessageBox
en True y su propiedad ShowSummary
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" />
Ya hemos completado el marcado declarativo de esta página. Nuestra siguiente tarea consiste en enlazar el conjunto de cuentas de usuario a GridView. Agregue un método denominado BindUserGrid
a la clase de código subyacente de la página RoleBasedAuthorization.aspx
que enlaza el MembershipUserCollection
devuelto por Membership.GetAllUsers
al GridView UserGrid
. Llame a este método desde el controlador de eventos Page_Load
en la primera visita de 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 descripción de GridView sobre cada cuenta de usuario del sistema.
Figura 7: El GridView UserGrid
enumera información sobre cada usuario del sistema (Haga clic para ver la imagen de tamaño completo)
Nota:
El GridView UserGrid
enumera todos los usuarios de 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 es configurar GridView para habilitar la paginación. El método Membership.GetAllUsers
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 usar para paginar de forma más eficaz a través de los usuarios, ya que devuelve solo el subconjunto preciso de cuentas de usuario en lugar todos ellos. Si tiene miles de cuentas de usuario, es posible que desee considerar una interfaz basada en filtros, una que solo muestre a los usuarios cuyo UserName comienza con un carácter seleccionado, por ejemplo. Membership.FindUsersByName method
es ideal para compilar una interfaz de usuario basada en filtros. Veremos la creación de 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. El GridView UserGrid
, sin embargo, tiene sus datos enlazados mediante programación; por lo tanto, debemos escribir código para realizar estas dos tareas. En particular, necesitaremos crear controladores de eventos para los eventos RowEditing
, RowCancelingEdit
, RowUpdating
y RowDeleting
del GridView, que se disparan cuando un visitante hace clic en los botones Editar, Cancelar, Actualizar o Eliminar del GridView.
Empiece por crear los controladores de eventos para los eventosRowEditing
, RowCancelingEdit
, y RowUpdating
de GridView y 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 controladores de eventos RowEditing
y RowCancelingEdit
simplemente establecen la propiedad de GridView EditIndex
y a continuación, vuelven a enlazar la lista de cuentas de usuario a la cuadrícula. Las cosas interesantes se producen en el controlador de eventos RowUpdating
. Este controlador de eventos comienza asegurándose de que los datos son válidos y a continuación, toma el valor UserName
de la cuenta de usuario editada de la colección DataKeys
. Los TextBoxes Email
y Comment
de los dos TemplateFields EditItemTemplate
se hacen referencia mediante programación. Sus propiedades Text
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, primero necesitamos obtener la información del usuario, que hacemos a través de una llamada a Membership.GetUser(userName)
. El objeto devuelto MembershipUser
y sus propiedades Email
y Comment
se actualizan con los valores introducidos en los dos TextBoxes de la interfaz de edición. Por último, estas modificaciones se guardan con una llamada a Membership.UpdateUser
. El controlador de eventos RowUpdating
finaliza revirtiendo GridView a su interfaz de edición previa.
A continuación, cree el controlador de eventos RowDeleting
y 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 tomando el valor UserName
de la colección DataKeys
de GridView; este valor UserName
se pasa al DeleteUser
método de la clase Membership. El método DeleteUser
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 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 se llama al método BindUserGrid
.
Nota:
El botón Eliminar no requiere ningún tipo de confirmación del usuario antes de eliminar la cuenta de usuario. Le recomendamos que agregue algún tipo de confirmación de usuario para reducir la posibilidad de que se elimine accidentalmente una cuenta. Una de las formas más fáciles de confirmar una acción es a través de un cuadro de diálogo confirmar del lado cliente. Para obtener más información sobre esta técnica, vea Agregar confirmación del lado cliente 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. Puesto que la página RoleBasedAuthorization.aspx
es accesible para todos los usuarios, cualquier usuario, incluso 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 pueden eliminar una cuenta de usuario.
La sección "Usar el control LoginView" 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, mostraremos instrucciones sobre cómo editar y eliminar usuarios. Si un usuario del rol Supervisores llega a esta página, mostraremos instrucciones sobre la edición de usuarios. Y si el visitante es anónimo o no está en el rol Supervisores o Administradores, mostraremos un mensaje que explica que no puede editar o eliminar información de la cuenta de usuario. En la sección "Funcionalidad de limitación mediante programación", escribiremos código que muestre u oculte mediante programación los botones Editar y Eliminar en función del rol del usuario.
Uso del 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 instrucciones diferentes en función del rol del usuario que visita.
Empiece agregando un elemento LoginView encima de GridView UserGrid
. Como hemos explicado anteriormente, el control LoginView tiene dos plantillas integradas: AnonymousTemplate
y LoggedInTemplate
. Escriba un breve mensaje 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 de rol. Cada RoleGroup contiene una sola propiedad, Roles
, que especifica a qué roles se aplica RoleGroup. La propiedad Roles
se puede establecer en un solo rol (como "Administradores") o en una lista delimitada por comas de roles (como "Administradores, Supervisores").
Para administrar RoleGroups, haga clic en el vínculo "Editar grupos de roles" de la etiqueta inteligente del control para abrir el Editor de recopilación RoleGroup. Agregue dos nuevos RoleGroups. Establezca la primera propiedad Roles
de RoleGroup en "Administradores" y la segunda en "Supervisores".
Figura 8: Administrar las plantillas específicas de rol de LoginView mediante el Editor de colecciones RoleGroup (haga clic para ver la imagennde tamaño completo)
Haga clic en Aceptar para cerrar el Editor de colección RoleGroup; esto actualiza el marcado declarativo de LoginView para incluir una sección <RoleGroups>
con un elemento secundario <asp:RoleGroup>
para cada RoleGroup definido en el Editor de colección RoleGroup. Además, en la lista desplegable "Vistas" de la etiqueta inteligente de LoginView, que inicialmente se listan solo AnonymousTemplate
y LoggedInTemplate
, ahora también se incluyen los RoleGroups agregados.
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 se muestran 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' 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 después la visite a través de un explorador. En primer lugar, visite la página como usuario anónimo. Debería mostrar el mensaje "No se ha registrado en el sistema. Por lo tanto, no puede editar ni eliminar ninguna 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 ninguna información de usuario".
A continuación, inicie sesión como 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 se registra como usuario en el rol Administradores, debería ver el mensaje específico del rol Administradores (vea la figura 10).
Figura 9: Bruce se muestra el mensaje específico del rol supervisores (haga clic para ver la imagen de tamaño completo)
Figura 10: Tito se muestra el mensaje específico del rol administradores (haga clic para ver la imagen de tamaño completo)
Como se muestran las capturas de pantalla de las figuras 9 y 10, LoginView solo representa una plantilla, incluso si se aplican varias plantillas. Bruce y Tito son usuarios que han iniciado sesión, pero LoginView representa solo el RoleGroup coincidente y no el LoggedInTemplate
. Además, Tito pertenece a los roles Administradores y Supervisores, pero el control LoginView representa la plantilla específica del rol Administradores en lugar de supervisores.
En la figura 11 se muestra el flujo de trabajo usado por el control LoginView para determinar qué plantilla se va a representar. Tenga en cuenta que si hay más de un RoleGroup especificado, la plantilla LoginView representa el primer RoleGroup que coincide. En otras palabras, si hubiéramos colocado el RoleGroup supervisores como el primer RoleGroup y los Administradores como el segundo, entonces cuando Tito visitó esta página vería el mensaje supervisores.
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)
Limitación de la funcionalidad mediante programación
Aunque el control LoginView muestra instrucciones diferentes en función del rol del usuario que visita la página, los botones Editar y Cancelar permanecen visibles para todos. Es necesario ocultar mediante programación los botones Editar y Eliminar para visitantes anónimos y usuarios que no están en el rol Supervisores ni Administradores. Es necesario ocultar el botón Eliminar para todos los usuarios que no son administradores. Para lograr esto, escribiremos un poco de código que haga referencia mediante programación a edit y delete LinkButtons de CommandField y establezca sus propiedades Visible
a false
, si es necesario.
La manera más sencilla de hacer referencia 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 un TemplateField". Esto convierte CommandField en un TemplateField con ItemTemplate
y EditItemTemplate
. ItemTemplate
contiene los botones Editar y Eliminar, mientras que EditItemTemplate
alberga los LinkButtons Actualizar y Cancelar.
Figura 12: Convertir CommandField en un TemplateField (haga clic para ver la imagen de tamaño completo)
Actualice los LinkButtons Editar y Eliminar en ItemTemplate
, estableciendo sus propiedades ID
a los valores 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 los datos se enlazan a GridView, GridView enumera los registros de su propiedad DataSource
y genera un objeto correspondiente GridViewRow
. A medida que se crea cada objeto GridViewRow
, se desencadena el evento RowCreated
. Para ocultar los botones Editar y Eliminar para usuarios no autorizados, es necesario crear un controlador de eventos para este evento y hacer referencia mediante programación a los botones Editar y Eliminar LinkButtons, estableciendo sus propiedades Visible
en consecuencia.
Cree un controlador de eventos para el evento RowCreated
y agregue el siguiente código:
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 evento RowCreated
se activa para todas las filas de GridView, incluido el encabezado, el pie de página, la interfaz del buscapersonas, etc. Solo queremos hacer referencia mediante programación a los botones Editar y Eliminar LinkButtons si estamos tratando con una fila de datos no en modo de edición (ya que la fila en modo de edición tiene botones Actualizar y Cancelar en lugar de Editar y Eliminar). Esta comprobación se controla mediante el extracto if
.
Si estamos tratando con una fila de datos que no está en modo de edición, se hace referencia a los elementos Edit y Delete LinkButtons y sus propiedades Visible
se establecen en función de los valores booleanos devueltos por el métodoUser
del objetoIsInRole(roleName)
. El objeto User hace referencia a la entidad de seguridad creada por RoleManagerModule
; en consecuencia, el método IsInRole(roleName)
usa la API de roles para determinar si el visitante actual pertenece a roleName.
Nota:
Podríamos haber usado la clase Roles directamente, reemplazando la llamada a User.IsInRole(roleName)
por una llamada al método Roles.IsUserInRole(roleName)
. Decidí usar el método IsInRole(roleName)
del objeto de entidad de seguridad en este ejemplo porque es más eficaz que usar directamente la API de roles. Anteriormente en este tutorial se ha configurado 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 IsInRole(roleName)
de la entidad de seguridad; las llamadas directas a la API de roles siempre implican un viaje al almacén de roles. Incluso si los roles no se almacenan en caché en una cookie, llamar al método IsInRole(roleName)
del objeto de entidad de seguridad suele ser más eficaz porque cuando se llama por primera vez durante una solicitud almacena en caché los resultados. La API roles, por otro lado, no realiza ningún almacenamiento en caché. Dado que el evento RowCreated
se desencadena una vez por cada fila de GridView, el uso de User.IsInRole(roleName)
implica solo un viaje al almacén de roles, mientras que Roles.IsUserInRole(roleName)
requiere N viajes, donde N es el número de cuentas de usuario mostradas en la cuadrícula.
La propiedad Visible
del botón Editar 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 Visible
del botón Eliminar se establece en true
solo si el usuario está en 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, CommandField está vacío; sigue existiendo, pero como una fina astilla sin los botones Editar o Eliminar.
Nota:
Es posible ocultar CommandField por completo cuando un no supervisor y no administrador está visitando la página. Lo dejo como ejercicio para el lector.
Figura 13: Los botones Editar y Eliminar están ocultos para no supervisores y no administradores (Haga clic para ver la imagen de tamaño completo)
Si un usuario que pertenece al rol Supervisores (pero no al rol Administradores), ve solo el botón Editar.
Figura 14: Mientras el botón Editar está disponible para 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.
Figura 15: Los botones Editar y Eliminar solo están disponibles para los administradores (Haga clic para ver la imagen de tamaño completo)
Paso 3: Aplicar reglas de autorización basadas en roles a clases y métodos
En el paso 2, limitamos las funcionalidades de edición a los usuarios de los roles supervisores y administradores y eliminamos las funcionalidades solo a los administradores. Esto se ha logrado ocultando los elementos de la interfaz de usuario asociados para los usuarios no autorizados a través de técnicas de programación. Estas medidas no garantizan que un usuario no autorizado no pueda realizar una acción con privilegios. Puede haber elementos de interfaz de usuario que se agreguen más adelante o que olvidemos ocultar para usuarios no autorizados. O es posible que un hacker descubra alguna otra manera de obtener la página de ASP.NET para ejecutar el método deseado.
Una manera fácil de asegurarse de que un usuario no autorizado pueda tener acceso a una determinada funcionalidad es decorar esa clase o método con el atributoPrincipalPermission
. Cuando el entorno de ejecución de .NET usa una clase o ejecuta uno de sus métodos, comprueba que el contexto de seguridad actual tiene permiso. El atributo PrincipalPermission
proporciona un mecanismo a través del cual podemos definir estas reglas.
Hemos examinado el uso del atributo PrincipalPermission
de nuevo en el tutorial autorización basada en el usuario. Específicamente, vimos cómo decorar el GridView's SelectedIndexChanged
y el controlador de eventos RowDeleting
para que solo pudieran ser ejecutados por usuarios autenticados y Tito, respectivamente. El atributo PrincipalPermission
funciona igual que con roles.
Vamos a demostrar el uso del atributo PrincipalPermission
en GridView's RowUpdating
y los RowDeleting
controladores de eventos para prohibir la ejecución de usuarios no autorizados. Todo lo que necesitamos hacer es agregar el atributo adecuado en 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 para el controlador de eventos RowUpdating
determina que solo los usuarios de los roles Administradores o Supervisores pueden ejecutar el controlador de eventos, donde como atributo en el controlador de eventos RowDeleting
limita la ejecución a los usuarios del rol Administradores.
Nota:
El atributo PrincipalPermission
se representa como una clase en el espacio de nombres System.Security.Permissions
. Asegúrese de agregar un extracto using System.Security.Permissions
en la parte superior del archivo de clase de código subyacente para importar este espacio de nombres.
Si, de alguna manera, un no administrador intenta ejecutar el controlador de eventos RowDeleting
o si un no supervisor o no administrador intenta ejecutar el controlador de eventos RowUpdating
, el entorno de ejecución de .NET generará un SecurityException
.
Figura 16: Si el contexto de seguridad no está autorizado para ejecutar el método, se produce una excepción SecurityException
(haga clic para ver la imagen de tamaño completo)
Además de las páginas ASP.NET, muchas aplicaciones también tienen una arquitectura que incluye varias capas, como la lógica de negocios y las capas de acceso a datos. Estas capas normalmente se implementan como bibliotecas de clases y ofrecen clases y métodos para realizar la funcionalidad relacionada con la lógica de negocios y los datos. El atributo PrincipalPermission
también es útil para aplicar reglas de autorización a estas capas.
Para obtener más información sobre el uso del atributo PrincipalPermission
para definir reglas de autorización en clases y métodos, consulte la entrada de blog de Scott Guthrie sobre Cómo agregar reglas de autorización a capas de datos y empresariales mediante PrincipalPermissionAttributes
.
Resumen
En este tutorial hemos visto cómo especificar reglas de autorización generales y específicas basadas en los roles del usuario. ASP. La característica de autorización de direcciones URL de NET permite al desarrollador de páginas especificar qué identidades se permiten o deniegan 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 direcciones URL se pueden aplicar por usuario. También se pueden aplicar por rol, como vimos en el paso 1 de este tutorial.
Las reglas de autorización específicas se pueden aplicar mediante declaración o mediante programación. En el paso 2, hemos visto 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 examinado formas 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 tratados en este tutorial, consulte los siguientes recursos:
- Adición de reglas de autorización a las capas de negocio y de datos mediante
PrincipalPermissionAttributes
- Examen de la pertenencia, los roles y el perfil de ASP.NET 2.0: Trabajar con roles
- Lista de preguntas de seguridad para ASP.NET 2.0
- Documentación técnica del elemento
<roleManager>
Acerca del autor
Scott Mitchell, autor de varios libros de ASP/ASP.NET y fundador de 4GuysFromRolla.com, ha estado trabajando con tecnologías web de Microsoft desde 1998. Scott trabaja como consultor independiente, entrenador y escritor. Su último libro es Sams Teach Yourself ASP.NET 2.0 in 24 Hours. Se puede acceder a Scott en mitchell@4guysfromrolla.com o a través de su blog en http://ScottOnWriting.NET.
Gracias especial a…
Esta serie de tutoriales fue revisada por muchos revisores de gran ayuda. Los revisores principales de este tutorial incluyen Suchi Banerjee y Teresa Murphy. ¿Le interesa revisar mis próximos artículos de MSDN? Si es así, escríbame a mitchell@4GuysFromRolla.com