通过密钥解析实例并使用SimpleInjector自动注册

时间:2016-06-19 21:22:42

标签: c# dependency-injection inversion-of-control factory simple-injector

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

我使用了类似上面链接中描述的解决方案,但稍微改了一下,因此实例可以提供自己的密钥。
(会有很多类实现IFoo,所以我想自己注册 他们的密钥)

以下是完整的工作示例(.NET Core控制台应用程序):
(我保持简短的可读性,所以只有一个类实现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>() },
});

如何使用工厂按键解析类型,但仍然让类型自己提供密钥?
我不想在注册代码中添加如上所示的行,每次添加一个实现IFoo的新类。

我已经阅读了Registration of open generic types(以及this one之类的答案),但我认为它不适用于我的情况,因为我需要通过字符串键解析。

2 个答案:

答案 0 :(得分:2)

考虑到您当前的设计,最简单的解决方案是简单地移动填充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抽象中删除。由于应用程序代码永远不应该向dictionarty添加实例,因此应该删除此方法(这会立即简化测试)。此外,由于应用程序代码可能永远不会调用IFoo.Name(因为它仅由工厂使用),因此它也应该被删除。在我的示例中,我提供了Register<T>方法的名称,但另一个选项是在Foo实现上放置一个属性。工厂方法可以从提供的实现中读取该属性,这可以防止您自己提供它。但是,由于这些值来自配置文件,因此不要让实现关注该名称似乎是合理的。拥有实现的属性,隐藏了抽象的命名,这很好,因为消费者不需要知道该名称(或者他们已经知道该名称)。

这个解决方案的缺点是IFoo的装饰器不能在这里应用,因为类型是通过它们的具体类型来解决的。如果这是一项要求,您可以通过应用存储RequestHandlerFactory个实例的resolve instances by key中的InstanceProcucer示例作为字典值来解决此问题。

答案 1 :(得分:0)

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

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