如何使用简单的注入器将类型与其接口注册为单例? [英] How can i register a type with its interfaces as singleton using simple injector?

查看:55
本文介绍了如何使用简单的注入器将类型与其接口注册为单例?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有如下模型:

 公共接口IUserLookupService {
Guid [] GetDirectChilds(Guid userId);
Guid [] GetAllChilds(Guid userId);
Guid GetDepartment(Guid userId);
}

公共接口ICanRefreshCache {
void Refresh();
}

公共类XHandler:IEventHandler< UserNameChanged> {...}
公共类YHandler:IEventHandler< TeamCreated> {...}

公共类CachedUserLookupService
:IUserLookupService,
ICanRefreshCache,
IEventHandler< UserNameChanged>
{
private Func< ISession> _sessionFactory;
private IDictionary< Guid,UserInfo> _users =新的Dictionary< Guid,UserInfo>();

public CachedUserLookupService(Func< ISession> sessionFactory){
_sessionFactory = sessionFactory;
}

public void Handle(UserNameChanged ev){
//使用新的事件参数更改缓存
}

public void Refresh( ){
var session = _sessionFactory();
//从数据库获取用户,然后创建userinfo ...
}

public Guid [] GetDirectChilds(Guid userId){
//代码
}

public Guid [] GetAllChilds(Guid userId){
//代码
}

public Guid GetDepartment(Guid userId){
//代码
}

公共类UserInfo
{
公共Guid ID {get;组; }
公用字串FullName {get;组; }
公共向导? ParentId {get; set;}
}
}

公共类CachedTeamService
:ITeamService,
ICanRefreshCache,
IEventHandler< TeamCreated> {
//与CachedUserLookupService类似
}

我的注册:

  container.RegisterManyForOpenGeneric(typeof(IEventHandler<>)),
(closedServiceType,实现)=>
{
container.RegisterAll(closedServiceType,Implementations);
},
applicationAssembly,typeof(Constants).Assembly);

var serviceRegistrations =
来自applicationAssembly.GetExportedTypes()中的类型
其中type.Name.EndsWith( Service)
其中!type.IsAbstract
其中!type.IsInterface
选择新的
{
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)
{
foreach(服务服务中的var service)
{
生活方式lifeStyle;
if(lifeStyles.TryGetValue(reg.Implementation,out lifeStyle)==否)
{
lifeStyle = Lifestyle.Singleton;
}

if(typeof(ICanRefreshCache)== service)
{
cacheableComponents.Add(reg.Implementation);
继续;
}

容器。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实例不同,因此此注册无济于事。 / p>

我需要简单的喷油器注册才能在电话下方提供。

  //必须返回Singleton CachedUserLookupService +其他
// IEventHandler< UserNameChanged>实现
container.GetAllInstances< IEventHandler< UserNameChanged>>>();

//必须返回Singleton CachedTeamService +其他
// IEventHandler< TeamCreated>实现
container.GetAllInstances< IEventHandler< TeamCreated>>>();

//必须返回Singleton CachedUserLookupService
容器。GetInstance< IUserLookupService>();

//必须返回Singleton CachedTeamService
container.GetInstance< ITeamService>();

//必须返回Singleton CachedUserLookupService + Singleton CachedTeamService
容器。GetAllInstances< ICanRefreshCache>();


解决方案

注意:信息这个答案已经过时了。在Simple Injector v4中,情况发生了很大变化。



如果我正确理解,您有一个实现多个接口的组件,并且您希望每个注册都映射到该实例的相同实例。零件。因此,无论您解析的是 ITeamService ICanRefreshCache 还是 IEventHandler< TeamCreated> code>,您想获得 CachedTeamService 的相同实例。



执行此操作的一般方法Simple Injector中的方法是手动创建 Registration 实例,并为每个接口注册它,如下所示:

  var注册= 
Lifestyle.Singleton.CreateRegistration< CachedTeamService>(容器);

container.AddRegistration(typeof(ITeamService),registration);
container.AddRegistration(typeof(ICanRefreshCache),registration);
container.AddRegistration(typeof(IEventHandler< TeamCreated>),注册);

这是此处



但是您的情况有点复杂,因为您将使用 RegisterManyForOpenGeneric 的批处理注册与常规注册混合使用。因此,您应该取消要作为单例的 IEventHandler< TeamCreated> 的批注册,或者您需要完全替换注册



替换但是,在这种情况下无法注册,因为Simple Injector不允许您替换集合中的注册。因此,我们可以按以下方式禁止该类型的注册:

  Type [] typesToRegisterManually = new [] 
{
typeof(CachedTeamService)
};

容器.RegisterManyForOpenGeneric(typeof(IEventHandler<>)),
(服务,impls)=>
{
container.RegisterAll(service,impls.Except (typesToRegisterManually));
},
程序集);

var registration =
Lifestyle.Singleton.CreateRegistration< CachedTeamService>(容器);

container.AddRegistration(typeof(ITeamService),registration);
container.AddRegistration(typeof(ICanRefreshCache),registration);

//使用SimpleInjector.Advanced
容器。AppendToCollection(typeof(IEventHandler< TeamCreated>),注册);

不过,我的经验是,复杂的注册通常表明您的代码违反了SOLID原则。很难对您的设计提供任何具体的反馈,但是我发现具有这些多个接口的类很可能具有多重职责,并且有多种更改理由(它们违反了 SRP ),导致您在添加新功能时进行更改(这是违反了OCP )。



相反,我可以建议一些事情:





这给您留下的是在大多数情况下只实现一个抽象的类,除非类实现了 ICanRefreshCache 。对于这种特殊情况,我建议制作一个复合 ICanRefreshCache 实现,它可以触发应用程序中的所有 ICanRefreshCache 。但是,不要将 IEnumerable< ICanRefreshCache> 注入该组合,而是将该组合作为您的组合根,并使其取决于容器。这使您可以在运行时搜索完整的配置,以找到所有 ICanRefreshCache 的实现。



这样的组合可能看起来像这样:

 公共类CanRefreshCacheComposite:ICanRefreshCache 
{
private readonly Lazy< InstanceProducer []> canRefreshProducers;

public CanRefreshCacheComposite(Container container){
this.canRefreshProducers =
new Lazy< InstanceProducer []>((()=> GetProducers(container).ToArray()) ;
}

public void Refresh(){
foreach(this.canRefreshProducers.Value中的var producer){
var refresher =(ICanRefreshCache)producer.GetInstance() ;
refresher.Refresh();
}
}

私有IEnumerable< InstanceProducer> GetProducers(容器容器){
从容器中的生产者返回$ b $b。GetCurrentRegistrations()
其中typeof(ICanRefreshCache).IsAssignableFrom(
producer.Registration.ImplementationType)
select生产者
}
}

您可以按以下方式注册:

 容器。RegisterSingle< ICanRefreshCache,CanRefreshCacheComposite>(); 

//要确保容器知道
//集合中的所有ICanRefreshCache实现,请在完成后调用Verify()
//注册。
container.Verify();

这样,您可以简单地依赖 ICanRefreshCache 从您的代码中,调用 Refresh 方法,组合将完成其余工作。


I have models like below:

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
}

My registration:

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);

