Függőséginjektálás a ASP.NET Web API 2-ben

Befejezett projekt letöltése

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

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 ProductRepository fü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.