Almacenar información de usuario adicional (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, incluidas las siguientes:

  • 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

En este tutorial responderemos a esta pregunta mediante la creación de una aplicación de libro de visitas muy rudimentaria. Al hacerlo, veremos diferentes opciones para modelar información de usuario en una base de datos y, a continuación, veremos cómo asociar estos datos a las cuentas de usuario creadas por el marco de pertenencia.

Introducción

El marco de pertenencia de ASP.NET ofrece una interfaz flexible para administrar usuarios. La API de pertenencia incluye métodos para validar credenciales, recuperar información sobre el usuario que ha iniciado sesión actualmente, crear una nueva cuenta de usuario y eliminar una cuenta de usuario, entre otros procesos. Cada cuenta de usuario del marco de pertenencia contiene solo las propiedades necesarias para validar las credenciales y realizar tareas esenciales relacionadas con la cuenta de usuario. Esto se evidencia mediante los métodos y propiedades de la clase MembershipUser, que modela una cuenta de usuario en el marco de pertenencia. Esta clase tiene propiedades como UserName, Email y IsLockedOut, y métodos como GetPassword y UnlockUser.

A menudo, las aplicaciones necesitan almacenar información de usuario adicional no incluida en el marco de pertenencia. Por ejemplo, un minorista en línea podría necesitar dejar que cada usuario almacene sus direcciones de envío y facturación, información de pago, preferencias de entrega y número de teléfono de contacto. Además, cada pedido del sistema está asociado a una cuenta de usuario determinada.

La clase MembershipUser no incluye propiedades como PhoneNumber, DeliveryPreferences o PastOrders. ¿Cómo se realiza el seguimiento de la información de usuario que necesita la aplicación y su integración con el marco de pertenencia? En este tutorial responderemos a esta pregunta mediante la creación de una aplicación de libro de visitas muy rudimentaria. Al hacerlo, veremos diferentes opciones para modelar información de usuario en una base de datos y, a continuación, veremos cómo asociar estos datos a las cuentas de usuario creadas por el marco de pertenencia. Comencemos.

Paso 1: Crear el modelo de datos de la aplicación de libro de visitas

Hay una variedad de técnicas que se pueden emplear para capturar información de usuario en una base de datos y asociarla a las cuentas de usuario creadas por el marco de pertenencia. Para ilustrar estas técnicas, es necesario aumentar la aplicación web del tutorial para que capture algún tipo de datos relacionados con el usuario. (Actualmente, el modelo de datos de la aplicación contiene solo las tablas de servicios de aplicación que requiere SqlMembershipProvider).

Vamos a crear una aplicación de libro de visitas muy sencilla, en la que un usuario autenticado puede dejar un comentario. Además de almacenar comentarios en el libro de visitas, vamos a permitir que cada usuario almacene su ciudad natal, página principal y firma. Si se proporcionan, la ciudad natal, la página principal y la firma del usuario aparecerán en cada mensaje que haya dejado en el libro de visitas.

Agregar la tabla GuestbookComments

Para capturar los comentarios del libro de visitas, es necesario crear una tabla de base de datos denominada GuestbookComments que tenga columnas como CommentId, Subject, Body y CommentDate. También es necesario que cada registro de la tabla GuestbookComments haga referencia al usuario que dejó el comentario.

Para agregar esta tabla a nuestra base de datos, vaya al Explorador de bases de datos en Visual Studio y profundice en la base de datos SecurityTutorials. Haga clic con el botón derecho en la carpeta Tablas y elija Agregar nueva tabla. Esto abre una interfaz que nos permite definir las columnas de la nueva tabla.

Add a New Table to the SecurityTutorials Database

Figura 1: Agregar una nueva tabla a la base de datos SecurityTutorials (haga clic para ver la imagen de tamaño completo)

A continuación, defina las columnas de GuestbookComments. Empiece agregando una columna denominada CommentId de tipo uniqueidentifier. Esta columna identificará de forma única cada comentario en el libro de visitas, por lo que no se permite NULL y se marca como clave principal de la tabla. En lugar de proporcionar un valor para el campo CommentId en cada INSERT, podemos indicar que se debe generar automáticamente un nuevo valor uniqueidentifier para este campo en INSERT estableciendo el valor predeterminado de la columna en NEWID(). Después de agregar este primer campo, marcarlo como clave principal y establecer su valor predeterminado, la visualización debe ser similar a la de la captura de pantalla que se muestra en la figura 2.

Add a Primary Column Named CommentId

Figura 2: Agregar una columna principal denominada CommentId (Haga clic para ver la imagen de tamaño completo)

A continuación, agregue una columna denominada Subject de tipo nvarchar(50) y una columna denominada Body de tipo nvarchar(MAX), lo cual no permite NULL en ambas columnas. Después, agregue una columna denominada CommentDate de tipo datetime. No permita NULL y establezca el valor predeterminado de la columna CommentDate en getdate().

Todo lo que queda es agregar una columna que asocie una cuenta de usuario con cada comentario del libro de visitas. Una opción sería agregar una columna denominada UserName de tipo nvarchar(256). Esta es una opción adecuada cuando se usa un proveedor de pertenencia distinto de SqlMembershipProvider. Pero al usar SqlMembershipProvider, como estamos en esta serie de tutoriales, no se garantiza que la columna UserName de la tabla aspnet_Users sea única. La clave principal de la tabla aspnet_Users es UserId y es de tipo uniqueidentifier. Por lo tanto, la tabla GuestbookComments necesita una columna denominada UserId de tipo uniqueidentifier (no se permiten valores NULL). Continúe y agregue esta columna.

Nota:

Como se describe en el tutorial Creación del esquema de pertenencia en SQL Server, el marco de pertenencia está diseñado para habilitar varias aplicaciones web con diferentes cuentas de usuario para compartir el mismo almacén de usuarios. Para ello, se crean particiones de cuentas de usuario en diferentes aplicaciones. Y aunque se garantiza que cada nombre de usuario sea único dentro de una aplicación, se puede usar el mismo nombre de usuario en diferentes aplicaciones que usen el mismo almacén de usuarios. Hay una restricción UNIQUE compuesta en la tabla aspnet_Users en los campos UserName y ApplicationId, pero ninguna solo en el campo UserName. Por lo tanto, es posible que la tabla aspnet_Users tenga dos (o más) registros con el mismo valor UserName. Sin embargo, hay una restricción UNIQUE en el campo UserId de la tabla aspnet_Users (ya que es la clave principal). Una restricción UNIQUE es importante porque sin ella no podemos establecer una restricción de clave externa entre las tablas GuestbookComments y aspnet_Users.

Después de agregar la columna UserId, guarde la tabla haciendo clic en el icono Guardar de la barra de herramientas. Asigne a la nueva tabla el nombre GuestbookComments.

Tenemos un último problema para prestar asistencia con la tabla GuestbookComments: es necesario crear una restricción de clave externa entre la columna GuestbookComments.UserId y la columna aspnet_Users.UserId. Para ello, haga clic en el icono Relación de la barra de herramientas para iniciar el cuadro de diálogo Relaciones de clave externa. (Como alternativa, puede iniciar este cuadro de diálogo si va al menú Diseñador de tablas y elige Relaciones).

Haga clic en el botón Agregar de la esquina inferior izquierda del cuadro de diálogo Relaciones de clave externa. Esto agregará una nueva restricción de clave externa, aunque todavía es necesario definir las tablas que participan en la relación.

Use the Foreign Key Relationships Dialog Box to Manage a Table's Foreign Key Constraints

Figura 3: Usar el cuadro de diálogo Relaciones de clave externa para administrar las restricciones de clave externa de una tabla (haga clic para ver la imagen de tamaño completo)

A continuación, haga clic en el icono de puntos suspensivos de la fila "Especificaciones de tabla y columnas" de la derecha. Se iniciará el cuadro de diálogo Tablas y columnas, desde el que se puede especificar la tabla y columna de clave principal y la columna de clave externa de la tabla GuestbookComments. En concreto, seleccione aspnet_Users y UserId como la tabla y columna de clave principal, y UserId de la tabla GuestbookComments como columna de clave externa (vea la figura 4). Después de definir las tablas y columnas de clave principal y externa, haga clic en Aceptar para volver al cuadro de diálogo Relaciones de clave externa.

Establish a Foreign Key Constraint Between the aspnet_Users and GuesbookComments Tables

Figura 4: Establecer una restricción de clave externa entre las tablas aspnet_Users y GuesbookComments (haga clic para ver la imagen de tamaño completo)

En este momento se ha establecido la restricción de clave externa. La presencia de esta restricción asegura la integridad relacional entre las dos tablas al garantizar que nunca habrá una entrada de libro de visitas que haga referencia a una cuenta de usuario inexistente. De forma predeterminada, una restricción de clave externa no permitirá que se elimine un registro primario si hay registros secundarios correspondientes. Es decir, si un usuario realiza uno o varios comentarios del libro de visitas y, a continuación, intentamos eliminar esa cuenta de usuario, se producirá un error en la eliminación a menos que se eliminen primero sus comentarios del libro de visitas.

Las restricciones de clave externa se pueden configurar para eliminar automáticamente los registros secundarios asociados cuando se elimina un registro primario. En otras palabras, podemos configurar esta restricción de clave externa para que las entradas del libro de visitas de un usuario se eliminen automáticamente cuando se elimine su cuenta de usuario. Para ello, expanda la sección "Especificación de INSERT y UPDATE" y establezca la propiedad "Eliminar regla" en Cascada.

Configure the Foreign Key Constraint to Cascade Deletes

Figura 5: Configurar la restricción de clave externa en eliminaciones en cascada (haga clic para ver la imagen de tamaño completo)

Para guardar la restricción de clave externa, haga clic en el botón Cerrar para salir de Relaciones de clave externa. Haga clic en el icono Guardar de la barra de herramientas para guardar la tabla.

Almacenar la ciudad natal, la página principal y la firma del usuario

En la tabla GuestbookComments se muestra cómo almacenar información que comparte una relación uno a varios con cuentas de usuario. Dado que cada cuenta de usuario puede tener un número arbitrario de comentarios asociados, esta relación se modela mediante la creación de una tabla para contener el conjunto de comentarios que incluye una columna que vincula cada comentario a un usuario determinado. Cuando se usa SqlMembershipProvider, este vínculo se establece mejor mediante la creación de una columna denominada UserId de tipo uniqueidentifier y una restricción de clave externa entre esta columna y aspnet_Users.UserId.

Ahora es necesario asociar tres columnas a cada cuenta de usuario para almacenar la ciudad natal, la página principal y la firma del usuario, que aparecerán en sus comentarios del libro de visitas. Hay dos formas diferentes de lograrlo:

  • Agregar nuevas columnas a las tablas aspnet_Users o aspnet_Membership. Este enfoque no es muy recomendable porque modifica el esquema usado por SqlMembershipProvider. La elección de esta opción puede generar problemas posteriormente. Por ejemplo, ¿qué ocurre si una versión futura de ASP.NET usa un esquema SqlMembershipProvider diferente? Microsoft puede incluir una herramienta para migrar los datos de ASP.NET 2.0 SqlMembershipProvider al nuevo esquema, pero si ha modificado el esquema de ASP.NET 2.0 SqlMembershipProvider, puede que no sea posible realizar esta conversión.

  • Usar el marco de perfil de ASP.NET, que define una propiedad de perfil para la ciudad natal, la página principal y la firma. ASP.NET incluye un marco de perfil diseñado para almacenar datos adicionales específicos del usuario. Al igual que el marco de pertenencia, el marco de perfil se compila sobre el modelo de proveedor. .NET Framework se distribuye con un SqlProfileProviderque almacena los datos de perfil en una base de datos de SQL Server. De hecho, nuestra base de datos ya tiene la tabla usada por SqlProfileProvider (aspnet_Profile), ya que se agregó al agregar los servicios de aplicación de nuevo en el tutorial Creación del esquema de pertenencia en SQL Server.
    La principal ventaja del marco de perfil es que permite a los desarrolladores definir las propiedades del perfil en Web.config: no es necesario escribir código para serializar los datos del perfil hacia y desde el almacén de datos subyacente. En resumen, es increíblemente fácil definir un conjunto de propiedades de perfil y trabajar con ellas en el código. Sin embargo, el sistema de perfiles deja mucho que desear cuando se trata del control de versiones, por lo que si tiene una aplicación en la que espera que se agreguen nuevas propiedades específicas del usuario más adelante, o que las existentes se quiten o modifiquen, es posible que el marco de perfil no sea la mejor opción. Además, SqlProfileProvider almacena las propiedades de perfil de una manera muy poco normalizada, por lo que resultará casi imposible ejecutar consultas directamente en los datos de perfil (como, el número de usuarios que tienen una ciudad natal de Nueva York).
    Para obtener más información sobre el marco de perfil, consulte la sección "Lecturas adicionales" al final de este tutorial.

  • Agregar estas tres columnas a una nueva tabla de la base de datos y establecer una relación uno a uno entre esta tabla y aspnet_Users. Este enfoque implica un poco más de trabajo que con el marco de perfil, pero ofrece máxima flexibilidad en cómo se modelan las propiedades de usuario adicionales en la base de datos. Esta es la opción que usaremos en este tutorial.

Crearemos una nueva tabla denominada UserProfiles para guardar la ciudad natal, la página principal y la firma de cada usuario. Haga clic con el botón derecho en la carpeta Tablas de la ventana Explorador de bases de datos y elija crear una nueva tabla. Asigne a la primera columna el nombre UserId y establezca su tipo en uniqueidentifier. No permita valores NULL y marque la columna como clave principal. A continuación, agregue columnas denominadas: HomeTown de tipo nvarchar(50); HomepageUrl de tiponvarchar(100); y firma de tipo nvarchar(500). Cada una de estas tres columnas puede aceptar un valor NULL.

Create the UserProfiles Table

Figura 6: Crear la tabla UserProfiles (haga clic para ver la imagen de tamaño completo)

Guarde la tabla y asígnele el nombre UserProfiles. Por último, establezca una restricción de clave externa entre el campo UserId de la tabla UserProfiles y el campo aspnet_Users.UserId. Como hicimos con la restricción de clave externa entre las tablas GuestbookComments y aspnet_Users, haga que esta restricción tenga eliminaciones en cascada. Dado que el campo UserId de UserProfiles es la clave principal, esto garantiza que no habrá más de un registro en la tabla UserProfiles para cada cuenta de usuario. Este tipo de relación se conoce como de uno a uno.

Ahora que hemos creado el modelo de datos, estamos listos para usarlo. En los pasos 2 y 3 veremos cómo el usuario que ha iniciado sesión puede ver y editar su información de ciudad natal, página principal y firma. En el paso 4, crearemos la interfaz para que los usuarios autenticados envíen nuevos comentarios al libro de visitas y vean los existentes.

Paso 2: Mostrar la ciudad natal, la página principal y la firma del usuario

Hay una variedad de maneras de permitir que el usuario que ha iniciado sesión vea y edite su información de ciudad natal, página principal y firma. Podemos crear manualmente la interfaz de usuario con controles TextBox y Label o podemos usar uno de los controles web de datos, como el control DetailsView. Para ejecutar las instrucciones SELECT y UPDATE de la base de datos, podemos escribir código ADO.NET en la clase de código subyacente de nuestra página o, como alternativa, emplear un enfoque declarativo con SqlDataSource. Lo ideal es que nuestra aplicación contenga una arquitectura en capas, que podemos invocar mediante programación desde la clase de código subyacente de la página o de forma declarativa mediante el control ObjectDataSource.

Dado que esta serie de tutoriales se centra en aspectos de autenticación, autorización, cuentas de usuario y roles de formularios, no se incluye una explicación exhaustiva de estas diferentes opciones de acceso a datos o por qué se prefiere una arquitectura en capas sobre la ejecución de instrucciones SQL directamente desde la página de ASP.NET. Voy a repasar el uso de DetailsView y SqlDataSource (la opción más rápida y sencilla), pero los conceptos descritos pueden aplicarse también a lógica de acceso a datos y controles web alternativos. Para obtener más información sobre cómo trabajar con datos en ASP.NET, consulte mi serie de tutoriales Trabajar con datos en ASP.NET 2.0.

Abra la página AdditionalUserInfo.aspx en la carpeta Membership y agregue un control DetailsView a la página, estableciendo su propiedad ID en UserProfile y borrando sus propiedades Width y Height. Expanda la etiqueta inteligente de DetailsView y elija enlazarla a un nuevo control de origen de datos. Se iniciará el Asistente para configuración de orígenes de datos (consulte la figura 7). El primer paso le pide que especifique el tipo de origen de datos. Puesto que vamos a conectarnos directamente a la base de datos SecurityTutorials, elija el icono Base de datos, especificando ID como UserProfileDataSource.

Add a New SqlDataSource Control Named UserProfileDataSource

Figura 7: Agregar un nuevo control SqlDataSource denominado UserProfileDataSource (haga clic para ver la imagen de tamaño completo)

En la siguiente pantalla se solicita el uso de la base de datos. Ya hemos definido un cadena de conexión en Web.config para la base de datos SecurityTutorials. El nombre de esta cadena de conexión (SecurityTutorialsConnectionString) debe estar en la lista desplegable. Seleccione esta opción y haga clic en Siguiente.

Choose SecurityTutorialsConnectionString from the Drop-Down List

Figura 8: Elegir SecurityTutorialsConnectionString en la lista desplegable (haga clic para ver la imagen de tamaño completo)

La siguiente pantalla nos pide que especifiquemos la tabla y las columnas que se van a consultar. Elija la tabla UserProfiles de la lista desplegable y compruebe todas las columnas.

Bring Back All of the Columns from the UserProfiles Table

Figura 9: Devolver todas las columnas de la tabla UserProfiles (haga clic para ver la imagen de tamaño completo)

La consulta actual de la figura 9 devuelve todos los registros de UserProfiles, pero solo estamos interesados en el registro del usuario que ha iniciado sesión actualmente. Para agregar una cláusula WHERE, haga clic en el botón WHERE para abrir el cuadro de diálogo Agregar una cláusula WHERE (vea la figura 10). Aquí puede seleccionar la columna para filtrar, el operador y el origen del parámetro de filtro. Seleccione UserId como columna y "=" como operador.

Por desgracia, no hay ningún origen de parámetro integrado para devolver el valor del usuario UserId que ha iniciado sesión actualmente. Tendremos que adoptar este valor mediante programación. Por lo tanto, establezca la lista desplegable Origen en "Ninguno", haga clic en el botón Agregar para agregar el parámetro y, a continuación, haga clic en Aceptar.

Add a Filter Parameter on the UserId Column

Figura 10: Agregar un parámetro de filtro en la columna UserId (haga clic para ver la imagen de tamaño completo)

Después de hacer clic en Aceptar, se le devolverá a la pantalla que se muestra en la figura 9. Sin embargo, esta vez, la consulta SQL en la parte inferior de la pantalla debe incluir una cláusula WHERE. Haga clic en Siguiente para pasar a la pantalla "Consulta de prueba". Aquí puede ejecutar la consulta y ver los resultados. Haga clic en Finalizar para completar el asistente.

Tras completar el Asistente de configuración de orígenes de datos, Visual Studio crea el control SqlDataSource en función de la configuración especificada en el asistente. Además, agrega manualmente BoundFields a DetailsView para cada columna devuelta por SelectCommand de SqlDataSource. No es necesario mostrar el campo UserId en DetailsView, ya que el usuario no requiere conocer este valor. Puede eliminar este campo directamente del marcado declarativo del control DetailsView o haciendo clic en el vínculo "Editar campos" desde su etiqueta inteligente.

En este momento, el marcado declarativo de la página debe tener un aspecto similar al siguiente:

<asp:DetailsView ID="UserProfile" runat="server"
     AutoGenerateRows="False" DataKeyNames="UserId"
     DataSourceID="UserProfileDataSource">
     <Fields>
          <asp:BoundField DataField="HomeTown" HeaderText="HomeTown"
               SortExpression="HomeTown" />
          <asp:BoundField DataField="HomepageUrl" HeaderText="HomepageUrl"
               SortExpression="HomepageUrl" />
          <asp:BoundField DataField="Signature" HeaderText="Signature"
               SortExpression="Signature" />
     </Fields>
</asp:DetailsView>
<asp:SqlDataSource ID="UserProfileDataSource" runat="server"
          ConnectionString="<%$ ConnectionStrings:SecurityTutorialsConnectionString %>"
          SelectCommand="SELECT [UserId], [HomeTown], [HomepageUrl], [Signature] FROM
          [UserProfiles] WHERE ([UserId] = @UserId)">
     <SelectParameters>
          <asp:Parameter Name="UserId" Type="Object" />
     </SelectParameters>
</asp:SqlDataSource>

Es necesario establecer mediante programación el parámetro UserId del control SqlDataSource en UserId del usuario que ha iniciado sesión actualmente antes de seleccionar los datos. Esto se puede lograr mediante la creación de un controlador de eventos para el evento Selecting de SqlDataSource y la adición del código siguiente allí:

protected void UserProfileDataSource_Selecting(object sender, 
          SqlDataSourceSelectingEventArgs e)
{
     // Get a reference to the currently logged on user
     MembershipUser currentUser = Membership.GetUser();
 
     // Determine the currently logged on user's UserId value
     Guid currentUserId = (Guid)currentUser.ProviderUserKey;
 
     // Assign the currently logged on user's UserId to the @UserId parameter
     e.Command.Parameters["@UserId"].Value = currentUserId;
}

El código anterior comienza obteniendo una referencia al usuario que ha iniciado sesión llamando al método GetUser de la clase Membership. Esto devuelve un objeto MembershipUser, cuya propiedad ProviderUserKey contiene UserId. Posteriormente, el valor UserId se asigna al parámetro @UserId de SqlDataSource.

Nota:

El método Membership.GetUser() devuelve información sobre el usuario que ha iniciado sesión actualmente. Si un usuario anónimo visita la página, devolverá un valor de null. En tal caso, esto provocará NullReferenceException en la siguiente línea de código al intentar leer la propiedad ProviderUserKey. Por supuesto, no tenemos que preocuparnos porque Membership.GetUser() devuelva un valor null en la página AdditionalUserInfo.aspx porque configuramos la autorización de dirección URL en un tutorial anterior para que solo los usuarios autenticados pudieran acceder a los recursos de ASP.NET de esta carpeta. Si necesita obtener acceso a información sobre el usuario que ha iniciado sesión actualmente en una página en la que se permite el acceso anónimo, asegúrese de comprobar que se devuelve un objeto que no es null MembershipUser del método GetUser() antes de hacer referencia a sus propiedades.

Si visita la página de AdditionalUserInfo.aspx con un explorador, verá una página en blanco porque todavía tenemos que agregar filas a la tabla UserProfiles. En el paso 6 veremos cómo personalizar el control CreateUserWizard para agregar automáticamente una nueva fila a la tabla UserProfiles cuando se crea una nueva cuenta de usuario. Por ahora, sin embargo, es necesario crear manualmente un registro en la tabla.

Vaya al Explorador de bases de datos en Visual Studio y expanda la carpeta Tablas. Haga clic con el botón derecho en la tabla aspnet_Users y elija "Mostrar datos de tabla" para ver los registros de la tabla; haga lo mismo para la tabla UserProfiles. En la figura 11 se muestran estos resultados al colocar en mosaico verticalmente. En mi base de datos actualmente hay registros aspnet_Users para Bruce, Fred y Tito, pero no hay registros en la tabla UserProfiles.

The Contents of the aspnet_Users and UserProfiles Tables are Displayed

Figura 11: Se muestra el contenido de las tablas aspnet_Users y UserProfiles (haga clic para ver la imagen de tamaño completo)

Agregue un nuevo registro a la tabla UserProfiles escribiendo manualmente los valores de los campos HomeTown, HomepageUrl y Signature. La manera más fácil de obtener un valor UserId válido en el nuevo registro UserProfiles es seleccionar el campo UserId de una cuenta de usuario determinada de la tabla aspnet_Users y copiarlo y pegarlo en el campo UserId de UserProfiles. En la figura 12 se muestra la tabla UserProfiles después de agregar un nuevo registro para Bruce.

A Record was Added to UserProfiles for Bruce

Figura 12: Se agregó un registro a UserProfiles para Bruce (haga clic para ver la imagen de tamaño completo)

Vuelva a la página AdditionalUserInfo.aspx, iniciando sesión como Bruce. Como se muestra en la figura 13, se muestra la configuración de Bruce.

The Currently Visiting User is Shown His Settings

Figura 13: El usuario actualmente activo muestra su configuración (haga clic para ver la imagen de tamaño completo)

Nota:

Continúe y agregue manualmente registros en la tabla UserProfiles para cada usuario de pertenencia. En el paso 6 veremos cómo personalizar el control CreateUserWizard para agregar automáticamente una nueva fila a la tabla UserProfiles cuando se crea una nueva cuenta de usuario.

Paso 3: Permitir que el usuario edite su ciudad natal, página principal y firma

En este momento, el usuario que ha iniciado sesión puede ver la configuración de la ciudad natal, la página principal y la firma, pero aún no puede modificarlas. Vamos a actualizar el control DetailsView para que se puedan editar los datos.

Lo primero que debemos hacer es agregar UpdateCommand para SqlDataSource, especificando la instrucción UPDATE que se va a ejecutar y sus parámetros correspondientes. Seleccione SqlDataSource y, en la ventana Propiedades, haga clic en los puntos suspensivos situados junto a la propiedad UpdateQuery para abrir el cuadro de diálogo Comando y Editor de parámetros. Escriba la siguiente instrucción UPDATE en el cuadro de texto:

UPDATE UserProfiles SET
     HomeTown = @HomeTown,
     HomepageUrl = @HomepageUrl,
     Signature = @Signature
WHERE UserId = @UserId

A continuación, haga clic en el botón "Actualizar parámetros", que creará un parámetro en la colección UpdateParameters del control SqlDataSource para cada uno de los parámetros de la instrucción UPDATE. Deje el origen de todos los parámetros establecidos en Ninguno y haga clic en el botón Aceptar para completar el cuadro de diálogo.

Specify the SqlDataSource's UpdateCommand and UpdateParameters

Figura 14: Especificación de UpdateCommand y UpdateParameters de SqlDataSource (haga clic para ver la imagen de tamaño completo)

Debido a las adiciones realizadas al control SqlDataSource, el control DetailsView ahora puede admitir la edición. En la etiqueta inteligente de DetailsView, active la casilla "Habilitar edición". Esto agrega un CommandField a la colección Fields del control con su propiedad ShowEditButton establecida en True. Se representa un botón Editar cuando DetailsView se muestra en modo de solo lectura y los botones Actualizar y Cancelar cuando se muestra en modo de edición. Sin embargo, en lugar de requerir que el usuario haga clic en Editar, podemos hacer que DetailsView se represente en un estado "siempre editable" estableciendo la propiedad DefaultMode del control DetailsView en Edit.

Después de realizar estos cambios, el marcado declarativo del control DetailsView debe ser similar al siguiente:

<asp:DetailsView ID="UserProfile" runat="server"
          AutoGenerateRows="False" DataKeyNames="UserId"
          DataSourceID="UserProfileDataSource" DefaultMode="Edit">
     <Fields>
          <asp:BoundField DataField="HomeTown" HeaderText="HomeTown"
               SortExpression="HomeTown" />
          <asp:BoundField DataField="HomepageUrl" HeaderText="HomepageUrl"
               SortExpression="HomepageUrl" />
          <asp:BoundField DataField="Signature" HeaderText="Signature"
               SortExpression="Signature" />
          <asp:CommandField ShowEditButton="True" />
     </Fields>
</asp:DetailsView>

Anote la adición de CommandField y la propiedad DefaultMode.

Siga adelante y pruebe esta página con un explorador. Al realizar la visita con un usuario que tiene un registro correspondiente en UserProfiles, la configuración del usuario se muestra en una interfaz editable.

The DetailsView Renders an Editable Interface

Figura 15: DetailsView representa una interfaz editable (haga clic para ver la imagen de tamaño completo)

Intente cambiar los valores y haga clic en el botón Actualizar. Parece que no sucede nada. Se produce un postback y los valores se guardan en la base de datos, pero no hay ningún comentario visual de que se haya producido en guardado.

Para solucionar este problema, vuelva a Visual Studio y agregue un control Label encima de DetailsView. Establezca su ID en SettingsUpdatedMessage, su propiedad Text en "Se ha actualizado la configuración" y sus propiedades Visible y EnableViewState en false.

<asp:Label ID="SettingsUpdatedMessage" runat="server"
     Text="Your settings have been updated."
     EnableViewState="false"
     Visible="false"></asp:Label>

Es necesario mostrar la etiqueta SettingsUpdatedMessage cada vez que se actualiza DetailsView. Para ello, cree un controlador de eventos para el evento ItemUpdated de DetailsView y agregue el código siguiente:

protected void UserProfile_ItemUpdated(object sender, DetailsViewUpdatedEventArgs e)
{
     SettingsUpdatedMessage.Visible = true;
}

Vuelva a la página AdditionalUserInfo.aspx con un explorador y actualice los datos. Esta vez se muestra un mensaje de estado útil.

A Short Message is Displayed When the Settings are Updated

Figura 16: Se muestra un mensaje corto cuando se actualizan la Configuración (haga clic para ver la imagen de tamaño completo)

Nota:

La interfaz de edición del control DetailsView deja mucho que desear. Usa cuadros de texto de tamaño estándar, pero es probable que el campo de firma sea un cuadro de texto de varias líneas. Se debe usar un RegularExpressionValidator para asegurarse de que la dirección URL de la página principal, si se especifica, comience con "http://" o "https://". Además, dado que el control DetailsView tiene su propiedad DefaultMode establecida en Edit, el botón Cancelar no hace nada. Debe eliminarse o, al hacer clic en él, se redirigirá al usuario a otra página (por ejemplo ~/Default.aspx). Dejo estas mejoras como ejercicio para el lector.

Actualmente, el sitio web no proporciona ningún vínculo a la página AdditionalUserInfo.aspx. La única manera de acceder a ella es escribir la dirección URL de la página directamente en la barra de direcciones del explorador. Vamos a agregar un vínculo a esta página en la página maestra Site.master.

Recuerde que la página maestra contiene un control web LoginView en su ContentPlaceHolder LoginContent que muestra un marcado diferente para visitantes autenticados y anónimos. Actualice el control de LoginView LoggedInTemplate para incluir un vínculo a la página AdditionalUserInfo.aspx. Después de realizar estos cambios, el marcado declarativo de LoginView debe ser similar al siguiente:

<asp:LoginView ID="LoginView1" runat="server">
     <LoggedInTemplate>
          Welcome back,
          <asp:LoginName ID="LoginName1" runat="server" />.
          <br />
          <asp:HyperLink ID="lnkUpdateSettings" runat="server" 
               NavigateUrl="~/Membership/AdditionalUserInfo.aspx">
               Update Your Settings</asp:HyperLink>
     </LoggedInTemplate>
     <AnonymousTemplate>
          Hello, stranger.
     </AnonymousTemplate>
</asp:LoginView>

Observe la adición del control HyperLink lnkUpdateSettings a LoggedInTemplate. Con este vínculo implementado, los usuarios autenticados pueden saltar rápidamente a la página para ver y modificar su configuración de ciudad natal, página principal y firma.

Paso 4: Agregar nuevos comentarios de libro de visitas

La página Guestbook.aspx es donde los usuarios autenticados pueden ver el libro de visitas y dejar un comentario. Comencemos con la creación de la interfaz para agregar nuevos comentarios del libro de visitas.

Abra la página Guestbook.aspx en Visual Studio y construya una interfaz de usuario que consta de dos controles TextBox, uno para el asunto del nuevo comentario y otro para su cuerpo. Establezca la propiedad ID del primer control TextBox en Subject y su propiedad Columns en 40; establezca la del segundo ID en Body, su TextMode en MultiLine y sus propiedades Width y Rows en "95%" y 8, respectivamente. Para completar la interfaz de usuario, agregue un control web Button denominado PostCommentButton y establezca su propiedad Text en un mensaje de tipo "Publicar su comentario".

Dado que cada comentario del libro de visitas requiere un asunto y un cuerpo, agregue un RequiredFieldValidator para cada uno de los controles TextBox. Establezca la propiedad ValidationGroup de estos controles en "EnterComment"; del mismo modo, establezca la propiedad PostCommentButton del control ValidationGroup en "EnterComment". Para obtener más información sobre los controles de validación de ASP.NET, consulte Validación de formularios en ASP.NET.

Después de crear la interfaz de usuario, el marcado declarativo de la página debe tener un aspecto similar al siguiente:

<h3>Leave a Comment</h3>
<p>
     <b>Subject:</b>
     <asp:RequiredFieldValidator ID="SubjectReqValidator" runat="server"
          ErrorMessage="You must provide a value for Subject"
          ControlToValidate="Subject" ValidationGroup="EnterComment">
     </asp:RequiredFieldValidator><br/>
     <asp:TextBox ID="Subject" Columns="40" runat="server"></asp:TextBox>
</p>
<p>
     <b>Body:</b>
     <asp:RequiredFieldValidator ID="BodyReqValidator" runat="server"
          ControlToValidate="Body"
          ErrorMessage="You must provide a value for Body" ValidationGroup="EnterComment">
     </asp:RequiredFieldValidator><br/>
     <asp:TextBox ID="Body" TextMode="MultiLine" Width="95%"
          Rows="8" runat="server"></asp:TextBox>
</p>
<p>
     <asp:Button ID="PostCommentButton" runat="server" 
          Text="Post Your Comment"
          ValidationGroup="EnterComment" />
</p>

Una vez completada la interfaz de usuario, la siguiente tarea consiste en insertar un nuevo registro en la tabla GuestbookComments cuando se hace clic en PostCommentButton. Esto se puede lograr de varias maneras: podemos escribir código ADO.NET en el controlador de eventos Click de Button; podemos agregar un control SqlDataSource a la página, configurar su InsertCommand y, a continuación, llamar a su método Insert desde el controlador de eventos Click; o podemos crear un nivel intermedio responsable de insertar nuevos comentarios del libro de visitas e invocar esta funcionalidad desde el controlador de eventos Click. Como hemos visto el uso de SqlDataSource en el paso 3, aquí vamos a usar código ADO.NET.

Nota:

Las clases de ADO.NET usadas para acceder mediante programación a datos desde una base de datos de Microsoft SQL Server se encuentran en el espacio de nombres System.Data.SqlClient. Es posible que tenga que importar este espacio de nombres en la clase de código subyacente de la página (es decir, using System.Data.SqlClient;).

Cree un controlador de eventos para el evento de PostCommentButtonClick y agregue el código siguiente al controlador:

protected void PostCommentButton_Click(object sender, EventArgs e)
{
     if (!Page.IsValid)
          return;
 
     // Determine the currently logged on user's UserId
     MembershipUser currentUser = Membership.GetUser();
     Guid currentUserId = (Guid)currentUser.ProviderUserKey;
 
     // Insert a new record into GuestbookComments
     string connectionString = 
          ConfigurationManager.ConnectionStrings["SecurityTutorialsConnectionString"].ConnectionString;
     string insertSql = "INSERT INTO GuestbookComments(Subject, Body, UserId) VALUES(@Subject,
               @Body, @UserId)";
 
     using (SqlConnection myConnection = new SqlConnection(connectionString))
     {
          myConnection.Open();
          SqlCommand myCommand = new SqlCommand(insertSql, myConnection);
          myCommand.Parameters.AddWithValue("@Subject", Subject.Text.Trim());
          myCommand.Parameters.AddWithValue("@Body", Body.Text.Trim());
          myCommand.Parameters.AddWithValue("@UserId", currentUserId);
          myCommand.ExecuteNonQuery();
          myConnection.Close();
     }
 
     // "Reset" the Subject and Body TextBoxes
     Subject.Text = string.Empty;
     Body.Text = string.Empty;
}

