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