Bagikan melalui


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 oleh Worker kelas. Dalam proyek besar dengan beberapa kelas tergantung pada MessageWriter, 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 MessageWriterbeton . 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 yang MessageWriter 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 MessageWriterbeton , hanya IMessageWriter 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:

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:

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 IMessageWriteryang 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 stringpada , itu bisa menjadi apa pun yang object Anda inginkan, selama jenis mengimplementasikan Equalsdengan 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