El controlador de eventos Click empieza comprobando que los datos proporcionados por el usuario son válidos. Si no es así, el controlador de eventos sale antes de insertar un registro. Suponiendo que los datos proporcionados son válidos, el valor UserId del usuario que ha iniciado sesión se recupera y se almacena en la variable local currentUserId. Este valor es necesario porque debemos proporcionar un valor UserId al insertar un registro en GuestbookComments.

Después de eso, la cadena de conexión de la base de datos SecurityTutorials se recupera de Web.config y se especifica la instrucción SQL INSERT. Posteriormente, se crea y se abre un objeto SqlConnection. A continuación, se construye un objeto SqlCommand y se asignan los valores de los parámetros usados en la consulta INSERT. Posteriormente, se ejecuta la instrucción INSERT y se cierra la conexión. Al final del controlador de eventos, las propiedades Subject y Body de Text de TextBoxes se borran para que los valores del usuario no se conserven en el postback.

Siga adelante y pruebe esta página en un explorador. Puesto que esta página está en la carpeta Membership, no es accesible para los visitantes anónimos. Por lo tanto, primero deberá iniciar sesión (si aún no lo ha hecho). Escriba un valor en TextBoxes Subject y Body y haga clic en el botón PostCommentButton. Esto hará que se agregue un nuevo registro a GuestbookComments. Al realizar un postback, el asunto y el cuerpo proporcionados se borran de los TextBoxes.

