Внедрение зависимостей 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 регистрируется приложением в Program файле и использует браузер для обработки HTTP-трафика в фоновом режиме.

Серверная сторона не HttpClient настроена как услуга по умолчанию. В серверном коде укажите HttpClient.

Дополнительные сведения см. в статье Вызов веб-API в приложении ASP.NET Core Blazor.

HttpClient регистрируется как служба с заданной областью, а не как singleton. Дополнительные сведения см. в разделе Время существования службы.

IJSRuntime

Клиентская сторона: Singleton

Серверная сторона: область действия

Платформа Blazor регистрирует IJSRuntime в контейнере службы приложения.

Представляет экземпляр среды выполнения JavaScript, в которую отправляются вызовы JavaScript. Дополнительные сведения см. в статье Вызов функций JavaScript из методов .NET в ASP.NET Core Blazor.

При попытке внедрить службу в одну службу на сервере выполните любой из следующих подходов:

  • Измените тип регистрации службы на Scoped (С областью действия) для соответствия регистрации IJSRuntime. Такой вариант подходит, если служба связана с состоянием пользователя.
  • Передайте IJSRuntime в реализацию singleton-службы в виде аргумента вызовов метода вместо внедрения в singleton.
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. Службы, зарегистрированные как Scoped, работают аналогично службам Singleton.

Разработка на стороне сервера поддерживает Scoped время существования HTTP-запросов, но не между SignalR сообщениями подключения и канала между компонентами, загруженными на клиент. Razor Pages или MVC-часть приложения взаимодействует со службами с заданной областью в обычном режиме и воссоздает службы для каждого HTTP-запроса при переходе между страницами или представлениями либо со страницы или из представления в компонент. Службы с заданной областью не воссоздаются при переходе между компонентами на клиенте, где обмен данными с сервером происходит через SignalR-подключение по каналу пользователя, а не через HTTP-запросы. В следующих сценариях использования компонентов на клиенте службы с заданной областью воссоздаются, так как для пользователя создается новый канал:

  • Пользователь закрывает окно браузера. Пользователь открывает новое окно и снова переходит к приложению.
  • Пользователь закрывает вкладку приложения в окне браузера. Пользователь открывает новую вкладку и снова переходит к приложению.
  • Пользователь нажимает кнопку перезагрузки или обновления в браузере.

Дополнительные сведения о сохранении состояния пользователя в серверных приложениях см. в разделе 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");
    }
}

Внедрение свойств

После добавления служб в коллекцию служб добавьте одну или несколько служб в компоненты с @injectRazor директивой, которая имеет два параметра:

  • Тип: тип службы для внедрения.
  • Свойство: имя свойства, получающего внедренную службу приложений. Свойство не требуется создавать вручную. Его создает компилятор.

См. дополнительные сведения о внедрении зависимостей в представления 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>();

Дополнительные ресурсы