Blazor ASP.NET core dependency injection

Nota

Questa non è la versione più recente di questo articolo. Per la versione corrente, vedere la versione .NET 8 di questo articolo.

Importante

Queste informazioni si riferiscono a un prodotto non definitive che può essere modificato in modo sostanziale prima che venga rilasciato commercialmente. Microsoft non riconosce alcuna garanzia, espressa o implicita, in merito alle informazioni qui fornite.

Per la versione corrente, vedere la versione .NET 8 di questo articolo.

Di Rainer Stropek e Mike Rousos

Questo articolo illustra come Blazor le app possono inserire servizi nei componenti.

L'inserimento delle dipendenze è una tecnica per accedere ai servizi configurati in una posizione centrale:

  • I servizi registrati dal framework possono essere inseriti direttamente nei Razor componenti.
  • Blazor le app definiscono e registrano servizi personalizzati e li rendono disponibili in tutta l'app tramite inserimento delle dipendenze.

Nota

Prima di leggere questo argomento, è consigliabile leggere Inserimento delle dipendenze in ASP.NET Core .

Servizi predefiniti

I servizi illustrati nella tabella seguente vengono comunemente usati nelle Blazor app.

Service Durata Descrizione
HttpClient Con ambito

Fornisce metodi per l'invio di richieste HTTP e la ricezione di risposte HTTP da una risorsa identificata da un URI.

Sul lato client, un'istanza di HttpClient viene registrata dall'app nel Program file e usa il browser per gestire il traffico HTTP in background.

Lato server, un HttpClient non è configurato come servizio per impostazione predefinita. Nel codice lato server specificare un oggetto HttpClient.

Per altre informazioni, vedere Chiamare un'API Web da un'app ASP.NET CoreBlazor.

Un HttpClient oggetto viene registrato come servizio con ambito, non come singleton. Per altre informazioni, vedere la sezione Durata del servizio.

IJSRuntime

Lato client: Singleton

Lato server: ambito

Il Blazor framework viene IJSRuntime registrato nel contenitore del servizio dell'app.

Rappresenta un'istanza di un runtime JavaScript in cui vengono inviate le chiamate JavaScript. Per altre informazioni, vedere Chiamare funzioni JavaScript da metodi .NET in ASP.NET Core Blazor.

Quando si cerca di inserire il servizio in un servizio singleton nel server, adottare uno degli approcci seguenti:

  • Modificare la registrazione del servizio impostando come ambito la corrispondenza IJSRuntimecon la registrazione, appropriata se il servizio gestisce lo stato specifico dell'utente.
  • Passare l'oggetto IJSRuntime nell'implementazione del servizio singleton come argomento delle chiamate al metodo anziché inserirlo nel singleton.
NavigationManager

Lato client: Singleton

Lato server: ambito

Il Blazor framework viene NavigationManager registrato nel contenitore del servizio dell'app.

Contiene helper per l'uso degli URI e dello stato di spostamento. Per altre informazioni, vedere URI e helper dello stato di spostamento.

I servizi aggiuntivi registrati dal Blazor framework sono descritti nella documentazione in cui vengono usati per descrivere Blazor le funzionalità, ad esempio la configurazione e la registrazione.

Un provider di servizi personalizzato non fornisce automaticamente i servizi predefiniti elencati nella tabella. Se si usa un provider di servizi personalizzato e si richiede uno dei servizi visualizzati nella tabella, aggiungere i servizi necessari al nuovo provider di servizi.

Aggiungere servizi lato client

Configurare i servizi per la raccolta di servizi dell'app nel Program file. Nell'esempio seguente l'implementazione ExampleDependency viene registrata per IExampleDependency:

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

await builder.Build().RunAsync();

Dopo la compilazione dell'host, i servizi sono disponibili nell'ambito di inserimento delle dipendenze radice prima del rendering dei componenti. Ciò può essere utile per l'esecuzione della logica di inizializzazione prima del rendering del contenuto:

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

L'host fornisce un'istanza di configurazione centrale per l'app. Basandosi sull'esempio precedente, l'URL del servizio meteo viene passato da un'origine di configurazione predefinita (ad esempio, appsettings.json) a 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();