Después de hacer clic en el botón PostCommentButton, no hay ninguna referencia visual de que el comentario se haya agregado al libro de visitas. Todavía tenemos que actualizar esta página para mostrar los comentarios del libro de visitas existentes, lo cual haremos en el paso 5. Una vez que lo logramos, el comentario recién agregado aparecerá en la lista de comentarios, proporcionando una referencia visual adecuada. Por ahora, confirme que el comentario del libro de visitas se guardó examinando el contenido de la tabla GuestbookComments.

En la figura 17 se muestra el contenido de la tabla GuestbookComments después de que se hayan dejado dos comentarios.

You Can See the Guestbook Comments in the GuestbookComments Table

Figura 17: Puede ver los comentarios del libro de visitas en la tabla GuestbookComments (haga clic para ver la imagen de tamaño completo)

Nota:

Si un usuario intenta insertar un comentario en el libro de visitas que contenga marcado potencialmente peligroso, como HTML, ASP.NET generará una excepción HttpRequestValidationException. Para obtener más información sobre esta excepción y sobre por qué se produce y cómo permitir que los usuarios envíen valores potencialmente peligrosos, consulte el documento técnico sobre la validación de solicitudes.

Paso 5: Enumerar los comentarios del libro de visitas existentes

Además de dejar comentarios, un usuario que visita la página Guestbook.aspx también debe poder ver los comentarios existentes del libro de visitas. Para ello, agregue un control ListView denominado CommentList a la parte inferior de la página.

