Partilhar via


ASP.NET Core injeção de dependência Blazor

Observação

Esta não é a versão mais recente deste artigo. Para a versão atual, consulte a versão .NET 10 deste artigo.

Advertência

Esta versão do ASP.NET Core não é mais suportada. Para obter mais informações, consulte a Política de suporte do .NET e do .NET Core. Para a versão atual, consulte a versão .NET 9 deste artigo.

Por Rainer Stropek e Mike Rousos

Este artigo explica como Blazor os aplicativos podem injetar serviços em componentes.

A injeção de dependência (DI) é uma técnica para acessar serviços configurados em um local central:

  • Os serviços registrados na estrutura podem ser injetados diretamente nos Razor componentes.
  • Blazor os aplicativos definem e registram serviços personalizados e os disponibilizam em todo o aplicativo via DI.

Observação

Recomendamos ler o tópico Injeção de Dependência no ASP.NET Core antes de ler este.

Serviços padrão

Os serviços mostrados na tabela a seguir são comumente usados em Blazor aplicativos.

Serviço Tempo de vida Descrição
HttpClient Âmbito de aplicação

Fornece métodos para enviar solicitações HTTP e receber respostas HTTP de um recurso identificado por um URI.

Do lado do cliente, uma instância de HttpClient é registrada pelo aplicativo no Program arquivo e usa o navegador para lidar com o tráfego HTTP em segundo plano.

Do lado do servidor, um HttpClient não está configurado como um serviço por padrão. No código do lado do servidor, forneça um HttpClient.

Para mais informações, consulte Chamar uma API da Web a partir de uma aplicação ASP.NET Core Blazor.

Um HttpClient é registrado como um serviço com escopo, não singleton. Para obter mais informações, consulte a seção Tempo de vida do serviço .

IJSRuntime

Lado do cliente: Singleton

Lado do servidor: limitado

A Blazor framework regista IJSRuntime no contentor de serviços da aplicação.

Representa uma instância de um ambiente de execução JavaScript onde as chamadas JavaScript são processadas. Para obter mais informações, veja Executar funções JavaScript a partir de métodos .NET no ASP.NET Core Blazor.

Ao procurar injetar o serviço em um serviço singleton no servidor, adote uma das seguintes abordagens:

  • Altere o registo do serviço para definido, de modo a corresponder ao registo de IJSRuntime, o que é apropriado se o serviço tratar de um estado específico do utilizador.
  • Passe o IJSRuntime para a implementação do serviço singleton como um argumento das chamadas de método, em vez de o injetar no singleton.
NavigationManager

Lado do cliente: Singleton

Lado do servidor: limitado

A Blazor framework regista NavigationManager no contentor de serviços da aplicação.

Contém auxiliares para trabalhar com URIs e estado de navegação. Para obter mais informações, consulte URI e auxiliares de estado de navegação.

Serviços adicionais registados pelo Blazor framework são descritos na documentação onde são usados para descrever Blazor funcionalidades, como configuração e registo.

Um provedor de serviços personalizado não fornece automaticamente os serviços padrão listados na tabela. Se você usar um provedor de serviços personalizado e precisar de qualquer um dos serviços mostrados na tabela, adicione os serviços necessários ao novo provedor de serviços.

Adicionar serviços do lado do cliente

Configure os serviços na coleção de serviços do aplicativo no arquivo Program. No exemplo a seguir, a ExampleDependency implementação é registrada para IExampleDependency:

var builder = WebAssemblyHostBuilder.CreateDefault(args);
...
builder.Services.AddSingleton<IExampleDependency, ExampleDependency>();
...

await builder.Build().RunAsync();

Depois que o host é criado, os serviços ficam disponíveis a partir do escopo DI raiz antes que qualquer componente seja renderizado. Isso pode ser útil para executar a lógica de inicialização antes de renderizar conteúdo:

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();

O host fornece uma instância de configuração central para o aplicativo. Com base no exemplo anterior, o URL do serviço meteorológico é passado de uma fonte de configuração padrão (por exemplo, appsettings.json) para 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();

Adicionar serviços do lado do servidor

Depois de criar um novo aplicativo, examine parte do Program arquivo:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorComponents()
    .AddInteractiveServerComponents();
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor();
builder.Services.AddSingleton<WeatherForecastService>();

A builder variável representa uma WebApplicationBuilder com uma IServiceCollection, que é uma lista de objetos descritores de serviço . Os serviços são adicionados fornecendo descritores de serviço à coleção de serviços. O exemplo a seguir demonstra o conceito com a IDataAccess interface e sua implementação DataAccessconcreta:

builder.Services.AddSingleton<IDataAccess, DataAccess>();

Depois de criar um novo aplicativo, examine o Startup.ConfigureServices método em Startup.cs:

using Microsoft.Extensions.DependencyInjection;

...

public void ConfigureServices(IServiceCollection services)
{
    ...
}

É passado um ConfigureServices ao método IServiceCollection, que é uma lista de objetos descritores de serviço. Os serviços são adicionados ao ConfigureServices método fornecendo descritores de serviço à coleção de serviços. O exemplo a seguir demonstra o conceito com a IDataAccess interface e sua implementação DataAccessconcreta:

public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<IDataAccess, DataAccess>();
}

Registar serviços comuns

Se um ou mais serviços comuns forem necessários do lado do cliente e do servidor, você poderá colocar os registros de serviço comuns em um método do lado do cliente e chamar o método para registrar os serviços em ambos os projetos.

Em primeiro lugar, considere os registos de serviços comuns num método separado. Por exemplo, crie um ConfigureCommonServices método do lado do cliente:

public static void ConfigureCommonServices(IServiceCollection services)
{
    services.Add...;
}

Para o ficheiro do lado Program do cliente, chame ConfigureCommonServices para registar os serviços comuns.

var builder = WebAssemblyHostBuilder.CreateDefault(args);

...

ConfigureCommonServices(builder.Services);

No arquivo do lado Program do servidor, chame ConfigureCommonServices para registrar os serviços comuns:

var builder = WebApplication.CreateBuilder(args);

...

Client.Program.ConfigureCommonServices(builder.Services);

Para obter um exemplo dessa abordagem, consulte ASP.NET Principais Blazor WebAssembly cenários de segurança adicionais.

Serviços do lado do cliente que falham durante a pré-renderização

Esta seção só se aplica aos componentes WebAssembly em Blazor Web Apps.

Blazor Web Apps normalmente pré-renderizam componentes WebAssembly do lado do cliente. Se um aplicativo for executado com um serviço necessário registrado apenas no projeto, a execução do aplicativo resultará em um erro de tempo de execução semelhante ao seguinte quando um componente tentar usar o serviço necessário durante a .Client pré-renderização:

InvalidOperationException: Não é possível fornecer um valor para {PROPERTY} no tipo '{ASSEMBLY}}. Client.Pages. {NOME DO COMPONENTE}'. Não existe um serviço registado do tipo '{SERVICE}'.

Use uma das seguintes abordagens para resolver esse problema:

  • Registre o serviço no projeto principal para disponibilizá-lo durante a pré-renderização do componente.
  • Se a pré-renderização não for necessária para o componente, desative a pré-renderização seguindo as orientações em Pré-renderização ASP.NET componentes principaisRazor. Se você adotar essa abordagem, não precisará registrar o serviço no projeto principal.

Para obter mais informações, consulte a seção Falha na resolução dos serviços do lado do cliente durante a pré-renderização do artigo Pré-renderização, que aparece posteriormente na Blazor documentação.

Vida útil do serviço

Os serviços podem ser configurados com os tempos de vida mostrados na tabela a seguir.

Tempo de vida Descrição
Scoped

Atualmente, o cliente não tem um conceito de âmbitos de DI. Os serviços Scoped-registados comportam-se como os serviços Singleton.

