Dependency Injection 개념과 Ninject 사용법

소프트웨어를 잘 만들기 위해서 많은 디자인 패턴들이 사용되는데, 그중에서 DI(Dependency Injection, 의존성 주입)에 대한 개념과 .NET MVC에서 많이 사용되는 DI Framework들 중 하나인 Ninject(Open Source Project)에 대해서 알아보겠다.

DI(Dependency Injection) 란?

프로그래밍에서 사용되는 객체들 사이의 의존관계를 소스코드가 아닌 외부 설정파일 등을 통해 정의하게 하는 디자인 패턴이다. 개발자는 각 객체의 의존관계를 일일이 소스코드에 작성할 필요 없이 설정파일에 의존관계가 필요하다는 정보만 된다. 그러면 객체들이 생성될때, 외부로부터 의존관계를 주입 받아 관계가 설정 된다.

DI를 적용하면, 의존관계 설정이 컴파일시 고정 되는 것이 아니라 실행시 설정파일을 통해 이루어져 모듈간의 결합도(Coupling)을 낮출 수 있다.
결합도가 낮아지면, 코드의 재사용성이 높아져서 모듈을 여러 곳에서 수정 없이 사용할 수 있게 되고, 모의 객체를 구성하기 쉬워지기 때문에 단위 테스트에 용이해 진다.

이제, .NET에서 MVC 프로젝트를 만들때 DI를 구현하기 위해 가장 많이 사용하는 Open Source인 Ninject에 대해서 알아보자.

NInject 알아보기

