Bagikan melalui


injeksi dependensi inti Blazor ASP.NET

Catatan

Ini bukan versi terbaru dari artikel ini. Untuk rilis saat ini, lihat versi .NET 8 dari artikel ini.

Peringatan

Versi ASP.NET Core ini tidak lagi didukung. Untuk informasi selengkapnya, lihat Kebijakan Dukungan .NET dan .NET Core. Untuk rilis saat ini, lihat versi .NET 8 dari artikel ini.

Penting

Informasi ini berkaitan dengan produk pra-rilis yang mungkin dimodifikasi secara substansial sebelum dirilis secara komersial. Microsoft tidak memberikan jaminan, tersirat maupun tersurat, sehubungan dengan informasi yang diberikan di sini.

Untuk rilis saat ini, lihat versi .NET 8 dari artikel ini.

Oleh Rainer Stropek dan Mike Rousos

Artikel ini menjelaskan bagaimana Blazor aplikasi dapat menyuntikkan layanan ke dalam komponen.

Injeksi dependensi (DI) adalah teknik untuk mengakses layanan yang dikonfigurasi di lokasi pusat:

  • Layanan terdaftar kerangka kerja dapat disuntikkan langsung ke komponen Razor .
  • Blazor aplikasi menentukan dan mendaftarkan layanan kustom dan membuatnya tersedia di seluruh aplikasi melalui DI.

Catatan

Sebaiknya baca injeksi Dependensi di ASP.NET Core sebelum membaca topik ini.

Layanan default

Layanan yang ditampilkan dalam tabel berikut ini umumnya digunakan dalam Blazor aplikasi.

Layanan Seumur hidup Deskripsi
HttpClient Cakupan

Menyediakan metode untuk mengirim permintaan HTTP dan menerima respons HTTP dari sumber daya yang diidentifikasi oleh URI.

Sisi klien, instans HttpClient didaftarkan oleh aplikasi dalam Program file dan menggunakan browser untuk menangani lalu lintas HTTP di latar belakang.

Sisi server, HttpClient tidak dikonfigurasi sebagai layanan secara default. Dalam kode sisi server, berikan HttpClient.

Untuk informasi selengkapnya, lihat Memanggil API web dari aplikasi ASP.NET CoreBlazor.

HttpClient Terdaftar sebagai layanan terlingkup, bukan singleton. Untuk informasi selengkapnya, lihat bagian Masa pakai layanan.

IJSRuntime

Sisi klien: Singleton

Sisi server: Terlingkup

Blazor Kerangka kerja mendaftar IJSRuntime di kontainer layanan aplikasi.

Mewakili instans runtime JavaScript tempat panggilan JavaScript dikirim. Untuk informasi lebih lanjut, lihat Memanggil fungsi JavaScript dari metode .NET di Blazor ASP.NET Core.

Saat mencoba menyuntikkan layanan ke layanan singleton di server, lakukan salah satu pendekatan berikut:

  • Ubah pendaftaran layanan menjadi cakupan agar sesuai IJSRuntimedengan pendaftaran, yang sesuai jika layanan berurusan dengan status khusus pengguna.
  • Teruskan IJSRuntime ke implementasi layanan singleton sebagai argumen dari panggilan metodenya alih-alih menyuntikkannya ke dalam singleton.
NavigationManager

Sisi klien: Singleton

Sisi server: Terlingkup

Blazor Kerangka kerja mendaftar NavigationManager di kontainer layanan aplikasi.

Berisi pembantu untuk bekerja dengan URI dan status navigasi. Untuk informasi selengkapnya, lihat URI dan pembantu status navigasi.

Layanan tambahan yang terdaftar oleh Blazor kerangka kerja dijelaskan dalam dokumentasi tempat layanan tersebut digunakan untuk menjelaskan Blazor fitur, seperti konfigurasi dan pengelogan.

Penyedia layanan kustom tidak secara otomatis menyediakan layanan default yang tercantum dalam tabel. Jika Anda menggunakan penyedia layanan kustom dan memerlukan salah satu layanan yang ditampilkan dalam tabel, tambahkan layanan yang diperlukan ke penyedia layanan baru.

