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, , IExampleTransientService
IExampleScopedService
, 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
diProgram
kelas atauStartup
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.