O desenvolvimento do lado do servidor suporta o Scoped período de vida entre solicitações HTTP, mas não entre SignalR mensagens de ligação/circuito entre componentes que são carregados pelo cliente. A Razor secção de Páginas ou MVC da aplicação trata os serviços com escopo normalmente e recria os serviços a cada pedido HTTP ao navegar entre páginas ou vistas ou de uma página ou vista para um componente. Os serviços com escopo não são reconstruídos ao navegar entre componentes no cliente, onde a comunicação com o servidor ocorre pela SignalR conexão do circuito do usuário, não por meio de solicitações HTTP. Nos seguintes cenários de componentes no cliente, os serviços com escopo são reconstruídos porque um novo circuito é criado para o usuário:

  • O usuário fecha a janela do navegador. O usuário abre uma nova janela e navega de volta para o aplicativo.
  • O utilizador fecha um separador do aplicativo numa janela do navegador. O usuário abre uma nova guia e navega de volta para o aplicativo.
  • O usuário seleciona o botão de recarga/atualização do navegador.

Para obter mais informações sobre como preservar o estado do usuário em aplicativos do lado do servidor, consulte Visão geral do gerenciamento do estado principal Blazor do ASP.NET e ASP.NET Gerenciamento do estado do lado do servidor principalBlazor.

Singleton DI cria uma única instância do serviço. Todos os componentes que requerem um Singleton serviço recebem a mesma instância do serviço.
Transient Sempre que um componente obtém uma instância de um Transient serviço do contêiner de serviço, ele recebe uma nova instância do serviço.

O sistema DI é baseado no sistema DI em ASP.NET Core. Para mais informações, consulte Injeção de Dependências no ASP.NET Core.

Solicitar um serviço em um componente

Para injeção de serviços em componentes, Blazor suporta injeção de construtor e injeção de propriedade.

Injeção por construtor

Depois que os serviços forem adicionados à coleção de serviços, injete um ou mais serviços em componentes com injeção do construtor. O exemplo a seguir injeta o NavigationManager serviço.

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");
    }
}

Injeção de propriedade

Depois que os serviços forem adicionados à coleção de serviços, injete um ou mais serviços em componentes com a @injectRazor diretiva, que tem dois parâmetros:

  • Tipo: Tipo de serviço a introduzir.
  • Propriedade: o nome da propriedade que recebe o serviço de aplicativo injetado. A propriedade não requer criação manual. O compilador cria a propriedade.

Para obter mais informações, consulte Injeção de dependência em modos de exibição no ASP.NET Core.

Use várias @inject declarações para injetar serviços diferentes.

O exemplo a seguir demonstra como usar a @inject diretiva. A implementação do serviço Services.NavigationManager é injetada na propriedade Navigation do componente. Observe como o código está usando apenas a NavigationManager abstração.

PropertyInjection.razor:

@page "/property-injection"
@inject NavigationManager Navigation

<button @onclick="@(() => Navigation.NavigateTo("/counter"))">
    Take me to the Counter component
</button>

Internamente, a propriedade gerada (Navigation) usa o [Inject] atributo. Normalmente, esse atributo não é usado diretamente. Se uma classe base for necessária para componentes e as propriedades injetadas também forem necessárias para a classe base, adicione manualmente o [Inject] atributo:

using Microsoft.AspNetCore.Components;

public class ComponentBase : IComponent
{
    [Inject]
    protected NavigationManager Navigation { get; set; } = default!;

    ...
}

Observação

Como os serviços injetados devem estar disponíveis, o literal padrão com o operador de perdão nulo (default!) é atribuído no .NET 6 ou posterior. Para obter mais informações, consulte Nullable reference types (NRTs) e Análise estática de estado nulo do compilador .NET.

Em componentes derivados de uma classe base, a @inject diretiva não é necessária. O InjectAttribute da classe base é suficiente. O componente requer apenas a @inherits diretiva. No exemplo a seguir, todos os serviços injetados de CustomComponentBase estão disponíveis para o Demo componente:

@page "/demo"
@inherits CustomComponentBase

Injeção de serviço através de um arquivo de importação de nível superior (_Imports.razor)

Esta seção aplica-se apenas a Blazor Web Apps.

