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