Получить специфицированный c интерфейс для отдельного конкретного типа бетона с декораторами, примененными с помощью простого инжектора - PullRequest
1 голос
/ 12 апреля 2020

Я использую SimpleInjector с MediatR и подключаю мои INotifications и INotificationHandlers<INotification> с атрибутами к моим классам реализации - так мы можем сопоставить сообщения шины сообщений (или уведомления) обработчикам уведомлений:

  1. Хост-приложение получает сообщение от шины
  2. Сообщение анализируется в уведомлении POCO
  3. Контейнер используется для поиска обработчиков уведомлений
  4. Уведомление отправляется каждому обработчику

Традиционно можно зарегистрировать все INotificationHandler<> с Container.GetTypesToRegister() и Container.Collection.Register(), и все будет хорошо, так как все обработчики будут возвращены контейнером для этого специфика c INotification.

В этом случае я sh получу конкретные c конкретные экземпляры, которые реализуют специфицируемый c INotificationHandler<SpecificEvent> для вызова. Мы решаем, что вызывать с атрибутами:

[MessageBusSubscription("v1/alerts/fire"]
class FirstClass : INotificationHandler<Aggregate>
{
    public Task Handle(Aggregate notification, CancellationToken cancellationToken)
    {
        throw new NotImplementedException();
    }
}

[MessageBusSubscription("v1/alerts/fire"]
class SecondClass : INotificationHandler<Aggregate>
{
    public Task Handle(Aggregate notification, CancellationToken cancellationToken)
    {
        throw new NotImplementedException();
    }
}

[MessageBusSubscription("v1/alerts/leak"]
class OtherClass : INotificationHandler<Aggregate>
{
    public Task Handle(Aggregate notification, CancellationToken cancellationToken)
    {
        throw new NotImplementedException();
    }
}

Я знаю, что могу получить и преобразовать в интерфейс (это приведение будет безопасным, так как при регистрации инфраструктура bootstrap будет принудительно проверять, что тип реализует интерфейс ):

Container Bootstrap()
{
    var container = new Container();

    // we cannot register the below with Container.Collection.Register as we wont be finding the registration
    // by INotificationHandler<INotification> but rather by the specific class (we could auto-wire by reflection)
    container.Register<FirstClass>();
    container.Register<OtherClass>();

    // other bootstrap code, including registration of all other INotificationHandler<> that do not have
    // MessageBusSubscriptionAttribute in the cases where we want to publish to all implementations
    // that are not scoped to a specific topic subscription or message type

    return container;
}

IEnumerable<INotificationHandler<TNotification>>> GetConcreteTypeThatImplementsNotificationHandlers<TNotification>(string topic)
    where TNotification : INotification
{
    // TopicRegistrations consists of the following:
    // { "v1/alerts/fire", [] { typeof(FirstClass), typeof(SecondClass) }
    // { "v1/alerts/leak", [] { typeof(OtherClass) }

    // based on notification type and topic string, get concrete implementation
    var concreteTypes = TopicRegistrations.GetTypeForTopic(topic);

    foreach(var type in concreteTypes)
    {
        var instance = Container.GetInstance(type);
        yield return (INotificationHandler<TNotification>>)instance;
    }
}

Затем я мог бы вызвать вышеупомянутое, как если бы это было INotificationHandler для этого определенного c типа уведомления и только для конкретных c конкретных реализаций, основанных на метаданных атрибута.

Однако как я могу гарантировать, что любые декораторы (например, ведение журналов или обработчики ошибок и т. Д. c) будут по-прежнему применяться к NotificationHandler, выбранному при использовании вышеуказанного подхода к коду?

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

var notficationHandlerType = typeof(INotificationHandler<>).MakeGenericType(notificationFromBus.GetType());
Container.GetRegistrationOnlyForConcreateType(notficationHandlerType, concreteType);

Что-нибудь, что можно сделать, чтобы сделать это чище или пропустить какие-либо риски, которые могут взорваться или пахнет кодом?

Обновление

Спасибо Стивену за его замечательный код, спасибо, что вдохновили нас на лучший код. Я немного адаптировал его так, чтобы он принимал тип и обработчики Speci c, чтобы зарегистрироваться для этого типа:

public class TestNotification : INotification { }

public class DifferentTestNotification : INotification { }

public class OtherClass : INotificationHandler<TestNotification>, INotificationHandler<DifferentTestNotification>
{
    public Task Handle(TestNotification notification, CancellationToken cancellationToken)
        { throw new NotImplementedException(); }

    public Task Handle(DifferentTestNotification notification, CancellationToken cancellationToken)
    { throw new NotImplementedException(); }
}

/* repeat the exact same for AmazingClass and AllStars giving 3 identical classes for which we can selectively register different handlers later */

И передать словарь обработчиков, которые я sh зарегистрирую:

registrationTypes = new Dictionary<Type, Type[]>()
{
    { typeof(OtherClass), new [] { typeof(INotificationHandler<>).MakeGenericType(typeof(TestNotification)) } },
    { typeof(AmazingClass), new [] { typeof(INotificationHandler<>).MakeGenericType(typeof(DifferentTestNotification)) } },
    { typeof(AllStars), new [] { typeof(INotificationHandler<>).MakeGenericType(typeof(TestNotification)), typeof(INotificationHandler<>).MakeGenericType(typeof(DifferentTestNotification)) } }
};

В следующий класс регистрации:

public class NotifcationProducerRegistrations
{
    readonly ConcurrentDictionary<Type, Dictionary<Type, InstanceProducer>> handlerProducers
        = new ConcurrentDictionary<Type, Dictionary<Type, InstanceProducer>>();

    public void AddProducer(Type notificationType, Type concreteType, InstanceProducer producer)
    {
        this.handlerProducers.AddOrUpdate(
            notificationType,
            (key) => new Dictionary<Type, InstanceProducer> { { concreteType, producer } },
            (key, dictionary) => { dictionary.Add(concreteType, producer); return dictionary; });
    }

    public IEnumerable<InstanceProducer> GetRegistrations(Type notificationType)
    {
        if(this.handlerProducers.TryGetValue(notificationType, out var dict))
        {
            foreach (var kvp in dict)
                yield return kvp.Value;
        }
    }
}

И зарегистрируйтесь следующим образом:

public static NotifcationProducerRegistrations GetNotificationHandlerProducersTest(this Container container, Dictionary<Type, Type[]> registrationTypes, Lifestyle lifestyle)
{

    var registrations = new NotifcationProducerRegistrations();

    foreach (var registration in registrationTypes)
    {
        var concreteType = registration.Key;
        var notificationHandlerTypes = registration.Value;
        var interfaceTypes = concreteType.GetClosedTypesOf(typeof(INotificationHandler<>));
        foreach(var filteredInterfaceType in interfaceTypes.Intersect(notificationHandlerTypes))
        {
            registrations.AddProducer(
                            filteredInterfaceType,
                            concreteType,
                            lifestyle.CreateProducer(filteredInterfaceType, concreteType, container));
        }
    }

    return registrations;
}

Со ссылкой на мой комментарий, с вышеупомянутым у меня есть Соотношение 1: 1 между производителем для определенного типа конкретного типа c, в массиве других конкретных типов для этого типа уведомления.

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

registrations

1 Ответ

1 голос
/ 13 апреля 2020

В вашем случае не допускайте добавления регистраций INotificationHandler<T> непосредственно в контейнер, используя:

container.Register<FirstClass>();

В этом случае класс будет регистрироваться по его конкретному типу, что запрещает применение декораторов. Вместо этого вы обычно используете следующую регистрацию:

container.Register<INotificationHandler<Aggregate>, FirstClass>();

Это позволяет применять декораторы на INotificationHandler<T>. Это, однако, все еще не будет работать в вашем случае, потому что есть несколько реализаций одной и той же абстракции. Таким образом, вместо этого вы обычно регистрируете его как коллекцию, как вы упомянули в своем вопросе:

container.Collection.Register(typeof(INotificationHandler<>),
    typeof(FirstClass).Assembly);

Но это будет работать хорошо и позволит вам фильтровать возвращенную коллекцию на основе атрибутов, которые размещены в реализации. ... до тех пор, пока вы не оборачиваете реализации с использованием декораторов, потому что в этом случае вы будете проверять атрибуты самого внешнего декоратора. Это, очевидно, то, чего вы хотите достичь.

Решение вашего квеста состоит в том, чтобы -не регистрировать эти компоненты во внутреннем словаре разрешения Simple Injector (т.е. используя один из вызовов Register), но вместо этого создать InstanceProducer экземпляры 'вручную' и сохраняют их в себе:

IEnumerable<Type> handlerTypes =
    container.GetTypesToRegister(typeof(INotificationHandler<>),
        typeof(FirstClass).Assembly);

Dictionary<Type, Dictionary<Type, InstanceProducer>> handlerProducers = (
    from handlerType in handlerTypes
    from interfaceType in handlerType.GetClosedTypesOf(typeof(INotificationHandler<>))
    let producer =
        Lifestyle.Transient.CreateProducer(interfaceType, handlerType, container)
    group new { handlerType, producer } by interfaceType into interfaceGroup
    select new
    {
        MessageType = interfaceGroup.GetGenericArguments().Single(),
        Producers = interfaceGroup.ToDictionary(i => i.handlerType, i => i.producer)
    })
    .ToDictionary(i => i.MessageType, i => i.Producers);

Вызов GetClosedTypesOf возвращает все закрытые версии INotificationHandler<T> для данного handlerType. Обработчик может реализовать несколько интерфейсов, и GetClosedTypesOf вернет все закрытые версии INotificationHandler<T>, которые реализует handlerTyp.

То, что interfaceType необходимо использовать для создания InstanceProducer. Это эквивалентно вызову Register(interfaceType, handlerType), поскольку позволяет Simple Injector применять декораторы на основе типа интерфейса.

Регистрация, созданная с использованием Lifestyle.CreateProducer, не может быть решена путем вызова Container.GetInstance, но они все еще остаются часть процесса проверки. Они проверены и диагностированы, как и любая другая регистрация. Вместо вызова Container.GetInstance, InstanceProducer содержит свой собственный метод GetInstance. Например:

IEnumerable<INotificationHandler<TNotification>>>
    GetConcreteTypeThatImplementsNotificationHandlers<TNotification>(string topic)
    where TNotification : INotification
{
    var concreteTypes = TopicRegistrations.GetTypeForTopic(topic);

    var notificationHandlerProducers = this.handlerProducers[typeof(TNotification)];

    foreach(var type in concreteTypes)
    {
        var instance = notificationHandlerProducers[type].GetInstance();
        yield return (INotificationHandler<TNotification>>)instance;
    }
}
...