Injeksi dependensi .NET
.NET mendukung pola desain perangkat lunak injeksi dependensi (DI), yang merupakan teknik untuk mencapai Inversion of Control (IoC) antara kelas dan dependensinya. Injeksi dependensi di .NET adalah bagian bawaan dari kerangka kerja, bersama dengan konfigurasi, pengelogan, dan pola opsi.
Dependensi adalah objek yang bergantung pada objek lain. Periksa kelas berikut MessageWriter
dengan Write
metode yang bergantung pada kelas lain:
public class MessageWriter
{
public void Write(string message)
{
Console.WriteLine($"MessageWriter.Write(message: \"{message}\")");
}
}
Kelas dapat membuat instans MessageWriter
kelas untuk menggunakan metodenya Write
. Dalam contoh berikut, MessageWriter
kelas adalah dependensi dari Worker
kelas :
public class Worker : BackgroundService
{
private readonly MessageWriter _messageWriter = new();
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
_messageWriter.Write($"Worker running at: {DateTimeOffset.Now}");
await Task.Delay(1_000, stoppingToken);
}
}
}
Kelas membuat dan secara langsung tergantung pada MessageWriter
kelas . Dependensi yang dikodekan secara permanen, seperti dalam contoh sebelumnya, bermasalah dan harus dihindari karena alasan berikut:
- Untuk mengganti
MessageWriter
dengan implementasi yang berbeda,Worker
kelas harus dimodifikasi. - Jika
MessageWriter
memiliki dependensi, dependensi juga harus dikonfigurasi olehWorker
kelas. Dalam proyek besar dengan beberapa kelas tergantung padaMessageWriter
, kode konfigurasi menjadi tersebar di seluruh aplikasi. - Implementasi ini sulit untuk pengujian unit. Aplikasi harus menggunakan kelas tiruan atau stub
MessageWriter
, yang tidak dimungkinkan dengan pendekatan ini.
Injeksi dependensi mengatasi masalah ini melalui:
- Penggunaan antarmuka atau kelas dasar untuk mengabstraksi implementasi dependensi.
- Pendaftaran dependensi dalam kontainer layanan. .NET menyediakan kontainer layanan bawaan, IServiceProvider. Layanan biasanya terdaftar di start-up aplikasi dan ditambahkan ke IServiceCollection. Setelah semua layanan ditambahkan, Anda menggunakan BuildServiceProvider untuk membuat kontainer layanan.
- Injeksi layanan ke konstruktor kelas tempat layanan digunakan. Kerangka kerja mengambil tanggung jawab untuk membuat instans dependensi dan membuangnya ketika tidak lagi diperlukan.
Sebagai contoh, IMessageWriter
antarmuka mendefinisikan Write
metode :
namespace DependencyInjection.Example;
public interface IMessageWriter
{
void Write(string message);
}
Antarmuka ini diimplementasikan oleh jenis beton, MessageWriter
:
namespace DependencyInjection.Example;
public class MessageWriter : IMessageWriter
{
public void Write(string message)
{
Console.WriteLine($"MessageWriter.Write(message: \"{message}\")");
}
}
Kode sampel mendaftarkan IMessageWriter
layanan dengan jenis MessageWriter
beton . Metode ini AddSingleton mendaftarkan layanan dengan masa pakai singleton, masa pakai aplikasi. Masa pakai layanan dijelaskan nanti dalam artikel ini.
using DependencyInjection.Example;
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddHostedService<Worker>();
builder.Services.AddSingleton<IMessageWriter, MessageWriter>();
using IHost host = builder.Build();
host.Run();
Dalam kode sebelumnya, aplikasi sampel:
Membuat instans pembuat aplikasi host.
Mengonfigurasi layanan dengan mendaftarkan:
Worker
sebagai layanan yang dihosting. Untuk informasi selengkapnya, lihat Layanan Pekerja di .NET.- Antarmuka
IMessageWriter
sebagai layanan singleton dengan implementasi kelas yangMessageWriter
sesuai.
Membangun host dan menjalankannya.
Host berisi penyedia layanan injeksi dependensi. Ini juga berisi semua layanan relevan lainnya yang diperlukan untuk secara otomatis membuat Worker
instans dan memberikan implementasi yang sesuai IMessageWriter
sebagai argumen.
namespace DependencyInjection.Example;
public sealed class Worker(IMessageWriter messageWriter) : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
messageWriter.Write($"Worker running at: {DateTimeOffset.Now}");
await Task.Delay(1_000, stoppingToken);
}
}
}
Dengan menggunakan pola DI, layanan pekerja:
- Tidak menggunakan jenis
MessageWriter
beton , hanyaIMessageWriter
antarmuka yang mengimplementasikannya. Itu memudahkan untuk mengubah implementasi yang digunakan layanan pekerja tanpa memodifikasi layanan pekerja. - Tidak membuat instans .
MessageWriter
Instans dibuat oleh kontainer DI.
Implementasi IMessageWriter
antarmuka dapat ditingkatkan dengan menggunakan API pengelogan bawaan:
namespace DependencyInjection.Example;
public class LoggingMessageWriter(
ILogger<LoggingMessageWriter> logger) : IMessageWriter
{
public void Write(string message) =>
logger.LogInformation("Info: {Msg}", message);
}
Metode yang diperbarui AddSingleton
mendaftarkan implementasi baru IMessageWriter
:
builder.Services.AddSingleton<IMessageWriter, LoggingMessageWriter>();
Jenis HostApplicationBuilder (builder
) adalah bagian Microsoft.Extensions.Hosting
dari paket NuGet.
LoggingMessageWriter
tergantung pada ILogger<TCategoryName>, yang dimintanya di konstruktor. ILogger<TCategoryName>
adalah layanan yang disediakan kerangka kerja.
Tidak biasa menggunakan injeksi dependensi dengan cara berantai. Setiap dependensi yang diminta pada gilirannya meminta dependensinya 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).
Dengan terminologi injeksi dependensi, layanan:
- Biasanya merupakan objek yang menyediakan layanan ke objek lain, seperti
IMessageWriter
layanan. - Tidak terkait dengan layanan web, meskipun layanan dapat menggunakan layanan web.
Kerangka kerja ini menyediakan sistem pengelogan yang kuat. Implementasi IMessageWriter
yang ditunjukkan dalam contoh sebelumnya ditulis untuk menunjukkan DI dasar, bukan untuk menerapkan pengelogan. Sebagian besar aplikasi tidak perlu menulis pencatat. Kode berikut menunjukkan menggunakan pengelogan default, yang hanya mengharuskan Worker
didaftarkan sebagai layanan AddHostedServiceyang dihosting :
public sealed class Worker(ILogger<Worker> logger) : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
await Task.Delay(1_000, stoppingToken);
}
}
}
Menggunakan kode sebelumnya, tidak perlu memperbarui Program.cs, karena pengelogan disediakan oleh kerangka kerja.
Beberapa aturan penemuan konstruktor
Ketika jenis mendefinisikan lebih dari satu konstruktor, penyedia layanan memiliki logika untuk menentukan konstruktor mana yang akan digunakan. Konstruktor dengan parameter terbanyak di mana jenis dapat diselesaikan dipilih. Pertimbangkan layanan contoh C# berikut:
public class ExampleService
{
public ExampleService()
{
}
public ExampleService(ILogger<ExampleService> logger)
{
// omitted for brevity
}
public ExampleService(FooService fooService, BarService barService)
{
// omitted for brevity
}
}
Dalam kode sebelumnya, asumsikan bahwa pengelogan telah ditambahkan dan dapat diselesaikan dari penyedia layanan tetapi FooService
jenis dan BarService
tidak. Konstruktor dengan ILogger<ExampleService>
parameter digunakan untuk menyelesaikan ExampleService
instans. Meskipun ada konstruktor yang mendefinisikan lebih banyak parameter, FooService
jenis dan BarService
tidak dapat diselesaikan.
Jika ada ambiguitas saat menemukan konstruktor, pengecualian akan dilemparkan. Pertimbangkan layanan contoh C# berikut:
public class ExampleService
{
public ExampleService()
{
}
public ExampleService(ILogger<ExampleService> logger)
{
// omitted for brevity
}
public ExampleService(IOptions<ExampleOptions> options)
{
// omitted for brevity
}
}
Peringatan
Kode ExampleService
dengan parameter jenis DI-resolvable ambigu akan melemparkan pengecualian. Jangan lakukan iniāini dimaksudkan untuk menunjukkan apa yang dimaksudkan dengan "jenis yang dapat dipisahkan kembali ambigu".
Dalam contoh sebelumnya, ada tiga konstruktor. Konstruktor pertama tanpa parameter dan tidak memerlukan layanan dari penyedia layanan. Asumsikan bahwa pengelogan dan opsi telah ditambahkan ke kontainer DI dan merupakan layanan yang dapat diselesaikan. Ketika kontainer DI mencoba menyelesaikan ExampleService
jenisnya, kontainer tersebut akan melemparkan pengecualian, karena kedua konstruktor tersebut ambigu.
Anda dapat menghindari ambiguitas dengan menentukan konstruktor yang menerima kedua jenis yang dapat diselesaikan sebagai gantinya:
public class ExampleService
{
public ExampleService()
{
}
public ExampleService(
ILogger<ExampleService> logger,
IOptions<ExampleOptions> options)
{
// omitted for brevity
}
}
Mendaftarkan grup layanan dengan metode ekstensi
Microsoft Extensions menggunakan konvensi untuk mendaftarkan sekelompok layanan terkait. Konvensi ini menggunakan metode ekstensi tunggal Add{GROUP_NAME}
untuk mendaftarkan semua layanan yang diperlukan oleh fitur kerangka kerja. Misalnya, AddOptions metode ekstensi mendaftarkan semua layanan yang diperlukan untuk menggunakan opsi.
Layanan yang disediakan kerangka kerja
Saat menggunakan salah satu pola host atau penyusun aplikasi yang tersedia, default diterapkan dan layanan didaftarkan oleh kerangka kerja. Pertimbangkan beberapa pola host dan pembuat aplikasi paling populer:
- Host.CreateDefaultBuilder()
- Host.CreateApplicationBuilder()
- WebHost.CreateDefaultBuilder()
- WebApplication.CreateBuilder()
- WebAssemblyHostBuilder.CreateDefault
- MauiApp.CreateBuilder
Setelah membuat penyusun dari salah satu API ini, memiliki layanan yang IServiceCollection
ditentukan oleh kerangka kerja, tergantung pada bagaimana host dikonfigurasi. Untuk aplikasi berdasarkan templat .NET, kerangka kerja dapat mendaftarkan ratusan layanan.
Tabel berikut mencantumkan sampel kecil layanan terdaftar kerangka kerja ini:
Jenis Layanan | Seumur hidup |
---|---|
Microsoft.Extensions.DependencyInjection.IServiceScopeFactory | Singleton |
IHostApplicationLifetime | Singleton |
Microsoft.Extensions.Logging.ILogger<TCategoryName> | Singleton |
Microsoft.Extensions.Logging.ILoggerFactory | Singleton |
Microsoft.Extensions.ObjectPool.ObjectPoolProvider | Singleton |
Microsoft.Extensions.Options.IConfigureOptions<TOptions> | Perubahan sementara |
Microsoft.Extensions.Options.IOptions<TOptions> | Singleton |
System.Diagnostics.DiagnosticListener | Singleton |
System.Diagnostics.DiagnosticSource | Singleton |
Masa pakai layanan
Layanan dapat didaftarkan dengan salah satu masa pakai berikut:
Bagian berikut menjelaskan masing-masing masa pakai sebelumnya. Pilih masa pakai yang sesuai untuk setiap layanan terdaftar.
Perubahan sementara
Layanan masa pakai sementara dibuat setiap kali diminta dari kontainer layanan. Untuk mendaftarkan layanan sebagai sementara, panggil AddTransient.
Di aplikasi yang memproses permintaan, layanan sementara dibuang di akhir permintaan. Masa pakai ini menimbulkan alokasi per/permintaan, karena layanan diselesaikan dan dibangun setiap saat. Untuk informasi selengkapnya, lihat Panduan Injeksi Dependensi: Panduan yang dapat diubah untuk instans sementara dan bersama.
Cakupan
Untuk aplikasi web, masa pakai terlingkup menunjukkan bahwa layanan dibuat sekali per permintaan klien (koneksi). Daftarkan layanan terlingkup dengan AddScoped.
Di aplikasi yang memproses permintaan, layanan tercakup dibuang di akhir permintaan.
Saat menggunakan Entity Framework Core, AddDbContext metode ekstensi mendaftarkan DbContext
jenis dengan masa pakai terlingkup secara default.
Catatan
Jangan menyelesaikan layanan terlingkup dari singleton dan berhati-hatilah untuk tidak melakukannya secara tidak langsung, misalnya, melalui layanan sementara. Ini dapat menyebabkan layanan memiliki status yang salah saat memproses permintaan berikutnya. Tidak apa-apa untuk:
- Atasi layanan singleton dari layanan terlingkup atau sementara.
- Atasi layanan terlingkup dari layanan lain yang terlingkup atau sementara.
Secara default, di lingkungan pengembangan, menyelesaikan layanan dari layanan lain dengan masa pakai yang lebih lama memberikan pengecualian. Untuk informasi selengkapnya, lihat Validasi cakupan.
Singleton
Layanan seumur hidup Singleton dibuat baik:
- Pertama kali mereka diminta.
- Oleh pengembang, saat memberikan instans implementasi langsung ke kontainer. Pendekatan ini jarang diperlukan.
Setiap permintaan implementasi layanan berikutnya dari kontainer injeksi dependensi menggunakan instans yang sama. Jika aplikasi memerlukan perilaku singleton, izinkan kontainer layanan untuk mengelola masa pakai layanan. Jangan terapkan pola desain singleton dan berikan kode untuk membuang singleton. Layanan tidak boleh dibuang oleh kode yang menyelesaikan layanan dari kontainer. Jika jenis atau pabrik terdaftar sebagai singleton, kontainer akan membuang singleton secara otomatis.
Daftarkan layanan singleton dengan AddSingleton. Layanan singleton harus aman utas dan sering digunakan dalam layanan stateless.
Dalam aplikasi yang memproses permintaan, layanan singleton dibuang saat ServiceProvider dibuang pada pematian aplikasi. Karena memori tidak dirilis sampai aplikasi dimatikan, pertimbangkan penggunaan memori dengan layanan singleton.
Metode pendaftaran layanan
Kerangka kerja ini menyediakan metode ekstensi pendaftaran layanan yang berguna dalam skenario tertentu:
Metode | Otomatis object disposal |
Beberapa Implementasi |
Meneruskan arg |
---|---|---|---|
Add{LIFETIME}<{SERVICE}, {IMPLEMENTATION}>() Contoh: services.AddSingleton<IMyDep, MyDep>(); |
Ya | Ya | Tidak |
Add{LIFETIME}<{SERVICE}>(sp => new {IMPLEMENTATION}) Contoh: services.AddSingleton<IMyDep>(sp => new MyDep()); services.AddSingleton<IMyDep>(sp => new MyDep(99)); |
Ya | Ya | Ya |
Add{LIFETIME}<{IMPLEMENTATION}>() Contoh: services.AddSingleton<MyDep>(); |
Ya | No | Tidak |
AddSingleton<{SERVICE}>(new {IMPLEMENTATION}) Contoh: services.AddSingleton<IMyDep>(new MyDep()); services.AddSingleton<IMyDep>(new MyDep(99)); |
Tidak | Ya | Ya |
AddSingleton(new {IMPLEMENTATION}) Contoh: services.AddSingleton(new MyDep()); services.AddSingleton(new MyDep(99)); |
Tidak | No | Ya |
Untuk informasi selengkapnya tentang pembuangan jenis, lihat bagian Pembuangan layanan .
Mendaftarkan layanan hanya dengan jenis implementasi setara dengan mendaftarkan layanan tersebut dengan implementasi dan jenis layanan yang sama. Sebagai contoh, perhatikan kode berikut:
services.AddSingleton<ExampleService>();
Ini setara dengan mendaftarkan layanan dengan layanan dan implementasi jenis yang sama:
services.AddSingleton<ExampleService, ExampleService>();
Kesetaraan inilah sebabnya beberapa implementasi layanan tidak dapat didaftarkan menggunakan metode yang tidak mengambil jenis layanan eksplisit. Metode ini dapat mendaftarkan beberapa instans layanan, tetapi semuanya akan memiliki jenis implementasi yang sama.
Salah satu metode pendaftaran layanan di atas dapat digunakan untuk mendaftarkan beberapa instans layanan dengan jenis layanan yang sama. Dalam contoh berikut, AddSingleton
dipanggil dua kali dengan IMessageWriter
sebagai jenis layanan. Panggilan kedua untuk AddSingleton
mengambil alih yang sebelumnya ketika diselesaikan sebagai IMessageWriter
dan ditambahkan ke yang sebelumnya ketika beberapa layanan diselesaikan melalui IEnumerable<IMessageWriter>
. Layanan muncul dalam urutan terdaftar ketika diselesaikan melalui IEnumerable<{SERVICE}>
.
using ConsoleDI.IEnumerableExample;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddSingleton<IMessageWriter, ConsoleMessageWriter>();
builder.Services.AddSingleton<IMessageWriter, LoggingMessageWriter>();
builder.Services.AddSingleton<ExampleService>();
using IHost host = builder.Build();
_ = host.Services.GetService<ExampleService>();
await host.RunAsync();
Kode sumber sampel sebelumnya mendaftarkan dua implementasi dari IMessageWriter
.
using System.Diagnostics;
namespace ConsoleDI.IEnumerableExample;
public sealed class ExampleService
{
public ExampleService(
IMessageWriter messageWriter,
IEnumerable<IMessageWriter> messageWriters)
{
Trace.Assert(messageWriter is LoggingMessageWriter);
var dependencyArray = messageWriters.ToArray();
Trace.Assert(dependencyArray[0] is ConsoleMessageWriter);
Trace.Assert(dependencyArray[1] is LoggingMessageWriter);
}
}
ExampleService
menentukan dua parameter konstruktor; satu IMessageWriter
, dan IEnumerable<IMessageWriter>
. Tunggal IMessageWriter
adalah implementasi terakhir yang telah didaftarkan, sedangkan IEnumerable<IMessageWriter>
mewakili semua implementasi terdaftar.
Kerangka kerja ini juga menyediakan TryAdd{LIFETIME}
metode ekstensi, yang mendaftarkan layanan hanya jika belum ada implementasi yang terdaftar.
Dalam contoh berikut, panggilan untuk AddSingleton
mendaftar ConsoleMessageWriter
sebagai implementasi untuk IMessageWriter
. Panggilan ke TryAddSingleton
tidak berpengaruh karena IMessageWriter
sudah memiliki implementasi terdaftar:
services.AddSingleton<IMessageWriter, ConsoleMessageWriter>();
services.TryAddSingleton<IMessageWriter, LoggingMessageWriter>();
tidak TryAddSingleton
berpengaruh, karena sudah ditambahkan dan "coba" akan gagal. Akan ExampleService
menegaskan hal-hal berikut:
public class ExampleService
{
public ExampleService(
IMessageWriter messageWriter,
IEnumerable<IMessageWriter> messageWriters)
{
Trace.Assert(messageWriter is ConsoleMessageWriter);
Trace.Assert(messageWriters.Single() is ConsoleMessageWriter);
}
}
Untuk informasi selengkapnya, lihat:
Metode TryAddEnumerable(ServiceDescriptor) mendaftarkan layanan hanya jika belum ada implementasi dengan jenis yang sama. Beberapa layanan diselesaikan melalui IEnumerable<{SERVICE}>
. Saat mendaftarkan layanan, tambahkan instans jika salah satu jenis yang sama belum ditambahkan. Penulis pustaka menggunakan TryAddEnumerable
untuk menghindari mendaftarkan beberapa salinan implementasi dalam kontainer.
Dalam contoh berikut, panggilan pertama untuk TryAddEnumerable
mendaftar MessageWriter
sebagai implementasi untuk IMessageWriter1
. Panggilan kedua mendaftar MessageWriter
untuk IMessageWriter2
. Panggilan ketiga tidak berpengaruh karena IMessageWriter1
sudah memiliki implementasi terdaftar dari MessageWriter
:
public interface IMessageWriter1 { }
public interface IMessageWriter2 { }
public class MessageWriter : IMessageWriter1, IMessageWriter2
{
}
services.TryAddEnumerable(ServiceDescriptor.Singleton<IMessageWriter1, MessageWriter>());
services.TryAddEnumerable(ServiceDescriptor.Singleton<IMessageWriter2, MessageWriter>());
services.TryAddEnumerable(ServiceDescriptor.Singleton<IMessageWriter1, MessageWriter>());
Pendaftaran layanan umumnya independen pesanan kecuali saat mendaftarkan beberapa implementasi dengan jenis yang sama.
IServiceCollection
adalah kumpulan ServiceDescriptor objek. Contoh berikut menunjukkan cara mendaftarkan layanan dengan membuat dan menambahkan ServiceDescriptor
:
string secretKey = Configuration["SecretKey"];
var descriptor = new ServiceDescriptor(
typeof(IMessageWriter),
_ => new DefaultMessageWriter(secretKey),
ServiceLifetime.Transient);
services.Add(descriptor);
Metode bawaan Add{LIFETIME}
menggunakan pendekatan yang sama. Misalnya, lihat kode sumber AddScoped.
Perilaku injeksi konstruktor
Layanan dapat diselesaikan dengan menggunakan:
- IServiceProvider
- ActivatorUtilities:
- Membuat objek yang tidak terdaftar dalam kontainer.
- Digunakan dengan beberapa fitur kerangka kerja.
Konstruktor dapat menerima argumen yang tidak disediakan oleh injeksi dependensi, tetapi argumen harus menetapkan nilai default.
Ketika layanan diselesaikan oleh IServiceProvider
atau ActivatorUtilities
, injeksi konstruktor memerlukan konstruktor publik .
Ketika layanan diselesaikan oleh ActivatorUtilities
, injeksi konstruktor mengharuskan hanya satu konstruktor yang berlaku yang ada. Kelebihan beban konstruktor didukung, tetapi hanya satu kelebihan beban yang dapat ada yang argumennya semuanya dapat dipenuhi oleh injeksi dependensi.
Validasi cakupan
Saat aplikasi berjalan di Development
lingkungan dan memanggil CreateApplicationBuilder untuk membangun host, penyedia layanan default melakukan pemeriksaan untuk memverifikasi bahwa:
- Layanan terlingkup tidak diselesaikan dari penyedia layanan akar.
- Layanan terlingkup tidak disuntikkan ke dalam singleton.
Penyedia layanan akar dibuat ketika BuildServiceProvider dipanggil. Masa pakai penyedia layanan akar sesuai dengan masa pakai aplikasi saat penyedia mulai dengan aplikasi dan dibuang saat aplikasi dimatikan.
Layanan tercakup dibuang oleh kontainer yang membuatnya. Jika layanan tercakup dibuat dalam kontainer akar, masa pakai layanan secara efektif dipromosikan ke singleton karena hanya dibuang oleh kontainer akar saat aplikasi dimatikan. Memvalidasi cakupan layanan menangkap situasi ini ketika BuildServiceProvider
dipanggil.
Skenario cakupan
IServiceScopeFactory selalu terdaftar sebagai singleton, tetapi IServiceProvider dapat bervariasi berdasarkan masa pakai kelas yang berisi. Misalnya, jika Anda menyelesaikan layanan dari cakupan, dan salah satu layanan tersebut mengambil IServiceProvider, itu akan menjadi instans terlingkup.
Untuk mencapai layanan cakupan dalam implementasi IHostedService, seperti BackgroundService, jangan menyuntikkan dependensi layanan melalui injeksi konstruktor. Sebagai gantinya, masukkan IServiceScopeFactory, buat cakupan, lalu atasi dependensi dari cakupan untuk menggunakan masa pakai layanan yang sesuai.
namespace WorkerScope.Example;
public sealed class Worker(
ILogger<Worker> logger,
IServiceScopeFactory serviceScopeFactory)
: BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
using (IServiceScope scope = serviceScopeFactory.CreateScope())
{
try
{
logger.LogInformation(
"Starting scoped work, provider hash: {hash}.",
scope.ServiceProvider.GetHashCode());
var store = scope.ServiceProvider.GetRequiredService<IObjectStore>();
var next = await store.GetNextAsync();
logger.LogInformation("{next}", next);
var processor = scope.ServiceProvider.GetRequiredService<IObjectProcessor>();
await processor.ProcessAsync(next);
logger.LogInformation("Processing {name}.", next.Name);
var relay = scope.ServiceProvider.GetRequiredService<IObjectRelay>();
await relay.RelayAsync(next);
logger.LogInformation("Processed results have been relayed.");
var marked = await store.MarkAsync(next);
logger.LogInformation("Marked as processed: {next}", marked);
}
finally
{
logger.LogInformation(
"Finished scoped work, provider hash: {hash}.{nl}",
scope.ServiceProvider.GetHashCode(), Environment.NewLine);
}
}
}
}
}
Dalam kode sebelumnya, saat aplikasi sedang berjalan, layanan latar belakang:
- Tergantung pada IServiceScopeFactory.
- IServiceScope Membuat untuk menyelesaikan layanan tambahan.
- Menyelesaikan layanan terlingkup untuk dikonsumsi.
- Bekerja pada pemrosesan objek dan kemudian menyampaikannya, dan akhirnya menandainya sebagai diproses.
Dari kode sumber sampel, Anda dapat melihat bagaimana implementasi IHostedService dapat memperoleh manfaat dari masa pakai layanan terlingkup.
Layanan utama
Dimulai dengan .NET 8, ada dukungan untuk pendaftaran layanan dan pencarian berdasarkan kunci, yang berarti dimungkinkan untuk mendaftarkan beberapa layanan dengan kunci yang berbeda, dan menggunakan kunci ini untuk pencarian.
Misalnya, pertimbangkan kasus di mana Anda memiliki implementasi antarmuka IMessageWriter
yang berbeda : MemoryMessageWriter
dan QueueMessageWriter
.
Anda dapat mendaftarkan layanan ini menggunakan kelebihan beban metode pendaftaran layanan (terlihat sebelumnya) yang mendukung kunci sebagai parameter:
services.AddKeyedSingleton<IMessageWriter, MemoryMessageWriter>("memory");
services.AddKeyedSingleton<IMessageWriter, QueueMessageWriter>("queue");
key
tidak terbatas string
pada , itu bisa menjadi apa pun yang object
Anda inginkan, selama jenis mengimplementasikan Equals
dengan benar .
Di konstruktor kelas yang menggunakan IMessageWriter
, Anda menambahkan FromKeyedServicesAttribute untuk menentukan kunci layanan untuk mengatasi:
public class ExampleService
{
public ExampleService(
[FromKeyedServices("queue")] IMessageWriter writer)
{
// Omitted for brevity...
}
}
Lihat juga
- Memahami dasar-dasar injeksi dependensi di .NET
- Gunakan injeksi dependensi di .NET
- Panduan injeksi dependensi
- Injeksi dependensi di ASP.NET Core
- Pola Konferensi NDC untuk pengembangan aplikasi DI
- Prinsip dependensi eksplisit
- Inversi kontainer kontrol dan pola injeksi dependensi (Martin Fowler)
- Bug DI harus dibuat di repositori github.com/dotnet/extensions