Context: I am trying to capture multiple events in our api using serilog and the elastic search sink, these events include: GET actions(regular web api flow) as well as login attempts (Owin). I am registering serilog like this:
container.RegisterConditional(
typeof(ICustomLogger),
c => typeof(CustomLogger<>).MakeGenericType(
c.Consumer?.ImplementationType ?? typeof(object)),
Lifestyle.Singleton,
c => true);
I am doing this for Owin registration:
app.Use(async (context, next) =>
{
using (var scope = container.BeginExecutionContextScope())
{
await next.Invoke();
}
});
Then when I call container.Verify();
the logger constructor gets called(as expected).
However when I call the logger from my OAuthAuthorizationServerProvider
implementation like this:
var logger = ObjectFactory.GetInstance<ICustomLogger>();
the constructor gets called again, meaning the singleton is not really a singleton anymore, I am totally aware of the service locator antipattern(don't really have much time to refactor that piece right now), what is interesting is that if I change the logger registration to the following (getting rid of the type piece) then only one instance is created:
container.RegisterSingleton(typeof(ICustomLogger), typeof(CustomLogger))
The reason why I am trying to use the first option is because I want to be able to use .ForContext(typeof(T));
for serilog, and in case you are wondering how I am registering the ObjectFactory
piece here it is:
Class:
public static class ObjectFactory
{
public static Container container;
public static void SetContainer(Container container)
{
ObjectFactory.container = container;
}
public static T GetInstance<T>() where T : class
{
return container.GetInstance<T>();
}
}
Registration(from my main bootstrapper):
ObjectFactory.SetContainer(container);
So my main question is: Why is only one instance created with RegisterSingleton
but multiple are created with RegisterConditional
and Lifestyle.Singleton
?
The reason multiple instances are created has nothing to do with the fact that you are using your ObjectFactory
, but simply because different closed versions of CustomLogger<T>
are created:
CustomLogger<object>
is created when resolved as root type (by calling GetInstance<ICustomLogger>()
CustomLogger<Service1>
is created when ICustomLogger
is injected into Service1
.Because a CustomLogger<object>
is a different type than a CustomLogger<Service1>
, it is impossible to have just one instance for both types. They both have to be created. This might seem weird, but consider these two classes:
public class Service1
{
public Service1(ICustomLogger logger) { }
}
public class Service2
{
public Service2(ICustomLogger logger) { }
}
At runtime, considering these two definitions, you want to create object graphs similar to the following:
var s1 = new Service1(new CustomLogger<Service1>());
var s2 = new Service2(new CustomLogger<Service2>());
In the above case, the CustomLogger
s are not cached and are, therefore, effectively transients. You might try to rewrite this to the following, but that would not be the same:
ICustomLogger logger = new CustomLogger<Service1>();
var s1 = new Service1(logger);
var s2 = new Service2(logger);
Now both service get the same single instance. However, this means that Service2
gets a CustomLogger<Service1>
which is not what you configured. You configured the dependency to be this:
typeof(CustomLogger<>).MakeGenericType(c.Consumer.ImplementationType)
Consequence of this is that when Service2
starts to log, it will look like if the messages are coming from Service1
. This would likely be incorrect behavior. Otherwise, you could have simply called RegisterSingleton<ICustomLogger, CustomLogger<Service1>>()
.
This all means that the Singleton guarantee for a RegisterConditional
that uses an implementation-type factory (as in your example) only holds for the specified closed generic type.
Note that RegisterConditional
isn't the only part in Simple Injector where you see this behavior. The following registrations do have the same effect:
container.RegisterSingleton(typeof(IFoo<>), typeof(Foo<>));
container.RegisterDecorator(typeof(IFoo<>), typeof(FooDecorator<>), Lifestyle.Singleton);
In both these cases multiple instances of closed generic versions of Foo<T>
and FooDecorator<T>
can be created, but Simple Injector guarantees that there is only one instance of every closed-generic version of Foo<T>
. With RegisterDecorator<T>
, however, even that is not guaranteed. Consider this example:
container.Collection.Append<ILogger, Logger1>(Lifestyle.Singleton);
container.Collection.Append<ILogger, Logger2>(Lifestyle.Singleton);
container.RegisterDecorator<ILogger, LoggerDecorator>(Lifestyle.Singleton);
In this case, a collection of ILogger
components is registered where each ILogger
element will be wrapped with a LoggerDecorator
. Because a LoggerDecorator
can only depend on either Logger1
or Logger2
, but not both, Simple Injector will have to create a new LoggerDecorator
instance for each element. If the LoggerDecorator
would be cached as a true Singleton, this would be the result:
private static ILogger logger = new LoggerDecorator(new Logger1());
private static loggers = new[] { logger, logger };
As there is only 1 single LoggerDecorator
, this decorator depends on Logger1
, which means that the collection of loggers only has two elements that both point to the same Logger1
instance. There is no Logger2
.
This information is reflected in the following Simple Injector documentation sections: