Bagikan melalui


Tutorial: Gunakan injeksi Dependensi di .NET

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

Dalam tutorial ini, Anda akan mempelajari cara:

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

Prasyarat

  • .NET Core 3.1 SDK atau yang lebih baru.
  • Memahami 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.Example. Tambahkan paket NuGet Microsoft.Extensions.Hosting 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.Hosting" Version="8.0.0" />
  </ItemGroup>

</Project>

Penting

Dalam contoh ini, paket NuGet Microsoft.Extensions.Hosting diperlukan untuk membangun dan menjalankan aplikasi. Beberapa metapackage mungkin berisi Microsoft.Extensions.Hosting paket, 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 pengidentifikasi unik layanan.
  • Properti ServiceLifetime yang mewakili masa pakai layanan.

AkuExampleTransientService.cs

using Microsoft.Extensions.DependencyInjection;

namespace ConsoleDI.Example;

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

AkuExampleScopedService.cs

using Microsoft.Extensions.DependencyInjection;

namespace ConsoleDI.Example;

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

AkuExampleSingletonService.cs

using Microsoft.Extensions.DependencyInjection;

namespace ConsoleDI.Example;

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

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

Menambahkan implementasi default

Contoh implementasi semua menginisialisasi properti mereka Id 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. Misalnya, ExampleSingletonService mengimplementasikan IExampleSingletonService.

Menambahkan layanan yang memerlukan DI

Tambahkan kelas reporter seumur hidup layanan berikut, yang bertindak 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})");
}

mendefinisikan ServiceLifetimeReporter konstruktor yang memerlukan masing-masing antarmuka layanan yang disebutkan di atas, yaitu, , IExampleTransientServiceIExampleScopedService, dan IExampleSingletonService. Objek mengekspos satu metode yang memungkinkan konsumen melaporkan layanan dengan parameter tertentu lifetimeDetails . Saat dipanggil, metode mencatat ReportServiceLifetimeDetails pengidentifikasi unik setiap layanan dengan pesan seumur hidup 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 (dan berpotensi mengonfigurasi) layanan. Sebaiknya aplikasi mengikuti konvensi ini. Jangan menempatkan metode ekstensi di Microsoft.Extensions.DependencyInjection namespace kecuali Anda menulis paket Microsoft resmi. Metode ekstensi yang ditentukan dalam Microsoft.Extensions.DependencyInjection namespace layanan:

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

Aplikasi:

  • Membuat instans IHostBuilder dengan pengaturan pembuat host.
  • Mengonfigurasi layanan dan menambahkannya dengan masa pakai layanan yang sesuai.
  • Memanggil Build() dan menetapkan instans IHost.
  • Memanggil ExemplifyScoping, melewati di 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 terhadap antarmuka, dan cara mendaftarkan kelas murni tanpa antarmuka cadangan. 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 layanan hanya berubah dengan cakupan baru, tetapi merupakan instans yang sama dalam cakupan.
  • Singleton layanan selalu sama, instans baru hanya dibuat sekali.

Baca juga