Keyed services in .NET 8 finally sees a dependency injection update

Published: Saturday 16 September 2023

Keyed services in .NET 8 finally brings an update to dependency injection in ASP.NET Core.

A feature that has been included with other .NET IoC containers for many years, keyed services allows us to inject multiple services of the same type but with a different implementation. The key allows us to determine which implementation to use.

C# coding challenges

C# coding challenges

Our online code editor allows you to compile the answer.

We'll have a look at how we configure keyed services in ASP.NET Core. In-addition, we'll show you how to use them with MVC, Web API and Minimal API endpoints.

Services we will use in dependency injection

Here are the services and their implementations that we will use in this tutorial to demonstrate how keyed services work.

public interface IOldVehicleService
{
	int NoOfWheels { get; }
}

public interface IVehicleService
{
	int NoOfWheels { get; }
}

public class CarService : IVehicleService, IOldVehicleService
{
	public int NoOfWheels => 4;
}

public class MotorbikeService : IVehicleService, IOldVehicleService
{
	public int NoOfWheels => 2;
}

Multiple implementations prior to .NET 8

It was possible to add multiple implementations of the same service prior to .NET 8.

In this instance, we are adding two IOldVehicleService services to the IoC container. One has the CarService as it's implementation. The other is MotorbikeService.

// Program.cs
builder.Services.AddScoped<IOldVehicleService, CarService>();
builder.Services.AddScoped<IOldVehicleService, MotorbikeService>();

However, if we injected IOldVehicleService as a parameter into a controller's constructor for example, it would reference the last implementation added to the IoC instance.

Here, the IOldVehicleService would contain the MotorbikeService class reference.

// VehicleApiController.cs
public class VehicleApiController : Controller 
{
	private readonly IOldVehicleService _oldVehicleService;

	public VehicleApiController (IOldVehicleService oldVehicleService) 
	{
		_oldVehicleService = oldVehicleService; // Would contain the MotorbikeService class reference
	}
}

If we wanted to get any other implementations of the same service, we would have to inject an IEnumerable instance of the service and loop through it to get the correct implementation.

// VehicleApiController.cs
public class VehicleApiController : Controller 
{
	private readonly IEnumerable<IOldVehicleService> _oldVehicleServices;

	public VehicleApiController (IEnumerable<IOldVehicleService> oldVehicleServices) 
	{
		_oldVehicleServices = oldVehicleServices; // Would contain both the CarService and MotorbikeService class references
	}
}

Introducing keyed services

Keyed services allows us to separate multiple services with a key (hence the name). It works with singleton, scoped and transient service lifetimes.

The AddKeyedSingleton, AddKeyedScoped and AddKeyedTransient has been added as IServiceCollection extension methods. All of these methods have a service key parameter which can be any object.

In this instance, we have two IVehicleService services added to the IoC container. The first is the CarService and uses car as the service key. The other is MotorbikeService which uses motorbike.

// Program.cs
builder.Services.AddKeyedScoped<IVehicleService, CarService>("car");
builder.Services.AddKeyedScoped<IVehicleService, MotorbikeService>("motorbike");

How to use with Minimal APIs

When injecting the IVehicleService into an endpoint's parameter, the FromKeyedServices attribute needs to be prepended to the parameter. This contains the key of the service so the correct implementation is injected.

// Program.cs
app.MapGet("/api/minimal-api", (
	[FromKeyedServices("car")] IVehicleService carService,
	[FromKeyedServices("motorbike")] IVehicleService motorbikeService
	) =>
{
	return Results.Ok(new
	{
		CarWheels = carService.NoOfWheels,
		MotorbikeWheels = motorbikeService.NoOfWheels
	});
});

The key determines which implementation it needs to use.

How to use with MVC and Web API controllers

The same FromKeyedServices attribute has to be prepended to the parameter which indicates which implementation to inject.

Here is how it works with a Web API endpoint:

// VehicleApiController.cs
[Route("api/web-api")]
[ApiController]
public class VehicleApiController : Controller 
{
	[HttpGet("dotnet-8")]
	public IActionResult ReturnNoOfWheels(
		[FromKeyedServices("car")] IVehicleService carService, 
		[FromKeyedServices("motorbike")] IVehicleService motorbikeService
		)
	{
		return Ok(new
		{
			CarWheels = carService.NoOfWheels,
			MotorbikeWheels = motorbikeService.NoOfWheels,
		});
	}
}

The service provider method

It's also possible to use it as part of the IServiceProvider instance. A new GetRequiredKeyedService (and GetKeyedService to avoid exceptions being thrown when null) method has been added to resolve a keyed service.

This contains a parameter to add the key of the service.

// VehicleApiController.cs
[Route("api/web-api")]
[ApiController]
public class VehicleApiController : ControllerBase
{
	private readonly IServiceProvider _serviceProvider;
	public VehicleApiController(
		IServiceProvider serviceProvider
		)
	{
		_serviceProvider = serviceProvider;
	}

	[HttpGet("dotnet-8-service-provider")]
	public IActionResult ReturnNoOfWheelsServiceProvider()
	{
		var carService = _serviceProvider.GetRequiredKeyedService<IVehicleService>("car");
		var motorbikeService = _serviceProvider.GetRequiredKeyedService<IVehicleService>("motorbike");

		return Ok(new
		{
			CarWheels = carService.NoOfWheels,
			MotorbikeWheels = motorbikeService.NoOfWheels,
		});
	}
}

Learn more about keyed services

Watch our video where we talk about keyed services in detail and how to add them to a ASP.NET Core web application in .NET 8.

In-addition, download the code example for this tutorial where you can experiment with keyed services yourself.

How to use keyed services now

This feature has been added to .NET 8 RC 1. This can be used by downloading Visual Studio 2022 preview (make sure it's version 17.8 if you're updating). In-addition, you'll need to download the .NET 8 SDK (RC 1 or later).

When .NET 8 is released in November, you'll be able to update Visual Studio 2022 to version 17.8 and ensure that you have the latest .NET 8 SDK installed.