Catatan
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba masuk atau mengubah direktori.
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba mengubah direktori.
Note
Ini bukan versi terbaru dari artikel ini. Untuk rilis saat ini, lihat versi .NET 10 dari artikel ini.
Warning
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 10 dari artikel ini.
Oleh Kirk Larkin, Steve Smith, dan Brandon Dahler
ASP.NET Core mendukung pola desain perangkat lunak injeksi dependensi (DI), yang merupakan teknik untuk mencapai Inversion of Control (IoC) antara kelas dan dependensinya.
Artikel ini menyediakan informasi tentang DI di aplikasi web ASP.NET Core. Untuk informasi tentang DI di semua jenis aplikasi, termasuk aplikasi selain aplikasi web, lihat Injeksi dependensi di .NET.
Panduan yang menambahkan atau menggantikan panduan dalam artikel ini ditemukan artikel berikut:
- ASP.NET Core Blazor injeksi dependensi
- Injeksi dependensi ke pengontrol di ASP.NET Core
- Menggunakan layanan DI untuk mengonfigurasi opsi
Contoh kode dalam artikel ini didasarkan pada Blazor. Untuk melihat Razor contoh Halaman, lihat versi 7.0 dari artikel ini.
Melihat atau mengunduh kode sampel (cara mengunduh)
Saat menggunakan kode sampel dalam artikel ini di lokal Blazor Web App untuk tujuan demonstrasi, adopsi mode render interaktif.
Gambaran umum injeksi dependensi
Dependensi adalah objek yang bergantung pada objek lain. Pertimbangkan kelas berikut MyDependency dengan WriteMessage metode:
public class MyDependency
{
public void WriteMessage(string message)
{
Console.WriteLine($"MyDependency.WriteMessage: {message}");
}
}
Kelas dapat membuat instans dari kelas MyDependency untuk memanggil metode WriteMessage. Dalam contoh berikut, MyDependency kelas adalah dependensi dari komponen Razor .
Pages/DependencyExample1.razor:
@page "/dependency-example-1"
<button @onclick="WriteMessage">Write message</button>
@code {
private readonly MyDependency dependency = new MyDependency();
private void WriteMessage() =>
dependency.WriteMessage("DependencyExample1.WriteMessage called");
}
Kelas dapat membuat instans dari kelas MyDependency untuk memanggil metode WriteMessage-nya. Dalam contoh berikut, kelas MyDependency adalah dependensi dari kelas halaman IndexModel.
Pages/Index.cshtml.cs:
public class IndexModel : PageModel
{
private readonly MyDependency _dependency = new MyDependency();
public void OnGet()
{
_dependency.WriteMessage("IndexModel.OnGet called");
}
}
Kelas yang mengonsumsi menyusun dan secara langsung tergantung pada kelas MyDependency. Mengambil dependensi langsung, seperti dalam contoh sebelumnya, bermasalah dan harus dihindari karena alasan berikut:
- Untuk mengganti
MyDependencydengan implementasi yang berbeda, kelas yang menggunakan harus dimodifikasi. - Jika
MyDependencymemiliki dependensi, maka kelas yang menggunakan harus mengonfigurasinya juga. Dalam proyek besar dengan beberapa kelas tergantung padaMyDependency, kode konfigurasi menjadi tersebar di sekitar aplikasi. - Implementasinya sulit untuk pengujian unit.
DI mengatasi masalah ini melalui:
- Penggunaan antarmuka atau kelas dasar untuk mengabstraksi implementasi dependensi.
- Pendaftaran dependensi dalam kontainer layanan, juga disebut kontainer DI. ASP.NET Core menyediakan kontainer layanan bawaan, IServiceProvider. Layanan biasanya terdaftar dalam file aplikasi
Program(.NET 6 atau yang lebih baru) atau file aplikasiStartup(.NET 5 atau yang lebih lama). - Injeksi layanan ke dalam kelas di mana layanan tersebut digunakan. Kerangka kerja membuat instans dependensi dan membuangnya saat tidak lagi diperlukan.
Dalam contoh berikut, IMyDependency antarmuka mendefinisikan WriteMessage tanda tangan metode.
Interfaces/IMyDependency.cs:
public interface IMyDependency
{
void WriteMessage(string message);
}
Antarmuka sebelumnya diimplementasikan oleh tipe konkret berikut, MyDependency.
Services/MyDependency.cs:
public class MyDependency : IMyDependency
{
public void WriteMessage(string message)
{
Console.WriteLine($"MyDependency.WriteMessage: {message}");
}
}
Aplikasi ini mendaftarkan layanan IMyDependency dengan tipe konkret MyDependency, di mana layanan ditambahkan ke dalam kontainer layanan, biasanya dalam berkas Program (.NET 6 atau yang lebih baru) atau metode Startup.ConfigureServices (.NET 5 atau yang lebih lama). Metode AddScoped mendaftarkan layanan dengan masa hidup terlingkup, yang merupakan masa Blazor hidup sirkuit (.NET 8 atau yang lebih baru) atau satu permintaan di aplikasi MVC atau Razor Pages.
Masa pakai Layanan dijelaskan nanti dalam artikel ini.
builder.Services.AddScoped<IMyDependency, MyDependency>();
services.AddScoped<IMyDependency, MyDependency>();
Layanan IMyDependency diminta dan digunakan untuk memanggil metode WriteMessage, sebagaimana ditunjukkan oleh komponen berikut Razor.
Pages/DependencyExample2.razor:
@page "/dependency-example-2"
@inject IMyDependency Dependency
<button @onclick="WriteMessage">Write message</button>
@code {
private void WriteMessage() =>
Dependency.WriteMessage("DependencyExample2.WriteMessage called");
}
Layanan IMyDependency diminta dan digunakan untuk memanggil metode WriteMessage, seperti ditunjukkan oleh kelas model halaman berikut.
Pages/Index.cshtml.cs:
public class IndexModel(IMyDependency dependency) : PageModel
{
public void OnGet()
{
dependency.WriteMessage("IndexModel.OnGet called");
}
}
Dengan menggunakan pola DI, kelas yang menggunakan dependensi:
- Tidak menggunakan jenis
MyDependencykonkret, hanya antarmukaIMyDependencyyang diimplementasikannya. Itu memudahkan untuk mengubah implementasi tanpa memodifikasi konsumen. - Tidak secara langsung membuat instans dari
MyDependencyatau menghapusnya. Dependensi dibuat dan dibuang oleh kontainer layanan.
Implementasi IMyDependency antarmuka dapat ditingkatkan dengan menggunakan API pengelogan bawaan, yang disuntikkan sebagai dependensi dalam contoh berikut.
Services/MyDependency.cs:
public class MyDependency(ILogger<MyDependency> logger) : IMyDependency
{
public void WriteMessage(string message)
{
logger.LogInformation($"MyDependency.WriteMessage: {message}");
}
}
MyDependency tergantung pada ILogger<TCategoryName>, sebuah layanan yang disediakan framework.
Adalah biasa untuk menggunakan DI dengan cara berantai. Pada gilirannya, setiap dependensi yang diminta akan meminta dependensi-dependensi miliknya sendiri. Kontainer menyelesaikan dependensi dalam grafik dan mengembalikan layanan yang diselesaikan sepenuhnya. Kumpulan dependensi kolektif yang harus diselesaikan biasanya disebut sebagai pohon dependensi, grafik dependensi, atau grafik objek.
Kontainer menyelesaikan ILogger<TCategoryName> dengan memanfaatkan jenis terbuka (generik), menghilangkan kebutuhan untuk mendaftarkan setiap jenis yang dibangun (generik).
Dalam terminologi DI, layanan:
- Biasanya adalah objek yang menyediakan layanan ke objek lain, seperti layanan sebelumnya
IMyDependency. - Tidak terkait dengan layanan web, meskipun layanan mungkin menggunakan layanan web.
Implementasi IMyDependency yang ditunjukkan dalam contoh sebelumnya ditulis untuk menunjukkan prinsip-prinsip DI umum, bukan untuk menerapkan pengelogan. Sebagian besar aplikasi tidak perlu membuat pencatat, seperti yang ditunjukkan contoh sebelumnya. Kode berikut menunjukkan secara langsung menggunakan API pengelogan bawaan kerangka kerja, yang tidak memerlukan pendaftaran layanan kustom (IMyDependency).
Pages/LoggingExample.razor:
@page "/logging-example"
@inject ILogger<LoggingExample> Logger
<button @onclick="WriteMessage">Write message</button>
@code {
private void WriteMessage() =>
Logger.LogInformation("LoggingExample.WriteMessage called");
}
Pages/IndexModel.cshtml.cs:
public class IndexModel(ILogger<IndexModel> logger) : PageModel
{
public void OnGet()
{
logger.LogInformation("IndexModel.OnGet called");
}
}
Layanan yang disuntikkan ke Startup
layanan dapat disuntikkan ke Startup konstruktor dan Startup.Configure metode.
Host Generik (IHostBuilder) ASP.NET Core 3.0 atau yang lebih baru, menggunakan satu kontainer layanan di seluruh siklus hidup aplikasi setelah penyedia layanan "root" sementara menggunakan layanan penting bagi host untuk memulai dan mengonfigurasi kontainer layanan utama aplikasi. Sebagian besar layanan, termasuk layanan kustom dan layanan kerangka kerja yang tidak terlibat dalam startup host, tidak dikonfigurasi atau tersedia dalam kontainer layanan saat Startup konstruktor dipanggil. Hanya layanan berikut yang dapat disuntikkan ke Startup konstruktor saat menggunakan Host Generik:
Dengan membatasi layanan yang tersedia di konstruktor kelas Startup, Host Generik mencegah Anda mencoba menggunakan layanan sebelum layanan tersebut dibuat atau tersedia, serta mencegah pembuatan beberapa instance layanan singleton yang kustom atau milik kerangka kerja. Layanan singleton yang dibuat dalam kontainer layanan sementara dapat berbeda dengan yang dibuat dalam kontainer layanan akhir.
Setiap layanan yang terdaftar dalam container layanan dapat diinjeksikan ke dalam metode Startup.Configure. Dalam contoh berikut ini, sebuah ILogger<TCategoryName> diinjeksi.
public void Configure(IApplicationBuilder app, ILogger<Startup> logger)
{
...
}
Untuk informasi selengkapnya, lihat Pengaktifan aplikasi di konfigurasi ASP.NET Core dan Access di Startup.
Metode pendaftaran layanan
Untuk panduan umum tentang pendaftaran layanan, lihat Pendaftaran layanan.
Adalah umum untuk menggunakan beberapa implementasi saat memalsukan tipe untuk pengujian. Untuk informasi selengkapnya, lihat Pengujian integrasi di ASP.NET Core.
Mendaftarkan layanan hanya dengan jenis implementasi yang setara dengan mendaftarkan layanan dengan implementasi dan jenis layanan yang sama:
builder.Services.AddSingleton<MyDependency>();
services.AddSingleton<MyDependency>();
Metode pendaftaran layanan dapat digunakan untuk mendaftarkan beberapa instans layanan dengan jenis layanan yang sama. Dalam contoh berikut, AddSingleton dipanggil dua kali dengan IMyDependency sebagai jenis layanan. Panggilan kedua untuk AddSingleton mengambil alih yang sebelumnya ketika diselesaikan sebagai IMyDependency dan ditambahkan ke yang sebelumnya ketika beberapa layanan diselesaikan melalui IEnumerable<IMyDependency>.
builder.Services.AddSingleton<IMyDependency, MyDependency>();
builder.Services.AddSingleton<IMyDependency, DifferentDependency>();
public class MyService
{
public MyService(IMyDependency myDependency,
IEnumerable<IMyDependency> myDependencies)
{
Trace.Assert(myDependency is DifferentDependency);
var dependencyArray = myDependencies.ToArray();
Trace.Assert(dependencyArray[0] is MyDependency);
Trace.Assert(dependencyArray[1] is DifferentDependency);
}
}
services.AddSingleton<IMyDependency, MyDependency>();
services.AddSingleton<IMyDependency, DifferentDependency>();
public class MyService
{
public MyService(IMyDependency myDependency,
IEnumerable<IMyDependency> myDependencies)
{
Trace.Assert(myDependency is DifferentDependency);
var dependencyArray = myDependencies.ToArray();
Trace.Assert(dependencyArray[0] is MyDependency);
Trace.Assert(dependencyArray[1] is DifferentDependency);
}
}
Mendaftarkan grup layanan dengan metode ekstensi
Konvensi kerangka kerja ASP.NET Core untuk mendaftarkan sekelompok layanan terkait adalah menggunakan metode ekstensi tunggal Add{GROUP NAME} untuk mendaftarkan semua layanan yang diperlukan oleh fitur kerangka kerja, di mana {GROUP NAME} tempat penampung adalah nama grup deskriptif. Misalnya, metode ekstensi AddRazorComponents mendaftarkan layanan yang diperlukan untuk render komponen sisi server Razor.
Pertimbangkan contoh berikut yang mengonfigurasi opsi dan mendaftarkan layanan:
builder.Services.Configure<PositionOptions>(
builder.Configuration.GetSection(PositionOptions.Position));
builder.Services.Configure<ColorOptions>(
builder.Configuration.GetSection(ColorOptions.Color));
builder.Services.AddScoped<IMyDependency, MyDependency>();
builder.Services.AddScoped<IMyDependency2, MyDependency2>();
services.Configure<PositionOptions>(
builder.Configuration.GetSection(PositionOptions.Position));
services.Configure<ColorOptions>(
builder.Configuration.GetSection(ColorOptions.Color));
services.AddScoped<IMyDependency, MyDependency>();
services.AddScoped<IMyDependency2, MyDependency2>();
Grup pendaftaran terkait dapat dipindahkan ke metode ekstensi untuk mendaftarkan layanan. Dalam contoh berikut:
- Metode
AddConfigekstensi mengaitkan data konfigurasi ke kelas C# yang bertipe kuat dan mendaftarkan kelas ke dalam wadah layanan. - Metode
AddDependencyGroupekstensi menambahkan dependensi kelas atau layanan tambahan.
namespace Microsoft.Extensions.DependencyInjection;
public static class ConfigServiceCollectionExtensions
{
public static IServiceCollection AddConfig(
this IServiceCollection services, IConfiguration config)
{
services.Configure<PositionOptions>(
config.GetSection(PositionOptions.Position));
services.Configure<ColorOptions>(
config.GetSection(ColorOptions.Color));
return services;
}
public static IServiceCollection AddDependencyGroup(
this IServiceCollection services)
{
services.AddScoped<IMyDependency, MyDependency>();
services.AddScoped<IMyDependency2, MyDependency2>();
return services;
}
}
Kode berikut memanggil metode ekstensi sebelumnya AddConfig dan AddDependencyGroup untuk mendaftarkan layanan:
builder.Services
.AddConfig(builder.Configuration)
.AddDependencyGroup();
services
.AddConfig(builder.Configuration)
.AddDependencyGroup();
Sebaiknya aplikasi mengikuti konvensi penamaan pembuatan metode ekstensi di namespace Microsoft.Extensions.DependencyInjection, yang:
- Merangkum grup pendaftaran layanan.
- Menyediakan akses IntelliSense yang nyaman ke layanan.
Masa pakai layanan
Untuk panduan umum tentang masa pakai layanan, lihat Masa pakai layanan. Untuk panduan tambahan untuk masa pakai layanan yang berlaku untuk Blazor aplikasi, lihat injeksi dependensi ASP.NET CoreBlazor.
Untuk menggunakan layanan terlingkup di middleware, gunakan salah satu pendekatan berikut:
- Masukkan layanan ke dalam metode
InvokeatauInvokeAsyncmilik middleware. Menggunakan injeksi konstruktor menghasilkan pengecualian runtime karena memaksa layanan terlingkup berperilaku seperti singleton. Sampel di bagian Opsi Masa Berlaku dan Pendaftaran menunjukkan pendekatanInvokeAsync. - Gunakan middleware berbasis pabrik. Middleware yang didaftarkan dengan pendekatan ini diaktifkan per permintaan klien (koneksi), yang memungkinkan layanan terlingkup bisa langsung disematkan ke konstruktor middleware.
Untuk informasi selengkapnya, lihat sumber daya berikut ini:
- Menulis middleware ASP.NET Core kustom
- Untuk informasi selengkapnya tentang menggunakan layanan bertanda dengan komponen Razor, lihat injeksi dependensi Blazor ASP.NET Core.
Layanan utama
Layanan berbasis kunci mendaftarkan dan mengambil layanan menggunakan kunci. Layanan dikaitkan dengan kunci dengan memanggil salah satu metode ekstensi berikut untuk pendaftaran layanan:
Contoh berikut menunjukkan layanan kunci dengan API berikut:
-
IStringCacheadalah antarmuka layanan denganGettanda tangan metode. -
StringCache1danStringCache2merupakan implementasi layanan konkret untukIStringCache.
Interfaces/IStringCache.cs:
public interface IStringCache
{
string Get(int key);
}
Services/StringCache1.cs:
public class StringCache1 : IStringCache
{
public string Get(int key) => $"Resolving {key} from StringCache1.";
}
Services/StringCache2.cs:
public class StringCache2 : IStringCache
{
public string Get(int key) => $"Resolving {key} from StringCache2.";
}
Akses layanan terdaftar dengan menentukan kunci dengan [FromKeyedServices] atribut .
Layanan utama dan contoh titik akhir API Minimal dalam Program file:
builder.Services.AddKeyedSingleton<IStringCache, StringCache1>("cache1");
builder.Services.AddKeyedSingleton<IStringCache, StringCache2>("cache2");
...
app.MapGet("/cache1", ([FromKeyedServices("cache1")] IStringCache stringCache1) =>
stringCache1.Get(1));
app.MapGet("/cache2", ([FromKeyedServices("cache2")] IStringCache stringCache2) =>
stringCache2.Get(2));
Contoh penggunaan dalam Razor kompponmen (Pages/KeyedServicesExample.razor) menggunakan [Inject] atribut .
InjectAttribute.Key Gunakan properti untuk menentukan kunci layanan yang akan disuntikkan:
@page "/keyed-services-example"
@Cache?.Get(3)
@code {
[Inject(Key = "cache1")]
public IStringCache? Cache { get; set; }
}
Untuk informasi selengkapnya tentang menggunakan layanan dengan kunci dengan komponen Razor, lihat ASP.NET Core Injeksi DependensiBlazor.
Contoh penggunaan di SignalR hub (Hubs/MyHub1.cs) dengan injeksi konstruktor utama:
using Microsoft.AspNetCore.SignalR;
public class MyHub1([FromKeyedServices("cache2")] IStringCache cache) : Hub
{
public void Method()
{
Console.WriteLine(cache.Get(4));
}
}
Contoh penggunaan di SignalR hub (Hubs/MyHub2.cs) dengan penyuntikan metode:
using Microsoft.AspNetCore.SignalR;
public class MyHub2 : Hub
{
public void Method([FromKeyedServices("cache2")] IStringCache cache)
{
Console.WriteLine(cache.Get(5));
}
}
Middleware mendukung layanan dengan kunci baik di konstruktor middleware maupun metodenya berikut: Invoke/InvokeAsync
internal class MyMiddleware
{
private readonly RequestDelegate _next;
public MyMiddleware(RequestDelegate next,
[FromKeyedServices("cache1")] IStringCache cache)
{
_next = next;
Console.WriteLine(cache.Get(6));
}
public Task Invoke(HttpContext context,
[FromKeyedServices("cache2")] IStringCache cache)
{
Console.WriteLine(cache.Get(7));
return _next(context);
}
}
Dalam alur pemrosesan aplikasi berkas Program (.NET 6 atau lebih baru) atau metode Startup.Configure (.NET 5 atau lebih lama):
app.UseMiddleware<MyMiddleware>();
Untuk informasi selengkapnya tentang membuat middleware, lihat Menulis middleware ASP.NET Core kustom.
Perilaku injeksi konstruktor
Untuk informasi selengkapnya tentang perilaku injeksi konstruktor, lihat sumber daya berikut:
Konteks Kerangka Kerja Entitas
Untuk panduan tentang EF Core di aplikasi sisi Blazor server, lihat ASP.NET Core Blazor dengan Entity Framework Core (EF Core).
Secara bawaan, konteks Entity Framework ditambahkan ke kontainer layanan menggunakan lifetime berlingkup karena operasi database aplikasi web biasanya dibatasi pada permintaan klien. Untuk menggunakan masa pakai yang berbeda, tentukan masa pakai dengan menggunakan AddDbContext kelebihan beban. Layanan seumur hidup tertentu tidak boleh menggunakan konteks database dengan masa pakai yang lebih pendek dari masa pakai layanan.
Opsi masa pakai dan pendaftaran
Untuk menunjukkan perbedaan antara masa pakai layanan dan opsi pendaftarannya, pertimbangkan antarmuka berikut yang mewakili tugas sebagai operasi dengan pengidentifikasi, OperationId. Bergantung pada bagaimana masa pakai layanan operasi dikonfigurasi untuk antarmuka berikut, kontainer menyediakan instans layanan yang sama atau berbeda saat diminta oleh kelas.
IOperation.cs:
public interface IOperation
{
string OperationId { get; }
}
public interface IOperationTransient : IOperation { }
public interface IOperationScoped : IOperation { }
public interface IOperationSingleton : IOperation { }
Kelas berikut Operation mengimplementasikan semua antarmuka sebelumnya. Konstruktor Operation menghasilkan GUID dan menyimpan empat karakter terakhir dalam OperationId properti .
Operation.cs:
public class Operation : IOperationTransient, IOperationScoped, IOperationSingleton
{
public Operation()
{
OperationId = Guid.NewGuid().ToString()[^4..];
}
public string OperationId { get; }
}
Kode berikut membuat beberapa pendaftaran Operation kelas sesuai dengan masa pakai bernama.
Di mana layanan terdaftar:
builder.Services.AddTransient<IOperationTransient, Operation>();
builder.Services.AddScoped<IOperationScoped, Operation>();
builder.Services.AddSingleton<IOperationSingleton, Operation>();
services.AddTransient<IOperationTransient, Operation>();
services.AddScoped<IOperationScoped, Operation>();
services.AddSingleton<IOperationSingleton, Operation>();
Contoh berikut menunjukkan masa pakai objek baik di dalam maupun di antara permintaan. Komponen Operation dan middleware meminta setiap tipe IOperation dan mencatat OperationId untuk masing-masing.
Pages/OperationExample.razor:
@page "/operation-example"
@inject IOperationTransient TransientOperation
@inject IOperationScoped ScopedOperation
@inject IOperationSingleton SingletonOperation
<ul>
<li>Transient: @TransientOperation.OperationId</li>
<li>Scoped: @ScopedOperation.OperationId</li>
<li>Singleton: @SingletonOperation.OperationId</li>
</ul>
Contoh berikut menunjukkan masa pakai objek baik di dalam maupun di antara permintaan.
IndexModel dan middleware meminta setiap jenis IOperation dan mencatat OperationId untuk setiap permintaan.
IndexModel.cshtml.cs:
public class IndexModel : PageModel
{
private readonly ILogger<IndexModel> _logger;
private readonly IOperationTransient _transientOperation;
private readonly IOperationScoped _scopedOperation;
private readonly IOperationSingleton _singletonOperation;
public IndexModel(ILogger<IndexModel> logger,
IOperationTransient transientOperation,
IOperationScoped scopedOperation,
IOperationSingleton singletonOperation)
{
_logger = logger;
_transientOperation = transientOperation;
_scopedOperation = scopedOperation;
_singletonOperation = singletonOperation;
}
public void OnGet()
{
_logger.LogInformation($"Transient: {_transientOperation.OperationId}");
_logger.LogInformation($"Scoped: {_scopedOperation.OperationId}");
_logger.LogInformation($"Singleton: {_singletonOperation.OperationId}");
}
}
Middleware juga dapat menyelesaikan dan menggunakan layanan yang sama. Layanan terlingkup dan sementara harus diselesaikan dalam InvokeAsync metode .
MyMiddleware.cs:
public class MyMiddleware(ILogger<IndexModel> logger,
IOperationSingleton singletonOperation)
{
public async Task InvokeAsync(HttpContext context,
IOperationTransient transientOperation, IOperationScoped scopedOperation)
{
logger.LogInformation($"Transient: {transientOperation.OperationId}");
logger.LogInformation($"Scoped: {scopedOperation.OperationId}");
logger.LogInformation($"Singleton: {singletonOperation.OperationId}");
await _next(context);
}
}
public class MyMiddleware
{
private readonly ILogger<IndexModel> _logger;
private readonly IOperationSingleton _singletonOperation;
public MyMiddleware(ILogger<IndexModel> logger,
IOperationSingleton singletonOperation)
{
_logger = logger;
_singletonOperation = singletonOperation;
}
public async Task InvokeAsync(HttpContext context,
IOperationTransient transientOperation, IOperationScoped scopedOperation)
{
_logger.LogInformation($"Transient: {transientOperation.OperationId}");
_logger.LogInformation($"Scoped: {scopedOperation.OperationId}");
_logger.LogInformation($"Singleton: {_singletonOperation.OperationId}");
await _next(context);
}
}
Dalam pipeline pemrosesan aplikasi dari file Program (.NET 6 atau yang lebih baru) atau metode Startup.Configure (.NET 5 atau yang lebih lama):
app.UseMiddleware<MyMiddleware>();
Untuk informasi selengkapnya tentang membuat middleware, lihat Menulis middleware ASP.NET Core kustom.
Output dari contoh sebelumnya menunjukkan:
-
Objek sementara selalu berbeda. Nilai sementara
OperationIdberbeda untuk komponen Razor dan di middleware. - Objek tercakup sama untuk permintaan tertentu tetapi berbeda di seluruh sirkuit baru Blazor .
- Objek singleton sama untuk setiap permintaan atau Blazor sirkuit.
-
Objek sementara selalu berbeda. Nilai sementara
OperationIdberbeda untuk halaman dan pada middleware. - Objek tercakup sama untuk permintaan tertentu tetapi berbeda di seluruh permintaan baru.
- Objek singleton sama untuk setiap permintaan.
Mengatasi layanan saat pengaktifan aplikasi
Kode berikut menunjukkan cara menyelesaikan layanan yang ditentukan ruang lingkupnya untuk durasi terbatas ketika aplikasi dimulai.
var app = builder.Build();
using (var serviceScope = app.Services.CreateScope())
{
var services = serviceScope.ServiceProvider;
var dependency = services.GetRequiredService<IMyDependency>();
dependency.WriteMessage("Call services from main");
}
Validasi cakupan
Untuk panduan tentang validasi cakupan, lihat sumber daya berikut:
Layanan Permintaan
Layanan dan dependensinya dalam permintaan ASP.NET Core diekspos melalui HttpContext.RequestServices.
Kerangka kerja membuat cakupan per permintaan, dan RequestServices mengekspos penyedia layanan terlingkup. Semua layanan terlingkup berlaku selama permintaan aktif.
Note
Lebih suka meminta dependensi sebagai parameter konstruktor daripada menyelesaikan layanan dari RequestServices. Meminta dependensi sebagai parameter konstruktor menghasilkan kelas yang lebih mudah diuji.
Layanan desain untuk injeksi dependensi
Saat merancang layanan untuk DI:
- Hindari kelas dan anggota yang stateful dan statis. Hindari membuat status global dengan merancang aplikasi untuk menggunakan layanan singleton sebagai gantinya.
- Hindari instansiasi langsung kelas dependen dalam layanan. Instansiasi langsung mengaitkan kode ke implementasi tertentu.
- Membuat layanan kecil, diperhitungkan dengan baik, dan mudah diuji.
Jika kelas memiliki banyak dependensi yang disuntikkan, mungkin merupakan tanda bahwa kelas memiliki terlalu banyak tanggung jawab dan melanggar Prinsip Tanggung Jawab Tunggal (SRP) . Coba refaktor kelas dengan memindahkan beberapa tanggung jawabnya ke kelas baru. Perlu diingat bahwa Razor kelas model halaman Halaman dan kelas pengontrol MVC harus berfokus pada masalah UI.
Pembuangan layanan
Kontainer memanggil Dispose untuk jenis IDisposable yang dibuatnya. Layanan yang diselesaikan dari kontainer tidak boleh dibuang oleh pengembang. Jika jenis atau pabrik terdaftar sebagai singleton, kontainer akan membuang singleton secara otomatis.
Dalam contoh berikut, layanan dibuat oleh kontainer layanan dan dibuang secara otomatis.
Services/Service1.cs:
public class Service1 : IDisposable
{
private bool _disposed;
public void Write(string message)
{
Console.WriteLine($"Service1: {message}");
}
public void Dispose()
{
if (_disposed)
{
return;
}
Console.WriteLine("Service1.Dispose");
_disposed = true;
GC.SuppressFinalize(this);
}
}
Services/Service2.cs:
public class Service2 : IDisposable
{
private bool _disposed;
public void Write(string message)
{
Console.WriteLine($"Service2: {message}");
}
public void Dispose()
{
if (_disposed)
{
return;
}
Console.WriteLine("Service2.Dispose");
_disposed = true;
GC.SuppressFinalize(this);
}
}
Services/Service3.cs:
public interface IService3
{
public void Write(string message);
}
public class Service3(string myKey) : IService3, IDisposable
{
private bool _disposed;
public void Write(string message)
{
Console.WriteLine($"Service3: {message}, Key = {myKey}");
}
public void Dispose()
{
if (_disposed)
{
return;
}
Console.WriteLine("Service3.Dispose");
_disposed = true;
GC.SuppressFinalize(this);
}
}
Dalam appsettings.Development.json:
"Key": "Value from appsettings.Development.json"
Di mana layanan didaftarkan oleh aplikasi:
builder.Services.AddScoped<Service1>();
builder.Services.AddSingleton<Service2>();
var key = builder.Configuration["Key"] ?? string.Empty;
builder.Services.AddSingleton<IService3>(sp => new Service3(key));
services.AddScoped<Service1>();
services.AddSingleton<Service2>();
var myKey = builder.Configuration["Key"] ?? string.Empty;
services.AddSingleton<IService3>(sp => new Service3(myKey));
Pages/DisposalExample.razor:
@page "/disposal-example"
@inject Service1 Service1
@inject Service2 Service2
@inject IService3 Service3
@code {
protected override void OnInitialized()
{
Service1.Write("DisposalExample.OnInitialized");
Service2.Write("DisposalExample.OnInitialized");
Service3.Write("DisposalExample.OnInitialized");
}
}
Konsol debug menunjukkan output berikut setelah setiap refresh halaman Indeks:
Service1: DisposalExample.OnInitialized
Service2: DisposalExample.OnInitialized
Service3: DisposalExample.OnInitialized, Key = Value from appsettings.Development.json
Service1.Dispose
Untuk melihat entri untuk pembuangan Service1, bergerak menjauh dari komponen DisposalExample untuk memicu pembuangan komponen tersebut.
Pages/Index.cshtml.cs:
public class IndexModel(
Service1 service1, Service2 service2, IService3 service3)
: PageModel
{
public void OnGet()
{
service1.Write("IndexModel.OnGet");
service2.Write("IndexModel.OnGet");
service3.Write("IndexModel.OnGet");
}
}
Konsol debug menunjukkan output berikut setelah setiap refresh halaman Indeks:
Service1: IndexModel.OnGet
Service2: IndexModel.OnGet
Service3: IndexModel.OnGet, Key = Value from appsettings.Development.json
Service1.Dispose
Layanan yang tidak dibuat oleh kontainer layanan
Pertimbangkan kode berikut:
builder.Services.AddSingleton(new Service1());
builder.Services.AddSingleton(new Service2());
services.AddSingleton(new Service1());
services.AddSingleton(new Service2());
Untuk kode sebelumnya:
- Instans layanan tidak dibuat oleh kontainer layanan.
- Kerangka kerja tidak membuang layanan secara otomatis.
- Pengembang bertanggung jawab untuk membuang layanan.
IDisposable panduan untuk instans sementara dan bersama
Untuk informasi selengkapnya, lihat Injeksi dependensi di .NET: Panduan IDisposable untuk instans sementara dan instans berbagi.
Penggantian wadah layanan default
Untuk informasi selengkapnya, lihat Injeksi dependensi di .NET: Penggantian kontainer layanan default.
Recommendations
Untuk informasi selengkapnya, lihat Panduan injeksi ketergantungan: Rekomendasi.
Hindari menggunakan pola pencari lokasi layanan. Misalnya, jangan panggil GetService untuk mendapatkan instans layanan saat Anda dapat menggunakan DI sebagai gantinya:
Incorrect:
Correct:
public class MyClass(IOptionsMonitor<MyOptions> optionsMonitor)
{
public void MyMethod()
{
var option = optionsMonitor.CurrentValue.Option;
...
}
}
Variasi pencari layanan lain yang harus dihindari adalah menginjeksi sebuah fabrik yang menyelesaikan atau mengatasi dependensi pada saat runtime. Kedua praktik ini mencampur strategi Inversi Kontrol .
Hindari akses statis ke HttpContext (misalnya, IHttpContextAccessor.HttpContext).
DI adalah alternatif untuk pola akses objek statis/global. Anda mungkin tidak dapat mewujudkan manfaat DI jika Anda mencampurnya dengan akses objek statis.
Pola yang direkomendasikan untuk multitenansi pada injeksi dependensi
Orchard Core adalah kerangka kerja untuk membangun aplikasi modular dan multitenant di ASP.NET Core. Untuk informasi selengkapnya, lihat Dokumentasi Orchard Core.
Untuk contoh cara membangun aplikasi modular dan multipenyewa dengan menggunakan Orchard Core Framework tanpa fitur khusus CMS, lihat contoh Orchard Core.
Layanan kerangka kerja
File Program (.NET 6 atau yang lebih baru) atau Startup file (.NET 5 atau yang lebih lama) mendaftarkan layanan yang digunakan aplikasi, termasuk fitur platform, seperti Entity Framework Core dan layanan untuk mendukung Razor komponen di Blazor (.NET 8 atau yang lebih baru). Awalnya, layanan IServiceCollection ditentukan oleh kerangka kerja berdasarkan cara host dikonfigurasi. Untuk aplikasi berdasarkan templat ASP.NET Core, kerangka kerja mendaftarkan lebih dari 250 layanan.
Tabel berikut ini menjelaskan sampel kecil layanan terdaftar kerangka kerja:
| Jenis layanan | Lifetime |
|---|---|
| Microsoft.AspNetCore.Hosting.Builder.IApplicationBuilderFactory | Transient |
| IHostApplicationLifetime | Singleton |
| IWebHostEnvironment | Singleton |
| Microsoft.AspNetCore.Hosting.IStartup | Singleton |
| Microsoft.AspNetCore.Hosting.IStartupFilter | Transient |
| Microsoft.AspNetCore.Hosting.Server.IServer | Singleton |
| Microsoft.AspNetCore.Http.IHttpContextFactory | Transient |
| Microsoft.Extensions.Logging.ILogger<TCategoryName> | Singleton |
| Microsoft.Extensions.Logging.ILoggerFactory | Singleton |
| Microsoft.Extensions.ObjectPool.ObjectPoolProvider | Singleton |
| Microsoft.Extensions.Options.IConfigureOptions<TOptions> | Transient |
| Microsoft.Extensions.Options.IOptions<TOptions> | Singleton |
| System.Diagnostics.DiagnosticSource | Singleton |
| System.Diagnostics.DiagnosticListener | Singleton |
Sumber daya tambahan
- ASP.NET Core Blazor injeksi dependensi
- Injeksi dependensi ke dalam tampilan di ASP.NET Core
- Injeksi dependensi ke pengontrol di ASP.NET Core
- Injeksi ketergantungan dalam pengendali persyaratan di ASP.NET Core
- Pola Konferensi NDC untuk pengembangan aplikasi DI
- Startup aplikasi di ASP.NET Core
- Aktivasi middleware berbasis pabrik di ASP.NET Core
- Memahami dasar-dasar injeksi dependensi di .NET
- Panduan injeksi dependensi
- Tutorial: Menggunakan injeksi dependensi di .NET
- Injeksidependensi .NET
- ASP.NET Core Dependency Injection: Apa itu IServiceCollection?
- Empat cara untuk membuang IDisposables di ASP.NET Core
- Menulis Kode Bersih di ASP.NET Core dengan Dependency Injection (MSDN)
- Prinsip Dependensi Eksplisit
- Inversi Kontainer Kontrol dan Pola Injeksi Dependensi (Martin Fowler)
- Cara mendaftarkan layanan dengan beberapa antarmuka di ASP.NET Core DI (Andrew Lock)
- Menghindari injeksi layanan Startup di ASP.NET Core 3 (Andrew Lock)
ASP.NET Core