Catatan
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba masuk atau mengubah direktori.
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba mengubah direktori.
Tutorial ini menunjukkan cara menyuntikkan dependensi ke pengontrol API Web ASP.NET Anda.
Versi perangkat lunak yang digunakan dalam tutorial
- WEB API 2
- Blok Aplikasi Unity
- Entity Framework 6 (versi 5 juga berfungsi)
Apa itu Injeksi Dependensi?
Dependensi adalah objek apa pun yang diperlukan objek lain. Misalnya, adalah umum untuk menentukan repositori yang menangani akses data. Mari kita ilustrasikan dengan contoh. Pertama, kita akan menentukan model domain:
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
}
Berikut adalah kelas repositori sederhana yang menyimpan item dalam database, menggunakan Kerangka Kerja Entitas.
public class ProductsContext : DbContext
{
public ProductsContext()
: base("name=ProductsContext")
{
}
public DbSet<Product> Products { get; set; }
}
public class ProductRepository : IDisposable
{
private ProductsContext db = new ProductsContext();
public IEnumerable<Product> GetAll()
{
return db.Products;
}
public Product GetByID(int id)
{
return db.Products.FirstOrDefault(p => p.Id == id);
}
public void Add(Product product)
{
db.Products.Add(product);
db.SaveChanges();
}
protected void Dispose(bool disposing)
{
if (disposing)
{
if (db != null)
{
db.Dispose();
db = null;
}
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
Sekarang mari kita tentukan pengontrol API Web yang mendukung permintaan GET untuk Product
entitas. (Saya meninggalkan POST dan metode lain untuk kesederhanaan.) Berikut adalah upaya pertama:
public class ProductsController : ApiController
{
// This line of code is a problem!
ProductRepository _repository = new ProductRepository();
public IEnumerable<Product> Get()
{
return _repository.GetAll();
}
public IHttpActionResult Get(int id)
{
var product = _repository.GetByID(id);
if (product == null)
{
return NotFound();
}
return Ok(product);
}
}
Perhatikan bahwa kelas pengontrol tergantung pada ProductRepository
, dan kami membiarkan pengontrol membuat ProductRepository
instans. Namun, ide yang buruk untuk mengkodekan dependensi dengan cara ini, karena beberapa alasan.
- Jika Anda ingin mengganti
ProductRepository
dengan implementasi yang berbeda, Anda juga perlu memodifikasi kelas pengontrol. ProductRepository
Jika memiliki dependensi, Anda harus mengonfigurasi ini di dalam pengontrol. Untuk proyek besar dengan beberapa pengontrol, kode konfigurasi Anda tersebar di seluruh proyek Anda.- Sulit untuk menguji unit, karena pengontrol dikodekan secara permanen untuk mengkueri database. Untuk pengujian unit, Anda harus menggunakan repositori tiruan atau stub, yang tidak dimungkinkan dengan desain saat ini.
Kita dapat mengatasi masalah ini dengan menyuntikkan repositori ke pengontrol. Pertama, refaktor ProductRepository
kelas ke dalam antarmuka:
public interface IProductRepository
{
IEnumerable<Product> GetAll();
Product GetById(int id);
void Add(Product product);
}
public class ProductRepository : IProductRepository
{
// Implementation not shown.
}
Kemudian berikan IProductRepository
sebagai parameter konstruktor:
public class ProductsController : ApiController
{
private IProductRepository _repository;
public ProductsController(IProductRepository repository)
{
_repository = repository;
}
// Other controller methods not shown.
}
Contoh ini menggunakan injeksi konstruktor. Anda juga dapat menggunakan injeksi setter, di mana Anda mengatur dependensi melalui metode atau properti setter.
Tetapi sekarang ada masalah, karena aplikasi Anda tidak membuat pengontrol secara langsung. API Web membuat pengontrol saat merutekan permintaan, dan Api Web tidak tahu apa-apa tentang IProductRepository
. Di sinilah pemecah masalah dependensi API Web masuk.
Pemecah Dependensi API Web
API Web mendefinisikan antarmuka IDependencyResolver untuk menyelesaikan dependensi. Berikut adalah definisi antarmuka:
public interface IDependencyResolver : IDependencyScope, IDisposable
{
IDependencyScope BeginScope();
}
public interface IDependencyScope : IDisposable
{
object GetService(Type serviceType);
IEnumerable<object> GetServices(Type serviceType);
}
Antarmuka IDependencyScope memiliki dua metode:
- GetService membuat satu instans jenis.
- GetServices membuat kumpulan objek dari jenis tertentu.
Metode IDependencyResolver mewarisi IDependencyScope dan menambahkan metode BeginScope . Saya akan berbicara tentang cakupan nanti dalam tutorial ini.
Saat Api Web membuat instans pengontrol, API pertama-tama memanggil IDependencyResolver.GetService, meneruskan jenis pengontrol. Anda dapat menggunakan kait ekstensibilitas ini untuk membuat pengontrol, menyelesaikan dependensi apa pun. Jika GetService mengembalikan null, Web API mencari konstruktor tanpa parameter pada kelas pengontrol.
Resolusi Dependensi dengan Kontainer Unity
Meskipun Anda dapat menulis implementasi IDependencyResolver lengkap dari awal, antarmuka ini benar-benar dirancang untuk bertindak sebagai jembatan antara Web API dan kontainer IoC yang ada.
Kontainer IoC adalah komponen perangkat lunak yang bertanggung jawab untuk mengelola dependensi. Anda mendaftarkan jenis dengan kontainer, lalu menggunakan kontainer untuk membuat objek. Kontainer secara otomatis mencari tahu hubungan dependensi. Banyak kontainer IoC juga memungkinkan Anda mengontrol hal-hal seperti masa pakai dan cakupan objek.
Catatan
"IoC" adalah singkatan dari "inversi kontrol", yang merupakan pola umum di mana kerangka kerja memanggil ke dalam kode aplikasi. Kontainer IoC membangun objek Anda untuk Anda, yang "menginversi" alur kontrol yang biasa.
Untuk tutorial ini, kita akan menggunakan Unity dari Microsoft Patterns &Practices. (Pustaka populer lainnya meliputi Castle Windsor, Spring.Net, Autofac, Ninject, Simple Injector dan StructureMap.) Anda dapat menggunakan NuGet Package Manager untuk menginstal Unity. Dari menu Alat di Visual Studio, pilih NuGet Package Manager, lalu pilih Package Manager Console. Di jendela Konsol Manajer Paket, ketik perintah berikut:
Install-Package Unity
Berikut adalah implementasi IDependencyResolver yang membungkus kontainer Unity.
using Microsoft.Practices.Unity;
using System;
using System.Collections.Generic;
using System.Web.Http.Dependencies;
public class UnityResolver : IDependencyResolver
{
protected IUnityContainer container;
public UnityResolver(IUnityContainer container)
{
if (container == null)
{
throw new ArgumentNullException(nameof(container));
}
this.container = container;
}
public object GetService(Type serviceType)
{
try
{
return container.Resolve(serviceType);
}
catch (ResolutionFailedException exception)
{
throw new InvalidOperationException(
$"Unable to resolve service for type {serviceType}.",
exception)
}
}
public IEnumerable<object> GetServices(Type serviceType)
{
try
{
return container.ResolveAll(serviceType);
}
catch (ResolutionFailedException exception)
{
throw new InvalidOperationException(
$"Unable to resolve service for type {serviceType}.",
exception)
}
}
public IDependencyScope BeginScope()
{
var child = container.CreateChildContainer();
return new UnityResolver(child);
}
public void Dispose()
{
Dispose(true);
}
protected virtual void Dispose(bool disposing)
{
container.Dispose();
}
}
Mengonfigurasi Pemecah Masalah Dependensi
Atur pemecah masalah dependensi pada properti DependencyResolver dari objek HttpConfiguration global.
Kode berikut mendaftarkan IProductRepository
antarmuka dengan Unity lalu membuat UnityResolver
.
public static void Register(HttpConfiguration config)
{
var container = new UnityContainer();
container.RegisterType<IProductRepository, ProductRepository>(new HierarchicalLifetimeManager());
config.DependencyResolver = new UnityResolver(container);
// Other Web API configuration not shown.
}
Cakupan Dependensi dan Masa Pakai Pengontrol
Pengontrol dibuat per permintaan. Untuk mengelola masa pakai objek, IDependencyResolver menggunakan konsep cakupan.
Pemecah masalah dependensi yang dilampirkan ke objek HttpConfiguration memiliki cakupan global. Saat Api Web membuat pengontrol, API memanggil BeginScope. Metode ini mengembalikan IDependencyScope yang mewakili cakupan anak.
API Web kemudian memanggil GetService pada cakupan anak untuk membuat pengontrol. Ketika permintaan selesai, API Web memanggil Buang pada cakupan anak. Gunakan metode Buang untuk membuang dependensi pengontrol.
Cara Anda menerapkan BeginScope tergantung pada kontainer IoC. Untuk Unity, cakupan sesuai dengan kontainer turunan:
public IDependencyScope BeginScope()
{
var child = container.CreateChildContainer();
return new UnityResolver(child);
}
Sebagian besar kontainer IoC memiliki setara.