Nota:

El control ListView es nuevo en ASP.NET versión 3.5. Está pensado para mostrar una lista de elementos en un diseño muy personalizable y flexible, pero todavía ofrece de forma integrada funcionalidad de edición, inserción, eliminación, paginación y ordenación, como GridView. Si usa ASP.NET 2.0, deberá usar el control DataList o Repeater, alternativamente. Para obtener más información sobre el uso de ListView, consulte la entrada de blog de Scott Guthrie sobre el control asp:ListView y mi artículo Mostrar datos con el control ListView.

Abra la etiqueta inteligente de ListView y, en la lista desplegable Elegir origen de datos, enlace el control a un nuevo origen de datos. Como vimos en el paso 2, se iniciará el Asistente para configurar orígenes de datos. Seleccione el icono Base de datos, asigne el nombre CommentsDataSource al SqlDataSource resultante y haga clic en Aceptar. A continuación, seleccione la cadena de conexión SecurityTutorialsConnectionString en la lista desplegable y haga clic en Siguiente.

En este punto del paso 2 especificamos los datos que se van a consultar seleccionando la tabla UserProfiles de la lista desplegable y seleccionando las columnas que se van a devolver (consulte la figura 9). Sin embargo, esta vez queremos crear una instrucción SQL que extraiga no solo los registros de GuestbookComments, sino también la ciudad natal, la página principal, la firma y el nombre de usuario del comentario. Por lo tanto, seleccione el botón de radio "Especificar una instrucción SQL personalizada o un procedimiento almacenado" y haga clic en Siguiente.

