Page | 1
Hands-On Lab
LAB 02 - Implementando desacoplamiento IoC DI con UNITY
Objetivos
El objetivo de este Lab es analizar de forma práctica los diferentes puntos de desarrollo relacionados con
el desacoplamiento de componentes mediante Inyección de Dependencias con Microsoft UNITY.
La mayoría de los siguientes LABs están relacionados con la implementación de la aplicación ejemplo
NLAYERAPP (http://microsoftnlayerapp.codeplex.com/ ).
Introducción a Unity
El Application Block denominado Unity (implementado por Microsoft Patterns & Practices), es un
contenedor de inyección de dependencias extensible y ligero (Unity no es un gran framework pesado).
Soporta inyección en el constructor, inyección de propiedades, inyección en llamadas a métodos y
contenedores anidados.
Básicamente, Unity es un contenedor donde podemos registrar tipos (clases, interfaces) y también
mapeos entre dichos tipos (como un mapeo de un interfaz hacia una clase) y además el contenedor de
Unity puede instanciar bajo demanda los tipos concretos requeridos.
Page | 2
Unity está disponible como un download público desde el site de Microsoft (es gratuito) y también está
incluido en la Enterprise Library 4.0/5.0 y en PRISM (Composite Applications Framework), los cuales
hacen uso extensivo de Unity.
Para hacer uso de Unity, normalmente registramos tipos y mapeos en un contenedor de forma que
especificamos las dependencias entre interfaces, clases base y tipos concretos de objetos. Podemos
definir estos registros y mapeos directamente por código fuente o bien, como normalmente se hará en
una aplicación real, mediante XML de ficheros de configuración. También se puede especificar inyección
de objetos en nuestras propias clases haciendo uso de atributos que indican las propiedades y métodos
que requieren inyección de objetos dependientes, así como los objetos especificados en los paráme tros
del constructor de una clase, que se inyectan automáticamente.
Unity proporciona las siguientes ventajas al desarrollo de aplicaciones:
- Soporta abstracción de requerimientos; esto permite a los desarrolladores el especificar dependencias en tiempo de ejecución o en configuración y simplifica la gestión de aspectos horizontales (crosscutting concerns), como puede ser el realizar pruebas unitarias contra mocks y
stubs, o contra los objetos reales de la aplicación.
- Proporciona una creación de objetos simplificada, especialmente con estructuras de objetos jerárquicos con dependencias, lo cual simplifica el código de la aplicación.
- Aumenta la flexibilidad al trasladar la configuración de los componentes al contenedor IoC.
Proporciona una capacidad de localización de servicios; esto permite a los clientes el guardar o cachear
el contenedor. Es por ejemplo especialmente útil en aplicaciones web ASP.NET donde los
desarrolladores pueden persistir el contenedor en la sesión o aplicación ASP.NET.
Page | 3
Ejercicio 1: Registro de Tipos en
contenedor de UNITY
Como ejemplo de uso de los métodos RegisterType y Resolve, a continuación realizamos un registro de
un mapeo de un interfaz llamado ICustomerService y especificamos que el contenedor debe devolver
una instancia de la clase CustomerService (la cual tendrá implementado el interfaz ICustomerService).
C#
//Registro de tipos en Contenedor de UNITY
IUnityContainer container = new UnityContainer();
container.RegisterType<ICustomerManagementService, CustomerManagementService>();
...
...
//Resolución de tipo a partir de Interfaz
ICustomerManagementService customerSrv = container.Resolve<IICustomerManagementService>();
Como posibilidad adicional, en la versión final de aplicación, el registro de clases, interfaces y mapeos en
el contenedor, se puede realizar de forma declarativa en el XML de los ficheros de configuración,
quedando completamente desacoplado. Sin embargo, tal y como se muestra en las líneas de código
anteriores, durante el desarrollo probamente es más cómodo realizarlo de forma ‘Hard-coded’, pues así
los errores tipográficos se detectarán en tiempo de compilación en lugar de en tiempo de ejecución
(como pasa con el XML).
Con respecto al código anterior, la línea que siempre estará en el código de la aplicación, sería la que
instancia propiamente el objeto resolviendo la clase que debe utilizarse mediante el contenedor, es
decir, la llamada al método Resolve() (Independientemente de si se realiza el registro de tipos por XML
o ‘Hard-Coded’).
Page | 4
Paso 1 – Analizar el Registro de Tipos (Clases e Interfaces) en la aplicación NLAYERSAMPLEAPP.
1. Abrir el fichero ‘IoCUnityContainer.cs’ del proyecto Infrastructure.CrossCutting.IoC:
2. Desplegar la sección ‘Private Methods’ dentro de dicho fichero:
3. Analizar los distintos registros de tipos realizados en el método
‘ConfigureRootContainer(IUnityContainer container)’:
Page | 5
4. El LifeTimeManager es el tipo de instanciación de clase que se realizará. En la mayoría de los
casos (Repositorios, Servicios, etc.) y por defecto si no se le especifica el LifeTimeManager a
Unity, la forma de instanciación normal será TransientLifetimeManager, es decir, un objeto
único por cada referencia. Pero en algunos casos puede interesarnos otros tipos de
instanciación como del estilo a ‘singleton’, compartidos por contextos de ejecución, etc.
Podemos tener los siguientes tipos de ‘vida’ de objetos instanciados:
o TransientLifetimeManager: Este LifeTimeManager asegura que las instancias de objetos
se crean nuevas cada vez (no reutilización de objetos entre diferentes llamadas).
o PerResolveLifetimeManager (Nuevo en Unity 2.0): Asegura que las instancias de un
tipo de objeto se reutilizan a lo largo de todo un grafo de dependencias de objetos
creados por Unity.
o ContainerControlledLifetimeManager: Implementa un comportamiento ‘singleton’ para
cada objeto creado. El contenedor si mantiene referencias a los objetos creados y se
hace un dispose automático cuando se hace un dispose del contenedor.
o ExternallyControlledLifetimeManager: Este LifeTimeManager mantiene una referencia
débil a su instancia manejada. Es decir, implementa un comportamiento ‘singleton’ pero
el contenedor no mantiene una referencia al objeto que deberá ser eliminado
(disposed) cuando se salga del ámbito.
o PerThreadLifetimeManager: Reutiliza una única instancia de cada clase por thread que
accede al grafo de objetos de dependencias creado por Unity.
Interfaz por el que se ‘preguntará’ Clase de Implementación asociada
Tipo de ‘LifeTimeManager’
Page | 6
o HierarchicalifetimeManager (Nuevo en Unity 2.0): Implementa un comportamiento
singleton, pero los contenedores hijos no comparten instancias con los contenedores
padres.
o PerExecutionContextLifetimeManager (Custom de NLayerSampleApp): Este
LifeTimeManager es ‘custom’ creado en la aplicación ejemplo NLayerApp. Su
comportamiento es parecido a ‘PerResolveLifetimeManager’, pero en lugar de ser
compartidas las instancias entre el grafo de objetos de dependencias a partir del primer
resolve(), cuando hacemos uso de ‘PerExecutionContextLifetimeManager’, los objetos
se comparten por todo el contexto de ejecución inicial, por ejemplo, para todas las
operaciones de una petición WCF (a partir de un WebMethod) o a partir de una petición
ASP.NET o incluso a partir de una ejecución de un método de Pruebas Unitarias.
En concreto nos basamos en los siguientes contextos:
OperationContext de WCF
HttpContext de ASP.NET
CallContext para UnitTesting, WinForms, WPF etc.
5. Como alterativa, podemos realizar el registro de tipos de una forma más desacoplada,
mediante configuración XML. Analizar el siguiente XML:
(NOTA: El nombre de los tipos no coincide pues este XML procede de una versión antigua de
NLayerSampleApp).
<?xml version="1.0" encoding="utf-8" ?> <unity> <typeAliases> <!-- Lifetime manager types --> <typeAlias alias="singleton" type="Microsoft.Practices.Unity.ContainerControlledLifetimeManager, Microsoft.Practices.Unity" /> <typeAlias alias="perThread" type="Microsoft.Practices.Unity.PerThreadLifetimeManager, Microsoft.Practices.Unity" /> <typeAlias alias="external" type="Microsoft.Practices.Unity.ExternallyControlledLifetimeManager, Microsoft.Practices.Unity" /> <typeAlias alias="perCall" type="Microsoft.Practices.Unity.TransientLifetimeManager, Microsoft.Practices.Unity"/> <!-- EF Context & Fake--> <typeAlias alias="IMainModuleContext" type="Microsoft.Samples.NLayerApp.Infrastructure.Data.MainModule.Context.IMainModuleContext, Microsoft.Samples.NLayerApp.Infrastructure.Data.MainModule " /> <typeAlias alias="MainModuleContext" type="Microsoft.Samples.NLayerApp.Infrastructure.Data.MainModule.Context.MainModuleContext, Microsoft.Samples.NLayerApp.Infrastructure.Data.MainModule" /> <typeAlias alias="MainModuleFakeContext" type="Microsoft.Samples.NLayerApp.Infrastructure.Data.MainModule.Mock.MainModuleFakeContext, Microsoft.Samples.NLayerApp.Infrastructure.Data.MainModule.Mock" /> <!-- Repositories--> ... ...
Page | 7
<typeAlias alias="ICustomerRepository" type="Microsoft.Samples.NLayerApp.Domain.MainModule.Contracts.ICustomerRepository, Microsoft.Samples.NLayerApp.Domain.MainModule" /> <typeAlias alias="CustomerRepository" type="Microsoft.Samples.NLayerApp.Infrastructure.Data.MainModule.Repositories.CustomerRepository, Microsoft.Samples.NLayerApp.Infrastructure.Data.MainModule" /> ... ... <!--Domain Services--> <typeAlias alias="IBankTransfersService" type="Microsoft.Samples.NLayerApp.Domain.MainModule.Services.IBankTransfersService, Microsoft.Samples.NLayerApp.Domain.MainModule" /> <typeAlias alias="BankTransfersService" type="Microsoft.Samples.NLayerApp.Domain.MainModule.Services.BankTransfersService, Microsoft.Samples.NLayerApp.Domain.MainModule" /> ... ... </typeAliases> <!-- UNITY CONTAINERS --> <containers> <container name="RootContext"> <types> <!-- MAPPING from Interfaces to Classes --> <type type="ICustomerRepository" mapTo="CustomerRepository"> <lifetime type="perCall"/> </type> ... ... <type type="IBankTransfersService" mapTo="BankTransfersService"> <lifetime type="perCall"/> </type> ... ... </types> </container> <container name="RealAppContext"> <types> <type type="IMainModuleContext" mapTo="MainModuleContext"> <lifetime type="perCall" /> </type> </types> </container> <container name="FakeAppContext"> <types> <type type="IMainModuleContext" mapTo="MainModuleFakeContext"> <lifetime type="perCall" /> </type> </types> </container> </containers> </unity>
Sin embargo, en tiempo de desarrollo puede ser más farragoso, pues si nos confundimos en
la escritura de tipos dentro del XML, los errores los obtendremos en tiempo de ejecución en
lugar de en tiempo de compilación.
6. En nuestro caso (NLAYERAPP), para abstraernos de la implementación específica de UNITY,
estamos utilizando un Interfaz nuestro llamado ‘IContainer’. De esta forma, al estar
trabajando mediante abstracciones con el propio contenedor IoC, podríamos llegar a
sustituir en el futuro a UNITY por otro contenedor IoC, de una forma más sencilla.
Analizar el interfaz IContainer dentro del fichero IContainer.cs.
Page | 8
Page | 9
Paso 2 – Haciendo uso de Jerarquía de Contenedores UNITY para cambio dinámico a uso de objetos
‘Fake’.
1. Registro de tipos en jerarquía de varios contenedores: Para poder hacer mocking de forma
dinámica como vimos en los Labs anterioes cambiando simplemente una clave de
configuración, en NLayerSampleApp hacemos uso de diferentes contenedores de Unity, con
la siguiente jerarquía:
Ver en el fichero IoCUnityContainers que en el contenedor ‘RealAppContext’ tenemos
registrado los tipos del contexto real de Entity Framework y en el contenedor
‘FakeAppContext’ registramos los tipos que hacen mocking del contexto de EF. Lógicamente
en ambos contenedores, los interfaces son los mismos, pues cuando se pide un objeto para
un interfaz dado, dicho interfaz estará mapeado de forma diferente en cada uno de los dos
contenedores Unity:
Mismo Interfaz por el que se ‘preguntará’
Mismo Interfaz por el que se ‘preguntará’
Mapeo a clase de UoW de Entity Fraework
Mapeo a clase Fake de UoW
Page | 10
De esta forma, cuando la propiedad de configuración AppSettings["defaultIoCContainer"]
es ‘RealAppContext’, haremos uso del contenedor RealAppContext añadiendo/solapando sus
tipos al de RootContainer. Y el mismo efecto cuando la propiedad es ‘RealAppContext’, pero
entonces solaparemos los tipos del contenedor ‘FakeAppContainer’ .
Recordamos la propiedad de configuración utilizada en el LAB 01:
2. La implementación de los UoW y contextos de EF ó Fake, se analizarán en los Labs de la Capa
de Infraestructura de Persistencia y Acceso a datos.
OTRAS POSIBILIDADES DEL SISTEMA DINAMICO DE INYECCION DE DEPENDENCIAS
Hay que tener en cuenta que este sistema dinámico de instanciación de unos u otros objetos como
implementación de abstracciones (interfaces), puede ser útil no solo para hacer mocking de Entity
Framework. Puede utilizarse para otros sistemas de mocking, por ejemplo de acceso o no a un sistema
backend o legacy (ERP, etc.) o bien a un fake que simule dicho acceso, etc. Las posibilidades están ahora
abiertas a la imaginación.
Page | 11
Ejercicio 2: Inyeccio n de Dependencias
con Unity en la Aplicacio n ejemplo de la
Arquitectura
La inyección de dependencias que hace uso NLayerSampleApp es mayoritariamente basada en
constructores, es decir, especificando las dependencias de los Servicios en los constructores.
Paso 1 – Analizar el Servicio ‘BankingManagementService’ de Application Layer.
1. Abrir el fichero ‘BankingManagementService.cs’ del proyecto Application.MainModule:
public class BankingManagementService : IBankingManagementService { IBankTransferDomainService _bankTransferDomainService; IBankAccountRepository _bankAccountRepository; public BankingManagementService(IBankTransferDomainService bankTransferDomainService, IBankAccountRepository bankAccountRepository) { ... _bankTransferDomainService = bankTransferDomainService; _bankAccountRepository = bankAccountRepository; } public void PerformTransfer(string fromAccountNumber, string toAccountNumber, decimal amount) { //Process: 1º Start Transaction // 2º Get Accounts objects from Repositories // 3º Call PerformTransfer method in Domain Service // 4º If no exceptions, save changes using repositories and Commit Transaction //Create a transaction context for this operation TransactionOptions txSettings = new TransactionOptions() { Timeout = TransactionManager.DefaultTimeout, IsolationLevel = IsolationLevel.Serializable // review this option }; using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required, txSettings)) { //Get Unit of Work IUnitOfWork unitOfWork = _bankAccountRepository.UnitOfWork as IUnitOfWork; //Create Queries' Specifications BankAccountNumberSpecification originalAccountQuerySpec = new BankAccountNumberSpecification(fromAccountNumber); BankAccountNumberSpecification destinationAccountQuerySpec = new BankAccountNumberSpecification(toAccountNumber); //Query Repositories to get accounts BankAccount originAccount = _bankAccountRepository.GetBySpec(originalAccountQuerySpec as ISpecification<BankAccount>)
Dependencias especificadas en Constructor, un Repositorio y un Servicio de Dominio
Page | 12
.SingleOrDefault(); BankAccount destinationAccount = _bankAccountRepository.GetBySpec(destinationAccountQuerySpec as ISpecification<BankAccount>) .SingleOrDefault(); if (originAccount == null || destinationAccount == null) throw new InvalidOperationException(Resources.Messages.exception_InvalidAccountsForTransfer); ////Start tracking STE entities (Self Tracking Entities) originAccount.StartTrackingAll(); destinationAccount.StartTrackingAll(); //Excute Domain Logic for the Transfer (In Domain Service) _bankTransferDomainService.PerformTransfer(originAccount, destinationAccount, amount); //Save changes and commit operations. //This opeation is problematic with concurrency. //"balance" property in bankAccount is configured //to FIXED in "WHERE concurrency checked predicates" _bankAccountRepository.Modify(originAccount); _bankAccountRepository.Modify(destinationAccount); //Complete changes in this Unit of Work unitOfWork.CommitAndRefreshChanges(); //Commit the transaction scope.Complete(); } }
2. Es importante destacar que, como se puede observar, no hemos hecho ningún ‘new’ explícito de
clases de Ropositorios (como BankAccountRepository) o de Servicios. Es el contenedor de Unity el que
automáticamente creará el objeto de BankAccountRepository y BankTransferDomainService
proporcionándolos como parámetro de entrada a nuestro constructor. Esa es precisamente la inyección
de dependencias en el constructor.
Paso 2 – Inicio de resolución de tipos y creación de grafo de objetos.
En tiempo de ejecución, el código de instanciación de BankingManagementService se realizás utilizando
el método Resolve() del contenedor de Unity, el cual origina la instanciación generada por el framework
de Unity de la clase BankAccountRepository dentro del ámbito de la clase
BankingManagementService.
1. Abrir el fichero MainModuleService.BankingManagement.cs del proyecto WCF de Servicios
Distribuidos (DistributedServices.MainModule) desde donde se arranca la creación de grafos de objetos
creados por Unity.
Este tipo de código es el que implementaremos normalmente en la capa de primer nivel que consume
objetos de Capa de Aplicación y del Dominio, es decir, normalmente en la capa de Servicios Distribuidos
(WCF) como este caso o incluso capa de presentación web ejecutándose en el mismo servidor de
aplicaciones (ASP.NET):
Page | 13
public partial class MainModuleService //Parte de Servicio WCF { ... ... public void PerformBankTransfer(TransferInformation transferInformation) { try { IBankingManagementService bankingManagement = IoCFactory.Instance.CurrentContainer.Resolve<IBankingManagementService>(); bankingManagement.PerformTransfer(transferInformation.OriginAccountNumber, transferInformation.DestinationAccountNumber, transferInformation.Amount); } catch(…) ... } }
Como se puede observar en el uso de Resolve<>(), en ningún momento hemos creado nosotros una
instancia de las clases de los tipos que dependemos (IBankTransferDomainService y
IBankAccountRepository) y por lo tanto nosotros no hemos pasado explícitamente dichos objetos al
constructor de nuestra clase BankingManagementService. Y sin embargo, cuando se instancie la clase
de servicio (BankingManagementService), automáticamente se nos habrá proporcionado en el
constructor las instancias de las dependencias (IBankTransferDomainService y
IBankAccountRepository). Eso lo habrá hecho precisamente el contenedor de Unity al detectar la
dependencia. Esta es la inyección de dependencia y nos proporciona la flexibilidad de poder cambiar la
dependencia en tiempo de configuración y/o ejecución. Por ejemplo, si en el fichero XML de
configuración hemos especificado que se creen objetos Mock (simulación) en lugar de objetos reales de
acceso a datos (Repository), la instanciación de las clases de implementación sería diferente.
2. Observar las definiciones internas de interfaces y clases que se utilizan en el código anterior, como la
definición de la clase de Servicio de AppLayer ‘BankingManagementService’ y el resto de dependencias en
su grafo.
Interfaz de Servicio de Application Layer, a resolver. A partir de aquí se crea el grafo de objetos
Page | 14
Ejercicio 3 (OPCIONAL): Inyeccio n de
Factorí as (Injection-Factory)
La inyección de factorías es una característica nueva en UNITY 2.0. En la versión actual de
NLayerSampleApp no lo estamos utilizando porque no lo hemos necesitado, pero puede ser algo muy
útil cuando la creación de un objeto debe ser realizada por una Factory que debe tener en cuenta
aspectos externos a dicho objeto y no nos es suficiente con apoyarnos solamente en el constructor.
Así pues, InjectionFactory es un mecanismo que permite indicarle a Unity un método (una
Func<IUnityContainer, object>, usualmente una lambda expresion) a usar cada vez que deba resolver un
objeto especificado. Como decíamos antes, permite que Unity use nuestras propias factorías.
Si tenemos la siguiente factoría para crear objetos del tipo IComplexService:
interface IComplexServiceFactory { IComplexService GetNewInstance(); } class ComplexServiceFactory : IComplexServiceFactory { public IComplexService GetNewInstance() { IComplexService complexService = new ComplexService(); // Do required actions by our factory... complexService.Initialize("dato 3", "dato 4", "dato 5"); return new complexService(); } }
A la hora de hacer uso con UNITY para resolver el tipo y crear el grafo de objetos, primero tenemos que
realizar el siguiente registro:
//Register Complex-Service using a Factory container.RegisterType<IComplexServiceFactory, ComplexServiceFactory>(new ContainerControlledLifetimeManager()); container.RegisterType<IComplexService>(new InjectionFactory(x => x.Resolve<IComplexServiceFactory>().GetNewInstance()));
Page | 15
En la última línea le indicamos a Unity que cada vez que alguien haga un Resolve<IComplexService>
ejecute el delegate que le indicamos, en este caso que obtenga una IComplexServiceFactory y llame al
método GetNewInstance().
El Resolve() se haría de forma normal, pues el uso de la factoría es interno:
IComplexService complexService = IoCFactory.Instance.CurrentContainer.Resolve<IComplexService>();
NOTA AL MARGEN:
En el caso de necesitar parámetros de entrada en el constructor de nuestra clase a resolver, esto
también se puede realizar con UNITY 2.0, de forma similar a la siguiente:
IComplexService complexService = IoCFactory.Instance.CurrentContainer.Resolve<IComplexService>( new ParameterOverride("param1", 3));
Page | 16
Ejercicio 4 (OPCIONAL): AOP e
intercepcio n de llamadas con UNITY
Uno de los usos fundamentales de AOP (Aspect Oriented Programming) consiste en eliminar
‘aparentemente’ código explícito utilizado para implementar aspectos 'Cross-Cutting'. Estos aspectos
normalmente suelen ser cosas como Logging, Validaciones, Gestión de Excepciones, etc. Esto se realiza
extrayendo dichos aspectos a un punto central reutilizable por todas las capas, de forma que los
desarrolladores puedan aplicarlo de una forma más transparente (aplicando aspectos o 'marcas' en
forma de atributos). De esta forma, el desarrollador puede focalizar más y de una forma mas clara en la
lógica del Dominio y de su aplicación concreta.
Hay diferentes formas de ‘inyectar’ estas acciones de una forma transparente. Es lo que en AOP se
denomina Aspect Waiver. Una forma puede ser generando código (C# o incluso IL) en tiempo de
compilación. Otra forma puede ser realizando intercepción de llamadas entre objetos e inyectando
ejecución de código entre dichas intercepciones de llamadas.
El mecanismo de intercepción captura la llamada realizada a un objeto (en tiempo de ejecución) y
proporciona la implementación completa de dicho objeto. Unity utiliza la clase 'Interceptor' para
especificar el mecanismo de intercepción a utilizar y como ocurre la intercepción. Por otro lado, utiliza la
clase InterceptionBehavior para describir qué hacer cuando un objeto se ha interceptado.
La intercepción de Unity está diseñada para ejecutar sus comportamientos sobre todo el objeto
completo y todos sus métodos.
Los Interceptadores juegan un rol solo en tiempo de creación del proxy (o tipo derivado). Una vez que el
proxy o tipo derivado se ha creado, el interceptador habrá proporcionado todos los componentes
requeridos por el objeto interceptado y lo habrá incorporado al procesamiento del proxy.
Page | 17
NOTA: Unity proporciona intercepción de llamadas a nivel de instancia y de tipo. Además esta
intercepción de llamadas puede realizarse con objetos contenidos en el contenedor IoC de Unity o bien
haciendo uso del API 'standalone' de UNITY (static intercept class) sin estar haciendo uso del contenedor
DI/IoC de Unity.
En el caso de hacer uso de Intercepción de llamadas cuando utilizamos el contenedor IoC de Unity,
deberemos seguir los siguientes pasos:
3. Añadir referencia al assembly de intercepción en UNITY:
4. Después de la creación del contenedor a utilizar, deberemos extenderlo para que soporte la
intercepción de llamadas. Así por ejemplo, dentro del constructor de nuestro
IoCUnityContainer, extenderíamos el código de la siguiente forma:
//Add this ‘using’
using Microsoft.Practices.Unity.InterceptionExtension;
...
...
public IoCUnityContainer()
{
...
//Create root container
IUnityContainer rootContainer = new UnityContainer();
//Configure container to support Interception
rootContainer.AddNewExtension<Interception>();
...
}
Page | 18
5. Configurar al contenedor de forma que realice intercepción cuando resuelva la clase donde
queremos realizar intercepción de llamadas. En este caso, por ejemplo, para nuestra clase
del Servicio del dominio 'BankTransferDomainService'.
Para ello, debemos indicar el mecanismo de intercepción a utilizar con el objeto Interceptor
y el comportamiento de intercepción a utilizar con un objeto InterceptionBehavior. Modificar
nuestro código, en el fichero ‘IoCUnityContainer.cs’ (donde registramos los tipos y
especificamos los mapeos), de la siguiente forma:
//Register domain services mappings
//(CDLTLL) Extending with Calls Interception
container.RegisterType<IBankTransferDomainService, BankTransferDomainService>(
new TransientLifetimeManager(),
new Interceptor<InterfaceInterceptor>(),
new InterceptionBehavior(new AuditBehavior(new
TraceSource("interception-audit-source"))));
6. Lógicamente, para que este código nos funcione, primero deberemos de haber definido
tanto en TraceSource "interception-audit" en el Web.config, como nuestra clase de ‘custom
behavior’ que hemos llamado ‘AuditBehavior’.
7. En el Web.config añadiríamos lo siguiente, para definir la fuente a utilizar:
<sources> ...
<source name="interception-audit-source" switchValue="All"> <listeners> <add name="interception-audit-listener"/> </listeners> </source> </sources> ...
...
<sharedListeners> ... ... <add name="interception-audit-listener" initializeData="interception-audit.log" traceOutputOptions="DateTime" type="System.Diagnostics.TextWriterTraceListener, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"/> </sharedListeners>
8. Y añadiríamos la siguiente clase de Behavior al proyecto de UNITY (Puesto que un Behaviour
es exclusivamente para Intercepción de llamadas en UNITY):
Page | 19
using System; using System.Collections.Generic; using System.Diagnostics; using Microsoft.Practices.Unity.InterceptionExtension; namespace Microsoft.Samples.NLayerApp.Infrastructure.CrossCutting.IoC.Unity { class AuditBehavior : IInterceptionBehavior, IDisposable { private TraceSource source; public AuditBehavior(TraceSource source) { if (source == null) throw new ArgumentNullException("source"); this.source = source; } public IEnumerable<Type> GetRequiredInterfaces() { return Type.EmptyTypes; } public IMethodReturn Invoke(IMethodInvocation input, GetNextInterceptionBehaviorDelegate getNext) { this.source.TraceInformation( "Invoking {0}", input.MethodBase.ToString()); IMethodReturn methodReturn = getNext().Invoke(input, getNext); if (methodReturn.Exception == null) { this.source.TraceInformation( "Successfully finished {0}", input.MethodBase.ToString()); } else { this.source.TraceInformation( "Finished {0} with exception {1}: {2}", input.MethodBase.ToString(), methodReturn.Exception.GetType().Name, methodReturn.Exception.Message); } this.source.Flush(); return methodReturn; } public bool WillExecute {
Page | 20
get { return true; } } public void Dispose() { this.source.Close(); } } }
8. También podríamos implementar todo este sistema de Intercepción de llamadas mediante
XML, extendiendo el XML de configuración de UNITY. Sin embargo, pasa lo mismo que
advertimos anteriormente. Si se comente un error tipográfico, el error se detectará en
tiempo de ejecución en lugar de en tiempo de compilación, lo cual es mucho peor. En
versiones finalizadas de la aplicación, si es una opción viable.
9. También se puede hacer uso de atributos e inyección de POLICIES para aspectos más
avanzados de intercepción de llamadas.
Por ejemplo, en el método del Dominio ‘’ de la clase ‘’, podríamos aplicar un atributo
diciendo que para ese método aplique la auditoría, y queiens no tengan dicho atributo, no
apliquen la auditoría (o la lógica que más conveniente se vea). Por ejemplo:
public class BankTransferDomainService : IBankTransferDomainService { [SetAuditSystem("interception-audit-source")] public void PerformTransfer(BankAccount originAccount, BankAccount destinationAccount, decimal amount) {
...
...
Este sistema de atributos y políticas no explicamos aquí como se implementaría y lo dejamos
a la curiosidad del lector estudiando los siguientes enlaces de Atributos y Políticas de UNITY:
http://207.46.16.251/en-us/library/ff660860(PandP.20).aspx
http://207.46.16.248/en-us/library/ff660915(PandP.20).aspx
http://blog.samstephens.co.nz/2010-11-15/policy-injection-attributes-preempt-calls-
functioning-systems/
Page | 21
APENDICE 1: Unity vs. MEF
MEF (Microsoft Extensibility Framework) no es un ‘Full IoC container’, aun cuando utiliza
conceptos IoC. Es un rfamework orientado a dar extensibilidad.
MEF focaliza en extensibilidad de aplicaciones con ‘descubrimiento de componentes’ y
‘composición’.
Unity si es un ‘IoC container’ tradicional, es su principal propósito.
Hasta la fecha (Febrero 2011), el posicionamiento de ambas tecnologías es el anterior. Y la
recomendación para realizar Inyección de Dependencias entre componentes de una Arquitectura N -
Layer es más recomendable hacerlo con Unity que con MEF. Unity es más potente que MEF en muchos
aspectos IoC MEF, y por el contrario, MEF está más indicado para extensibilidad de aplicaciones
(especialmente en capa de presentación) a realizar incluso por terceras partes que no son quienes han
creado la aplicación.
En futuras versiones de MEF si es posible que vaya realizando un crecimiento y ‘absorción’ de
capacidades de Unity. Las capacidades de MEF serán mucho mayores en el futuro. Pero esto es un
futurible, la realidad actual es lo especificado en los anteriores puntos.
Blog Posts interesantes al respecto:
http://gorskib.wordpress.com/2010/12/04/is-mef-a-next-dependency-injection-container-%E2%80%93-
my-vote-no/
http://stackoverflow.com/questions/293051/is-mef-a-dependency-injection-framework
Top Related