ASP.NET Web API 2에서에서 종속성 주입Dependency Injection in ASP.NET Web API 2

Mike Wassonby Mike Wasson

완료 된 프로젝트 다운로드Download Completed Project

이 자습서에서는 ASP.NET Web API 컨트롤러에 종속성을 주입 하는 방법을 보여줍니다.This tutorial shows how to inject dependencies into your ASP.NET Web API controller.

이 자습서에 사용 되는 소프트웨어 버전Software versions used in the tutorial

종속성 주입 이란?What is Dependency Injection?

‘종속성’은 다른 개체에 필요한 모든 개체입니다.A dependency is any object that another object requires. 예를 들어 정의에 공통적으로 적용 되는 리포지토리 데이터 액세스를 처리 하는 합니다.For example, it's common to define a repository that handles data access. 예를 들어 살펴보겠습니다.Let's illustrate with an example. 첫째, 도메인 모델을 정의 합니다.First, we'll define a domain model:

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

Entity Framework를 사용 하 여 데이터베이스에서 항목을 저장 하는 간단한 저장소 클래스는 다음과 같습니다.Here is a simple repository class that stores items in a database, using 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);
    }
}

이제 Web API 컨트롤러에 대 한 GET 요청을 지를 정의 하겠습니다 Product 엔터티.Now let's define a Web API controller that supports GET requests for Product entities. (겠다 게시물 및 단순성에 대 한 다른 방법을.) 첫 번째 시도 다음과 같습니다.(I'm leaving out POST and other methods for simplicity.) Here is a first attempt:

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

컨트롤러 클래스에 따라 달라 집니다 ProductRepository를 만들 컨트롤러는 우리가 하는 ProductRepository 인스턴스.Notice that the controller class depends on ProductRepository, and we are letting the controller create the ProductRepository instance. 그러나 여러 가지 이유로 이러한 방식으로 종속성 하드 코드 하는 것은입니다.However, it's a bad idea to hard code the dependency in this way, for several reasons.

  • 바꾸려는 경우 ProductRepository 를 다른 구현 해야 컨트롤러 클래스를 수정 합니다.If you want to replace ProductRepository with a different implementation, you also need to modify the controller class.
  • 경우는 ProductRepository 종속성에 컨트롤러 내에서 구성 해야 합니다.If the ProductRepository has dependencies, you must configure these inside the controller. 여러 컨트롤러를 사용 하 여 대규모 프로젝트에 대 한 구성 코드 프로젝트에 분산 됩니다.For a large project with multiple controllers, your configuration code becomes scattered across your project.
  • 하기 어렵습니다 단위 테스트, 컨트롤러 데이터베이스를 쿼리하고도 하드 코딩 되어 있습니다.It is hard to unit test, because the controller is hard-coded to query the database. 단위 테스트에 대 한 현재 디자인 가능 하지는 mock 또는 스텁 리포지토리를 사용 해야 합니다.For a unit test, you should use a mock or stub repository, which is not possible with the current design.

이러한 문제를 처리할 수 있었습니다 삽입 저장소 컨트롤러입니다.We can address these problems by injecting the repository into the controller. 첫째, 리팩터링는 ProductRepository 인터페이스 클래스:First, refactor the ProductRepository class into an interface:

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

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

제공 된 IProductRepository 생성자 매개 변수로:Then provide the IProductRepository as a constructor parameter:

public class ProductsController : ApiController
{
    private IProductRepository _repository;

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

    // Other controller methods not shown.
}

이 예제에서는 생성자 주입합니다.This example uses constructor injection. 사용할 수도 있습니다 setter 주입setter 메서드 또는 속성을 통해 종속성을 설정 합니다.You can also use setter injection, where you set the dependency through a setter method or property.

하지만 이제 문제가 있어 응용 프로그램 컨트롤러를 직접 만들지 않습니다.But now there is a problem, because your application doesn't create the controller directly. Web API 요청을 라우팅하고 Web API는 모든 것을 인식 하지 못합니다 때 컨트롤러를 만듭니다 IProductRepository합니다.Web API creates the controller when it routes the request, and Web API doesn't know anything about IProductRepository. Web API 종속성 확인자 제공 되는 위치입니다.This is where the Web API dependency resolver comes in.

웹 API 종속성 확인자The Web API Dependency Resolver

웹 API를 정의 합니다 IDependencyResolver 종속성 확인에 대 한 인터페이스입니다.Web API defines the IDependencyResolver interface for resolving dependencies. 인터페이스의 정의 다음과 같습니다.Here is the definition of the interface:

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

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

합니다 IDependencyScope 인터페이스에 두 가지 방법이 있습니다.The IDependencyScope interface has two methods:

  • GetService 는 형식의 인스턴스를 만듭니다.GetService creates one instance of a type.
  • GetServices 지정 된 형식의 개체의 컬렉션을 만듭니다.GetServices creates a collection of objects of a specified type.

합니다 IDependencyResolver 메서드를 상속 IDependencyScope 추가 합니다 BeginScope 메서드.The IDependencyResolver method inherits IDependencyScope and adds the BeginScope method. 이 자습서의 뒷부분에서 범위에 대 한 이야기 하겠습니다.I'll talk about scopes later in this tutorial.

Web API 컨트롤러 인스턴스를 만드는 경우 먼저 호출한 IDependencyResolver.GetService컨트롤러 형식에 전달 합니다.When Web API creates a controller instance, it first calls IDependencyResolver.GetService, passing in the controller type. 모든 종속성을 확인할 컨트롤러를 만들려면이 확장성 후크를 사용할 수 있습니다.You can use this extensibility hook to create the controller, resolving any dependencies. 하는 경우 GetService null을 반환, Web API 컨트롤러 클래스에 있는 매개 변수가 없는 생성자를 찾습니다.If GetService returns null, Web API looks for a parameterless constructor on the controller class.

