Keyed Services in .NET: A Deep Dive with Examples

Introduction

.NET 8 introduced several exciting features that improve the developer experience and expand the flexibility of dependency injection (DI). Among these features is one called Keyed Services, which allows you to register and resolve services based on a key. This feature addresses scenarios where you have multiple implementations of an interface and want to resolve them selectively without relying on IEnumerable or more complex logic.

In this blog, we’ll explore what keyed services are, why they’re useful, and how you can implement them in your .NET applications with practical examples.

Why Keyed Services?

Traditionally in .NET, when you had multiple implementations of a service interface, you would resolve all implementations using IEnumerable. Then, you’d either loop through the services and pick the correct one based on some condition, or use named services with custom factories or resolvers.

For example, you might have an interface like the one below:

public interface IGreetingService
{
    string Greet(string name);
}

In this scenario, imagine you have multiple implementations of that interface:

public class EnglishGreetingService : IGreetingService
{
    public string Greet(string name)
    {
        return $"Hello, {name}!";
    }
}

public class SpanishGreetingService : IGreetingService
{
    public string Greet(string name)
    {
        return $"Hola, {name}!";
    }
}

Each of these services is registered in the Startup.cs or Program.cs as a transient service for the interface:

public void ConfigureServices(IServiceCollection services)
{
    services.AddTransient<IGreetingService, EnglishGreetingService>();
    services.AddTransient<IGreetingService, SpanishGreetingService>();
}

To be able to inject these services, you’d need to inject an IEnumerable<IGreetingService> like this:

[ApiController]
[Route("api/[controller]")]
public class GreetingController : ControllerBase
{
    private readonly IEnumerable<IGreetingService> _greetingServices;

    public GreetingController(IEnumerable<IGreetingService> greetingServices)
    {
        _greetingServices = greetingServices;
    }

    [HttpGet("greet")]
    public IActionResult Greet(string language, string name)
    {
        // Select the appropriate service based on the language condition
        IGreetingService selectedService = _greetingServices.FirstOrDefault(service =>
        {
            return (language == "English" && service is EnglishGreetingService) ||
                   (language == "Spanish" && service is SpanishGreetingService);
        });

        // Use the selected service to generate the greeting
        var greeting = selectedService?.Greet(name) ?? "Language not supported.";

        return Ok(greeting);
    }
}

As you can see, this is quite complex. Keyed services simplify this pattern by allowing you to assign keys to service registrations, and later resolve the correct implementation using that key. This removes the need for custom logic and makes your DI container setup cleaner and easier to maintain.

Keyed Services in Action

Let’s dive into an example using keyed services.

Suppose you’re building an application where you need different strategies for calculating discounts, depending on the user’s membership level. You might have the following discount strategies:

• BasicMemberDiscount
• PremiumMemberDiscount
• VIPMemberDiscount

All these classes implement a common interface IDiscountService.

public interface IDiscountService
{
    decimal CalculateDiscount(decimal totalAmount);
}

public class BasicMemberDiscount : IDiscountService
{
    public decimal CalculateDiscount(decimal totalAmount) => totalAmount * 0.05m;
}

public class PremiumMemberDiscount : IDiscountService
{
    public decimal CalculateDiscount(decimal totalAmount) => totalAmount * 0.10m;
}

public class VIPMemberDiscount : IDiscountService
{
    public decimal CalculateDiscount(decimal totalAmount) => totalAmount * 0.20m;
}

Registering Keyed Services

With keyed services, you can register these implementations with specific string keys.

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddKeyedSingleton<IDiscountService, BasicMemberDiscount>("Basic");
builder.Services.AddKeyedSingleton<IDiscountService, PremiumMemberDiscount>("Premium");
builder.Services.AddKeyedSingleton<IDiscountService, VIPMemberDiscount>("VIP");

var app = builder.Build();

In this example, we use AddKeyedSingleton, which is one of the new methods for registering keyed services. You can also use AddKeyedScoped or AddKeyedTransient depending on the service’s lifetime requirements.

Resolving Keyed Services

Once the services are registered, you can resolve them by their key. For instance, you might have a minimal API endpoint where you resolve the discount service based on the user’s membership level.

app.MapGet("/calculate-discount/{membershipLevel}", (string membershipLevel, [FromServices] IServiceProvider serviceProvider, decimal totalAmount) =>
{
    var discountService = serviceProvider.GetRequiredKeyedService<IDiscountService>(membershipLevel);
    var discount = discountService.CalculateDiscount(totalAmount);
    
    return Results.Ok(new { DiscountedAmount = totalAmount - discount });
});

app.Run();

In this code, instead of injecting IDiscountService directly, we inject a IServiceProvider and use its GetRequiredKeyedService method to resolve the service based on the membershipLevel key. The correct discount strategy is chosen based on the user’s membership level passed in the route.

Keyed Service Factory

.NET 8 also allows you to create factories for keyed services, providing even more flexibility. This can be useful if you need more complex logic for resolving services or if the key is dynamic.

Here’s an example of how to implement a factory for keyed services:

public class DiscountServiceFactory
{
    private readonly IServiceProvider _serviceProvider;

