我如何使用简单的注入器将一个类型及其接口注册为单例?
How can i register a type with its interfaces as singleton using simple injector?
我有如下模型:
public interface IUserLookupService {
Guid[] GetDirectChilds(Guid userId);
Guid[] GetAllChilds(Guid userId);
Guid GetDepartment(Guid userId);
}
public interface ICanRefreshCache{
void Refresh();
}
public class XHandler : IEventHandler<UserNameChanged> { ... }
public class YHandler : IEventHandler<TeamCreated> { ... }
public class CachedUserLookupService
: IUserLookupService,
ICanRefreshCache,
IEventHandler<UserNameChanged>
{
private Func<ISession> _sessionFactory;
private IDictionary<Guid, UserInfo> _users = new Dictionary<Guid, UserInfo>();
public CachedUserLookupService(Func<ISession> sessionFactory) {
_sessionFactory = sessionFactory;
}
public void Handle(UserNameChanged ev) {
// change cache with new event parameters
}
public void Refresh() {
var session = _sessionFactory();
// get user from db then create userinfo...
}
public Guid[] GetDirectChilds(Guid userId) {
// code
}
public Guid[] GetAllChilds(Guid userId) {
// code
}
public Guid GetDepartment(Guid userId) {
// code
}
public class UserInfo
{
public Guid Id { get; set; }
public string FullName { get; set; }
public Guid? ParentId {get;set;}
}
}
public class CachedTeamService
: ITeamService,
ICanRefreshCache,
IEventHandler<TeamCreated>{
// similar with CachedUserLookupService
}
我的注册:
container.RegisterManyForOpenGeneric(typeof(IEventHandler<>),
(closedServiceType, implementations) =>
{
container.RegisterAll(closedServiceType, implementations);
},
applicationAssembly, typeof(Constants).Assembly);
var serviceRegistrations =
from type in applicationAssembly.GetExportedTypes()
where type.Name.EndsWith("Service")
where !type.IsAbstract
where !type.IsInterface
select new
{
Services = type.GetInterfaces(),
Implementation = type
};
var lifeStyles = new Dictionary<Type, Lifestyle>()
{
{typeof(CachedUserLookupService),Lifestyle.Singleton},
{typeof(CachedTeamService),Lifestyle.Singleton}
};
List<Type> cacheableComponents = new List<Type>();
foreach (var reg in serviceRegistrations)
{
foreach (var service in reg.Services)
{
Lifestyle lifeStyle;
if (lifeStyles.TryGetValue(reg.Implementation, out lifeStyle) == false)
{
lifeStyle = Lifestyle.Singleton;
}
if (typeof(ICanRefreshCache) == service)
{
cacheableComponents.Add(reg.Implementation);
continue;
}
container.Register(service, reg.Implementation, lifeStyle);
}
}
container.RegisterAll(typeof(ICanRefreshCache), cacheableComponents);
我想在系统启动时使用 ICanRefreshCache->Refresh 方法刷新所有缓存,所以我调用:
第 1 步:
container.GetAllInstances<ICanRefreshCache>().Each(c=>c.Refresh());
第 2 步:
在我随时调用 IEventHandler<UserNameChanged>
或任何其他接口类型属于 CachedUserLookupService(或 CachedTeamService)之后,返回的实例与第 1 步实例不同,因此此注册对我没有帮助。
我需要注册 Simple Injector 才能提供以下调用。
// must return Singleton CachedUserLookupService + other
// IEventHandler<UserNameChanged> implementations
container.GetAllInstances<IEventHandler<UserNameChanged>>();
// must return Singleton CachedTeamService + other
// IEventHandler<TeamCreated> implementations
container.GetAllInstances<IEventHandler<TeamCreated>>();
// must return Singleton CachedUserLookupService
container.GetInstance<IUserLookupService>();
// must return Singleton CachedTeamService
container.GetInstance<ITeamService>();
// must return Singleton CachedUserLookupService + Singleton CachedTeamService
container.GetAllInstances<ICanRefreshCache>();
我使用以下注册解决了我的问题,但我认为还有其他方法。
List<Registration> cacheableComponents = new List<Registration>();
foreach (var reg in serviceRegistrations)
{
Lifestyle lifeStyle;
if (lifeStyles.TryGetValue(reg.Implementation, out lifeStyle) == false)
{
lifeStyle = Lifestyle.Singleton;
}
Registration registration = null;
if (lifeStyle == Lifestyle.Singleton)
{
registration = Lifestyle.Singleton.CreateRegistration(reg.Implementation, container);
}
else
{
registration = Lifestyle.Transient.CreateRegistration(reg.Implementation, container);
}
foreach (var service in reg.Services)
{
if (typeof(ICanRefreshCache) == service)
{
cacheableComponents.Add(registration);
continue;
}
container.AddRegistration(service,registration);
}
}
container.RegisterAll(typeof(ICanRefreshCache), cacheableComponents);
注意:此答案中的信息已过时。在 Simple Injector v4.
中情况发生了很大变化
如果我没理解错的话,您有一个实现多个接口的组件,并且您希望每个注册都映射到该组件的同一个实例。所以无论你解析 ITeamService
、ICanRefreshCache
还是 IEventHandler<TeamCreated>
,你都希望获得 CachedTeamService
.
的相同实例
在 Simple Injector 中执行此操作的一般方法是手动创建一个 Registration
实例并为每个接口注册它,如下所示:
var registration =
Lifestyle.Singleton.CreateRegistration<CachedTeamService>(container);
container.AddRegistration(typeof(ITeamService), registration);
container.AddRegistration(typeof(ICanRefreshCache), registration);
container.AddRegistration(typeof(IEventHandler<TeamCreated>), registration);
这是解释here。
然而,您的情况有点复杂,因为您将使用 RegisterManyForOpenGeneric
的批量注册与普通注册混合在一起。所以你应该抑制你想要的 IEventHandler<TeamCreated>
作为单身人士的批量注册,或者你需要完全 replace the registration。
然而,在这种情况下替换注册是不可能的,因为 Simple Injector 不允许您替换属于集合的注册。所以我们可以按如下方式抑制该类型的注册:
Type[] typesToRegisterManually = new[]
{
typeof(CachedTeamService)
};
container.RegisterManyForOpenGeneric(typeof(IEventHandler<>),
(service, impls) =>
{
container.RegisterAll(service, impls.Except(typesToRegisterManually));
},
assemblies);
var registration =
Lifestyle.Singleton.CreateRegistration<CachedTeamService>(container);
container.AddRegistration(typeof(ITeamService), registration);
container.AddRegistration(typeof(ICanRefreshCache), registration);
// Using SimpleInjector.Advanced
container.AppendToCollection(typeof(IEventHandler<TeamCreated>), registration);
然而,我的经验是,复杂的注册通常表明您的代码中违反了 SOLID 原则。很难就您的设计给出任何具体的反馈,但我发现具有这些多个接口的 class 很可能具有多重职责并且有多种更改原因(它们违反了 SRP), causing you to change them while adding new features (which is a OCP 违规)。
相反,我可以提供一些建议:
- 将通用
IEventHandler<T>
的逻辑移到它自己的 class 中。事件处理程序显然是不同的职责,接口名称已经告诉您这一点。这个 class 然后可以依赖旧的 class 从中提取逻辑,或者将事件处理程序所依赖的逻辑提取到它自己的 class 中,并让 'old' class 和事件处理程序都依赖于这个新的共享 class.
IUserLookupService
接口本身看起来像是一个变相的存储库,这基本上意味着您违反了 SRP、OCP 和 ISP (as explained in this article). So as that article 描述,自定义查询应该有自己的抽象:IQueryHandler<TQuery, TResult>
.这使得在它们上应用横切关注点变得非常容易,使它们成为 SOLID,并使得使用 container.RegisterManyForOpenGeneric
. 注册它们变得非常简单
留给你的是 class在大多数情况下只实现一个抽象,除了 class 实现 ICanRefreshCache
的情况。对于这种特殊情况,我建议制作一个 composite ICanRefreshCache
implementation that allows triggering all ICanRefreshCache
in the application. But instead of injecting an IEnumerable<ICanRefreshCache>
into that composite, make that composite part of your Composition Root 并让它取决于容器。这允许您在运行时搜索完整配置以查找所有 ICanRefreshCache
实现。
这样的组合可能是这样的:
public class CanRefreshCacheComposite : ICanRefreshCache
{
private readonly Lazy<InstanceProducer[]> canRefreshProducers;
public CanRefreshCacheComposite(Container container) {
this.canRefreshProducers =
new Lazy<InstanceProducer[]>(() => GetProducers(container).ToArray());
}
public void Refresh() {
foreach (var producer in this.canRefreshProducers.Value) {
var refresher = (ICanRefreshCache)producer.GetInstance();
refresher.Refresh();
}
}
private IEnumerable<InstanceProducer> GetProducers(Container container) {
return
from producer in container.GetCurrentRegistrations()
where typeof(ICanRefreshCache).IsAssignableFrom(
producer.Registration.ImplementationType)
select producer;
}
}
并且您可以通过以下方式注册:
container.RegisterSingle<ICanRefreshCache, CanRefreshCacheComposite>();
// To make sure all all ICanRefreshCache implementations that are part of
// a collection are known to the container, call Verify() when you're done
// registering.
container.Verify();
这样您就可以简单地从您的代码中依赖 ICanRefreshCache
,调用它的 Refresh
方法,然后复合材料将完成剩下的工作。
我有如下模型:
public interface IUserLookupService {
Guid[] GetDirectChilds(Guid userId);
Guid[] GetAllChilds(Guid userId);
Guid GetDepartment(Guid userId);
}
public interface ICanRefreshCache{
void Refresh();
}
public class XHandler : IEventHandler<UserNameChanged> { ... }
public class YHandler : IEventHandler<TeamCreated> { ... }
public class CachedUserLookupService
: IUserLookupService,
ICanRefreshCache,
IEventHandler<UserNameChanged>
{
private Func<ISession> _sessionFactory;
private IDictionary<Guid, UserInfo> _users = new Dictionary<Guid, UserInfo>();
public CachedUserLookupService(Func<ISession> sessionFactory) {
_sessionFactory = sessionFactory;
}
public void Handle(UserNameChanged ev) {
// change cache with new event parameters
}
public void Refresh() {
var session = _sessionFactory();
// get user from db then create userinfo...
}
public Guid[] GetDirectChilds(Guid userId) {
// code
}
public Guid[] GetAllChilds(Guid userId) {
// code
}
public Guid GetDepartment(Guid userId) {
// code
}
public class UserInfo
{
public Guid Id { get; set; }
public string FullName { get; set; }
public Guid? ParentId {get;set;}
}
}
public class CachedTeamService
: ITeamService,
ICanRefreshCache,
IEventHandler<TeamCreated>{
// similar with CachedUserLookupService
}
我的注册:
container.RegisterManyForOpenGeneric(typeof(IEventHandler<>),
(closedServiceType, implementations) =>
{
container.RegisterAll(closedServiceType, implementations);
},
applicationAssembly, typeof(Constants).Assembly);
var serviceRegistrations =
from type in applicationAssembly.GetExportedTypes()
where type.Name.EndsWith("Service")
where !type.IsAbstract
where !type.IsInterface
select new
{
Services = type.GetInterfaces(),
Implementation = type
};
var lifeStyles = new Dictionary<Type, Lifestyle>()
{
{typeof(CachedUserLookupService),Lifestyle.Singleton},
{typeof(CachedTeamService),Lifestyle.Singleton}
};
List<Type> cacheableComponents = new List<Type>();
foreach (var reg in serviceRegistrations)
{
foreach (var service in reg.Services)
{
Lifestyle lifeStyle;
if (lifeStyles.TryGetValue(reg.Implementation, out lifeStyle) == false)
{
lifeStyle = Lifestyle.Singleton;
}
if (typeof(ICanRefreshCache) == service)
{
cacheableComponents.Add(reg.Implementation);
continue;
}
container.Register(service, reg.Implementation, lifeStyle);
}
}
container.RegisterAll(typeof(ICanRefreshCache), cacheableComponents);
我想在系统启动时使用 ICanRefreshCache->Refresh 方法刷新所有缓存,所以我调用:
第 1 步:
container.GetAllInstances<ICanRefreshCache>().Each(c=>c.Refresh());
第 2 步:
在我随时调用 IEventHandler<UserNameChanged>
或任何其他接口类型属于 CachedUserLookupService(或 CachedTeamService)之后,返回的实例与第 1 步实例不同,因此此注册对我没有帮助。
我需要注册 Simple Injector 才能提供以下调用。
// must return Singleton CachedUserLookupService + other
// IEventHandler<UserNameChanged> implementations
container.GetAllInstances<IEventHandler<UserNameChanged>>();
// must return Singleton CachedTeamService + other
// IEventHandler<TeamCreated> implementations
container.GetAllInstances<IEventHandler<TeamCreated>>();
// must return Singleton CachedUserLookupService
container.GetInstance<IUserLookupService>();
// must return Singleton CachedTeamService
container.GetInstance<ITeamService>();
// must return Singleton CachedUserLookupService + Singleton CachedTeamService
container.GetAllInstances<ICanRefreshCache>();
我使用以下注册解决了我的问题,但我认为还有其他方法。
List<Registration> cacheableComponents = new List<Registration>();
foreach (var reg in serviceRegistrations)
{
Lifestyle lifeStyle;
if (lifeStyles.TryGetValue(reg.Implementation, out lifeStyle) == false)
{
lifeStyle = Lifestyle.Singleton;
}
Registration registration = null;
if (lifeStyle == Lifestyle.Singleton)
{
registration = Lifestyle.Singleton.CreateRegistration(reg.Implementation, container);
}
else
{
registration = Lifestyle.Transient.CreateRegistration(reg.Implementation, container);
}
foreach (var service in reg.Services)
{
if (typeof(ICanRefreshCache) == service)
{
cacheableComponents.Add(registration);
continue;
}
container.AddRegistration(service,registration);
}
}
container.RegisterAll(typeof(ICanRefreshCache), cacheableComponents);
注意:此答案中的信息已过时。在 Simple Injector v4.
中情况发生了很大变化如果我没理解错的话,您有一个实现多个接口的组件,并且您希望每个注册都映射到该组件的同一个实例。所以无论你解析 ITeamService
、ICanRefreshCache
还是 IEventHandler<TeamCreated>
,你都希望获得 CachedTeamService
.
在 Simple Injector 中执行此操作的一般方法是手动创建一个 Registration
实例并为每个接口注册它,如下所示:
var registration =
Lifestyle.Singleton.CreateRegistration<CachedTeamService>(container);
container.AddRegistration(typeof(ITeamService), registration);
container.AddRegistration(typeof(ICanRefreshCache), registration);
container.AddRegistration(typeof(IEventHandler<TeamCreated>), registration);
这是解释here。
然而,您的情况有点复杂,因为您将使用 RegisterManyForOpenGeneric
的批量注册与普通注册混合在一起。所以你应该抑制你想要的 IEventHandler<TeamCreated>
作为单身人士的批量注册,或者你需要完全 replace the registration。
然而,在这种情况下替换注册是不可能的,因为 Simple Injector 不允许您替换属于集合的注册。所以我们可以按如下方式抑制该类型的注册:
Type[] typesToRegisterManually = new[]
{
typeof(CachedTeamService)
};
container.RegisterManyForOpenGeneric(typeof(IEventHandler<>),
(service, impls) =>
{
container.RegisterAll(service, impls.Except(typesToRegisterManually));
},
assemblies);
var registration =
Lifestyle.Singleton.CreateRegistration<CachedTeamService>(container);
container.AddRegistration(typeof(ITeamService), registration);
container.AddRegistration(typeof(ICanRefreshCache), registration);
// Using SimpleInjector.Advanced
container.AppendToCollection(typeof(IEventHandler<TeamCreated>), registration);
然而,我的经验是,复杂的注册通常表明您的代码中违反了 SOLID 原则。很难就您的设计给出任何具体的反馈,但我发现具有这些多个接口的 class 很可能具有多重职责并且有多种更改原因(它们违反了 SRP), causing you to change them while adding new features (which is a OCP 违规)。
相反,我可以提供一些建议:
- 将通用
IEventHandler<T>
的逻辑移到它自己的 class 中。事件处理程序显然是不同的职责,接口名称已经告诉您这一点。这个 class 然后可以依赖旧的 class 从中提取逻辑,或者将事件处理程序所依赖的逻辑提取到它自己的 class 中,并让 'old' class 和事件处理程序都依赖于这个新的共享 class. IUserLookupService
接口本身看起来像是一个变相的存储库,这基本上意味着您违反了 SRP、OCP 和 ISP (as explained in this article). So as that article 描述,自定义查询应该有自己的抽象:IQueryHandler<TQuery, TResult>
.这使得在它们上应用横切关注点变得非常容易,使它们成为 SOLID,并使得使用container.RegisterManyForOpenGeneric
. 注册它们变得非常简单
留给你的是 class在大多数情况下只实现一个抽象,除了 class 实现 ICanRefreshCache
的情况。对于这种特殊情况,我建议制作一个 composite ICanRefreshCache
implementation that allows triggering all ICanRefreshCache
in the application. But instead of injecting an IEnumerable<ICanRefreshCache>
into that composite, make that composite part of your Composition Root 并让它取决于容器。这允许您在运行时搜索完整配置以查找所有 ICanRefreshCache
实现。
这样的组合可能是这样的:
public class CanRefreshCacheComposite : ICanRefreshCache
{
private readonly Lazy<InstanceProducer[]> canRefreshProducers;
public CanRefreshCacheComposite(Container container) {
this.canRefreshProducers =
new Lazy<InstanceProducer[]>(() => GetProducers(container).ToArray());
}
public void Refresh() {
foreach (var producer in this.canRefreshProducers.Value) {
var refresher = (ICanRefreshCache)producer.GetInstance();
refresher.Refresh();
}
}
private IEnumerable<InstanceProducer> GetProducers(Container container) {
return
from producer in container.GetCurrentRegistrations()
where typeof(ICanRefreshCache).IsAssignableFrom(
producer.Registration.ImplementationType)
select producer;
}
}
并且您可以通过以下方式注册:
container.RegisterSingle<ICanRefreshCache, CanRefreshCacheComposite>();
// To make sure all all ICanRefreshCache implementations that are part of
// a collection are known to the container, call Verify() when you're done
// registering.
container.Verify();
这样您就可以简单地从您的代码中依赖 ICanRefreshCache
,调用它的 Refresh
方法,然后复合材料将完成剩下的工作。