Se abrirá la pantalla "Definir instrucciones personalizadas o procedimientos almacenados". Haga clic en el botón del Generador de consultas para compilar gráficamente la consulta. El Generador de consultas empieza por solicitarnos que especifiquemos las tablas desde las que queremos realizar la consulta. Seleccione las tablas GuestbookComments, UserProfiles y aspnet_Users y haga clic en Aceptar. Esto agregará las tres tablas a la superficie de diseño. Dado que hay restricciones de clave externa entre las tablas GuestbookComments, UserProfiles y aspnet_Users, el Generador de consultas realiza automáticamente JOIN con ellas.

Solo resta especificar las columnas que se van a devolver. En la tabla GuestbookComments, seleccione las columnas Subject, Body y CommentDate; devuelva las columnas HomeTown, HomepageUrl y Signature de la tabla UserProfiles; y devuelva UserName de aspnet_Users. Además, agregue "ORDER BY CommentDate DESC" al final de la consulta SELECT para que primero se devuelvan las publicaciones más recientes. Después de realizar estas selecciones, la interfaz del Generador de consultas debe ser similar a la de la captura de pantalla de la figura 18.

The Constructed Query JOINs the GuestbookComments, UserProfiles, and aspnet_Users Tables

Figura 18: La consulta construida realiza JOIN con las tablas GuestbookComments, UserProfiles y aspnet_Users (haga clic para ver la imagen de tamaño completo)

Haga clic en Aceptar para cerrar la ventana del Generador de consultas y volver a la pantalla "Definir instrucciones personalizadas o procedimientos almacenados". Haga clic en Siguiente para avanzar a la pantalla "Consulta de prueba", donde puede ver los resultados de la consulta haciendo clic en el botón Consulta de prueba. Cuando esté listo, haga clic en Finalizar para completar el Asistente para configurar orígenes de datos.

Cuando completamos el Asistente para configurar orígenes de datos en el paso 2, la colección Fields del control DetailsView asociado se actualizó para incluir un BoundField para cada columna devuelta por SelectCommand. Sin embargo, ListView permanece sin cambios; todavía necesitamos definir su diseño. El diseño de ListView se puede construir manualmente a través de su marcado declarativo o desde la opción "Configurar ListView" en su etiqueta inteligente. Normalmente yo prefiero definir el marcado a mano, pero puede usar el método que le resulte más natural.

Para finalizar, uso los siguientes LayoutTemplate, ItemTemplatey ItemSeparatorTemplate para mi control ListView:

<asp:ListView ID="CommentList" runat="server" DataSourceID="CommentsDataSource">
     <LayoutTemplate>
          <span ID="itemPlaceholder" runat="server" />
          <p>
               <asp:DataPager ID="DataPager1" runat="server">
                    <Fields>
                         <asp:NextPreviousPagerField ButtonType="Button" 
                              ShowFirstPageButton="True"
                              ShowLastPageButton="True" />
                    </Fields>
               </asp:DataPager>
          </p>
     </LayoutTemplate>
     <ItemTemplate>
          <h4><asp:Label ID="SubjectLabel" runat="server" 
               Text='<%# Eval("Subject") %>' /></h4>
          <asp:Label ID="BodyLabel" runat="server" 
               Text='<%# Eval("Body").ToString().Replace(Environment.NewLine, "<br />") %>' />
          <p>
               ---<br />
               <asp:Label ID="SignatureLabel" Font-Italic="true" runat="server"
                    Text='<%# Eval("Signature") %>' />
               <br />
               <br />
               My Home Town:
               <asp:Label ID="HomeTownLabel" runat="server" 
                    Text='<%# Eval("HomeTown") %>' />
               <br />
               My Homepage:
               <asp:HyperLink ID="HomepageUrlLink" runat="server" 
                    NavigateUrl='<%# Eval("HomepageUrl") %>' 
                    Text='<%# Eval("HomepageUrl") %>' />
          </p>
          <p align="center">
               Posted by
               <asp:Label ID="UserNameLabel" runat="server" 
                    Text='<%# Eval("UserName") %>' /> on
               <asp:Label ID="CommentDateLabel" runat="server" 
                    Text='<%# Eval("CommentDate") %>' />
          </p>
     </ItemTemplate>
     <ItemSeparatorTemplate>
          <hr />
     </ItemSeparatorTemplate>
