Freigeben über


Abhängigkeitsinjektion in ASP.NET-Web-API 2

Abgeschlossenes Projekt herunterladen

In diesem Lernprogramm wird gezeigt, wie Abhängigkeiten in Den ASP.NET-Web-API Controller eingefügt werden.

Im Lernprogramm verwendete Softwareversionen

Was ist Dependency Injection?

Eine Abhängigkeit ist ein beliebiges Objekt, das ein anderes Objekt benötigt. Beispielsweise ist es üblich, ein Repository zu definieren, das den Datenzugriff verarbeitet. Lassen Sie uns mit einem Beispiel veranschaulichen. Zunächst definieren wir ein Domänenmodell:

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

Dies ist eine einfache Repositoryklasse, die Elemente in einer Datenbank mit Entity Framework speichert.

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

Nun definieren wir einen Web-API-Controller, der GET-Anforderungen für Product Entitäten unterstützt. (Ich verlässt POST und andere Methoden aus Gründen der Einfachheit.) Hier ist ein erster Versuch:

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

Beachten Sie, dass die Controllerklasse davon ProductRepositoryabhängt, und wir lassen den Controller die ProductRepository Instanz erstellen. Es ist jedoch eine schlechte Idee, die Abhängigkeit auf diese Weise zu hartcodieren, aus mehreren Gründen.

  • Wenn Sie eine andere Implementierung ersetzen ProductRepository möchten, müssen Sie auch die Controllerklasse ändern.
  • Wenn die ProductRepository Abhängigkeiten vorhanden sind, müssen Sie diese innerhalb des Controllers konfigurieren. Für ein großes Projekt mit mehreren Controllern wird Ihr Konfigurationscode über Ihr Projekt verteilt.
  • Es ist schwierig, den Komponententest zu testen, da der Controller hartcodiert ist, um die Datenbank abzufragen. Für einen Komponententest sollten Sie ein Pseudo- oder Stub-Repository verwenden, das mit dem aktuellen Design nicht möglich ist.

Wir können diese Probleme beheben, indem wir das Repository in den Controller einfügen . Umgestalten Sie zunächst die ProductRepository Klasse in eine Schnittstelle:

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

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

Geben Sie dann den IProductRepository Parameter als Konstruktor an:

public class ProductsController : ApiController
{
    private IProductRepository _repository;

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

    // Other controller methods not shown.
}

In diesem Beispiel wird die Konstruktoreinfügung verwendet. Sie können auch die Settereinfügung verwenden, bei der Sie die Abhängigkeit über eine Settermethode oder -eigenschaft festlegen.

Aber jetzt gibt es ein Problem, da Ihre Anwendung den Controller nicht direkt erstellt. Die Web-API erstellt den Controller, wenn er die Anforderung weitergibt, und die Web-API weiß nichts über IProductRepository. Hier kommt der Web-API-Abhängigkeitslöser ein.

Der Web-API-Abhängigkeitslöser

Die Web-API definiert die IDependencyResolver-Schnittstelle zum Auflösen von Abhängigkeiten. Hier ist die Definition der Schnittstelle:

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

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

Die IDependencyScope-Schnittstelle verfügt über zwei Methoden:

  • GetService erstellt eine Instanz eines Typs.
  • GetServices erstellt eine Auflistung von Objekten eines angegebenen Typs.

Die IDependencyResolver-Methode erbt IDependencyScope und fügt die BeginScope-Methode hinzu. Ich werde später in diesem Lernprogramm über Bereiche sprechen.

Wenn die Web-API eine Controllerinstanz erstellt, ruft sie zuerst IDependencyResolver.GetService auf und übergibt den Controllertyp. Sie können diesen Erweiterbarkeits-Hook verwenden, um den Controller zu erstellen und alle Abhängigkeiten aufzulösen. Wenn GetService null zurückgibt, sucht die Web-API nach einem parameterlosen Konstruktor in der Controllerklasse.

Abhängigkeitsauflösung mit dem Unity-Container

Obwohl Sie eine vollständige IDependencyResolver-Implementierung von Grund auf neu schreiben könnten, ist die Schnittstelle wirklich so konzipiert, dass sie als Brücke zwischen Web-API und vorhandenen IoC-Containern fungiert.

Ein IoC-Container ist eine Softwarekomponente, die für die Verwaltung von Abhängigkeiten verantwortlich ist. Sie registrieren Typen beim Container und verwenden dann den Container zum Erstellen von Objekten. Der Container bestimmt automatisch die Abhängigkeitsbeziehungen. Viele IoC-Container ermöglichen ihnen auch die Steuerung von Objekten wie objektlebensdauer und Bereich.

Hinweis

"IoC" steht für "Inversion des Steuerelements". Dabei handelt es sich um ein allgemeines Muster, bei dem ein Framework den Anwendungscode aufruft. Ein IoC-Container erstellt Ihre Objekte für Sie, wodurch der übliche Kontrollfluss "invertiert" wird.

In diesem Lernprogramm verwenden wir Unity aus Microsoft Patterns & Practices. (Weitere beliebte Bibliotheken sind:Castle Windsor, Spring.Net, Autofac, Ninject, Simple Injector und StructureMap.) Sie können NuGet-Paket-Manager verwenden, um Unity zu installieren. Wählen Sie im Menü "Extras" in Visual Studio "NuGet Paket-Manager" und dann Paket-Manager Konsole aus. Geben Sie im Paket-Manager Konsolenfenster den folgenden Befehl ein:

Install-Package Unity

Hier ist eine Implementierung von IDependencyResolver , die einen Unity-Container umschließt.

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

Konfigurieren des Abhängigkeitslösers

Legen Sie den Abhängigkeitslöser für die DependencyResolver-Eigenschaft des globalen HttpConfiguration-Objekts fest.

Der folgende Code registriert die IProductRepository Schnittstelle bei Unity und erstellt dann eine 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.
}

Abhängigkeitsbereich und Controllerlebensdauer

Controller werden pro Anforderung erstellt. Zum Verwalten von Objektlebensdauern verwendet IDependencyResolver das Konzept eines Bereichs.

Der an das HttpConfiguration-Objekt angefügte Abhängigkeitslöser hat einen globalen Bereich. Wenn Die Web-API einen Controller erstellt, ruft sie BeginScope auf. Diese Methode gibt einen IDependencyScope zurück, der einen untergeordneten Bereich darstellt.

Die Web-API ruft dann GetService im untergeordneten Bereich auf, um den Controller zu erstellen. Wenn die Anforderung abgeschlossen ist, ruft Web-API Dispose für den untergeordneten Bereich auf. Verwenden Sie die Dispose-Methode, um die Abhängigkeiten des Controllers zu verwerfen .

Wie Sie BeginScope implementieren, hängt vom IoC-Container ab. Für Unity entspricht der Bereich einem untergeordneten Container:

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

Die meisten IoC-Container verfügen über ähnliche Entsprechungen.