Cómo hacer un encadenamiento genérico de decorador abierto con unity + UnityAutoRegistration

Se fue a una tangente interesante hoy después de leer este artículo sobre la decoración del controlador de comandos . Quería ver si podía implementar el patrón usando Unity en lugar de SimpleInjector , y hasta ahora está resultando extremadamente difícil.

Lo primero que tuve que hacer fue instalar UnityAutoRegistration para resolver la interfaz genérica abierta ICommandHandler . La solución actual para ese aspecto es la siguiente:

 Container = new UnityContainer().LoadConfiguration(); Container.ConfigureAutoRegistration() .ExcludeSystemAssemblies() .Include(type => type.ImplementsOpenGeneric(typeof(ICommandHandler)), (t, c) => c.RegisterType(typeof(ICommandHandler), t) ) .ApplyAutoRegistration() ; 

Esto funciona para la primera parte, resolviendo cualquier ICommandHandler . Lo que hasta ahora ha resultado frustrante es implementar un controlador de decoración. Tan pronto como añado un segundo ICommandHandler como decorador, Unity lanza una excepción StackOverflowException. No sé lo suficiente sobre los elementos internos de Unity, pero supongo que esto se debe a que no puede determinar a qué instancia se puede resolver (el controlador de comandos o el decorador del controlador de comandos), ya que ambos implementan ICommandHandler interfaz.

Buscar en Google me llevó primero a este artículo , que explica cómo hacerlo en lo que consideraría un método de fuerza bruta. También encontré estas páginas relacionadas, pero ninguna es una solución completa a mi problema (y soy demasiado ignorante para resolverlo por mí mismo).

Luego encontré este artículo, que parece abordar mis mismas preocupaciones . Sin embargo, la solución de Beef no tiene en cuenta los generics abiertos. Actualmente, la mayoría de nuestras dependencias se cargan desde una sección de unidad en el archivo .config, por lo que no quiero escribir una tonelada de código comstackdo para cada controlador o decorador. Parece que tener algún tipo de UnityContainerExtension y DecoratorBuildStrategy es el camino correcto, pero no puedo entenderlo. He estado jugando con el código de Beef por un tiempo y estoy completamente atascado. Mis bashs de modificar su código para tener en cuenta los generics han dado lugar a BadImageFormatExceptions (se intentó cargar un progtwig con un formato incorrecto (excepción de HRESULT: 0x8007000B)).

Me gusta la idea de hacer esto para implementar el encadenamiento de decoradores, porque es corto y solo hay 1 línea por inquietud:

 var container = new Container(); // Go look in all assemblies and register all implementations // of ICommandHandler by their closed interface: container.RegisterManyForOpenGeneric(typeof(ICommandHandler), AppDomain.CurrentDomain.GetAssemblies()); // Decorate each returned ICommandHandler object with an // TransactionCommandHandlerDecorator. container.RegisterDecorator(typeof(ICommandHandler), typeof(TransactionCommandHandlerDecorator)); // Decorate each returned ICommandHandler object with an // DeadlockRetryCommandHandlerDecorator. container.RegisterDecorator(typeof(ICommandHandler), typeof(DeadlockRetryCommandHandlerDecorator)); 

… pero no quiero cambiar mi contenedor de Unity a Simple Injector si no tengo que hacerlo.

Entonces, ¿mi pregunta es cómo podría implementar el encadenamiento de decoradores generics abiertos con unity (más UnityAutoRegistration )?

Este sería el equivalente en Unidad:

 // Go look in all assemblies and register all implementa- // tions of ICommandHandler by their closed interface: var container = new UnityContainer(); var handlerRegistrations = from assembly in AppDomain.CurrentDomain.GetAssemblies() from implementation in assembly.GetExportedTypes() where !implementation.IsAbstract where !implementation.ContainsGenericParameters let services = from iface in implementation.GetInterfaces() where iface.IsGenericType where iface.GetGenericTypeDefinition() == typeof(ICommandHandler<>) select iface from service in services select new { service, implementation }; foreach (var registration in handlerRegistrations) { container.RegisterType(registration.service, registration.implementation, "Inner1"); } // Decorate each returned ICommandHandler object with an // TransactionCommandHandlerDecorator. container.RegisterType(typeof(ICommandHandler<>), typeof(TransactionCommandHandlerDecorator<>), "Inner2", InjectionConstructor(new ResolvedParameter( typeof(ICommandHandler<>), "Inner1"))); // Decorate each returned ICommandHandler object with an // DeadlockRetryCommandHandlerDecorator. container.RegisterType(typeof(ICommandHandler<>), typeof(DeadlockRetryCommandHandlerDecorator<>), InjectionConstructor(new ResolvedParameter( typeof(ICommandHandler<>), "Inner2"))); 

Espero entender el problema correctamente y sentí curiosidad por intentar que esto funcionara y no soy un experto en Unity, pero estaba pensando en una solución que es un poco más fácil de implementar y que también sería más fácil de hacer. Con un contenedor diferente. Parecería que la única forma de admitir el genérico abierto, así como los otros tipos, es tener 2 contenedores separados (1 para el genérico abierto) y uno para los manejadores de comandos. Esta podría no ser la mejor manera, pero funcionó con Unity. y supongo que también será más fácil con los demás.

Así que se me ocurrió esto:

Creé los contenedores de la siguiente manera (puede utilizar el enfoque de su convención aún para el contenedor del manejador)

 var container = new UnityContainer(); var container2 = new UnityContainer(); container2.RegisterType(typeof(ICommandHandler), typeof(QueryCommandHandler)); container.RegisterInstance("Handlers", container2); container.RegisterInstance(container); container.RegisterType(typeof(ICommandHandler<>), typeof(DecoratedHandler<>)); 

Verá el contenedor 2 que contiene los Manejadores como una instancia con nombre.

Entonces acabo de crear una clase de decorador base genérico según el requisito del patrón:

 public class DecoratorCommandHandler : ICommandHandler { private ICommandHandler inner; public DecoratorCommandHandler( ICommandHandler inner) { this.inner = inner; } public virtual void Handle(TCommand command) { this.inner.Handle(command); } } 

Segundo, creé otro controlador genérico que envolvería todas las decoraciones que desea hacer para su solución, aquí agregará decoración para TryCatch / Caching / Transactions o cualquier otra cosa que desee aplicar a cada controlador de comando:

 public class DecoratedHandler : DecoratorCommandHandler { public DecoratedHandler(UnityContainer container) : base(BuildInner(container)) { } private static ICommandHandler BuildInner( UnityContainer container) { var handlerContainer = container.Resolve("Handlers"); var commandHandler = handlerContainer.Resolve>(); return new TryCatchCommandHandler(commandHandler); } } 

Notará que el primer interno resuelve el controlador de comandos real según el que solicitó, como QueryCommandHandler , UpdateCommandHandler , ExecuteCommandHandler o cualquier otro que ExecuteCommandHandler que ver con ExecuteCommandHandler específicos. Y luego se envuelve con todos los decoradores que quieras en común a todos ellos.

Entonces pude resolver el controlador correcto, decorado de la manera correcta:

 ICommandHandler handler = container.Resolve>(); var cmd = new QueryCommand(); handler.Handle(cmd); 

Espero que esto ayude