I'm using Simple Injector to inject dependencies into my objects via Constructor Injection.
For a particular set of objects (all derived from a common abstract base class) I inject a factory instead of the concrete object so that I can determine at run-time which derived instance should be injected. The factory is registered with the container as a singleton, as are the derived instances.
After registering all of my objects with the DI Container I call the Container.Verify() method to verify my configuration. The problem is, the implementation of this method creates an instance of every type registered with the container. These derived instances are expensive to create and their creation has side-effects (they come from legacy code that is being updated to use DI). Long-term I'll get rid of the side-effects but short term it's not feasable.
How can I tell the container not to verify these derived instances?
I want to keep the Verify() call (at the very least for debug builds) but can't accept these instances being instantiated by the Verify() call. Furthermore, they need to be registered with a Singleton Lifestyle so I can't just not register them with the container.
One solution I came up with is to not register the derived objects with the container (thereby making them non-verifiable), and then have the (Singleton Lifestyle) Factory implementation cache the first instance it creates. It works, but it's dirty, and if someone explicitly requests an instance of the derived type somewhere else (unlikely) a new instance would be created (because the default Lifestyle is Transient).
Here's an example of the class structure I have:
public class AbstractBase { }
public class Derived1 : AbstractBase { }
public class Derived2 : AbstractBase { }
public class Derived3 : AbstractBase { }
public class MyFactory
{
private readonly Func<Derived1> _factory1;
private readonly Func<Derived2> _factory2;
private readonly Func<Derived3> _factory3;
public MyFactory(Func<Derived1> factory1, Func<Derived2> factory2, Func<Derived3> factory3)
{
_factory1 = factory1;
_factory2 = factory2;
_factory3 = factory3;
}
public AbstractBase Create()
{
if (AppSettings.ProductType == ProductType.Type1)
return _factory1();
if (AppSettings.ProductType == ProductType.Type2)
return _factory2();
if (AppSettings.ProductType == ProductType.Type3)
return _factory3();
throw new NotSupportedException();
}
}
And the registrations with the DI Container:
Container container = new Container();
container.RegisterSingle<Derived1>();
container.RegisterSingle<Derived2>();
container.RegisterSingle<Derived3>();
container.RegisterSingle<Func<Derived1>>(() => container.GetInstance<Derived1>());
container.RegisterSingle<Func<Derived2>>(() => container.GetInstance<Derived2>());
container.RegisterSingle<Func<Derived3>>(() => container.GetInstance<Derived3>());
container.RegisterSingle<MyFactory>();
container.Verify(); // <-- will instantiate new instances of Derived1, Derived2, and Derived3. Not desired.
答案 0 :(得分:3)
根据您的问题,我了解您非常了解当前方法的缺点,并且您目前正在处理一些无法在一次迭代中更改的遗留代码。但是,由于其他人也会阅读这篇文章,我想做一遍injection constructors should be simple, fast and reliable。
解决这个问题,回答问题:不,没有办法在Simple Injector中标记要跳过注册的注册。
在调用InstanceProducer
之前为容器创建的所有Verify()
个实例都将得到验证(除非它们之前是垃圾回收)。通常,当您调用InstanceProducer
重载时,会隐式为您创建Register
个实例,但您也可以通过调用InstanceProducer
来创建新的Lifestyle.CreateProducer
个实例。这些生成器不会成为任何对象图的一部分(这意味着Simple Injector不会使用它们来自动连接其他类型),并且它们充当根类型。然而,容器仍然跟踪那些生产者,并且验证也将适用于他们。
所以这里的技巧是在验证过程之后触发创建新的InstanceProducer
实例。您可以通过调用Lifestyle.CreateProducer
来执行此操作,也可以通过解析未注册的具体类型来执行此操作,就像您在示例中所做的那样。当然,解决未注册类型的缺点是默认情况下它会被解析为瞬态。有两种方法可以解决这个问题。您可以自己缓存实例,也可以指示容器将该特定类型创建为单例。
自己进行缓存可能如下所示:
var lazy1 = new Lazy<Derived1>(container.GetInstance<Derived1>);
container.RegisterSingle<Func<Derived1>>(() => lazy1.Value);
然而,自己进行缓存有一个缺点,就是你使诊断系统失明,并且无法为你发现任何Lifestyle mismatches。所以更好的选择是override the lifestyle selection behavior,看起来像:
// Custom lifestyle selection behavior
public class AbstractBaseDerivativesAsSingleton : ILifestyleSelectionBehavior {
public Lifestyle SelectLifestyle(Type serviceType, Type implementationType) {
typeof(AbstractBase).IsAssignableFrom(implementationType)
? Lifestyle.Singleton
: Lifestyle.Transient;
}
}
// Usage
var container = new Container();
container.Options.LifestyleSelectionBehavior =
new AbstractBaseDerivativesAsSingleton();
container.RegisterSingle<Func<Derived1>>(() => container.GetInstance<Derived1>());
解决此问题的另一种方法是使用InstanceProducers
调用自己创建Lifestyle.CreateProducer
。既然你想要单身,你就必须打电话给Lifestyle.Singleton.CreateProducer
。需要在调用验证后创建这些生成器,因此您仍然需要使用Lazy作为延迟机制:
// This factory should be part of your composition root,
// because it now depends on the container.
public class MyFactory : IMyFactory
{
private readonly Container container;
private readonly Dictionary<ProductType, Lazy<InstanceProducer>> producers;
public MyFactory(Container container) {
this.container = container;
this.producers = new Dictionary<ProductType, Lazy<InstanceProducer>>
{
{ProductType.Type1, new Lazy<InstanceProducer>(this.CreateProducer<Derived1>)},
{ProductType.Type2, new Lazy<InstanceProducer>(this.CreateProducer<Derived2>)},
{ProductType.Type3, new Lazy<InstanceProducer>(this.CreateProducer<Derived3>)}
};
}
public AbstractBase Create() {
return (AbstractBase)this.producers[AppSettings.ProductType].GetInstance()
}
private InstanceProducer CreateProducer<T>() where T : AbstractBase {
Lifestyle.Singleton.CreateProducer<AbstractBase, T>(this.container);
}
}
还可以考虑将工厂更改为调解员或代理。工厂通常not the right abstraction,因为它们通常只会增加复杂性。相反,你可以创建一个代理,它接受相同的接口,并将调用委托给真实实例(你仍然在后台使用类似工厂的行为):
public class AbstractBaseAppSettingsSwitchProxy : AbstractBase
{
private readonly IMyFactory factory;
public AbstractBaseAppSettingsSwitchProxy(IMyFactory factory) {
this.factory = factory;
}
public override void SomeFunction() {
this.factory.Create().SomeFunction();
}
}
使用此代理,您可以向任何消费者隐藏有多个可能的AbstractBase实现的事实。消费者可以简单地与AbstractBase
进行通信,就像总是只有一个人一样。这可以使您的应用程序代码更清晰,消费者代码更简单,并使消费者更容易测试。
如果代理不是一个选项,您仍然可以考虑介体模式。中介与上面的代理大致相同,但区别在于它有自己的接口。所以它很像工厂(有自己的接口),但区别在于中介负责调用委托对象。它不会将实例返回给消费者。
我知道这些解决方案可能不适用于您,因为AbstractBase
的结构。如果你有一个胖基类,其中一些方法是虚拟的而其他方法没有,那么这可能很难。这些解决方案通常仅适用于精心设计的(SOLID)系统。但实际上它是如何运作的。我的经验是,如果没有SOLID代码,一切都会变得很麻烦。我们作为软件开发人员的主要工作之一是降低软件的总拥有成本,最好的方法之一是将SOLID原则应用于我们的软件。
最后一点说明。在我看来,你正在阅读工厂内的一些配置值。如果在应用程序的配置文件中定义了此值,则只能通过重新启动应用程序来更改该值(这是IIS自动为您执行的操作)。如果这是这样的配置值,你实际上根本不需要所有这些无意义。您可以简单地进行以下注册:
Container container = new Container();
container.RegisterSingle(typeof(AbstractBase, GetConfiguredAbstractBaseType()));
private static Type GetConfiguredAbstractBaseType() {
switch (AppSettings.ProductType) {
case ProductType.Type1: return typeof(Derived1);
case ProductType.Type2: return typeof(Derived2);
case ProductType.Type3: return typeof(Derived3);
default: throw new NotSupportedException();
}
}
当然,这又让我们回到最初无法验证的问题。但是我们可以用代理再次解决这个问题:
public class LazyAbstractBaseProxy : AbstractBase
{
private readonly Lazy<AbstractBase> lazy;
public LazyAbstractBaseProxy(Lazy<AbstractBase> lazy) {
this.lazy = lazy;
}
public override void SomeFunction() {
this.lazy.Value.SomeFunction();
}
}
Type type = GetConfiguredAbstractBaseType();
var lazy = new Lazy<InstanceProducer>(() =>
Lifestyle.Singleton.CreateProducer(typeof(AbstractBase), type, container));
container.RegisterSingle<AbstractBase>(new LazyAbstractBaseProxy(
new Lazy<AbstractBase>(() => (AbstractBase)lazy.Value.GetInstance()));
如果不可能,您甚至可以跳过工厂并直接向消费者注入Func:
Type type = GetConfiguredAbstractBaseType();
var lazy = new Lazy<InstanceProducer>(() =>
Lifestyle.Singleton.CreateProducer(typeof(AbstractBase), type, container));
container.RegisterSingle<Func<AbstractBase>>(() => (AbstractBase)lazy.Value.GetInstance());
答案 1 :(得分:0)
您可以注册Func<T>
Lazy<T>
,InstanceProducer
将加载private static Lazy<T> Lazy<T>(Func<T> func) => new Lazy<T>(func);
public static void RegisterDelayedFunc<T>(this Container container, Lifestyle lifestyle)
where T : class
{
var lazy = Lazy(() => lifestyle.CreateProducer<T, T>(container));
container.RegisterSingleton<Func<T>>(() => lazy.Value.GetInstance());
}
Container container = new Container();
container.RegisterDelayedFunc<Derived1>(Lifestyle.Singleton);
container.RegisterDelayedFunc<Derived2>(Lifestyle.Singleton);
container.RegisterDelayedFunc<Derived3>(Lifestyle.Singleton);
container.RegisterSingleton<MyFactory>();
懒惰,如下所示:
protected override void OnException(ExceptionContext filterContext)
{
if (filterContext.Exception is MyException)
{
var controllerName = (string)filterContext.RouteData.Values["controller"];
var actionName = (string)filterContext.RouteData.Values["action"];
var model = new HandleErrorInfo(filterContext.Exception, controllerName, actionName);
filterContext.Result = new ViewResult
{
ViewName = actionName,
ViewData = new ViewDataDictionary<HandleErrorInfo>(model),
TempData = filterContext.Controller.TempData
};
filterContext.HttpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
}
base.OnException(filterContext);
}
答案 2 :(得分:0)
要回答一个主要问题:如何跳过对容器中对象的验证。
您可以这样做:
Container container = new Container();
Lifestyle singletonLifestyle = Lifestyle.CreateHybrid(
lifestyleSelector: () => !container.IsVerifying,
trueLifestyle: Lifestyle.Singleton,
falseLifestyle: Lifestyle.Transient);
container.Register<TConcrete>(singletonLifestyle);