다음을 통해 공유


ASP.NET Web API 2의 종속성 주입

완료된 프로젝트 다운로드

이 자습서에서는 ASP.NET Web API 컨트롤러에 종속성을 삽입하는 방법을 보여 줍니다.

자습서에서 사용되는 소프트웨어 버전

종속성 주입이란?

‘종속성’은 다른 개체에 필요한 모든 개체입니다. 예를 들어 데이터 액세스를 처리하는 리포지토리를 정의하는 것이 일반적입니다. 예를 들어 살펴보겠습니다. 먼저 도메인 모델을 정의합니다.

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

다음은 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);
    }
}

이제 엔터티에 대한 Product GET 요청을 지원하는 Web API 컨트롤러를 정의해 보겠습니다. (편의상 POST 및 기타 메서드를 제외합니다.) 첫 번째 시도는 다음과 같습니다.

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 . 그러나 여러 가지 이유로 이러한 방식으로 종속성을 하드 코딩하는 것은 좋지 않습니다.

  • 다른 구현으로 바꾸려 ProductRepository 면 컨트롤러 클래스도 수정해야 합니다.
  • ProductRepository 종속성이 있는 경우 컨트롤러 내에서 구성해야 합니다. 여러 컨트롤러가 있는 대규모 프로젝트의 경우 구성 코드가 프로젝트 전체에 분산됩니다.
  • 컨트롤러는 데이터베이스를 쿼리하기 위해 하드 코딩되므로 단위 테스트가 어렵습니다. 단위 테스트의 경우 현재 디자인에서는 불가능한 모의 또는 스텁 리포지토리를 사용해야 합니다.

리포지토리를 컨트롤러에 주입하여 이러한 문제를 해결할 수 있습니다. 먼저 클래스를 ProductRepository 인터페이스로 리팩터링합니다.

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

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

그런 다음 생성자 매개 변수로 제공합니다 IProductRepository .

public class ProductsController : ApiController
{
    private IProductRepository _repository;

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

    // Other controller methods not shown.
}

이 예제에서는 생성자 주입을 사용합니다. setter 메서드 또는 속성을 통해 종속성을 설정하는 setter 삽입을 사용할 수도 있습니다.

그러나 이제 애플리케이션이 컨트롤러를 직접 만들지 않기 때문에 문제가 발생합니다. Web API는 요청을 라우팅할 때 컨트롤러를 만들고 Web API는 에 대해 IProductRepository아무것도 알지 못합니다. Web API 종속성 확인기가 들어오는 위치입니다.

Web API 종속성 확인자

Web API는 종속성을 확인하기 위한 IDependencyResolver 인터페이스를 정의합니다. 인터페이스의 정의는 다음과 같습니다.

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

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

IDependencyScope 인터페이스에는 다음 두 가지 메서드가 있습니다.

  • GetService 는 형식의 인스턴스를 하나 만듭니다.
  • GetServices는 지정된 형식의 개체 컬렉션을 만듭니다.

IDependencyResolver 메서드는 IDependencyScope상속하고 BeginScope 메서드를 추가합니다. 이 자습서의 뒷부분에서 범위에 대해 살펴보겠습니다.

Web API는 컨트롤러 인스턴스를 만들 때 먼저 IDependencyResolver.GetService를 호출하여 컨트롤러 유형을 전달합니다. 이 확장성 후크를 사용하여 컨트롤러를 만들어 종속성을 확인할 수 있습니다. GetService가 null을 반환하는 경우 Web API는 컨트롤러 클래스에서 매개 변수가 없는 생성자를 찾습니다.

Unity 컨테이너를 사용한 종속성 확인

처음부터 완전한 IDependencyResolver 구현을 작성할 수 있지만 인터페이스는 실제로 Web API와 기존 IoC 컨테이너 간의 브리지 역할을 하도록 설계되었습니다.

IoC 컨테이너는 종속성 관리를 담당하는 소프트웨어 구성 요소입니다. 컨테이너에 형식을 등록한 다음 컨테이너를 사용하여 개체를 만듭니다. 컨테이너는 종속성 관계를 자동으로 파악합니다. 또한 많은 IoC 컨테이너를 사용하면 개체 수명 및 범위와 같은 항목을 제어할 수 있습니다.

참고 항목

"IoC"는 프레임워크가 애플리케이션 코드를 호출하는 일반적인 패턴인 "제어 반전"을 의미합니다. IoC 컨테이너는 일반적인 제어 흐름을 "반전"하는 개체를 생성합니다.

이 자습서에서는 Microsoft 패턴 및 사례의 Unity를 사용합니다. (기타 인기 있는 라이브러리는 다음과 같습니다.캐슬 윈저, Spring.Net, Autofac, Ninject, 단순 인젝터StructureMap.) NuGet 패키지 관리자 사용하여 Unity를 설치할 수 있습니다. Visual Studio의 도구 메뉴에서 NuGet 패키지 관리자 선택한 다음, 패키지 관리자 콘솔을 선택합니다. 패키지 관리자 콘솔 창에서 다음 명령을 입력합니다.

Install-Package Unity

다음은 Unity 컨테이너를 래핑하는 IDependencyResolver의 구현입니다.

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

종속성 확인자 구성

전역 HttpConfiguration 개체의 DependencyResolver 속성에 대한 종속성 확인자를 설정합니다.

다음 코드는 인터페이스를 Unity에 IProductRepository 등록한 다음, 만듭니다 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.
}

종속성 범위 및 컨트롤러 수명

컨트롤러는 요청당 생성됩니다. 개체 수명을 관리하기 위해 IDependencyResolver는 범위개념을 사용합니다.

HttpConfiguration 개체에 연결된 종속성 확인자에는 전역 범위가 있습니다. Web API는 컨트롤러를 만들 때 BeginScope를 호출 합니다. 이 메서드는 자식 범위를 나타내는 IDependencyScope 를 반환합니다.

그런 다음 Web API는 자식 범위에서 GetService를 호출하여 컨트롤러를 만듭니다. 요청이 완료되면 Web API는 자식 범위에서 Dispose를 호출합니다. Dispose 메서드를 사용하여 컨트롤러의 종속성을 삭제합니다.

BeginScope를 구현하는 방법은 IoC 컨테이너에 따라 달라집니다. Unity의 경우 범위는 자식 컨테이너에 해당합니다.

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

대부분의 IoC 컨테이너에는 유사한 항목이 있습니다.