我正在尝试使用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之类的答案),但我认为它不适用于我的情况,因为我需要通过字符串键解析。
答案 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);
}