Unity
Transcripción
Unity
Marcos de Desarrollo Diseño e implementación de aplicaciones Web con .NET Contenido Conocer y saber utilizar el contenedor Unity Aprender a registrar tipos desde código y desde archivo de configuración Saber realizar DI de constructor, método y propiedad Saber indicar el tiempo de vida de una instancia Unity. Introducción Proporciona una forma sencilla de implementar el patrón Inversión de control (Inversion of Control, IoC), y consecuentemente el patrón Inyección de Dependencias (Dependency Injection, DI) NOTA: Más información sobre estos patrones en: http://martinfowler.com/articles/injection.html Ejemplo de dependencia public void RegisterUser(String loginName, String clearPassword, UserProfileDetailsVO userProfileDetailsVO) { <...> DbConnection connection = (DbConnection) new SQLConnection(); <...> IUserProfileDAO dao = (IUserProfileDAO) new UserProfileDAO(); String encryptedPassword = Crypto.crypt(clearPassword); UserProfileVO userProfileVO = new UserProfileVO(loginName, encryptedPassword, userProfileDetailsVO); dao.Create(connection, transaction, userProfileVO); <...> } Problemas originados por las dependencias Código altamente acoplado Aislamiento de código se complica Se dificulta la realización de las pruebas Mantenimiento complejo Desconocimiento acerca de los efectos colaterales de la modificación de código Problemas originados por las dependencias: soluciones Solución 1: Delegar en factorías public void RegisterUser(String loginName, String clearPassword, UserProfileDetailsVO userProfileDetailsVO) { <...> DbDconnection connection = dbProviderFactory.CreateConnection(); <...> IUserProfileDAO dao = UserProfileDAOFactory.GetDAO(); String encryptedPassword = Crypto.crypt(clearPassword); UserProfileVO userProfileVO = new UserProfileVO(loginName, encryptedPassword, userProfileDetailsVO); dao.Create(connection, transaction, userProfileVO); <...> } Leerá de un fichero de configuración el nombre de la instancia correcta Problemas originados por las dependencias: soluciones Solución 1: Delegar en factorías Inconvenientes: o Permanece la dependencia con respecto a la propia factoría o Distribución código implica la distribución de la factoría o Externo a la lógica del negocio o Codificación de la factoría es específica Problemas originados por las dependencias: soluciones Solución 2: Usar un contenedor que permita DI Se programa con interfaces de la clase Un contenedor "inyecta" la implementación de la interfaz Problemas originados por las dependencias: soluciones Solución 2: Usar un contenedor que permita DI public void RegisterUser(String loginName, String clearPassword, UserProfileDetailsVO userProfileDetailsVO) { <...> IUserProfileDAO dao = container.Resolve<IUserProfileDAO>(); <...> } Leerá de un fichero de configuración (o se indicará al container por código) el mapeado de clases correcto Instanciación y configuración del Contenedor Contenedor almacenará las referencias a las clases o instancias que posteriormente se inyectarán Permitirá configurar la inyección de cada dependencia Configuración posible directamente a través de código o bien a través de xml Instanciación y configuración del Contenedor Es necesario añadir las siguientes referencias al proyecto: Microsoft.Practices.Unity Microsoft.Practices.Unity.Configuration System.Configuration (si no estaba añadida) Instanciación y configuración del Contenedor Configuración a través de código using MPU = Microsoft.Practices.Unity; using MPUC = Microsoft.Practices.Unity.Configuration; using SC = System.Configuration; ... /* Se instancia el contenedor */ MPU.IUnityContainer container = new MPU.UnityContainer(); // Registro de tipos container.RegisterType<IUserProfileDao, UserProfileDaoEntityFramework>(); container.RegisterType<IUserService, UserService>(); container.RegisterType<ASingleton, ObjectContext> (new ContainerControlledLifetimeManager()); ... Resolución de dependencias tratará ObjectContext como un singleton Instanciación y configuración del Contenedor Configuración a través fichero de configuración <configSections> <section name="unity“ type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Microsoft.Practices.Unity.Configuration" /> </configSections> ... <unity xmlns="http://schemas.microsoft.com/practices/2010/unity"> <!‐‐ <alias alias="aliasName" type="Class FullName, Assembly Name" /> ‐‐> <alias alias="IUserProfileDao" type="Es.Udc.DotNet.MiniPortal.Model.UserProfileDao.IUserProfileDao, MiniPortal.Model" /> <alias alias="UserProfileDaoEntityFramework" type="Es.Udc.DotNet.MiniPortal.Model.UserProfileDao.UserProfileDaoEntityFramework, MiniPortal.Model" /> <alias alias="ObjectContext" type="System.Data.Objects.ObjectContext, System.Data.Entity" /> <alias alias="singleton" type="Microsoft.Practices.Unity.ContainerControlledLifetimeManager, Microsoft.Practices.Unity" /> ... Instanciación y configuración del Contenedor Configuración a través fichero de configuración <container> <!‐‐ ************ Mappings for Bussiness Objects ************* ‐‐> <!‐‐ IUserProfileDao ‐‐> <register type="IUserProfileDao" mapTo="UserProfileDaoEntityFramework"></register> <!‐‐ IUserService ‐‐> <register type="IUserService" mapTo="UserService"></register> <!‐‐ Object Context ‐‐> <register type="ObjectContext" mapTo="ObjectContext"> <lifetime type="singleton" /> <constructor> <param name="connectionString" type="System.String"> <value value=" <...> " /> </param> </constructor> </register> </container> </unity> Instanciación y configuración del Contenedor Configuración a través fichero de configuración: configuración del contenedor using MPU = Microsoft.Practices.Unity; using MPUC = Microsoft.Practices.Unity.Configuration; using SC = System.Configuration; ... /* Se instancia el contenedor */ MPU.IUnityContainer container = new MPU.UnityContainer(); /* Se lee UnityConfigurationSection del fichero de configuración * por defecto (App.config o Web.config), y se configura el contenedor */ MPUC.UnityConfigurationSection section = (MPUC.UnityConfigurationSection)SC.ConfigurationManager.GetSection("unity"); section.Configure(container, section.Containers.Default.Name); ... ¿Cuándo se crea el contenedor? Debe crearse al inicio de la ejecución En un proyecto de consola, puede ubicarse en el "Main" En el caso de un proyecto Web, puede ubicarse en el archivo Global.asax En general, en una clase que garantice que se configura antes de que se vaya a necesitar "resolver" tipos Unity Tipos de IoC comúnmente usados en Unity Tipos de Inyección de Dependencias comúnmente usados en Unity Propiedad Constructor Inyección de propiedad namespace Es.Udc.DotNet.MiniPortal.Model.UserService { public interface IUserService { [Dependency] IUserProfileDao UserProfileDao { set; } <...> } } Dependencia se inyecta a al realizar una "resolución" de IUserService Inyección de propiedad Configuración mediante código container.RegisterType<IUserProfileDao, UserProfileDaoEntityFramework>(); container.RegisterType<IUserService, UserService>(); Configuración mediante fichero de configuración <container> <!‐‐ ************ Mappings for Bussiness Objects ************* ‐‐> <!‐‐ IUserProfileDAO ‐‐> <register type="IUserProfileDao" mapTo="UserProfileDaoEntityFramework"></register> <!‐‐ IUserService ‐‐> <register type="IUserService" mapTo="UserService"></register> </type> </container> Inyección de constructor con parámetros public class ObjectContext { private String connectionString; public String ConnectionString { get; private set; } public ObjectContext(String connectionString) { <...> } <...> } Parámetro, clase, etc. Se inyecta a través del constructor Inyección de constructor con parámetros Configuración mediante código String connectionString = <...> // Data should be read from configuration file container.Configure<InjectedMembers>() .ConfigureInjectionFor<ObjectContext>( new InjectionConstructor(connectionString)); Configuración mediante fichero de configuración <!‐‐ Object Context ‐‐> <register type="ObjectContext" mapTo="ObjectContext"> <lifetime type="singleton" /> <constructor> <param name="connectionString" type="System.String"> <value value=" <...> " /> </param> </constructor> </register> Intercepción Mediante una extensión, Unity ofrece soporte a puntos de la Programación Orientada a Aspectos Intercepción Permite interceptar la llamada a un método, analizar los contenidos de dicha llamada y o bien continuar con la misma sin modificación o bien modificar su comportamiento. Es necesario añadir las referencias : Microsoft.Practices.Unity.Interception Microsoft.Practices.Unity.Interception.Configuration Útil, por ejemplo, para la gestión de transacciones Intercepción Es necesario añadir las siguientes referencias al proyecto: Microsoft.Practices.Unity.InterceptionExtension Microsoft.Practices.Unity.InterceptionExtension.Configuration System.Configuration (si no estaba añadida) Intercepción Estructura típica de un método transaccional void TransactionalMethod(...) { ... using (TransactionScope transaction = new TransactionScope()) { ... transaction.Complete(); } } Código idéntico en todos los casos de uso transaccionales Intercepción Creación atributo Para construir un atributo de tipo "handler" propio, se debe crear una clase que derive de HandlerAttribute namespace Microsoft.Practices.Unity.InterceptionExtension { public abstract class HandlerAttribute : Attribute { ... public abstract ICallHandler CreateHandler(IUnityContainer container); } } namespace Es.Udc.DotNet.ModelUtil.Transactions { public class TransactionalAttribute : HandlerAttribute { public override ICallHandler CreateHandler(IUnityContainer container) { return new TransactionalHandler(); } } } Intercepción Creación interceptor (handler) public class TransactionalHandler : ICallHandler { public int Order { get; set; } public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext) { IMethodReturn result; using (TransactionScope transaction = new TransactionScope()) { result = getNext()(input, getNext); Creación entorno transaccional transaction.Complete(); } return result; } } } Llamada al método mediante delegado Intercepción Configuración para el uso de la intercepción en Unity (código) container.RegisterType<IUserService, UserService>(); container.AddNewExtension<Interception>(); container.Configure<Interception>(). SetDefaultInterceptorFor<IUserService>( new InterfaceInterceptor()); Intercepción Configuración para el uso de la intercepción en Unity (fichero de configuración) <unity xmlns="http://schemas.microsoft.com/practices/2010/unity"> <sectionExtension type="Microsoft.Practices.Unity.InterceptionExtension.Configuration.InterceptionConfigurationEx tension, Microsoft.Practices.Unity.Interception.Configuration" /> <container> <...> <!‐‐ Interception schema for transactional methods ‐‐> <extension type="Interception" /> <interceptors> <interceptor type="InterfaceInterceptor" > <default type="IUserService"/> </interceptor> </interceptors> </container> </unity> Interfaz que será interceptada Intercepción Ejemplo de uso namespace Es.Udc.DotNet.MiniPortal.Model.UserService { public interface IUserService { [Dependency] IUserProfileDao UserProfileDao { set; } /// <summary> /// Registers a new user. /// </summary> /// <param name="loginName">Name of the login.</param> /// <param name="clearPassword">The clear password.</param> /// <param name="userProfileDetails">The user profile details.</param> /// <exception cref="DuplicateInstanceException"/> [Transactional] long RegisterUser(String loginName, String clearPassword, UserProfileDetails userProfileDetails); Bibliografía Recomendada: Unity Application Block 2,0 – Abril 2010. http://msdn.microsoft.com/en-us/library/ff663144.aspx Unity 2.X Printable Documentation http://unity.codeplex.com/releases/view/31277