(공식 페이지 : http://www.ninject.org/)

간단히 이름부터 살펴보면, NInject는, N(Ninja) + Inject로 대표 이미지로도 Ninja로 되어 있다.

홈페이지 대문에 보면 “Be fast, be agile, be precise”라는 슬로건이 있는데 닌자처럼 빠르고 민첩하며 정확하게 프로그램을 만들수 있게 하겠다는 정신이 담겨있는 것 같다.

(Nate Kohari라는 소프트웨어 엔지니어가 최초 개발을 했는데, 개인적인 생각으로는 N을 중의적인 의미로 사용한게 아닌가 싶다. 참고 : https://www.infoq.com/articles/ninject10-released/)

NInject는 Open Source 라이브러리로 Apache License 2.0에 따라 배포되었으며,
2007년 부터 .NET 어플리케이션의 DI를 구현하기 쉽게 해주도록 지원하고 있다.

이제, 실제로 사용해 보자

NInject 사용해 보기

  1. Package Install
    • Visual Studio에서 제공하는 Nuget Package Installer를 사용하여 다음 Package들을 설치한다.
      – Ninject
      – Ninject.Web.WebApi
      – Ninject.Web.Common
      – Ninject.Web.Common.WebHost
  2. Edit Ninject.Web.Common.cs
    • 위 Package 설치가 완료되면, 프로젝트 최상단에서 App_Start 폴더에 Ninject.Web.Common.cs 파일이 생성된 것을 확인 할 수 있다.
    • 해당 파일을 열어 보면, CreateKernel이라는 method가 있는데 다음 코드를 추가한다. 그러면 NInject가 controller의 의존성 주입을 구성해 줄 수 있게 된다.
RegisterServices(kernel);
GlobalConfiguration.Configuration.DependencyResolver = new NinjectDependencyResolver(kernel);
return kernel;
  1. Register Service
    • 스크롤을 조금 내려 보면, RegisterServices라는 method를 확인 할 수 있는데, 실제로 의존관계를 설정(bind)하는 곳이다. 의존성을 주입할 객체들의 관계를 다음과 같이 추가한다.
private static void RegisterServices(IKernel kernel)
{
    kernel.Bind<ICommonStore>().To<CommonStore>();
}
  1. Use it on controller
    • 이제 의존성 주입을 위한 IoC 설정과, 의존관계 설정(bind)작업을 모두 하였으니 Controller에서 사용해보자.
public class CommonStoreController : Controller
{    
    public CommonStoreController(ICommonStore common)
    {
        this.commonStore = common;
    }

    private ICommonStore commonStore;

    public int GetItemCount(string id)
    {
        return commonStore.Add(id);
    }
}

Design Pattern 중 DI(Dependency Injection, 의존성 주입)이라는 패턴에은 표준 프로그래밍을 할 때 중요한 요소이긴하지만 무조건 사용해야 하는 것은 아니다. 간단한 프로그램이나 객체간의 결합이 명확하여 구분하지 않아도 되는 경우 굳이 프로젝트를 무겁게(?) 만들 필요없다.

그리고 Ninject는 .NET에서 많이 사용되는 Open Source DI Framework 중 하나로 DI를 할 때 쉽게 구현할 수 있어서 선호되는 편이지만 다양한 DI Open Source Framework들이 있으며, 성능 면에서도 훨씬 더 좋은 것들이 있으니 확인하고 사용하길 바란다.
(참고 : https://www.claudiobernasconi.ch/2019/01/24/the-ultimate-list-of-net-dependency-injection-frameworks/)

[.NET]An existing connection was forcibly closed by the remote host 해결 방법

SSL-TLS(이하 TLS)보안 인증된 서버 Application의 API 호출 할때, An existing connection was forcibly closed by the remote host에러가 발생하는 경우가 있다.

이런 경우는 서버의 TLS 버전을 기본값인 1.0을 사용하지 않고 상위 버전인 1.1이나 1.2를 사용하고 있기 때문이다. (TLS 버전은 2018년 4월 기준으로 1.0부터 1.3까지 있으나 1.3은 아직 정식으로 발표되지 않았다.)
각 버전에 따라 네트워크 전송계층에서 사용하는 암호와 알고리즘이 달라지기 때문에 Server-Client 간에 사용 버전이 다르면 바로 An existing connection was forcibly closed by the remote host오류가 발생한다.

.NET에서는 별도의 설정이 없으면 TLS 버전을 기본값 1.0으로 셋팅하여 HTTP 요청을 하도록 되어 있기 때문에 상위 TLS 버전을 사용하는 서버를 그냥 호출하면 위와 같은 오류가 나온다. 이런경우, 상위 TLS 버전을 사용하여 통신 할수 있도록 System.Net.Security DLL을 이용하여 Security Protocol을 다음과 같이 설정해 주어야 한다.

(ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls;)

using (HttpClient client = new HttpClient())
{
    string jsonString = "data list";

    client.BaseAddress = new Uri("https://<Your App Name>.azurewebsites.net");
    
    client.DefaultRequestHeaders.Accept.Clear();
    client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
    
    ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls;
    var response = client.PostAsync("/api/getYourData", new StringContent(jsonString, Encoding.UTF8, "application/json")).Result;

    if (response.IsSuccessStatusCode)
    {
        result = response.Content.ReadAsStringAsync().Result;
    }
}

호출 대상 서버의 TLS 제공 버전을 정확하게 알고 있으면 좋겠지만, 그렇지 않은 경우, 위 예시 코드처럼 지원하는 TLS버전 리스트를 Or(‘|’)로 묶어서 설정 하면 Client에서 요청 할때 알아서 알맞는 TLS Protocol 버전으로 통신(Handshake)을 하도록 되어 있어서 크게 신경 쓰지 않아도 된다.

단지, 너무 오래된(취약점이 있는)버전의 경우 보안 이슈가 될 수 있으니 적당한 지원 버전 영역을 관리 하는 것을 권장한다. 

Azure Cloud Service Too many redirects

Azure의 Cloud Service를 개발 할 때, Local에서 테스트 할 때는 정상 작동했는데, 프로젝트 패키지를 프로덕션(혹은 스테이징)에 배포하고 확인 하니 Too many redirects 라는 오류 페이지가 뜨고 아무것도 확인 할 수 없을 때가 있다.

이런 오류는 보통 Cloud Service에 설정된 Framework과 로컬에서 프로젝트에 구성한 Framework버전이 달라서 생기는 문제다.

우선 WebRole을 비롯한 솔루션에 추가된(종속된) 프로젝트들의 속성에 들어가 그림과 같이 Target Framework(대상 프레임워크) 버전을 확인 한다. (되도록이면, 사용하는 프로젝트들의 Framework 버전중 최상위 버전 1가지로 통일 하는 것을 추천하며 예제에서는 4.6을 사용한다고 가정한다.) 

각 프로젝트의 버전을 확인 한 후 Cloud Service에서 해당 Framework 버전을 지원하는 Azure Guest OS Release 버전을 확인 한다.  
(참조 링크: https://docs.microsoft.com/ko-kr/azure/cloud-services/cloud-services-guestos-update-matrix)

링크에 들어가서 보면 알 수 있겠지만 글 작성일 기준으로 각 Guest OS에 설치된 Framework Version을 정리해 보면 다음 표와 같다.

Guest OS Release Installed .NET Framework Version
Release 5, Windows Server 2016 4.0, 4.5, 4.5.1, 4.5.2, 4.6, 4.6.1, 4.6.2
Release 4, Windows Server 2012 R2 4.0, 4.5, 4.5.1, 4.5.2
Release 3, Windows Server 2012 4.0, 4.5, 4.5.1, 4.5.2
Release 2, Windows Server 2008 R2 SP1 3.5, 4.0, 4.5, 4.5.1, 4.5.2

이제 위에서 확인한 프로젝트들의 target Framework 버전을 지원하는 Guest OS가 설정되어 있는지 Cloud Service 환경설정을 확인 해보아야 한다. Cloud Service의 환경설정을 확인 하기 위해서 아래 그림의 위치의 *.cscfg 파일을 열어 보자.

확인 해 보면 Line:2의 ‘ServiceConfiguration‘태그의 ‘osFamily‘속성을 확인 해 볼 수 있는데, 이곳이 OS Release 버전을 설정하는 곳이다. 프로젝트들의 Target Framework(대상 프레임웍)버전 설정이 Cloud Service에 설정된 osFamily에서 지원하는 것인지 확인하여 지원하는 것으로 고쳐 준다. (예제는 Release 4로 설정되어 있는 osFamily 속성을 4.6버전을 지원하는 Release 5로 변경하였다.)

AS-IS

<ServiceConfiguration serviceName="CloudServiceSample1" xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceConfiguration" osFamily="4" osVersion="*" schemaVersion="2015-04.2.6">

TO-BE

<ServiceConfiguration serviceName="CloudServiceSample1" xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceConfiguration" osFamily="5" osVersion="*" schemaVersion="2015-04.2.6">

이제 다시 빌드 하고 배포하면 정상적인 프로젝트 동작을 확인 해 볼 수 있다.

[.NET]HttpResponseMessage 클래스 메서드 오류 CS1061 해결 방법

프로젝트에서 API를 호출하고 나서 결과를 읽어 Model(Object)에 담을때 다음과 같이 코드를 한다.

그런데 간혹 잘 코딩 했다고 생각을 했어도 다음과 같은 오류 메시지가 나올 때가 있다. 

오류 CS1061 ‘HttpContent’에는 ‘ReadAsAsync’에 대한 정의가 포함되어 있지 않고, ‘HttpContent’ 형식의 첫 번째 인수를 허용하는 확장 메서드 ‘ReadAsAsync’이(가) 없습니다. 
using 지시문 또는 어셈블리 참조가 있는지 확인하세요. 

이런경우 참조 패키지가 잘 준비되지 않은 경우이다. 
먼저 NuGet Package에 들어가서 System.Net.Http 버전을 최신버전으로 업데이트 하고,bMicrosoft.AspNet.WebApi.Client 패키지가 설치 되어 있는지 확인 해본다. 아마 안되어 있거나 구 번전일 것이다.

install-package Microsoft.AspNet.WebApi.Client

설치 및 업데이트가 완료 되었다면 문제 없이 작동 할 것이다.