A Comprehensive Guide to Best Practices and Common Scenarios Using Dependency Injection in .NET 8 with C#10

Overview

As a vital component of modern software development, dependency injection promotes decoupling and maintainability. With .NET 8, dependency injection is a first-class citizen, so developers can manage and inject dependencies more easily. In this article, we'll explore the ins and outs of dependency injection in .NET 8, covering best practices and common scenarios.

Understanding Dependency Injection

The dependency injection design pattern facilitates the inversion of control (IoC) in an application by injecting the required dependencies from outside rather than creating instances of them within a class. This promotes modularity, testability, and ease of maintenance.

Add Required NuGet Packages

If you want to use dependency injection in .NET 8, you will need to install the necessary packages. The built-in DI container is part of the Microsoft.Extensions.DependencyInjection package.

Three Ways to Add NuGet Packages

In this section of the article, we will look at the three ways to get the NuGet Packages installed on our .net C# project. I have kept the steps very simple to follow.

Using Visual Studio 2022 IDE

Step 1

Open our Project in Visual Studio 2022 IDE

Visual Studio 2022 IDE Step 1

Step 2

Right-click on our Project in the Solution Explorer.

Visual Studio 2022 IDE Step 2

Step 3

Select the Manage NuGet Packages

Visual Studio 2022 IDE Step 3

Step 4

In the Browse Tab search for Microsoft.Extensions.DependencyInjection

Visual Studio 2022 IDE Step 4

Step 5

Select the page and click Install.

Visual Studio 2022 IDE Step 5

Using Visual Studio Code IDE

Step 1

Open Visual Studio Code: Launch Visual Studio Code on our computer.

Visual Studio Code IDE Step 1

Step 2

Open our Project: Open the project in which you want to install the NuGet package.

Visual Studio Code IDE Step 2

Step 3

