Navigation
Recherche
|
How to use keyed services in ASP.NET Core
jeudi 23 octobre 2025, 11:00 , par InfoWorld
Dependency injection (also known as DI) is a design pattern in which the dependent types of a class are injected (passed to it by another class or object) rather than created directly, thereby facilitating loose coupling and promoting easier testing and maintenance.
In ASP.NET Core, both framework services and application services can be injected into your classes, rather than being tightly coupled. In this article, we’ll examine how we can work with keyed services to simplify dependency injection in ASP.NET Core. To use the code examples provided in this article, you should have Visual Studio 2022 installed in your system. If you don’t already have a copy, you can download Visual Studio 2022 here. Create an ASP.NET Core Web API project in Visual Studio 2022 To create an ASP.NET Core Web API project in Visual Studio 2022, follow the steps outlined below. Launch the Visual Studio 2022 IDE. Click on “Create new project.” In the “Create new project” window, select “ASP.NET Core Web API” from the list of templates displayed. Click “Next.” In the “Configure your new project” window, specify the name and location for the new project. Optionally check the “Place solution and project in the same directory” check box, depending on your preferences. Click “Next.” In the “Additional Information” window shown next, select “.NET 9.0 (Standard Term Support)” as the framework version and uncheck the check box that says “Use controllers.” We’ll be using minimal APIs in this project. Elsewhere in the “Additional Information” window, leave the “Authentication Type” set to “None” (the default) and make sure the check boxes “Enable Open API Support,” “Configure for HTTPS,” and “Enable Docker” remain unchecked. We won’t be using any of those features here. Click “Create.” We’ll use this ASP.NET Core Web API project to work with the code examples given in the sections below. What are keyed services? Keyed services—i.e., services that you register and access using a unique key—provide an elegant way to handle multiple implementations of the same interface when working with dependency injection in ASP.NET Core. They enable you to simplify dependency injection without using custom factories or service locator patterns, thereby allowing multiple implementations of the same interface to coexist in harmony. Recall that dependency injection eases the testing and maintenance of our code by allowing us to implement a service in one place, then use DI to insert it anywhere in our application where we want to use it. Let’s say we have a logger service that we want to use in six parts of our application. Without DI, we would need to implement the logger service six times, in all of these different places—resulting in duplication of code and convoluted testing and maintenance. If we later wanted to change the logger service, we would need to find and replace all of these implementations. With DI, we have only one implementation to change. Keyed services offer additional advantages when using DI. They allow us to have multiple versions of a service and choose the version to use at run time. In addition, they eliminate the need to write boilerplate code, namely factories, when using multiple implementations of an interface. And they enhance testability by keeping our source code simple, modular, type-safe, extensible, and manageable. Next, we’ll examine how we can implement keyed services in an ASP.NET Core application, using a custom logger. Create a custom logger interface First off, let’s create a new.cs file named ICustomLogger.cs and enter the following code to define an interface named ICustomLogger. public interface ICustomLogger { void Log(string message); } Create three logger classes Next, we’ll create three new classes called FileLogger, DatabaseLogger, and EventLogger in three different files. Name the files FileLogger.cs, DBLogger.cs, and EventLogger.cs. Write the following code in FileLogger.cs. public class FileLogger: ICustomLogger { public void Log(string message) { File.AppendAllText('log.txt', $'[FileLogger] {DateTime.Now}: {message}n'); } } Write the following code in DatabaseLogger.cs. public class DatabaseLogger: ICustomLogger { public void Log(string message) { Console.WriteLine($'[DatabaseLogger] {DateTime.Now}: {message}'); } } And write the following code in EventLogger.cs. public class EventLogger: ICustomLogger { public void Log(string message) { Console.WriteLine($'[EventLogger] Event log {DateTime.Now}: {message}'); } } Register the logger classes as keyed services Before we can use our logger services, we must register them with the ASP.NET Core request processing pipeline. We can register these implementations of ICustomLogger in the Program.cs file using the following code. var builder = WebApplication.CreateBuilder(args); // Register multiple keyed services for the ICustomLogger interface builder.Services.AddKeyedScoped('file'); builder.Services.AddKeyedScoped('database'); builder.Services.AddKeyedScoped('event'); var app = builder.Build(); Note how the FileLogger, DatabaseLogger, and EventLogger services are registered using the keys 'file', 'database', and 'event', respectively. Inject the keyed logger services We can use the [FromKeyedServices] attribute to inject a specific implementation of our logger service in our minimal API endpoints as shown in the code snippet given below. app.MapGet('/customlogger/file', ([FromKeyedServices('file')] ICustomLogger fileLogger) => { fileLogger.Log('This text is written to the file system.'); return Results.Ok('File logger executed successfully.'); }); app.MapGet('/customlogger/db', ([FromKeyedServices('database')] ICustomLogger databaseLogger) => { databaseLogger.Log('This text is stored in the database.'); return Results.Ok('Database logger executed successfully.'); }); app.MapGet('/customlogger/event', ([FromKeyedServices('event')] ICustomLogger logger) => { logger.Log('This text is recorded in the event system.'); return Results.Ok('Event logger executed successfully.'); }); Thus, by using DI and keyed services, we can implement each of our logger services once, then simply ask for the right type of the logger when we need one without having to use a factory to instantate the logger. And whenever we want to swap the implementations—from FileLogger to DatabaseLogger, for example—all we need to do is change the configuration we specied while registering the services with the container. The DI system will plug in the right logger automatically at run time. Complete keyed services example – minimal APIs The complete source code of the Program.cs file is given below for your reference. var builder = WebApplication.CreateBuilder(args); // Register multiple keyed services for the ICustomLogger interface builder.Services.AddKeyedScoped('file'); builder.Services.AddKeyedScoped('database'); builder.Services.AddKeyedScoped('event'); // Add services to the container. builder.Services.AddControllers(); var connectionString = builder.Configuration.GetConnectionString('DefaultConnection'); builder.Services.AddDbContext(options => { options.UseSqlServer( builder.Configuration['ConnectionStrings:DefaultConnection']); }); var app = builder.Build(); // Configure the HTTP request pipeline. app.MapGet('/customlogger/file', ([FromKeyedServices('file')] ICustomLogger fileLogger) => { fileLogger.Log('This text is written to the file system.'); return Results.Ok('File logger executed successfully.'); }); app.MapGet('/customlogger/db', ([FromKeyedServices('database')] ICustomLogger databaseLogger) => { databaseLogger.Log('This text is stored in the database.'); return Results.Ok('Database logger executed successfully.'); }); app.MapGet('/customlogger/event', ([FromKeyedServices('event')] ICustomLogger logger) => { logger.Log('This text is recorded in the event system.'); return Results.Ok('Event logger executed successfully.'); }); app.UseHttpsRedirection(); app.MapControllers(); app.Run(); public interface ICustomLogger { void Log(string message); } public class FileLogger: ICustomLogger { public void Log(string message) { File.AppendAllText('log.txt', $'[FileLogger] {DateTime.Now}: {message}n'); } } public class DatabaseLogger: ICustomLogger { public void Log(string message) { Console.WriteLine($'[DatabaseLogger] {DateTime.Now}: {message}'); } } public class EventLogger: ICustomLogger { public void Log(string message) { Console.WriteLine($'[EventLogger] Event log {DateTime.Now}: {message}'); } } Injecting keyed services in controllers If you’re using API controllers in your application, you can inject these keyed services using the constructor of your controller class as shown in the following code. [ApiController] [Route('api/customlogger')] public class CustomLoggerController: ControllerBase { private readonly ILogger _fileLogger; private readonly ILogger _databaseLogger; private readonly ILogger _eventLogger; public CustomLoggerController( [FromKeyedServices('file')] ICustomLogger fileLogger, [FromKeyedServices('database')] ICustomLogger databaseLogger, [FromKeyedServices('event')] ICustomLogger eventLogger) { _fileLogger = fileLogger; _databaseLogger = databaseLogger; _eventLogger = eventLogger; } } Complete keyed services example – controllers The complete source code of the CustomLoggerController class is given in the code listing below. [ApiController] [Route('api/customlogger')] public class CustomLoggerController: ControllerBase { private readonly ICustomLogger _fileLogger; private readonly ICustomLogger _dbLogger; private readonly ICustomLogger _eventLogger; public CustomLoggerController( [FromKeyedServices('file')] ICustomLogger fileLogger, [FromKeyedServices('database')] ICustomLogger dbLogger, [FromKeyedServices('event')] ICustomLogger eventLogger) { _fileLogger = fileLogger; _dbLogger = dbLogger; _eventLogger = eventLogger; } [HttpPost('file')] public IActionResult LogToFile([FromBody] string message) { _fileLogger.Log(message); return Ok('File logger invoked.'); } [HttpPost('database')] public IActionResult LogToDatabase([FromBody] string message) { _dbLogger.Log(message); return Ok('Database logger invoked.'); } [HttpPost('event')] public IActionResult LogToEvent([FromBody] string message) { _eventLogger.Log(message); return Ok('Event logger invoked.'); } } Key takeaways Keyed services enable you to register multiple implementations of the same interface and resolve them at runtime using a specific key that identifies each of these implementations. This helps you to select a particular service dynamically at runtime and provide a cleaner and type-safe alternative to using service factories or manual service resolution techniques. That said, you should avoid using keyed services unless you need to select from multiple implementations of an interface at run time. This is because resolving dependencies at run time introduces additional complexity and performance overhead, especially if it involves many dependencies. Additionally, using keyed services can lead to wiring up dependencies you don’t really need and complicate dependency management in your application.
https://www.infoworld.com/article/4075743/how-to-use-keyed-services-in-asp-net-core.html
Voir aussi |
56 sources (32 en français)
Date Actuelle
jeu. 23 oct. - 20:27 CEST
|