</asp:ListView>

LayoutTemplate define el marcado emitido por el control, mientras que ItemTemplate representa cada elemento devuelto por SqlDataSource. El marcado resultante de ItemTemplate se coloca en el control itemPlaceholder de LayoutTemplate. Además de itemPlaceholder, LayoutTemplate incluye un control DataPager, que limita ListView a mostrar solo 10 comentarios del libro de visitas por página (el valor predeterminado) y representa una interfaz de paginación.

Mi ItemTemplate muestra el asunto de cada comentario del libro invitado en un elemento <h4> con el cuerpo situado debajo del asunto. Tenga en cuenta que la sintaxis utilizada para mostrar el cuerpo adopta los datos devueltos por la instrucción databinding de Eval("Body"), lo convierte en una cadena y reemplaza los saltos de línea por el elemento <br />. Esta conversión es necesaria para mostrar los saltos de línea especificados al enviar el comentario, ya que HTML omite el espacio en blanco. La firma del usuario se muestra debajo del cuerpo en cursiva, seguida de la ciudad natal del usuario, un vínculo a su página principal, la fecha y hora en que se realizó el comentario y el nombre de usuario de la persona que dejó el comentario.

Dedique un momento a ver esta página con un explorador. Aquí debería ver reflejados los comentarios que agregó al libro de visitas en el paso 5.

Guestbook.aspx Now Displays the Guestbook's Comments

Figura 19: Guestbook.aspx Ahora muestra los comentarios del libro de visitas (haga clic para ver la imagen de tamaño completo)

Intente agregar un nuevo comentario al libro de visitas. Al hacer clic en el botón PostCommentButton, se realiza postback de la página y el comentario se agrega a la base de datos, pero el control ListView no se actualiza para mostrar el nuevo comentario. Esto se puede corregir de una de estas formas:

  • Actualizando el controlador de eventos PostCommentButton del botón Click para que invoque el método DataBind() del control ListView después de insertar el nuevo comentario en la base de datos, o bien
  • Estableciendo la propiedad EnableViewState del control ListView en false. Este enfoque funciona porque, al deshabilitar el estado de vista del control, debe volver a enlazarse a los datos subyacentes en cada postback.

En el sitio web del tutorial que se puede descargar de este tutorial se muestran ambas técnicas. La propiedad EnableViewState del control ListView en false y el código necesario para volver a enlazar mediante programación los datos a ListView está presente en el controlador de eventos Click, pero con una marca de comentario.

Nota:

Actualmente, la página AdditionalUserInfo.aspx permite al usuario ver y editar su configuración de ciudad natal, página principal y firma. Seria fantástico poder actualizar AdditionalUserInfo.aspx para mostrar los comentarios del libro de visitas del usuario que ha iniciado sesión. Es decir, además de examinar y modificar su información, que un usuario pudiera visitar la página AdditionalUserInfo.aspx para ver qué comentarios del libro de visitas ha realizado en el pasado. Lo dejo como un ejercicio para el lector interesado.

Paso 6: Personalizar el control CreateUserWizard para incluir una interfaz para la ciudad natal, la página principal y la firma

La consulta SELECT usada por la página Guestbook.aspx usa INNER JOIN para combinar los registros relacionados entre las tablas GuestbookComments, UserProfiles y aspnet_Users. Si un usuario que no tiene ningún registro en UserProfiles realiza un comentario en el libro de visitas, el comentario no se mostrará en ListView porque INNER JOIN solo devuelve registros GuestbookComments cuando hay registros coincidentes en UserProfiles y aspnet_Users. Y como vimos en el paso 3, si un usuario no tiene un registro en UserProfiles, no puede ver ni editar su configuración en la página AdditionalUserInfo.aspx.

Evidentemente, debido a nuestras decisiones de diseño, es importante que todas las cuentas de usuario del sistema de pertenencia tengan un registro coincidente en la tabla UserProfiles. Lo que queremos es que se agregue un registro correspondiente a UserProfiles cada vez que se cree una nueva cuenta de usuario de pertenencia a través de CreateUserWizard.

Como se describe en el tutorial Creación de cuentas de usuario, después de crear la nueva cuenta de usuario de pertenencia, el control CreateUserWizard genera su evento CreatedUser. Podemos crear un controlador de eventos para este evento, obtener el UserId para el usuario recién creado y, a continuación, insertar un registro en la tabla UserProfiles con valores predeterminados para las columnas HomeTown, HomepageUrl y Signature. Además, es posible solicitar al usuario estos valores personalizando la interfaz del control CreateUserWizard para incluir TextBoxes adicionales.

Veamos primero cómo agregar una nueva fila a la tabla UserProfiles en el controlador de eventos CreatedUser con valores predeterminados. Después, veremos cómo personalizar la interfaz de usuario del control CreateUserWizard para incluir campos de formulario adicionales para recopilar la ciudad natal, la página principal y la firma del nuevo usuario.

Agregar una fila predeterminada a UserProfiles

En el tutorial Creación de cuentas de usuario agregamos un control CreateUserWizard a la página CreatingUserAccounts.aspx de la carpeta Membership. Para que el control CreateUserWizard agregue un registro a la tabla UserProfiles tras la creación de la cuenta de usuario, es necesario actualizar la funcionalidad del control CreateUserWizard. En lugar de realizar estos cambios en la página CreatingUserAccounts.aspx, vamos a agregar un nuevo control CreateUserWizard a la página EnhancedCreateUserWizard.aspx y realizaremos las modificaciones de este tutorial allí.

Abra la página EnhancedCreateUserWizard.aspx en Visual Studio y arrastre un control CreateUserWizard desde la caja de herramientas a la página. Establezca la propiedad ID del control CreateUserWizard en NewUserWizard. Como hemos descrito en el tutorial Creación de cuentas de usuario, la interfaz de usuario predeterminada de CreateUserWizard solicita al visitante la información necesaria. Una vez proporcionada esta información, el control crea internamente una nueva cuenta de usuario en el marco de pertenencia, todo sin tener que escribir ni una sola línea de código.

El control CreateUserWizard genera una serie de eventos durante su flujo de trabajo. Una vez que un visitante proporciona la información de solicitud y envía el formulario, el control CreateUserWizard activa inicialmente su evento CreatingUser. Si e produce algún problema durante el proceso de creación, se activa el evento CreateUserError; sin embargo, si el usuario se crea correctamente, se genera el evento CreatedUser. En el tutorial Creación de cuentas de usuario, creamos un controlador de eventos para el evento CreatingUser para garantizar que el nombre de usuario proporcionado no contenía ningún espacio inicial o final y que el nombre de usuario no aparecía en ningún lugar de la contraseña.

Para agregar una fila en la tabla UserProfiles para el usuario recién creado, es necesario crear un controlador de eventos para el evento CreatedUser. Cuando se ha activado el evento CreatedUser, la cuenta de usuario ya se ha creado en el marco de pertenencia, lo que nos permite recuperar el valor UserId de la cuenta.

Cree un controlador de eventos para el evento de NewUserWizardCreatedUser y agregue el código siguiente al controlador:

protected void NewUserWizard_CreatedUser(object sender, EventArgs e)
{
     // Get the UserId of the just-added user
     MembershipUser newUser = Membership.GetUser(NewUserWizard.UserName);
     Guid newUserId = (Guid)newUser.ProviderUserKey;
 
     // Insert a new record into UserProfiles
     string connectionString = 
          ConfigurationManager.ConnectionStrings["SecurityTutorialsConnectionString"].ConnectionString;
     string insertSql = "INSERT INTO UserProfiles(UserId, HomeTown, HomepageUrl,
          Signature) VALUES(@UserId, @HomeTown, @HomepageUrl, @Signature)";
 
     using (SqlConnection myConnection = new SqlConnection(connectionString))
     {
          myConnection.Open();
          SqlCommand myCommand = new SqlCommand(insertSql, myConnection);
          myCommand.Parameters.AddWithValue("@UserId", newUserId);
          myCommand.Parameters.AddWithValue("@HomeTown", DBNull.Value);
          myCommand.Parameters.AddWithValue("@HomepageUrl", DBNull.Value);
          myCommand.Parameters.AddWithValue("@Signature", DBNull.Value);
          myCommand.ExecuteNonQuery();
          myConnection.Close();
     }
}

El código anterior empieza recuperando el UserId de la cuenta de usuario que acaba de agregar. Esto se logra mediante el método Membership.GetUser(username) para devolver información sobre un usuario determinado y, posteriormente, mediante la propiedad ProviderUserKey para recuperar su UserId. El nombre de usuario especificado por el usuario en el control CreateUserWizard está disponible a través de su propiedad UserName.

A continuación, la cadena de conexión se recupera de Web.config y se especifica la instrucción INSERT. Se crean instancias de los objetos ADO.NET necesarios y se ejecuta el comando. El código asigna una instancia DBNull a los parámetros @HomeTown, @HomepageUrl y @Signature, lo cual tiene el efecto de insertar valores NULL de base de datos para los campos HomeTown, HomepageUrl y Signature.

