Множественная регистрация сервисов в Blazor Hybrid

В подавляющем большинстве случаев, приложения, использующие внедрение зависимостей строятся по принципу «один сервис — одна используемая реализация сервиса». При необходимости, мы создаем другую реализацию сервиса (например, при обновлении приложения) и регистрируем эту реализацию в приложении. Однако, может возникнуть ситуация, когда нам потребуется на один сервис зарегистрировать несколько реализаций. Например, вы решите написать приложение для подсчета количества рабочих дней по пяти- и шестидневной рабочей неделе. В этом случае у вас будет один сервис (рабочий календарь) и две его реализации — для пятидневной рабочей недели и шестидневной. В этом случае вам пригодится множественная регистрация сервисов.

Проблема множественной регистрации сервиса

В предыдущей части мы разработали сервис ITimeService, который выглядит следующим образом:

public interface ITimeService
{
    public DateTime GetDateTime(); 
}

а также его реализацию:

public class TimeService : ITimeService
{
    private readonly DateTime _datetime;

    public TimeService()
    {
        _datetime = DateTime.Now;
    }

    public DateTime GetDateTime()
    {
        return _datetime;
    }
}

Создадим новое приложение Blazor Hybrid и добавим этот сервис и его реализацию в приложение в папку Services:

Реализация нашего сервиса возвращает время создания объекта. Допустим, мы решили создать ещё одну реализацию сервиса, которая будет возвращать текущее время. Добавим в папку Services ещё один класс:

public class CurrentTimeService : ITimeService
{
    public DateTime GetDateTime()
    {
        return DateTime.Now;
    }
}

Зарегистрируем обе реализации в приложении:

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

            builder.Services.AddSingleton<ITimeService, TimeService>(); //регистрируем первую реализацию
            builder.Services.AddSingleton<ITimeService, CurrentTimeService>(); //регистрируем вторую реализацию

            builder.Services.AddMauiBlazorWebView();

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

            return builder.Build();
        }
    }

Как нам теперь запросить в компоненте Razor, например, в Home разные реализации сервиса? Или только реализацию TimeService? С приведенным выше способом регистрации мы всегда будем получать последнюю зарегистрированную версию сервиса, то есть CurrentTimeService. Мы, конечно, можем осуществить регистрацию сервисов вот так:

builder.Services.AddSingleton<ITimeService, TimeService>(); //регистрируем первую реализацию
builder.Services.AddSingleton<CurrentTimeService>(); //регистрируем вторую реализацию

а потом запрашивать второй сервис в компоненте так:

@inject ITimeService TimeService
@inject CurrentTimeService CurrentTimeService

но тогда теряется суть внедрения зависимостей. Зачем «городить» разные реализации сервиса, если в итоге наш компонент запрашивает конкретную реализацию? В итоге, при таком подходе мы столкнемся с теми же проблемами поддержки приложения, что и без внедрения зависимостей. Поэтому, лучше воспользоваться другим подходом — осуществить множественную регистрацию сервиса с использованием сервисов с ключами.

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

Используя методы расширения IServiceCollection с именами соответствующими шаблону AddKeyed[Lificycle]() мы можем назначить каждой реализации сервиса свой уникальный ключ и, используя этот ключ в дальнейшем, запрашивать в компонентах конкретные реализации сервисов, не теряя при этом смысла использования механизма внедрения зависимостей в приложении. Перепишем код MauiProgram.cs следующим образом:

using BlazorDiLifiteme.Services;

using Microsoft.Extensions.Logging;

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

            builder.Services.AddKeyedSingleton<ITimeService, TimeService>("createTime"); //регистрируем первую реализацию
            builder.Services.AddKeyedSingleton<ITimeService, CurrentTimeService>("currentTime"); //регистрируем вторую реализацию

            builder.Services.AddMauiBlazorWebView();

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

            return builder.Build();
        }
    }
}

Теперь реализация сервиса TimeService получит ключ «createTime«, а CurrentTimeService — ключ «currentTime«. В качестве ключа могут выступать любые объекты, включая и строки. Теперь остается только запросить необходимые нам реализации сервиса.

К сожалению, директива Razor @inject не поддерживает работу с сервисами для которых определены ключи и при попытке получить сервис через свойство следующим образом:

@inject ITimeService TimeService
@inject ITimeService CurrentTimeService

мы получим ошибку:

Cannot provide a value for property ‘CurrentTimeService’ on type ‘Components.Pages.Home’. There is no registered service of type ‘Services.ITimeService’.

Единственный, на данный момент, способом запросить в компоненте сервисы с ключами является использование атрибута [Inject]. Для этого нам необязательно создавать разделяемый класс. Например, мы можем переписать код компонента Home следующим образом:

@page "/"

@using BlazorDiLifiteme.Services

<h1>Hello, world!</h1>

<p>TimeService: @(TimeService.GetDateTime())</p>
<p>CurrentTimeService @(CurrentTimeService.GetDateTime())</p>


@code{
    [Inject(Key = "createTime")]
    private ITimeService TimeService { get; set; }

    [Inject(Key = "currentTime")]
    private ITimeService CurrentTimeService { get; set; }
}

Здесь в коде C# компонента мы создаем два новый свойства с атрибутами [Inject], указав в свойстве атрибута Key ключи сервисов. Теперь эти свойства получат необходимые им реализации сервисов. Запустим приложение и убедимся в этом:

При этом, мы сохраняем преимущества Dependency Injection — компоненту не важно, что скрывается за интерфейсом ITimeService, а в случае необходимости, мы можем в файле MauiProgram.cs заменить одну реализацию сервиса на другую, сохранив ключ сервиса.

Итого

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

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