Dela via


ASP.NET Core-beroendeinjektion Blazor

Anmärkning

Det här är inte den senaste versionen av den här artikeln. För den nuvarande utgåvan, se .NET 9-versionen av den här artikeln .

Varning

Den här versionen av ASP.NET Core stöds inte längre. Mer information finns i supportpolicyn för .NET och .NET Core. För den nuvarande utgåvan, se .NET 9-versionen av den här artikeln .

Viktigt!

Den här informationen gäller en förhandsversionsprodukt som kan ändras avsevärt innan den släpps kommersiellt. Microsoft lämnar inga garantier, uttryckliga eller underförstådda, med avseende på den information som tillhandahålls här.

För den nuvarande utgåvan, se .NET 9-versionen av den här artikeln .

Av Rainer Stropek och Mike Rousos

Den här artikeln förklarar hur Blazor appar kan mata in tjänster i komponenter.

Beroendeinmatning (DI) är en teknik för åtkomst till tjänster som konfigurerats på en central plats:

  • Ramverksregistrerade tjänster kan matas in direkt i Razor komponenter.
  • Blazor appar definierar och registrerar anpassade tjänster och gör dem tillgängliga i hela appen via DI.

Anmärkning

Vi rekommenderar att du läser Beroendeinmatning i ASP.NET Core innan du läser det här avsnittet.

Standardtjänster

De tjänster som visas i följande tabell används ofta i Blazor appar.

Tjänster Livstid Beskrivning
HttpClient Omfattad

Innehåller metoder för att skicka HTTP-begäranden och ta emot HTTP-svar från en resurs som identifieras av en URI.

På klientsidan registreras en instans av HttpClient av appen i Program filen och använder webbläsaren för att hantera HTTP-trafiken i bakgrunden.

På serversidan är en HttpClient inte konfigurerad som en tjänst som standard. I kod på serversidan anger du en HttpClient.

Mer information finns i Anropa ett webb-API från en ASP.NET Core-appBlazor.

En HttpClient är registrerad som en begränsad tjänst, inte singleton. Mer information finns i avsnittet Tjänstlivslängd .

IJSRuntime

Klientsidan: Singleton

Serversidan – Avgränsad

Ramverket Blazor registreras i appens tjänstcontainer IJSRuntime .

Representerar en instans av en JavaScript-körning där JavaScript-anrop skickas. Mer information finns i Anropa JavaScript-funktioner från .NET-metoder i ASP.NET Core Blazor.

När du försöker mata in tjänsten i en singleton-tjänst på servern använder du någon av följande metoder:

  • Ändra tjänstregistreringen till kontextberoende så att den matchar IJSRuntime-registreringen, vilket är lämpligt om tjänsten hanterar användarspecifikt tillstånd.
  • Skicka in IJSRuntime i singletonens tjänsteimplementering som ett argument för metodanropen i stället för att injicera det i singletonen.
NavigationManager

Klientsidan: Singleton

Serversidan – Avgränsad

Ramverket Blazor registreras i appens tjänstcontainer NavigationManager .

Innehåller hjälp för att arbeta med URI:er och navigeringstillstånd. Mer information finns i URI- och navigeringstillståndshjälpare.

Ytterligare tjänster som registrerats av ramverket Blazor beskrivs i dokumentationen där de används för att beskriva Blazor funktioner, till exempel konfiguration och loggning.

En anpassad tjänstleverantör tillhandahåller inte automatiskt de standardtjänster som anges i tabellen. Om du använder en anpassad tjänstleverantör och kräver någon av de tjänster som visas i tabellen lägger du till de nödvändiga tjänsterna till den nya tjänstleverantören.

Lägga till tjänster på klientsidan

Konfigurera tjänster för appens tjänstsamling i Program filen. I följande exempel registreras implementeringen ExampleDependency för IExampleDependency:

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

await builder.Build().RunAsync();

När värden har byggts är tjänster tillgängliga från det övergripande DI-omfånget innan några komponenter renderas. Detta kan vara användbart för att köra initieringslogik innan innehåll återges:

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

Värden tillhandahåller en central konfigurationsinstans för appen. Med utgångspunkt i föregående exempel skickas vädertjänstens URL från en standardkonfigurationskälla (till exempel appsettings.json) till 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();

Lägga till tjänster på serversidan

När du har skapat en ny app undersöker du en del av Program filen:

var builder = WebApplication.CreateBuilder(args);

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

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

