Keyed Service Dependency Injection in .NET
If you are unaware of the concept of Dependency Injection read this article :
EP 62: Dependency Injection Explained in .NET
Keyed Services in .NET 8
Keyed service retrieves the dependency using a key when an interface simply has multiple implementations. This concept was introduced in .NET 8.
Suppose we have the following piece of code where we have two classes implementing the same interface :
public interface IPrinter
{
void Print(string message);
}
internal class ConsolePrinter : IPrinter
{
public void Print(string message)
{
Console.WriteLine(message);
}
}
internal class FilePrinter : IPrinter
{
public void Print(string message)
{
File.WriteAllText("file.txt", message);
}
}
Keyed Services in .NET 8
As we can see from the previous code snippet, the two classes are implementing a single interface, letβs see how can we register its dependency using a key.
We need to provide a string-based key in the Program.cs, but we can use enums as well instead of hard coding string values :
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddKeyedSingleton<IPrinter, ConsolePrinter>("Console");
builder.Services.AddKeyedSingleton<IPrinter, FilePrinter>("File");
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddKeyedSingleton<IPrinter, ConsolePrinter>(PrintTypes.Console);
builder.Services.AddKeyedSingleton<IPrinter, FilePrinter>(PrintTypes.File);
public enum PrintTypes
{
Console,
File
}
There are a couple of ways to inject the keyed services
-
Controller level & assign them
-
Method level specifically
[Route("[controller]")]
public class DemoController : ControllerBase
{
public DemoController([FromKeyedServices(PrintTypes.Console)]
IPrinter printer){ }
public IActionResult Method1([FromKeyedServices(PrintTypes.File)]
IPrinter filePrinter)
{ }
}
[Route("[controller]")]
public class DemoController : ControllerBase
{
[Inject(Key=PrintTypes.Console)]
Public IPrinter _printer {get;set;}
public DemoController([FromKeyedServices(PrintTypes.Console)]
IPrinter printer)
{
_printer = printer;
}
}
Currently, we have a limitation that this can not be applied directly to Minimal APIs yet.
How to achieve the same behavior in older versions of .NET?
We can achieve the same in older versions as well like this :
// Register services
builder.Services.AddSingleton<IPrinter, ConsolePrinter>();
builder.Services.AddSingleton<IPrinter, FilePrinter>();
[Route("[controller]")]
public class DemoController : ControllerBase
{
private readonly IEnumerable<IPrinter> _printers;
public DemoController(IEnumerable<IPrinter> printers)
{
_printers = printers;
}
[HttpGet]
public IActionResult Method1()
{
// Find the ConsolePrinter instance from the collection of printers
var consolePrinter = _printers.First(x => x.GetType() == typeof(ConsolePrinter));
consolePrinter.Print("Hello from ConsolePrinter");
return Ok("Message printed using ConsolePrinter.");
}
[HttpGet("file")]
public IActionResult Method2()
{
// Find the FilePrinter instance from the collection of printers
var filePrinter = _printers.First(x => x.GetType() == typeof(FilePrinter));
filePrinter.Print("Hello from FilePrinter");
return Ok("Message printed using FilePrinter.");
}
}
Whenever you're ready, there are 3 ways I can help you:
- Subscribe to my youtube channel : For in-depth tutorials, coding tips, and industry insights.
- Promote yourself to 9,000+ subscribers : By sponsoring this newsletter
- Patreon community : Get access to all of my blogs and articles at one place