Explain Keyed Services in .NET 8

Introduction

.NET 8 introduces a significant enhancement to its built-in dependency injection (DI) system: keyed services. This feature, long present in third-party DI frameworks like StructureMap and Autofac, empowers developers with greater control and flexibility in how their applications receive dependencies. Let's delve into the details of keyed services,

Understanding Keyed Services

Traditionally, .NET DI registered services based solely on their type. While this was effective for most scenarios, it limited control when multiple implementations existed for the same interface. Keyed services address this by allowing us to associate a unique "key" with each service registration. This key can be a string, enum, or any other value that uniquely identifies the desired implementation.

Benefits of Keyed Services

  • Flexibility: Inject specific implementations based on dynamic conditions, configuration settings, or runtime context.
  • Decoupling: Separate concerns by implementing variations of the same interface for different purposes.
  • Maintainability: Enhance code clarity by explicitly naming and referencing desired dependencies.
  • Configurability: Inject different implementations based on environment variables or configuration files.

Example

Imagine an application needing data storage based on user preferences or configuration settings. We have two implementations: LocalStorage and CloudStorage, both implementing the IDataStore interface.

1. Define Interfaces

public interface IDataStore
{
    void SaveData(string data);
}

public class LocalStorage : IDataStore
{
    public void SaveData(string data)
    {
        // Implementation to save data locally
        Console.WriteLine($"Saving data locally: {data}");
    }
}

public class CloudStorage : IDataStore
{
    public void SaveData(string data)
    {
        // Implementation to save data in the cloud
        Console.WriteLine($"Saving data in the cloud: {data}");
    }
}

2. Register Services with Keys

// In Startup.cs

public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<IDataStore, LocalStorage>(key: "local");
    services.AddSingleton<IDataStore, CloudStorage>(key: "cloud");
}

3. Inject and Use Keyed Service

public class MyService
{
    private readonly IKeyedServiceProvider _provider;
    private readonly IConfiguration _configuration;

    public MyService(IKeyedServiceProvider provider, IConfiguration configuration)
    {
        _provider = provider;
        _configuration = configuration;
    }

    public void DoSomething()
    {
        string storageType = _configuration["StorageType"]; // e.g., "local" or "cloud"

        IDataStore store = _provider.GetKeyedService<IDataStore>(storageType);
        store.SaveData("This data will be saved based on the configuration.");
    }
}

4. Run the Application

// Program.cs
public static void Main(string[] args)
{
    var builder = WebApplication.CreateBuilder(args);

    // Configure services and app...

    var app = builder.Build();

    // Run the app...
}

Conclusion

Keyed services represent a valuable addition to the .NET 8 DI system, offering a more nuanced and flexible approach to dependency injection. By understanding their benefits, usage patterns, and advanced applications, you can unlock greater control and clarity in your application architecture. Explore, experiment, and leverage keyed services to write more dynamic, adaptable, and well-structured .NET applications!