Как сделать открытую общую цепочку декораторов с помощью единства + UnityAutoRegistration

Сегодня мы пошли по интересному пути после прочтения этой статьи о декорировании обработчиков команд. Я хотел посмотреть, смогу ли я реализовать шаблон с помощью 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>.

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

Затем я нашел эту статью, которая, кажется, решает мои проблемы . Однако мощное решение не учитывает работу с открытыми дженериками. В настоящее время большинство наших зависимостей загружаются из раздела единства в файле .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, если мне это не нужно.

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


person danludwig    schedule 21.03.2012    source источник


Ответы (2)


Это будет эквивалентно в 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")));
person Steven    schedule 13.12.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, содержащий обработчики как именованный экземпляр.

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

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);

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

person Andre    schedule 28.03.2012
comment
Должен сказать, что нахожу ваше решение очень креативным, так что +1 за это. Однако наличие нескольких контейнеров, к сожалению, станет большим кошмаром для обслуживания. Оба контейнера необходимо поддерживать, и, поскольку вы явно обновляете декораторы, вам нужно вручную вводить все зависимости. Но что более важно, вы получите очень странное поведение со временем жизни зарегистрированных сервисов... - person Steven; 28.03.2012
comment
Возьмем, к примеру, вероятную ситуацию, когда и TryCatchCommandHandler, и реальный обработчик команд зависят от интерфейса ILogger, который вы зарегистрировали как синглтон. Поскольку обработчик и декоратор происходят из разных контейнеров, они оба получат свой собственный экземпляр, хотя он определен как синглтон. И это только очевидный пример. Это станет очень сложным очень быстро. - person Steven; 28.03.2012
comment
Привет, Стивен, ваши наблюдения снова точны, я согласен с тем, что 2 контейнера требуют дополнительного обслуживания, и я также рекомендовал бы, чтобы при обновлении декорированного обработчика вручную вы использовали контейнер для разрешения всех зависимостей и придерживались их времени жизни, возможно вы также можете разрешить TryCatchHandler или любой другой обработчик из контейнера, а не создавать его заново. - person Andre; 29.03.2012