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.
Avviso
Questa versione di ASP.NET Core non è più supportata. Per altre informazioni, vedere Criteri di supporto di .NET e .NET Core. 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 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:
|
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 WebApplicationBuilder IServiceCollectionoggetto , 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 DataAccess
concreta:
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 DataAccess
concreta:
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 Web Apps.
Blazor Web Appè in genere 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 '{ASSEMBLY}}. Client.Pages. {NOME COMPONENTE}'. Nessun servizio registrato di tipo '{SERVICE}'.
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. Lo sviluppo lato server supporta la
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="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");
}
}
Inserimento di proprietà
Dopo l'aggiunta dei servizi alla raccolta di servizi, inserire uno o più servizi nei componenti con la @inject
Razor 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à Navigation
del 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)
{
...
}
...
}
L'inserimento del costruttore è supportato con costruttori primari in C# 12 (.NET 8) o versioni successive:
using System.Net.Http;
public class DataAccess(HttpClient http) : IDataAccess
{
...
}
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 GetRequiredService GetService. 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
comeTimeTravel1
. - 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 diDT
rimane invariato.TimeTravel2
ottiene una nuovaITimeTravel
istanza del servizio inTimeTravel2
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 dibuilder
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();
).
- Lo spazio dei nomi dell'app
- L'app registra il
TransientDisposableService
servizio senza generare un'eccezione. Tuttavia, il tentativo di risolvere il servizio inTransientService.razor
genera un'eccezione InvalidOperationException quando il framework tenta di costruire un'istanza diTransientDisposableService
.
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 perITransitiveTransientDisposableDependency
(builder.Services.AddTransient<ITransitiveTransientDisposableDependency, TransitiveTransientDisposableDependency>();
).
- Lo spazio dei nomi dell'app
- L'app registra il
TransientDependency
servizio senza generare un'eccezione. Tuttavia, il tentativo di risolvere il servizio inTransientService.razor
genera un'eccezione InvalidOperationException quando il framework tenta di costruire un'istanza diTransientDependency
.
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 delTransientService
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(
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;
}
}
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 AsyncLocal
oggetto , che archivia l'oggetto BlazorIServiceProvider per il contesto asincrono corrente. È possibile acquisire un'istanza BlazorServiceAccessor
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.cs
aggiungere come BlazorServiceAccessor
servizio con ambito:
services.AddScoped<BlazorServiceAccessor>();
Risorse aggiuntive
- Inserimento del servizio tramite un file di importazione di primo livello (
_Imports.razor
) in Blazor Web Apps - Inserimento delle dipendenze in ASP.NET Core
IDisposable
indicazioni per le istanze temporanee e condivise- Inserimento di dipendenze in viste in ASP.NET Core
- Costruttori primari (Guida per C#)