Stop Overwriting Your Services: The Case for Keyed DI in .NET 8

What is Dependency Injection?

Dependency Injection (DI) is a design pattern used to improve code maintainability and testability by decoupling the creation of objects from their usage. In essence, it promotes loose coupling, regardless of the programming language in use.

Simply put, DI is a technique where an object receives the dependencies it needs—typically other objects or services—from an external source rather than creating them internally. This separation of object construction from usage results in more modular and testable code.

In .NET Core, Dependency Injection can be implemented in two primary ways:

  • Constructor Injection: Dependencies are passed through the class constructor.
  • Setter Injection: Dependencies are assigned through public setter methods.

Additionally, when registering services, you can define their lifetime using the following methods:

  • AddTransient: A new instance of the service is created every time it's requested. Ideal for lightweight and stateless services. For example, a service used in both a Controller and a View would be instantiated twice.
  • AddScoped: A new instance is created once per HTTP request. So, if the same service is used in a Controller and View during one request, it will be the same instance.
  • AddSingleton: A single instance is created and shared across the application’s lifetime. It is created either when first requested or at application startup if registered in Program.cs.

What is Keyed Dependency Injection?

Traditionally, a single interface could only be mapped to one concrete class when registered in Program.cs. If multiple implementations were registered for the same interface, only the last one would be used—overwriting the others.

While third-party libraries have offered solutions to this limitation, .NET 8 introduces Keyed Dependency Injection natively. This allows multiple implementations of the same interface to be registered and accessed using a unique key, enabling more flexible service resolution.

Previously, when working with a generic interface and multiple implementations, the system would always resolve to the last registered one. With .NET 8’s new keyed DI feature, you can bind multiple classes to the same interface using a named key.

Implementation Guide


Step 1. Define Services in Program.cs

Instead of using AddSingleton, AddScoped, or AddTransient, you now use:

  • AddKeyedSingleton
  • AddKeyedScope
  • AddKeyedTransient
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddKeyedSingleton<ICodingLanguage, DotNet>("DOTNET");
builder.Services.AddKeyedSingleton<ICodingLanguage, Java>("JAVA");

Here, both DotNet and Java implement the ICodingLanguage interface and are registered with unique keys.

Step 2. Inject Services in Constructors

In your services or controllers, use the [FromKeyedServices("key")] attribute to inject a specific implementation:

private readonly ICodingLanguage _codingLanguage;

// DotNet service
public class DotnetService([FromKeyedServices("DOTNET")] ICodingLanguage codingLanguage)
{
    _codingLanguage = codingLanguage;
}

// Java Service
public class JavaService([FromKeyedServices("JAVA")] ICodingLanguage codingLanguage)
{
    _codingLanguage = codingLanguage;
}

Step 3. Inject at Method Level

You can also inject keyed services directly into methods:

public string SetDotNet([FromKeyedServices("DOTNET")] ICodingLanguage language)
{
    return language.Greetings();
}

Keyed Dependency Injection in Blazor

In Blazor, you cannot inject keyed services directly into .razor pages. Instead, you need to move your logic to a code-behind file (.razor.cs), where you can use the [Inject] attribute with a Key parameter:

namespace ProjectName.Components.Pages
{
    public partial class DotNet
    {
        [Inject(Key = "DOTNET")]
        public ICodingLanguage codingLanguage { get; set; }

        // Additional methods here
    }
}

To create a code-behind file, right-click on the @code section and select the option to move the code to a .cs file.

Limitations with Minimal APIs

Keyed Dependency Injection is not currently supported in Minimal APIs. Attempting to use it will result in exceptions. You should continue using standard DI in those cases:

var app = builder.Build();

// This will not work
app.MapGet("/chooseLanguage", ([FromKeyedServices("DOTNET")] ICodingLanguage service) =>
    service.Greetings());

app.Run();

Up Next
    Ebook Download
    View all
    Learn
    View all