Содержание
В .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>
.