Unity 컨테이너를 사용 하 여 종속성 확인Dependency Resolution with the Unity Container

전체를 작성할 수 있지만 IDependencyResolver Web API와 기존 IoC 컨테이너 간의 브리지 역할을 실제로부터 인터페이스를 구현 합니다.Although you could write a complete IDependencyResolver implementation from scratch, the interface is really designed to act as bridge between Web API and existing IoC containers.

IoC 컨테이너는 종속성을 관리 하는 일을 담당 하는 소프트웨어 구성 요소입니다.An IoC container is a software component that is responsible for managing dependencies. 컨테이너로 형식을 등록 하 고이 정보를 개체를 만드는 컨테이너를 사용 합니다.You register types with the container, and then use the container to create objects. 컨테이너는 자동으로 종속성 관계를 파악합니다.The container automatically figures out the dependency relations. 많은 IoC 컨테이너를 사용 하면 개체 수명 및 범위와 같은 항목을 제어할 수도 있습니다.Many IoC containers also allow you to control things like object lifetime and scope.

Note

"IoC"는 "제어 반전"에 대 한 일반적인 패턴을 설정 하는 프레임 워크 응용 프로그램 코드를 호출 하는 경우이 합니다."IoC" stands for "inversion of control", which is a general pattern where a framework calls into application code. IoC 컨테이너 생성 개체를 "반전" 컨트롤의 일반적인 흐름입니다.An IoC container constructs your objects for you, which "inverts" the usual flow of control.

이 자습서에서는 Unity 에서 Microsoft Patterns & 사례입니다.For this tutorial, we'll use Unity from Microsoft Patterns & Practices. (다른 인기 있는 라이브러리 포함 Castle WindsorSpring.NetAutofacNinject, 및 StructureMap .) Unity를 설치 하려면 NuGet 패키지 관리자를 사용할 수 있습니다.(Other popular libraries include Castle Windsor, Spring.Net, Autofac, Ninject, and StructureMap.) You can use NuGet Package Manager to install Unity. 도구 Visual Studio에서 메뉴 NuGet 패키지 관리자을 선택한 후 패키지 관리자 콘솔합니다.From the Tools menu in Visual Studio, select NuGet Package Manager, then select Package Manager Console. 패키지 관리자 콘솔 창에서 다음 명령을 입력 합니다.In the Package Manager Console window, type the following command:

Install-Package Unity

여기의 구현인 IDependencyResolver Unity 컨테이너를 래핑합니다.Here is an implementation of IDependencyResolver that wraps a Unity container.

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("container");
        }
        this.container = container;
    }

    public object GetService(Type serviceType)
    {
        try
        {
            return container.Resolve(serviceType);
        }
        catch (ResolutionFailedException)
        {
            return null;
        }
    }

    public IEnumerable<object> GetServices(Type serviceType)
    {
        try
        {
            return container.ResolveAll(serviceType);
        }
        catch (ResolutionFailedException)
        {
            return new List<object>();
        }
    }

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

    public void Dispose()
    {
        Dispose(true);
    }

    protected virtual void Dispose(bool disposing)
    {
        container.Dispose();
    }
}

Note

경우는 GetService 메서드는 형식을 확인할 수 없습니다, 반환할 null합니다.If the GetService method cannot resolve a type, it should return null. 경우는 GetServices 메서드는 형식을 확인할 수 없습니다, 빈 컬렉션 개체를 반환 해야 합니다.If the GetServices method cannot resolve a type, it should return an empty collection object. 알 수 없는 형식에 대 한 예외를 throw 하지 마십시오.Don't throw exceptions for unknown types.

종속성 확인자를 구성합니다.Configuring the Dependency Resolver

종속성 확인자를 설정 합니다 DependencyResolver 은 전역 HttpConfiguration 개체입니다.Set the dependency resolver on the DependencyResolver property of the global HttpConfiguration object.

다음 코드는 등록 된 IProductRepository Unity를 사용 하 여 인터페이스를 만듭니다를 UnityResolver합니다.The following code registers the IProductRepository interface with Unity and then creates a 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.
}

종속성 범위 및 컨트롤러 수명Dependency Scope and Controller Lifetime

컨트롤러는 요청에 따라 생성 됩니다.Controllers are created per request. 개체 수명 관리 IDependencyResolver 개념을 사용 하는 범위합니다.To manage object lifetimes, IDependencyResolver uses the concept of a scope.

종속성 확인자에 연결 합니다 HttpConfiguration 개체에 전역 범위입니다.The dependency resolver attached to the HttpConfiguration object has global scope. Web API 컨트롤러를 만들 때 호출한 BeginScope합니다.When Web API creates a controller, it calls BeginScope. 이 메서드는 IDependencyScope 자식 범위를 나타내는입니다.This method returns an IDependencyScope that represents a child scope.

웹 API 호출 GetService 컨트롤러를 만들려면 자식 범위에 있습니다.Web API then calls GetService on the child scope to create the controller. Web API를 호출 하는 요청이 완료 되 면 Dispose 자식 범위에 있습니다.When request is complete, Web API calls Dispose on the child scope. 사용 된 Dispose 컨트롤러의 종속성을 삭제 하는 방법입니다.Use the Dispose method to dispose of the controller's dependencies.

구현 하는 방법을 BeginScope IoC 컨테이너에 따라 달라 집니다.How you implement BeginScope depends on the IoC container. Unity에 대 한 범위는 자식 컨테이너에 해당 됩니다.For Unity, scope corresponds to a child container:

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

대부분의 IoC 컨테이너에 유사한 상응 합니다.Most IoC containers have similar equivalents.