Menambahkan layanan sisi klien

Konfigurasikan layanan untuk kumpulan layanan aplikasi dalam Program file. Dalam contoh berikut, ExampleDependency implementasi terdaftar untuk IExampleDependency:

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

await builder.Build().RunAsync();

Setelah host dibuat, layanan tersedia dari cakupan DI akar sebelum komponen apa pun dirender. Ini dapat berguna untuk menjalankan logika inisialisasi sebelum merender konten:

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

Host menyediakan instans konfigurasi pusat untuk aplikasi. Dibangun pada contoh sebelumnya, URL layanan cuaca diteruskan dari sumber konfigurasi default (misalnya, appsettings.json) ke 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();

Menambahkan layanan sisi server

Setelah membuat aplikasi baru, periksa bagian dari 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>();

Variabel builder mewakili WebApplicationBuilder dengan IServiceCollection, yang merupakan daftar objek deskriptor layanan. Layanan ditambahkan dengan menyediakan deskriptor layanan ke koleksi layanan. Contoh berikut menunjukkan konsep dengan IDataAccess antarmuka dan implementasi DataAccesskonkretnya :

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

Setelah membuat aplikasi baru, periksa Startup.ConfigureServices metode di Startup.cs:

using Microsoft.Extensions.DependencyInjection;

...

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

Metode ConfigureServices ini diteruskan IServiceCollection, yang merupakan daftar objek deskriptor layanan. Layanan ditambahkan dalam ConfigureServices metode dengan memberikan deskriptor layanan ke kumpulan layanan. Contoh berikut menunjukkan konsep dengan IDataAccess antarmuka dan implementasi DataAccesskonkretnya :

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

Mendaftarkan layanan umum

Jika satu atau beberapa layanan umum diperlukan klien dan sisi server, Anda dapat menempatkan pendaftaran layanan umum di sisi klien metode dan memanggil metode untuk mendaftarkan layanan di kedua proyek.

Pertama, faktor pendaftaran layanan umum ke dalam metode terpisah. Misalnya, buat ConfigureCommonServices metode sisi klien:

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

Untuk file sisi Program klien, panggil ConfigureCommonServices untuk mendaftarkan layanan umum:

var builder = WebAssemblyHostBuilder.CreateDefault(args);

...

ConfigureCommonServices(builder.Services);

Dalam file sisi Program server, panggil ConfigureCommonServices untuk mendaftarkan layanan umum:

var builder = WebApplication.CreateBuilder(args);

...

Client.Program.ConfigureCommonServices(builder.Services);

Untuk contoh pendekatan ini, lihat skenario keamanan tambahan ASP.NET CoreBlazor WebAssembly.

Layanan sisi klien yang gagal selama pra-penyajian

Bagian ini hanya berlaku untuk komponen WebAssembly di Blazor Web Apps.

Blazor Web Apps biasanya merender komponen WebAssembly sisi klien. Jika aplikasi dijalankan dengan layanan yang diperlukan hanya terdaftar dalam .Client proyek, menjalankan aplikasi menghasilkan kesalahan runtime yang mirip dengan yang berikut ini ketika komponen mencoba menggunakan layanan yang diperlukan selama pra-penyajian:

InvalidOperationException: Tidak dapat memberikan nilai untuk {PROPERTY} pada tipe '{ASSEMBLY}}. Client.Pages. {COMPONENT NAME}'. Tidak ada layanan terdaftar tipe '{SERVICE}'.

Gunakan salah satu pendekatan berikut untuk mengatasi masalah ini:

  • Daftarkan layanan di proyek utama untuk membuatnya tersedia selama pra-penyajian komponen.
  • Jika pra-penyajian tidak diperlukan untuk komponen, nonaktifkan pra-penyajian dengan mengikuti panduan dalam mode render inti Blazor ASP.NET. Jika Anda mengadopsi pendekatan ini, Anda tidak perlu mendaftarkan layanan di proyek utama.

Untuk informasi selengkapnya, lihat Layanan sisi klien gagal diselesaikan selama pra-penyajian.

