DNM+ Online
dotnetmania 2.0
Implementación de un Membership Provider basada en ADO.NET Entity Framework
En este artículo se analiza SqlMembershipProvider, el proveedor de membresía (pertenencia) de ASP.NET para SQL Server, y se propone una implementación funcionalmente similar a la de este proveedor, pero basada en ADO.NET Entity Framework.

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 auto­má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 en­fren­tar 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.

blog comments powered by Disqus
autores
referencias