Pues bien, comentaba hace poco que una de las sorpresas que acompañaba a la nueva versión de Unity, el Contenedor de Inversión de Control creado por el equipo de Patterns & Practices de Microsoft, es precisamente la posibilidad de usar convenciones a la hora de registrar asociaciones entre interfaces y clases de nuestro sistema, lo cual nos vendrá de fábula en muchas ocasiones.
Imaginad, por ejemplo, el siguiente código para Unity, muy habitual en sistemas que usan cualquier tipo de contenedor IoC para aplicar inyección de dependencias:
container.RegisterType<IProductServices, ProductServices>(Como habréis observado, en este caso el registro de interfaces y clases tiene un patrón común: simplemente mapeamos los interfaces con nombre
new PerRequestLifetimeManager());
container.RegisterType<IInvoiceServices, InvoiceServices>(
new PerRequestLifetimeManager());
container.RegisterType<ICustomerServices, CustomerServices>
(new PerRequestLifetimeManager());
container.RegisterType<IProductRepository, ProductRepository>(
new PerRequestLifetimeManager());
container.RegisterType<IInvoiceRepository, InvoiceRepository>(
new PerRequestLifetimeManager());
container.RegisterType<ICustomerRepository, CustomerRepository>(
new PerRequestLifetimeManager());
container.RegisterType<IUnitOfWork, UnitOfWork>(
new PerRequestLifetimeManager());
container.RegisterType<ILogger, Logger>(
new PerRequestLifetimeManager());
container.RegisterType<IFileStorage, FileStorage>(
new PerRequestLifetimeManager());
// etc...
ISomething
a su implementación concreta en la clase Something
, una y otra vez, para todas las dependencias de nuestro sistema. Esto, además de ser bastante tedioso y repetitivo, es muy propenso a fallos, pues es normal olvidarse de registrar nuevas clases e interfaces conforme se van incluyendo en el proyecto.Pues bien, esto es un buen ejemplo de cómo las convenciones pueden ayudarnos bastante. Fijaos que el código anterior refleja intrínsecamente que existe una convención de nombrado de componentes en nuestro proyecto: todas las interfaces “ILoquesea” serán mapeadas a las clases “Loquesea” que lo implementen.
Usando Unity 3, todo ese código podría quedar de la siguiente forma:
container.RegisterTypes(Y lo mejor es que no necesitaremos venir al registro a introducir las nuevas interfaces y clases que vayamos añadiendo a nuestro proyecto: si siguen la convención de nombrado serán registradas automáticamente.
AllClasses.FromAssemblies(typeof(MvcApplication).Assembly),
WithMappings.FromMatchingInterface,
WithName.Default,
WithLifetime.Custom<PerRequestLifetimeManager>
);
Básicamente, el método
RegisterTypes()
acepta los siguientes parámetros:- una colección de los tipos de datos a mapear, en forma de
IEnumerable<Type>
. En el ejemplo anterior, usamos la claseAllClasses
, provista también por Unity, para obtener los tipos cargados en el ensamblado de la aplicación mediante su métodoFromAssemblies()
, pero, por supuesto, podríamos incluir aquí otros ensamblados separándolos por comas, o incluso indicar que queremos escanear todos los ensamblados cargados usando el métodoFromLoadedAssemblies().
- una función lambda o delegado que recibirá cada uno de los tipos anteriores y retornará los interfaces a asociar a dicho tipo en el registro de Unity. La clase estática
WithMappings
proporciona varios métodos que retornan este delegado “precocinado”: FromMatchingInterface,
el usado en el ejemplo anterior, que retorna todos los interfaces implementados por el tipo suministrado, y cuyo nombre corresponde al patrón “I”+nombre de la clase.FromAllInterfaces
, que retornará todos los interfaces que implementen el tipo suministrado.FromAllInterfacesInSameAssembly
, que, como su nombre indica, es idéntico al anterior, pero referido exclusivamente a los interfaces definidos en el mismo ensamblado que la clase que lo implementa.
- una lambda que permite especificar el nombre con el que será almacenada la asociación entre interfaz y clase en el registro de Unity. En el ejemplo podemos ver que la clase
WithName
ofrece algunos métodos válidos, comoDefault
(que establece anull
el nombre), oTypeName
, que lo obtendrá del nombre del tipo del componente. En la mayoría de los casos, pasarnull
oWithName.Default
bastará, pues este nombre se usa exclusivamente cuando se desea resolver un tipo haciendo referencia expresa a su asociación en el registro, lo cual no es muy frecuente.
- otra lambda, que será invocada para cada uno de los tipos de datos pasados en el primer parámetro y que retornará el
LifetimeManager
apropiado para cada uno. La clase estáticaWithLifeTime
ofrece varios métodos de utilidad ya preparados, comoHierarchical
,PerThread
,Transient
, oCustom<T>
, siendoT
elLifetimeManager
que nos interese. En este caso, observad que otorgábamos a todos los objetos creados desde el contenedor una vida “PerRequest”, es decir, que serán liberados cuando termine el proceso de la petición, que es lo habitual en desarrollos para la web.
MyProject.Application
de cualquier ensamblado, podríamos hacerlo así, retocando el primer parámetro:container.RegisterTypes(O, por ejemplo, si quisiéramos usar un LifetimeManager per request sólo para las clases pertenecientes a dicho espacio de nombres:
AllClasses.FromLoadedAssemblies()
.Where(a=>a.Namespace == "MyProject.Application"),
WithMappings.FromMatchingInterface,
WithName.Default,
WithLifetime.Custom<PerRequestLifetimeManager>
);
container.RegisterTypes(O, algo simplificado, si quisiéramos cambiar la convención de nombrado de interfaces de
AllClasses.FromAssemblies(typeof (MvcApplication).Assembly),
WithMappings.FromMatchingInterface,
WithName.Default,
type => type.Namespace == "MyProject.Application" ?
(LifetimeManager)new PerRequestLifetimeManager() : (LifetimeManager)null
);
IComponent
a ComponentInterface
, podríamos reflejarlo así en el momento realizar el registro:container.RegisterTypes(En definitiva, el uso de convenciones en el registro de componentes de nuestro contenedor IoC es un avance que ya teníamos disponible en otros paquetes, pero que Unity acaba de incorporar en su versión 3.0. Sin duda, una gran ayuda para reducir la base de código, evitar errores y ser más productivos.
AllClasses.FromAssemblies(typeof(MvcApplication).Assembly),
type => type.GetInterfaces().Where(i => i.Name == type.Name + "Interface"),
WithName.Default,
WithLifetime.Custom<PerRequestLifetimeManager>
);
Publicado en: Variable not found.