Aggiungere servizi lato server

Dopo aver creato una nuova app, esaminare parte del Program file:

var builder = WebApplication.CreateBuilder(args);

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

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

La builder variabile rappresenta un oggetto con un WebApplicationBuilderIServiceCollectionoggetto , che è un elenco di oggetti descrittore del servizio. I servizi vengono aggiunti fornendo i descrittori del servizio alla raccolta di servizi. L'esempio seguente illustra il concetto con l'interfaccia e la IDataAccess relativa implementazione DataAccessconcreta:

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

Dopo aver creato una nuova app, esaminare il Startup.ConfigureServices metodo in Startup.cs:

using Microsoft.Extensions.DependencyInjection;

...

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

Il ConfigureServices metodo viene passato a , IServiceCollectionche è un elenco di oggetti descrittore del servizio. I servizi vengono aggiunti nel ConfigureServices metodo fornendo i descrittori del servizio alla raccolta di servizi. L'esempio seguente illustra il concetto con l'interfaccia e la IDataAccess relativa implementazione DataAccessconcreta:

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

Registrare i servizi comuni

Se uno o più servizi comuni sono necessari sul lato client e sul lato server, è possibile inserire le registrazioni del servizio comune in un lato client del metodo e chiamare il metodo per registrare i servizi in entrambi i progetti.

Prima di tutto, considerare le registrazioni comuni del servizio in un metodo separato. Ad esempio, creare un ConfigureCommonServices metodo sul lato client:

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

Per il file lato Program client, chiamare ConfigureCommonServices per registrare i servizi comuni:

var builder = WebAssemblyHostBuilder.CreateDefault(args);

...

ConfigureCommonServices(builder.Services);

Nel file lato Program server chiamare ConfigureCommonServices per registrare i servizi comuni:

var builder = WebApplication.CreateBuilder(args);

...

Client.Program.ConfigureCommonServices(builder.Services);

Per un esempio di questo approccio, vedere ASP.NET Scenari di sicurezza aggiuntivi di baseBlazor WebAssembly.

Servizi lato client che hanno esito negativo durante la pre-esecuzione del pre-ordinamento

Questa sezione si applica solo ai componenti WebAssembly in Blazor App Web.

BlazorApp Web normalmente prerendere componenti WebAssembly sul lato client. Se un'app viene eseguita con un servizio obbligatorio registrato solo nel .Client progetto, l'esecuzione dell'app genera un errore di runtime simile al seguente quando un componente tenta di usare il servizio richiesto durante la pre-esecuzione:

InvalidOperationException: impossibile specificare un valore per {PROPERTY} nel tipo '{AS edizione Standard MBLY}}. Client.Pages. {NOME COMPONENTE}'. Nessun servizio registrato di tipo '{edizione Standard RVICE}'.

Usare uno degli approcci seguenti per risolvere il problema:

  • Registrare il servizio nel progetto principale per renderlo disponibile durante il prerendering dei componenti.
  • Se il prerendering non è necessario per il componente, disabilitare il prerendering seguendo le indicazioni riportate in ASP.NET modalità di rendering coreBlazor. Se si adotta questo approccio, non è necessario registrare il servizio nel progetto principale.

Per altre informazioni, vedere I servizi sul lato client non riescono a risolvere durante la pre-esecuzione del servizio.

Durata del servizio

I servizi possono essere configurati con le durate illustrate nella tabella seguente.

Durata Descrizione
Scoped

Il lato client non ha attualmente un concetto di ambiti di inserimento delle dipendenze. ScopedI servizi registrati si comportano come Singleton i servizi.

