Se ha hecho común en la arquitectura de software el diseño basado
en proveedores. La infraestructura de ASP.NET ofrece servicios de
gestión de los usuarios de un sitio Web, y para ello la
comunicación con la fuente de almacenamiento descansa en lo que se
conoce como proveedor de membresía o pertenencia (Membership
Provider), un heredero de la clase
System.Web.Security.MembershipProvider. ASP.NET 2.0 incluye de
manera predeterminada dos implementaciones de proveedores de
membresía:
• SqlMembershipProvider, que mantiene los datos
de membresía en una base de datos Microsoft SQL Server o Microsoft
SQL Server Express.
• ActiveDirectoryMembershipProvider, que recupera
los datos de membresía de Microsoft Active Directory.
En este artículo se analiza SqlMembershipProvider y se propone una
implementación funcionalmente similar a la de este proveedor, pero
basada en ADO.NET Entity Framework (en adelante EF).
SqlMembershipProvider almacena los datos de membresía usando un
esquema pre-establecido de base de datos SQL. El acceso a los datos
no se realiza directamente sobre las tablas que la componen, sino
que se delega en procedimientos almacenados [1]. Esta concepción
permite que el funcionamiento para esquemas lógicos diferentes
pueda resolverse modificando solamente procedimientos almacenados.
Sin embargo, el esfuerzo necesario para adaptarse a un esquema
lógico distinto puede ser tedioso, ya que son más de 16 los
procedimientos almacenados que dan soporte al acceso a datos
relacionado con la gestión de la membresía. Por otro lado, el
desarrollador de la aplicación Web que usa la API de membresía no
tiene necesariamente que estar familiarizado con T-SQL.
Por lo tanto, sería deseable que el proveedor de membresía no
dependiera del esquema lógico de base de datos ni del sistema de
gestión de base de datos.
Hacia un mejor enfoque
EF se ajusta a las exigencias impuestas por un escenario como éste.
Su modelo de datos de entidades (Entity Data Model, EDM) nos ofrece
un alto nivel de abstracción del almacén de datos. La esencia de
este nuevo marco de trabajo es proveer un contexto donde la
atención se centre en entes conceptuales llamados entidades y las
distintas relaciones entre ellos; el esquema lógico de los datos
queda en segundo plano, salvo a la hora de especificar cómo asociar
cada entidad hacia y desde la fuente de datos.
Lo que hay que hacer ahora es trasladar el conocimiento del
proveedor de membresía del esquema de los datos del nivel lógico al
nivel conceptual. La estructura pre-establecida esta vez se impone
como un EDM, en adelante referido como ASP.NET Provider Data Model
o simplemente Provider Data Model, y cada almacén de datos deberá
especificar su debida correspondencia con esta especie de base de
datos conceptual. Gracias a ello, se podrá considerar como medio de
almacenamiento posible para el servicio de membresía toda fuente de
datos para la que sea posible:
• Asociarla a las entidades
correspondientes
• Disponer del soporte de EF requerido para la
gestión de los datos.
El soporte de EF necesario para cada uno de los sistemas de gestión
de base de datos del mercado es implementado generalmente por las
mismas compañías que los sustentan, o por terceros interesados
comercialmente.
ASP.NET Provider Data Model
El esquema de base de datos sobre el que opera
SqlMembershipProvider fue concebido no solo con la idea del
servicio de membresía en mente, sino también pensando en otros
servicios disponibles hoy, como el de la gestión de estado de
sesión, y con la posibilidad de que se desee adicionar nuevos
servicios en el futuro. Este esquema contiene tablas que son
específicas de algún proveedor y otras que son de propósito general
[2].
Por simplicidad, el esquema lógico de base de datos sobre el cual
trabajaremos en este artículo es el que fue propuesto por Microsoft
para ofrecer soporte de almacenamiento en una base de datos
Microsoft SQL Server o Microsoft SQL Server Express a varios
exponentes del modelo de proveedores de ASP.NET, entre ellos el de
membresía. Este esquema lógico se puede obtener con la herramienta
aspnetregsql; para ello, ejecute esta utilidad desde Visual Studio
2008 Command Prompt y siga los pasos del asistente que aparecerá.
Nuestro modelo de datos, al menos para esta etapa, obedecerá lo más
fielmente posible al esquema lógico original con respecto a tablas
físicas (no vistas) y procedimientos almacenados (figura 1).
Una alternativa pudiera basarse en que aspnetMembership herede de
aspnetUsers, pero habría entonces que enfocarse en decisiones como
la de qué hacer con la relación de aspnetMembership con
aspnet_Applications, muy pertinente en el esquema lógico por
disminuir la necesidad de realizar encuentros (joins) y optimizar
las consultas de miembros. No obstante, nuestra intención no es
definir un modelo de datos ideal, sino mostrar los beneficios de
trasladar el conocimiento acerca del almacén de datos de membresía
de un nivel lógico a un nivel conceptual. La figura 2 muestra el
modelo, generado automáticamente a partir de la base de datos
original, sobre el cual operará nuestro EdmMembershipProvider, un
proveedor de membresía basado en EF.
EdmMembershipProvider
El dominio de fuentes de datos sobre las cuales pudiera operar
EdmMembershipProvider es amplio y puede ser medido por la capacidad
de mapeo de EDM. No obstante, una solución que obvie completamente
los procedimientos o funciones del almacén no es lo mejor, puesto
que éstos en ocasiones pudieran ser la decisión más acertada.
Imagine escenarios donde evitar viajes de ida y vuelta de datos
(roundtrips), desatar acciones complejas en el servidor ante
condiciones aleatorias o una alta explotación de la potencia del
lenguaje de consulta nativo sean requerimientos prioritarios. La
gran diferencia con el enfoque de SqlMembershipProvider es el hecho
de que no será la estrategia primaria, sino una alternativa para
cuando las necesidades rebasen el alcance del EDM.
Tendremos entonces dos contextos totalmente distintos de
comportamiento operacional:
• Acceso a datos, caracterizado por delegar la
ejecución en llamadas a procedimientos almacenados.
• Esquema de datos, que se basa en consultar el
modelo.
Un modo de comportamiento de una operación se especifica según uno
de los dos correspondientes valores del tipo enumerado
ProgrammabilityMode: DataAccess para acceso a datos y DataSchema
para esquema de datos.
Una clase que herede de System.
Web.Security.MembershipProvider debe implementar los métodos de
ésta para garantizar una adecuada interacción con el servicio de
membresía. Cada una de estas operaciones, según
EdmMembershipProvider, sería ejecutada en solo uno de los modos
posibles de comportamiento funcional. La tabla 1 muestra las
operaciones básicas de todo proveedor de membresía y los
correspondientes atributos (de valores DataAccess o DataSchema)
especificables por operación para manipular su modo de
comportamiento.
La implementación interna de cada uno de estos métodos obedecería
un patrón de la forma "si mi correspondiente modo de
programabilidad es igual a DataAccess, invoco el procedimiento
almacenado asociado; si no, consulto el modelo de datos". Es
importante destacar el nivel de granularidad que ofrece este
control por operaciones, permitiendo un modo mixto de
comportamiento, que posibilita que algunas operaciones se ejecuten
mediante llamadas a procedimientos almacenados y otras consultando
el modelo. Para evitar tener que hacer una configuración
exhaustiva, establezca primero el valor por defecto con el atributo
defaultMode y luego declare todos aquellos modos que difieran del
valor por defecto.
Visto desde la perspectiva de programación, EDM es un almacén de
datos más y por tanto debe integrarse a las tecnologías de acceso a
datos de .NET. EF ofrece una API para consultar modelos
conceptuales siguiendo el patrón de diseño de clientes ADO.NET, que
se denomina EntityClient. Leer datos mediante este cliente suele
ser mucho más complicado, dado que las columnas no tienen por qué
ser siempre valores simples sino que pudieran ser a su vez
colecciones de datos; por ejemplo, para contener los resultados de
la consulta de una propiedad de navegación. Su consulta se basa en
un lenguaje estilo SQL, pero diseñado específicamente para
manipular entidades: eSQL. Este escenario de uso es idóneo cuando
usted necesita obtener resultados autodescriptivos, pero es
trabajoso para situaciones simples. Para facilitar las cosas, EF
ofrece una capa superior sobre EntityClient, conocida como Object
Services, en la que las consultas también se expresan mediante
eSQL, pero los resultados se pueden obtener como objetos.
Finalmente, una tercera variante de interacción con un modelo
conceptual es LINQ to Entities, un dialecto de LINQ para consultar
modelos de entidades. Todo este entorno es provisto por EF; para
más información al respecto, revise [5]. Consultar el modelo (modo
de programabilidad DataSchema) se refiere a basarse en una de estas
tres alternativas, según sea más conveniente.
Configuración e inicialización del EdmMembershipProvider
De todos los métodos de un proveedor, el primero que se invoca es
Initialize, que recopila toda la información de la configuración y
prepara la instancia para las llamadas subsiguientes. Es una
operación funcional que obedece al patrón de diseño general
heredado de ProviderBase, la clase base para el modelo extensible
de proveedores en .NET. En este punto es donde se hacen los
chequeos de validación.
Además de aquellos atributos XML reconocidos por todo proveedor de
membresía, como passwordFormat y applicationName,
EdmMembershipProvider reconoce:
• Los modos de programabilidad por operación, que
incluyen los referidos en la tabla 1 y el especial
defaultMode.
• El manifiesto de los proveedores cuyo soporte
tiene instalado la fuente de datos; esto es, tablas, procedimientos
y todo elemento del esquema de datos en cuestión (tabla 2). Estos
atributos se hacen necesarios por el desconocimiento que se tiene
en el modelo de la verdadera naturaleza lógica del almacén.
Los atributos de manifiesto se utilizan debido a la incapacidad por
parte del modelo conceptual de conocer el esquema lógico real del
almacén de datos. Por ejemplo, cuando se elimine un usuario, debe
existir una forma de borrar en consecuencia el perfil del usuario
si existiese también soporte para un proveedor de perfiles
(ProfileProvider). Vea en el listado 1 una configuración típica de
un EdmMembershipProvider.
Procedimientos almacenados en el ASP.NET Provider Data Model
En un EDM es posible hacer uso de procedimientos almacenados de dos
formas: como entradas de modificaciones para un tipo de entidad
determinado (inserción, actualización y eliminación) o como
funciones de importación. La primera es parcialmente válida para
embeber lógica de cambios en el ASP.NET Provider Data Model, pero
solo funcionaría si el modo de programabilidad de las operaciones,
que provoquen cambios en la entidad de interés, es DataSchema.
La función de importación es el recurso de modelado que más hemos
explotado, tratando así a los procedimientos almacenados como
"ciudadanos del nivel conceptual". Al importarse una función, debe
especificarse si retornará datos o no, y en caso positivo indicar
un tipo de retorno, que puede ser una entidad o un tipo escalar. La
generación automática de código dotará entonces de un método
equivalente al descendiente de ObjectContext generado. Si el tipo
de retorno es una entidad, todo funciona sin problemas. Sin
embargo, deben distinguirse tres patrones fundamentales que hay que
"retocar" para lograr un modelo de objetos homogéneo:
• Procedimiento almacenado que retorna un tipo
escalar (ya sea directamente como resultado de consulta o
estratégicamente mediante el valor de retorno del
procedimiento).
• Procedimiento almacenado que retorna un tipo
anónimo.
• Procedimiento almacenado con parámetros de
salida.
Cualquiera de estos casos impide la generación automática de un
método equivalente en la capa de objetos, por lo que es necesario
resolverlos manualmente.
En los métodos generados automáticamente que retornan entidades se
aprecia un proceder bastante claro: primero crear y configurar por
cada parámetro de la función un objeto asociado de tipo
ObjectParameter y posteriormente invocar ObjectContext.
ExecuteFunction<T> con el conjunto de objetos ObjectParameter
previamente configurados, donde T es el tipo de entidad a retornar.
Observe la signatura de ExecuteFunction<T> en el listado 2:
functionName representa el nombre del procedimiento o función a
llamar y parameters los parámetros previamente configurados. Con
ObjectResult<T> se puede iterar sobre los resultados. Veamos
en lo que sigue cómo lidiar similarmente con procedimientos
almacenados que devuelven tipos escalares mediante el valor de
retorno del procedimiento o tipos anónimos.
Procedimiento almacenado que "retorna" tipo escalar mediante
el valor de retorno
Esta característica no está soportada completamente en la versión
1.0 de EF. Se puede importar un procedimiento almacenado que
estratégicamente devuelva un tipo escalar en el valor de
retorno, pero su equivalente en el modelo de objetos no se
generará. Sin embargo, es posible invocar el procedimiento mediante
EntityClient y efectuar manualmente la lectura del valor de
retorno. Para ello hemos desarrollado el método extensor del
listado 3.
Con este método se puede entonces ejecutar procedimientos que no
produzcan resultado o que estratégicamente retornen un valor
escalar. Soporta adicionalmente parámetros de salida, obtención del
valor de retorno con que finalizó la ejecución -aun cuando no se
produzca directamente resultado alguno- e incluso permite optar por
el chequeo de este último para detectar ejecuciones fallidas. El
listado 4 muestra una llamada a la función para consultar la
cantidad de usuarios conectados, donde el valor de retorno es la
cantidad solicitada. Si se tratara propiamente de un procedimiento
que retorna un tipo escalar ExecuteNonQuery no sería suficiente,
porque habría que reemplazar la llamada a DbCommand.ExecuteNonQuery
por DbCommand.ExecuteScalar. El listado 6 ejemplifica una llamada a
la función con que se crea un usuario, pero el código de retorno
esta vez informa el tipo de error ocurrido si no se pudo efectuar
la creación (cero en ausencia de errores). Ambos casos utilizan el
método ExecuteNonQuery anterior con semánticas distintas. Por
último, el listado 5 muestra el método estático CreateInputParam de
la clase ProvidersUtility, útil en estas implementaciones porque
modulariza la creación y configuración de un parámetro de
entrada.
Procedimiento almacenado que retorna tipo anónimo
Un caso un poco más complicado son los procedimientos almacenados
que retornan estructuras; el problema es debido a que EDM aún no
soporta resultados estructurados que no sean entidades. La solución
es entonces modelar entidades ficticias que sean compatibles en
número, tipo y nombre con las columnas que devuelve el
procedimiento almacenado.
En el listado 7 se muestra un fragmento de código del procedimiento
almacenado aspnetmembershipGetUserByName, que retorna los datos
del miembro con un nombre específico.
Veamos cómo efectivamente enfrentar este caso. Lo primero que se
debe hacer es definir el EntityType y el EntitySet en el SSDL
(listados 8 y 9, respectivamente). En este último se hace uso de
consultas definitorias (DefiningQuery) como vía para incluir texto
descriptivo.
De manera similar, hay que definir la contrapartida en los
conceptos. En el nivel conceptual existe la preocupación de que
estos falsos tipos no sean expuestos a los clientes de las clases
generadas, porque una consulta sobre ellos conllevaría a un error
en ejecución puesto que "False Entity Set" no es una consulta
válida. Note el uso de modificadores de visibilidad. Definamos el
EntityType y el EntitySet en el CSDL (listados 10 y 11,
respectivamente).
Ahora ya podemos especificar el mapeo entre los tipos: el del
aspnet_
trickGetUserByName del SSDL con el aspnettrickGetUserByName del
CSDL (listado 12). Luego se debe importar, apoyado en el diseñador
visual, la función aspnetmembershipGetUserByName, definiendo como
tipo de retorno la entidad aspnettrick_GetUserByName [4].
Conclusiones
En este artículo se han analizado los beneficios de trasladar el
modelo de proveedor de membresía de ASP.NET de un nivel lógico a un
nivel conceptual. Se ha presentado una implementación de proveedor
basado en EF, EdmMembershipProvider, explicándose los principios de
su funcionamiento, la flexibilidad de su comportamiento operacional
y el modelo conceptual sobre el que opera: el ASP.NET Provider Data
Model. Posteriormente, se ha discutido sobre cómo dotar a la capa
de objetos del modelo con métodos de instancia que invoquen
procedimientos almacenados importados como funciones, comenzando
con aquellos que retornan tipos escalares, a la vez que permitan
parámetros de salida, o que retornen tipos anónimos. En una próxima
entrega se espera completar las especificidades de las funciones
importadas y repasar diferentes escenarios y casos de estudio.