Содержание
В .NET зависимости могут иметь различные жизненные циклы (всего выделяют три цикла) от которых зависит поведение сервиса. В этой части мы рассмотрим каким образом мы можем регистрировать свои сервисы в приложении и как жизненный цикл влияет на работу сервиса.
Жизненный цикл зависимостей в Blazor Hybrid
Всего выделяется три типа жизненных циклов зависимостей (сервисов):
- Transient (временные) — реализация такого сервиса создается при каждом его запросе у провайдера
- Scoped (ограниченный) — сервисы с таким жизненным циклом существуют в рамках определенной области (scope). Как только запрос сервиса происходит из другой области — создается новый объект сервиса.
- Singleton (одноэлементные) — объект сервиса создается один раз (при первом запросе) и существует до окончания работы приложения.
Для демонстрации работы сервиса в зависимости от установленного для него жизненного цикла, создадим новое приложение Blazor Hybrid, добавим в него новую папку Services и разместим в папке следующий сервис:
public interface ITimeService
{
public DateTime GetDateTime();
}
также добавим реализацию этого сервиса
public class TimeService : ITimeService
{
private readonly DateTime _datetime;
public TimeService()
{
_datetime = DateTime.Now;
}
public DateTime GetDateTime()
{
return _datetime;
}
}
Смысл нашего сервиса — показать дату/время, когда создавался очередной экземпляр. Теперь посмотрим как будет работать этот сервис. Для этого перейдем к файлу компонента Home.razor и изменим его код следующим образом:
@page "/" @using BlazorDiLifiteme.Services @inject ITimeService TimeService <h1>Hello, world!</h1> <p>@TimeService.GetDateTime()</p>
Теперь нам осталось только зарегистрировать наш сервис различными способами и посмотреть на результат.
Методы регистрации сервисов
Мы уже знаем, что для регистрации сервисов в приложении Blazor Hybrid необходимо перейти в MauiProgram.cs и, используя свойство MauiApBuilder.Services добавить сервис в контейнер DI. Для регистрации мы используем методы расширения для IServiceCollection имена которых соответствуют следующему шаблону:
Add{Keyed}[Lifecycle]()
здесь Keyed — часть названия метода, которая может отсутствовать. Сервисы, которые регистрируются с использованием методов AddKeyed... также называются сервисами с ключами (о таких сервисах мы поговорим позднее). [Lifecycle] — один из жизненных циклов сервиса (Transient, Scoped или Singleton).
Таким образом, имена методов для регистрации сервисов могут быть такими:
AddTransient()AddScoped()AddSingleton()AddKeyedTransient()AddKeyedScoped()AddKeyedSingleton()
При этом, каждый такой метод может иметь несколько перегруженных версий с различными параметрами. Например, метод AddTransient() на момент выхода .NET 9 имел девять перегруженных версий. Мы рассмотрим только наиболее распространенные варианты методов регистрации сервисов.
AddTransient() вы можете ознакомиться в этой статье блогаTransient (временный) сервис
Объект такого сервиса создается каждый раз при запросе. Если нет необходимости хранить данных состоянии, сервис выполняет простые задачи, например, отправляет уведомление, электронное письмо и так далее, то можно зарегистрировать transient-сервис. Зарегистрируем наш сервис в приложении как временный, добавив в файл 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");
});
builder.Services.AddTransient<ITimeService, TimeService>(); //регистрируем сервис
builder.Services.AddMauiBlazorWebView();
#if DEBUG
builder.Services.AddBlazorWebViewDeveloperTools();
builder.Logging.AddDebug();
#endif
return builder.Build();
}
}
Здесь мы использовали одну из версий метода, указав тип сервиса и тип, реализующий сервис:
builder.Services.AddTransient<ITimeService, TimeService>(); //регистрируем сервис
Теперь запустим приложение и убедимся, что сервис вернул нам время создания своего экземпляра:
Теперь попробуем перейти, например, на страницу Counter, а затем снова вернуться на главную — вы увидите, что время изменяется каждый раз как только вы возвращаетесь к компоненту Home:
То есть, как и ожидалось, сервис создается каждый раз при его запросе из контейнера.
Singleton (одноэлементный) сервис
Такой сервис создается один раз при первом запросе, а при повторных запросах возвращается уже созданный ранее объект. Такие виды сервисов нужны в том случае, если необходимо хранить состояние системы (или её части) или, когда создание/уничтожение transient-сервиса может быть дороже, чем постоянно держать в памяти один объект singleton-сервиса.
Изменим в MauiProgram.cs способ регистрации сервиса:
builder.Services.AddSingleton<ITimeService, TimeService>(); //регистрируем сервис
Запустим приложение и повторим те же операции, что и в предыдущем случае. Сколько бы раз вы не возвращались к Home — сервис будет возвращать всегда одно и то же значение времени так как объект сервиса не уничтожается.
Scoped (ограниченный) сервис
Действие по умолчанию в Blazor Hybrid
Как было сказано выше, такие сервисы «живут» в какой-то ограниченной области. Что это за область? Зависит от типа приложения .NET с которым мы работаем. Например, если речь идет о приложениях ASP.NET Core Web API, MVC и так далее, то для таких приложений «областью» действия сервиса выступает http-запрос — пока обрабатывается запрос пользователя сервис «живет». Что касается приложений Blazor, то здесь, по умолчанию, scoped-сервисы ведут себя как обычные singleton-сервисы. Например, зарегистрируем на сервис следующим образом:
builder.Services.AddScoped<ITimeService, TimeService>(); //регистрируем сервис
Теперь запустим приложение и снова попытаемся несколько раз перейти на страницу Home. Опять же, как и в случае с Singleton — мы будем получать всё время одно и то же значение времени. Однако, это не означает, что scoped-сервисы бесполезны в Blazor Hybrid. Мы можем создать область, соответствующую времени жизни компонента Razor. Для этого нам потребуется изменить предка для компонента.
Использование классов OwningComponentBase и OwningComponentBase<TService>
Класс OwningComponentBase — это абстрактный класс производный от ComponentBase (базового класса всех компонентов Razor) который предоставляет нам защищенное свойство
protected IServiceProvider ScopedServices { get; }
благодаря которому мы можем запрашивать сервисы с областью действия, соответствующей времени жизни компонента. Чтобы продемонстрировать работу с этим классом перепишем компонент Home следующим образом:
@page "/"
@using BlazorDiLifiteme.Services
@inherits OwningComponentBase
@inject ITimeService TimeService
<h1>Hello, world!</h1>
<p>Scoped-сервис по умолчанию: @(TimeService.GetDateTime())</p>
<p>Scoped-сервис @(ScopedTimeService.GetDateTime())</p>
@code{
private ITimeService ScopedTimeService = default!;
protected override void OnInitialized()
{
ScopedTimeService = ScopedServices.GetRequiredService<ITimeService>();
}
}
Рассмотрим этот код немного подробнее. Первое, на сто необходимо обратить внимание — это на новую для нас директиву Razor:
@inherits OwningComponentBase
Эта директива означает, что компонент наследуется от класса OwningComponentBase. Второй момент — теперь мы дважды запрашиваем сервис: первый раз — как обычно, через свойство:
@inject ITimeService TimeService
а второй раз — используя свойство ScopedServices родительского класса компонента:
protected override void OnInitialized()
{
ScopedTimeService = ScopedServices.GetRequiredService<ITimeService>();
}
При этом, для получения сервиса мы используем метод GetRequiredService() смысл которого заключается в том, чтобы вернуть либо готовый к использованию экземпляр сервиса, либо сгенерировать исключение, если требуемый сервис не будет обнаружен в контейнере.
Теперь запустим приложение. При запуске мы увидим, что время, возвращаемое обоими экземплярами сервиса полностью совпадают:
Однако, как только вы перейдете на другую страницу и снова вернетесь к Home, то время будет различаться:
так как во втором случае (при запросе с помощью свойства ScopedServices) мы получаем сервис ограниченный временем жизни компонента.
Что касается класса OwningComponentBase<TService>, то этот класс наследуется от OwningComponentBase и предоставляет нам новое свойство:
protected TService Service { get; }
То есть можно переписать код компонента следующим образом
@page "/" @using BlazorDiLifiteme.Services @inherits OwningComponentBase<ITimeService> @inject ITimeService TimeService <h1>Hello, world!</h1> <p>Scoped-сервис по умолчанию: @(TimeService.GetDateTime())</p> <p>Scoped-сервис @(Service.GetDateTime())</p>
и получить точно такой же результат, как и в предыдущем случае.
Итого
Сервисы в .NET могут иметь три типа жизненных циклов: Transient (временные) — реализация такого сервиса создается при каждом его запросе у провайдера; Scoped (ограниченный) — сервисы с таким жизненным циклом существуют в рамках определенной области (scope) и Singleton (одноэлементные) — объект сервиса создается один раз (при первом запросе) и существует до окончания работы приложения. В зависимости от того, какой жизненный цикл предполагается использовать для зависимости (сервиса) выбирается их метод регистрации в ServiceCollection — AddTransient(), AddScoped() или AddSingleton(). Для того, чтобы сервис в приложении Blazor Hybrid был ограничен областью, соответствующей времени жизни компонента Razor, такой компонент должен наследоваться от OwningComponentBase или его наследника — OwningComponentBase<TService>.