Visite la página EnhancedCreateUserWizard.aspx con un explorador y cree una nueva cuenta de usuario. Después de hacerlo, vuelva a Visual Studio y examine el contenido de las tablas aspnet_Users y UserProfiles (como lo hicimos en la figura 12). Debería ver la nueva cuenta de usuario en aspnet_Users y una fila correspondiente UserProfiles (con valores NULL para HomeTown, HomepageUrl y Signature).

A New User Account and UserProfiles Record Have Been Added

Figura 20: Se han agregado una nueva cuenta de usuario y un registro UserProfiles (haga clic para ver la imagen de tamaño completo)

Después de que el visitante haya proporcionado su nueva información de cuenta y haya hecho clic en el botón "Crear usuario", se crea la cuenta de usuario y se agrega una fila a la tabla UserProfiles. A continuación, CreateUserWizard muestra su CompleteWizardStep, que muestra un mensaje de operación correcta y un botón Continuar. Al hacer clic en el botón Continuar, se produce un postback, pero no se realiza ninguna acción, dejando al usuario bloqueado en la página EnhancedCreateUserWizard.aspx.

Podemos especificar una dirección URL para enviar al usuario cuando se haga clic en el botón Continuar a través de la propiedad ContinueDestinationPageUrl del control CreateUserWizard. Establezca la propiedad ContinueDestinationPageUrl en "~/Membership/AdditionalUserInfo.aspx". Esto lleva al nuevo usuario a AdditionalUserInfo.aspx, donde puede ver y actualizar su configuración.

Personalización de la interfaz de CreateUserWizard para solicitar la nueva ciudad natal, página principal y firma del usuario

La interfaz predeterminada del control CreateUserWizard es suficiente para escenarios de creación de cuentas simples en los que solo se necesita recopilar información básica de la cuenta de usuario, como el nombre de usuario, la contraseña y el correo electrónico. Pero, ¿qué ocurre si queremos pedir al visitante que introduzca su ciudad natal, página principal y firma mientras crea su cuenta? Es posible personalizar la interfaz del control CreateUserWizard para recopilar información adicional en el registro, y esta información se puede usar en el controlador de eventos CreatedUser para insertar registros adicionales en la base de datos subyacente.

El control CreateUserWizard extiende el control ASP.NET Wizard, que es un control que permite al desarrollador de páginas definir una serie ordenada de WizardSteps. El control Wizard representa el paso activo y proporciona una interfaz de navegación que permite al visitante desplazarse por estos pasos. El control Wizard es ideal para dividir una tarea larga en varios pasos cortos. Para obtener más información sobre el control Wizard, consulte Creación de una interfaz de usuario paso a paso con el control Wizard de ASP.NET 2.0.

El marcado predeterminado del control CreateUserWizard define dos WizardSteps: CreateUserWizardStep y CompleteWizardStep.

<asp:CreateUserWizard ID="NewUserWizard" runat="server"
     ContinueDestinationPageUrl="~/Membership/AdditionalUserInfo.aspx">
     <WizardSteps>
          <asp:CreateUserWizardStep ID="CreateUserWizardStep1" runat="server">
          </asp:CreateUserWizardStep>
          <asp:CompleteWizardStep ID="CompleteWizardStep1" runat="server">
          </asp:CompleteWizardStep>
     </WizardSteps>
</asp:CreateUserWizard>

El primer WizardStep, CreateUserWizardStep, representa la interfaz que solicita el nombre de usuario, la contraseña, el correo electrónico, etc. Una vez que el visitante proporciona esta información y hace clic en "Crear usuario", se muestra CompleteWizardStep, que muestra el mensaje de operación correcta y un botón Continuar.

Para personalizar la interfaz del control CreateUserWizard para incluir campos de formulario adicionales, podemos:

  • Crear uno o varios nuevos WizardSteppara contener los elementos adicionales de la interfaz de usuario. Para agregar un nuevo WizardStep a CreateUserWizard, haga clic en el vínculo "Agregar o quitar WizardSteps" de su etiqueta inteligente para iniciar el editor de recopilación de WizardStep. Desde allí puede agregar, eliminar o reordenar los pasos del asistente. Este es el enfoque que usaremos para este tutorial.

  • Convertir CreateUserWizardStep en WizardStepeditable. Esto reemplaza CreateUserWizardStep por un WizardStep equivalente cuyo marcado define una interfaz de usuario que coincide con las instancias de CreateUserWizardStep. Al convertir CreateUserWizardStep en WizardStep, podemos cambiar la posición de los controles o agregar elementos adicionales de la interfaz de usuario a este paso. Para convertir CreateUserWizardStep o CompleteWizardStep en un objeto editable WizardStep, haga clic en el vínculo "Personalizar paso de usuario" o "Personalizar paso completo" desde la etiqueta inteligente del control.

  • Usar alguna combinación de las dos opciones anteriores.

Lo importante que hay que tener en cuenta es que el control CreateUserWizard ejecuta su proceso de creación de cuentas de usuario cuando se hace clic en el botón "Crear usuario" desde su CreateUserWizardStep. No importa si hay más WizardStep después de CreateUserWizardStep o no.

Al agregar un WizardStep personalizado al control CreateUserWizard para recopilar entradas de usuario adicionales, el WizardStep personalizado se puede colocar antes o después de CreateUserWizardStep. Si viene antes de CreateUserWizardStep, la entrada adicional del usuario recopilada del WizardStep personalizado está disponible para el controlador de eventos CreatedUser. Sin embargo, si el WizardStep personalizado viene después de CreateUserWizardStep, en el momento en que se muestra el WizardStep personalizado, la nueva cuenta de usuario ya se ha creado y el evento CreatedUser ya se ha activado.

En la figura 21 se muestra el flujo de trabajo cuando el WizardStep agregado precede a CreateUserWizardStep. Dado que la información adicional del usuario se ha recopilado en el momento en que se desencadena el evento CreatedUser, todo lo que tenemos que hacer es actualizar el controlador de eventos CreatedUser para recuperar estas entradas y usarlas para los valores de parámetro de la instrucción INSERT (en lugar de DBNull.Value).

The CreateUserWizard Workflow When an Additional WizardStep Precedes the CreateUserWizardStep

Figura 21: El flujo de trabajo CreateUserWizard cuando un WizardStep adicional precede a CreateUserWizardStep (haga clic para ver la imagen de tamaño completo)

Si el WizardStep personalizado se coloca después de CreateUserWizardStep, sin embargo, el proceso de creación de la cuenta de usuario se produce antes de que el usuario haya tenido la oportunidad de introducir su ciudad natal, página principal o firma. En tal caso, esta información adicional debe insertarse en la base de datos una vez creada la cuenta de usuario, como se muestra en la figura 22.

The CreateUserWizard Workflow When an Additional WizardStep Comes After the CreateUserWizardStep

Figura 22: El flujo de trabajo CreateUserWizard cuando un WizardStep adicional va después de CreateUserWizardStep (haga clic para ver la imagen de tamaño completo)

El flujo de trabajo que se muestra en la figura 22 espera a insertar un registro en la tabla UserProfiles hasta que finalice el paso 2. Sin embargo, si el visitante cierra su explorador después del paso 1, habremos alcanzado un estado en el que se creó una cuenta de usuario, pero no se agregó ningún registro a UserProfiles. Una solución alternativa es tener un registro con NULL o valores predeterminados insertados en UserProfiles en el controlador de eventos CreatedUser (que se activa después del paso 1) y, a continuación, actualizar este registro después de que se complete el paso 2. Esto garantiza que se agregará un registro UserProfiles para la cuenta de usuario aunque el usuario salga del proceso de registro a mitad del proceso.

Para este tutorial, vamos a crear un nuevo WizardStep que se producirá después de CreateUserWizardStep, pero antes de CompleteWizardStep. Vamos a hacer primero que WizardStep esté bien situado y configurado y, a continuación, veremos el código.

En la etiqueta inteligente del control CreateUserWizard, seleccione la opción "Agregar o quitar WizardStep", que abre el cuadro de diálogo del editor de recopilación de WizardStep. Agregue un nuevo WizardStep, configure su ID en UserSettings, su Title en "Su configuración" y su StepType en Step. A continuación, colóquelo para que venga después de CreateUserWizardStep ("Suscribirse a su nueva cuenta") y antes de CompleteWizardStep ("Completar"), como se muestra en la figura 23.

Add a New WizardStep to the CreateUserWizard Control

Figura 23: Agregar un nuevo WizardStep al control CreateUserWizard (haga clic para ver la imagen de tamaño completo)

Haga clic en Aceptar para cerrar el cuadro de diálogo del editor de recopilación de WizardStep. El nuevo elemento WizardStep queda destacado por el marcado declarativo actualizado del control CreateUserWizard:

