Dependency Injection in .NET Console Apps using Top-Level Statements

Friday, May 26, 2023

Using Dependency Injection (DI) in a .NET Console applications using Top-Level Statements involves several steps, including setting up a service collection, adding your services, and building the service provider. Here is an example of how to do that and some tips to do this efficiently.

Create a new console application

You can use either Visual Studio or the .NET CLI. If you're using the .NET CLI, the command to create a new console application would be:

dotnet new console -n MyConsoleApp

Install the Microsoft.Extensions.DependencyInjection NuGet package

This package includes the classes you need to set up and use DI. Use the .NET CLI to install it:

dotnet add package Microsoft.Extensions.DependencyInjection

Set up Dependency Injection in your Program.cs

In the Main method, create a new ServiceCollection, add your services to it, and then build a ServiceProvider from it. You can then use the ServiceProvider to get your services and use them.

Here's a basic example:

Program.cs
using Microsoft.Extensions.DependencyInjection;
using System;
 
var services = new ServiceCollection();
services.AddTransient<IMyService, MyService>();
 
var serviceProvider = services.BuildServiceProvider();
 
var myService = serviceProvider.GetService<IMyService>();
myService.DoSomething();
 
public interface IMyService
{
    void DoSomething();
}
 
public class MyService : IMyService
{
    public void DoSomething()
    {
        Console.WriteLine("Hello from MyService!");
    }
}

In this example, the service configuration, service provider creation, and service usage all occur as top-level statements.

Then, further on, you can use the ServiceProvider to get instances of your services and call them.

For this example the service interface and implementation are also defined at the top level, but in real solutions this would be placed in seperate files.

Multiple ways to register a service

Keep in mind that there are three main ways to register a service:

1. AddTransient: A new instance of MyService is created each time you ask for an IMyService. This is ideal for lightweight, stateless services.

2. AddScoped: A new instance of MyService is created once per scope. In a web application, a scope is created per request, so you get one instance per request. But in a console application, there's typically only one scope, so AddScoped behaves like AddSingleton, unless you manually create additional scopes.

3. AddSingleton: A single instance of MyService is created and reused each time you ask for an IMyService. This is ideal for services that maintain state or hold expensive resources.

In the provided example, it doesn't make a difference because you're only resolving the service once.

Organizing your dependencies

As your application grows, it might be beneficial to manage and organize your dependencies in a separate file altogether. This approach provides a few advantages:

1. Separation of concerns: Moving the setup of your DI to a separate file or class library allows your Program.cs file to focus on the program's execution flow. This results in cleaner and more maintainable code.

2. Reduced coupling: By placing the DI setup in a separate class library, the root project doesn't need to reference all other projects. This reduces the coupling of your application and makes individual parts of your system easier to swap out or update without affecting other parts.

3. Better testability: Separating your DI setup can make it easier to switch between different configurations for testing and production environments.

Here's an example of how you might create a DependencyInjection.cs file:

DependencyInjection.cs
public static class DependencyInjection
{
    public static IServiceCollection AddApplicationServices(this IServiceCollection services)
    {
        services.AddTransient<IMyService, MyService>();
        // Add more services here...
 
        return services;
    }
}

Then, in your Program.cs file, you could use this method to add your services:

Program.cs
var services = new ServiceCollection();
services.AddApplicationServices();
 
var serviceProvider = services.BuildServiceProvider();
 
var myService = serviceProvider.GetService<IMyService>();
myService.DoSomething();

That's it! All there is to effectively use dependency injection in your .NET console applications.