C# GZipStream 문자열 압축과 해제



많은 문자열을 네트워크를 통해 보낼때 CPU 비용 보다는 네트워크 비용을 줄이기 위해 사용할 수 있을것이다. 하지만 압축과 해제는 CPU뿐만 아니라 메모리에서도 제약 사항을 많이 받는다. 최대 4GB까지 지원하지만 압축된 데이터를 저장할 수 있는 메모리 공간과 압축 해제된 데이타를 저장할 수 있는 공간이 필요 하기에 예상보다 많은 메모리 사용량이 필요 할 수 있다. 그러르모 언제든지 OutOfMemoryException(OOM)이 발생할 수 있으며, InSufficientMemoryException 또한 발생할 수 있다. 그래서 실 사용에서는 사용환경에 맞게 최대 허용가능한 용량을 산정하여 사용할 필요가 있다.


실예로 모 사이트에서 수만 row의 값을 압축하여 클라이언트에 내려 보냈으나 압축해제시 OutofMemoryException이 발생하여 고생을 했던적이 있다. 그리고 서버상에서는 여러 사용자가 요청할 수도 있는 환경이기에 더욱더 주의해서 사용해야 한다.


아래는 실제로 메모리 상에서 문자열을 압축하고 해제하는 코드다.


압축 메소드

/// <summary>
/// 중복된 많은 문자열을 용량을 줄일 때 사용
/// , 메모리가 많이 사용되기 때문에 충분히 테스트가 되어야 하며 사전에 사용할 수 있는 최대값을 결정하고 허용된 범위 안에서만 되도록 한다.
/// </summary>
/// <param name="str">압축할 문자열</param>
/// <returns>압축된 문자열</returns>
/// <remarks>
/// UTF-8기반 문자열 압축 - 최대 4Gb 이하만 사용
/// , 메모리가 많이 사용되기 때문에 충분히 테스트가 되어야 하며 사전에 사용할 수 있는 최대값을 결정하고 허용된 범위 안에서만 되도록 한다.
/// </remarks>
/// <example> 문자열 압축
/// <code>
/// string str = "compress string";
/// string compressed = Compression(str);
/// </code>
/// </example>
/// <exception cref="OutOfMemoryException"></exception>
/// <exception cref="InsufficientMemoryException">사용 가능한 메모리가 없을때 발생</exception>
public static string Compression(string str)
{
    var rowData = Encoding.UTF8.GetBytes(str);
    byte[] compressed = null;
    using (var outStream = new MemoryStream())
    {
        using (var hgs = new GZipStream(outStreamCompressionMode.Compress))
        {
            //outStream에 압축을 시킨다.
            hgs.Write(rowData0rowData.Length);
        }
        compressed = outStream.ToArray();
    }
 
    return Convert.ToBase64String(compressed);
}


압추해제 메소드

/// <summary>
/// 문자열 압축 해제
/// , 메모리가 많이 사용되기 때문에 충분히 테스트가 되어야 하며 사전에 사용할 수 있는 최대값을 결정하고 허용된 범위 안에서만 되도록 한다.
/// </summary>
/// <param name="compressedStr">압축된 문자열</param>
/// <returns>평문 문자열</returns>
/// <remarks>
///  UTF-8기반 문자열 압축 해제 - 최대 4Gb 이하만 사용
///  , 메모리가 많이 사용되기 때문에 충분히 테스트가 되어야 하며 사전에 사용할 수 있는 최대값을 결정하고 허용된 범위 안에서만 되도록 한다.
/// </remarks>
/// <example> 문자열 압축 해제
/// <code>
/// string deCompressed = DeCompression(compressedStr); //압축 해제된 문자열
/// </code>
/// </example>
/// <exception cref="OutOfMemoryException"></exception>
/// <exception cref="InsufficientMemoryException">사용 가능한 메모리가 없을때 발생</exception>
public static string DeCompression(string compressedStr)
{
    string output = null;
    byte[] cmpData = Convert.FromBase64String(compressedStr);
    using (var decomStream = new MemoryStream(cmpData))
    {
        using (var hgs = new GZipStream(decomStreamCompressionMode.Decompress))
        {
            //decomStream에 압축 헤제된 데이타를 저장한다.
            using (var reader = new StreamReader(hgs))
            {
                output = reader.ReadToEnd();
            }
        }
    }
 
    return output;
}