Variabeln builder representerar en WebApplicationBuilder med IServiceCollection, som är en lista över tjänstbeskrivningsobjekt. Tjänster läggs till genom att tillhandahålla tjänstbeskrivningar till tjänstsamlingen. I följande exempel visas konceptet med IDataAccess gränssnittet och dess konkreta implementering DataAccess:

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

När du har skapat en ny app undersöker du Startup.ConfigureServices metoden i Startup.cs:

using Microsoft.Extensions.DependencyInjection;

...

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

Metoden ConfigureServices skickas en IServiceCollection, vilket är en lista över objekt för tjänstbeskrivare. Tjänster läggs till i ConfigureServices metoden genom att tillhandahålla tjänstbeskrivningar till tjänstsamlingen. I följande exempel visas konceptet med IDataAccess gränssnittet och dess konkreta implementering DataAccess:

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

Registrera vanliga tjänster

Om en eller flera vanliga tjänster krävs på klient- och serversidan kan du placera common service-registreringarna på en metodklientsida och anropa metoden för att registrera tjänsterna i båda projekten.

Börja med att bryta ut gemensamma tjänsteregistreringar till en separat metod. Skapa till exempel en ConfigureCommonServices metod på klientsidan:

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

För filen på klientsidan Program anropar du ConfigureCommonServices för att registrera de vanliga tjänsterna:

var builder = WebAssemblyHostBuilder.CreateDefault(args);

...

ConfigureCommonServices(builder.Services);

I filen på serversidan Program anropar du ConfigureCommonServices för att registrera de vanliga tjänsterna:

var builder = WebApplication.CreateBuilder(args);

...

Client.Program.ConfigureCommonServices(builder.Services);

Ett exempel på den här metoden finns i ASP.NET Core Blazor WebAssembly ytterligare säkerhetsscenarier.

Tjänster på klientsidan som misslyckas under prerendering

Det här avsnittet gäller endast WebAssembly-komponenter i Blazor Web Apps.

Blazor Web Apps normalt sett prerenderar klient-sidan dessa WebAssembly-komponenter. Om en app körs med en obligatorisk tjänst som endast är registrerad i .Client projektet resulterar körningen av appen i ett körningsfel som liknar följande när en komponent försöker använda den nödvändiga tjänsten under förinläsningen:

InvalidOperationException: Det går inte att ange ett värde för {PROPERTY} på typen {ASSEMBLY}}. Client.Pages. {KOMPONENTNAMN}'. Det finns ingen registrerad tjänst av typen {SERVICE}.

Använd någon av följande metoder för att lösa problemet:

  • Registrera tjänsten i huvudprojektet för att göra den tillgänglig under komponentförbeläsningen.
  • Om förrendering inte krävs för komponenten inaktiverar du prerendering genom att följa riktlinjerna i Prerender ASP.NET Core-komponenterRazor. Om du använder den här metoden behöver du inte registrera tjänsten i huvudprojektet.

Mer information finns i avsnittet Om tjänster på klientsidan inte kan lösas under prerendering i artikeln Prerendering , som visas senare i dokumentationen Blazor .

Tjänstlivslängd

Tjänster kan konfigureras med livslängden som visas i följande tabell.

Livstid Beskrivning
Scoped

Klientsidan har för närvarande inte något begrepp om DI-omfång. Scoped-registrerade tjänster fungerar som Singleton tjänster.

Utveckling på serversidan stöder livscykeln Scoped över HTTP-begäranden, men inte för SignalR-anslutnings-/kretsmeddelanden mellan komponenter som laddas på klienten. Sidor eller MVC-delen av appen behandlar avgränsade tjänster normalt och återskapar tjänsterna vid varje HTTP-begäran när du navigerar mellan sidor eller vyer eller från en sida eller vy till en komponent. Begränsade tjänster rekonstrueras inte när du navigerar mellan komponenter på klienten, där kommunikationen till servern sker via SignalR anslutningen av användarens krets, inte via HTTP-begäranden. I följande komponentscenarier på klienten rekonstrueras begränsade tjänster eftersom en ny krets skapas för användaren:

  • Användaren stänger webbläsarens fönster. Användaren öppnar ett nytt fönster och navigerar tillbaka till appen.
  • Användaren stänger en flik i appen i ett webbläsarfönster. Användaren öppnar en ny flik och navigerar tillbaka till appen.
  • Användaren väljer webbläsarens knapp för att läsa in/uppdatera igen.