Lo sviluppo lato server supporta la Scoped durata delle richieste HTTP, ma non tra SignalR i messaggi di connessione/circuito tra i componenti caricati nel client. La Razor parte Pages o MVC dell'app tratta normalmente i servizi con ambito e ricrea i servizi in ogni richiesta HTTP durante lo spostamento tra pagine o visualizzazioni o da una pagina o una vista a un componente. I servizi con ambito non vengono ricostruiti durante lo spostamento tra i componenti nel client, in cui la comunicazione con il server avviene sulla SignalR connessione del circuito dell'utente, non tramite richieste HTTP. Negli scenari di componenti seguenti nel client i servizi con ambito vengono ricostruiti perché viene creato un nuovo circuito per l'utente:

  • L'utente chiude la finestra del browser. L'utente apre una nuova finestra e torna all'app.
  • L'utente chiude una scheda dell'app in una finestra del browser. L'utente apre una nuova scheda e torna all'app.
  • L'utente seleziona il pulsante ricarica/aggiornamento del browser.

Per altre informazioni sul mantenimento dello stato utente nelle app lato server, vedere ASP.NET Gestione dello stato coreBlazor.

Singleton L'inserimento delle dipendenze crea una singola istanza del servizio. Tutti i componenti che richiedono un Singleton servizio ricevono la stessa istanza del servizio.
Transient Ogni volta che un componente ottiene un'istanza di un Transient servizio dal contenitore del servizio, riceve una nuova istanza del servizio.

Il sistema di inserimento delle dipendenze si basa sul sistema di inserimento delle dipendenze in ASP.NET Core. Per altre informazioni, vedere Inserimento di dipendenze in ASP.NET Core.

Richiedere un servizio in un componente

Per l'inserimento di servizi nei componenti, Blazor supporta l'inserimento di costruttori e l'inserimento di proprietà.

Inserimento del costruttore

Dopo l'aggiunta dei servizi alla raccolta di servizi, inserire uno o più servizi nei componenti con inserimento del costruttore. L'esempio seguente inserisce il NavigationManager servizio.

ConstructorInjection.razor:

@page "/constructor-injection"

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

ConstructorInjection.razor.cs:

using Microsoft.AspNetCore.Components;

public partial class ConstructorInjection(NavigationManager navigation)
{
    protected NavigationManager Navigation { get; } = navigation;
}

Inserimento di proprietà

Dopo l'aggiunta dei servizi alla raccolta di servizi, inserire uno o più servizi nei componenti con la @injectRazor direttiva , che ha due parametri:

  • Tipo: tipo del servizio da inserire.
  • Proprietà: nome della proprietà che riceve il servizio app inserito. La proprietà non richiede la creazione manuale. Il compilatore crea la proprietà .

Per altre informazioni, vedere Inserimento delle dipendenze nelle visualizzazioni in ASP.NET Core.

Usare più @inject istruzioni per inserire servizi diversi.

Nell'esempio seguente viene illustrato come usare la @inject direttiva . Il servizio che implementa Services.NavigationManager viene inserito nella proprietà Navigationdel componente . Si noti che il codice usa solo l'astrazione NavigationManager .

PropertyInjection.razor:

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

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

Internamente, la proprietà generata (Navigation) usa l'attributo[Inject]. In genere, questo attributo non viene usato direttamente. Se è necessaria una classe base per i componenti e le proprietà inserite sono necessarie anche per la classe base, aggiungere manualmente l'attributo [Inject]:

using Microsoft.AspNetCore.Components;

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

    ...
}

Nota

Poiché si prevede che i servizi inseriti siano disponibili, il valore letterale predefinito con l'operatore null-forgiving (default!) viene assegnato in .NET 6 o versione successiva. Per altre informazioni, vedere Tipi di riferimento Nullable (NRT) e analisi statica dello stato null del compilatore .NET.

Nei componenti derivati da una classe base, la @inject direttiva non è obbligatoria. L'oggetto InjectAttribute della classe di base è sufficiente. Il componente richiede solo la @inherits direttiva . Nell'esempio seguente tutti i servizi inseriti di CustomComponentBase sono disponibili per il Demo componente:

@page "/demo"
@inherits CustomComponentBase

Usare l'inserimento delle dipendenze nei servizi