Um arquivo de importação de nível superior na pasta Components (Components/_Imports.razor) injeta suas referências em todos os componentes na hierarquia de pastas, que inclui o componente App (App.razor). O App componente é sempre renderizado estaticamente, mesmo se a pré-renderização de um componente de página estiver desabilitada. Portanto, injetar serviços por meio do arquivo de importações de nível superior resulta na resolução de duas instâncias do serviço em componentes de página.

Para resolver esse cenário, injete o serviço em um novo arquivo de importações colocado na pasta Pages (Components/Pages/_Imports.razor). A partir dessa localização, o serviço só é processado uma vez nos componentes da página.

Usar DI em serviços

Serviços complexos podem exigir serviços adicionais. No exemplo a seguir, DataAccess requer o HttpClient serviço padrão. @inject (ou o [Inject] atributo) não está disponível para uso em serviços. Em vez disso, deve ser utilizada a injeção no construtor. Os serviços necessários são adicionados adicionando parâmetros ao construtor do serviço. Quando a DI cria o serviço, ela reconhece os serviços necessários no construtor e os fornece de acordo. No exemplo a seguir, o construtor recebe um HttpClient via DI. HttpClient é um serviço padrão.

using System.Net.Http;

public class DataAccess : IDataAccess
{
    public DataAccess(HttpClient http)
    {
        ...
    }

    ...
}

A injeção do construtor é suportada com construtores primários em C# 12 (.NET 8) ou posterior:

using System.Net.Http;

public class DataAccess(HttpClient http) : IDataAccess
{
    ...
}

Prerequisitos para injeção de construtor:

  • Deve existir um construtor cujos argumentos possam ser todos cumpridos pela DI. Parâmetros adicionais não cobertos pelo DI são permitidos se especificarem valores padrão.
  • O construtor aplicável deve ser public.
  • Deve existir um construtor aplicável. Em caso de ambiguidade, a DI lança uma exceção.

Injetar serviços chaveados em componentes

Blazor Suporta a injeção de serviços com chave usando o [Inject] atributo. As chaves permitem definir o âmbito para o registo e o consumo de serviços ao usar a injeção de dependência. Utilize a propriedade InjectAttribute.Key para especificar a chave para o serviço que deve ser injetado.

[Inject(Key = "my-service")]
public IMyService MyService { get; set; }

Classes de componentes básicos do utilitário para gerenciar um escopo DI

Em aplicativos que não são ASP.NET Core, os serviços com escopo e transitórios estão normalmente associados à solicitação atual. Após a conclusão da solicitação, os serviços de escopo limitado e transitórios são descartados pelo sistema DI.

