Dependency injection is a widely used concept in most of the .NET applications. One reason for it may be that it comes straight out of the box in .NET web API but it also provides some important other benefits.
However, when starting a new console application, the dependency injection is not available out of the box, let's add it!
Setup 🧰
The setup is pretty minimal and we only have to create our .NET console application:
~$ dotnet new console -o DepencyInjectionInConsoleApp
Our simple console application 📦
For our example, we will work with a text editor, which will have a spell checker.
Let's start by creating our spell checker:
// ISpellChecker.cs
namespace DependencyInjectionInConsoleApp;
public interface ISpellChecker
{
bool IsValid(string text);
}
public class VeryBadSpellChecker : ISpellChecker
{
public bool IsValid(string text)
=> true;
}
We can now create our text editor:
// TextEditor.cs
namespace DependencyInjectionInConsoleApp;
public class TextEditor
{
private readonly ISpellChecker _spellChecker;
public TextEditor(ISpellChecker spellChecker)
=> _spellChecker = spellChecker;
public void Edit(string text)
{
if (!_spellChecker.IsValid(text))
{
throw new InvalidOperationException("Text is not valid");
}
// Process the text
Console.WriteLine("Text processed");
}
}
Finally, let's create our editor and call it:
// Program.cs
using DependencyInjectionInConsoleApp;
var spellChecker = new VeryBadSpellChecker();
var textEditor = new TextEditor(spellChecker);
textEditor.Edit("Hello, world!");
Optionally, we can run out program to test our setup:
~/DepencyInjectionInConsoleApp$ dotnet run
Text processed
We're all set !
The issue
In our example we only have a few classes and a single dependency of TextEditor
on ISpellChecker
but in a real project we would probable have dozens of each.
In that case, creating our objects in the Program.cs
file would not be a good solution as it will lead to many lines of code purely dedicated to the creation of objects, something like:
var logger = new SpecificLogger();
var databaseContext = new DatabaseContext(logger);
var spellChecker = new FrenchSpellChecker(logger);
var currentUserService = new CurrentUserService(logger, databaseContext)
var textEditor = new TextEditor(currentUserService, spellChecker);
// and so on
Moreover, if we ever change the dependency of an object, we might have to go through our whole initialization process. Worse, if we are dealing with to different implementation of the same interface, we better be careful:
var spellChecker1 = new FrenchSpellChecker();
var spellChecker2 = new EnglishSpellChecker();
// ...
var englandAmbassyTextEditor = new TextEditor(spellChecker1);
// ^ We probably not meant it to be the french one !
Mixing up dependencies here would be a hard catch since it would be buried under many other lines of code.
That's some problems that dependency injection could have fixed, we better do so!
Bringing dependency injection 💉
When I first faced the challenge of bringing dependency injection in a console application, I expected it to be a harsh process but, to my surprise, it is really not.
First, we would need to add the Microsoft's dependency injection extensions package:
~DepencyInjectionInConsoleApp$ dotnet add package Microsoft.Extensions.DependencyInjection
We can now configure our dependency injection container:
using Microsoft.Extensions.DependencyInjection;
var serviceProvider = new ServiceCollection()
.AddTransient<ISpellChecker, VeryBadSpellChecker>()
.AddTransient<TextEditor>()
.BuildServiceProvider();
And finally, get rid of our manual initialization and retrieve our TextEditor
from our dependency injection container:
- var spellChecker = new VeryBadSpellChecker();
- var textEditor = new TextEditor(spellChecker);
+ var textEditor = serviceProvider.GetRequiredService<TextEditor>();
textEditor.Edit("Hello, world!");
We can now run our program once again to verify that nothing changed:
~/DepencyInjectionInConsoleApp$ dotnet run
Text processed
And we're done !
Take aways
Starting from our simple console application, we successfully brought the power of the dependency injection into our project making it more straightforward when invoking objects and easier to update their dependencies.
In this example we used the container provided by Microsoft but they are several others that also exist, chose them according to your needs and feel!
Top comments (1)
Great write-up, thank you!