<asp:CreateUserWizard ID="NewUserWizard" runat="server"
     ContinueDestinationPageUrl="~/Membership/AdditionalUserInfo.aspx">
     <WizardSteps>
          <asp:CreateUserWizardStep ID="CreateUserWizardStep1" runat="server">
          </asp:CreateUserWizardStep>
          <asp:WizardStep runat="server" ID="UserSettings" StepType="Step"
               Title="Your Settings">
          </asp:WizardStep>
          <asp:CompleteWizardStep ID="CompleteWizardStep1" runat="server">
          </asp:CompleteWizardStep>
     </WizardSteps>
</asp:CreateUserWizard>

Anote el nuevo elemento <asp:WizardStep>. Es necesario agregar la interfaz de usuario para recopilar aquí la nueva ciudad natal, página principal y firma del usuario. Puede escribir este contenido en la sintaxis declarativa o a través del Diseñador. Para usar el Diseñador, seleccione el paso "Su configuración" en la lista desplegable de la etiqueta inteligente para ver el paso en el Diseñador.

Nota:

Al seleccionar un paso por la lista desplegable de la etiqueta inteligente, se actualiza la propiedad ActiveStepIndex del control CreateUserWizard, que especifica el índice del paso inicial. Por lo tanto, si usa esta lista desplegable para editar el paso "Su configuración" en el Diseñador, asegúrese de volver a establecerlo en "Suscribirse a su nueva cuenta" para que este paso se muestre cuando los usuarios visiten la página EnhancedCreateUserWizard.aspx por primera vez.

Cree una interfaz de usuario en el paso "Su configuración" que contenga tres controles TextBox denominados HomeTown, HomepageUrly Signature. Después de construir esta interfaz, el marcado declarativo de CreateUserWizard debe ser similar al siguiente:

<asp:CreateUserWizard ID="NewUserWizard" runat="server"
     ContinueDestinationPageUrl="~/Membership/AdditionalUserInfo.aspx">
     <WizardSteps>
          <asp:CreateUserWizardStep ID="CreateUserWizardStep1" runat="server">
          </asp:CreateUserWizardStep>
          <asp:WizardStep runat="server" ID="UserSettings" StepType="Step"
               Title="Your Settings">
               <p>
                    <b>Home Town:</b><br />
                    <asp:TextBox ID="HomeTown" runat="server"></asp:TextBox>
               </p>
               <p>
                    <b>Homepage URL:</b><br />
                    <asp:TextBox ID="HomepageUrl" Columns="40" runat="server"></asp:TextBox>
               </p>
               <p>
                    <b>Signature:</b><br />
                    <asp:TextBox ID="Signature" TextMode="MultiLine" Width="95%"
                         Rows="5" runat="server"></asp:TextBox>
               </p>
          </asp:WizardStep>
          <asp:CompleteWizardStep ID="CompleteWizardStep1" runat="server">
          </asp:CompleteWizardStep>
     </WizardSteps>
</asp:CreateUserWizard>

Continúe y visite esta página con un explorador y cree una nueva cuenta de usuario, especificando valores para la ciudad natal, la página principal y la firma. Después de completar CreateUserWizardStep, la cuenta de usuario se crea en el marco de pertenencia y se ejecuta el controlador de eventos CreatedUser, lo cual agrega una nueva fila a UserProfiles, pero con un valor de base de datos NULL para HomeTown, HomepageUrl y Signature. Los valores especificados para la ciudad natal, la página principal y la firma nunca se usan. El resultado neto es una nueva cuenta de usuario con un registro UserProfiles cuyos campos HomeTown, HomepageUrl y Signature aún no se han especificado.

Necesitamos ejecutar código después del paso "Su configuración" que adopta los valores de ciudad natal, el página principal y firma especificados por el usuario y actualiza el registro UserProfiles adecuado. Cada vez que el usuario se mueve entre los pasos de un control Wizard, se desencadena el evento ActiveStepChanged de Wizard. Podemos crear un controlador de eventos para este evento y actualizar la tabla UserProfiles cuando se haya completado el paso "Su configuración".

Agregue un controlador de eventos para el evento ActiveStepChanged de CreateUserWizard y agregue el código siguiente:

protected void NewUserWizard_ActiveStepChanged(object sender, EventArgs e)
{
     // Have we JUST reached the Complete step?
     if (NewUserWizard.ActiveStep.Title == "Complete")
     {
          WizardStep UserSettings = NewUserWizard.FindControl("UserSettings") as
          WizardStep;
 
          // Programmatically reference the TextBox controls
          TextBox HomeTown = UserSettings.FindControl("HomeTown") as TextBox;
          TextBox HomepageUrl = UserSettings.FindControl("HomepageUrl") as TextBox;
          TextBox Signature = UserSettings.FindControl("Signature") as TextBox;
 
          // Update the UserProfiles record for this user
          // Get the UserId of the just-added user
          MembershipUser newUser = Membership.GetUser(NewUserWizard.UserName);
          Guid newUserId = (Guid)newUser.ProviderUserKey;
 
          // Insert a new record into UserProfiles
          string connectionString = 
               ConfigurationManager.ConnectionStrings["SecurityTutorialsConnectionString"].ConnectionString;
          string updateSql = "UPDATE UserProfiles SET HomeTown = @HomeTown, HomepageUrl
               = @HomepageUrl, Signature = @Signature WHERE UserId = @UserId";
 
          using (SqlConnection myConnection = new SqlConnection(connectionString))
          {
               myConnection.Open();
               SqlCommand myCommand = new SqlCommand(updateSql, myConnection);
               myCommand.Parameters.AddWithValue("@HomeTown", HomeTown.Text.Trim());
               myCommand.Parameters.AddWithValue("@HomepageUrl", HomepageUrl.Text.Trim());
               myCommand.Parameters.AddWithValue("@Signature", Signature.Text.Trim());
               myCommand.Parameters.AddWithValue("@UserId", newUserId);
               myCommand.ExecuteNonQuery();
               myConnection.Close();
          }
     }
}

El código anterior comienza por determinar si acabamos de alcanzar el paso "Completar". Dado que el paso "Completar" se produce inmediatamente después del paso "Su configuración", cuando el visitante alcanza el paso "Completar" significa que acaba de terminar el paso "Su configuración".

En tal caso, es necesario hacer referencia mediante programación a los controles TextBox dentro de UserSettings WizardStep. Para ello, primero se usa el método FindControl para hacer referencia mediante programación a UserSettings WizardStep y, a continuación, de nuevo para hacer referencia a los TextBoxes desde dentro de WizardStep. Una vez se ha hecho referencia a los TextBoxes, estamos listos para ejecutar la instrucción UPDATE. La instrucción UPDATE tiene el mismo número de parámetros que la instrucción INSERT en el controlador de eventos CreatedUser, pero aquí usamos los valores de ciudad natal, página principal y firma proporcionados por el usuario.

Con este controlador de eventos implementado, visite la página EnhancedCreateUserWizard.aspx con un explorador y cree una cuenta de usuario que especifique valores para la ciudad natal, la página principal y la firma. Después de crear la nueva cuenta, se le debe redirigir a la página AdditionalUserInfo.aspx, donde se muestra la información de firma, página principal y ciudad natal recién introducida.

Nota:

Nuestro sitio web tiene actualmente dos páginas desde las que un visitante puede crear una nueva cuenta: CreatingUserAccounts.aspx y EnhancedCreateUserWizard.aspx. El mapa del sitio web y la página de inicio de sesión apuntan a la página CreatingUserAccounts.aspx, pero la página CreatingUserAccounts.aspx no solicita al usuario la información de su ciudad natal, página principal y firma, y no agrega una fila correspondiente a UserProfiles. Por lo tanto, actualice la página CreatingUserAccounts.aspx para que ofrezca esta funcionalidad o actualice el mapa del sitio y la página de inicio de sesión para hacer referencia a EnhancedCreateUserWizard.aspx en lugar de CreatingUserAccounts.aspx. Si elige la última opción, asegúrese de actualizar el archivo de la carpeta MembershipWeb.config, para permitir que los usuarios anónimos accedan a la página EnhancedCreateUserWizard.aspx.

Resumen

En este tutorial hemos examinado técnicas para modelar datos relacionados con las cuentas de usuario dentro del marco de pertenencia. En concreto, hemos examinado las entidades de modelado que comparten una relación de uno a varios con cuentas de usuario, así como datos que comparten una relación de uno a uno. Además, hemos visto cómo se puede mostrar, insertar y actualizar esta información relacionada, con algunos ejemplos mediante el control SqlDataSource y otros mediante el código ADO.NET.

Con este tutorial se completa nuestro repaso de las cuentas de usuario. A partir del siguiente tutorial, nos centraremos en los roles. En los siguientes tutoriales veremos el marco de roles, veremos cómo crear nuevos roles, cómo asignar roles a los usuarios, cómo determinar a qué roles pertenece un usuario y cómo aplicar la autorización basada en roles.

¡Feliz programación!

Lecturas adicionales

Para obtener más información sobre los temas tratados 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 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.

Agradecimientos especiales a…

Esta serie de tutoriales fue revisada por muchos revisores de gran ayuda. ¿Le interesa revisar mis próximos artículos de MSDN? Si es así, escríbame a mitchell@4GuysFromRolla.com.