I servizi complessi potrebbero richiedere servizi aggiuntivi. Nell'esempio seguente è DataAccess necessario il HttpClient servizio predefinito. @inject (o il [Inject] attribute) non è disponibile per l'uso nei servizi. L'inserimento del costruttore deve essere invece usato. I servizi necessari vengono aggiunti aggiungendo parametri al costruttore del servizio. Quando l'inserimento delle dipendenze crea il servizio, riconosce i servizi necessari nel costruttore e li fornisce di conseguenza. Nell'esempio seguente il costruttore riceve un'eccezione HttpClient tramite di inserimento delle dipendenze. HttpClient è un servizio predefinito.

using System.Net.Http;

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

Prerequisiti per l'inserimento del costruttore:

  • Un costruttore deve esistere i cui argomenti possono essere soddisfatti dall'inserimento delle dipendenze. Se specificano valori predefiniti, sono consentiti parametri aggiuntivi non coperti dall'inserimento delle dipendenze.
  • Il costruttore applicabile deve essere public.
  • Deve esistere un costruttore applicabile. In caso di ambiguità, l'inserimento delle dipendenze genera un'eccezione.

Inserire i servizi con chiave nei componenti

Blazor supporta l'inserimento di servizi con chiave usando l'attributo [Inject] . Le chiavi consentono di definire l'ambito della registrazione e dell'utilizzo dei servizi quando si usa l'inserimento delle dipendenze. Usare la InjectAttribute.Key proprietà per specificare la chiave per il servizio da inserire:

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

Classi di componenti di base dell'utilità per gestire un ambito di inserimento delle dipendenze

Blazor Nelle app non ASP.NET Core, l'ambito dei servizi temporanei e con ambito è in genere limitato alla richiesta corrente. Al termine della richiesta, i servizi temporanei e con ambito vengono eliminati dal sistema di inserimento delle dipendenze.

Nelle app sul lato Blazor server interattive, l'ambito di inserimento delle dipendenze dura per la durata del circuito (la SignalR connessione tra il client e il server), che può comportare servizi temporanei con ambito e eliminabili che vivono molto più a lungo della durata di un singolo componente. Pertanto, non inserire direttamente un servizio con ambito in un componente se si intende che la durata del servizio corrisponda alla durata del componente. I servizi temporanei inseriti in un componente che non implementano IDisposable vengono sottoposto a Garbage Collection quando il componente viene eliminato. Tuttavia, i servizi temporanei inseriti che implementano IDisposable vengono gestiti dal contenitore di inserimento delle dipendenze per la durata del circuito, che impedisce la Garbage Collection del servizio quando il componente viene eliminato e comporta una perdita di memoria. Un approccio alternativo per i servizi con ambito basato sul OwningComponentBase tipo è descritto più avanti in questa sezione e i servizi temporanei eliminabili non devono essere usati affatto. Per altre informazioni, vedere Progettare per risolvere gli eliminabili temporanei in Blazor Server (dotnet/aspnetcore #26676)..

Anche nelle app sul lato Blazor client che non operano su un circuito, i servizi registrati con una durata con ambito vengono considerati come singleton, quindi vivono più a lungo dei servizi con ambito nelle tipiche app di ASP.NET Core. I servizi temporanei eliminabili lato client vivono anche più a lungo dei componenti in cui vengono inseriti perché il contenitore di inserimento delle dipendenze, che contiene riferimenti a servizi eliminabili, mantiene per tutta la durata dell'app, impedendo l'operazione di Garbage Collection nei servizi. Anche se i servizi temporanei eliminabili di lunga durata sono di maggiore preoccupazione nel server, devono essere evitati anche come registrazioni del servizio client. L'uso del OwningComponentBase tipo è consigliato anche per i servizi con ambito client per controllare la durata del servizio e i servizi temporanei eliminabili non devono essere usati affatto.

Un approccio che limita la durata di un servizio è l'uso del OwningComponentBase tipo . OwningComponentBase è un tipo astratto derivato da ComponentBase che crea un ambito di inserimento delle dipendenze corrispondente alla durata del componente. Usando questo ambito, un componente può inserire servizi con una durata con ambito e attivarli fino a quando il componente. Quando il componente viene eliminato definitivamente, vengono eliminati anche i servizi del provider di servizi con ambito del componente. Ciò può essere utile per i servizi riutilizzati all'interno di un componente, ma non condivisi tra i componenti.