(소스 코드 자체에 주석과 직관적인 코딩으로 충분히 파악이 가능할 것으로 예상하므로 별도의 설명을 생략)
※ 장황한 설명 보다는 주석과 소스코드 자체 만으로도 이해할 수 있도록 포스팅을 하는제 주안점을 두도록 하겠습니다.
  진짜로 필요한 소스는 단순히 Copy & Paste만드르도 사용할 수 있도록 노력할 것이며 주석을 nDoc이나 별도의 자동 DOC 제작 유틸로 API 문서를 만드는 데에도 도움이 되었으면 한다.
DOC Util link



다음은 위 메소드가 정상적으로 동작하는지 테스트를 해 보도록 하겠다.

[TestMethod]
public void Compress_DeCompress_TestMethod()
{
    string str = "양복 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press";
    string compressed = Compression(str);
    int fooLength = compressed.Length;      // 60   - 약 90% 정도가 압축이 되었다.
    int barLength = str.Length;             // 704
    string deCompressed = DeCompression(compressed);
    int bazLength = deCompressed.Length;    // 704
 
    //System.IO.BinaryWriter
    //System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
 
    Assert.AreEqual(strdeCompressed);
}

(위 테스트는 중복된 문자열이 많게 하여 테스트를 진행하였다.)


압축과 해제를 적절한 곳에 제한적으로 사용한다면 훌륭한 모듈이지만 과도하게 적용하여 사용하다가 보면 CPU나 메모리 제한으로 언제 어떠한 일이 발생할지 모른다. 그러므로 적절히 제한적으로 사용하여 시스템의 전체적인 퍼포먼스를 제대로 사용할 수 있는 개발자가 되었으면 한다.


Tip!

1. 압축에 관계된 오픈 소스가 여러가지 존재 하고 있다.

.Net Framework에서 기본제공 하는 속도나 메모리 제약 사항을 뛰어 넘는 훌륭한 솔루션들이 많이 있지만 제한에 대한 높이만 다를뿐 언제든지 이와 같은 문제에 직면할 수 있으므로 주의 해서 사용 해야 한다.


2. 다음에는 문자열만 압축하는 것이 아닌 Object 자체 까지도 압축할 수 있는 방법을 알아 보도록 하겠다.


C# FileSystemWatcher 디렉토리, 파일 변경 모니터링