Mer information om hur du bevarar användartillstånd i appar på serversidan finns i översikten över ASP.NET Core Blazor State Management och ASP.NET Core-tillståndshantering Blazor på serversidan.

Singleton DI skapar en enda instans av tjänsten. Alla komponenter som kräver en Singleton tjänst får samma instans av tjänsten.
Transient När en komponent hämtar en instans av en Transient tjänst från tjänstcontainern får den en ny instans av tjänsten.

DI-systemet baseras på DI-systemet i ASP.NET Core. Mer information finns i Beroendeinmatning i ASP.NET Core.

Begära en tjänst i en komponent

För att mata in tjänster i komponenter, Blazor stöder konstruktorinmatning och egenskapsinmatning.

Konstruktorinjektion

När tjänster har lagts till i tjänstsamlingen matar du in en eller flera tjänster i komponenter med konstruktorinmatning. I följande exempel matas tjänsten NavigationManager in.

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

Egenskapsinmatning

När tjänster har lagts till i tjänstsamlingen matar du in en eller flera tjänster i komponenter med @injectRazor direktivet, som har två parametrar:

  • Typ: Den typ av tjänst som ska matas in.
  • Egenskap: Namnet på den egenskap som tar emot den inmatade apptjänsten. Egenskapen kräver inte manuellt skapande. Kompilatorn skapar egenskapen.

Mer information finns i Beroendeinmatning i vyer i ASP.NET Core.

Använd flera @inject instruktioner för att mata in olika tjänster.

I följande exempel visas hur du använder @inject direktivet. Implementeringstjänsten Services.NavigationManager införs i komponentens egenskap Navigation. Observera att koden bara använder abstraktionen NavigationManager .

PropertyInjection.razor:

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

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

Internt använder den genererade egenskapen (Navigation) [Inject] attributet. Det här attributet används vanligtvis inte direkt. Om en basklass krävs för komponenter och inmatade egenskaper också krävs för basklassen [Inject]lägger du till attributet manuellt:

using Microsoft.AspNetCore.Components;

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

    ...
}

Anmärkning

Eftersom inmatade tjänster förväntas vara tillgängliga tilldelas standardliteralen med operatorn null-forgiving (default!) i .NET 6 eller senare. För mer information, se Nullable reference types (NRTs) och statisk null-tillståndsanalys i .NET-kompilatorn.

I komponenter som härleds från en basklass @inject krävs inte direktivet. Basklassens InjectAttribute är tillräcklig. Komponenten kräver @inherits endast direktivet. I följande exempel är alla inmatade tjänster av CustomComponentBase tillgängliga för komponenten Demo :

@page "/demo"
@inherits CustomComponentBase

Tjänstinmatning via en importfil på toppnivå (_Imports.razor)

Det här avsnittet gäller endast Blazor Web Apps.

En importfil på den översta nivån i mappen Components (Components/_Imports.razor) matar in sina referenser i alla komponenter i mapphierarkin, som innehåller komponenten App (App.razor). Komponenten App återges alltid statiskt även om prerendering av en sidkomponent är inaktiverad. Genom att mata in tjänster via den översta importfilen kan du därför lösa två instanser av tjänsten i sidkomponenter.

Åtgärda det här scenariot genom att mata in tjänsten i en ny importfil som placeras i mappen Pages (Components/Pages/_Imports.razor). Från denna plats löses tjänsten bara en gång för sidkomponenterna.

Använda DI i tjänster

Komplexa tjänster kan kräva ytterligare tjänster. I följande exempel DataAccess kräver HttpClient standardtjänsten. @inject(eller attributet[Inject]) är inte tillgängligt för användning i tjänster. Konstruktorinmatning måste användas i stället. Nödvändiga tjänster läggs till genom att parametrar läggs till i tjänstens konstruktor. När DI skapar tjänsten identifieras de tjänster som krävs i konstruktorn och tillhandahåller dem därefter. I följande exempel tar konstruktorn emot en HttpClient via DI. HttpClient är en standardtjänst.

using System.Net.Http;

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

    ...
}

Konstruktorinmatning stöds med primära konstruktorer i C# 12 (.NET 8) eller senare:

using System.Net.Http;

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

Krav för konstruktorinmatning:

  • En konstruktor måste finnas vars argument kan uppfyllas av DI. Ytterligare parametrar som inte omfattas av DI tillåts om de anger standardvärden.
  • Den tillämpliga konstruktorn måste vara public.
  • En tillämplig konstruktor måste finnas. Vid tvetydighet utlöser DI ett undantag.

