【发布时间】:2020-07-25 07:55:32
【问题描述】:
我正在使用 SimpleInjector 和 MediatR 并将我的 INotifications 和 INotificationHandlers<INotification> 与我的实现类上的属性挂钩 - 这样我们就可以将消息总线消息(也称为通知)映射到通知处理程序:
- 主机应用从总线接收消息
- 消息被解析成通知 POCO
- 容器用于查找通知处理程序
- 向每个处理程序发送通知
传统上,将所有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