Sono disponibili due versioni di OwningComponentBase tipo e descritte nelle due sezioni seguenti:

OwningComponentBase

OwningComponentBase è un elemento figlio astratto e eliminabile del ComponentBase tipo con una proprietà protetta ScopedServices di tipo IServiceProvider. Il provider può essere usato per risolvere i servizi che hanno come ambito la durata del componente.

I servizi DI inseriti nel componente usando @inject o l'attributo [Inject] non vengono creati nell'ambito del componente. Per usare l'ambito del componente, i servizi devono essere risolti usando ScopedServices o GetRequiredServiceGetService. Tutti i servizi risolti usando il ScopedServices provider hanno le relative dipendenze fornite nell'ambito del componente.

Nell'esempio seguente viene illustrata la differenza tra l'inserimento diretto di un servizio con ambito e la risoluzione di un servizio usando ScopedServices nel server. L'interfaccia e l'implementazione seguenti per una classe di spostamento temporale includono una DT proprietà per contenere un DateTime valore. L'implementazione chiama DateTime.Now da impostare DT quando viene creata un'istanza della TimeTravel classe .

ITimeTravel.cs:

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

TimeTravel.cs:

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

Il servizio viene registrato come ambito nel file lato Program server. I servizi con ambito server hanno una durata uguale alla durata del circuito.

Nel file Program:

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

Nel componente TimeTravel seguente:

  • Il servizio di spostamento temporale viene inserito direttamente con @inject come TimeTravel1.
  • Il servizio viene risolto anche separatamente con ScopedServices e GetRequiredService come 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>();
    }
}

Inizialmente passando al TimeTravel componente, il servizio di spostamento del tempo viene creato due volte quando il componente viene caricato e TimeTravel1 ha TimeTravel2 lo stesso valore iniziale:

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

Quando si passa dal TimeTravel componente a un altro componente e torna al TimeTravel componente:

  • TimeTravel1 viene fornita la stessa istanza del servizio creata al primo caricamento del componente, quindi il valore di DT rimane invariato.
  • TimeTravel2 ottiene una nuova ITimeTravel istanza del servizio in TimeTravel2 con un nuovo valore DT.

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

TimeTravel1 è associato al circuito dell'utente, che rimane intatto e non viene eliminato fino a quando il circuito sottostante non viene distrutto. Ad esempio, il servizio viene eliminato se il circuito viene disconnesso per il periodo di conservazione del circuito disconnesso.

Nonostante la registrazione del servizio con ambito nel Program file e la durata del circuito dell'utente, TimeTravel2 riceve una nuova ITimeTravel istanza del servizio ogni volta che il componente viene inizializzato.

OwningComponentBase<TService>

OwningComponentBase<TService> deriva da OwningComponentBase e aggiunge una proprietà che restituisce un'istanza Service di dal provider di inserimento T delle dipendenze con ambito. Questo tipo è un modo pratico per accedere ai servizi con ambito senza usare un'istanza di IServiceProvider quando è presente un servizio primario richiesto dall'app dal contenitore di inserimento delle dipendenze usando l'ambito del componente. La ScopedServices proprietà è disponibile, quindi l'app può ottenere servizi di altri tipi, se necessario.

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

Rilevare gli eliminabili temporanei sul lato client

È possibile aggiungere codice personalizzato a un'app lato Blazor client per rilevare servizi temporanei eliminabili in un'app che deve usare OwningComponentBase. Questo approccio è utile se si è preoccupati che il codice aggiunto all'app in futuro utilizza uno o più servizi eliminabili temporanei, inclusi i servizi aggiunti dalle librerie. Il codice dimostrativo è disponibile nel Blazor repository GitHub degli esempi (come scaricare).