Masa pakai layanan

Layanan dapat dikonfigurasi dengan masa pakai yang diperlihatkan dalam tabel berikut.

Seumur hidup Deskripsi
Scoped

Sisi klien saat ini tidak memiliki konsep cakupan DI. Scoped-layanan terdaftar berulah seperti Singleton layanan.

Pengembangan sisi server mendukung Scoped masa pakai di seluruh permintaan HTTP tetapi tidak di seluruh SignalR pesan koneksi/sirkuit di antara komponen yang dimuat pada klien. Bagian Razor Halaman atau MVC aplikasi memperlakukan layanan terlingkup secara normal dan membuat ulang layanan pada setiap permintaan HTTP saat menavigasi di antara halaman atau tampilan atau dari halaman atau tampilan ke komponen. Layanan tercakup tidak direkonstruksi saat menavigasi di antara komponen pada klien, di mana komunikasi ke server terjadi melalui SignalR koneksi sirkuit pengguna, bukan melalui permintaan HTTP. Dalam skenario komponen berikut pada klien, layanan tercakup direkonstruksi karena sirkuit baru dibuat untuk pengguna:

  • Pengguna menutup jendela browser. Pengguna membuka jendela baru dan menavigasi kembali ke aplikasi.
  • Pengguna menutup tab aplikasi di jendela browser. Pengguna membuka tab baru dan menavigasi kembali ke aplikasi.
  • Pengguna memilih tombol muat ulang/refresh browser.

Untuk informasi selengkapnya tentang mempertahankan status pengguna di aplikasi sisi server, lihat manajemen status ASP.NET CoreBlazor.

Singleton DI membuat satu instans layanan. Semua komponen yang Singleton memerlukan layanan menerima instans layanan yang sama.
Transient Setiap kali komponen mendapatkan instans Transient layanan dari kontainer layanan, komponen menerima instans layanan baru.

Sistem DI didasarkan pada sistem DI di ASP.NET Core. Untuk informasi lebih lanjut, lihat Injeksi dependensi di ASP.NET Core.

Meminta layanan dalam komponen

Untuk menyuntikkan layanan ke dalam komponen, Blazor mendukung injeksi konstruktor dan injeksi properti.

Injeksi konstruktor

Setelah layanan ditambahkan ke kumpulan layanan, masukkan satu atau beberapa layanan ke dalam komponen dengan injeksi konstruktor. Contoh berikut menyuntikkan NavigationManager layanan.

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

Injeksi properti

Setelah layanan ditambahkan ke koleksi layanan, masukkan satu atau beberapa layanan ke dalam komponen dengan direktif @injectRazor , yang memiliki dua parameter:

  • Jenis: Jenis layanan yang akan disuntikkan.
  • Properti: Nama properti yang menerima layanan aplikasi yang disuntikkan. Properti tidak memerlukan pembuatan manual. Pengkompilasi membuat properti .

Untuk informasi selengkapnya, lihat Injeksi dependensi ke dalam tampilan di ASP.NET Core.

Gunakan beberapa @inject pernyataan untuk menyuntikkan layanan yang berbeda.

Contoh berikut menunjukkan cara menggunakan direktif @inject . Penerapan layanan Services.NavigationManager disuntikkan ke properti Navigationkomponen . Perhatikan bagaimana kode hanya menggunakan NavigationManager abstraksi.

PropertyInjection.razor:

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

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

Secara internal, properti yang dihasilkan (Navigation) menggunakan [Inject] atribut . Biasanya, atribut ini tidak digunakan secara langsung. Jika kelas dasar diperlukan untuk komponen dan properti yang disuntikkan juga diperlukan untuk kelas dasar, tambahkan [Inject] atribut secara manual:

using Microsoft.AspNetCore.Components;

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

    ...
}

Catatan

Karena layanan yang disuntikkan diharapkan tersedia, literal default dengan operator pengampunan null (default!) ditetapkan di .NET 6 atau yang lebih baru. Untuk informasi selengkapnya, lihat Jenis referensi nullable (NRTs) dan .NET compiler null-state static analysis.

