Convenciones en Unity 3 - Code Variables -->
Code Variables Code Variables

Latest news

جاري التحميل ...

Convenciones en Unity 3

Microsoft patterns & practicesSin duda, las convenciones están de moda. Cualquier framework actual que se precie trae su propio conjunto de convenciones, que no son sino una serie de reglas predefinidas, normalmente de carácter opcional, cuyo cumplimiento evitará que tengamos que tomar decisiones, evitará que cometamos errores, y, normalmente, aumentarán nuestra productividad.

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>(
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...
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 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(
AllClasses.FromAssemblies(typeof(MvcApplication).Assembly),
WithMappings.FromMatchingInterface,
WithName.Default,
WithLifetime.Custom<PerRequestLifetimeManager>
);
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.

Básicamente, el método RegisterTypes() acepta los siguientes parámetros:
  1. una colección de los tipos de datos a mapear, en forma de IEnumerable<Type>. En el ejemplo anterior, usamos la clase AllClasses, provista también por Unity, para obtener los tipos cargados en el ensamblado de la aplicación mediante su método FromAssemblies(), 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étodo FromLoadedAssemblies().
     
  2. 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.
       
  3. 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, como Default (que establece a null el nombre), o TypeName, que lo obtendrá del nombre del tipo del componente. En la mayoría de los casos, pasar null o WithName.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.
     
  4. 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ática WithLifeTime ofrece varios métodos de utilidad ya preparados, como Hierarchical, PerThread, Transient, o Custom<T>, siendo T el LifetimeManagerque 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.
Este diseño, muy al estilo funcional y apoyándose en expresiones lambda, hacen que resulte realmente sencillo adaptar Unity a las convenciones de nuestro equipo de desarrollo. Por ejemplo, si queremos introducir en el registro sólo las clases definidas en el espacio de nombres MyProject.Application de cualquier ensamblado, podríamos hacerlo así, retocando el primer parámetro:
container.RegisterTypes(
AllClasses.FromLoadedAssemblies()
.Where(a=>a.Namespace == "MyProject.Application"),
WithMappings.FromMatchingInterface,
WithName.Default,
WithLifetime.Custom<PerRequestLifetimeManager>
);
O, por ejemplo, si quisiéramos usar un LifetimeManager per request sólo para las clases pertenecientes a dicho espacio de nombres:
container.RegisterTypes(
AllClasses.FromAssemblies(typeof (MvcApplication).Assembly),
WithMappings.FromMatchingInterface,
WithName.Default,
type => type.Namespace == "MyProject.Application" ?
(LifetimeManager)new PerRequestLifetimeManager() : (LifetimeManager)null
);
O, algo simplificado, si quisiéramos cambiar la convención de nombrado de interfaces de IComponent a ComponentInterface, podríamos reflejarlo así en el momento realizar el registro:
container.RegisterTypes(
AllClasses.FromAssemblies(typeof(MvcApplication).Assembly),
type => type.GetInterfaces().Where(i => i.Name == type.Name + "Interface"),
WithName.Default,
WithLifetime.Custom<PerRequestLifetimeManager>
);
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.

Publicado en: Variable not found.

Comments



If you like the content of our blog, we hope to stay in constant communication, just enter your email to subscribe to the blog's express mail to receive new blog updates, and you can send a message by clicking on the button next ...

إتصل بنا

About the site

author Code Variables  Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript...

Learn more ←

Blog visitors

Blog stats

All Copyrights Reserved

Code Variables

2019