The crux of my misunderstanding is that I'm looking to directly Resolve() a type within a nested method called as a result of an OnActivating event, for the same singleton type, and autofac is attempting to create a second instance of that singleton.
The much, much longer version:
First a complete example, and then I'll summarize:
public static class AutofacTest
{
public static void Test()
{
var builder = new ContainerBuilder();
// Register the environment as a singleton, and call Initialize when created
builder.RegisterType<Environment>().AsSelf().As<IEnvironment>().SingleInstance().OnActivating(e => e.Instance.Initialize());
// Register the simulator, also a singleton and dependent on
builder.RegisterType<Simulator>().AsSelf().As<ISimulator>().SingleInstance();
// Register a simple class, that needs an initialized environment
builder.RegisterType<IndependentClass>();
// Build/scope
var context = builder.Build();
var scope = context.BeginLifetimeScope();
// Register the service locator
ServiceLocator.GlobalScope = scope;
//var childScope = scope.BeginLifetimeScope(cb =>
//{
// cb.RegisterType<IndependentClass>();
//});
// Now resolve the independent class, which will trigger the environment/simulator instantiation
var inst = scope.Resolve<IndependentClass>();
}
}
public static class ServiceLocator
{
public static ILifetimeScope GlobalScope { get; set; }
}
public interface IEnvironment
{
bool IsInitialized { get; }
}
public class Environment : IEnvironment
{
private static Environment Instance;
private SampleComponent _component;
private bool _isInitialized;
public bool IsInitialized
{
get { return _isInitialized; }
}
public void Initialize()
{
if (Instance != null) throw new InvalidOperationException();
Instance = this;
// Canonical complex code which forces me into what I think is a tricky situation...
_component = new SampleComponent(SampleServiceType.SimulatedThing);
_component.Initialize();
_isInitialized = true;
}
}
public interface ISimulator { }
public class Simulator : ISimulator
{
private static Simulator Instance;
private readonly IEnvironment _environment;
public Simulator(IEnvironment environment)
{
if (Instance != null) throw new InvalidOperationException();
Instance = this;
_environment = environment;
}
}
public enum SampleServiceType
{
None = 0,
RealThing,
SimulatedThing,
}
public class SampleComponent
{
private readonly SampleServiceType _serviceType;
public SampleComponent(SampleServiceType serviceType)
{
_serviceType = serviceType;
}
public void Initialize()
{
// Sample component that has different types of repositories
switch (_serviceType)
{
case SampleServiceType.SimulatedThing:
var sim = ServiceLocator.GlobalScope.Resolve<ISimulator>();
// Create a repositiry object or something requriing the simulator
break;
}
}
}
public class IndependentClass
{
public IndependentClass(IEnvironment env)
{
if (!env.IsInitialized) throw new InvalidOperationException();
}
}
So the key points:
An
Environment
is the top level container, a simulator depends on an environment, and a component of the environment (SampleComponent
) depends on both the environment and the simulator.Critically, the component doesn't always use the simulator (not unusual), so there's a place for a factory style pattern here. I will typically use a global service locator in case like this (and I believe I understand why that can be evil, and very well might be biting me here) - but the main reason being that precisely for something like a simulator (or often for UI purposes), I don't want to take the dependency on the simulator in the constructor since it's only used in some scenarios. (More below.)
The environment should be initialized after creation. Hence the use of
OnActivating
here, which works well except for one caveat...The
IndependentClass
takes anIEnvironment
, and I want a fully initializedIEnvironment
at this point. In this case though, the Resolve of theIndependentClass
is what triggers the resolve ofIEnvironment
. So if I useOnActivated
, then we don't have the resolve problem, but the environment isn't Initialized until after the constructor is called.
The actual problem (finally!):
As written, what currently happens:
Resolve<IndependentClass>
triggers..Resolve<IEnvironment>
OnActivating<Environment>
triggersEnvironment.Initialize
...- Which then calls
SampleComponent.Initialize
... - Which calls the global scope
Resolve<IEnvironment>
.. - Which then resolves/instantiates a second
Environment
So even though I have registered Environment
as a singleton, two instances are created.
It's not a bug, it appears to be the expected behavior (since the Initialize
call occurs in OnActivating
and the instance hasn't yet be registered), but what should I do to resolve this?
I'd like to require:
That the Environment
Resolve
occurs on a deferred basis whenSampleComponent
isResolve
'd. (Since an environment isn't always needed.)That the
Environment.Initialize
call is made before the instance is passed to theSampleComponent
ctor.That
SampleComponent
not have to take anISimulator
ctor argument since it's not required usually. (But I wouldn't be opposed to restructuring the factory pattern into something more Autofac friendly, as long as I don't have to require my (non-top level) components to be Autofac aware.)
Basically, I just want to require that the Initialization call is made prior to the IEnvironment instance being used, and since the Environment
/SampleComponent
/Simulator
object graph is entirely separate, that seems like something that should be able to wired up/expressed.
Things I've tried:
Explicitly resolving the trade environment first: As noted, this works, but I find the requirement a little too constraining. Mainly because I have some optional configuration that I like to allow (via a UI or whatever) after the container is built (but before the environment is resolved), and since an environment (or simulator) isn't always required, I don't want to instantiate it until it's needed. (Same holds true with
IStartable
orAutoActivate
, unless there's an alternate way to use them I'm not seeing.)Abandoning the service locator pattern. In that case though, I'd need to express that
SampleComponent
needs to resolve anISimulator
only for certain values of serviceType, and otherwise pass null to the constructor (or Property/etc). Is there a clean way to express that?Finally, creating my own instance registration, and storing the Environment instance as a static singleton. Something like:
builder.Register(c => CreateInstance()).AsSelf().As().SingleInstance().OnActivating(e => e.Instance.Initialize());
Where:
private static Environment _globalInstance; private static Environment CreateInstance() { if (_globalInstance == null) { _globalInstance = new Environment(); } return _globalInstance; }
This works, though: 1.
OnActivating
is still called for every "new" instance. 2. Just feels way too hacky - ultimately I'm now managing the instance and construction, which is what the container is for. (It's also a little more annoying when you actually want to used the container to resolve parameters, but again can be worked around easily enough.)
So ALL that said (and I do greatly appreciate you making it this far), it seems like I have a fundamental misunderstanding here. (I'm guessing it relates to the service locator pattern and/or the haphazard factory in the SampleComponent
, but I'll stop speculating.)
I guess the real question is: What am I missing?
Trying to run your exact code from the example, I am not able to resolve
IndependentClass
because I (correctly) get an exception. The exception stack looks like a circular dependency where it nests and nests and nests the same exception, like a stack overflow:In a comment on the question you correctly noted that Autofac does support circular dependencies. That's true, but it's true in context of a single resolution cycle. The problem here is that the single resolution chain has been broken up by adding service location in the middle, specifically in the
SampleComponent.Initialize
method.No matter how you stack it - whether the problem is that you're somehow getting two singletons or you're getting this exception - it comes down to needing to break that circular dependency.
If you absolutely must use service location, one way to break the dependency is to use the
Lazy<T>
relationship. What this does is provide you with a delayed resolution for a component. In yourSampleComponent.Initialize
method, change the service location method to look like this:If you create a repository that needs the
ISimulator
try changing the constructor of that repository to take aLazy<ISimulator>
and only callLazy<ISimulator>.Value
at the very last possible moment. That will delay the resolution operation of theEnvironment
long enough to let the whole chain complete correctly the first time and get you out of that circular resolution problem.A better option would be to refactor to use DI all the way down. Right now you're sort of mixing dependency injection, service location, and manual instance construction through the code.
Environment
manually creates aSampleComponent
;SampleComponent
uses service location to get anISimulator
;ISimulator
uses DI to get anIEnvironment
. Mixing and matching like this is going to lead you into all sorts of trouble like you're seeing now.In fact, using DI all the way down means you don't actually need to implement the singleton pattern anywhere - instead just use the constructor and register things
SingleInstance
as needed.Here is an updated version of your code (in console app form) that shows some ideas of what might be done. Obviously your real code is probably more complex so I can't literally show you every possible solution to every edge case, but this is one way to break the chain. You can leverage ideas from here and from the other available implicit relationship types to figure out ways around the challenge.