Open Terminal: In Visual Studio Code, open the integrated terminal. You can do this by navigating to the View menu and selecting "Terminal" or by pressing Ctrl+` (backtick) on your keyboard.

Visual Studio Code IDE Step 3

Step 4

Navigate to Your Project Directory: Use the terminal to navigate to the directory of your project where the project file (.csproj) is located.

Visual Studio Code IDE Step 4

Step 5

Install the NuGet Package: In the terminal, type the following command to install the NuGet package using the .NET CLI:

dotnet add package Microsoft.Extensions.DependencyInjection

Visual Studio Code IDE Step 5

Step 6

Press Enter: After typing the command, press Enter to execute it. The .NET CLI will download and install the specified NuGet package along with its dependencies.

Visual Studio Code IDE Step 6

Step 7

Verify Installation: Once the installation process is complete, you can verify that the NuGet package was installed successfully by checking your project file (.csproj) to see if the package reference has been added.

Visual Studio Code IDE Step 7

Using .NET CLI

Step 1

Open Command Prompt :Open Command Prompt (Windows).

Command Line CLI Step 1

Step 2

Navigate to Your Project Directory: Use the cd command to navigate to the directory of your project where the project file (.csproj) is located.

Command Line CLI Step 2

Step 3

Install the NuGet Package: In the Command Prompt or Terminal, use the following command to install the NuGet package:

dotnet add package Microsoft.Extensions.DependencyInjection 

Command Line CLI Step 3

Step 4

Press Enter: After entering the command, press Enter to execute it. The .NET CLI will download and install the specified NuGet package along with its dependencies.

Command Line CLI Step 4

Step 5

Verify Installation: Once the installation process is complete, you can verify that the NuGet package was installed successfully by checking your project file (.csproj) to see if the package reference has been added. By first selecting the project file .csproj.

Command Line CLI Step 5

Step 6

Then right-click and open with notepad to see whether the NuGet package has been installed successfully.

Command Line CLI Step 6

Configure Services in Program.cs

Within the ConfigureServices method of your Program.cs file, configure the services your application requires.

using DI.NET8_CSharp10_Guide_ZiggyRafiq.Services.Interfaces;
using DI.NET8_CSharp10_Guide_ZiggyRafiq.Services;
using Microsoft.Extensions.DependencyInjection;

Console.WriteLine("Hello, I am Ziggy Rafiq and I work for Capgemini!");

// Setup dependency injection container
var serviceProvider = new ServiceCollection()
    .AddTransient<IFooService, FooService>()
    .AddScoped<IBarService, BarService>()
    .AddSingleton<IBazService, BazService>()
    .BuildServiceProvider();

// Resolve services and use them
var fooService = serviceProvider.GetService<IFooService>();
var barService = serviceProvider.GetService<IBarService>();
var bazService = serviceProvider.GetService<IBazService>();

// Use the services Example One
fooService?.DoSomething();
barService?.DoSomethingElse();
bazService?.DoAnotherThing();

// Use the services Example Two
serviceProvider.GetService<IFooService>()?.DoSomething();
serviceProvider.GetService<IBarService>()?.DoSomethingElse();
serviceProvider.GetService<IBazService>()?.DoAnotherThing();

A service instance's lifetime and its sharing between components are determined by the service lifetimes AddTransient, AddScoped, and AddSingleton. You can read my blog post here https://blog.ziggyrafiq.com/2023/02/aspnet-core-services-lifetime.html

Best Practices
 

Favor Constructor Injection

Using class constructors to inject dependencies ensures that dependencies are explicitly declared and easily discoverable.

using DI.NET8_CSharp10_Guide_ZiggyRafiq.Services.Interfaces;

namespace DI.NET8_CSharp10_Guide_ZiggyRafiq.ConstructorInjection;
public class ZiggyRafiqService
{
    private readonly IFooService _fooService;

    public ZiggyRafiqService(IFooService fooService)
    {
        _fooService = fooService;
    }

    public void DoSomething()
    {
        // Use the IFooService instance
        _fooService.DoSomething();
    }
}

Use Dependency Injection in Controllers

For better testability and maintainability, controllers in ASP.NET Core should utilize constructor injection.

using DI.NET8_CSharp10_Guide_ZiggyRafiq.Services.Interfaces;
using Microsoft.AspNetCore.Mvc;

namespace DI.NET8_CSharp10_Guide_ZiggyRafiq.Controller;
public class ZiggyRafiqController: ControllerBase
{
    private readonly IFooService _fooService;

    public ZiggyRafiqController(IFooService fooService)
    {
        _fooService = fooService;
    }

    // Action method to handle GET requests
    [HttpGet]
    public IActionResult Get()
    {
        // Perform some action using IFooService
        _fooService.DoSomething();

        // Return an IActionResult
        return Ok("GET request handled");
    }

}

Register Services with Interfaces

Service implementations should always be registered as interfaces rather than concrete implementations to allow for flexibility and easy swapping of implementations.

services.AddScoped< IFooService, FooService >();

Common Scenarios
 

Multiple Implementations

To distinguish between multiple implementations of an interface, use named or typed registrations.

services.AddScoped<IShoppingCartService, StandardShoppingCartService>();
services.AddScoped<IShoppingCartService, PremiumShoppingCartService>("premium");

Conditional Registrations

Configure or runtime conditions can be used to register services conditionally.

services.AddScoped<IFeatureService>(sp => configuration.GetValue<bool>("UseFeatureX") ? sp.GetService<FeatureXService>() : sp.GetService<DefaultFeatureService>());

Scoped Services in Background Tasks

Prevent memory leaks by properly handling scoped services when using dependency injection in long-running background tasks.

public class ZiggyRafiqBackgroundService : BackgroundService
{
    private readonly IServiceScopeFactory _scopeFactory;

    public ZiggyRafiqBackgroundService(IServiceScopeFactory scopeFactory)
    {
        _scopeFactory = scopeFactory;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        using (var scope = _scopeFactory.CreateScope())
        {
            var myScopedService = scope.ServiceProvider.GetRequiredService<IMyScopedService>();

            while (!stoppingToken.IsCancellationRequested)
            {
                // Background task logic using myScopedService
                await Task.Delay(1000, stoppingToken);
            }
        }
    }
}

Summary

With dependency injection in .NET 8, you can use it to promote modularity and testability in your applications by following best practices and understanding common scenarios. Dependency injection is a key component for building maintainable and scalable software for ASP.NET Core web applications, console applications, and background services.

I would greatly appreciate your support by following me on LinkedIn at https://www.linkedin.com/in/ziggyrafiq/  and liking this article. Your encouragement and support mean everything to me, and I truly appreciate it. 🙏


Similar Articles