Unlocking Flexibility: Keyed Dependency Injection in .NET 8
Published: 2024-02-26 00:35:32

**Introduction:**

In the ever-evolving world of software development, flexibility is paramount. Choosing the right tools and patterns to adapt to changing requirements is crucial for building robust and maintainable applications. This is where keyed dependency injection (DI) in .NET 8 shines.

**What is Keyed Dependency Injection?**

Keyed DI is a novel feature introduced in .NET 8 that empowers developers to register multiple implementations of the same interface, each associated with a unique key. This enables you to dynamically select the specific implementation needed at runtime based on various factors like configuration settings, user context, or environmental variables.

**Benefits of Keyed DI:**

Enhanced Flexibility: Inject the most suitable implementation based on dynamic conditions, leading to adaptable and responsive applications.
Improved Code Organization: Group related implementations under a single interface with distinct keys, promoting code clarity and maintainability.
Streamlined Maintenance: Isolate service selection logic, making it easier to understand, modify, and test.

**Implementing Keyed DI:**

Here’s a step-by-step breakdown of implementing keyed DI in your .NET 8 project:

Registering Services:

- Utilize new methods like AddKeyedSingleton<TService, TImplementation>(key) or AddKeyedTransient<TService, TImplementation>(key) to register multiple implementations of an interface with different keys.

2. Injecting Services:

- Annotate the constructor parameter with the [FromKeyedServices(object key)] attribute, specifying the same key used during registration.

```
using System;

namespace KeyedDependencyInjectionDemo
{
    // Define the IMessageWriter interface
    public interface IMessageWriter
    {
        void WriteMessage(string message);
    }

    // Implement the MemoryMessageWriter class
    public class MemoryMessageWriter : IMessageWriter
    {
        public void WriteMessage(string message)
        {
            Console.WriteLine($"Writing to memory: {message}");
        }
    }

    // Implement the QueueMessageWriter class
    public class QueueMessageWriter : IMessageWriter
    {
        public void WriteMessage(string message)
        {
            Console.WriteLine($"Writing to queue: {message}");
        }
    }

    // Define the MyClass class which depends on IMessageWriter
    public class MyClass
    {
        private readonly IMessageWriter _writer;

        public MyClass(IMessageWriter writer)
        {
            _writer = writer;
        }

        public void SendMessage(string message)
        {
            _writer.WriteMessage(message);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            // Configuration (pseudo-code)
            string messageType = "memory"; // GetMessageTypeFromConfig();

            // Register services with different keys
            var serviceProvider = new ServiceProvider();
            serviceProvider.AddKeyedService<IMessageWriter, MemoryMessageWriter>("memory");
            serviceProvider.AddKeyedService<IMessageWriter, QueueMessageWriter>("queue");

            // Use the appropriate writer based on configuration
            var myClass = new MyClass(
                messageType == "memory" ?
                    serviceProvider.GetRequiredService<IMessageWriter>("memory") :
                    serviceProvider.GetRequiredService<IMessageWriter>("queue")
            );

            // Send a message using MyClass
            myClass.SendMessage("Hello, world!");
        }
    }

    // Simple service provider for demonstration purposes
    public class ServiceProvider
    {
        private readonly Dictionary<string, object> _services = new Dictionary<string, object>();

        public void AddKeyedService<TService, TImplementation>(string key) where TImplementation : TService, new()
        {
            _services[key] = new TImplementation();
        }

        public TService GetRequiredService<TService>(string key)
        {
            if (_services.TryGetValue(key, out object service))
            {
                return (TService)service;
            }
            throw new KeyNotFoundException($"No service of type {typeof(TService)} with key '{key}' has been registered.");
        }
    }
}
```

This console application demonstrates how you can use keyed dependency injection in a simple scenario. It registers two implementations of the IMessageWriter interface (MemoryMessageWriter and QueueMessageWriter) with different keys ("memory" and "queue"). Then, based on a configuration setting (messageType), it resolves the appropriate implementation and sends a message using MyClass.

Please note that this example uses a simple custom ServiceProvider for demonstration purposes. In a real-world scenario, you would typically use the built-in dependency injection container provided by the .NET framework.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2z3r93ch03oblhgpuqzu.png)

**Conclusion:**

Keyed dependency injection in .NET 8 empowers developers with a powerful and versatile approach to managing multiple service implementations. By embracing this feature, you can build more adaptable, maintainable, and future-proof applications.