Bagikan melalui


Tutorial: Menggunakan injeksi dependensi di .NET

Tutorial ini menunjukkan cara menggunakan injeksi dependensi (DI) di .NET. Dengan Microsoft Extensions, DI dikelola melalui penambahan layanan dan pengaturan konfigurasi dalam IServiceCollection. Antarmuka IHost mengekspos instans IServiceProvider, yang bertindak sebagai kontainer dari semua layanan terdaftar.

Dalam tutorial ini, Anda mempelajari cara:

  • Membuat aplikasi konsol .NET yang menggunakan injeksi dependensi
  • Bangun dan konfigurasikan Host Generik
  • Menulis beberapa antarmuka dan implementasi yang sesuai
  • Gunakan masa pakai layanan dan cakupan untuk DI

Prasyarat

  • .NET Core 3.1 SDK atau yang lebih baru.
  • Keakraban dengan membuat aplikasi .NET baru dan menginstal paket NuGet.

Membuat aplikasi konsol baru

Menggunakan perintah baru dotnet atau wizard proyek baru IDE, buat aplikasi konsol .NET baru bernama ConsoleDI.. Tambahkan paket Microsoft.Extensions.Hosting NuGet ke proyek.

File proyek aplikasi konsol baru Anda harus menyerupai yang berikut ini:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>true</ImplicitUsings>
    <RootNamespace>ConsoleDI.Example</RootNamespace>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="9.0.2" />
    <PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.2" />
  </ItemGroup>

</Project>

Penting

Dalam contoh ini, paket Microsoft.Extensions.Hosting NuGet diperlukan untuk membangun dan menjalankan aplikasi. Beberapa metapackage mungkin berisi paket Microsoft.Extensions.Hosting, dalam hal ini referensi paket eksplisit tidak diperlukan.

Menambahkan antarmuka

Dalam aplikasi sampel ini, Anda akan mempelajari bagaimana injeksi dependensi menangani masa pakai layanan. Anda akan membuat beberapa antarmuka yang mewakili masa pakai layanan yang berbeda. Tambahkan antarmuka berikut ke direktori akar proyek:

IReportServiceLifetime.cs

using Microsoft.Extensions.DependencyInjection;

namespace ConsoleDI.Example;

public interface IReportServiceLifetime
{
    Guid Id { get; }

    ServiceLifetime Lifetime { get; }
}

Antarmuka IReportServiceLifetime mendefinisikan:

  • Properti Guid Id yang mewakili identifikasi unik layanan.
  • Properti ServiceLifetime yang mewakili masa pakai layanan.

IExampleTransientService.cs

using Microsoft.Extensions.DependencyInjection;

namespace ConsoleDI.Example;

public interface IExampleTransientService : IReportServiceLifetime
{
    ServiceLifetime IReportServiceLifetime.Lifetime => ServiceLifetime.Transient;
}

IExampleScopedService.cs

using Microsoft.Extensions.DependencyInjection;

namespace ConsoleDI.Example;

public interface IExampleScopedService : IReportServiceLifetime
{
    ServiceLifetime IReportServiceLifetime.Lifetime => ServiceLifetime.Scoped;
}

IExampleSingletonService.cs

using Microsoft.Extensions.DependencyInjection;

namespace ConsoleDI.Example;

public interface IExampleSingletonService : IReportServiceLifetime
{
    ServiceLifetime IReportServiceLifetime.Lifetime => ServiceLifetime.Singleton;
}

Semua subinterface IReportServiceLifetime secara eksplisit mengimplementasikan IReportServiceLifetime.Lifetime dengan default. Misalnya, IExampleTransientService secara eksplisit menerapkan IReportServiceLifetime.Lifetime dengan nilai ServiceLifetime.Transient.

Menambahkan implementasi default

Semua contoh implementasi menginisialisasi properti Id mereka dengan hasil Guid.NewGuid(). Tambahkan kelas implementasi default berikut untuk berbagai layanan ke direktori akar proyek:

ExampleTransientService.cs

