使用 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);
}
我正在尝试 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);
}