When refactoring the all-inclusive controller, I’ve added Ninject for dependency injection in the main project where ASP.NET Web API lives. I recommend this simple installation instruction in order to avoid those irritating small, blocking problems. I didn’t choose Ninject for any particular reason, which is probably the first such case in this project. Any container was just required in order to link all the projects. Creating such a bunch of them might look like overkill, but it’s actually in accordance with good practices.
One set of rules called SOLID says with its first letter that each class should have a single responsibility, do one task (not necessarily in one method). Thanks to that we make code easier to understand, test and maintain, even though it requires more units like projects or classes. The term is a bit fuzzy in general and probably often violated. A few hints I’ve met are:
- Describe what the class does in natural language. If you say “and”, then the class has too much responsibility, should be split and get the other ones as dependencies.
- Avoid creating objects with “new”. An exception is when the class exists exactly for creating objects or it performs some mapping. When you need to create an object only because it has a method suitable for the situation, then that other object should rather be injected as a dependency (or maybe there is a deeper design issue).
How to set something as a class’ dependency? Add it as a constructor parameter. It is also possible to use properties for that (I guess depending on a container), but it’s a worse solution. One drawback is that in tests objects must be created explicitly and properties could be ommitted by mistake. Another thing is that properties could need special attributes coming from the container’s library, introducing some noise in those classes. It is recommended that a container is known only by the application root (the main project) and used only for its configuration. Everything else should work automagically. Dependencies passed into a constructor have some additional meaning – an object of this class can’t exist without some other ones.
What is the role of a container? It’s an object in which we register mappings, literally pairs of <From, To>, where From is a type and To is a type or an object. In Ninject it looks like this:
kernel.Bind<ISomeClassA>().To<SomeClassA>(); ISomeClassB constantValue = new SomeClassB(...); kernel.Bind<ISomeClassB>().ToConstant(constantValue);
where kernel is an object coming from Ninject. Then, when somewhere in the application a class which depends on From is instantiated non-explicitly (without “new” – for example, a Web API or MVC controller), the container steps in and tries to resolve the dependency. Resolving means just searching for a registered mapping <From, Something> and returning that Something (either an instance of the type or a specified object). If nothing matching is registered, an exception is raised. If there are multiple mappings from the same type, then it probably depends on a container whether the former ones will be replaced or it will lead to an exception. What is also useful is the fact that containers will resolve dependencies recursively, when SomeClassA depends on something else.
Another story is that we can declare whether ISomeClassA will always have a value of new SomeClassA(), or a new object will be created only sometimes, or it will be a singleton. These options are called scopes. In Ninject they are set by chaining registrations:
In general there isn’t much magic in DI containers. Once you’ve learnt one or two it’s almost as if you’ve learnt all of them because differences are in details (like property injection mentioned above).