Esaminare quanto segue in .NET 6 o versioni successive dell'esempio BlazorSample_WebAssembly :

  • DetectIncorrectUsagesOfTransientDisposables.cs
  • Services/TransientDisposableService.cs
  • In Program.cs:
    • Lo spazio dei nomi dell'app Services viene fornito all'inizio del file (using BlazorSample.Services;).
    • DetectIncorrectUsageOfTransients viene chiamato immediatamente dopo l'assegnazione di builder da WebAssemblyHostBuilder.CreateDefault.
    • l'oggetto TransientDisposableService è registrato (builder.Services.AddTransient<TransientDisposableService>();).
    • EnableTransientDisposableDetection viene chiamato nell'host predefinito nella pipeline di elaborazione dell'app (host.EnableTransientDisposableDetection();).
  • L'app registra il TransientDisposableService servizio senza generare un'eccezione. Tuttavia, il tentativo di risolvere il servizio in TransientService.razor genera un'eccezione InvalidOperationException quando il framework tenta di costruire un'istanza di TransientDisposableService.

Rilevare gli eliminabili temporanei sul lato server

È possibile aggiungere codice personalizzato a un'app lato server per rilevare i servizi temporanei eliminabili sul Blazor lato server in un'app che deve usare OwningComponentBase. Questo approccio è utile se si è preoccupati che il codice aggiunto all'app in futuro utilizza uno o più servizi eliminabili temporanei, inclusi i servizi aggiunti dalle librerie. Il codice dimostrativo è disponibile nel Blazor repository GitHub degli esempi (come scaricare).

Esaminare quanto segue in .NET 8 o versioni successive dell'esempio BlazorSample_BlazorWebApp :

Esaminare quanto segue nelle versioni di .NET 6 o .NET 7 dell'esempio BlazorSample_Server :

  • DetectIncorrectUsagesOfTransientDisposables.cs
  • Services/TransitiveTransientDisposableDependency.cs:
  • In Program.cs:
    • Lo spazio dei nomi dell'app Services viene fornito all'inizio del file (using BlazorSample.Services;).
    • DetectIncorrectUsageOfTransients viene chiamato sul generatore host (builder.DetectIncorrectUsageOfTransients();).
    • Il TransientDependency servizio è registrato (builder.Services.AddTransient<TransientDependency>();).
    • L'oggetto TransitiveTransientDisposableDependency è registrato per ITransitiveTransientDisposableDependency (builder.Services.AddTransient<ITransitiveTransientDisposableDependency, TransitiveTransientDisposableDependency>();).
  • L'app registra il TransientDependency servizio senza generare un'eccezione. Tuttavia, il tentativo di risolvere il servizio in TransientService.razor genera un'eccezione InvalidOperationException quando il framework tenta di costruire un'istanza di TransientDependency.

Registrazioni di servizi temporanei per IHttpClientFactory/HttpClient i gestori

È consigliabile registrare i servizi temporanei per IHttpClientFactory/HttpClient i gestori. Se l'app contiene IHttpClientFactory/HttpClient gestori e usa IRemoteAuthenticationBuilder<TRemoteAuthenticationState,TAccount> per aggiungere il supporto per l'autenticazione, vengono individuati anche gli eliminabili temporanei seguenti per l'autenticazione lato client, che è previsto e possono essere ignorati:

Vengono individuate anche altre istanze di IHttpClientFactory/HttpClient . Queste istanze possono anche essere ignorate.

Le Blazor app di esempio nel Blazor repository GitHub di esempi (come scaricare) illustrano il codice per rilevare eliminazioni temporanee. Tuttavia, il codice viene disattivato perché le app di esempio includono IHttpClientFactory/HttpClient gestori.

Per attivare il codice dimostrativo e verificare l'operazione:

  • Rimuovere il commento dalle linee eliminabili temporanee in Program.cs.

  • Rimuovere il controllo NavLink.razor condizionale che impedisce la visualizzazione del TransientService componente nella barra laterale di spostamento dell'app:

    - else if (name != "TransientService")
    + else
    
  • Eseguire l'app di esempio e passare al TransientService componente in /transient-service.

Uso di un dbContext di Entity Framework Core (EF Core) da DI

Per altre informazioni, vedere ASP.NET Core Blazor con Entity Framework Core (EF Core).