namespace ConsoleDI.Example;

internal sealed class ExampleTransientService : IExampleTransientService
{
    Guid IReportServiceLifetime.Id { get; } = Guid.NewGuid();
}

ExampleScopedService.cs

namespace ConsoleDI.Example;

internal sealed class ExampleScopedService : IExampleScopedService
{
    Guid IReportServiceLifetime.Id { get; } = Guid.NewGuid();
}

ExampleSingletonService.cs

namespace ConsoleDI.Example;

internal sealed class ExampleSingletonService : IExampleSingletonService
{
    Guid IReportServiceLifetime.Id { get; } = Guid.NewGuid();
}

Setiap implementasi didefinisikan sebagai internal sealed dan mengimplementasikan antarmuka yang sesuai. Mereka tidak diwajibkan menjadi internal atau sealed, namun biasa memperlakukan implementasi sebagai internal untuk menghindari kebocoran jenis implementasi kepada pihak luar. Selain itu, karena setiap jenis tidak akan diperpanjang, ditandai sebagai sealed. Misalnya, ExampleSingletonService menerapkan IExampleSingletonService.

Menambahkan layanan yang memerlukan DI

Tambahkan kelas pelapor masa pakai layanan berikut, yang berfungsi sebagai layanan ke aplikasi konsol:

ServiceLifetimeReporter.cs

namespace ConsoleDI.Example;

internal sealed class ServiceLifetimeReporter(
    IExampleTransientService transientService,
    IExampleScopedService scopedService,
    IExampleSingletonService singletonService)
{
    public void ReportServiceLifetimeDetails(string lifetimeDetails)
    {
        Console.WriteLine(lifetimeDetails);

        LogService(transientService, "Always different");
        LogService(scopedService, "Changes only with lifetime");
        LogService(singletonService, "Always the same");
    }

    private static void LogService<T>(T service, string message)
        where T : IReportServiceLifetime =>
        Console.WriteLine(
            $"    {typeof(T).Name}: {service.Id} ({message})");
}

ServiceLifetimeReporter mendefinisikan konstruktor yang memerlukan setiap antarmuka layanan yang disebutkan di atas, yaitu, IExampleTransientService, IExampleScopedService, dan IExampleSingletonService. Objek mengekspos satu metode yang memungkinkan konsumen melaporkan layanan dengan parameter lifetimeDetails tertentu. Saat dipanggil, metode ReportServiceLifetimeDetails mencatat pengidentifikasi unik setiap layanan dengan pesan durasi layanan. Pesan log membantu memvisualisasikan masa pakai layanan.

Mendaftarkan layanan untuk DI

Perbarui Program.cs dengan kode berikut:

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using ConsoleDI.Example;

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

builder.Services.AddTransient<IExampleTransientService, ExampleTransientService>();
builder.Services.AddScoped<IExampleScopedService, ExampleScopedService>();
builder.Services.AddSingleton<IExampleSingletonService, ExampleSingletonService>();
builder.Services.AddTransient<ServiceLifetimeReporter>();

using IHost host = builder.Build();

ExemplifyServiceLifetime(host.Services, "Lifetime 1");
ExemplifyServiceLifetime(host.Services, "Lifetime 2");

await host.RunAsync();

static void ExemplifyServiceLifetime(IServiceProvider hostProvider, string lifetime)
{
    using IServiceScope serviceScope = hostProvider.CreateScope();
    IServiceProvider provider = serviceScope.ServiceProvider;
    ServiceLifetimeReporter logger = provider.GetRequiredService<ServiceLifetimeReporter>();
    logger.ReportServiceLifetimeDetails(
        $"{lifetime}: Call 1 to provider.GetRequiredService<ServiceLifetimeReporter>()");

    Console.WriteLine("...");

    logger = provider.GetRequiredService<ServiceLifetimeReporter>();
    logger.ReportServiceLifetimeDetails(
        $"{lifetime}: Call 2 to provider.GetRequiredService<ServiceLifetimeReporter>()");

    Console.WriteLine();
}