    public DiscountServiceFactory(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    public IDiscountService GetDiscountService(string membershipLevel)
    {
        return _serviceProvider.GetRequiredKeyedService<IDiscountService>(membershipLevel);
    }
}

You can then inject the DiscountServiceFactory into your controllers or services to resolve the appropriate keyed service as needed.

Conclusion

Keyed services in .NET 8 add a powerful new tool to your DI toolkit. They make it easier to manage multiple implementations of an interface without the need for complex resolution logic or cumbersome named service patterns. By leveraging keyed services, you can keep your codebase clean, maintainable, and flexible.

If you’re already using .NET’s built-in dependency injection, incorporating keyed services is a natural evolution that can simplify your service registration and resolution patterns. Whether you’re building a microservice architecture or a large monolithic application, keyed services offer a clear path to cleaner code and better service management.

Whether you’re looking to streamline your dependency injection setup with keyed services or need expert guidance on custom software architecture, Trailhead has the skills and experience to help your team succeed. Contact us today to discuss how we can enhance your project with tailored solutions that fit your unique needs.

Picture of Aleksandar Dickov

Aleksandar Dickov

Aleksandar is a seasoned professional with a decade of experience in software development. He holds a B.S. in Computer Science from the Faculty of Technical Science. Throughout his career, he has actively contributed to the creation of web and desktop applications, acquiring proficiency in languages such as C# and a strong command of the .NET platform. Thriving on the challenge of solving complex problems, Aleksandar is known for his result-oriented approach and unwavering dedication to delivering impactful solutions. His passion for software development extends beyond the professional realm, believing in the continuous pursuit of knowledge to enhance his contributions to any team. Beyond the world of coding, Aleksandar finds joy in basketball, football, and skiing. Whether it's exploring new places, engaging in sports, or maintaining a blog, he embraces a well-rounded approach to life.

Free Consultation

Sign up for a FREE consultation with one of Trailhead's experts.

"*" indicates required fields

This field is for validation purposes and should be left unchanged.

Related Blog Posts

We hope you’ve found this to be helpful and are walking away with some new, useful insights. If you want to learn more, here are a couple of related articles that others also usually find to be interesting:

Our Gear Is Packed and We're Excited to Explore With You

Ready to come with us? 

Together, we can map your company’s software journey and start down the right trails. If you’re set to take the first step, simply fill out our contact form. We’ll be in touch quickly – and you’ll have a partner who is ready to help your company take the next step on its software journey. 

We can’t wait to hear from you! 

Main Contact

This field is for validation purposes and should be left unchanged.

Together, we can map your company’s tech journey and start down the trails. If you’re set to take the first step, simply fill out the form below. We’ll be in touch – and you’ll have a partner who cares about you and your company. 

We can’t wait to hear from you! 

Montage Portal

Montage Furniture Services provides furniture protection plans and claims processing services to a wide selection of furniture retailers and consumers.

Project Background

Montage was looking to build a new web portal for both Retailers and Consumers, which would integrate with Dynamics CRM and other legacy systems. The portal needed to be multi tenant and support branding and configuration for different Retailers. Trailhead architected the new Montage Platform, including the Portal and all of it’s back end integrations, did the UI/UX and then delivered the new system, along with enhancements to DevOps and processes.

Logistics

We’ve logged countless miles exploring the tech world. In doing so, we gained the experience that enables us to deliver your unique software and systems architecture needs. Our team of seasoned tech vets can provide you with:

Custom App and Software Development

We collaborate with you throughout the entire process because your customized tech should fit your needs, not just those of other clients.

Cloud and Mobile Applications

The modern world demands versatile technology, and this is exactly what your mobile and cloud-based apps will give you.

User Experience and Interface (UX/UI) Design

We want your end users to have optimal experiences with tech that is highly intuitive and responsive.

DevOps

This combination of Agile software development and IT operations provides you with high-quality software at reduced cost, time, and risk.

Trailhead stepped into a challenging project – building our new web architecture and redeveloping our portals at the same time the business was migrating from a legacy system to our new CRM solution. They were able to not only significantly improve our web development architecture but our development and deployment processes as well as the functionality and performance of our portals. The feedback from customers has been overwhelmingly positive. Trailhead has proven themselves to be a valuable partner.

– BOB DOERKSEN, Vice President of Technology Services
at Montage Furniture Services

Technologies Used

When you hit the trails, it is essential to bring appropriate gear. The same holds true for your digital technology needs. That’s why Trailhead builds custom solutions on trusted platforms like .NET, Angular, React, and Xamarin.

Expertise

We partner with businesses who need intuitive custom software, responsive mobile applications, and advanced cloud technologies. And our extensive experience in the tech field allows us to help you map out the right path for all your digital technology needs.

  • Project Management
  • Architecture
  • Web App Development
  • Cloud Development
  • DevOps
  • Process Improvements
  • Legacy System Integration
  • UI Design
  • Manual QA
  • Back end/API/Database development

We partner with businesses who need intuitive custom software, responsive mobile applications, and advanced cloud technologies. And our extensive experience in the tech field allows us to help you map out the right path for all your digital technology needs.

Our Gear Is Packed and We're Excited to Explore with You

Ready to come with us? 

Together, we can map your company’s tech journey and start down the trails. If you’re set to take the first step, simply fill out the contact form. We’ll be in touch – and you’ll have a partner who cares about you and your company. 

We can’t wait to hear from you! 

Thank you for reaching out.

You’ll be getting an email from our team shortly. If you need immediate assistance, please call (616) 371-1037.