Mata in nyckelade tjänster i komponenter

Blazor stöder inmatning av nyckelade tjänster med hjälp av attributet [Inject] . Nycklar möjliggör begränsning av registrering och konsumtion av tjänster när du använder dependency injection. Använd egenskapen InjectAttribute.Key för att ange nyckeln för tjänsten som ska matas in:

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

Baskomponentklasser för verktyg för att hantera ett DI-omfång

I icke-ASP.NET Core-apparBlazor begränsas begränsade och tillfälliga tjänster vanligtvis till den aktuella begäran. När begäran har slutförts tas omfångs- och tillfälliga tjänster bort av DI-systemet.

I interaktiva appar på serversidan Blazor varar DI-omfånget under kretsens varaktighet ( SignalR anslutningen mellan klienten och servern), vilket kan leda till att begränsade och disponibla tillfälliga tjänster lever mycket längre än livslängden för en enskild komponent. Injicera därför inte direkt en begränsad tjänst i en komponent om du avser att tjänstens livslängd ska matcha komponentens livslängd. Tillfälliga tjänster som matas in i en komponent som inte implementeras IDisposable är skräp som samlas in när komponenten tas bort. Inmatade tillfälliga tjänster som implementeras IDisposable underhålls dock av DI-containern under kretsens livslängd, vilket förhindrar skräpinsamling av tjänsten när komponenten tas bort och resulterar i en minnesläcka. En alternativ metod för begränsade tjänster baserat på OwningComponentBase typen beskrivs senare i det här avsnittet, och disponibla tillfälliga tjänster bör inte användas alls. Mer information finns i Design för att hantera temporära engångsprodukter på Blazor Server (dotnet/aspnetcore #26676).

Även i appar på klientsidan Blazor som inte fungerar via en krets behandlas tjänster som registrerats med en begränsad livslängd som singletons, så de lever längre än begränsade tjänster i typiska ASP.NET Core-appar. Disponibla tillfälliga tjänster på klientsidan lever också längre än de komponenter där de matas in eftersom DI-containern, som innehåller referenser till disponibla tjänster, bevaras under appens livslängd, vilket förhindrar skräpinsamling på tjänsterna. Även om långlivade disponibla tillfälliga tjänster är av större intresse på servern, bör de också undvikas som klienttjänstregistreringar. Användning av typen OwningComponentBase rekommenderas också för tjänster på klientsidan för att styra tjänstens livslängd, och disponibla tillfälliga tjänster bör inte användas alls.

En metod som begränsar en tjänstlivslängd är användning av typen OwningComponentBase . OwningComponentBase är en abstrakt typ som härleds från ComponentBase och som skapar ett DI-omfång som motsvarar komponentens livslängd. Med det här omfånget kan en komponent mata in tjänster med en begränsad livslängd och låta dem leva så länge som komponenten. När komponenten förstörs tas även tjänster från komponentens begränsade tjänstleverantör bort. Detta kan vara användbart för tjänster som återanvänds i en komponent men som inte delas mellan komponenter.

Två versioner av OwningComponentBase typen är tillgängliga och beskrivs i följande två avsnitt:

OwningComponentBase

OwningComponentBase är en abstrakt, engångs-använd underordnad typ ComponentBase med en skyddad egenskap ScopedServices av typen IServiceProvider. Providern kan användas för att lösa tjänster som är kopplade till komponentens livslängd.

DI-tjänster som matas in i komponenten med @inject eller med [Inject] attributet skapas inte i komponentens omfång. För att använda komponentens omfång måste tjänsterna lösas med ScopedServices tillsammans med antingen GetRequiredService eller GetService. Alla tjänster som löses med providern ScopedServices har sina beroenden som tillhandahålls i komponentens omfång.

I följande exempel visas skillnaden mellan att mata in en begränsad tjänst direkt och att matcha en tjänst som använder ScopedServices på servern. Följande gränssnitt och implementering för en tidsreseklass innehåller en DT egenskap som innehåller ett DateTime värde. Implementeringen anropar DateTime.Now för att ställa in DT när TimeTravel-klassen instansieras.

ITimeTravel.cs:

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

TimeTravel.cs:

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

Tjänsten är registrerad som scopad i serverns Program-fil. Begränsade tjänster på serversidan har en livslängd som motsvarar kretsens varaktighet.

Program I filen:

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

I den följande TimeTravel-komponenten:

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

När du först navigerar till komponenten TimeTravel instansieras tidsresetjänsten två gånger när komponenten laddas, och TimeTravel1 samt TimeTravel2 har samma initiala värde.

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

När du navigerar bort från komponenten TimeTravel till en annan komponent och tillbaka till komponenten TimeTravel :

  • TimeTravel1 tilldelas samma tjänstinstans som skapades när komponenten först lästes in, så värdet av DT förblir detsamma.
  • TimeTravel2 hämtar en ny ITimeTravel tjänstinstans i TimeTravel2 med ett nytt DT-värde.

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

TimeTravel1 är kopplad till användarens krets, som förblir intakt och inte tas bort förrän den underliggande kretsen har dekonstruerats. Tjänsten tas till exempel bort om kretsen är frånkopplad under den frånkopplade kretskvarhållningsperioden.

Trots att tjänsten registreras med begränsat omfång i Program-filen och användarens krets har en lång livslängd, får TimeTravel2 en ny ITimeTravel-tjänstinstans varje gång komponenten initieras.

OwningComponentBase<TService>

OwningComponentBase<TService> härleder från OwningComponentBase och lägger till en Service egenskap som returnerar en instans av T från den begränsade DI-providern. Den här typen är ett praktiskt sätt att komma åt begränsade tjänster utan att använda en instans av IServiceProvider när det finns en primär tjänst som appen kräver från DI-containern med komponentens omfång. Egenskapen ScopedServices är tillgänglig så att appen kan hämta tjänster av andra typer om det behövs.

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

Identifiera tillfälliga engångsartiklar på klientsidan

Anpassad kod kan läggas till i en app på klientsidan Blazor för att identifiera disponibla tillfälliga tjänster i en app som ska använda OwningComponentBase. Den här metoden är användbar om du är orolig för att kod som läggs till i appen i framtiden förbrukar en eller flera tillfälliga disponibla tjänster, inklusive tjänster som läggs till av bibliotek. Demonstrationskod är tillgänglig på Blazor GitHub-exempellagringsplatsen (så här laddar du ned).

Granska följande i .NET 6 eller senare versioner av BlazorSample_WebAssembly exemplet:

  • DetectIncorrectUsagesOfTransientDisposables.cs
  • Services/TransientDisposableService.cs
  • I Program.cs:
    • Appens Services namnområde finns överst i filen (using BlazorSample.Services;).
    • DetectIncorrectUsageOfTransients anropas omedelbart efter att builder har tilldelats från WebAssemblyHostBuilder.CreateDefault.
    • TransientDisposableService är registrerad (builder.Services.AddTransient<TransientDisposableService>();).
    • EnableTransientDisposableDetection anropas på den inbyggda värden i appens bearbetningspipeline (host.EnableTransientDisposableDetection();).
  • Appen registrerar TransientDisposableService tjänsten utan att utlösa ett undantag. Men om du försöker lösa tjänsten i TransientService.razor genereras en InvalidOperationException när ramverket försöker konstruera en instans av TransientDisposableService.

Identifiera tillfälliga objekt för borttagning på serversidan

Anpassad kod kan läggas till en serverside Blazor app för att identifiera tillfälliga disponibla tjänster på serversidan i en app som ska använda OwningComponentBase. Den här metoden är användbar om du är orolig för att kod som läggs till i appen i framtiden förbrukar en eller flera tillfälliga disponibla tjänster, inklusive tjänster som läggs till av bibliotek. Demonstrationskod är tillgänglig på Blazor GitHub-exempellagringsplatsen (så här laddar du ned).

Granska följande i .NET 8 eller senare versioner av BlazorSample_BlazorWebApp exemplet:

Granska följande i .NET 6- eller .NET 7-versioner av BlazorSample_Server exemplet:

  • DetectIncorrectUsagesOfTransientDisposables.cs
  • Services/TransitiveTransientDisposableDependency.cs:
  • I Program.cs:
    • Appens Services namnområde finns överst i filen (using BlazorSample.Services;).
    • DetectIncorrectUsageOfTransients anropas på värdverktyget (builder.DetectIncorrectUsageOfTransients();).
    • Tjänsten TransientDependency är registrerad (builder.Services.AddTransient<TransientDependency>();).
    • TransitiveTransientDisposableDependency är registrerad för ITransitiveTransientDisposableDependency (builder.Services.AddTransient<ITransitiveTransientDisposableDependency, TransitiveTransientDisposableDependency>();).
  • Appen registrerar TransientDependency tjänsten utan att utlösa ett undantag. Men om du försöker lösa tjänsten i TransientService.razor genereras en InvalidOperationException när ramverket försöker konstruera en instans av TransientDependency.

Tillfälliga tjänstregistreringar för IHttpClientFactory/HttpClient hanterare

Tillfälliga tjänstregistreringar för IHttpClientFactory/HttpClient hanterare rekommenderas. Om appen innehåller IHttpClientFactory/HttpClient hanterare och använder IRemoteAuthenticationBuilder<TRemoteAuthenticationState,TAccount> för att lägga till stöd för autentisering identifieras även följande tillfälliga engångsartiklar för autentisering på klientsidan, vilket förväntas och kan ignoreras:

Andra instanser av IHttpClientFactory/HttpClient identifieras också. Dessa fall kan också ignoreras.

Exempelapparna BlazorBlazor i GitHub-exempellagringsplatsen (hur du laddar ned) visar koden för att identifiera tillfälliga engångsartiklar. Koden inaktiveras dock eftersom exempelapparna innehåller IHttpClientFactory/HttpClient hanterare.

Så här aktiverar du demonstrationskoden och bevittnar dess åtgärd:

  • Avkommentera de tillfälliga disponibla linjerna i Program.cs.

  • Ta bort det villkorsbaserade testet i NavLink.razor som förhindrar att komponenten TransientService visas i appens navigeringssidofält.

    - else if (name != "TransientService")
    + else
    
  • Kör exempelappen och navigera till komponenten TransientService/transient-service.

Användning av en Entity Framework Core (EF Core) DbContext från DI

Mer information finns i ASP.NET Core Blazor med Entity Framework Core (EF Core).

Få åtkomst till tjänster på serversidan Blazor från ett annat DI-omfång

Kretsaktivitetshanterare tillhandahåller en metod för att komma åt avgränsade Blazor tjänster från andra icke-DI omfång, såsom omfång skapade med Blazor.

Innan släppet av ASP.NET Core i .NET 8 krävde åtkomst till tjänster med kretsområde från andra beroendeinjiceringomfång att man använder en anpassad baskomponenttyp. Med kretsaktivitetshanterare krävs ingen anpassad baskomponenttyp, vilket visas i följande exempel:

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

Anropa AddCircuitServicesAccessor i appens Program fil:

builder.Services.AddCircuitServicesAccessor();

Få åtkomst till de kretsomfattande tjänsterna genom att injicera dem där de CircuitServicesAccessor behövs.

Ett exempel som visar hur du kommer åt AuthenticationStateProvider från en DelegatingHandler-konfiguration med hjälp av IHttpClientFactory finns i ASP.NET Core-serversidan och Blazor Web App ytterligare säkerhetsscenarier.

Det kan finnas tillfällen när en Razor komponent anropar asynkrona metoder som kör kod i ett annat DI-omfång. Utan korrekt angreppssätt har dessa DI-omfång inte åtkomst till Blazors tjänster, såsom IJSRuntime och Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage.

Till exempel HttpClient har instanser som skapats med hjälp av IHttpClientFactory sitt eget DI-tjänstomfång. Det innebär att HttpMessageHandler instanser som konfigurerats på HttpClient inte kan injicera Blazor tjänster direkt.

Skapa en klass BlazorServiceAccessor som definierar en AsyncLocal, som lagrar BlazorIServiceProvider för den aktuella asynkrona kontexten. En BlazorServiceAccessor instans kan hämtas från ett annat DI-tjänstomfång för att få åtkomst till Blazor tjänster.

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

Om du vill ange värdet BlazorServiceAccessor.Services för automatiskt när en async komponentmetod anropas skapar du en anpassad baskomponent som implementerar om de tre primära asynkrona startpunkterna i Razor komponentkoden:

Följande klass visar implementeringen för baskomponenten.

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

Alla komponenter som utökar CustomComponentBase får automatiskt BlazorServiceAccessor.Services angivet till IServiceProvider i det aktuella DI-scope Blazor.

Lägg slutligen till Program som en begränsad tjänst i BlazorServiceAccessor filen:

builder.Services.AddScoped<BlazorServiceAccessor>();

Lägg slutligen till Startup.ConfigureServices som en begränsad tjänst i i :Startup.csBlazorServiceAccessor

services.AddScoped<BlazorServiceAccessor>();

Ytterligare resurser