Iniekcja zależności w ASP.NET Web API 2

według Jan Wasson

Pobierz ukończony projekt

W tym samouczku pokazano, jak wstrzyknąć zależności do kontrolera interfejsu API sieci Web ASP.NET.

Wersje oprogramowania używane w samouczku

Co to jest iniekcja zależności?

Zależność jest dowolnym obiektem, który jest wymagany przez inny obiekt. Na przykład często istnieje możliwość zdefiniowania repozytorium , które obsługuje dostęp do danych. Ilustrujemy przykład. Najpierw zdefiniujemy model domeny:

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
}

Oto prosta Klasa repozytorium, która przechowuje elementy w bazie danych przy użyciu Entity Framework.

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);
    }
}

Teraz zdefiniujemy kontroler interfejsu API sieci Web, który obsługuje żądania GET dla Product jednostek. (Pozostawiam wpis i inne metody dla uproszczenia). Oto pierwsza próba:

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);
    }
}

Należy zauważyć, że klasa kontrolera jest zależna od ProductRepository , i zezwalamy kontrolerowi na tworzenie ProductRepository wystąpienia. Niemniej jednak dobrym pomysłem jest napełnienie kodu zależności w ten sposób, z kilku powodów.

  • Jeśli chcesz zastąpić ProductRepository inną implementacją, należy również zmodyfikować klasę Controller.
  • Jeśli ProductRepository ma zależności, należy je skonfigurować w kontrolerze. W przypadku dużego projektu z wieloma kontrolerami kod konfiguracji jest rozmieszczany w całym projekcie.
  • Test jednostkowy jest trudno, ponieważ jest on zakodowany do wykonywania zapytań w bazie danych. W przypadku testów jednostkowych należy użyć repozytorium makiety lub stub, które nie jest możliwe w bieżącym projekcie.

Możemy rozwiązać te problemy poprzez wstrzyknięcie repozytorium do kontrolera. Najpierw Refaktoryzacja ProductRepository klasy do interfejsu:

public interface IProductRepository
{
    IEnumerable<Product> GetAll();
    Product GetById(int id);
    void Add(Product product);
}

public class ProductRepository : IProductRepository
{
    // Implementation not shown.
}

Następnie podaj IProductRepository jako parametr konstruktora:

public class ProductsController : ApiController
{
    private IProductRepository _repository;

    public ProductsController(IProductRepository repository)  
    {
        _repository = repository;
    }

    // Other controller methods not shown.
}

W tym przykładzie zastosowano iniekcję konstruktora. Można również użyć iniekcji setter, gdzie ustawia się zależność za pomocą metody lub właściwości setter.

Jednak teraz występuje problem, ponieważ aplikacja nie tworzy kontrolera bezpośrednio. Interfejs API sieci Web tworzy kontroler, gdy kieruje żądanie, a interfejs API sieci Web nie wie nic o programie IProductRepository . Jest to miejsce, w którym znajduje się program rozpoznawania zależności interfejsu API sieci Web.

Program rozpoznawania zależności interfejsu API sieci Web

Internetowy interfejs API definiuje interfejs IDependencyResolver do rozpoznawania zależności. Oto definicja interfejsu:

public interface IDependencyResolver : IDependencyScope, IDisposable
{
    IDependencyScope BeginScope();
}

public interface IDependencyScope : IDisposable
{
    object GetService(Type serviceType);
    IEnumerable<object> GetServices(Type serviceType);
}

Interfejs IDependencyScope ma dwie metody:

  • GetService tworzy jedno wystąpienie typu.
  • GetServices tworzy kolekcję obiektów określonego typu.

Metoda IDependencyResolver dziedziczy IDependencyScope i dodaje metodę BeginScope . Porozmawiamy o zakresach w dalszej części tego samouczka.

Gdy interfejs API sieci Web tworzy wystąpienie kontrolera, najpierw wywołuje IDependencyResolver. GetService, przekazując do typu kontrolera. Za pomocą tego punktu zaczepienia rozszerzalności można utworzyć kontroler, rozwiązując wszystkie zależności. Jeśli GetService zwraca wartość null, interfejs API sieci Web szuka konstruktora bez parametrów w klasie Controller.

Rozpoznawanie zależności przy użyciu kontenera Unity

Chociaż można napisać kompletną implementację IDependencyResolver od podstaw, interfejs jest naprawdę zaprojektowany do działania jako Most między interfejsem API sieci Web a istniejącymi kontenerami IOC.

Kontener IoC to składnik oprogramowania, który jest odpowiedzialny za zarządzanie zależnościami. Możesz zarejestrować typy w kontenerze, a następnie użyć kontenera do tworzenia obiektów. Kontener automatycznie określa relacje zależności. Wiele kontenerów IoC umożliwia również Sterowanie elementami, takimi jak okres istnienia obiektu i zakres.

Note

"IoC" oznacza "Inversion of Control", który jest ogólnym wzorcem, w którym struktura wywołuje kod aplikacji. Kontener IoC konstruuje obiekty, co oznacza, że jest to normalny przepływ sterowania.

Na potrzeby tego samouczka będziemy używać aparatu Unity z wykorzystaniem wzorców firmy Microsoft & . (Inne popularne biblioteki obejmują Castle Windsor, Spring.NET, Autofac, Ninjecti StructureMap). Do zainstalowania aparatu Unity można użyć Menedżera pakietów NuGet. W menu Narzędzia w programie Visual Studio wybierz pozycję Menedżer pakietów NuGet, a następnie wybierz pozycję konsola Menedżera pakietów. W oknie Konsola Menedżera pakietów wpisz następujące polecenie:

Install-Package Unity

Oto implementacja IDependencyResolver , która otacza kontener 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();
    }
}

Konfigurowanie programu rozpoznawania zależności

Ustaw program rozpoznawania zależności we właściwości DependencyResolver globalnego obiektu HttpConfiguration .

Poniższy kod rejestruje IProductRepository interfejs za pomocą aparatu Unity, a następnie tworzy 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.
}

Zakres zależności i okres istnienia kontrolera

Na żądanie są tworzone kontrolery. Aby zarządzać okresami istnienia obiektów, IDependencyResolver używa koncepcji zakresu.

Program rozpoznawania zależności dołączony do obiektu HttpConfiguration ma zakres globalny. Gdy interfejs API sieci Web tworzy kontroler, wywołuje BeginScope. Ta metoda zwraca IDependencyScope , który reprezentuje zakres podrzędny.

Interfejs API sieci Web następnie wywołuje metodę GetService w zakresie podrzędnym, aby utworzyć kontroler. Po zakończeniu żądania interfejs API sieci Web wywołuje metodę Dispose dla zakresu podrzędnego. Użyj metody Dispose , aby usunąć zależności kontrolera.

Sposób implementacji BeginScope zależy od kontenera IOC. W przypadku aparatu Unity zakres odpowiada kontenerowi podrzędnemu:

public IDependencyScope BeginScope()
{
    var child = container.CreateChildContainer();
    return new UnityResolver(child);
}

Większość kontenerów IoC ma podobne odpowiedniki.