Внедрение зависимостей в .NET MAUI. Сервисы с ключами

Сервисы с ключами появились в .NET 8 и используются в случае, если необходимо зарегистрировать в приложении несколько реализаций одного и того же сервиса.

Использование сервисов с ключами

Достаточно долгое время в .NET существовала проблема так называемой множественной регистрации сервисов. Например, в нашем приложении есть сервис ILogger и его реализация — FileLogger.  В какой-то момент времени мы решили, что было бы неплохо использовать в нашем приложении вторую реализацию сервиса. Назовем её FileLoggerExt:

class FileLoggerExt : ILogger
{
    const string FILE_NAME = "Log.txt";
    private string fileName;

    public string FileName
    {
        get
        {
            return fileName;
        }
        set
        {
            fileName = value;
        }
    }

    private readonly string _newLine = Environment.NewLine;

    public FileLoggerExt()
    {
        fileName = FILE_NAME;
    }

    public void WriteLine(string message)
    {
        File.AppendAllText(fileName, $"[Calculator] [{DateTime.Now}] - {message}{_newLine}");
    }
}

Если мы зарегистрируем обе реализации сервиса, используя известные нам методы, например, вот так:

builder.Services.AddTransient<ILogger, FileLogger>();
builder.Services.AddTransient<ILogger, FileLoggerExt>();

то, при попытке получить сервис любым из способов, мы всегда будем получать из контейнера DI последнюю зарегистрированную реализацию, то есть FileLoggerExt. Решением этой проблемы «из коробки» является использование сервисов с ключами. Рассмотрим их использование на примере нашего приложения.

Для регистрации сервиса с ключом используются методы расширения IServiceCollection имена которых соответствуют шаблону AddKeyed[Lifetime](), где [Lifetime]жизненный цикл сервиса.

Сервисы с ключами в .NET MAUI

Изменим код MauiProgram.cs следующим образом:

public static class MauiProgram
{
    public static MauiApp CreateMauiApp()
    {
        var builder = MauiApp.CreateBuilder();
        builder
            .UseMauiApp<App>()
            .ConfigureFonts(fonts =>
            {
                fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
                fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
            });

        builder.Services.AddKeyedTransient<ILogger, FileLogger>("simple");
        builder.Services.AddKeyedTransient<ILogger, FileLoggerExt>("ext");

#if DEBUG
        builder.Logging.AddDebug();
#endif

        return builder.Build();
    }
}

в качестве ключа при регистрации сервиса мы использовали строку, хотя можем использовать вообще любые объекты. Теперь попробуем запросить оба эти сервиса в нашем приложении. Изменим код класса MainPage следующим образом:

public partial class MainPage : ContentPage
{
    int count = 0;

    private ILogger _logger;
    private ILogger _extlogger;

    public MainPage([FromKeyedServices("ext")] ILogger extlogger) 
    {
        InitializeComponent();
        _extlogger = extlogger;
        HandlerChanged += OnHandlerChanged;
    }


    private void OnHandlerChanged(object? sender, EventArgs e)
    {
        _logger ??= Handler.MauiContext.Services.GetKeyedService<ILogger>("simple");
    }

    private async void OnCounterClicked(object sender, EventArgs e)
    {
        count++;

        if (count == 1)
            CounterBtn.Text = $"Clicked {count} time";
        else
            CounterBtn.Text = $"Clicked {count} times";

        SemanticScreenReader.Announce(CounterBtn.Text);
        //используем сервис
        _logger.WriteLine(CounterBtn.Text);
        _extlogger.WriteLine(CounterBtn.Text);
    }
}

Здесь мы запрашиваем оба сервиса, используя различные способы. Первый сервис мы запрашиваем через конструктор:

public MainPage([FromKeyedServices("ext")] ILogger extlogger)

а второй — используя локатор сервисов:

_logger ??= Handler.MauiContext.Services.GetKeyedService<ILogger>("simple");

Обратите внимание на то, как запрашивается сервис в конструкторе — здесь мы используем специальный атрибут [FromKeyedServices("ext")] в котором указываем ключ сервиса.

Теперь можно запустить приложение, кликнуть по кнопке и убедиться, что в приложении используется две реализации одного и того де сервиса. Вот как будет выглядеть лог:

[23.03.2025 18:45:18] - Clicked 1 time
[Calculator] [23.03.2025 18:45:20] - Clicked 1 time

Итого

Использование сервисов с ключами позволяет зарегистрировать в контейнере DI две и более реализации одного и того же сервиса. Для регистрации сервисов с ключами используются методы расширения IServiceCollection вида AddKeyed[Lifetime](). При запросе сервиса из контейнера используется либо метод GetKeyedService(), либо, при constructor injection — с использованием специального атрибута FromKeyedServices с ключом сервиса.

Подписаться
Уведомить о
guest
0 Комментарий
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии