获取单个指定具体类型的特定接口,装饰器应用简单注入器
Get specific interface for a single specified concrete type, with decorators applied with Simple Injector
我将 SimpleInjector
与 MediatR
一起使用,并将我的 INotifications
和 INotificationHandlers<INotification>
与我的实现 classes 上的属性挂钩 - 这就是我们可以将消息总线消息(又名通知)映射到通知处理程序:
- 主机应用从总线接收消息
- 消息被解析为 Notification POCO
- 容器用于查找通知处理程序
- 向每个处理程序发送通知
传统上人们会将所有 INotificationHandler<>
注册到 Container.GetTypesToRegister()
和 Container.Collection.Register()
,并且所有处理程序都将由容器 returned 来处理具体 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();
}
}
我知道我可以获取并转换为接口(此转换是安全的,因为在注册期间 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
,并且仅针对基于属性元数据的特定具体实现。
但是,在使用上述代码方法时,我如何确保任何装饰器(如日志记录或错误处理程序等)仍应用于获取的 NotificationHandler
?
最终我正在寻找类似于下面的容器函数,它将 return 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)) } }
};
进入下方报名class:
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 关系。
如前所述,我目前看到的是一个数组,而我认为(并且可能是错误的)我应该只需要得到一个制作人,类似于下图:
在您的情况下,使用以下方法防止将 INotificationHandler<T>
注册直接添加到容器中:
container.Register<FirstClass>();
这样做会通过其具体类型注册 class,这将不允许应用装饰器。相反,您通常会使用以下注册:
container.Register<INotificationHandler<Aggregate>, FirstClass>();
它允许应用 INotificationHandler<T>
上的装饰器。但是,这在您的情况下仍然行不通,因为同一个抽象有多个实现。因此,正如您在问题中提到的那样,您通常会将其注册为一个集合:
container.Collection.Register(typeof(INotificationHandler<>),
typeof(FirstClass).Assembly);
但这会很好地工作,并允许您根据放置在实现上的属性过滤 returned 集合...只要您不使用装饰器包装实现,因为在那如果您要检查最外层装饰器的属性。
您的任务的解决方案是 - 不 - 在 Simple Injector 的内部解析字典中注册这些组件(即使用 Register
调用之一),而是创建 InstanceProducer
个实例 'manually' 并将它们存储到你自己中:
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
获取给定 handlerType
的 INotificationHandler<T>
的所有封闭版本。处理程序可以实现多个接口,GetClosedTypesOf
将 return handlerTyp
实现的 INotificationHandler<T>
的所有封闭版本。
那 interfaceType
必须用来创建 InstanceProducer
。这相当于调用 Register(interfaceType, handlerType)
,因为它允许 Simple Injector 根据接口类型应用装饰器。
使用 Lifestyle.CreateProducer
创建的注册无法通过调用 Container.GetInstance
解决,但它们仍然是验证过程的一部分。就像任何其他注册一样,它们经过验证和诊断。 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;
}
}
更新:
Simple Injector v5 包含一个新的 Dependency Metadata feature,在这种情况下可能会有所帮助。请查看文档以获取更多信息。
我将 SimpleInjector
与 MediatR
一起使用,并将我的 INotifications
和 INotificationHandlers<INotification>
与我的实现 classes 上的属性挂钩 - 这就是我们可以将消息总线消息(又名通知)映射到通知处理程序:
- 主机应用从总线接收消息
- 消息被解析为 Notification POCO
- 容器用于查找通知处理程序
- 向每个处理程序发送通知
传统上人们会将所有 INotificationHandler<>
注册到 Container.GetTypesToRegister()
和 Container.Collection.Register()
,并且所有处理程序都将由容器 returned 来处理具体 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();
}
}
我知道我可以获取并转换为接口(此转换是安全的,因为在注册期间 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
,并且仅针对基于属性元数据的特定具体实现。
但是,在使用上述代码方法时,我如何确保任何装饰器(如日志记录或错误处理程序等)仍应用于获取的 NotificationHandler
?
最终我正在寻找类似于下面的容器函数,它将 return 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)) } }
};
进入下方报名class:
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 关系。
如前所述,我目前看到的是一个数组,而我认为(并且可能是错误的)我应该只需要得到一个制作人,类似于下图:
在您的情况下,使用以下方法防止将 INotificationHandler<T>
注册直接添加到容器中:
container.Register<FirstClass>();
这样做会通过其具体类型注册 class,这将不允许应用装饰器。相反,您通常会使用以下注册:
container.Register<INotificationHandler<Aggregate>, FirstClass>();
它允许应用 INotificationHandler<T>
上的装饰器。但是,这在您的情况下仍然行不通,因为同一个抽象有多个实现。因此,正如您在问题中提到的那样,您通常会将其注册为一个集合:
container.Collection.Register(typeof(INotificationHandler<>),
typeof(FirstClass).Assembly);
但这会很好地工作,并允许您根据放置在实现上的属性过滤 returned 集合...只要您不使用装饰器包装实现,因为在那如果您要检查最外层装饰器的属性。
您的任务的解决方案是 - 不 - 在 Simple Injector 的内部解析字典中注册这些组件(即使用 Register
调用之一),而是创建 InstanceProducer
个实例 'manually' 并将它们存储到你自己中:
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
获取给定 handlerType
的 INotificationHandler<T>
的所有封闭版本。处理程序可以实现多个接口,GetClosedTypesOf
将 return handlerTyp
实现的 INotificationHandler<T>
的所有封闭版本。
那 interfaceType
必须用来创建 InstanceProducer
。这相当于调用 Register(interfaceType, handlerType)
,因为它允许 Simple Injector 根据接口类型应用装饰器。
使用 Lifestyle.CreateProducer
创建的注册无法通过调用 Container.GetInstance
解决,但它们仍然是验证过程的一部分。就像任何其他注册一样,它们经过验证和诊断。 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;
}
}
更新:
Simple Injector v5 包含一个新的 Dependency Metadata feature,在这种情况下可能会有所帮助。请查看文档以获取更多信息。