이번 포스트에서는 System.IO 네임스페이스에 위치하는 FileSystemWatcher 클래스에 대해서 알아 보도록 하겠다.

    FileSystemWatcher watch = null;
    watch = new FileSystemWatcher(Path.Combine(Environment.CurrentDirectory"folder"), "*.DLL");   //실행되는 폴더의 folder이라는 하위 폴더에서 DLL 파일만 변경에 대해서 모니터링 세팅
    watch.EnableRaisingEvents = true;   //이벤트 활성화 시킴
    watch.NotifyFilter = NotifyFilters.CreationTime | NotifyFilters.Attributes | NotifyFilters.DirectoryName | NotifyFilters.FileName | NotifyFilters.LastAccess | NotifyFilters.LastWrite | NotifyFilters.Security | NotifyFilters.Size;
 
    watch.Changed += (se=> { Console.WriteLine("Changed"); };
    watch.Created += (se=> { Console.WriteLine("Created"); };
    watch.Deleted += (se=> { Console.WriteLine("Deleted"); };
    watch.Renamed += (se=> { Console.WriteLine("Renamed"); };
 
    Console.ReadKey();

위와 코드를 실행하고 'folder'폴더 안에 있는 dll파일을 추가 하거나 수정하면 해당 이벤트가 


전체적으로 어렵지 않게 사용할 수 있어서  주석과 직관적인 코드로 설명을 대체 하도록 하겠다.


아래는 NotifyFilter의 속성에 대한 설명이다.

멤버이름 설명
Attributes 파일 또는 폴더의 특성
CreateTime 파일 또는 폴더를 만든 시간
DirectoryName 디렉토리 이름
FileName 파일이름
LastAccess 파일 또는 폴더를 마지막으로 열었던 날짜
LastWrite 파일 또는 폴더에 마지막으로 기록된 날짜
Security 파일 또는 폴더의 보안 설정
Size 파일 또는 폴더의 크기


Tip!

NotifyFilter을 적절하게 세팅하고 발생하는 이벤트도 관리해서 중복되는 이벤트가 발생하지 않도록 해야 하며 중복 발생에 대한 처리도 해서 실질적으로 사용해야 하겠다.


C# Delegate


Delegate는 인자만 맞으면 실행되게 하는 대신 실행 시켜주는 Managed C# point 역할을 하는 특별한 객체이다. 이번 포스트에서는 묶여진 Delegation을 풀어 따로 결과값을 받아 오는 방안에 대해서 알아 보도록 하겠다.

아래와 같이 메소드를 등록(간단하게 Action 으로 진행)하고 마지막에 인자를 넘겨 실행 시키면 전체 연결된 체인 메소드가 실행이 된다.

            TestDelegate testDelegate = null;
 
            testDelegate += (ab=> { return (a + b); };
            testDelegate += (ab=> { return (a - b); };
            testDelegate += (ab=> { return (a / b); };
            testDelegate += (ab=> { return (a * b); };
 
            Debug.WriteLine(testDelegate(1020));

하지만 위와 같이 실행하면 각각의 결과에 대한 값이 나오는 것이 아니라 최종 마지막에 실행될 '*' 연산 결과에 대해서만 화면에 뿌려주게 된다. 전에 실행된 결과값은 메모리상에서 사라지게 되는 것이다.

이번에는 위와 같이 체인으로 묶여 있어도 각각의 결과값에 대해서 사용할 수 있도록 만든 메소드를 소개 하겠다.
        /// <summary>

        /// MulticastDelegate를 이용해 여러 메소드를 실행 할 때 각각의 반환값을 받아 온다.         /// </summary>         /// <typeparam name="T">return object type</typeparam>         /// <param name="func">Delegate[]를 반환하는 Func</param>         /// <param name="args">delegate가 실행에 필요한 인자 값</param>         /// <returns>delegate가 실행되고 반환된 리턴값들</returns>         /// <remarks>         /// 동적인 delegate의 결과값을 IEnumerable<T>로 받아 오기         /// </remarks>         /// <exception cref="ArgumentNullException">         /// Null 을 넘겨 주면 발생         /// </exception>         /// <exception cref="InvalidCastException">         /// Generic가 제대로 선언되지 않으면 발생         /// </exception>         /// <example>         /// delegate int TestDelegate(int a, int b);         /// TestDelegate testDelegate = null;         /// testDelegate += (a, b) => { return (a + b); };         /// testDelegate += (a, b) => { return (a - b); };         /// testDelegate += (a, b) => { return (a / b); };         /// testDelegate += (a, b) => { return (a * b); };         /// GetDelegateInvokeResults<int>(() => delegate.GetInvocationList(), 1, 2);   //호출시 generic 방법으로 반환될 형식을 정의 함.         /// </example>                  public IEnumerable<T> GetDelegateInvokeResults<T>(Func<Delegate[]> funcparams object[] args)         {                          List<T> list = new List<T>();             //인자로 받은 Func를 실행한다.             var delList = func.Invoke();             //Delegate[]를 loop             foreach (var del in delList)             {                 //동적 인자로 실행하고 반환값을 List에 넣는다.                 list.Add((T)del.DynamicInvoke(args));             }             return list;         }

delegate를 동적으로 컨트롤 하기 위해서 Func와 params 라는 개념을 도입을 하여 메소드를 만들었다.


위 메소드를 아래와 같이 사용하여 results에 담아 올 수 있다.

TestDelegate testDelegate = null;         testDelegate += (ab=> { return (a + b); };         testDelegate += (ab=> { return (a - b); };         testDelegate += (ab=> { return (a / b); };         testDelegate += (ab=> { return (a * b); };         var results = GetDelegateInvokeResults<int>(() => testDelegate.GetInvocationList(), 1020);

이렇게 되면 결과값을 가지고 지지고 볶을 수 있을 것이다.


다음에 기회가 된다면 일률적인 값으로 파라메터를 넘기는 것이 아닌 각각의 delegate마다 다른 파라메터를 넘길 수 있는 방안을 알아 보도록 하겠다.

이전시간에 이어서 패턴 개론에 대해서 이어가도록 하겠다.

 

주석

- 모든 변수, 모든 행에 주석을 다는 게 아닌 특이하고 흥미로운 알고르즘이나 기법을 사용한 사용한 경우에 주석을 단다.
  주석은 코드를 읽을 미래의 독자에게 주는 힌트

 

API 문서 작성

아래는 JS Doc를 자동으로 생성해 주는 무료 툴이다. 요구하는 형식에 맞게 주석을 달면 툴에서 자동으로 해당 API 문서를 만들어 낸다.
- JSDoc : http://code.google.com/p/jsdoc-toolkit

- YUIDoc : http://yuilibrary.com/projects/yuidoc

독자를 위한 문서 작성

- API 문서를 작성하라는 것은 초안일 뿐인 첫 번째 코딩에 대해서 문석 작성시 한번 더 살펴 독자 입장에서 살펴 보라는 것이다.

코드 또는 API 문서를 작성할 때 다른 누군가가 읽을 것이라고 생각하라는 것이다. 이 사실 자체만으로도 눈 앞의 문제를 해결하는 좀더 나은 방법을 생각해 보게 된다.

 

동료 리뷰

- 애자일에서 코드 품질을 높이는데 사용하는 방안이다.

자신이 작성한 코드를 옆에 있는 동료에게 검토를 부탁이나 설명을 해주는 것만으로도 가능하다. 이런 방법으로 서로의 경험과 개개인의 접근방식을 배우게 된다는 점에서도 장점을 찾을 수 있다.

 

출시 단계(개발완료 단계)의 압축

- 압축이란 자바스크립트 코드에서 공백, 주석 및 기타 중요하지 않은 부분들을 삭제함으로써 서버에서 브라우저로 전송되는 파일 크기를 감소시키는 공정이다.

압축에 관련된 툴로는 YUI컴프레서, 구글의 클로저 컴파일러, Microsoft Ajax Minifier 가 있다. – 대략 절반 정도의 압축을 할 수 있다.
      - http://yui.github.com/yuicompressor/, http://refresh-sf.com/yui/ online에서 압축 시켜 줌.
      - http://closure-compiler.appspot.com/home online 지원, https://developers.google.com/closure/compiler/?hl=ko
      - http://www.asp.net/ajaxlibrary/AjaxMinDocumentation.ashx, http://aspnet.codeplex.com/releases/view/40584 Download

- 압축 시 지역 변수는 임의의 변수로 치환되어 압축 한다.

 

JSLint 실행

- JSLint에서 제공되는 가이드 라인에서 위배되는 사항을 개발자가 알 수 있도록 알려주는 툴이다.

일반적인 개발 사항에서 좀더 가시적이고 오류가 없도록 도와주는 역할을 하는 도구다.

- Visual Studio JSLint를 컴파일 단계에서 점검해 볼 수 있도록 확장 기능을 제공하기도 한다.

유지보수 가능한 코드 작성

- 읽기 쉽게

- 일관적이게

- 예측 가능하도록

- 문서화( 코드 코멘트, 주석, 개발 문서 )

 

전역 변수 최소화

- vGlobal = ‘hello’;

console.log(vGlobal);      //”hello”

console.log(window.vGlobal);      //”hello”

console.log(window.[“vGlobal”]);      //”hello”

console.log(this.vGlobal);      //”hello”

위에서 “vGlobal”은 다 같은 곳을 바라보고 있다. 그렇지 않다면 'undefined’가 출력이 되었을 것이다.

 

전역 변수의 문제점

- 외부 라이브러리가 변경

- 다른 개발자가 변경

의도치 않게 기준 값이 변경되어 버그가 발생할 수 있다.

 

단일 var 패턴

- 함수 상단에서 var를 이용해 모든 변수를 선언 함

- 강제로 선언 시 함수 초기에 ‘use strict’을 사용하면 html5에서는 변수 선언을 강제 할 수 있다.

 

암묵적인 타입 캐스팅 피하기

- “===” 이나 “!==”을 사용한다.

 

eval() 피하기

- var property = “value”;     //안티패턴
  alert(eval(‘obj.’ + property));

  var property = “value”;     //권장
  alert(obj[property]);

- eval은 보안 문제와도 관련이 있다.
만약 네트워크로 가져온 코드를 실행 하는 케이스가 있을 때 잘못된 코드가 온다면 의도되지 않은 실행이나 개인 정보 유출의 위험성이 존재하게 된다.

 

- 위와 같은 패턴으로 실행되는 케이스가 더 있다.

     1. setTimeout(“execute code”, 1000);     //안티패턴

            -> 권장안 setTimeout(function(){ /* execute code */}, 1000);

           2. new Function() 생성자 – eval을 반드시 사용해야 한다면 new Function() 사용을 고려해 볼 수 있다.
              new Function()으로 실행되는 코드는 지역 함수의 유효범위 안에서 실행 – 자동으로 전역 변수로 만들어 주지는 않는다. (ex. var를 실행하면 eval에서는 전역 변수로 선언되지만 new Function()에서는 지역 변수로 선언된다.)

              var str = ‘var temp = 2; console.log(temp);’;
              new Function(str)();           - new Function()으로 실행 - 추천

              (function() {                     - eval로 실행 – 추천(격리되어 실행되게 한다)

                  eval(temp);

              }());

parseInt()를 통한 숫자 변환

- var month = ‘01’, year = ‘2013’
  month = parseInt(month, 10);
  year = parseInt(year, 10);

위와 같이 기수 매개 변수를 꼭 입력해야 한다.
ECMAScript 3에서는 8진수가 기본이고, ECMAScript 5에서는 변경이 되었다.

위에서 문제가 되는 부분은 시작하는 숫자가 ‘0’으로 시작하는 month 변수가 잘못 해석이 될 수 있다.

 

코딩규칙

- 코딩 규칙을 수립하고 준수하는 목적은 이를 통해 코드의 일관성이 유지되고 예측가능해지며 읽고 이해하기 훨씬 더 쉬워지기 때문이다.
  (코딩 규칙을 정하고자 한다면 조직 안에서 반발이 일어 날 수 있고 예외 적인 부분 때문에 막힐 수도 있지만 규칙을 수립하여 일관되게 준수하는 것 자체가 규칙의 세부 사항보다 훨씬 중요하다.)

   들여 쓰기, 중괄호, 여는 중괄호의 위치, 공백에 대해서 기준을 정하고 규칙을 준수하도록 노력해야 하겠다.

 

명명 규칙

- 코드를 좀더 예측 가능하고 유지보수하기 쉽게 만드는 방법이다. 즉 변수와 함수의 이름을 일관된 방식으로 만드는 것이다.

1. 생성자를 대문자로 하기

   var foo = new Person(); – 함수가 대문자로 시작하면 생성자로 사용할 function인지 파악할 수 있다. – 기본적으로 javascript에서는 function 이름을 소문자로 시작한다.

2. 단어 구분

   javascript에서는 단어 조합시 카멜 표기법(camel case)을 사용한다. 이 표기법의 사용은 
      - 생성자는 단어의 첫 부분을 대문자로 시작하고 나머지는 소문자
         ex. MyConstructor()
      - 함수와 메서드는 단어의 첫 부분은 소문자 이어지는 단어의 첫 문자는 대문자를 사용
         ex. myFunction(){…}
      - 변수명은 단어와 단어 사이에 “_”를 넣는 것을 추천한다. – 함수와 변수의 차이점을 금방 알아 볼 수 있다.
        ex. first_name,  company_name, …

3. 그 밖의 명명 규칙
      - 상수 성격의 변수 ( domain 안에서 값이 변경 되어서는 안 되는 변수 )

        ex. PI = 3.14, MAX_WIDTH = 800;
      - 내부에서 사용하는 비 공개 메서드
        ex. _getName()

C# WriteSubstitution - OutputCache


웹을 개발하는데 있어서 캐시를 적절히 사용하면 웹 서버의 성능을 비약적으로 끌어 올릴 수 있다.

 

아래와 같이 특정 액션이 자원을 많이 소모하여 얻어낸 결과를 사용자가 요청할 때마다 얻어와야 하는 곳에서 OutputCache Attribute를 사용함으로써 선택적으로 캐시를 사용할 수 있다.

위와 같이 설정이 되면 10초 동안 서버에서는 캐시 된 정보를 반환하도록 되어 있다.

 

그렇지만 캐시가 되어 있는 상황에서도

일부 데이터는 갱신되어야 하는 시나리오가 있을 수 있을 것이다.

이럴 때 사용할 수 있는 방안 중에 Response.WriteSubstitution를 소개하도록 하겠다.

 

@ViewBag.Time : 서버에서 캐시 된 값을 Duration 시간 동안 출력한다.

@{ Response.WriteSubstitution((context) => DateTime.Now.ToString()); } : 캐시 된 Content에서도 요청 시 마다 새로운 값을 출력이 되도록 해주는 메소드 ( Action 메소드를 통해 delegate를 구현하였다. )

 

위와 같이 설정하고 아래 화면에서 확인해 보도록 하자.

첫번째 형광색 표시가 WriteSubstitution으로 출력된 값이고

두번째 형광색 표시가 캐시 된 값을 보여주고 있다.

C# ASP.NET MVC 4에서 CommonServiceLocator.WindsorAdapter 사용하여 IoC 세팅


이전 포스트에서 Windsor를 이용해서 IoC를 세팅하는 방법을 소개하였다.

(2013/01/25 - [ASP.NET MVC] - [IoC & DI]ASP.NET MVC 4에서 Windsor (Castle)로 IoC로 Controller 생성)

이전 방법은 Windsor IoC에 대해서 Dependency하게 세팅하는 방법으로 다른 종류의 IoC를 세팅하기 위해서는 다른 접근 전략을 세워야만 했었다.

그렇지만 CommonServiceLocator을 사용하면 아래와 같은 IoC에 상관없이 일관되게 프로젝트에서 접근하고 사용할 수 있다.

- NInject

- Windsor

- MEF

- Unity

- StructureMap

- … ( ETC )

 

우선 CommonServiceLocator의 Open Source를 알려 주도록 하겠다.

http://commonservicelocator.codeplex.com/ : CommonServiceLocator URL

다음으로 이번에 같이 소개할 WindsorAdapter ( CommonServiceLocator을 사용할 수 있도록 도와주는 Extended Adapter )

https://github.com/yellowfeather/CommonServiceLocator.WindsorAdapter

 

이번 포스트에서는 WindosrAdapter을 사용하여 ASP.NET MVC 4에서 Controller 생성하도록 하겠다.

이제 Visual Studio에서 해당 DLL을 추가하면 된다.

Tip!
다른 IoC를 CommonServiceLocator Adapter해서 구현해 놓은 라이브러리가 있다.

Castle Windsor Adapter

Spring .NET Adapter

Unity Adapter

StructureMap Adapter

Autofac Adapter

MEF Adapter now on .NET Framework 4.0

LinFu Adapter

Multi-target CSL binaries

 

Visual studio에서 Nuget을 통해 CommonServiceLocator.WindsorAdapter을 프로젝트에 추가한다.

추가하였으면

다음 클래스를 작성하기 바란다.

파일명 : CommonServiceLocatorControllerFactory.cs

/// <summary>
/// CommonServiceLocator을 이용해서 DefaultControllerFactory를 구현한다.
/// </summary>
public class CommonServiceLocatorControllerFactory : DefaultControllerFactory
{
    /// <summary>
    /// 인스턴스된 Controller를 가져온다.
    /// </summary>
    /// <param name="requestContext">Request Context</param>
    /// <param name="controllerType">Typeof Controller</param>
    /// <returns>Controller object</returns>
    protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
    {
        //controllerType이 있으면
        if (controllerType == null)
            return base.GetControllerInstance(requestContext, controllerType);

        //ServiceLocator에  파라메터를 넘겨서 IController를 가져오도록 한다.
        return ServiceLocator.Current.GetInstance(controllerType) as IController;
    }
}

 

이제 Global에서 수정하면 IoC가 세팅이 될 것이다.

파일명 : Global.asax

public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();

            WebApiConfig.Register(GlobalConfiguration.Configuration);
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);
            AuthConfig.RegisterAuth();

 

//Windosr Container을 세팅한다.
//FromAssembly.This()를 실행하면  ControllersInstaller ( IWindsorInstaller 구현 클래스 )에 정의된 범위에서 Instance시킨다.
container = new WindsorContainer().Install(FromAssembly.This());

//ServiceLocator에 Provider을 등록한다.
ServiceLocator.SetLocatorProvider(() => new WindsorServiceLocator(container));
//ASP.NET MVC Framework에 ControllerFactory를 세팅한다.
ControllerBuilder.Current.SetControllerFactory(typeof(CommonServiceLocatorControllerFactory));

        }
    }

 

위와 같이 수정하고 실행해 보도록 하자.

정상적으로 페이지가 나오는 것을 확인할 수 있을 것이다.

 

그렇지만 의문이 들 것이다.

내가 세팅한 ServiceLocator가 작동이 된 것인지 아니면 기본적인 설정으로 된 것인지 알 수 없기 때문이리라.

그리하여 직접 Debug 모드에서 확인해 보도록 하겠다.

아래와 같이 CommonServiceLocatorControllerFactory.cs파일에서 return되기 직전에 breakpoint를 걸어 보도록 하겠다.

이제 F5를 눌러 실행시켜 보자.

페이지가 호출 될 때마다 해당 Controller 찾는 시도를 한다는 것을 알 수 있을 것이다.

그래서 이제는 정상적으로 동작하고 있다는 것을 확실 할 수 있을 것이다.

 

Tip!

CommonServiceLocator을 사용하기 위해서는 Windsor이 온전하게 동작을 한 상태에서 ServiceLocator을 연결해야 한다.

예로 Windsor IoC에서 Register을 이용해 어떤 DLL(범위 확정)에서 어떤 타입의 객체를 인스턴스 시켜줘야 하는지에 대한 작업이 필요 하다

이 작업은 IWindsorInstaller을 상속하여 구현한 클래스에 정의 되어 있다.

 

2013/01/25 - [ASP.NET MVC] - [IoC & DI]ASP.NET MVC 4에서 Windsor (Castle)로 IoC로 Controller 생성



BootStrapper.cs


 

ASP.NET MVC를 실행 할 때 Global.asax 파일에서 일부 설정 파일을 적용토록 하는 작업을 해 보았을 것이다.

ASP.NET MVC 4에서는 기본적으로는 아래와 같이 간소화 되어 Global.asax의 Application_Start()로 만들어 두었다.

파일명 : Global.asax

protected void Application_Start()
{
     AreaRegistration.RegisterAllAreas();

     WebApiConfig.Register(GlobalConfiguration.Configuration);
     FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
     RouteConfig.RegisterRoutes(RouteTable.Routes);
     BundleConfig.RegisterBundles(BundleTable.Bundles);
     AuthConfig.RegisterAuth();

}

위에서 Web API 설정, 필터 설정, 라우트 설정 등등 여러 가지 설정을 MVC Framework에 제공하는 기능으로 대체 하여 MVC의 기본 Configuration을 지정하였다.

그리고 무엇보다 중요한 기능은 외부 라이브러리를 추가하여(DLL 참조) 사용할 때 그 라이브러리의 환경 변수를 개발자가 설정하지 않고 기본 값을 자체적으로 설정하여 Default Behavior를 갖도록 할 수 있다는 것이다.

 

그렇지만 사용자가 임의의 수정하거나 변경을 하기 위해서는 Global.asax파일이 지저분 해지도록 할 수 밖에 없지만

지금 소개하는 WebActivator 라이브러리를 이용하면 파일 구조나 깊이에 상관없이 설정 또는 Business logic을 할 수 있다.

 

이제 본격적으로 소개하도록 하겠다.

“WebAativator”를 Nuget에서 검색하여 프로젝트에 추가한다.

그런 다음 아래와 같이 소스를 추가한다.

파일명 : /App_Start/BootStrapper.cs

( 임의로 BootStrapper.cs로 명명 하였다 )
오른쪽 그림과 같이 파일 추가                                                       

[assembly: WebActivator.PreApplicationStartMethod(typeof(TDDMVCTest.App_Start.BootStrapper), "PreStart")]
[assembly: WebActivator.PostApplicationStartMethod(typeof(TDDMVCTest.App_Start.BootStrapper), "PostStart")]
[assembly: WebActivator.ApplicationShutdownMethod(typeof(TDDMVCTest.App_Start.BootStrapper), "Shutdown")]

namespace TDDMVCTest.App_Start
{
    public static class BootStrapper
    {
        private static IWindsorContainer container;

        /// <summary>
        /// 최초 Web Server가 실행 할 때  Application Start가 되기 전에 실행 됨
        /// </summary>
        public static void PreStart() {

            //Business logic or configuration job
            var temp = string.Empty;
        }

        /// <summary>
        /// 최초 Web Server가 실행 되고 나서 Application End가 되고 나서 실행 됨
        /// </summary>
        public static void PostStart()
        {

            //Business logic or configuration job
            var temp = string.Empty;
        }

        /// <summary>
        /// Web Server가 종료되기 전에 실행 됨
        /// </summary>
        public static void Shutdown()
        {

            //Business logic or configuration job
            var temp = string.Empty;
        }
    }
}

위 실행 순서는 IHttpModule 상속하여 구현된 ActivationManager.cs에서 실행을 하며 전체 DLL을 읽어 들여 실행이 되게 하는 구조로 되어 있다.

- 클래스 명이나

- 네임 스페이스가 다름

- 외부 라이브러리로 선언

위와 같아도 Web Server가 실행이 되면 실행이 되는 구조다.

 

Tip !

    WebActivator.ActivationManager.RunPreStartMethods();
    WebActivator.ActivationManager.Run();
    WebActivator.ActivationManager.RunPostStartMethods();
    WebActivator.ActivationManager.RunShutdownMethods();

    위 메소드 실행으로 Activator 단계를 임의로 실행 할 수 있다.

    궁금하면 한번 테스트 해보기 바란다.

 

Tip !

    바로 이전 IoC 컨테이너 속성을 Activator을 이용해 초기화를 할 수도 있을 것이다. 바로 아래에 예시를 달도록 하겠다.

   2013/01/25 - [ASP.NET MVC] - ASP.NET MVC 4에서 Windsor (Castle)로 IoC로 Controller 생성 - 참조


using System;
using System.Web.Mvc;
using TDDMVCTest.Core;
using Castle.Windsor;
using Castle.Windsor.Installer;

[assembly: WebActivator.PreApplicationStartMethod(typeof(TDDMVCTest.App_Start.BootStrapper), "PreStart")]
[assembly: WebActivator.PostApplicationStartMethod(typeof(TDDMVCTest.App_Start.BootStrapper), "PostStart")]
[assembly: WebActivator.ApplicationShutdownMethod(typeof(TDDMVCTest.App_Start.BootStrapper), "Shutdown")]

namespace TDDMVCTest.App_Start
{
    public static class BootStrapper
    {
        private static IWindsorContainer container;

        /// <summary>
        /// 최초 Web Server가 실행 할 때  Application Start가 되기 전에 실행 됨
        /// </summary>
        public static void PreStart() {
            container = new WindsorContainer().Install(FromAssembly.This());

            var controllerFactory = new WindsorControllerFactory(container.Kernel);
            ControllerBuilder.Current.SetControllerFactory(controllerFactory);
        }

 

       ……..

    }

}


ASP.NET MVC 4에서 Windsor (Castle)로 IoC로 Controller 생성


Visual studio에서 Nuget을 통해 Windsor을 프로젝트에 추가한다.

위 그림은 Online에서 ‘Windsor’로 검색한 결과다.

 

Tip! - Windsor ver 3.1.0 – 2012.08.15

 

정상적으로 References에 참조 등록이 된 것을 확인하고

아래 파일을 추가한다.

 

파일명 : WindsorControllerFactory.cs
(DefaultControllerFactory를 상속받아 Windsor을 이용해 객체를 생성하는 기능을 구현한다)

    public class WindsorControllerFactory : DefaultControllerFactory
    {
        private readonly IKernel kernel;

        public WindsorControllerFactory(IKernel kernel)
        {
            this.kernel = kernel;
        }

        public override void ReleaseController(IController controller)
        {
            kernel.ReleaseComponent(controller);
        }

        protected override IController GetControllerInstance(System.Web.Routing.RequestContext requestContext, Type controllerType)
        {
            if (controllerType == null)
            {
                throw new HttpException(404, string.Format("The controller for path '{0}' could not be found.", requestContext.HttpContext.Request.Path));
            }

            return (IController)kernel.Resolve(controllerType);
        }
    }

 

파일명 : ControllersInstaller.cs
(어떤 Assembly에서 어떤 객체를 IoC 해줄지를 등록한다)

    public class ControllersInstaller : IWindsorInstaller
    {

        public void Install(Castle.Windsor.IWindsorContainer container, Castle.MicroKernel.SubSystems.Configuration.IConfigurationStore store)
        {
            container.Register(Classes.FromThisAssembly()
                                        .BasedOn<IController>()
                                        .LifestyleTransient());
        }
    }

 

위의 파일을 만든 다음

Global.asax 파일을 아래와 같이 수정한다.

파일명 : Global.asax.cs
( 컨테이너를 MVC Framework에 끼워 넣는다 )

    public class MvcApplication : System.Web.HttpApplication
    {
        private static IWindsorContainer container;

        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();

            WebApiConfig.Register(GlobalConfiguration.Configuration);
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);
            AuthConfig.RegisterAuth();

            container = new WindsorContainer().Install(FromAssembly.This());

            var controllerFactory = new WindsorControllerFactory(container.Kernel);
            ControllerBuilder.Current.SetControllerFactory(controllerFactory);
        }
    }

 

위와 같이 WindsorControllerFactory를 Global.asax 파일에 연결 시켰으면 이제 실행해 보도록 하자.

 

Ctrl + F5 –> 실행

위와 같이 화면을 정상적으로 보여주고 있다.

 

이제 이 프로젝트는 IoC 컨테이너(Windsor)를 통해 Controller 객체를 생성하여 실행이 되는 구조로 바뀐 것이다.

Web API - JSONP 호출 반환


Web API는 기본적으로 JSONP를 지원하지 않는다.


JSONP를 지원하기 위해서는 Fomatter를 추가하여 아래와 같이 해결 할 수 있습니다.


설정 파일


[코드] JSONP로 변환해주도록 설정 함




Business Logic


[코드] JSONP로 변환해주는 코드



HTML 코드


[코드] HTML에서 Javascript로 호출하여 확인하는 코드




위와 같이 세팅하면

Asp.net MVC 4에서 JSONP 형식으로 리턴이 된다.




+ Recent posts