使用 SimpleInjector 通过键和自动注册解析实例

Resolve instances by key and auto-registration with SimpleInjector

我正在尝试 resolve instances by key 使用 SimpleInjector。
在我的例子中,键是来自配置文件的字符串,我需要工厂根据字符串 return 正确的类型。

我使用了与上面 link 中描述的解决方案类似的解决方案,但稍作更改,因此实例可以提供自己的密钥。
(会有很多 class 实现 IFoo,所以我想用 他们的密钥自动注册它们)

这是完整的工作示例(.NET Core 控制台应用程序):
(为了便于阅读,我保持简短,所以只有一个 class 实现了 IFoo,我省略了 auto-register code

using SimpleInjector;
using System;
using System.Collections.Generic;

namespace SimpleInjectorTest1
{
    public interface IFoo
    {
        string Name { get; }
    }

    public class SpecificFoo : IFoo
    {
        public string Name { get { return "foooo"; } }
    }

    public interface IFooFactory
    {
        void Add(IFoo foo);
        IFoo Create(string fooName);
    }

    public class FooFactory : Dictionary<string, IFoo>, IFooFactory
    {
        public void Add(IFoo foo)
        {
            // use the instance's Name property as dictionary key, so I don't  
            // need to hard-code it in the code which does the registration
            this.Add(foo.Name, foo);
        }

        public IFoo Create(string fooName)
        {
            return this[fooName];
        }
    }

    public class Program
    {
        public static void Main(string[] args)
        {
            var container = new Container();

            // TODO: loop everything that implements IFoo, create 
            // an instance and add it to the factory
            var factory = new FooFactory();
            factory.Add(new SpecificFoo());
            container.RegisterSingleton<IFooFactory>(factory);
            container.Verify();

            // usage
            var factory2 = container.GetInstance<IFooFactory>();
            IFoo foo = factory2.Create("foooo");
            Console.WriteLine("Success!");
        }
    }
}

一开始效果很好,直到我意识到 SpecificFoo (以及其他 IFoo需要通过 SimpleInjector 进行依赖。

所以当我将 SpecificFoo 添加到工厂时,我需要通过 SimpleInjector 而不是 new SpecificFoo().

创建实例

所以我更改了我的代码,如下所示:

using SimpleInjector;
using System.Collections.Generic;

namespace SimpleInjectorTest2
{
    // dummy dependency
    public interface IBar { }
    public class Bar : IBar { }

    // marker interface
    public interface IFoo
    {
        string Name { get; }
    }

    public interface ISpecificFoo : IFoo
    {
        // empty by purpose
    }

    public class SpecificFoo : ISpecificFoo, IFoo
    {
        private readonly IBar bar;
        public SpecificFoo(IBar bar) { this.bar = bar; }

        public string Name { get { return "foooo"; } }
    }

    public interface IFooFactory
    {
        void Add(IFoo foo);
        IFoo Create(string fooName);
    }

    public class FooFactory : Dictionary<string, IFoo>, IFooFactory
    {
        public void Add(IFoo foo)
        {
            // use the instance's Name property as dictionary key, so I don't
            // need to hard-code it in the code which does the registration
            this.Add(foo.Name, foo);
        }

        public IFoo Create(string fooName)
        {
            return this[fooName];
        }
    }

    public class Program
    {
        public static void Main(string[] args)
        {
            var container = new Container();
            container.Register<IBar, Bar>();

            var factory = new FooFactory();

            // TODO: loop everything that implements IFoo, create
            // an instance and add it to the factory
            container.Register<ISpecificFoo, SpecificFoo>();
            factory.Add(container.GetInstance<ISpecificFoo>());

            // The next line throws an exception because of this:
            // https://simpleinjector.readthedocs.io/en/latest/decisions.html#the-container-is-locked-after-the-first-call-to-resolve
            container.RegisterSingleton<IFooFactory>(factory);
        }
    }
}

上面已经说过,工厂注册失败是因为the container is locked after the GetInstance call.

我知道我可以改为从 Dictionary<string, Func<IFoo>> 继承工厂(如文档中的 Resolve instances by key 所示),但我需要在注册时提供字符串键,如图所示在文档的示例中:

container.RegisterSingle<IRequestHandlerFactory>(new RequestHandlerFactory
{
    { "default", () => container.GetInstance<DefaultRequestHandler>() },
    { "orders", () => container.GetInstance<OrdersRequestHandler>() },
    { "customers", () => container.GetInstance<CustomersRequestHandler>() },
});

如何使用工厂通过键解析类型,但仍然让类型自己提供它们的键?
我不想在注册码中添加像上面那样的一行,每次我添加一个新的 class 实现 IFoo.

我也读过Registration of open generic types (and answers like this one),但我认为它不适用于我的情况,因为我需要通过字符串键来解析。

考虑到您当前的设计,最简单的解决方案是简单地移动填充 Foo 的代码,直到在 Simple Injector 中完成注册:

container.Verify();
factory.Add(container.GetInstance<ISpecificFoo>());
factory.Add(container.GetInstance<ISomeOtherFoo>());

但请注意,由于工厂无限期地保留实例,因此您至少应该将所有 Foo 实例注册为容器中的单例;这允许容器对这些实例进行分析和诊断,它会立即向您显示您当前的注册中有一个 Lifestyle Mismatch

但是不是让工厂有一组实例,一个可能更灵活的方法是让工厂从容器中再次解析:

public interface IFooFactory {
    IFoo Create(string fooName);
}

public class FooFactory : Dictionary<string, Type>, IFooFactory
{
    private readonly Container container;
    public FooFactory(Container container) { this.container = container; }

    public void Register<T>(string fooName) where T : IFoo {
        this.container.Register(typeof(T));
        this.Add(name, typeof(T));
    }

    public IFoo Create(string fooName) => this.container.GetInstance(this[fooName]);
}

// Registration
var factory = new FooFactory(container);

container.RegisterSingleton<IFooFactory>(factory);

factory.Register<SpecificFoo>("foooo");
factory.Register<AnotherFoo>("another");

这里字典不仅缓存了Type实例,还在容器中进行了注册。这允许容器对完整的对象图进行分析。稍后,工厂将 Create 请求转发给容器。容器可以构建完整的对象图。

请注意,Add 方法已从 IFooFactory 抽象中删除。由于应用程序代码永远不应向字典添加实例,因此应删除此方法(这会立即简化测试)。此外,由于应用程序代码可能永远不会调用 IFoo.Name(因为它只被工厂使用),所以它也应该被删除。在我的示例中,我为 Register<T> 方法提供了名称,但另一种选择是在 Foo 实现上放置一个属性。工厂方法可以从提供的实现中读取该属性,这样您就不必自己提供它了。然而,由于这些值来自配置文件,因此不让实现与该名称相关似乎是合理的。在实现上具有属性,从抽象中隐藏命名,这很好,因为消费者不需要知道那个名字(或者他们已经知道那个名字了)。

此解决方案的缺点是无法在此处应用 IFoo 的装饰器,因为类型由其具体类型解析。如果这是一项要求,您可以通过应用 resolve instances by key 中的 RequestHandlerFactory 示例来解决此问题,该示例将 InstanceProcucer 个实例存储为字典值。

这是一个使用泛型的完整实现:​​

public interface IKeyedFactory<TKey, out TValue> : IDictionary<TKey, Type>
{
    TValue Get(TKey match);
}

public class KeyedFactory<TKey, TValue> : Dictionary<TKey, Type>, IKeyedFactory<TKey, TValue>
{
    private readonly Container _container;
    private readonly bool _autoRegister;
    private readonly Lifestyle _lifestyle;

     public KeyedFactory(Container container) : this(container, false, null)
    {
    }

     public KeyedFactory(Container container, Lifestyle lifestyle) : this(container, true, lifestyle)
    {
    }

     private KeyedFactory(Container container, bool autoRegister, Lifestyle lifestyle)
    {
        _container = container;
        _autoRegister = autoRegister;
        _lifestyle = lifestyle;
    }

     public new void Add(TKey key, Type value)
    {
        if (_autoRegister)
        {
            _container.Register(value, value, _lifestyle);
        }

         base.Add(key, value);
    }

     public TValue Get(TKey source) =>
        (TValue)_container.GetInstance(this[source]);
}

public static class ContainerExtensions
{
    public static TValue GetInstance<TFactory, TKey, TValue>(this Container container, TKey match) where TFactory : class, IKeyedFactory<TKey, TValue>
    {
        var factory = (IKeyedFactory<TKey, TValue>)container.GetInstance<TFactory>();
        return factory.Get(match);
    }
}

用法

报名人数:

    interface IVerificationHandler
    {
    }

     class RemoteVerificationHandler : IVerificationHandler
    {
    }

     class InMemoryVerificationHandler : IVerificationHandler
    {
    }

     enum Source
    {
        Remote,
        InMemory
    }

     void AutoRegistrationUsage()
    {
        var container = new Container();
         //Register keyed factory by specifying key (Source) and value (IVerificationHandler)
        container.RegisterInstance<IKeyedFactory<Source, IVerificationHandler>>(new KeyedFactory<Source, IVerificationHandler>(container, Lifestyle.Transient)
        {
            { Source.InMemory, typeof(InMemoryVerificationHandler) },
            { Source.Remote, typeof(RemoteVerificationHandler) }
        });
    }

     void ManualRegistrationUsage()
    {
        var container = new Container();
         //Register keyed factory by specifying key (Source) and value (IVerificationHandler)
        container.RegisterInstance<IKeyedFactory<Source, IVerificationHandler>>(new KeyedFactory<Source, IVerificationHandler>(container, Lifestyle.Transient)
        {
            { Source.InMemory, typeof(InMemoryVerificationHandler) },
            { Source.Remote, typeof(RemoteVerificationHandler) }
        });
    }

分辨率:

    class DependencyInjectionRoot
    {
        private readonly IKeyedFactory<Source, IVerificationHandler> _factory;

         public DependencyInjectionRoot(IKeyedFactory<Source, IVerificationHandler> factory)
        {
            _factory = factory;
        }

         public void AccessDependency(Source key)
        {
            IVerificationHandler dependency = _factory.Get(key);
        }

    }

     public void ResolutionWithDependencyInjection()
    {
        var container = new Container();
        //...Register factory
        container.Register<DependencyInjectionRoot>();
        var dependencyRoot = container.GetInstance<DependencyInjectionRoot>();
        dependencyRoot.AccessDependency(Source.Remote);
    }

     public void ResolutionWithDirectContainerAccess()
    {
        var container = new Container();
        //...Register factory
        var factory = container.GetInstance<IKeyedFactory<Source, IVerificationHandler>>();
        var resolvedInstance = factory.Get(Source.Remote);
    }

     public void ResolutionWithDirectContainerAccessUsingExtensionMethod()
    {
        var container = new Container();
        //...Register factory
        var resolvedInstance = container.GetInstance<IKeyedFactory<Source, IVerificationHandler>, Source, IVerificationHandler>(Source.Remote);
    }