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

Documentos relacionados