Nas aplicações interativas do lado Blazor do servidor, o âmbito DI prolonga-se pela duração do circuito (a conexão entre o cliente e o servidor), o SignalR que pode resultar em serviços transitórios com escopo e descartáveis a viverem muito além da vida útil de um único componente. Portanto, não injete diretamente um serviço com escopo em um componente se você pretende que o tempo de vida do serviço corresponda ao tempo de vida do componente. Serviços transitórios injetados em um componente que não implementam IDisposable são lixo coletado quando o componente é descartado. No entanto, os serviços transitórios injetados que implementam IDisposable são mantidos pelo contêiner DI durante a vida útil do circuito, o que impede a coleta de lixo de serviço quando o componente é descartado e resulta em um vazamento de memória. Uma abordagem alternativa para serviços com escopo baseada no tipo OwningComponentBase é descrita mais adiante nesta seção, e não se devem usar serviços transitórios descartáveis. Para obter mais informações, consulte Design para resolver descartáveis transitórios em Blazor Server (dotnet/aspnetcore #26676).

Mesmo em aplicativos do lado Blazor do cliente que não operam em um circuito, os serviços registrados com um tempo de vida definido são tratados como singletons, portanto, vivem mais do que os serviços com escopo em aplicativos ASP.NET Core típicos. Os serviços transitórios descartáveis do lado do cliente também vivem mais do que os componentes onde são injetados porque o contêiner DI, que contém referências a serviços descartáveis, persiste durante a vida útil do aplicativo, impedindo a coleta de lixo nos serviços. Embora os serviços transitórios descartáveis de longa duração sejam de maior preocupação no servidor, eles também devem ser evitados como registros de serviço ao cliente. O uso do tipo OwningComponentBase também é recomendado para controlar o tempo de vida de serviços com escopo do cliente, e serviços transitórios descartáveis não devem ser usados.

Uma abordagem que limita a vida útil de um serviço é o uso do tipo OwningComponentBase. OwningComponentBase é um tipo abstrato derivado de ComponentBase que cria um escopo DI correspondente ao tempo de vida do componente. Usando esse escopo, um componente pode injetar serviços com um tempo de vida definido e mantê-los ativos enquanto o componente. Quando um componente é destruído, os serviços do fornecedor de serviços com escopo do componente também são descartados. Isso pode ser útil para serviços reutilizados dentro de um componente, mas não compartilhados entre componentes.

Duas versões do OwningComponentBase tipo estão disponíveis e descritas nas próximas duas seções:

OwningComponentBase

OwningComponentBase é uma entidade abstrata e descartável do tipo ComponentBase, com uma propriedade protegida ScopedServices do tipo IServiceProvider. O provedor pode ser usado para resolver serviços que têm escopo para o ciclo de vida do componente.

Os serviços DI injetados no componente usando @inject ou o [Inject] atributo não são criados no escopo do componente. Para usar o escopo do componente, os serviços devem ser resolvidos usando ScopedServices com GetRequiredService ou GetService. Todos os serviços resolvidos usando o ScopedServices provedor têm suas dependências fornecidas no escopo do componente.

O exemplo a seguir demonstra a diferença entre injetar um serviço com escopo diretamente e resolver um serviço usando ScopedServices no servidor. A interface e a implementação seguintes para uma classe de viagem no tempo incluem uma DT propriedade para armazenar um DateTime valor. A implementação chama DateTime.Now para definir DT quando a TimeTravel classe é instanciada.

ITimeTravel.cs:

public interface ITimeTravel
{
    public DateTime DT { get; set; }
}

TimeTravel.cs:

public class TimeTravel : ITimeTravel
{
    public DateTime DT { get; set; } = DateTime.Now;
}

O serviço está registado como de âmbito específico no ficheiro do lado Program do servidor. Os serviços com escopo do lado do servidor têm uma vida útil igual à duração do circuito.

No ficheiro Program:

builder.Services.AddScoped<ITimeTravel, TimeTravel>();

No componente seguinte TimeTravel:

  • O serviço de viagem no tempo é injetado diretamente com @inject como TimeTravel1.
  • O serviço é igualmente resolvido de forma autónoma com ScopedServices e GetRequiredService tal como 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>();
    }
}

Ao navegar inicialmente para o componente TimeTravel, o serviço de viagem no tempo é instanciado duas vezes quando o componente é carregado, e TimeTravel1 e TimeTravel2 têm o mesmo valor inicial:

TimeTravel1.DT: 8/31/2022 2:54:45 PM
TimeTravel2.DT: 8/31/2022 2:54:45 PM

Ao navegar para fora do componente TimeTravel para outro componente e voltar ao componente TimeTravel:

  • TimeTravel1 é fornecida a mesma instância de serviço que foi criada quando o componente foi carregado pela primeira vez, portanto, o valor de DT permanece o mesmo.
  • TimeTravel2 obtém uma nova instância de serviço ITimeTravel em TimeTravel2, com um novo valor DT.

TimeTravel1.DT: 8/31/2022 2:54:45 PM
TimeTravel2.DT: 8/31/2022 2:54:48 PM

TimeTravel1 está ligado ao circuito do utilizador, que permanece intacto e não é eliminado até que o circuito subjacente seja desconstruído. Por exemplo, o serviço é eliminado se o circuito estiver desligado durante o período de retenção do circuito desligado.

Apesar do registo de serviço delimitado no arquivo Program e da longevidade do circuito do utilizador, TimeTravel2 recebe uma nova instância de serviço ITimeTravel sempre que o componente é inicializado.

