This post was authored by guest blogger André Obelink, a Visual Basic MVP, and published by the VBTeam on his behalf.
In my previous post, I wrote about the basics of dependency injection. I explained the technique to define an interface and injecting the dependencies to a client object. These dependencies contain the real implementation of that specific interface. Applying dependency injection makes your code more loosely coupled, which helps you in maintaining, extending, and testing your codebase. The example we ended up with works fine, but it can still be improved for some scenarios. In this post I will show the use of an IoC container, a technique what can help you to make your code much flexible. It uses dependency injection to inject the correct types, based on the defined interfaces.
Back to the basics
As said, using an IoC container can help to improve your code. But to explain what we actually can improve, we should take a closer look at the code from the previous post. Starting with the context of the code example: When you were a child, and you were thirsty, you asked your mother for something to drink. You were happy when she handed you ‘something’. But what was that ‘something’ a cup of milk, a glass of orange juice, or something different? Your mother was in charge: you asked for a drink, but you didn’t know in advance, what you eventually get. The following code is an excerpt from the full example in Visual Basic .NET source code:
In this example, the actual decision of what kind of IDrinkService is returned by the GetDrinkService() method on the Mother class is hard-coded. This isn’t ideal. If you want to change the behavior of the injector, you must change the code in that class. In more complex scenarios, this can be quite hard because you must figure out where the code lives, and it can be very time consuming, especially if you must change more implementations. It isn’t a best practice to have these ‘decision code snippets’ all around your code. It’s much more convenient to have this composition code centrally located in one method of your application. In addition to that, you must recompile and deploy your application again. In some cases, it is obvious and not a big issue. But you can think of other examples where it would be handy to do it by simply changing one or more settings in an external configuration file. For all these scenarios, you can consider using an IoC container.
Inversion of control (IoC)
IoC stands for ‘inversion of control’. An IoC container manages the flow and logic of an application, basically in one method in your code. Mostly it is initialized during startup of your application and, depending on the defined scope, it is responsible for returning all the actual implementations for the configured interfaces. This is quite a different approach from what we are used to as more procedural programmers. You can see it as a generic framework for serving types, based on configuration. The definition of IoC on Wikipedia states more or less the same: “In software engineering, inversion of control (IoC) is a design principle in which custom-written portions of a computer program receive the flow of control from a generic framework. A software architecture with this design inverts control as compared to traditional procedural programming: in traditional programming, the custom code that expresses the purpose of the program calls into reusable libraries to take care of generic tasks, but with inversion of control, it is the framework that calls into the custom, or task-specific, code.”
There are several IoC frameworks in the market today, most of them in the open-source space. If you search on the internet, you can see there are many of them. I easily found around 40! The most well-known IoC frameworks for .NET are Unity, Ninject, SimpleInjector, and Autofac. But which one should you choose? That depends… Is the framework still under active development? Most of them have their repositories on GitHub, so it’s easy to verify when the last code was checked in and when the latest version was released. Another rationale could be performance of the various frameworks. It turns out that some are substantially faster than others. Or, and I think this applies in many scenarios, how good is the integration with other components? For example, with the Model-View-ViewModel (MVVM) framework you want to use in your application?
In this post, I’ll use Autofac. But remember that fundamentally all frameworks use the same concept. They differ only in syntax and the options to configure them.
Concept of an IoC container
As said, most IoC frameworks are based on the same concept. But what is that concept? During initialization of your application, you register the types to use for the various interfaces. You ‘tell’ to the IoC container what type it should return. If the application ‘asks’ for an IDrinkService, it must deliver for example the MilkDrinkService. And this is, in short, what an IoC container does. And of course, there are several configurations possible, like for example the scope of the object. Should it be instantiated as new object every time it’s needed? Or is the singleton pattern more suitable? Or is configuration hardcoded only or can you do it also with help of an external configuration file? But at the end, the main concept is the same.
Let’s see how this works out in code. In my daily life, the different implementations of an interface are mostly implemented as separate assemblies. We will take the same example as in the previous post, but we split them up in more projects.
- Create a new Console App and name it DependencyInjectionVBPart02. Rename vb to MainModule.vb. Go to the Properties Window of this project and set the Startup object to Sub Main.
- Add a new Class Library project and name it Interfaces. Delete Class1.vb and add an Interface with the name IDrinkService.vb.
- Add a new Class Library project and name it Milk. Rename Class1.vb to DrinkService.vb.
- Add a new Class Library project and name it OrangeJuice. Rename, just as in the previous step, Class1.vb to DrinkService.vb.
All our projects are created. Now we must set the references between them. We’ve created a separate project that contains the interface, in our case the IDrinkService. The reason why this is extracted to a separate assembly is that all the other projects must have knowledge of this generic part of our software. The Console App, because that code will consume the interface, and the other two libraries, because they will implement that interface. For now, we will also set references from the Console App to the two actual implementation projects. In the end of this post, you can remove those references to see a very flexible scenario. But for now:
- Add in DependencyInjectionVBPart02, Milk and DependencyInjectionVBPart02.OrangeJuice a project reference to DependencyInjectionVBPart02.Interfaces.
- Add in DependencyInjectionVBPart02 a project reference to Milk and DependencyInjectionVBPart02.OrangeJuice.
After we have set these references, we will define the IDrinkService and implement them in the other two class libraries. Change the code of IDrinkService.vb in DependencyInjectionVBPart02.Interfaces to:
In DependencyInjectionVBPart02.Milk, you implement the specific business logic to drink milk.
And do the same for the orange juice implementation in DependencyInjectionVBPart02.OrangeJuice.
Now it’s time to create the object that will consume the interface, in terms of the previous post: the client object. Add a new class to the Console App and name it Child.vb. Add the following code:
Let us reflect a bit on what we have created until now. We have a class named Child, that makes use of the IDrinkService interface. Depending on the specific scenario, it gets from the injector, which in our example is an Autofac IoC container, an instance of the Milk.DrinkService or OrangeJuice.DrinkService. As said in the first post, the client object isn’t aware of the actual implementations. When a new Child is instantiated, it gets one of the two DrinkServices injected.
Finally, we are at the point where the IoC container comes in place. As shown at the beginning of the post, the Mother was used as injector and it was responsible for creating the instance of, for example, the MilkDrinkService. Creation of new specific objects from code is what you want to prevent when using an IoC container. So, this is where the code in this post will be really going different.
Add to our Console App the Autofac NuGet Package.
PM> Install-Package Autofac -Version 4.6.0
To use an IoC container, we must define the container and build or construct it using a ContainerBuilder. Add the following code to MainModule.vb:
The InitializeContainer() method is responsible for registering or mapping of the implementation types to the corresponding interfaces. As you can see, we are ‘telling’ the container that when we are ‘asking’ for an object of the type IDrinkService that an object of the type Milk.DrinkService must be returned. You can imagine that you can register more than one type in this method, and we will do this later.
I think you want to see this code in action, but we aren’t finished yet. What is missing is the code to call all this magic. Add the following code to Sub Main():
Hit F5 and… voila!
We can go one step further and demonstrate a feature of Autofac to support constructor injection automatically. Let’s introduce a new interface IChild. Add a new interface to our class library with interfaces, DependencyInjectionVBPart02.Interfaces. Name it IChild.vb and add the following code:
Modify the current Child.vb class to implement the interface just created:
Remember, introducing a new interface means that we must register this type in our InitializeContainer() method. Modify this method and add a new line under the existing one where we register the Milk.DrinkService.
Now, after we’ve registered the IChild interface, we can ask the IoC container to resolve the Child object. Run our application and note that this is also working as expected.
Let’s summarize what’s happening here:
- The Main() method asks the container for an IChild;
- The container sees that IChild maps to Child, so it starts creating a Child;
- The container sees that the Child needs an IDrinkService in its constructor;
- The container sees that IDrinkService maps to DrinkService, so it creates a new Milk.DrinkService instance;
- The container uses the new DrinkService instance to finish constructing Child;
- The container returns the fully constructed Child to the Main() method to consume.
As you can see, it’s quite impressive how smart Autofac, and most of the other IoC containers, are in dealing with this kind of dependency-resolving and injection.
Use of a configuration file
What you’ve seen is pretty cool, but it can still a bit cooler. The construction of our container is still hard-coded in our application. Even though this way of configuration is encouraged by Autofac, in some scenarios you need ultimate flexibility. An example could be that you want to change the behavior of your software, without recompiling your application. For these kinds of technical requirements, you can extract the configuration out of the source code and place it in an external file. To establish this, we must add two other NuGet packages to the Console App: Autofac.Configuration and Microsoft.Extensions.Configuration.Json. Use the NuGet Package Manager Window in Visual Studio as shown earlier or enter the following commands in the Package Manager Console:
PM> Install-Package Autofac.Configuration -Version 4.0.1
PM> Install-Package Microsoft.Extensions.Configuration.Json -Version 1.1.2
When installing these two NuGet packages, many other packages are also pulled in. If you need to support XML files instead of JSON, you can install Microsoft.Extensions.Configuration.Xml instead of the JSON-variant. The basic steps to getting configuration set up with an external file and your application are:
- Set up your configuration in JSON files that can be read by Extensions.Configuration. JSON configuration uses Microsoft.Extensions.Configuration.Json implementation.
- Build the configuration using the Extensions.Configuration.ConfigurationBuilder.
- Create a new Configuration.ConfigurationModule and pass the built Microsoft.Extensions.Configuration.IConfiguration into it.
- Register the Configuration.ConfigurationModule with your container.
This theory translated to Visual Basic .NET means that we have to rewrite our InitializeContainer() method.
If you’ve read the basic steps thoroughly, you might have noticed that we skipped the first step. We still need to create the configuration file. Add to the Console App an additional item of type JSON file. Give it the name Autofac.json. Go to the properties of this file in the Solution Explorer and set the value of ‘Copy to Output Directory’ to the value of ‘Copy if newer’. This ensures that our executable can find the JSON file in its own directory when is executed. Copy and paste the following JSON into this file.
The format of this file – and all possible options – is described in detail on the web site of Autofac.But in the end, what you can see in this file is the same as what we wrote in the code. We register the type and map it to its corresponding interface. The first parameter of the ‘type’ attribute is the name of the class, including the full namespace. After that and separated by a comma, the second parameter. This is the name of the assembly where this type or interface lives (without the extension .EXE or .DLL).
When you run the application, you can see that it still works as expected!
In theory, you can now delete the references set in the Console App to DependencyInjectionVBPart02.Milk and DependencyInjectionVBPart02.OrangeJuice. Your application doesn’t have to know about these implementations. It’s all done by configuration in the external JSON file. But, if you do so, be aware that if the references are missing, these class libraries won’t be built automatically when you start the application. When these assemblies already exist in the output directory and nothing has changed, there is no problem. But when you changed something in a class library and you see that the old logic is still used, don’t forget to choose Rebuild Solution. You won’t be the first person who encounters this ‘problem’ and you will definitely not be the last!
In this post, I’ve showed you how to use an IoC container. Using this technique can make your code much more flexible and easier to maintain. I recommend you to give it a try in your current, or one of your next projects. It’ll make your life much easier.