04 Feb

Dependency Injection in .NET 8.0 – Keyed Service Registration

Dependency Injection (DI) in .NET provides a mechanism for managing the dependencies between various components within an application. When registering services, it allows specification of service lifetimes, service types, and their corresponding implementations.

The service lifetime defines how instances of the service are managed by the DI container, including options such as Transient (created each time they’re requested), Scoped (created once per scope), and Singleton (created once and shared throughout the application).

Service types represent the interfaces or base classes that define the contract for the services.

Implementations are concrete classes that fulfill those contracts. Through the DI container, services can be registered, resolved, and injected into consuming classes, facilitating loose coupling, better testability, and modular development practices within the .NET ecosystem.

Starting with .NET Core and continuing into .NET 5, .NET 6, .NET 7.0 and .NET 8.0, Microsoft has provided robust support for dependency injection.

I’ve covered the concept of dependency injection in my articles. Some examples are provided below.

Multiple interface implementations in asp.net core.

Action Injection in Web API 2

Dependency Injection Service lifetimes in .NET 6.0

Keyed Service Registration

Keyed Service Registration in .NET 8.0 is a concept in dependency injection that allows you to register multiple implementations of the same service interface with a unique key identifier. This feature is handy when you want to retrieve a specific implementation based on a key at runtime.

Here’s an example of how you can use Keyed Service Registration in .NET 8.0 for dependency injection:

Tools which I have used for this post.

  1. VS 2022 Community Edition 17.8.0
  2. .NET 8.0
  3. Web API

Source code can be downloaded from GitHub.

As a first step, let us go ahead and create a Web API Project.

Consider an example where you have an interface INotoficationService and two implementations SMSNotification and EmailNotification. You want to register both implementations with different keys.

namespace KeyedServiceRegistration.Interfaces
{
    public interface INotificationService
    {
      Task<string>  NotifyAsync(string message);
    }
}

using KeyedServiceRegistration.Interfaces;

namespace KeyedServiceRegistration.Implementations
{
    public class EmailNotification : INotificationService
    {
        public async Task<string> NotifyAsync(string message)=> 
            await Task.FromResult($"Email Notification: {message}");
        
    }
}
using KeyedServiceRegistration.Interfaces;

namespace KeyedServiceRegistration.Implementations
{
    public class SMSNotification : INotificationService
    {
        public async Task<string> NotifyAsync(string message)=> 
            await Task.FromResult($"SMS Notification: {message}");
            
    }
}

Now let us register the services in Program.cs
//Keyed Service Resigtration
builder.Services.AddKeyedTransient<INotificationService,SMSNotification>(Keys.SMS);
builder.Services.AddKeyedTransient<INotificationService,EmailNotification>(Keys.Email);

To enlist a keyed service, utilize any of the AddKeyedSingleton(), AddKeyedScoped(), or AddKeyedTransient() variations, while supplying an object as the key.

Let us see how these services can be resolved in Controllers.

To access keyed services within a controller, we’ll examine two different methods.

[FromKeyedServices] Attribute


Add the [FromKeyedServices(object key)] attribute to a parameter within your controller action method.
using KeyedServiceRegistration.Enums;
using KeyedServiceRegistration.Interfaces;
using Microsoft.AspNetCore.Mvc;

// For more information on enabling Web API for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860

namespace KeyedServiceRegistration.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class EmailNotificationController : ControllerBase
    {
        // GET: api/<EmailNotificationController>
        [HttpGet("email")]
        public IActionResult Get([FromKeyedServices(Keys.Email)] INotificationService notificationService)
        =>
            Ok(notificationService.NotifyAsync("Keyed Service Registration"));
        
    }
}

In summary, this code defines a Web API controller (EmailNotificationController) with an HTTP GET endpoint (/api/EmailNotificationController/email) that retrieves an INotificationService instance based on a specific key (Keys.Email) and triggers an email notification using that service.

IServiceProvider Interface

IServiceProvider is a mechanism in .NET that serves as a container for services and provides a way to retrieve these services by their registered types or names.

namespace KeyedServiceRegistration.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class SmsNotificationController : ControllerBase
    {
        private readonly INotificationService _notificationService;
        public SmsNotificationController(IServiceProvider serviceProvider)=>
        
            _notificationService=serviceProvider.GetRequiredKeyedService<INotificationService>(Keys.SMS);

        // GET: api/<NotificationController>
        [HttpGet("sms")]
        public IActionResult Get()
        {
            return Ok(_notificationService.NotifyAsync("Keyed Service Registration"));
        }

    }

In summary, this code defines a Web API controller (SmsNotificationController) that fetches an INotificationService instance with the key Keys.SMS using dependency injection in the constructor. It has an endpoint (/api/SmsNotificationController/sms) that triggers an SMS notification using the fetched service instance.

This way, you can utilize Keyed Service Registration in .NET 8.0 or later to manage multiple implementations of the same interface within your application using the built-in dependency injection system.

About the Author

Comments are closed.