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

이제 엔터티에 대 한 GET 요청을 지 원하는 Web API 컨트롤러를 정의 해 보겠습니다 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. 단위 테스트의 경우 현재 디자인에서 사용할 수 없는 모의 또는 스텁 리포지토리를 사용 해야 합니다.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. 웹 API는 요청을 라우팅할 때 컨트롤러를 만들며 Web API는에 대해 알지 못합니다 IProductRepository .Web API creates the controller when it routes the request, and Web API doesn't know anything about IProductRepository. 웹 API 종속성 해결 프로그램이 제공 되는 위치입니다.This is where the Web API dependency resolver comes in.

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

Web 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.

이 자습서에서는 Microsoft 패턴 방법의 Unity 를 사용 & 합니다.For this tutorial, we'll use Unity from Microsoft Patterns & Practices. 기타 인기 있는 라이브러리에는 성 Windsor, Spring.Net, Autofac, NinjectStructureMap가 포함 됩니다. NuGet 패키지 관리자를 사용 하 여 Unity를 설치할 수 있습니다.(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

다음은 Unity 컨테이너를 래핑하는 되며 idependencyresolver 의 구현입니다.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(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();
    }
}

종속성 확인자 구성Configuring the Dependency Resolver

전역 Httpconfiguration 개체의 DependencyResolver 속성에서 종속성 해결 프로그램을 설정 합니다.Set the dependency resolver on the DependencyResolver property of the global HttpConfiguration object.

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

Web 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.