Внедрение зависимостей Blazor в ASP.NET Core
Примечание.
Это не последняя версия этой статьи. В текущем выпуске см . версию .NET 8 этой статьи.
Предупреждение
Эта версия ASP.NET Core больше не поддерживается. Дополнительные сведения см. в статье о политике поддержки .NET и .NET Core. В текущем выпуске см . версию .NET 8 этой статьи.
Внимание
Эта информация относится к предварительному выпуску продукта, который может быть существенно изменен до его коммерческого выпуска. Майкрософт не предоставляет никаких гарантий, явных или подразумеваемых, относительно приведенных здесь сведений.
В текущем выпуске см . версию .NET 8 этой статьи.
Авторы: Райнер Стропек (Rainer Stropek) и Майк Роусос (Mike Rousos)
В этой статье объясняется, как приложения Blazor добавляют службы в компоненты.
Внедрение зависимостей — это методика доступа к службам, настроенным в центральном расположении.
- Зарегистрированные платформы службы можно внедрять непосредственно в Razor компоненты.
- Приложения Blazor определяют и регистрируют пользовательские службы и предоставляют к ним доступ в рамках всего приложения с помощью внедрения зависимостей.
Примечание.
Перед прочтением этого раздела рекомендуем ознакомиться со статьей Внедрение зависимостей в ASP.NET Core.
Службы по умолчанию
В таблице ниже представлены службы, часто используемые в приложениях Blazor.
Service | Время существования | Description |
---|---|---|
HttpClient | Ограниченные | Предоставляет методы для отправки HTTP-запросов и получения HTTP-ответов от ресурса с заданным URI. На стороне клиента экземпляр HttpClient регистрируется приложением в Серверная сторона не HttpClient настроена как услуга по умолчанию. В серверном коде укажите HttpClient. Дополнительные сведения см. в статье Вызов веб-API в приложении ASP.NET Core Blazor. HttpClient регистрируется как служба с заданной областью, а не как singleton. Дополнительные сведения см. в разделе Время существования службы. |
IJSRuntime | Клиентская сторона: Singleton Серверная сторона: область действия Платформа Blazor регистрирует IJSRuntime в контейнере службы приложения. |
Представляет экземпляр среды выполнения JavaScript, в которую отправляются вызовы JavaScript. Дополнительные сведения см. в статье Вызов функций JavaScript из методов .NET в ASP.NET Core Blazor. При попытке внедрить службу в одну службу на сервере выполните любой из следующих подходов:
|
NavigationManager | Клиентская сторона: Singleton Серверная сторона: область действия Платформа Blazor регистрирует NavigationManager в контейнере службы приложения. |
Содержит вспомогательные методы для работы с URI и состоянием навигации. Дополнительные сведения см. в разделе URI и вспомогательные инструменты состояния навигации. |
Дополнительные службы, которые регистрируются платформой Blazor, описаны в документации, в которой они используются для описания конфигурации, ведения журнала и других функций Blazor.
Пользовательский поставщик услуг не предоставляет перечисленные в таблице службы по умолчанию автоматически. Если вы используете пользовательский поставщик услуг и нуждаетесь в какой-либо из служб, указанных в таблице, добавьте необходимые службы в новый поставщик услуг.
Добавление клиентских служб
Настройте службы для коллекции служб приложения в Program
файле. В следующем примере реализация ExampleDependency
зарегистрирована для IExampleDependency
:
var builder = WebAssemblyHostBuilder.CreateDefault(args);
...
builder.Services.AddSingleton<IExampleDependency, ExampleDependency>();
...
await builder.Build().RunAsync();
После сборки узла доступ к службам можно получить из корневой области внедрения зависимостей до отрисовки каких-либо компонентов. Это может быть удобно для запуска логики инициализации перед отрисовкой содержимого:
var builder = WebAssemblyHostBuilder.CreateDefault(args);
...
builder.Services.AddSingleton<WeatherService>();
...
var host = builder.Build();
var weatherService = host.Services.GetRequiredService<WeatherService>();
await weatherService.InitializeWeatherAsync();
await host.RunAsync();
Узел предоставляет центральный экземпляр конфигурации для приложения. Основываясь на предыдущем примере, URL-адрес службы погоды передается из источника конфигурации по умолчанию (например, appsettings.json
) в InitializeWeatherAsync
:
var builder = WebAssemblyHostBuilder.CreateDefault(args);
...
builder.Services.AddSingleton<WeatherService>();
...
var host = builder.Build();
var weatherService = host.Services.GetRequiredService<WeatherService>();
await weatherService.InitializeWeatherAsync(
host.Configuration["WeatherServiceUrl"]);
await host.RunAsync();
Добавление серверных служб
После создания приложения изучите часть файла Program
.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorComponents()
.AddInteractiveServerComponents();
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor();
builder.Services.AddSingleton<WeatherForecastService>();
Переменная builder
представляет собой WebApplicationBuilder с IServiceCollection, который является списком объектов дескриптора службы. Службы добавляются путем предоставления дескрипторов служб в коллекцию служб. В следующем примере показана концепция с интерфейсом IDataAccess
и его конкретной реализацией DataAccess
.
builder.Services.AddSingleton<IDataAccess, DataAccess>();
После создания приложения изучите метод Startup.ConfigureServices
в Startup.cs
.
using Microsoft.Extensions.DependencyInjection;
...
public void ConfigureServices(IServiceCollection services)
{
...
}
В метод ConfigureServices передается IServiceCollection, представляющий собой список объектов дескриптора службы. Службы добавляются в метод ConfigureServices
путем предоставления дескрипторов служб в коллекцию служб. В следующем примере показана концепция с интерфейсом IDataAccess
и его конкретной реализацией DataAccess
.
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IDataAccess, DataAccess>();
}
Регистрация общих служб
Если для одной или нескольких общих служб требуются клиентские и серверные службы, вы можете разместить общие регистрации служб на стороне клиента метода и вызвать метод для регистрации служб в обоих проектах.
Во-первых, включите регистрации общих служб в отдельный метод. Например, создайте ConfigureCommonServices
клиент метода:
public static void ConfigureCommonServices(IServiceCollection services)
{
services.Add...;
}
Для клиентского Program
файла вызовите ConfigureCommonServices
регистрацию общих служб:
var builder = WebAssemblyHostBuilder.CreateDefault(args);
...
ConfigureCommonServices(builder.Services);
В файле на стороне Program
сервера вызовите ConfigureCommonServices
регистрацию общих служб:
var builder = WebApplication.CreateBuilder(args);
...
Client.Program.ConfigureCommonServices(builder.Services);
Дополнительные примеры такой реализации см. в статье Сценарии обеспечения дополнительной безопасности для ASP.NET Core Blazor WebAssembly.
Клиентские службы, которые завершаются сбоем во время предварительной подготовки
Этот раздел применяется только к компонентам WebAssembly в Blazor Web Apps.
Blazor Web Appобычно предопределенные клиентские компоненты WebAssembly. Если приложение выполняется только с обязательной службой, зарегистрированной в .Client
проекте, выполнение приложения приводит к ошибке среды выполнения, аналогичной следующей, когда компонент пытается использовать требуемую службу во время предварительной подготовки:
InvalidOperationException: не удается указать значение для {PROPERTY} в типе "{ASSEMBLY}}". Client.Pages. {ИМЯ КОМПОНЕНТА}". Зарегистрированная служба типа "{SERVICE}" отсутствует.
Используйте любой из следующих подходов для решения этой проблемы:
- Зарегистрируйте службу в основном проекте, чтобы сделать ее доступной во время предварительной подготовки компонентов.
- Если предварительная отрисовка не требуется для компонента, отключите предварительную отрисовку, следуя указаниям в режимах отрисовки ASP.NET CoreBlazor. Если вы используете этот подход, вам не нужно регистрировать службу в основном проекте.
Дополнительные сведения см. в разделе "Не удается разрешить клиентские службы во время предварительной подготовки".
Время существования службы
Для служб можно настроить параметры времени существования, указанные в следующей таблице.
Время существования | Description |
---|---|
Scoped | На стороне клиента в настоящее время нет концепции областей DI. Службы, зарегистрированные как Разработка на стороне сервера поддерживает
Дополнительные сведения о сохранении состояния пользователя в серверных приложениях см. в разделе ASP.NET Управление состоянием CoreBlazor. |
Singleton | Система внедрения зависимостей создает один экземпляр службы. Все компоненты, для которых требуется служба Singleton , получают один и тот же экземпляр. |
Transient | Каждый раз, когда компонент получает экземпляр службы Transient из контейнера службы, он получает новый экземпляр этой службы. |
Система внедрения зависимостей основана на системе внедрения зависимостей в ASP.NET Core. Дополнительные сведения см. в статье Внедрение зависимостей в ASP.NET Core.
Запрос службы в компоненте
Для внедрения служб в компоненты поддерживает внедрение конструктора Blazor и внедрение свойств.
Внедрение конструктора
После добавления служб в коллекцию служб добавьте одну или несколько служб в компоненты с внедрением конструктора. В следующем примере служба внедряется NavigationManager
.
ConstructorInjection.razor
:
@page "/constructor-injection"
<button @onclick="HandleClick">
Take me to the Counter component
</button>
ConstructorInjection.razor.cs
:
using Microsoft.AspNetCore.Components;
public partial class ConstructorInjection(NavigationManager navigation)
{
private void HandleClick()
{
navigation.NavigateTo("/counter");
}
}
Внедрение свойств
После добавления служб в коллекцию служб добавьте одну или несколько служб в компоненты с @inject
Razor директивой, которая имеет два параметра:
- Тип: тип службы для внедрения.
- Свойство: имя свойства, получающего внедренную службу приложений. Свойство не требуется создавать вручную. Его создает компилятор.
См. дополнительные сведения о внедрении зависимостей в представления ASP.NET Core.
Используйте несколько инструкций @inject
для внедрения различных служб.
В следующем примере показано, как использовать директиву @inject
. Служба, реализующая Services.NavigationManager
, внедряется в свойство Navigation
компонента. Обратите внимание, что код используется только с помощью NavigationManager
абстракции.
PropertyInjection.razor
:
@page "/property-injection"
@inject NavigationManager Navigation
<button @onclick="@(() => Navigation.NavigateTo("/counter"))">
Take me to the Counter component
</button>
На внутреннем уровне создаваемое свойство (Navigation
) использует атрибут [Inject]
. Как правило, этот атрибут не используется напрямую. Если базовый класс необходим для компонентов, а для базового класса также требуются обязательные свойства, добавьте атрибут [Inject]
вручную:
using Microsoft.AspNetCore.Components;
public class ComponentBase : IComponent
{
[Inject]
protected NavigationManager Navigation { get; set; } = default!;
...
}
Примечание.
Так как ожидается, что внедренные службы будут доступны, литерал по умолчанию с оператором прощения null (default!
) назначается в .NET 6 или более поздней версии. Дополнительные сведения см. в статьях ссылочных типов с возможностью NULL (NRTs) и статического анализа состояния компилятора .NET.
В компонентах, производных от базового класса, @inject
директива не требуется. Базовый InjectAttribute класс достаточно. Для компонента требуется только директива @inherits
. В следующем примере все внедренные службы CustomComponentBase
доступны компоненту Demo
:
@page "/demo"
@inherits CustomComponentBase
Использование внедрения зависимостей в службах
Для сложных служб могут потребоваться дополнительные службы. В приведенном ниже примере для DataAccess
требуется служба HttpClient по умолчанию. @inject
(или атрибут [Inject]
) невозможно использовать в службах. Вместо этого следует использовать внедрения конструктора. Необходимые службы добавляются путем добавления параметров в конструктор службы. Когда система внедрения зависимостей создает службу, она распознает необходимые службы в конструкторе и предоставляет их соответствующим образом. В следующем примере конструктор получает HttpClient через внедрение зависимостей. HttpClient — это служба по умолчанию.
using System.Net.Http;
public class DataAccess : IDataAccess
{
public DataAccess(HttpClient http)
{
...
}
...
}
Внедрение конструктора поддерживается с основными конструкторами в C# 12 (.NET 8) или более поздней версии:
using System.Net.Http;
public class DataAccess(HttpClient http) : IDataAccess
{
...
}
Необходимые условия для внедрения конструктора:
- Должен существовать один конструктор, аргументы которого могут использоваться системой внедрения зависимостей. Дополнительные параметры, не охваченные системой внедрения зависимостей, разрешены, если они указывают значения по умолчанию.
- Применимый конструктор должен быть
public
. - Должен существовать один подходящий конструктор. В случае неоднозначности система внедрения зависимостей выдает исключение.
Внедрение ключевых служб в компоненты
Blazor поддерживает внедрение ключевых служб с помощью атрибута [Inject]
. Ключи позволяют определить регистрацию и потребление служб при использовании внедрения зависимостей. InjectAttribute.Key Используйте свойство, чтобы указать ключ для службы для внедрения:
[Inject(Key = "my-service")]
public IMyService MyService { get; set; }
Служебные классы базовых компонентов служебной программы для управления областью внедрения зависимостей
В приложениях, отличныхBlazor от ASP.NET Core, области и временные службы обычно применяются к текущему запросу. После завершения запроса область действия и временные службы удаляются системой DI.
В интерактивных приложениях на стороне Blazor сервера область di длится в течение длительности канала ( SignalR соединение между клиентом и сервером), что может привести к ограниченному и устранимому временным службам, живущим гораздо дольше времени существования одного компонента. Поэтому не внедряйте непосредственно службу с областью действия в компонент, если планируется время существования службы совпадать со временем существования компонента. Временные службы, внедренные в компонент, который не реализует IDisposable , собираются мусор при удалении компонента. Однако внедренные временные службы , которые реализуются IDisposable контейнером DI в течение всего времени существования канала, что предотвращает сборку мусора службы при удалении компонента и приводит к утечке памяти. Альтернативный подход к службам, основанным на типе OwningComponentBase , описан далее в этом разделе, а удаленные временные службы не должны использоваться вообще. Дополнительные сведения см. в разделе "Проектирование для решения временных одноразовых ресурсов" Blazor Server (dotnet/aspnetcore
No 26676).
Даже в клиентских Blazor приложениях, которые не работают над каналом, службы, зарегистрированные в пределах времени существования, рассматриваются как одноэлементные, поэтому они живут дольше, чем в обычных приложениях ASP.NET Core. Клиентские временные службы также живут дольше, чем компоненты, в которых они внедряются, так как контейнер DI, содержащий ссылки на удаленные службы, сохраняется в течение всего времени существования приложения, предотвращая сборку мусора в службах. Хотя долгосрочные временные службы имеют большую озабоченность на сервере, их следует избежать как регистрации клиентских служб. OwningComponentBase Использование типа также рекомендуется для клиентских служб с ограниченной областью для управления временем существования службы, а удаленные временные службы не должны использоваться вообще.
Подход, ограничивающий время существования службы, использует OwningComponentBase тип. OwningComponentBase является абстрактным типом, производным от ComponentBase этого, создает область DI, соответствующую времени существования компонента. С помощью этой области компонент может внедрять службы с заданной областью существования и иметь их срок жизни до тех пор, пока компонент не будет. При удалении компонента также удаляются и службы из поставщика служб с заданной областью действия этого компонента. Это может быть полезно для служб, повторно используемых в компоненте, но не совместно используемых между компонентами.
Доступны две версии типа OwningComponentBase, которые описаны в следующих двух разделах:
OwningComponentBase
OwningComponentBase является абстрактной высвобождаемой дочерней версией типа ComponentBase с защищенным свойством ScopedServices типа IServiceProvider. Этот поставщик можно использовать для разрешения служб, ограниченных временем существования компонента.
Службы внедрения зависимостей, внедренные в компонент с помощью @inject
или атрибута [Inject]
, не создаются в области компонента. Чтобы использовать область компонента, необходимо разрешить службы с помощью ScopedServices с GetRequiredService или GetService. Все службы, разрешенные с помощью поставщика ScopedServices, имеют свои зависимости в области компонента.
В следующем примере показано различие между внедрением ограниченной службы напрямую и разрешением службы, используемой ScopedServices на сервере. Следующий интерфейс и реализация для класса перехода по времени включают свойство DT
для хранения значения DateTime. Реализация вызывает DateTime.Now, чтобы задать DT
при создании экземпляра класса TimeTravel
.
ITimeTravel.cs
:
public interface ITimeTravel
{
public DateTime DT { get; set; }
}
TimeTravel.cs
:
public class TimeTravel : ITimeTravel
{
public DateTime DT { get; set; } = DateTime.Now;
}
Служба зарегистрирована как область действия в файле на стороне Program
сервера. Серверные службы имеют время существования, равное длительности канала.
В файле Program
:
builder.Services.AddScoped<ITimeTravel, TimeTravel>();
Следующий компонент TimeTravel
:
- Служба перехода по времени напрямую внедряется с
@inject
кTimeTravel1
. - Служба также разрешается отдельно с ScopedServices и GetRequiredService как
TimeTravel2
.
TimeTravel.razor
:
@page "/time-travel"
@inject ITimeTravel TimeTravel1
@inherits OwningComponentBase
<h1><code>OwningComponentBase</code> Example</h1>
<ul>
<li>TimeTravel1.DT: @TimeTravel1?.DT</li>
<li>TimeTravel2.DT: @TimeTravel2?.DT</li>
</ul>
@code {
private ITimeTravel TimeTravel2 { get; set; } = default!;
protected override void OnInitialized()
{
TimeTravel2 = ScopedServices.GetRequiredService<ITimeTravel>();
}
}
@page "/time-travel"
@inject ITimeTravel TimeTravel1
@inherits OwningComponentBase
<h1><code>OwningComponentBase</code> Example</h1>
<ul>
<li>TimeTravel1.DT: @TimeTravel1?.DT</li>
<li>TimeTravel2.DT: @TimeTravel2?.DT</li>
</ul>
@code {
private ITimeTravel TimeTravel2 { get; set; } = default!;
protected override void OnInitialized()
{
TimeTravel2 = ScopedServices.GetRequiredService<ITimeTravel>();
}
}
Первоначально переходя к компоненту TimeTravel
, дважды создается экземпляр службы перехода по времени при загрузке компонента, а TimeTravel1
и TimeTravel2
имеют одинаковое начальное значение:
TimeTravel1.DT: 8/31/2022 2:54:45 PM
TimeTravel2.DT: 8/31/2022 2:54:45 PM
При переходе от компонента к TimeTravel
другому компоненту и обратно к компоненту TimeTravel
:
TimeTravel1
предоставляется тот же экземпляр службы, который был создан при первой загрузке компонента, поэтому значениеDT
остается неизменным.TimeTravel2
получает новый экземпляр службыITimeTravel
вTimeTravel2
с новым значением DT.
TimeTravel1.DT: 8/31/2022 2:54:45 PM
TimeTravel2.DT: 8/31/2022 2:54:48 PM
TimeTravel1
привязан к каналу пользователя, который остается нетронутым и не удаляется, пока базовый канал не будет деконструирован. Например, служба удаляется, если канал отключен для периода хранения отключенного канала.
Несмотря на регистрацию службы в Program
файле и долголетие канала пользователя, TimeTravel2
каждый раз при инициализации компонента получает новый ITimeTravel
экземпляр службы.
OwningComponentBase<TService>
OwningComponentBase<TService> является производным от OwningComponentBase и добавляет свойство Service, которое возвращает экземпляр T
из поставщика внедрения зависимостей с заданной областью. Этот тип удобен для доступа к службам с заданной областью без использования экземпляра IServiceProvider при наличии одной основной службы, которую приложение запрашивает из контейнера внедрения зависимостей с использованием области компонента. Свойство ScopedServices доступно, поэтому при необходимости приложение может получить службы других типов.
@page "/users"
@attribute [Authorize]
@inherits OwningComponentBase<AppDbContext>
<h1>Users (@Service.Users.Count())</h1>
<ul>
@foreach (var user in Service.Users)
{
<li>@user.UserName</li>
}
</ul>
Обнаружение временных временных удаленных решений на стороне клиента
Пользовательский код можно добавить в клиентское Blazor приложение для обнаружения удаленных временных служб в приложении, которое должно использоваться OwningComponentBase. Этот подход полезен, если вы обеспокоены тем, что код, добавленный в приложение в будущем, использует одну или несколько временных удаленных служб, включая службы, добавленные библиотеками. Демонстрационный код доступен в Blazor репозитории примеров GitHub (как скачать).
Проверьте следующие версии примера в .NET 6 или более поздних версиях BlazorSample_WebAssembly
:
DetectIncorrectUsagesOfTransientDisposables.cs
Services/TransientDisposableService.cs
- Включено:
Program.cs
- Пространство имен приложения
Services
предоставляется в верхней части файла (using BlazorSample.Services;
). DetectIncorrectUsageOfTransients
вызывается сразу послеbuilder
назначения из WebAssemblyHostBuilder.CreateDefault.- Зарегистрировано
TransientDisposableService
(builder.Services.AddTransient<TransientDisposableService>();
). EnableTransientDisposableDetection
вызывается на встроенном узле в конвейере обработки приложения (host.EnableTransientDisposableDetection();
).
- Пространство имен приложения
- Приложение регистрирует
TransientDisposableService
службу без исключения. Однако при попытке разрешения службы возникаетTransientService.razor
InvalidOperationException ошибка при попытке платформы создать экземплярTransientDisposableService
.
Обнаружение временных временных удалений на стороне сервера
Пользовательский код можно добавить в серверное приложение для обнаружения временных служб на стороне Blazor сервера в приложении, которое должно использоваться OwningComponentBase. Этот подход полезен, если вы обеспокоены тем, что код, добавленный в приложение в будущем, использует одну или несколько временных удаленных служб, включая службы, добавленные библиотеками. Демонстрационный код доступен в Blazor репозитории примеров GitHub (как скачать).
Проверьте следующие версии примера в .NET 8 или более поздних версиях BlazorSample_BlazorWebApp
:
Проверьте следующие версии примера в .NET 6 или .NET 7 BlazorSample_Server
:
DetectIncorrectUsagesOfTransientDisposables.cs
Services/TransitiveTransientDisposableDependency.cs
:- Включено:
Program.cs
- Пространство имен приложения
Services
предоставляется в верхней части файла (using BlazorSample.Services;
). DetectIncorrectUsageOfTransients
вызывается в построителе узлов (builder.DetectIncorrectUsageOfTransients();
).- Служба
TransientDependency
зарегистрирована (builder.Services.AddTransient<TransientDependency>();
). - Зарегистрировано
TransitiveTransientDisposableDependency
дляITransitiveTransientDisposableDependency
(builder.Services.AddTransient<ITransitiveTransientDisposableDependency, TransitiveTransientDisposableDependency>();
).
- Пространство имен приложения
- Приложение регистрирует
TransientDependency
службу без исключения. Однако при попытке разрешения службы возникаетTransientService.razor
InvalidOperationException ошибка при попытке платформы создать экземплярTransientDependency
.
Временные регистрации служб для IHttpClientFactory
/HttpClient
обработчиков
Рекомендуется регистрировать временные службы для обработчиков IHttpClientFactory/HttpClient. Если приложение содержит IHttpClientFactory/HttpClient обработчики и использует IRemoteAuthenticationBuilder<TRemoteAuthenticationState,TAccount> поддержку проверки подлинности, то также обнаруживаются следующие временные удаления для проверки подлинности на стороне клиента, которые ожидаются и могут игнорироваться:
Также обнаружены другие экземпляры IHttpClientFactory/HttpClient . Эти экземпляры также можно игнорировать.
Примеры Blazor приложений в Blazor репозитории GitHub (как скачать) демонстрируют код для обнаружения временных одноразовых носителей. Однако код деактивирован, так как примеры приложений включают IHttpClientFactory/HttpClient обработчики.
Чтобы активировать демонстрационный код и засвидетельствовать его операцию:
Раскомментируйте временные одноразовые линии в
Program.cs
.Удалите условную проверку
NavLink.razor
, которая предотвращаетTransientService
отображение компонента на боковой панели навигации приложения:- else if (name != "TransientService") + else
Запустите пример приложения и перейдите к компоненту
TransientService
/transient-service
.
Использование Entity Framework Core (EF Core) DbContext из DI
Дополнительные сведения см. в разделе ASP.NET Core с Entity Framework Core Blazor (EF Core).
Доступ к службам на стороне Blazor сервера из другой области DI
Обработчики действий канала предоставляют подход для доступа к службам с областью Blazor действия из другихBlazor областей внедрения зависимостей (DI), таких как области, созданные с помощью IHttpClientFactory.
До выпуска ASP.NET Core в .NET 8 доступ к службам с областью действия канала из других областей внедрения зависимостей, необходимых с помощью пользовательского базового типа компонента. При использовании обработчиков действий канала пользовательский базовый тип компонента не требуется, как показано в следующем примере:
public class CircuitServicesAccessor
{
static readonly AsyncLocal<IServiceProvider> blazorServices = new();
public IServiceProvider? Services
{
get => blazorServices.Value;
set => blazorServices.Value = value;
}
}
public class ServicesAccessorCircuitHandler(
IServiceProvider services, CircuitServicesAccessor servicesAccessor)
: CircuitHandler
{
public override Func<CircuitInboundActivityContext, Task> CreateInboundActivityHandler(
Func<CircuitInboundActivityContext, Task> next) =>
async context =>
{
servicesAccessor.Services = services;
await next(context);
servicesAccessor.Services = null;
};
}
public static class CircuitServicesServiceCollectionExtensions
{
public static IServiceCollection AddCircuitServicesAccessor(
this IServiceCollection services)
{
services.AddScoped<CircuitServicesAccessor>();
services.AddScoped<CircuitHandler, ServicesAccessorCircuitHandler>();
return services;
}
}
Доступ к службам с областью канала путем внедрения CircuitServicesAccessor
необходимых служб.
Пример, в котором показано, как получить доступ к настройке DelegatingHandler с помощьюIHttpClientFactory, см. в дополнительных сценариях безопасности на стороне сервера ASP.NET CoreBlazor.AuthenticationStateProvider
Иногда Razor компонент вызывает асинхронные методы, выполняющие код в другой области DI. Без правильного подхода эти области DI не имеют доступа к Blazorслужбам, таким как IJSRuntime и Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage.
Например, экземпляры, HttpClient созданные с помощью IHttpClientFactory собственной области службы DI. В результате экземпляры, настроенные в нейHttpClient, HttpMessageHandler не могут напрямую внедрять Blazor службы.
Создайте класс BlazorServiceAccessor
, определяющий AsyncLocal
, который сохраняет BlazorIServiceProvider текущий асинхронный контекст. Экземпляр BlazorServiceAccessor
можно получить из другой области службы DI для доступа к Blazor службам.
BlazorServiceAccessor.cs
:
internal sealed class BlazorServiceAccessor
{
private static readonly AsyncLocal<BlazorServiceHolder> s_currentServiceHolder = new();
public IServiceProvider? Services
{
get => s_currentServiceHolder.Value?.Services;
set
{
if (s_currentServiceHolder.Value is { } holder)
{
// Clear the current IServiceProvider trapped in the AsyncLocal.
holder.Services = null;
}
if (value is not null)
{
// Use object indirection to hold the IServiceProvider in an AsyncLocal
// so it can be cleared in all ExecutionContexts when it's cleared.
s_currentServiceHolder.Value = new() { Services = value };
}
}
}
private sealed class BlazorServiceHolder
{
public IServiceProvider? Services { get; set; }
}
}
Чтобы задать значение BlazorServiceAccessor.Services
автоматически при async
вызове метода компонента, создайте настраиваемый базовый компонент, который повторно реализует три основные асинхронные точки входа в Razor код компонента:
Следующий класс демонстрирует реализацию базового компонента.
CustomComponentBase.cs
:
using Microsoft.AspNetCore.Components;
public class CustomComponentBase : ComponentBase, IHandleEvent, IHandleAfterRender
{
private bool hasCalledOnAfterRender;
[Inject]
private IServiceProvider Services { get; set; } = default!;
[Inject]
private BlazorServiceAccessor BlazorServiceAccessor { get; set; } = default!;
public override Task SetParametersAsync(ParameterView parameters)
=> InvokeWithBlazorServiceContext(() => base.SetParametersAsync(parameters));
Task IHandleEvent.HandleEventAsync(EventCallbackWorkItem callback, object? arg)
=> InvokeWithBlazorServiceContext(() =>
{
var task = callback.InvokeAsync(arg);
var shouldAwaitTask = task.Status != TaskStatus.RanToCompletion &&
task.Status != TaskStatus.Canceled;
StateHasChanged();
return shouldAwaitTask ?
CallStateHasChangedOnAsyncCompletion(task) :
Task.CompletedTask;
});
Task IHandleAfterRender.OnAfterRenderAsync()
=> InvokeWithBlazorServiceContext(() =>
{
var firstRender = !hasCalledOnAfterRender;
hasCalledOnAfterRender |= true;
OnAfterRender(firstRender);
return OnAfterRenderAsync(firstRender);
});
private async Task CallStateHasChangedOnAsyncCompletion(Task task)
{
try
{
await task;
}
catch
{
if (task.IsCanceled)
{
return;
}
throw;
}
StateHasChanged();
}
private async Task InvokeWithBlazorServiceContext(Func<Task> func)
{
try
{
BlazorServiceAccessor.Services = Services;
await func();
}
finally
{
BlazorServiceAccessor.Services = null;
}
}
}
Все компоненты, расширяющиеся CustomComponentBase
автоматически, имеют BlazorServiceAccessor.Services
IServiceProvider значение в текущей Blazor области DI.
Наконец, в Program
файле добавьте службу с областью BlazorServiceAccessor
действия:
builder.Services.AddScoped<BlazorServiceAccessor>();
Наконец, добавьте Startup.ConfigureServices
Startup.cs
службу в BlazorServiceAccessor
качестве области:
services.AddScoped<BlazorServiceAccessor>();
Дополнительные ресурсы
- Внедрение службы с помощью файла импорта верхнего уровня (
_Imports.razor
) в Blazor Web Apps - Использование внедрения зависимостей в ASP.NET Core
- Руководство по применению временных и общих экземпляров
IDisposable
- Внедрение зависимостей в представления в ASP.NET Core
- Основные конструкторы (руководство по C#)
ASP.NET Core