ASP.NET MVC 콘트롤러에 HttpContextBase 의존성 주입하기

ASP.NET MVC 프로젝트를 이용하여 웹 개발을 하고 있으면 반드시 최소한 한 번은 사용하게 되는 HttpContext 인스턴스가 있다. 이 인스턴스는 보통 HttpContext.Current 형태의 싱글톤으로 호출하여 사용할 수 있으며 MVC 패턴에서는 Controller.HttpContext 속성이 이를 대신하고 있다. 하지만, 여기서 이 HttpContext 속성이 갖는 가장 큰 문제는 오로지 get만 지원하고 있어서, 임의의 HttpContext 값을 설정하는 의존성 주입을 할 수 없다는 것이다. 이를 위해 @Paul Hadfield는 자신의 블로그 포스트에서 이 HttpContext 속성을 재정의하는 방법을 제시한다. 예전 포스트에서 언급한 내용은 HttpContext 대신 HttpContextBase를 사용하면 유닛테스트에 유용하다 정도였지만, 여기서는 어떻게 유닛테스트에서 이것을 활용할 수 있을까에 대한 내용이 될 것이다.

생성자를 이용한 의존성 주입

public abstract class BaseController : Controller
{
  public new HttpContextBase HttpContext { get; private set; }

  protected BaseController()
  {
    this.HttpContext = base.HttpContext;
  }

  protected BaseController(HttpContextBase httpContext)
  {
    if(httpContext == null)
    {
      throw new ArgumentNullException("httpContext");
    }
    this.HttpContext = httpContext;
  }
}

위와 같이 베이스 콘트롤러를 정의하고 그 안에서 HttpContext를 재정의한 후 생성자를 통해 의존성을 주입하는 방식을 쓰는 것이다. 이를 적용하면 아래와 같은 형태가 될 수 있다.

public class HomeController : BaseController
{
  public HomeController(HttpContextBase httpContext)
    : base(httpContext)
  {
  }
}

실제로 Unity 또는 Autofac 등과 같은 IoC 컨테이너들을 사용할 경우 콘트롤러는 더욱 많은 인스턴스들을 의존성 주입에 필요로 하게 되므로, 개인적으로는 이 HttpContext와 같은 것들은 Setter 메소드를 통해 의존성 주입을 하는 것을 권장한다. 하지만, 이것은 취향이니까 존중. 개인적으로는 아래와 같은 방법을 선호한다.

세터 메소드를 용한 의존성 주입

public abstract class BaseController : Controller
{
  public new HttpContextBase HttpContext { get; private set; }

  protected BaseController()
  {
    this.HttpContext = base.HttpContext;
  }

  public void SetHttpContext(HttpContextBase httpContext)
  {
    if (httpContext == null)
      throw new ArgumentNullException("httpContext");

    this.HttpContext = httpContext;
  }
}

추가 의존성 주입

물론, 세션이나 쿠키 같은 내용들은 별도의 인스턴스를 통해 의존성을 주입해야 한다. 만약 세션 인스턴스도 의존성 주입이 필요하다면 아래와 같은 내용을 추가할 수 있다.

public abstract class BaseController : Controller
{
  public new HttpContextBase HttpContext { get; private set; }
  public new HttpSessionStateBase Session { get; private set; }

  protected BaseController()
  {
    this.HttpContext = base.HttpContext;
  }

  public void SetHttpContext(HttpContextBase httpContext)
  {
    if (httpContext == null)
      throw new ArgumentNullException("httpContext");

    this.HttpContext = httpContext;
  }

  public void SetHttpSessionState(HttpSessionStateBase httpSessionState)
  {
    if (httpSessionState == null)
      throw new ArgumentNullException("httpSessionState");

    this.Session = httpSessionState;
  }
}

이렇게 의존성 주입이 가능한 HTTP 추상 클라스들의 리스트는 아래와 같다.

  • HttpContextBase
  • HttpRequestBase
  • HttpResponseBase
  • HttpServerUtilityBase
  • HttpSessionStateBase

유닛테스트 예시

이렇게 콘트롤러를 만들어 놓으면 유닛테스트에서는 아래와 같은 형태로 사용이 가능하다. 여기서는 NUnitNSubstitute를 사용한다고 가정한다.

[Test]
public void Test()
{
  var httpContext = Substitute.For();
  var controller = new HomeController();
  controller.SetHttpContext(httpContext);

  // Testing logics here
}

물론, 필요에 따라 httpContext로 목킹한 인스턴스에 다양한 내용을 추가 가능하다.

맺으며

지금까지 간략하게 HttpContext 인스턴스를 ASP.NET MVC 콘트롤러에 주입하는 방법에 대해 논의해 보았다. 이 방법이 가장 옳다는 것은 아니고 이런 식으로 하면 되겠다 하는 것이다보니 언제든 이보다 더욱 효과적인 방법을 찾을 경우 이 포스트는 꾸준히 업데이트가 될 것이다.