Dalam komponen yang berasal dari kelas dasar, @inject direktif tidak diperlukan. Kelas InjectAttribute dasar sudah cukup. Komponen hanya memerlukan direktif @inherits . Dalam contoh berikut, setiap layanan CustomComponentBase yang disuntikkan tersedia untuk Demo komponen:

@page "/demo"
@inherits CustomComponentBase

Menggunakan DI dalam layanan

Layanan kompleks mungkin memerlukan layanan tambahan. Dalam contoh berikut, DataAccess memerlukan HttpClient layanan default. @inject (atau [Inject] atribut) tidak tersedia untuk digunakan dalam layanan. Injeksi konstruktor harus digunakan sebagai gantinya. Layanan yang diperlukan ditambahkan dengan menambahkan parameter ke konstruktor layanan. Ketika DI membuat layanan, LAYANAN mengenali layanan yang diperlukan dalam konstruktor dan menyediakannya dengan sesuai. Dalam contoh berikut, konstruktor menerima HttpClient melalui DI. HttpClient adalah layanan default.

using System.Net.Http;

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

Prasyarat untuk injeksi konstruktor:

  • Satu konstruktor harus ada yang argumennya semuanya dapat dipenuhi oleh DI. Parameter tambahan yang tidak tercakup oleh DI diizinkan jika menentukan nilai default.
  • Konstruktor yang berlaku harus public.
  • Satu konstruktor yang berlaku harus ada. Dalam kasus ambiguitas, DI melempar pengecualian.

Menyuntikkan layanan kunci ke dalam komponen

Blazor mendukung menyuntikkan layanan kunci menggunakan [Inject] atribut . Kunci memungkinkan pencakupan pendaftaran dan konsumsi layanan saat menggunakan injeksi dependensi. InjectAttribute.Key Gunakan properti untuk menentukan kunci layanan yang akan disuntikkan:

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

Kelas komponen dasar utilitas untuk mengelola cakupan DI

Di aplikasi Non-ASP.NETBlazor Core, layanan terlingkup dan sementara biasanya dilingkup ke permintaan saat ini. Setelah permintaan selesai, layanan tercakup dan sementara dibuang oleh sistem DI.

