Memahami injeksi dependensi
aplikasi ASP.NET Core sering kali perlu mengakses layanan yang sama di beberapa komponen. Misalnya, beberapa komponen mungkin perlu mengakses layanan yang mengambil data dari database. ASP.NET Core menggunakan kontainer injeksi dependensi bawaan (DI) untuk mengelola layanan yang digunakan aplikasi.
Injeksi Dependensi dan Inversi Kontrol (IoC)
Pola injeksi dependensi adalah bentuk Inversion of Control (IoC). Dalam pola injeksi dependensi, komponen menerima dependensinya dari sumber eksternal daripada membuatnya sendiri. Pola ini memisahkan kode dari dependensi, yang membuat kode lebih mudah diuji dan dikelola.
Pertimbangkan file Program.cs berikut:
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using MyApp.Services;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton<PersonService>();
var app = builder.Build();
app.MapGet("/",
(PersonService personService) =>
{
return $"Hello, {personService.GetPersonName()}!";
}
);
app.Run();
Dan file PersonService.cs berikut:
namespace MyApp.Services;
public class PersonService
{
public string GetPersonName()
{
return "John Doe";
}
}
Untuk memahami kode, mulailah dengan kode yang disorot app.MapGet . Kode ini memetakan permintaan HTTP GET untuk URL akar (/) ke delegasi yang mengembalikan pesan salam. Tanda tangan delegasi mendefinisikan PersonService parameter bernama personService. Saat aplikasi berjalan dan klien meminta URL akar, kode di dalam delegasi bergantung pada PersonService layanan untuk mendapatkan beberapa teks untuk disertakan dalam pesan salam.
Di mana delegasi mendapatkan PersonService layanan? Ini secara implisit disediakan oleh kontainer layanan. Baris yang disorot builder.Services.AddSingleton<PersonService>() memberi tahu kontainer layanan untuk membuat instans PersonService baru kelas saat aplikasi dimulai, dan untuk menyediakan instans tersebut ke komponen apa pun yang membutuhkannya.
Komponen apa pun yang membutuhkan PersonService layanan dapat mendeklarasikan parameter jenis PersonService dalam tanda tangan delegasinya. Kontainer layanan akan secara otomatis menyediakan instans PersonService kelas saat komponen dibuat. Delegasi tidak membuat instans itu PersonService sendiri, hanya menggunakan instans yang disediakan kontainer layanan.
Antarmuka dan injeksi dependensi
Untuk menghindari dependensi pada implementasi layanan tertentu, Anda dapat mengonfigurasi layanan untuk antarmuka tertentu dan kemudian hanya bergantung pada antarmuka. Pendekatan ini memberi Anda fleksibilitas untuk menukar implementasi layanan, yang membuat kode lebih dapat diuji dan lebih mudah dipertahankan.
Pertimbangkan antarmuka untuk PersonService kelas :
public interface IPersonService
{
string GetPersonName();
}
Antarmuka ini mendefinisikan metode tunggal, GetPersonName, yang mengembalikan string. Kelas ini PersonService mengimplementasikan IPersonService antarmuka:
internal sealed class PersonService : IPersonService
{
public string GetPersonName()
{
return "John Doe";
}
}
Alih-alih mendaftarkan kelas secara PersonService langsung, Anda dapat mendaftarkannya sebagai implementasi IPersonService antarmuka:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton<IPersonService, PersonService>();
var app = builder.Build();
app.MapGet("/",
(IPersonService personService) =>
{
return $"Hello, {personService.GetPersonName()}!";
}
);
app.Run();
Contoh ini Program.cs berbeda dari contoh sebelumnya dengan dua cara:
-
PersonServiceInstans terdaftar sebagai implementasiIPersonServiceantarmuka (bukan mendaftarkan kelas secaraPersonServicelangsung). - Tanda tangan delegasi sekarang mengharapkan
IPersonServiceparameter alih-alihPersonServiceparameter.
Saat aplikasi berjalan dan klien meminta URL akar, kontainer layanan menyediakan instans PersonService kelas karena terdaftar sebagai implementasi IPersonService antarmuka.
Petunjuk / Saran
Anggap IPersonService saja sebagai kontrak. Ini mendefinisikan metode dan properti yang harus dimiliki implementasi. Delegasi menginginkan instans IPersonService. Ini tidak peduli sama sekali tentang implementasi yang mendasar, hanya saja instans memiliki metode dan properti yang ditentukan dalam kontrak.
Pengujian dengan injeksi dependensi
Menggunakan antarmuka memudahkan pengujian komponen dalam isolasi. Anda dapat membuat implementasi tiruan IPersonService antarmuka untuk tujuan pengujian. Ketika Anda mendaftarkan implementasi tiruan dalam pengujian, kontainer layanan menyediakan implementasi tiruan ke komponen yang sedang diuji.
Misalnya, katakanlah bahwa alih-alih mengembalikan string yang dikodekan secara permanen, GetPersonName metode di PersonService kelas mengambil nama dari database. Untuk menguji komponen yang bergantung pada IPersonService antarmuka, Anda dapat membuat implementasi tiruan antarmuka IPersonService yang mengembalikan string yang dikodekan secara permanen. Komponen yang sedang diuji tidak tahu perbedaan antara implementasi nyata dan implementasi tiruan.
Misalkan juga aplikasi Anda memetakan titik akhir API yang mengembalikan pesan salam. Titik akhir tergantung pada IPersonService antarmuka untuk mendapatkan nama orang yang akan disambut. Kode yang mendaftarkan IPersonService layanan dan memetakan titik akhir API mungkin terlihat seperti ini:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton<IPersonService, PersonService>();
var app = builder.Build();
app.MapGet("/", (IPersonService personService) =>
{
return $"Hello, {personService.GetPersonName()}!";
});
app.Run();
Ini mirip dengan contoh sebelumnya dengan IPersonService. Delegasi mengharapkan IPersonService parameter, yang disediakan kontainer layanan. Seperti disebutkan sebelumnya, asumsikan bahwa PersonService yang mengimplementasikan antarmuka mengambil nama orang untuk disambut dari database.
Sekarang pertimbangkan pengujian XUnit berikut yang menguji titik akhir API yang sama:
Petunjuk / Saran
Jangan khawatir jika Anda tidak terbiasa dengan XUnit atau Moq. Menulis pengujian unit berada di luar cakupan modul ini. Contoh ini hanya untuk menggambarkan bagaimana injeksi dependensi dapat digunakan dalam pengujian.
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.Extensions.DependencyInjection;
using Moq;
using MyWebApp;
using System.Net;
public class GreetingApiTests : IClassFixture<WebApplicationFactory<Program>>
{
private readonly WebApplicationFactory<Program> _factory;
public GreetingApiTests(WebApplicationFactory<Program> factory)
{
_factory = factory;
}
[Fact]
public async Task GetGreeting_ReturnsExpectedGreeting()
{
//Arrange
var mockPersonService = new Mock<IPersonService>();
mockPersonService.Setup(service => service.GetPersonName()).Returns("Jane Doe");
var client = _factory.WithWebHostBuilder(builder =>
{
builder.ConfigureServices(services =>
{
services.AddSingleton(mockPersonService.Object);
});
}).CreateClient();
// Act
var response = await client.GetAsync("/");
var responseString = await response.Content.ReadAsStringAsync();
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal("Hello, Jane Doe!", responseString);
}
}
Tes sebelumnya:
- Membuat implementasi tiruan antarmuka
IPersonServiceyang mengembalikan string yang dikodekan secara permanen. - Mendaftarkan implementasi tiruan dengan kontainer layanan.
- Membuat klien HTTP untuk membuat permintaan ke titik akhir API.
- Menegaskan bahwa respons dari titik akhir API seperti yang diharapkan.
Tes tidak peduli bagaimana PersonService kelas mendapatkan nama orang yang akan disambut. Ini hanya peduli bahwa nama disertakan dalam pesan salam. Pengujian ini menggunakan implementasi tiruan antarmuka IPersonService untuk mengisolasi komponen yang sedang diuji dari implementasi nyata layanan.