Accedere ai servizi lato Blazor server da un ambito di inserimento delle dipendenze diverso

I gestori di attività del circuito forniscono un approccio per l'accesso ai servizi con Blazor ambito da altriBlazor ambiti di inserimento delle dipendenze (DI), ad esempio gli ambiti creati usando IHttpClientFactory.

Prima del rilascio di ASP.NET Core in .NET 8, accedere ai servizi con ambito circuito da altri ambiti di inserimento delle dipendenze necessari usando un tipo di componente di base personalizzato. Con i gestori di attività del circuito, non è necessario un tipo di componente di base personalizzato, come illustrato nell'esempio seguente:

public class CircuitServicesAccessor
{
    static readonly AsyncLocal<IServiceProvider> blazorServices = new();

    public IServiceProvider? Services
    {
        get => blazorServices.Value;
        set => blazorServices.Value = value;
    }
}

public class ServicesAccessorCircuitHandler : CircuitHandler
{
    readonly IServiceProvider services;
    readonly CircuitServicesAccessor circuitServicesAccessor;

    public ServicesAccessorCircuitHandler(IServiceProvider services, 
        CircuitServicesAccessor servicesAccessor)
    {
        this.services = services;
        this.circuitServicesAccessor = servicesAccessor;
    }

    public override Func<CircuitInboundActivityContext, Task> CreateInboundActivityHandler(
        Func<CircuitInboundActivityContext, Task> next)
    {
        return async context =>
        {
            circuitServicesAccessor.Services = services;
            await next(context);
            circuitServicesAccessor.Services = null;
        };
    }
}

public static class CircuitServicesServiceCollectionExtensions
{
    public static IServiceCollection AddCircuitServicesAccessor(
        this IServiceCollection services)
    {
        services.AddScoped<CircuitServicesAccessor>();
        services.AddScoped<CircuitHandler, ServicesAccessorCircuitHandler>();

        return services;
    }
}

Accedere ai servizi con ambito circuito inserendo dove CircuitServicesAccessor è necessario.

Per un esempio che illustra come accedere AuthenticationStateProvider a da una DelegatingHandler configurazione usando IHttpClientFactory, vedere Scenari di sicurezza aggiuntivi sul lato server ASP.NET CoreBlazor.

In alcuni casi un Razor componente richiama metodi asincroni che eseguono codice in un ambito di inserimento delle dipendenze diverso. Senza l'approccio corretto, questi ambiti di inserimento delle dipendenze non hanno accesso ai Blazorservizi di , ad esempio IJSRuntime e Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage.

Ad esempio, HttpClient le istanze create con IHttpClientFactory hanno un proprio ambito di servizio di inserimento delle dipendenze. Di conseguenza, HttpMessageHandler le istanze configurate in HttpClient non sono in grado di inserire Blazor direttamente i servizi.

Creare una classe BlazorServiceAccessor che definisce un AsyncLocaloggetto , che archivia l'oggetto BlazorIServiceProvider per il contesto asincrono corrente. È possibile acquisire un'istanza BlazorServiceAcccessor dall'interno di un ambito di servizio di inserimento delle dipendenze diverso per accedere ai Blazor servizi.

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

Per impostare automaticamente il valore di BlazorServiceAccessor.Services quando viene richiamato un async metodo componente, creare un componente di base personalizzato che implementi nuovamente i tre punti di ingresso asincroni primari nel Razor codice del componente:

La classe seguente illustra l'implementazione per il componente di 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;
        }
    }
}

Tutti i componenti che CustomComponentBase si estendono automaticamente sono BlazorServiceAccessor.Services impostati su IServiceProvider nell'ambito di inserimento delle dipendenze corrente Blazor .

Infine, nel Program file aggiungere come BlazorServiceAccessor servizio con ambito:

builder.Services.AddScoped<BlazorServiceAccessor>();

Infine, in Startup.ConfigureServices di Startup.csaggiungere come BlazorServiceAccessor servizio con ambito:

services.AddScoped<BlazorServiceAccessor>();

Risorse aggiuntive