Dalam aplikasi sisi Blazor server interaktif, cakupan DI berlangsung selama durasi sirkuit ( SignalR koneksi antara klien dan server), yang dapat mengakibatkan layanan sementara yang tercakup dan sekali pakai hidup lebih lama dari masa pakai satu komponen. Oleh karena itu, jangan langsung menyuntikkan layanan terlingkup ke dalam komponen jika Anda berniat seumur hidup layanan agar sesuai dengan masa pakai komponen. Layanan sementara yang disuntikkan ke dalam komponen yang tidak diterapkan IDisposable adalah sampah yang dikumpulkan ketika komponen dibuang. Namun, layanan sementara yang disuntikkan yang diterapkan IDisposable dipertahankan oleh kontainer DI selama masa pakai sirkuit, yang mencegah pengumpulan sampah layanan ketika komponen dibuang dan menghasilkan kebocoran memori. Pendekatan alternatif untuk layanan tercakup berdasarkan OwningComponentBase jenisnya dijelaskan nanti di bagian ini, dan layanan sementara sekali pakai tidak boleh digunakan sama sekali. Untuk informasi selengkapnya, lihat Desain untuk memecahkan sekali pakai sementara pada Blazor Server (dotnet/aspnetcore #26676).

Bahkan di aplikasi sisi Blazor klien yang tidak beroperasi melalui sirkuit, layanan yang terdaftar dengan masa pakai cakupan diperlakukan sebagai singleton, sehingga mereka hidup lebih lama dari layanan terlingkup di aplikasi ASP.NET Core biasa. Layanan sementara sekali pakai sisi klien juga hidup lebih lama dari komponen tempat mereka disuntikkan karena kontainer DI, yang menyimpan referensi ke layanan sekali pakai, bertahan selama masa pakai aplikasi, mencegah pengumpulan sampah pada layanan. Meskipun layanan sementara sekali pakai berumur panjang menjadi perhatian yang lebih besar di server, layanan tersebut juga harus dihindari sebagai pendaftaran layanan klien. Penggunaan OwningComponentBase jenis ini juga direkomendasikan untuk layanan tercakup sisi klien untuk mengontrol masa pakai layanan, dan layanan sementara sekali pakai tidak boleh digunakan sama sekali.

Pendekatan yang membatasi masa pakai layanan adalah penggunaan jenis tersebut OwningComponentBase . OwningComponentBase adalah jenis abstrak yang berasal dari ComponentBase yang membuat cakupan DI yang sesuai dengan masa pakai komponen. Dengan menggunakan cakupan ini, komponen dapat menyuntikkan layanan dengan masa pakai terlingkup dan membuatnya hidup selama komponen. Ketika komponen dihancurkan, layanan dari penyedia layanan tercakup komponen juga dibuang. Ini dapat berguna untuk layanan yang digunakan kembali dalam komponen tetapi tidak dibagikan di seluruh komponen.

Dua versi jenis OwningComponentBase tersedia dan dijelaskan di dua bagian berikutnya:

OwningComponentBase

OwningComponentBaseadalah anak abstrak dan sekali pakai dari ComponentBase jenis dengan properti jenis IServiceProvideryang dilindungi ScopedServices . Penyedia dapat digunakan untuk menyelesaikan layanan yang terlingkup hingga masa pakai komponen.

Layanan DI yang disuntikkan ke dalam komponen menggunakan @inject atau [Inject] atribut tidak dibuat dalam cakupan komponen. Untuk menggunakan cakupan komponen, layanan harus diselesaikan menggunakan ScopedServices dengan GetRequiredService atau GetService. Setiap layanan yang diselesaikan menggunakan ScopedServices penyedia memiliki dependensi yang disediakan dalam cakupan komponen.

Contoh berikut menunjukkan perbedaan antara menyuntikkan layanan tercakup secara langsung dan menyelesaikan layanan menggunakan ScopedServices di server. Antarmuka dan implementasi berikut untuk kelas perjalanan waktu mencakup DT properti untuk menyimpan DateTime nilai. Implementasi memanggil DateTime.Now untuk mengatur DT kapan TimeTravel kelas dibuat.

ITimeTravel.cs:

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

TimeTravel.cs:

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

Layanan ini terdaftar sebagai cakupan dalam file sisi Program server. Layanan tercakup sisi server memiliki masa pakai yang sama dengan durasi sirkuit.

Dalam file Program:

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

Dalam komponen TimeTravel berikut:

  • Layanan perjalanan waktu langsung disuntikkan dengan @inject sebagai TimeTravel1.
  • Layanan ini juga diselesaikan secara terpisah dengan ScopedServices dan GetRequiredService sebagai 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>();
    }
}

Awalnya menavigasi ke TimeTravel komponen, layanan perjalanan waktu dibuat dua kali ketika komponen dimuat, dan TimeTravel1 dan TimeTravel2 memiliki nilai awal yang sama:

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

Saat menavigasi menjauh dari komponen ke TimeTravel komponen lain dan kembali ke TimeTravel komponen:

  • TimeTravel1 disediakan instans layanan yang sama yang dibuat ketika komponen pertama kali dimuat, sehingga nilainya DT tetap sama.
  • TimeTravel2 mendapatkan instans layanan baru ITimeTravel dengan TimeTravel2 nilai DT baru.

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

TimeTravel1 terkait dengan sirkuit pengguna, yang tetap utuh dan tidak dibuang sampai sirkuit yang mendasar didekonstruksi. Misalnya, layanan dibuang jika sirkuit terputus untuk periode retensi sirkuit yang terputus.

Terlepas dari pendaftaran layanan terlingkup dalam Program file dan umur panjang sirkuit pengguna, TimeTravel2 menerima instans layanan baru ITimeTravel setiap kali komponen diinisialisasi.

OwningComponentBase<TService>

