First, I'm using Simple Injector but I think this question could apply to any DI framework.
When creating services to be registered for an application, is it considered bad practice to pass the services container into the service through its constructor?
For instance. Consider the following code.
//IServiceInterface.cs
interface IServiceInterface {}
//MyService.cs
//All standard using statements here...
using SimpleInjector;
class MyService : IServiceInterface
{
private _container {get; set;}
public MyService(Container container)
{
_container = container;
//Construct!
}
}
//MyApp.cs
public Contrainer container;
....
//My application bootstrapper method
void Bootstrap()
{
var container = new Container();
container.RegisterSingle<IServiceInterface >(() => new MyService(container));
container.Verify();
this.container = container;
}
As you can see from the method above, I define a service class that takes a Simple Injector container. When I register the container, I pass the container that is associated with my app, to the service.
This seems like a useful approach when you are defining a service that will be in a separate project, that may even be in a different namespace, that will need to register new services at some point in the application's lifecycle. However, I've not seen this done in any example and before I try anything like this, I want to makes sure that this approach is correct.
Is this behavior considered good DI practice? If not, how do you obtain your applications DI container, and register new services as they are needed from within a service?
UPDATE -- ADDITIONAL DETAILS
I decided to start using Dependency Injection for a new project that I have for two reasons. One, this is a massive project that will probably include 10-12 Visual Studio projects and, second, many of these projects contain code that I've copied and pasted from one application, to another over the years and then slightly modified as they have been needed. Enough of this, it's time to write my own business-logic framework that works with our company, as we need it to.
This first, big project seemed like the place to start with DI and my homebrew framework. To build and test this application one-layer at a time, I'm defining a lot of interfaces and "shell" service classes. This way, I can just wire up my top-level application, and update the dependencies as their projects are completed and linked into my solution.
Since this is such a LARGE application, I have services, that will need to depend on services... that will depend further on services.
My thought was that my application should only register the services that it will need to authenticate my users and load views. The View services should register the Model views. The ModelView services should register their associated Model services, which will register database connectivity services... sigh, which will EVENTUALLY register a server-side, synchronization service, that will register a local DB connection service AND web application service. Phew! Sound confusing? Well, it kind of is.
My thought was that I could define these classes that can take a Container object, and then each service would use the container to either obtain any underlying service that may already exist, or instantiate a new one if one hasn't been created.
For instane, my user Auth service may cache information through an ILocalDB
service that should be shared with my Model services. If I register all of these services when I boot up my app, the app will be sluggish an the whole registration will look pretty gnarly.
I assume there has to be an elegant solution to this. What am I missing?
The answer to the basic question in the title has already been answered by @Steven - yes it is generally considered bad practice to inject the container into any classes. @Steven is the author of SimpleInjector and firmly sticks by this principle - see here
It’s unclear to me what else you are asking but here’s some info that may help.
When the first type is resolved from the container, the container is locked for further changes. When a call to one of the registration methods is made after that, the container will throw an exception. The container can't be unlocked. This behavior is fixed and can't be changed. If it must be possible to register new types after this point, unregistered type resolution can be used.
If you have objects that take some time to instantiate then you can inject Lazy<>
instances so that an instance is not instantiated until/unless it is explicitly referenced in the calling path of your code. See here
Object Lifetime Management should deal with all the intricacies of passing in an existing instance / instantiating a new one. See here
To keep the registration process from becoming “gnarly” you could divide the process into classes that each registers an area of your solution e.g. DAL, CommandHandlers, Services, etc. These classes should be internal to the composition root that is responsible for bootstrapping the entire application and the whole bootstrap process is only called once at start-up. The composition root does not even need explicit references to all of the assemblies containing all of the implementations in your solution; it simply requires references to all of the services you have defined. See here and here