Introduction

Dependency Injection (DI) is a design pattern that promotes loose coupling and enhances testability and maintainability in your applications. It is a key feature in modern C# development, especially with frameworks like ASP.NET Core. This post will cover the basics of dependency injection, how to set it up in a C# application, and provide practical examples to illustrate its usage.

What is Dependency Injection?

Dependency Injection is a design pattern that deals with how components get their dependencies. Instead of the component creating its dependencies, they are provided to the component by an external source.

Key Benefits:

  • Loose Coupling: Reduces dependencies between components.
  • Enhanced Testability: Makes it easier to test components in isolation.
  • Maintainability: Simplifies changing dependencies without modifying the component’s code.

Setting Up Dependency Injection

To use dependency injection in a C# application, you need a DI container. In ASP.NET Core, the built-in DI container is used.

Adding Services to the DI Container

You typically configure the DI container in the Startup class of an ASP.NET Core application.

Example: Configuring DI in ASP.NET Core

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            });
}

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddTransient<IMyService, MyService>();
        services.AddScoped<IOtherService, OtherService>();
        services.AddSingleton<ISingletonService, SingletonService>();
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        app.UseRouting();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
        });
    }
}

public interface IMyService
{
    void DoWork();
}

public class MyService : IMyService
{
    public void DoWork()
    {
        Console.WriteLine("MyService is doing work.");
    }
}

public interface IOtherService
{
    void PerformTask();
}

public class OtherService : IOtherService
{
    public void PerformTask()
    {
        Console.WriteLine("OtherService is performing a task.");
    }
}

public interface ISingletonService
{
    void Execute();
}

public class SingletonService : ISingletonService
{
    public void Execute()
    {
        Console.WriteLine("SingletonService is executing.");
    }
}

Explanation:

  • ConfigureServices: Adds services to the DI container with different lifetimes (Transient, Scoped, Singleton).
  • IMyService, IOtherService, ISingletonService: Interfaces for the services.
  • MyService, OtherService, SingletonService: Implementations of the interfaces.

Injecting Dependencies

Once services are registered in the DI container, you can inject them into your classes via constructors.

Example: Injecting Dependencies into a Controller

using Microsoft.AspNetCore.Mvc;

[ApiController]
[Route("[controller]")]
public class MyController : ControllerBase
{
    private readonly IMyService _myService;
    private readonly IOtherService _otherService;

    public MyController(IMyService myService, IOtherService otherService)
    {
        _myService = myService;
        _otherService = otherService;
    }

    [HttpGet]
    public IActionResult Get()
    {
        _myService.DoWork();
        _otherService.PerformTask();
        return Ok("Services executed successfully.");
    }
}

Explanation:

  • MyController: A controller that uses dependency injection to get instances of IMyService and IOtherService.
  • Constructor Injection: The services are injected via the constructor.

Service Lifetimes

When registering services in the DI container, you can specify their lifetimes.

  1. Transient: A new instance is created each time it is requested.
   services.AddTransient<IMyService, MyService>();
  1. Scoped: A new instance is created per scope (e.g., per web request).
   services.AddScoped<IOtherService, OtherService>();
  1. Singleton: A single instance is created and shared throughout the application’s lifetime.
   services.AddSingleton<ISingletonService, SingletonService>();

Full Example Code

Here’s the complete code for setting up dependency injection, registering services, and injecting them into a controller:

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.AspNetCore.Mvc;
using System;

public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            });
}

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddTransient<IMyService, MyService>();
        services.AddScoped<IOtherService, OtherService>();
        services.AddSingleton<ISingletonService, SingletonService>();
        services.AddControllers();
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        app.UseRouting();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
        });
    }
}

public interface IMyService
{
    void DoWork();
}

public class MyService : IMyService
{
    public void DoWork()
    {
        Console.WriteLine("MyService is doing work.");
    }
}

public interface IOtherService
{
    void PerformTask();
}

public class OtherService : IOtherService
{
    public void PerformTask()
    {
        Console.WriteLine("OtherService is performing a task.");
    }
}

public interface ISingletonService
{
    void Execute();
}

public class SingletonService : ISingletonService
{
    public void Execute()
    {
        Console.WriteLine("SingletonService is executing.");
    }
}

[ApiController]
[Route("[controller]")]
public class MyController : ControllerBase
{
    private readonly IMyService _myService;
    private readonly IOtherService _otherService;

    public MyController(IMyService myService, IOtherService otherService)
    {
        _myService = myService;
        _otherService = otherService;
    }

    [HttpGet]
    public IActionResult Get()
    {
        _myService.DoWork();
        _otherService.PerformTask();
        return Ok("Services executed successfully.");
    }
}

Conclusion

Dependency Injection is a fundamental design pattern that enhances the modularity, testability, and maintainability of your code. By understanding how to set up and use dependency injection in C#, you can build more robust and flexible applications.

Similar Posts