OwningComponentBase<TService>

OwningComponentBase<TService> deriva de OwningComponentBase e adiciona uma propriedade Service que retorna uma instância de T do provedor de DI com escopo. Esse tipo é uma maneira conveniente de acessar serviços com escopo sem usar uma instância de IServiceProvider quando há um serviço primário que o aplicativo requer do contêiner DI usando o escopo do componente. A propriedade ScopedServices está disponível, permitindo que o aplicativo obtenha serviços de outros tipos, se necessário.

@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>

Detetar descartáveis temporários no lado do cliente

O código personalizado pode ser adicionado a um aplicativo do lado do cliente Blazor para identificar serviços transitórios descartáveis em aplicações que devem usar OwningComponentBase. Essa abordagem é útil se você estiver preocupado que o código adicionado ao aplicativo no futuro consuma um ou mais serviços descartáveis transitórios, incluindo serviços adicionados por bibliotecas. O código de demonstração está disponível no Blazor repositório GitHub de exemplos (como fazer o download).

Inspecione o seguinte na amostra BlazorSample_WebAssembly no .NET 6 ou versões posteriores:

  • DetectIncorrectUsagesOfTransientDisposables.cs
  • Services/TransientDisposableService.cs
  • Em Program.cs:
    • O namespace do Services aplicativo é fornecido na parte superior do arquivo (using BlazorSample.Services;).
    • DetectIncorrectUsageOfTransients é chamado imediatamente após builder ser atribuído a partir de WebAssemblyHostBuilder.CreateDefault.
    • O TransientDisposableService está registado (builder.Services.AddTransient<TransientDisposableService>();).
    • EnableTransientDisposableDetection é chamado no host configurado no pipeline de processamento da aplicação (host.EnableTransientDisposableDetection();).
  • A aplicação regista o serviço TransientDisposableService sem lançar uma exceção. No entanto, ao tentar resolver o serviço em TransientService.razor, é lançado um InvalidOperationException quando o framework tenta construir uma instância de TransientDisposableService.

Detetar descartáveis transitórios do lado do servidor

O código personalizado pode ser adicionado a uma aplicação do lado Blazor do servidor para detetar serviços transitórios descartáveis na aplicação que deveria usar OwningComponentBase. Essa abordagem é útil se você estiver preocupado que o código adicionado ao aplicativo no futuro consuma um ou mais serviços descartáveis transitórios, incluindo serviços adicionados por bibliotecas. O código de demonstração está disponível no Blazor repositório GitHub de exemplos (como fazer o download).

Inspecione o seguinte exemplo no .NET 8 ou em versões posteriores do BlazorSample_BlazorWebApp:

Inspecione o seguinte do exemplo BlazorSample_Server nas versões .NET 6 ou .NET 7:

  • DetectIncorrectUsagesOfTransientDisposables.cs
  • Services/TransitiveTransientDisposableDependency.cs:
  • Em Program.cs:
    • O namespace do Services aplicativo é fornecido na parte superior do arquivo (using BlazorSample.Services;).
    • DetectIncorrectUsageOfTransients é chamado no host builder (builder.DetectIncorrectUsageOfTransients();).
    • O TransientDependency serviço está registado (builder.Services.AddTransient<TransientDependency>();).
    • O TransitiveTransientDisposableDependency está registado para ITransitiveTransientDisposableDependency (builder.Services.AddTransient<ITransitiveTransientDisposableDependency, TransitiveTransientDisposableDependency>();).
  • A aplicação regista o serviço TransientDependency sem lançar uma exceção. No entanto, ao tentar resolver o serviço em TransientService.razor, é lançado um InvalidOperationException quando o framework tenta construir uma instância de TransientDependency.

Registros de serviço transitórios para IHttpClientFactory/HttpClient manipuladores

Registros de serviço transitórios para IHttpClientFactory/HttpClient manipuladores são recomendados. Se a aplicação contiver IHttpClientFactory/HttpClient manipuladores e usar o IRemoteAuthenticationBuilder<TRemoteAuthenticationState,TAccount> para adicionar suporte para autenticação, os seguintes objetos transitórios descartáveis para autenticação do lado do cliente também serão descobertos, o que é esperado e não causa preocupação e pode ser ignorado:

Outros casos de IHttpClientFactory/HttpClient também são descobertos. Essas instâncias também podem ser ignoradas.

Os Blazor aplicativos de exemplo no Blazor repositório GitHub de exemplos (como fazer download) demonstram o código para detetar descartáveis transitórios. No entanto, o código é desativado porque os aplicativos de exemplo incluem IHttpClientFactory/HttpClient manipuladores.

Para ativar o código de demonstração e testemunhar o seu funcionamento:

  • Descomente as linhas descartáveis transitórias em Program.cs.

  • Remova a verificação condicional em NavMenu.razor que impede o componente TransientService de ser exibido na barra lateral de navegação do aplicativo.

    - && (c.Name != "TransientService")
    
  • Execute a aplicação de exemplo e navegue até ao componente TransientService no /transient-service.

Os Blazor aplicativos de exemplo no Blazor repositório GitHub de exemplos (como fazer download) demonstram o código para detetar descartáveis transitórios. Execute a aplicação de exemplo e navegue até ao componente TransientService no /transient-service.

Uso de um Entity Framework Core (EF Core) DbContext da DI

Para obter mais informações, consulte ASP.NET Core Blazor com o Entity Framework Core (EF Core).

Acesse serviços do lado Blazor do servidor a partir de um escopo de DI diferente

Os manipuladores de atividade de circuito fornecem uma abordagem para acessar serviços com escopo definido por Blazor de outros escopos de injeção que não são de dependência Blazor(DI), como escopos criados usando IHttpClientFactory.

Antes do lançamento do ASP.NET Core no .NET 8, o acesso a serviços com escopo de circuito a partir de outros escopos de injeção de dependência exigia a utilização de um tipo de componente base personalizado. Com manipuladores de atividade de circuito, um tipo de componente base personalizado não é necessário, como demonstra o exemplo a seguir:

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;
    }
}

Chame AddCircuitServicesAccessor no arquivo Program do aplicativo.

builder.Services.AddCircuitServicesAccessor();

Aceda aos serviços no âmbito do circuito injetando CircuitServicesAccessor onde for necessário.

Para obter um exemplo que mostra como aceder ao AuthenticationStateProvider a partir de uma configuração DelegatingHandler usando IHttpClientFactory, consulte Blazor Web App.

Pode haver momentos em que um Razor componente invoca métodos assíncronos que executam código em um escopo DI diferente. Sem a abordagem correta, estes escopos DI não têm acesso aos serviços de Blazor, como IJSRuntime e Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage.

Por exemplo, HttpClient as instâncias criadas usando IHttpClientFactory têm o seu próprio escopo de serviço de DI. Como resultado, as instâncias configuradas em HttpMessageHandler não são capazes de injetar serviços HttpClient diretamente.

Crie uma classe BlazorServiceAccessor que defina um AsyncLocal, que armazena o BlazorIServiceProvider para o contexto assíncrono atual. Uma instância de BlazorServiceAccessor pode ser adquirida de dentro de um escopo diferente de serviço de DI para acessar serviços de 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; }
    }
}

Para definir automaticamente o valor de BlazorServiceAccessor.Services quando um método do componente async é invocado, crie um componente base personalizado que re-implementa os principais pontos de entrada assíncronos no código do componente Razor.

A classe a seguir demonstra a implementação para o componente base.

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;
        }
    }
}

Todos os componentes que extendem CustomComponentBase automaticamente têm BlazorServiceAccessor.Services configurado no IServiceProvider no escopo DI atual Blazor.

Finalmente, no Program arquivo, adicione o BlazorServiceAccessor como um serviço com escopo:

builder.Services.AddScoped<BlazorServiceAccessor>();

Finalmente, em Startup.ConfigureServices, Startup.cs, adicione o BlazorServiceAccessor como um serviço delimitado.

services.AddScoped<BlazorServiceAccessor>();

Recursos adicionais