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
andIOtherService
. - Constructor Injection: The services are injected via the constructor.
Service Lifetimes
When registering services in the DI container, you can specify their lifetimes.
- Transient: A new instance is created each time it is requested.
services.AddTransient<IMyService, MyService>();
- Scoped: A new instance is created per scope (e.g., per web request).
services.AddScoped<IOtherService, OtherService>();
- 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.