Setiap metode ekstensi services.Add{LIFETIME}<{SERVICE}> menambahkan layanan (dan berpotensi mengonfigurasi). Sebaiknya aplikasi mengikuti konvensi ini. Jangan menempatkan metode ekstensi di namespace Microsoft.Extensions.DependencyInjection kecuali Anda menulis paket Microsoft resmi. Metode ekstensi yang ditentukan dalam namespace Microsoft.Extensions.DependencyInjection:

  • Ditampilkan dalam IntelliSense tanpa memerlukan arahan tambahan using.
  • Kurangi jumlah arahan using yang diperlukan di kelas Program atau Startup tempat metode ekstensi ini biasanya dipanggil.

Aplikasi:

  • Membuat instans IHostBuilder dengan pengaturan penyusun host .
  • Mengonfigurasi layanan dan menambahkannya dengan masa pakai layanan yang sesuai.
  • Memanggil Build() dan menetapkan instans IHost.
  • Memanggil ExemplifyScoping, dengan meneruskan IHost.Services.

Kesimpulan

Dalam aplikasi sampel ini, Anda membuat beberapa antarmuka dan implementasi yang sesuai. Masing-masing layanan ini diidentifikasi secara unik dan dipasangkan dengan ServiceLifetime. Aplikasi sampel menunjukkan pendaftaran implementasi layanan ke sebuah antarmuka, dan cara mendaftarkan kelas langsung tanpa antarmuka pendukung. Aplikasi sampel kemudian menunjukkan bagaimana dependensi yang didefinisikan sebagai parameter konstruktor diselesaikan pada waktu proses.

Saat Anda menjalankan aplikasi, aplikasi akan menampilkan output yang mirip dengan yang berikut ini:

// Sample output:
// Lifetime 1: Call 1 to provider.GetRequiredService<ServiceLifetimeReporter>()
//     IExampleTransientService: d08a27fa-87d2-4a06-98d7-2773af886125 (Always different)
//     IExampleScopedService: 402c83c9-b4ed-4be1-b78c-86be1b1d908d (Changes only with lifetime)
//     IExampleSingletonService: a61f1ff4-0b14-4508-bd41-21d852484a7b (Always the same)
// ...
// Lifetime 1: Call 2 to provider.GetRequiredService<ServiceLifetimeReporter>()
//     IExampleTransientService: b43d68fb-2c7b-4a9b-8f02-fc507c164326 (Always different)
//     IExampleScopedService: 402c83c9-b4ed-4be1-b78c-86be1b1d908d (Changes only with lifetime)
//     IExampleSingletonService: a61f1ff4-0b14-4508-bd41-21d852484a7b (Always the same)
// 
// Lifetime 2: Call 1 to provider.GetRequiredService<ServiceLifetimeReporter>()
//     IExampleTransientService: f3856b59-ab3f-4bbd-876f-7bab0013d392 (Always different)
//     IExampleScopedService: bba80089-1157-4041-936d-e96d81dd9d1c (Changes only with lifetime)
//     IExampleSingletonService: a61f1ff4-0b14-4508-bd41-21d852484a7b (Always the same)
// ...
// Lifetime 2: Call 2 to provider.GetRequiredService<ServiceLifetimeReporter>()
//     IExampleTransientService: a8015c6a-08cd-4799-9ec3-2f2af9cbbfd2 (Always different)
//     IExampleScopedService: bba80089-1157-4041-936d-e96d81dd9d1c (Changes only with lifetime)
//     IExampleSingletonService: a61f1ff4-0b14-4508-bd41-21d852484a7b (Always the same)

Dari output aplikasi, Anda dapat melihat bahwa:

  • Transient layanan selalu berbeda, instans baru dibuat dengan setiap pengambilan layanan.
  • Scoped service hanya berubah dengan cakupan baru, tetapi tetap menjadi instans yang sama dalam cakupan.
  • Singleton layanan selalu sama, sebuah instans baru hanya dibuat satu kali.

Lihat juga