I want to refresh all cache at system startup using ICanRefreshCache->Refresh method so I call:

Step 1:

container.GetAllInstances<ICanRefreshCache>().Each(c=>c.Refresh());

Step 2:

after I called IEventHandler<UserNameChanged> or any other interfaces type its belonging to CachedUserLookupService (or CachedTeamService) any time, returning instance is different from Step 1 instance therefore this registration does not help me.

I need Simple Injector registration to supply below calls.

// 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>(); 

解决方案

NOTE: The information in this answer is outdated. Things have changed considerably in Simple Injector v4.

If I understand correctly, you have a component that implements multiple interfaces and you want each registration to map to the same instance of that component. So no matter whether you resolve an ITeamService, ICanRefreshCache, or IEventHandler<TeamCreated>, you want to get the same instance of CachedTeamService.

The general way to do this in Simple Injector is to create an Registration instance manually and register it for each interface as follows:

var registration = 
    Lifestyle.Singleton.CreateRegistration<CachedTeamService>(container);

container.AddRegistration(typeof(ITeamService), registration);
container.AddRegistration(typeof(ICanRefreshCache), registration);
container.AddRegistration(typeof(IEventHandler<TeamCreated>), registration);

This is explained here.

Your case however is a bit more complicated, because you mix batch registration using the RegisterManyForOpenGeneric with normal registrations. So you should either suppress the batch-registration of the IEventHandler<TeamCreated> that you want as singleton, or you need to replace the registration completely.

Replacing the registration in this case however is not possible, because Simple Injector does not allow you to replace an registration that is part of a collection. So we can suppress registration of that type as follows:

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);

My experience however is that complex registrations are often an indication of SOLID principle violations in your code. It's hard to give any specific feedback on your design, but I find it very likely that the classes with those multiple interfaces have multiple responsibilities and will have multiple reasons to change (they violate SRP), causing you to change them while adding new features (which is a OCP violation).

Instead, there are a few things that I can advice:

  • Move the logic for the generic IEventHandler<T> into its own class. An event handler is clearly a different responsibility and the interface name is already telling you that. This class could then depend upon the old class the logic was extracted from, or extract the logic that the event handler depends upon into its own class and let the 'old' class and the event handler both depend on this new shared class.
  • The IUserLookupService interface isself seems like a repository in disguise, and this basically means that you are violating SRP, OCP and ISP (as explained in this article). So as that article describes, custom queries deserve their own abstraction: IQueryHandler<TQuery, TResult>. This makes it very easy to apply cross-cutting concerns on them, makes them SOLID, and makes it really straightforward to register them using container.RegisterManyForOpenGeneric.

What this leaves you is classes that in most cases just implement one abstraction, except the case that a class implements ICanRefreshCache. For this special case I suggest making a 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 and let it depend on the container. This allows you to search through complete configuration at runtime to find all ICanRefreshCache implementations.

This is what such composite might look like:

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;
    }
}

And you can register it as follows:

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();

This way you can simply depend upon ICanRefreshCache from within your code, call the Refresh method on it, and the composite will do the rest.

这篇关于如何使用简单的注入器将类型与其接口注册为单例?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