【问题标题】:Get specific interface for a single specified concrete type, with decorators applied with Simple Injector获取单个指定具体类型的特定接口,并使用 Simple Injector 应用装饰器
【发布时间】:2020-07-25 07:55:32
【问题描述】:

我正在使用 SimpleInjectorMediatR 并将我的 INotificationsINotificationHandlers<INotification> 与我的实现类上的属性挂钩 - 这样我们就可以将消息总线消息(也称为通知)映射到通知处理程序:

  1. 主机应用从总线接收消息
  2. 消息被解析成通知 POCO
  3. 容器用于查找通知处理程序
  4. 向每个处理程序发送通知

传统上,将所有INotificationHandler<> 注册为Container.GetTypesToRegister()Container.Collection.Register(),所有处理程序都将由该特定INotification 的容器返回。

在这种情况下,我希望获得一个特定的具体实例,该实例实现了要调用的特定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();
    }
}

我知道我可以获取并转换为接口(这种转换是安全的,因为在注册引导基础设施期间会强制检查该类型是否实现了接口):

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,并且仅针对基于属性元数据的特定具体实现。

但是,当使用上述代码方法时,我如何确保任何装饰器(如日志记录或错误处理程序等)仍应用于获取的 NotificationHandler

最终,我正在寻找一个类似于下面的 Container 函数,它将为 提供的具体类型返回 INotificationHandler,并应用装饰器(如果有):

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

有什么办法可以让这个更干净,或者忽略任何可能爆炸或代码异味的风险?

更新

感谢 Steven 的精彩代码,感谢他激励我们更好地编写代码。我稍微调整了一下,让它接受一个类型,以及为该类型注册的特定处理程序:

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 */

并传递我希望注册的处理程序的字典:

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# .net simple-injector mediatr


    【解决方案1】:

    在您的情况下,请防止使用以下方法将 INotificationHandler&lt;T&gt; 注册直接添加到容器中:

    container.Register<FirstClass>();
    

    这样做会通过其具体类型注册该类,从而禁止应用装饰器。相反,您通常会使用以下注册:

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

    它允许在INotificationHandler&lt;T&gt; 上应用装饰器。但是,这仍然不适用于您的情况,因为同一个抽象有多个实现。因此,正如您在问题中提到的那样,您通常会将其注册为一个集合:

    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 的调用获取给定handlerTypeINotificationHandler&lt;T&gt; 的所有封闭版本。一个处理程序可以实现多个接口,GetClosedTypesOf 将返回handlerTyp 实现的所有关闭版本的INotificationHandler&lt;T&gt;

    interfaceType 必须用于创建InstanceProducer。这相当于调用 Register(interfaceType, handlerType),因为它允许 Simple Injector 根据接口类型应用装饰器。

    使用Lifestyle.CreateProducer 创建的注册无法通过调用Container.GetInstance 来解决,但它们仍然是验证过程的一部分。就像任何其他注册一样,它们经过验证和诊断。 InstanceProducer 不是调用Container.GetInstance,而是包含它自己的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;
        }
    }
    

    更新

    Simple Injector v5 包含一个新的Dependency Metadata feature,它可能在这种情况下有所帮助。请查看文档以获取更多信息。

    【讨论】:

    • 太棒了,谢谢你把我的袜子脱了,逐行检查代码,它工作!我不得不将 `var instance = notificationHandlerProducers[type].GetInstance();` 修改为 var instance = notificationHandlerProducers[type].FirstOrDefault().GetInstance();
    • 这里偷偷摸摸的部分是一个类可以实现多个接口;这使得实现不那么明显。
    • 嗨史蒂文,通过代码我看到映射是由INotificationType =&gt; ConcreteType =&gt; INotificationProducers[]完成的。由于我们在handlerProducers 实例化上转换数组,并且由于一个类只能实现一种类型的INotificationHandler&lt;SpecificNotification&gt;,您能否详细说明为什么我们需要生产者的排列?再次感谢所有帮助
    • 对不起,史蒂文,澄清你的第一个答案是正确的(可以定义多个接口,我的意思是两个完全相同的INotificationHandker&lt;SameType&gt; 按预期抛出编译时错误)。查看select new { concreteGroup.Key, Value = concreteGroup.ToArray() }) 行,它创建了一个数组,但无论类/注册组合如何,我只看到一个生产者,除非我遗漏了什么,而且只需要notificationHandlerProducers[type].GetInstance(); 就可以更容易地调度(我目前实际上需要循环的地方)。 TIA
    • 你是对的。额外的分组没有任何意义,并且保证在所有情况下每个处理程序类型都产生一个生产者。我更新了我的答案以反映这一点。
    猜你喜欢
    • 2019-05-01
    • 1970-01-01
    • 2016-06-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多