ASP.NET Core 8 Web API -

Dependency Injection

Dependency Injection (DI) is an essential design pattern in ASP.NET Core that helps in managing dependencies in a more maintainable and testable way. This guide will cover all aspects of DI in ASP.NET Core 8 Web API, including types of DI, service lifetimes, examples, best practices, and detailed explanations.


1. Introduction to Dependency Injection

Dependency Injection is a design pattern that implements IoC (Inversion of Control), allowing the creation of dependent objects outside of a class and providing those objects to a class through various means. ASP.NET Core comes with a built-in DI container, making it easy to manage dependencies.


2. Types of Dependency Injection

ASP.NET Core supports three primary types of dependency injection:

2.1 Constructor Injection

Constructor Injection is the most commonly used form of DI in ASP.NET Core. Dependencies are provided through a class constructor.

        
            
public class MyService
{
    private readonly IMyDependency _myDependency;

    public MyService(IMyDependency myDependency)
    {
        _myDependency = myDependency;
    }

    public void PerformOperation()
    {
        _myDependency.Execute();
    }
}

        
    

Benefits:

2.2 Method Injection

Method Injection involves passing dependencies directly into methods where they are needed.

        
            
public class MyService
{
    public void PerformOperation(IMyDependency myDependency)
    {
        myDependency.Execute();
    }
}

        
    

Benefits:

2.3 Property Injection

Property Injection involves setting dependencies through properties. This approach is less common but can be useful in specific scenarios.

        
            
public class MyService
{
    public IMyDependency MyDependency { get; set; }

    public void PerformOperation()
    {
        MyDependency.Execute();
    }
}

        
    

Benefits:


3. Service Lifetimes

ASP.NET Core DI container supports three main service lifetimes:

3.1 Transient

Transient services are created each time they are requested. They are ideal for lightweight, stateless services.

        
            
builder.Services.AddTransient<IMyDependency, MyDependency>();

        
    

Benefits:

3.2 Scoped

Scoped services are created once per request. They are useful for services that maintain state across a single request.

        
            
builder.Services.AddScoped<IMyDependency, MyDependency>();

        
    

Benefits:

3.3 Singleton

Singleton services are created the first time they are requested and reused for every subsequent request. They are useful for services that maintain state across the application lifetime.

        
            
builder.Services.AddSingleton<IMyDependency, MyDependency>();

        
    

Benefits:


4. Registering Services

Services are registered with the DI container in the Program.cs file.

        
            
var builder = WebApplication.CreateBuilder(args);

// Register services
builder.Services.AddTransient<IMyDependency, MyDependency>();
builder.Services.AddScoped<IMyOtherDependency, MyOtherDependency>();
builder.Services.AddSingleton<IMySingletonDependency, MySingletonDependency>();

var app = builder.Build();

        
    

5. Injecting Services into Controllers

Controllers can have their dependencies injected through their constructors.

        
            
public class MyController : ControllerBase
{
    private readonly IMyDependency _myDependency;

    public MyController(IMyDependency myDependency)
    {
        _myDependency = myDependency;
    }

    [HttpGet]
    public IActionResult Get()
    {
        _myDependency.Execute();
        return Ok();
    }
}

        
    

6. Advanced Dependency Injection

6.1 Conditional Registrations

You can register services conditionally based on the environment or configuration.

        
            
if (builder.Environment.IsDevelopment())
{
    builder.Services.AddTransient<IMyDependency, DevelopmentDependency>();
}
else
{
    builder.Services.AddTransient<IMyDependency, ProductionDependency>();
}

        
    
6.2 Named Registrations

Named registrations are not directly supported in ASP.NET Core DI but can be achieved through custom factories or by using third-party DI containers.

6.3 Using Factories

Factories can be used to create instances of services with more complex initialization logic.

        
            
builder.Services.AddTransient<IMyDependency>(serviceProvider =>
{
    var config = serviceProvider.GetRequiredService<IConfiguration>();
    return new MyDependency(config["MySetting"]);
});

        
    

7. How Dependency Injection Works Behind the Scenes

ASP.NET Core's DI container is built on top of the IServiceProvider interface. When you register services, the container builds a dependency graph and resolves dependencies at runtime.

Example: Custom Service Provider

        
            
public class CustomServiceProvider : IServiceProvider
{
    private readonly Dictionary<Type, object> _services = new();

    public object GetService(Type serviceType)
    {
        if (_services.ContainsKey(serviceType))
        {
            return _services[serviceType];
        }

        // Logic to create and return the service
        var service = Activator.CreateInstance(serviceType);
        _services[serviceType] = service;
        return service;
    }
}

        
    

8. Best Practices

Example: Options Pattern

Define Options Class
        
            
public class MySettings
{
    public string SettingValue { get; set; }
}

        
    
Configure Options
        
            
builder.Services.Configure<MySettings>(builder.Configuration.GetSection("MySettings"));

        
    
Inject Options
        
            
public class MyService
{
    private readonly MySettings _settings;

    public MyService(IOptions<MySettings> options)
    {
        _settings = options.Value;
    }

    public void PerformOperation()
    {
        // Use _settings.SettingValue
    }
}

        
    

9. Combining Examples

Here is a comprehensive example combining multiple DI concepts:

        
            
var builder = WebApplication.CreateBuilder(args);

// Register services with different lifetimes
builder.Services.AddTransient<ITransientService, TransientService>();
builder.Services.AddScoped<IScopedService, ScopedService>();
builder.Services.AddSingleton<ISingletonService, SingletonService>();

// Register options pattern
builder.Services.Configure<MySettings>(builder.Configuration.GetSection("MySettings"));

var app = builder.Build();

// Use middleware
app.UseMiddleware<ExceptionHandlingMiddleware>();

app.MapGet(" / ", (ITransientService transientService, IScopedService scopedService, ISingletonService singletonService, IOptions<MySettings> options) =>
{
    transientService.PerformOperation();
    scopedService.PerformOperation();
    singletonService.PerformOperation();
    var settingValue = options.Value.SettingValue;
    return Results.Ok($"Setting Value: {settingValue}");
});

app.Run();

        
    

10. Conclusion

Dependency Injection is a powerful feature in ASP.NET Core 8 Web API that promotes loose coupling, testability, and maintainability. By understanding the different types of DI, service lifetimes, advanced registration techniques, and best practices, you can effectively manage dependencies in your applications. This comprehensive guide provides a solid foundation for mastering DI in ASP.NET Core 8 Web API.