Megjegyzés
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhat bejelentkezni vagy módosítani a címtárat.
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhatja módosítani a címtárat.
Ez az oktatóanyag bemutatja, hogyan szúrhat be függőségeket a ASP.NET webes API-vezérlőbe.
Az oktatóanyagban használt szoftververziók
- Web API 2
- Unity alkalmazás-blokk
- Entity Framework 6 (az 5-ös verzió is működik)
Mi az a függőséginjektálás?
A függőség bármely objektum, amelyet egy másik objektum igényel. Gyakori például az adathozzáférést kezelő adattár definiálása. Szemléltetjük egy példával. Először meghatározunk egy tartománymodellt:
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
}
Az alábbiakban egy egyszerű adattárosztályt talál, amely egy adatbázisban, az Entity Framework használatával tárolja az elemeket.
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);
}
}
Most definiáljunk egy webes API-vezérlőt, amely támogatja az entitások GET-kéréseit Product . (Kihagyom a POST-t és más módszereket az egyszerűség kedvéért.) Íme egy első kísérlet:
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);
}
}
Figyelje meg, hogy a vezérlőosztály ProductRepository-től függ, és lehetővé tesszük, hogy a vezérlő hozza létre a ProductRepository példányt. Azonban több okból is rossz ötlet a függőség ilyen módon történő kódolása.
- Ha egy másik implementációra szeretne cserélni
ProductRepository, módosítania kell a vezérlőosztályt is. - Ha a
ProductRepositoryfüggőségek vannak, ezeket a vezérlőn belül kell konfigurálnia. Több vezérlővel rendelkező nagy projekt esetén a konfigurációs kód szétszórva lesz a projektben. - Nehéz az egységtesztelés, mert a vezérlő hard-kódolt az adatbázis lekérdezéséhez. Egységteszthez használjon egy makett- vagy csonk-adattárat, amely az aktuális kialakítással nem lehetséges.
Ezeket a problémákat úgy oldhatjuk meg, ha az adattárat a vezérlőbe injektáljuk . Először alakítsa át az ProductRepository osztályt egy felületté:
public interface IProductRepository
{
IEnumerable<Product> GetAll();
Product GetById(int id);
void Add(Product product);
}
public class ProductRepository : IProductRepository
{
// Implementation not shown.
}
Ezután adja meg konstruktorparaméterként IProductRepository :
public class ProductsController : ApiController
{
private IProductRepository _repository;
public ProductsController(IProductRepository repository)
{
_repository = repository;
}
// Other controller methods not shown.
}
Ez a példa konstruktorinjektálást használ. A setter-injektálást is használhatja, ahol a függőséget egy setter metódussal vagy tulajdonságdal állíthatja be.
Most azonban probléma merült fel, mert az alkalmazás nem hozza létre közvetlenül a vezérlőt. A Webes API létrehozza a vezérlőt, amikor átirányítja a kérést, és a Web API nem tud semmit a IProductRepository-ről. Itt jön létre a webes API függőségfeloldója.
A webes API függőségfeloldója
A Web API meghatározza a függőségek feloldásához szükséges IDependencyResolver felületet. Az interfész definíciója a következő:
public interface IDependencyResolver : IDependencyScope, IDisposable
{
IDependencyScope BeginScope();
}
public interface IDependencyScope : IDisposable
{
object GetService(Type serviceType);
IEnumerable<object> GetServices(Type serviceType);
}
Az IDependencyScope interfész két módszerrel rendelkezik:
- A GetService létrehoz egy típuspéldányt.
- A GetServices egy adott típusú objektumgyűjteményt hoz létre.
Az IDependencyResolver metódus örökli az IDependencyScope metódust, és hozzáadja a BeginScope metódust. Az oktatóanyag későbbi részében a hatókörökről fogok beszélni.
Amikor a Web API létrehoz egy vezérlőpéldányt, először meghívja az IDependencyResolver.GetService szolgáltatást, amely a vezérlő típusának felel meg. Ezzel a bővíthetőségi kampóval létrehozhatja a vezérlőt, feloldva az esetleges függőségeket. Ha a GetService null értéket ad vissza, a Web API paraméter nélküli konstruktort keres a vezérlőosztályban.
Függőségfeloldás a Unity-tárolóval
Bár a teljes IDependencyResolver implementációt az alapoktól is megírhatja, a felület valóban úgy lett kialakítva, hogy hídként működjön a webes API és a meglévő IoC-tárolók között.
Az IoC-tárolók olyan szoftverösszetevők, amelyek a függőségek kezeléséért felelősek. Típusokat regisztrálhat a tárolóban, majd a tárolóval objektumokat hozhat létre. A tároló automatikusan kitalálja a függőségi kapcsolatokat. Számos IoC-tároló lehetővé teszi az objektumok élettartamának és hatókörének szabályozását is.
Megjegyzés:
Az "IoC" a "vezérlés inverziója", amely egy általános minta, amelyben egy keretrendszer alkalmazáskódba hív be. Az IoC-tárolók az objektumokat az Ön számára építik fel, ami "megfordítja" a szokásos vezérlési folyamatot.
Ebben az oktatóanyagban a Unityt fogjuk használni a Microsoft Patterns & Practicesből. (Más népszerű könyvtárak közé tartozik Castle Windsor, Spring.Net, Autofac, Ninject, Simple Injector és StructureMap.) A Unity telepítéséhez használhatja a NuGet Package Managert. A Visual Studio Eszközök menüjében válassza a NuGet Package Manager, majd a Package Manager Console lehetőséget. A Package Manager konzol ablakában írja be a következő parancsot:
Install-Package Unity
Itt található az IDependencyResolver implementációja, amely egy Unity-tárolót burkol.
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();
}
}
A függőségfeloldó konfigurálása
Állítsa be a függőségfeloldót a globális HttpConfiguration objektum DependencyResolver tulajdonságán.
Az alábbi kód regisztrálja az interfészt a IProductRepository Unityben, majd létrehoz egy 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.
}
Függőség hatóköre és a vezérlő élettartama
A vezérlők kérésenként jönnek létre. Az objektumélettartamok kezeléséhez az IDependencyResolver a hatókör fogalmát használja.
A HttpConfiguration objektumhoz csatolt függőségfeloldó globális hatókörrel rendelkezik. Amikor a Web API létrehoz egy vezérlőt, meghívja a BeginScope-t. Ez a metódus egy gyermekhatókört képviselő IDependencyScope értéket ad vissza.
A Webes API ezután meghívja a GetService-t a gyermek hatókörben a vezérlő létrehozásához. Amikor a kérés befejeződött, a webes API meghívja az Elidegenítést a gyermek hatókörén. A vezérlő függőségeinek megsemmisítéséhez használja az Elidegenítés metódust.
A BeginScope implementálása az IoC-tárolótól függ. A Unity esetében a hatókör egy gyermektárolónak felel meg:
public IDependencyScope BeginScope()
{
var child = container.CreateChildContainer();
return new UnityResolver(child);
}
A legtöbb IoC-tároló hasonló egyenértékűségekkel rendelkezik.