Scenario questions about SI

May 9, 2013 at 7:08 PM
I have been evaluating Simple Injector, and am having challenges with the following scenarios. I was hoping for some comments:
  1. Scoped registration and instancing are tightly coupled and code must be aware
You cannot request an instance of a service that has been registered as scoped in a global context. You can only register as scoped, and then need to retrieve as scoped. This seems to violate a principle that the consumer of the service should need to know as little as possible about details like this.
  1. No apparent way to set a scoped instance
You can manually set instances of globally scoped services, but you can't seem to set instances of scoped services. Example scenarios:
Outer, global logging instance. Some scoped calls would like to supply a scoped logging object that applies to the calling context.
Outer, global security credentials object. Calls outside a scope want to use the global security credentials, but some scoped calls provide their own credentials, want to set those.

I realize you can probably do this stuff with various specialized Factory objects and/or instance caches etc., but that seems to defeat the purpose of having a DI framework. Thanks for any feedback!
Coordinator
May 9, 2013 at 7:44 PM
I'm afraid I don't fully understand your question. Could you please supply me with some more details?
You cannot request an instance of a service that has been registered as scoped in a global context.
Are you talking about the Lifetime Scope lifestyle?
Do you mean that you can't get an instance when you didn't call container.BeginLifetimeScope()?
No apparent way to set a scoped instance
Can you supply me with a concrete (realistic) code example of what you're trying to achieve?
May 10, 2013 at 9:34 PM
Here is code for the Scoped Instance Scenario (I will provide the other scenario in another message). I think it is easiest with the concrete use- case, which is for a logging interface. We have a lot of code that is in windows services, where the code provides several different services running on different threads using different loggers. When I make a call to the middleware in a scoped instance from one of those threads, I want the middleware to use the logging object that is for that specific service. The example below is for some middleware service ISomeService that takes a ILogger which I want to supply. I want to set that instance of the Logger in the scope so that all DI resolutions within that scope will use that logger instance.

//ctor for SomeService : ISomeService

public SomeService(ILogger logger) {
...
}

// sample scenario:
Container container = new Container();

// global registrations not shown
container.Register<ISomeService, SomeService>(new LifestyleScoped());


using (container.BeginLifetimeScope())
{
container.RegisterSingle<ILogger>(anExistingLoggerInstance); // THIS IS WHAT I WANT TO DO
...
//
ISomeService svc = container.GetInstance<ISomeService >(); // SHOULD GET CONSTRUCTED WITH THE INSTANCE I SET
}
Coordinator
May 11, 2013 at 10:18 AM
Simple Injector does not allow resolving Lifetime Scoped instances outside the context of a scope. This design is deliberate. One of the earlier versions of the framework in fact behaved differently and followed the design of Autofac and other containers to return a singleton instance in the case there was no scope.

This design however proved to be bad, since returning a singleton is almost always a bad idea, since components are often registered with Lifetime Scope because they are not thread-safe and should not be reused. A great example of this is the Unit of Work pattern that Entity Framework implements with its DbContext and ObjectContext classes. We saw users spinning off background threads and forget to start a new scope, which caused their Unit of Work class to be shared over multiple threads, which lead to errors in production.

Because of this we decided to disallow this by default. Perhaps your case is different and it is okay to return a default instance. If you really want to do this, you can enable this by using a Hybrid lifestyle, as follows:
var scopedLifestyleWithSingletonFallback = Lifestyle.CreateHybrid(
    () => container.GetCurrentLifetimeScope() != null,
    new LifetimeScopeLifestyle(),
    Lifestyle.Singleton); // the fallback lifestyle
But please take a look carefully at your design if this really is what you need and if it is possible for a programmer to make a mistake in such way that things accidentally are returned as singleton, while it should have been scoped. Since you are running a Windows Service that spins of new threads to do its work, it seems very unlikely that any code should even run outside the context of a lifetime scope.

When you spin of a background thread and have any components registered as Lifetime Scope, you will have to wrap the execution with a lifetime scope. You will have to start the scope before requesting the root object, for instance:
Task.Factory.StartNew(() =>
{
    using (container.BeginLifetimeScope())
    {
         // resolve root object
         var service = container.GetInstance<ISomeService>();
         service.Do();
    }
}
This code should be part of your application's Composition Root and not of any application code. This prevents the application to know anything about it. Of course the code cannot run without outside the context of a lifetime scope, but this is quite obvious. The fact that the consumer should now as little as possible doesn't mean it should be able to work in any case. Not starting a scope is a misconfiguration and you'd rather see the application fail than silently continue in error. Neither would you want the application to continue when one of your registrations is missing. You should always fail fast.
I want the middleware to use the logging object that is for that specific service.
This seems more like Context Based Injection.

Lifetime Scoping in Simple Injector differs from the concept of Child Containers that frameworks such as Unity use. A child container allows you to do special registrations in that child container. Simple Injector does use this approach, since it is impossible to both allow creating child containers with new registrations and having good performance at the same time. If I understand correctly you want to register a new instance at the beginning of the thread and reuse that same instance for the duration of that scope. There are several ways to do this. Here is an idea:
// Registration
container.RegisterLifetimeScope<LoggerContext>();

container.RegisterLifetimeScope<ILogger>(
    () => container.GetInstance<LoggerContext>().Logger ?? 
        NullLogger.Instance);

// This class is unknown to the application, only known in 
// the composition root.
public class LoggerContext
{
    public ILogger Logger { get; set; }
}

// Part of the Compostion root that starts a use case:
Task.Factory.StartNew(() =>
{
    using (container.BeginLifetimeScope())
    {
        // Set the logger to be used during this scope.
        container.GetInstance<LoggerContext>().Logger =
            new Logger(typeof(ISomeService), "special stuff");
    
         // resolve root object
         var service = container.GetInstance<ISomeService>();
         service.Do();
    }
}
Marked as answer by dot_NET_Junkie on 2/26/2014 at 1:30 PM
May 12, 2013 at 4:05 PM
That was all very helpful, thanks!