OwningComponentBase<TService> berasal dari OwningComponentBase dan menambahkan Service properti yang mengembalikan instans dari T penyedia DI tercakup. Jenis ini adalah cara mudah untuk mengakses layanan tercakup tanpa menggunakan instans IServiceProvider ketika ada satu layanan utama yang diperlukan aplikasi dari kontainer DI menggunakan cakupan komponen. Properti ScopedServices tersedia, sehingga aplikasi bisa mendapatkan layanan dari jenis lain, jika perlu.

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

Mendeteksi sekali pakai sementara sisi klien

Kode kustom dapat ditambahkan ke aplikasi sisi Blazor klien untuk mendeteksi layanan sementara sekali pakai di aplikasi yang harus menggunakan OwningComponentBase. Pendekatan ini berguna jika Anda khawatir kode yang ditambahkan ke aplikasi di masa mendatang menggunakan satu atau beberapa layanan sekali pakai sementara, termasuk layanan yang ditambahkan oleh pustaka. Kode demonstrasi tersedia di Blazor repositori GitHub sampel (cara mengunduh).

Periksa yang berikut ini dalam versi .NET 6 atau yang lebih baru BlazorSample_WebAssembly dari sampel:

  • DetectIncorrectUsagesOfTransientDisposables.cs
  • Services/TransientDisposableService.cs
  • Dalam: Program.cs
    • Namespace layanan aplikasi Services disediakan di bagian atas file (using BlazorSample.Services;).
    • DetectIncorrectUsageOfTransients dipanggil segera setelah builder ditetapkan dari WebAssemblyHostBuilder.CreateDefault.
    • TransientDisposableService terdaftar (builder.Services.AddTransient<TransientDisposableService>();).
    • EnableTransientDisposableDetection dipanggil pada host bawaan dalam alur pemrosesan aplikasi (host.EnableTransientDisposableDetection();).
  • Aplikasi ini mendaftarkan TransientDisposableService layanan tanpa melemparkan pengecualian. Namun, mencoba menyelesaikan layanan dalam TransientService.razor melempar InvalidOperationException ketika kerangka kerja mencoba membangun instans TransientDisposableService.

Mendeteksi sekali pakai sementara sisi server

Kode kustom dapat ditambahkan ke aplikasi sisi Blazor server untuk mendeteksi layanan sementara sekali pakai sisi server di aplikasi yang harus menggunakan OwningComponentBase. Pendekatan ini berguna jika Anda khawatir kode yang ditambahkan ke aplikasi di masa mendatang menggunakan satu atau beberapa layanan sekali pakai sementara, termasuk layanan yang ditambahkan oleh pustaka. Kode demonstrasi tersedia di Blazor repositori GitHub sampel (cara mengunduh).

Periksa yang berikut ini di .NET 8 atau versi sampel yang lebih baru BlazorSample_BlazorWebApp :

Periksa contoh berikut dalam versi BlazorSample_Server .NET 6 atau .NET 7:

  • DetectIncorrectUsagesOfTransientDisposables.cs
  • Services/TransitiveTransientDisposableDependency.cs:
  • Dalam: Program.cs
    • Namespace layanan aplikasi Services disediakan di bagian atas file (using BlazorSample.Services;).
    • DetectIncorrectUsageOfTransients dipanggil pada pembangun host (builder.DetectIncorrectUsageOfTransients();).
    • Layanan TransientDependency terdaftar (builder.Services.AddTransient<TransientDependency>();).
    • TransitiveTransientDisposableDependency terdaftar untuk ITransitiveTransientDisposableDependency (builder.Services.AddTransient<ITransitiveTransientDisposableDependency, TransitiveTransientDisposableDependency>();).
  • Aplikasi ini mendaftarkan TransientDependency layanan tanpa melemparkan pengecualian. Namun, mencoba menyelesaikan layanan dalam TransientService.razor melempar InvalidOperationException ketika kerangka kerja mencoba membangun instans TransientDependency.

Pendaftaran layanan sementara untuk IHttpClientFactory/HttpClient handler

Pendaftaran layanan sementara untuk IHttpClientFactory/HttpClient handler disarankan. Jika aplikasi berisi IHttpClientFactory/HttpClient handler dan menggunakan IRemoteAuthenticationBuilder<TRemoteAuthenticationState,TAccount> untuk menambahkan dukungan untuk autentikasi, penggunaan sementara berikut untuk autentikasi sisi klien juga ditemukan, yang diharapkan dan dapat diabaikan:

Instans IHttpClientFactory/HttpClient lain juga ditemukan. Instans ini juga dapat diabaikan.

Aplikasi Blazor sampel di Blazor repositori GitHub sampel (cara mengunduh) menunjukkan kode untuk mendeteksi sekali pakai sementara. Namun, kode dinonaktifkan karena aplikasi sampel menyertakan IHttpClientFactory/HttpClient handler.

Untuk mengaktifkan kode demonstrasi dan menyaksikan operasinya:

  • Batalkan komentar baris sekali pakai sementara di Program.cs.

  • Hapus cek masuk NavLink.razor kondisi yang mencegah TransientService komponen ditampilkan di bilah sisi navigasi aplikasi:

    - else if (name != "TransientService")
    + else
    
  • Jalankan aplikasi sampel dan navigasikan ke TransientService komponen di /transient-service.

Penggunaan Entity Framework Core (EF Core) DbContext dari DI

Untuk informasi selengkapnya, lihat ASP.NET Core Blazor dengan Entity Framework Core (EF Core).

Mengakses layanan sisi Blazor server dari cakupan DI yang berbeda

Penanganan aktivitas sirkuit menyediakan pendekatan untuk mengakses layanan tercakup Blazor dari cakupan injeksi non-dependensiBlazor (DI) lainnya, seperti cakupan yang dibuat menggunakan IHttpClientFactory.

Sebelum rilis ASP.NET Core di .NET 8, mengakses layanan cakupan sirkuit dari cakupan injeksi dependensi lain yang diperlukan menggunakan jenis komponen dasar kustom. Dengan penanganan aktivitas sirkuit, jenis komponen dasar kustom tidak diperlukan, seperti yang ditunjukkan contoh berikut:

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

Akses layanan cakupan sirkuit dengan menyuntikkan tempat CircuitServicesAccessor yang diperlukan.

Untuk contoh yang memperlihatkan cara mengakses AuthenticationStateProvider dari DelegatingHandler penyiapan menggunakan IHttpClientFactory, lihat Skenario keamanan tambahan sisi server ASP.NET CoreBlazor.

Mungkin ada kalanya Razor komponen memanggil metode asinkron yang menjalankan kode dalam cakupan DI yang berbeda. Tanpa pendekatan yang benar, cakupan DI ini tidak memiliki akses ke Blazorlayanan 's, seperti IJSRuntime dan Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage.

Misalnya, HttpClient instans yang dibuat menggunakan IHttpClientFactory memiliki cakupan layanan DI mereka sendiri. Akibatnya, HttpMessageHandler instans yang dikonfigurasi pada HttpClient tidak dapat langsung menyuntikkan Blazor layanan.

Buat kelas BlazorServiceAccessor yang mendefinisikan AsyncLocal, yang menyimpan BlazorIServiceProvider untuk konteks asinkron saat ini. BlazorServiceAcccessor Instans dapat diperoleh dari dalam cakupan layanan DI yang berbeda untuk mengakses Blazor layanan.

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

Untuk mengatur nilai BlazorServiceAccessor.Services secara otomatis saat async metode komponen dipanggil, buat komponen dasar kustom yang mengimplementasikan ulang tiga titik entri asinkron utama ke dalam Razor kode komponen:

Kelas berikut menunjukkan implementasi untuk komponen dasar.

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

Komponen apa pun yang diperluas CustomComponentBase secara otomatis telah BlazorServiceAccessor.Services diatur ke IServiceProvider dalam cakupan DI saat ini Blazor .

Terakhir, dalam Program file, tambahkan BlazorServiceAccessor sebagai layanan terlingkup:

builder.Services.AddScoped<BlazorServiceAccessor>();

Akhirnya, di Startup.ConfigureServices dari Startup.cs, tambahkan BlazorServiceAccessor sebagai layanan terlingkup:

services.AddScoped<BlazorServiceAccessor>();

Sumber Daya Tambahan: