Как сделать открытую универсальную цепочку декораторов с Unity + UnityAutoRegistration - PullRequest
3 голосов
/ 22 марта 2012

Ушел сегодня по интересной теме после прочтения этой статьи об оформлении обработчика команд . Я хотел посмотреть, смогу ли я реализовать шаблон, используя Unity вместо SimpleInjector , и до сих пор это оказалось чрезвычайно сложно.

Первое, что мне нужно было сделать, это установить UnityAutoRegistration , чтобы разрешить открытый общий интерфейс ICommandHandler<TCommand>. Текущее решение для этого аспекта заключается в следующем:

Container = new UnityContainer().LoadConfiguration();

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

Это работает для первой части, разрешая любые одиночные ICommandHandler<TCommand>. До сих пор разочаровывает реализация обработчика украшений. Как только я добавляю в качестве декоратора вторую ICommandHandler<TCommand>, Unity создает исключение StackOverflowException. Я не знаю достаточно о внутренностях Unity, но я предполагаю, что это потому, что он не может определить, какой экземпляр разрешить - обработчик команд или декоратор обработчика команд - так как оба реализуют интерфейс ICommandHandler<TCommand> .

Погуглив поводом, я первым обратился к этой статье , в которой объясняется, как это сделать в том, что я бы назвал методом грубой силы. Я также нашел эти связанные страниц , но ни одно из них не является полным решением моей проблемы (и я слишком невежественен, чтобы понять это для себя).

Затем я нашел эту статью, в которой, похоже, рассматриваются мои же проблемы . Однако решение Бифи не учитывает открытые дженерики. В настоящее время большинство наших зависимостей загружаются из раздела единицы в файле .config, поэтому я не хочу писать тонну скомпилированного кода для каждого обработчика или декоратора. Кажется, что наличие некоторого UnityContainerExtension и DecoratorBuildStrategy - верный путь, но я не могу понять это. Я немного поигрался с кодом Бифи и совершенно застрял. Мои попытки изменить его код для учета непатентованных средств привели к появлению BadImageFormatExceptions (была предпринята попытка загрузить программу с неверным форматом. (Исключение из HRESULT: 0x8007000B)).

Мне нравится идея сделать это для реализации цепочки декоратора, потому что она короткая, и для каждой задачи есть только 1 строка:

var container = new Container();

// Go look in all assemblies and register all implementations
// of ICommandHandler<T> by their closed interface:
container.RegisterManyForOpenGeneric(typeof(ICommandHandler<>),
    AppDomain.CurrentDomain.GetAssemblies());

// Decorate each returned ICommandHandler<T> object with an
// TransactionCommandHandlerDecorator<T>.
container.RegisterDecorator(typeof(ICommandHandler<>),
    typeof(TransactionCommandHandlerDecorator<>));

// Decorate each returned ICommandHandler<T> object with an
// DeadlockRetryCommandHandlerDecorator<T>.
container.RegisterDecorator(typeof(ICommandHandler<>),
    typeof(DeadlockRetryCommandHandlerDecorator<>));

... но я не хочу менять свой контейнер с Unity на Simple Injector, если мне не нужно.

Так что мой вопрос: как я могу реализовать открытую обобщенную цепочку декоратора с использованием Unity (плюс UnityAutoRegistration)?

Ответы [ 2 ]

4 голосов
/ 13 декабря 2012

Это будет эквивалент в Unity:

// Go look in all assemblies and register all implementa-
// tions of ICommandHandler<T> 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<T> object with an
// TransactionCommandHandlerDecorator<T>.
container.RegisterType(typeof(ICommandHandler<>), 
    typeof(TransactionCommandHandlerDecorator<>),
    "Inner2",
    InjectionConstructor(new ResolvedParameter(
        typeof(ICommandHandler<>), "Inner1")));

// Decorate each returned ICommandHandler<T> object with an
// DeadlockRetryCommandHandlerDecorator<T>.
container.RegisterType(typeof(ICommandHandler<>), 
    typeof(DeadlockRetryCommandHandlerDecorator<>), 
    InjectionConstructor(new ResolvedParameter(
        typeof(ICommandHandler<>), "Inner2")));
3 голосов
/ 28 марта 2012

Надеюсь, я правильно понимаю проблему, и мне было любопытно попробовать заставить ее работать, и я ни в коем случае не эксперт по Unity, но я думал о решении, которое немного проще в реализации и также будет проще сделать с другим контейнером. Казалось бы, единственный способ поддержать открытый универсальный и другие типы - это иметь 2 отдельных контейнера (1 для открытого универсального) и один для ваших командных обработчиков, это может быть не лучшим способом, но это работает с Unity и я предполагаю, что с другими тоже будет легче.

Итак, я придумал это:

Я создал контейнеры следующим образом (вы можете использовать свой традиционный подход, все еще не уверенный для контейнера обработчика)

var container = new UnityContainer();

var container2 = new UnityContainer();

container2.RegisterType(typeof(ICommandHandler<QueryCommand>),
    typeof(QueryCommandHandler));
container.RegisterInstance("Handlers", container2);
container.RegisterInstance(container);
container.RegisterType(typeof(ICommandHandler<>),
    typeof(DecoratedHandler<>));

В качестве именованного экземпляра вы видите контейнер 2, содержащий обработчики.

Тогда я просто создал базовый базовый класс декоратора в соответствии с требованием шаблона:

public class DecoratorCommandHandler<TCommand>
    : ICommandHandler<TCommand>
{
    private ICommandHandler<TCommand> inner;
    public DecoratorCommandHandler(
        ICommandHandler<TCommand> inner)
    {
        this.inner = inner;
    }

    public virtual void Handle(TCommand command)
    {
         this.inner.Handle(command);
    }
}

Во-вторых, я создал еще один универсальный обработчик, который обернул бы все декорации, которые вы хотите сделать для своего решения, здесь вы добавите декорирование для TryCatch / Caching / Transactions или все, что вы хотите применить к каждому обработчику команд:

public class DecoratedHandler<TCommand>
    : DecoratorCommandHandler<TCommand>
{
    public DecoratedHandler(UnityContainer container)
        : base(BuildInner(container))
    {
    }

    private static ICommandHandler<TCommand> BuildInner(
        UnityContainer container)
    {
         var handlerContainer =
             container.Resolve<UnityContainer>("Handlers");
         var commandHandler =
             handlerContainer.Resolve<ICommandHandler<TCommand>>();

         return new TryCatchCommandHandler<TCommand>(commandHandler);
    }
}

Вы заметите, что первый внутренний разрешает фактический обработчик команд в соответствии с тем, который вы запрашивали, например QueryCommandHandler, UpdateCommandHandler, ExecuteCommandHandler или любым другим, имеющим дело со спецификой. А затем оборачивается всеми декораторами, которые вы хотите использовать для всех из них.

Тогда я смог найти правильный обработчик, оформленный в правильном виде:

ICommandHandler<QueryCommand> handler =
    container.Resolve<ICommandHandler<QueryCommand>>();

var cmd = new QueryCommand();

handler.Handle(cmd);

Надеюсь, это поможет

...