Security Context Token(SCT) Expired on WCF

Posted in .NET Framework // Posted at 2015. 3. 3. 18:15
728x90

초단위로 반복적으로 WCF를 호출하는 응용프로그램이 있다.

이 WCF 응용프로그램의 바인딩은 wsHttpBinding을 설정하였으며 X.509 인증서기반의 Message 보안모드 상태에서 동작한다. 더불어 클라이언트 인증은 UserName 자격증명을 이용중이다.

 

참고로, WCF는 자체적으로 보안강화를 위해 안전장치를 몇 가지 마련해 두고 있는데, 대표적으로 reply 공격을 방어하기 위해 클라이언트와 서버시간이 일정시간(기본 5분) 이상 차이가 나면 서비스 호출이 불가능하도록 하는 것을 예로 들 수 있다.

 

이런 안전장치들은 WCF 보안 기능을 사용할 경우, 기본적으로 제공되는 것들이다.

따라서 간혹 보안기능에 문제가 발생할때 이런 기본사항을 인지하지 않고 있다면 문제해결이 어려운 경우가 있다. 물론 이러한 기본 보안장치들은 특정 비즈니스 환경에 적합하지 않을 수도 있기에 개발자에 의해 커스터마이징이 가능하다.

 

앞서 초단위로 반복 호출되는 WCF 응용프로그램에서, 어느날 다음과 같은 오류가 발견되었다.

이 오류가 초단위로 지속적으로 나타나고 있었다. 메시지 인증을 지속적으로 시도하여 UserName 자격증명을 처리하는 서비스 오퍼레이션이 불필요하게 많이 호출되는 것이다.

 

메시지 인증을 실행하지 못했습니다.
 서비스: http://mydomain/Service.svc
 동작: http://schemas.xmlsoap.org/ws/2005/02/trust/RST/SCT/Renew
 ClientIdentity:
 ActivityId: <null>
 SecurityContextTokenValidationException: context-id=urn:uuid:xxxxxxx-xxxx-xxxx-81bb-8218dddd5e8f(key generation-id=)을(를) 가진 SecurityContextSecurityToken이 등록되지 않았습니다. 

 

이 상황에서의, 클라이언트 측 HTTP 응답오류는 다음과 같다. 

HTTP/1.1 500 Internal Server Error
Content-Length: 663
Content-Type: application/soap+xml; charset=utf-8
Server: Microsoft-IIS/7.5
X-Powered-By: ASP.NET
Date: Tue, 03 Mar 2015 06:44:16 GMT

<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing"><s:Header>
<a:Action s:mustUnderstand="1">http://www.w3.org/2005/08/addressing/soap/fault</a:Action>

<a:RelatesTo>urn:uuid:2def471f-b90d-498b-85b3-edfcaee8dff1</a:RelatesTo></s:Header>
<s:Body><s:Fault><s:Code><s:Value>s:Sender</s:Value><s:Subcode><s:Value xmlns:a="http://schemas.xmlsoap.org/ws/2005/02/sc">a:BadContextToken</s:Value></s:Subcode></s:Code>
<s:Reason><s:Text xml:lang="ko-KR">보안 컨텍스트 토큰이 만료되었거나 잘못되었습니다. 메시지가 처리되지 않았습니다.</s:Text></s:Reason></s:Fault></s:Body></s:Envelope> 

 

WCF Trace Log를 남겨서 조사도해보고, 몇 가지 예상되는 시나리오를 가정하고 테스트를 해 봐도 쉽게 잡히질 않았다. 더욱이 멀티 쓰레드 환경에서 백그라운드로 실행되어, 명시적인 서비스 오퍼레이션 호출에 기인한 메시지 인증의 자동요청이라 패킷검사에서도 서비스 오퍼레이션 이름이 아닌, 인증토큰과 관련된 협상과정만 반복되어 디버깅에 더 애를 먹게 되었다.

 

유독 특정 사용자에게서만 발생하던 오류라 그 사용자의 PC환경과 응용프로그램 사용패턴을 들여다보고, 자료를 좀 찾아보니 예상되는것이 나온다.

 

Idle Session 상태에서 보안토큰의 만료시간으로 인한 토큰 검증에 실패한 것으로 추측된다.

그 사용자는 응용프로그램을 실행한 뒤, 한참을 그냥 두고 PC가 유휴상태에 있다가 다시 활성상태로 돌아와서 다시 사용하는 등 Idle Session 상태를 빈번히 유발하는 것이다.

 

구글링에서 몇 가지 좋은 아이디어를 얻을 수 있었다.

이제 서비스 환경과 정책에 맞도록 idle Session에 대한 보안토큰 만료 완화 혹은 명시적 거부를 구현하기만 하면 될 듯 하다. 이런 오류는 사람을 매우 피곤하게 하지만, 해결이 되면 그마이 기쁜것이 없다.

----------------------------------------------------

http://www.codeproject.com/Articles/156994/WCF-Secure-Channel-cannot-be-opened-Load-Balancing

 

http://stackoverflow.com/questions/912580/invalid-or-expired-security-context-token-in-wcf-web-service

 

http://stackoverflow.com/questions/16575799/invalid-or-expired-security-context-token-when-running-after-a-debugging-resta

 

http://stackoverflow.com/questions/1683724/what-are-the-impacts-of-setting-establishsecuritycontext-false-if-i-use-https

 

https://msdn.microsoft.com/ko-kr/library/ms731814(v=vs.110).aspx

https://msdn.microsoft.com/en-us/library/ms731346(v=vs.110).aspx
https://msdn.microsoft.com/ko-kr/library/ms731346(v=vs.110).aspx


http://forums.asp.net/t/1760793.aspx?establishSecurityContext+false+


https://social.msdn.microsoft.com/Forums/en-US/3faee9bb-e843-42fc-a634-f6feb786c698/expired-security-token?forum=wcf

 

------------------------------------------------------------

 

해당 오류에 대한 클라이언트 측 Exception 은 최초 키 갱신에 대한 오류 이후, MessageSecurityException 즉, 일반적인 메시지보안 오류가 지속된다.

SessionKeyExpiredException 은 private로, 타입비교가 불가능해, 굳이 해당 예외에 맞춤형 처리를 하고자 한다면 문자열로 변환해서 비교하면 될 듯 하다. 

System.ServiceModel.Security.SessionKeyExpiredException: 보안 세션 키를 갱신할 수 없습니다.

Server stack trace:
   위치: System.Runtime.AsyncResult.End[TAsyncResult](IAsyncResult result)
   위치: System.ServiceModel.Channels.ServiceChannel.SendAsyncResult.End(SendAsyncResult result)
   위치: System.ServiceModel.Channels.ServiceChannel.EndCall(String action, Object[] outs, IAsyncResult result)
   위치: System.ServiceModel.Channels.ServiceChannelProxy.InvokeEndService(IMethodCallMessage methodCall, ProxyOperationRuntime operation)
   위치: System.ServiceModel.Channels.ServiceChannelProxy.Invoke(IMessage message)

 

 System.ServiceModel.Security.MessageSecurityException: 상대방으로부터 보호되지 않거나 잘못 보호된 오류를 받았습니다. 오류 코드 및 세부 정보는 내부 FaultException을 참조하십시오. ---> System.ServiceModel.FaultException: 보안 컨텍스트 토큰이 만료되었거나 잘못되었습니다. 메시지가 처리되지 않

았습니다.
   --- 내부 예외 스택 추적의 끝 ---

Server stack trace:
   위치: System.Runtime.AsyncResult.End[TAsyncResult](IAsyncResult result)
   위치: System.ServiceModel.Channels.ServiceChannel.SendAsyncResult.End(SendAsyncResult result)
   위치: System.ServiceModel.Channels.ServiceChannel.EndCall(String action, Object[] outs, IAsyncResult result)
   위치: System.ServiceModel.Channels.ServiceChannelProxy.InvokeEndService(IMethodCallMessage methodCall, ProxyOperationRuntime operation)
   위치: System.ServiceModel.Channels.ServiceChannelProxy.Invoke(IMessage message)

InnerException Info>>
System.ServiceModel.FaultException: 보안 컨텍스트 토큰이 만료되었거나 잘못되었습니다. 메시지가 처리되지 않았습니다.

 

'.NET Framework' 카테고리의 다른 글

미스터 빈, 너로구나  (2) 2014.07.31
Caching in WCF  (6) 2014.02.21
ASP.NET SignalR  (6) 2013.12.06
maxConcurrentSessions in WCF  (4) 2013.11.21
SQL Double Split  (6) 2013.11.13

미스터 빈, 너로구나

Posted in .NET Framework // Posted at 2014. 7. 31. 16:57
728x90

netTcpBinding 기반의 WCF 서비스에 다수의 (논리적으로 구분할 수 잇는) 서비스가 구성되어 있다.

 

서비스는 IIS 호스팅 환경에 셋팅되어 있는데, (논리적인) 서비스 중 한 놈은 큰 용량의 자료를 클라이언트로부터 수신하여 처리하는 기능을 가지고 있는데 이 놈 때문에 다른 서비스의 질에 영향을 주고 싶지 않아 응용프로그램 풀을 분리하기로 결정했다.

 

IIS 상 웹사이트 하위 디렉터리를 응용프로그램으로 바꾸고 풀을 따로 설정한 후 실행하니 WCF 서비스가 동작하지 않는다.

 

문제는 라이브러리로 만들어진 서비스 dll을 찾지 못하는 것이다.

 

가만히 생각해보니, 하위 폴더를 독립된 응용프로그램 영역으로 만들었으니 이 폴더가 응용 프로그램 도메인 루트가 되어 더이상 웹 사이트 루트의 bin 폴더가 참조되지 않기 때문이다.

 

그래서 bin 폴더를 옮겨놓으니 잘 된다.

그러나 매번 빌드하고 bin 폴더를 옮기는 것은 매우, 아주, 심히, 귀찮은 일이다. 깜빡할 수도 있고...

 

VS의 빌드 이벤트는 이런 상황에 알맞은 해답을 제시해 준다. 바로 빌드이벤트 명령이다.

 

다음과 같이 빌드 후 이벤트 명령줄에 입력한다.

 

xcopy "$(TargetDir)*.*" "$(ProjectDir)\SubDirectory\bin" /Y /I

 

이젠 프로젝트를 빌드하면 하위 폴더에 자동으로 dll 들이 복사되어 신경 쓸 필요가 없게 되었다.

 

 

'.NET Framework' 카테고리의 다른 글

Security Context Token(SCT) Expired on WCF  (2) 2015.03.03
Caching in WCF  (6) 2014.02.21
ASP.NET SignalR  (6) 2013.12.06
maxConcurrentSessions in WCF  (4) 2013.11.21
SQL Double Split  (6) 2013.11.13

Caching in WCF

Posted in .NET Framework // Posted at 2014. 2. 21. 14:42
728x90

 

WCF 기반 서비스들이 공통으로 사용할 프레임워크를 만들면서, 오퍼레이션의 결과에 대한 캐싱 기능을 프레임워크 아키텍처에 포함 시키기로 했다.

 

서비스 오퍼레이션이 수행되기 전에 제어권을 받아 분기처리가 가능하도록 지원하는 IOperationInvoker 기반의 Caching Invoker를 제작하는데 캐시 API의 구현을 고민하게 되었다.

 

WCF에서도 ASP.NET의 Cache(System.Web.Caching.Cache)를 사용할 수 있지만, 이를 사용하기 위해서는 ASP.NET 호환성을 위해 서비스 클래스에 AspNetCompatibilityRequirements Attribute를 설정하고 Config 파일에 <serviceHostingEnvironment> 요소 aspNetCompatibilityEnabled 속성을 활성화 시켜 주어야 하는 등 프레임워크 연동에 제약을 가하게 되며, 더불어 이와같은 환경은 IIS 호스팅을 강제하기 때문에 셀프 호스팅 환경에서는 프레임워크의 캐싱 기능을 활용 할 수 없는, 즉 범용성의 저해가 발생해 버린다.

 

따라서 ASP.NET의 Cache는 적절치 못하다는 생각이다.

 

다른 방편으로, 전역 static 변수로 Dictionary 형태의 자료구조를 직접 관리할 수 있으나, 캐시 만료시간과 우선순위, Dependency 및 캐시 상태 변화에 따른 콜백 이벤트 등이 필요할 경우 이를 직접 구현해야 하니 역시 적절치 못하다 하겠다.

 

그러던 중, .NET 4.0에 추가된 MemoryCache라는 넘을 알게 되었다.

MSDN의 다음 한 구절이 나를 흥분(?)하게 만들었다.

ASP.NET Cache MemoryCache 클래스 사이의 주요 차이는 ASP.NET 응용 프로그램이 아닌 .NET Framework 응용 프로그램에서 사용할 수 있도록 MemoryCache 클래스가 변경되었다는 점입니다

 

왜 이걸 이제야 알게 되었단 말인가? 프레임워크 버전업에 따른 기본 API 학습을 게을리 한 탓이다. ㅡ,ㅡ;

MSDN을 찬찬히 훑어보니 딱! 원하던 그것이다.

 

WCF 서비스 기반에서 ASP.NET 호환성과 무관하게 캐싱 기능을 제공하기 위해 사용하기로 했다.

본격적인 적용 이전에 확인해야 할 사항은, 이 캐시가 ASP.NET 캐시처럼 응용 프로그램 도메인 내 전역 메모리를 사용하는가 하는 것이었다.

 

WCF 서비스가 매 호출마다 새로운 인스턴스를 만들거나 Caching Invoker가 매번 새롭게 생성된다면 MemoryCache를 인스턴스 범위를 넘어서도록 관리해야 한다.

 

간단히 테스트를 해보니 자연스럽게 응용 프로그램 도메인내에서 공유가 가능한 영역에서 캐시를 관리할 수 있다는 것을 확인하였다. 기본적인 활용 코드는 대략 다음과 같다.

 

ObjectCache cache = MemoryCache.Default;
CacheItemPolicy policy = new CacheItemPolicy();
policy.AbsoluteExpiration = aAbsoluteExpirationDateTime;
policy.SlidingExpiration = slidingExpirationTimeSpan;

                   

cache.Set(keyName, cacheValue, policy);

 

...

 

cache.Get(keyName);

 

 

'.NET Framework' 카테고리의 다른 글

Security Context Token(SCT) Expired on WCF  (2) 2015.03.03
미스터 빈, 너로구나  (2) 2014.07.31
ASP.NET SignalR  (6) 2013.12.06
maxConcurrentSessions in WCF  (4) 2013.11.21
SQL Double Split  (6) 2013.11.13

ASP.NET SignalR

Posted in .NET Framework // Posted at 2013. 12. 6. 16:34
728x90

오늘 우연히 다른 기술 자료를 검색하다, SignalR 이라는 라이브러리를 접하게 되었다. 실시간 웹 기능을 지원해 주는 ASP.NET 기반 오픈소스 라이브러리이다.

 

 

 

 

웹의 실시간 기능을 위한 다양한 기술과 기법이 사용되어 왔으나, HTML5에 와서야 Web Socket를 통한 진정한 양방향 실시간 통신이 가능해 졌다고 할 수 있다. 이 블로그에서도 관련 내용을 다룬바 있다.

 

=> [HTML5] Web Socket

=> [HTML5] Server-Sent Events

 

SignalR은 실시간 기능을 구현하기 위해 Web Socket을 사용하고 있으나, Web Socket를 지원하지 않는 환경을 위한 fallback 도 마련해 두고 있다.(이때 사용되는 기술이 Server-Sent Event, LongPolling과 같은 것들이다.)

(대부분의 환경을 지원하기 위한, 친절한 라이브러리가 아닐 수 없다. ^^)

 

다음 사이트들에서 SignalR의 개념과 소스, 응용 데모를 확인해 볼 수 있다.

 

눈이 번쩍 뜨여, 몇 가지 데모를 실행해 봤더니 놀라울 따름이다. 닷넷과 자바스크립트로 구성되어 실시간 웹 기능을 위한 서버와 클라이언트 측을 포괄하고 있다. 실시간 웹 기능을 위한 생산성과 안정성이 크게 향상될 것으로 보인다.

 

클라이이언트 측 Knockout.js와 묶으면 기가막힌 실시간 싱글 페이지를 매우 쉽게 구현할 수 있을 것 같다.

=> Knockout.js

 

이런 생각을 하고 있던 차에, 다음 블로그의 글을 발견했다. SignalR과 Knockout.js의 조합 뿐만 아니라 서버 측 자동 이벤트 발생을 위한 EF SqlDependency를 한데 묶어 데이터 변경 기반 실시간 웹 기능 사례를 보여주고 있다.

 

=> SignalR + SqlDependency + Knockout JS를 활용한 실시간 데이터 조회

 

환경적인 부분에서 참고할 사항은 SignalR이 어떤 통신 기반(WebSocket? Comet?..)으로 구동하는지는,

브라우저에 따라서도 다르지만 윈도우 서버의 IIS에 호스팅 될 경우 서버 버전도 염두해야 한다.

 

도우 서버 2012 또는 윈도우 8에서 WebSocket 프로토콜을 활성하 시킨 경우에만 웹 소켓을 기반으로 한다는 사실을 참고하자.

 

IIS외에 셀프 호스팅도 가능하니 다음 URL에서 간단한 힌트를 얻을 수 있겠다.

=> Signalr selfhosting

 

시간이 갈수록 개발자가 점점 편해지고 있는 듯 하다. 장/단이 있으며 양면성이 있으나, 개발자가 아닌 툴 이용자로 전락하지 않기 위한 노력이 필요하겠다.

 

'.NET Framework' 카테고리의 다른 글

미스터 빈, 너로구나  (2) 2014.07.31
Caching in WCF  (6) 2014.02.21
maxConcurrentSessions in WCF  (4) 2013.11.21
SQL Double Split  (6) 2013.11.13
Optimizing IIS Performance  (8) 2013.11.07

maxConcurrentSessions in WCF

Posted in .NET Framework // Posted at 2013. 11. 21. 15:05
728x90

일전에 WCF 서비스 스로톨링(Service Throttling)의 기본 값을 알아본 바 있다.

=> http://m.mkexdev.net/189

 

 

 

 

 

스로톨링 항목 중, 최대 연결 세션 수를 제한하는 maxConcurrentSessions 라는 속성이 있는데, 이 값이 세션을 사용하지 않는 바인딩 환경에서도 제한값으로 동작하는지 궁금해 졌다.

 

WCF 서적을 보니, 트랜스포트 세션/보안 세션/신뢰 세션 등을 사용하지 않는 바인딩에서는 이 속성은 의미없다라고 설명하고 있으나, 어떤 인터넷 자료에서는 그렇지 않다는 예기가 있다.

 

상식적으로 생각해 봐도, 세션에 기반하지 않는 바인딩이라면 세션 제한 수가 무의미한게 맞는 듯 하지만 확실히 해두기 위해 테스트를 수행해 보았다.

 

두 개의 WCF 서비스를 만들고, 하나는 basicHttpBinding 으로 나머지 하나는 wsHttpBinding 기반의 신뢰세션을 사용하도록 한 뒤 세션 제한 수를 5로 설정하고 클라이언트에서 각각 10번씩 호출해 본다. 이때 세션 제한 수에 걸리도록, 프록시 객체를 닫지(Close) 않도록 한다.

 

//WCF Service 설정

<serviceThrottling maxConcurrentSessions="5" /> 

 

//클라이언트 코드

for (int i = 0; i < 10; i++)
{
       ServiceReference1.Service1Client proxy = new ServiceReference1.Service1Client();                
       Console.WriteLine(string.Format("{0}-{1}",i,proxy.GetData(7)));
       //proxy.Close(); //세션 제한수에 걸리도록 프록시를 닫지 않는다.
}

 

결과는 예상했던 대로, 세션을 사용하지 않는 basicHttpBinding은 maxConcurrentSessions 제한 값과 무관하게 10번 모두 호출되었다. 반면 신뢰 세션을 사용하도록 한 wsHttpBinding은 이 제한값에 영향을 받아 5번 호출되고 대기하다가 결국 Timeout 오류가 발생했다.

 

그래서 이렇게 결론을 내렸다.

: 세션을 사용하지 않는 바인딩 환경에서는 maxConcurrentSessions 제한 값이 무의미하다.

 

좀 더 테스트 해보기 위해, wsHttpBinding에서 신뢰세션을 제거하고 테스트를 해 보기로 했다.

wsHttpBinding에서 신뢰 세션을 제거하면 결국 그 어떤 세션도 사용하지 않게 되므로 basicHttpBinding와 같이 제한 값이 무의미해져야 하는데, 테스트 결과는 여전히 5번 호출 뒤 대기하는 것이 확인되었다.

 

이해할 수 없는 결과로 몇 번의 삽질을 거듭하다가 문득, wsHttpBinding의 기본 보안 모드가 message이고 클라이언트 신원증명이 윈도우계정이라는 것이 떠올라 해법을 찾게 되었다.

 

간혹, wsHttpBinding 바인딩을 사용하는 WCF 서비스를 개발할 때, 본인의 PC에서는 잘 동작하던 것이 서비스를 원격 서버로 이동하면 보안문제가 발생하는 것을 경험해 보았을 것이다. 이것이 바로 wsHttpBinding가 기본적으로 Message 보안 모드에 윈도우 계정에 의한 클라이언트 자격증명이 이뤄지기 때문에 그런 것이다. 즉 로컬 환경에 서비스와 클라이언트가 모두 존재할 경우 윈도우 인증이 자동으로 가능해지기 때문에 로컬에서는 문제가 없었던 것이다. 필자 역시 과거 이러한 경험을 반추하여 위와 같은 해법을 찾게 된 것이다.

 

결국 wsHttpBinding에 기본 모안보드를 변경하기 위해 명시적으로 다음과 같이 설정을 하고 테스트를 해 보니 basicHttpBinding과 동일하게 10번 모두 정상 호출되었다.

<wsHttpBinding>
     <binding name="myBinding">
         <security mode="None"></security>
      </binding>
</wsHttpBinding>

 

즉 결론은 동일하다.
: 세션을 사용하지 않는 바인딩 환경(basicHttpBinding, wsHttpBinding 모두)에서는 maxConcurrentSessions 제한 값이 무의미하다.

(참고: wsHttpBinding 일 경우, 보안모드를 명시적으로 None로 설정해야 보안세션이 사용되지 않는다.) 

 

 

'.NET Framework' 카테고리의 다른 글

Caching in WCF  (6) 2014.02.21
ASP.NET SignalR  (6) 2013.12.06
SQL Double Split  (6) 2013.11.13
Optimizing IIS Performance  (8) 2013.11.07
클라이언트의 동시 연결 수(maxconnection)  (6) 2013.11.06

SQL Double Split

Posted in .NET Framework // Posted at 2013. 11. 13. 18:38
728x90

SQL에서 Split 함수 구현은 쉽게 찾을 수 있다. 그런데 프로젝트 진행 중, 내가 필요한 것은 더블 스플릿이다.

(더블 스필릿이라는 공식 명칭은 없다. 그냥 내 맘대로 붙여 먹은 이름이니 어디가서 들먹이면 안됨!)

 

즉 하나의 구분자로 문자들을 분리하고, 분리된 문자에는 또 다시 구분자가 있어 다시 스필릿 해야 하는 구조.

대략 다음과 같이...

 

: key1=value1,key2=value2,key3=value3

 

위 예시에서 첫번째 구분자는 콤마(,)이고 두번째 구분자는 이꼴(=)이다.

이 값을 차례대로 분리해서 테이블 형태로 반환하고 싶은 것이다.

 

과거 훌륭한 DBA로 부터 수급한 싱클 스플릿 함수 구현체에, 꾸역꾸역 더블 스플릿이 가능하도록 끼워넣었다.

사소하게 기뿌다 ^^;

 

ALTER FUNCTION [dbo].[UFN_DoubleSplit] (@STRMORE AS VARCHAR(8000),@STRDELIMETER1 AS VARCHAR(10),@STRDELIMETER2 AS VARCHAR(10)) 
RETURNS @RETURN_TABLE TABLE
(
    idx int identity(1,1)
,   strVALUE1 VARCHAR(500)
,   strVALUE2 VARCHAR(500)
)
AS
BEGIN
DECLARE
    @NINDEX   INT
, @NINDEX2  INT
,   @DEL_LENGTH INT
, @SUBSET  VARCHAR(100)
,   @STRVALUE1  VARCHAR(100)
,   @STRVALUE2  VARCHAR(100)
 
SET @DEL_LENGTH = LEN(@STRDELIMETER1)
 
WHILE LEN(@STRMORE) > 0
    BEGIN
   
        SET @NINDEX = CHARINDEX(@STRDELIMETER1, @STRMORE)
 
        IF (@NINDEX = 0)
        BEGIN      
   SET @NINDEX2 = CHARINDEX(@STRDELIMETER2, @STRMORE)
   SET @STRVALUE1 = SUBSTRING(@STRMORE, 0, @NINDEX2)
   SET @STRVALUE2 = SUBSTRING(@STRMORE, @NINDEX2 + 1, LEN(@STRMORE))      
   INSERT @RETURN_TABLE (strVALUE1,strVALUE2) VALUES (@STRVALUE1,@STRVALUE2)
   RETURN
        END       
        ELSE IF (@NINDEX = 1)
        BEGIN
   SET @STRMORE = SUBSTRING(@STRMORE, @DEL_LENGTH + 1, LEN(@STRMORE))
   CONTINUE
        END
 
        SET @SUBSET = SUBSTRING(@STRMORE, 0, @NINDEX)
        SET @NINDEX2 = CHARINDEX(@STRDELIMETER2, @SUBSET)
        SET @STRVALUE1 = SUBSTRING(@SUBSET, 0, @NINDEX2)
  SET @STRVALUE2 = SUBSTRING(@SUBSET, @NINDEX2 + 1, LEN(@SUBSET))                                               
        INSERT @RETURN_TABLE (strVALUE1,strVALUE2) VALUES (@STRVALUE1,@STRVALUE2) 
                 
        SET @STRMORE = SUBSTRING(@STRMORE, @NINDEX + @DEL_LENGTH, LEN(@STRMORE) - @NINDEX)   
    END
RETURN
END

 

 

2014.02.21 업데이트>>

이 글을 작성하고 난 뒤 'Double Split'를 프로젝트에 적용해서 잘 사용하던 중, Split 해야 할 또 하나의 값이 추가되어 버렸다.

 

즉,  Double Split 기준의 매개변수인 'key1=value1,key2=value2,key3=value3' 형태가 다음처럼 바뀌었다.

: key1=value1&value11,key2=value2&value22,key3=value3&value33

 

그렇다면, Triple Split를 다시 구현할 것인가???

이런.. 유연성이 전혀 없잖어. 또 다른 기준 값이 추가되면 역시 힘겹게 수정해야 한다. 사실 이 글에서 제시한 Split 구현이 그리 깔끔하거나 쉽게 확장가능한 코드가 아니다.

 

결국 XML형태로 매개변수를 입력 받고 파싱해서 테이블로 반환하기로 했다. 구현이 훨씬 깔끔해져 나중에 수정을 하려고 해도 쉽게 가능해 진다.

 

Double Split 이여 안녕~~

 

CREATE FUNCTION [dbo].[UFN_ConvertFromXmlToTable] (@XMLString AS NVARCHAR(MAX))
RETURNS @RETURN_TABLE TABLE
(
    idx int identity(1,1)
,   strValue1 VARCHAR(500)
,   strValue2 NVARCHAR(100)
,   intValue  BIGINT
)
AS
BEGIN
 declare @xml xml = cast(@XMLString as xml)
    
 INSERT @RETURN_TABLE (strValue1,strValue2,intValue)
  select
     x.value('data(@strValue1[1])', 'VARCHAR(500)')
   , x.value('data(@strValue2[1])', 'NVARCHAR(100)')
   , x.value('data((text())[1])', 'BIGINT')
   from
    @xml.nodes('/s') x(x)

RETURN
END

  

 그리고 이렇게 활용하면 된다.

SELECT * FROM dbo.[UFN_ConvertFromXmlToTable]('<s strValue1="1" strValue2="11">1000</s><s strValue1="2" strValue2="22">2000</s><s strValue1="3" strValue2="33">3000</s>')

 

 

'.NET Framework' 카테고리의 다른 글

ASP.NET SignalR  (6) 2013.12.06
maxConcurrentSessions in WCF  (4) 2013.11.21
Optimizing IIS Performance  (8) 2013.11.07
클라이언트의 동시 연결 수(maxconnection)  (6) 2013.11.06
비동기 프로그래밍에서의 메모리 누수  (6) 2013.11.06

Optimizing IIS Performance

Posted in .NET Framework // Posted at 2013. 11. 7. 15:27
728x90

다음 글은 MSDN의 다음 글을 발번역한 것이다.

 

> Optimizing IIS Performance

 

IIS 7.0을 기준으로 하고 있지만, 생뚱 맞게 ASP 가이드가 웬말이냐...

어쨋던 ASP.NET에서도 유사한 설정이 있으니 참고할만하며, ASP.NET 가이드 부분은 실질적 도움이 된다.

 

 

 

 

 

IIS 성능 최적화

 

IIS 성능 향상을 위한 구성 옵션 적용

IIS는 성능에 영향을 미치는 다양한 구성 매개변수를 노출한다. 이 토픽에서는 이러한 매개변수 몇 가지를 설명하고 IIS 성능 향상을 위해 필요한 매개변수 설정의 일반적인 지침을 제공한다.

 

* 로그는 필수적인 정보만 저장하거나 전혀 사용하지 않도록 하라.

프로덕션 환경에서는 IIS 로깅을 최소화 하거나 비활성화 해야 한다. 다음 단계를 따르라.

단계 요약: IIS 관리자>해당 사이트>로깅>사용 안 함 설정

 

 

* 프로덕션 환경에서는 IIS ASP 디버깅 기능을 비활성화 시켜라.

프로덕션 환경에서는 IIS ASP 디버깅을 사용하지 말아야 한다. 다음 단계를 따르라.

 

단계 요약: IIS 관리자> 사이트> ASP> 컴파일> 디버깅속성> 서버 쪽 디버깅 사용, 클라이언트 쪽 디버깅 사용 항목을 false로 설정

 

ASP.NET 응용프로그램 또는 웹 서비스의 디버깅 비활성화는 web.config 파일의 <compliation debug=”false” />로 설정

 

 

* 프로세서 당 ASP 스레드 제한 값을 조정하라.

ASP 스레드 제한 속성은 프로세서 당 생성할 수 있는 작업 스레드의 최대값을 나타낸다. 이 값을 프로세서 사용률이 적어도 50% 이상 충족할 때까지 값을 높여라. 이 설정은 웹 응용프로그램의 확장성과 전반적인 서버의 성능에 많은 영향을 미칠 수 있다. 이 값은 동시에 실행할 수 있는 ASP 요청의 최대 값을 정의하기 때문에 당신의 ASP 응용 프로그램이 외부 구성요소로 확장된 요청들을 하지 않는 한 기본 값을 유지해야 한다. 확장된 요청을 하는 경우라면, 값을 높이는 것이 좋다. 이렇게 하면 서버가 더 많은 동시 요청을 처리하기 위해 더 많은 스레드를 만들 수 있다. 기본 값은 25이다. 이 속성의 최대 권장 값은 100이다. 다음 단계를 따르라.

 

단계 요약: IIS 관리자>서버>ASP>제한속성>프로세서 당 스레드 제한 값을 원하는 값으로 설정(기본: 25)

 

IIS 7.0ASP 제한 설정에 대한 더 더 많은 정보는 다음 URL에서 확인

http://www.iis.net/configreference/system.webserver/asp/limits

 

노트1> 이 속성은 서버 레벨에서 적용할 수 있기 때문에, 값의 수정은 서버의 모든 웹 사이트에 영향을 미친다.

노트2> IIS 7.0의 이 속성은 IIS 6.0 ASPProcessorThreadMax ASP Metabase 세팅을 대체한다

 

 

* ASP 큐 길이 조정하라.

이 값을 설정하는 이유는 ASP 요청 큐가 찼을 때 HTTP 503(Server Too Busy)가 반환되는 빈도를 최소화 하면서 좋은 응답성을 보장하기 위함이다. 만일 큐 길이가 너무 낮으면, 서버는 503 오류를 자주 뱉어 내게 될 것이다. 반면 큐 길이가 너무 높으면, 요청이 큐에 대기하는 동안 사용자는 서버가 응답하지 않는다고 인식할 것이다. 높은 트래픽 상황에서 큐를 주의 깊게 살펴보고 피크 타임의 대기 요청 수를 기록하고 이 값보다 높은 값으로 큐 길이를 설정해야 한다. 응답 시간을 보장하고 단기적인 스파이크를 처리하기 위해 큐를 사용하며 지속적인 예상치 못한 스파이크가 발생할 때 과부하를 방지하기 위해 이 값을 조정한다. 당신이 큐 길이 제한에 대한 데이터가 없는 경우, 총 스레드 큐의 일대일 비율로 설정하는 것을 권장한다. 예를 들어 4개의 프로세서 환경에서 프로세서당 ASP 스레드 수의 제한 값이 25일 경우 4 * 25 = 100으로 설정하고 여기서부터 출발하는 것이다. 큐 길이 조정을 위한 다음 단계를 수행하라

 

단계 요약: IIS 관리자>서버>ASP>제한 속성>큐 길이에서 원하는 값 지정

 

노트1> 이 속성은 서버 레벨에서 적용할 수 있기 때문에, 값의 수정은 서버의 모든 웹 사이트에 영향을 미친다.

노트2> IIS 7ASP 큐 길이 속성은 IIS 6.0 AspRequestQueueMax ASP Metabase 셋팅값을 대체한다.

 

 

* MaxPoolThreads 레지스터리 항목을 조정하라.

이 값은 CPU당 풀에 생성할 수 있는 스레드 수를 나타낸다. 풀 스레드는 들어오는 요청을 감시하고 처리한다. MaxPoolThreads 수는 ISAPI 응용프로그램에서 소비하는 스레드는 포함하지 않는다. 일반적으로 프로세서당 20개 이상의 스레드를 생성할 필요는 없다. 이 설정은 레지스터리 HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\InetInfo\Parameters\ REG_DWORD 값으로 지정할 수 있으며 기복 값은 4로 설정되어 있다.

 

 

* WCF 서비스 트레이싱을 비활성화 시켜라.

프로덕션 서버에 WCF 구성 설정 편집기(SvcConfigEditor.exe)를 사용하여 트레이싱을 비활성화 시켜라.

이 툴에 대한 더 자세한 정보는 다음 url에서 확인

http://msdn.microsoft.com/en-us/library/ms732009.aspx

 

 

* IIS 7.0 통합모드를 위한 ASP.NET MaxConcurrentRequests를 구성하라.

IIS 7.0 통합모드에서 ASP.NET 2.0이 호스트 되는 경우, 스레드 처리가 클래식 모드나 IIS 6.0과는 다르게 처리된다. 이 경우 ASP.NET 2.0은 동시 실행 스레드 수 대신 동시 실행 요청 수를 제한한다. 동기 시나리오에서는 요청 수가 스레드 수와 동일하기 때문에 간접적으로 스레드 수를 제한할 것이다. 그러나 비동기 시나리오에서는 스레드 수 보다 훨씬 더 많은 요청이 있을 수 있기 때문에 요청수와 스레드 수가 많이 다를 수 있다. ASP.NET 2.0 IIS7.0 통합모드에서 실행될 경우, machine.config 파일의 httpruntime 요소의 minFreeThreads minLocalRequestFreeThreads 값이 무시된다. IIS 7.0 통합모드에서는 레지스터리의 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\ASP.NET\2.0.50727.0에 있는 MaxConcurrentRequestsPerCPU 값에 의해 CPU당 동시 요청 수를 결정하게게 된다. 기본적으로 이 레지스터리 키는 존재하지 않으며 기본 값은 CPU 12개 이. net framework 3.5 SP1에는 aspnet.config 파일을 통해 IIS 응용프로그램 풀 구성을 할 수있는 2.0 바이너리에 대한 업데이트가 포함되어 있다. 이 파일은 통합모드에서만 유효하다.(IIS 6.0 혹은 클래식 모드일 경우 이 파일의 설정은 무시된다.) 이 새로운 설정 파일의 기본 값은 다음과 같다.

 

<system.web>

   <applicationPool maxConcurrentRequestsPerCPU="12" maxConcurrentThreadsPerCPU="0" requestQueueLimit="5000"/>

</system.web>

 

경험상으로 봤을 때, MaxConcurrentRequestsPerCPU 값은 5000 정도로 큰 값으로 설정해야 한다.

IIS 7.0 통합모드에서는 machine.config 파일의 processModel 요소의 maxWorkerThreads, maxIOThreads 설정은 본질적으로 실행중인 요청 수를 제어하는데 사용되지 않는다. 그러나 여전히 ASP.NET에 의해 사용되는 CLR 스레드 풀의 사이즈를 제어하는 데는 사용된다.

machine.config 파일의 processModel 섹션의 autoConfig 설정이 true(기본 값)일 경우, 응용프로그램 풀에 (논리적인) CPU당 최대 100까지의 스레드(MaxWorkerThreads)를 생성할 수 있다. 2 듀얼 코어 CPU일 경우에는 400개가 된다. This should be sufficient for all but the most demanding applications.

 

IIS 7.0 6.0 ASP.NET 스레드 활용법을 위한 더 많은 정보는 다음 사이트에서 확인 하시길.

http://msdn.microsoft.com/en-us/library/ee377050%28v=bts.10%29.aspx

 

 

* HTTP 압축을 사용하라.

대역폭을 보다 효율적으로 사용하고 싶다면 IIS HTTP 압축을 활성화하라. 압축을 사용하면 콘텐트가 로컬 저장소에 있거나 UNC 리소스이거나 관계없이 (압축을 허용하는) 브라우저와 IIS 간 더 빠른 전송 효과를 볼 수 있다.

(단계 요약: IIS 관리자>서버(또는 사이트)>압축>원하는 압축 옵션 설정)

 

 

 

'.NET Framework' 카테고리의 다른 글

maxConcurrentSessions in WCF  (4) 2013.11.21
SQL Double Split  (6) 2013.11.13
클라이언트의 동시 연결 수(maxconnection)  (6) 2013.11.06
비동기 프로그래밍에서의 메모리 누수  (6) 2013.11.06
OAuth 2.0 Flow  (6) 2013.09.05

클라이언트의 동시 연결 수(maxconnection)

Posted in .NET Framework // Posted at 2013. 11. 6. 16:41
728x90

응용프로그램의 성능 저하 문제는 사소하게 간과한, 몇 가지 이유로 유발되는 경우도 있다.

여기서 언급하고자 하는 것도 그러한 것 중 하나이다.

 

바로 클라이언트의 동시 연결 수 제한에 따른 성능 저하 현상이다. 닷넷 응용프로그램에서 원격 호스트와 통신할 때 적용되는 동시 연결 수의 제한인데, 특별히 값을 지정하지 않는 경우(기본 값) 2개의 연결만 허용하게 된다.

 

보편적인 (단순한) 환경이라면 이 제한 값은 문제 되지 않을 것이다. 하지만 매우 바쁜 호스트가 멀티 쓰레드 환경에서 원격시 서버와 통신을 빈번히 하는 경우라면 예기가 달라진다.

 

특히 두 호스트가 Server to Server 방식이라면 이 제한 값은 문제될 소지가 높아진다. 

 

예를 들어, A 서버가 클라이언트의 요청을 받아서 작업을 수행하는데 이 작업 중에서 원격지 서버와 통신하는 부분이 있다고 가정해보자. 이때 A 서버는 그 자체가 서버 응용프로그램이기 때문에 멀티 쓰레드 환경에서 요청을 처리하게 되는 경우(이 말은 꼭 개발자가 직접 작성한 멀티 쓰레드 프로그램만을 말하는 것은 아니다. 프로그램이 얹혀지는 호스팅 환경 자체에서 풀을 기반으로 동시 처리를 하게 되는 경우도 포함한다. 이러한 사례는 웹 응용프로그램이 대표적이다.)가 많으며 요청이 과도하게 몰릴 경우 원격 서버와 통신이 그 만큼 빈번하게 이뤄지게 된다. 이런 환경에서 최대 동시 연결 수가 2개 밖에 되지 않는다면 병목 현상으로 이어질 확률이 높다. 특히 원격지 서버의 작업 수행 시간이 길어진다면 연결을 위한 대기 시간은 점점 늘어나게 될 것이다.

 

더욱 문제는 이 값의 설정이 기본적으로 노출되어 있지 않다는 데 있다. 닷넷 응용프로그램의 config 파일에는 몇 가지 기본 설정들이 자동으로 포함되는데 이 설정 값은 그렇지 않아, 기본 값인 2가 사용되도록 되어 있다.

 

이러한 이유로 많은 개발자들이 이 값의 설정을 간과하게 되며, 심지어 이러한 설정이 있는지 모르는 경우도 많다. 복잡한 환경에서 서버의 응답 속도가 느려 진다는 것은 참으로 많은 구간을 확인해야 하는 일이다. 네트워크, 시스템, DB, 응용프로그램 등 다양한 구간에 가능성을 두고 분할 정복해야 한다.

 

특히 Server to Server 통신 환경에서, 원격 서버의 응답 지연 현상이 반드시 원격 서버만의 문제가 아닐 수 있다는 가능성도 열어 두길 바란다. 앞서 시나리오라면 클라이언트의 설정 값의 변경만으로도 성능 개선이 많이 이뤄질 수도 있다. (물론 원격 서버의 응답시간 자체는 허용 범위 내에 있다는 가정을 둔다.)

 

이런 시나리오를 간단히 테스트 해 보자. 

 

* 원격 서버: ASP.NET MVC Web API 로 기본 Getter 구현. 의도적으로 응답을 지연 시킨다.

 

* 클라이언트: 콘솔 어플리케이션으로 멀티 쓰레드로 원격 서버의 Getter 호출(대략 다음의 코드)

   for (int i = 0; i < 20; i++)
   {
           Thread thread = new Thread(new ThreadStart(Start));
            thread.Start();  //원격 서버 호출하는 스레드 시작
    }

 

 

1. 기본 값을 사용한 경우

특별히 maxconnection 설정을 추가하지 않은 경우, netstat 나 TCPView 프로그램으로 연결 수를 확인해 보면 20개의 요청이 동시에 이뤄지지만, 연결 수는 단 2개만 존재하는 것을 확인할 수 있다.

 

 

 

2. maxconnection을 20으로 설정한 경우

<system.net>
    <connectionManagement>
       <add address="*" maxconnection="20"/>
    </connectionManagement>
</system.net>

 

클라이언트 프로그램의 설정 파일에 위와 같이 maxconnection을 설정하고 다시 확인해 보면 20개의 요청만큼 동시 연결이 되어 있는 것을 확인할 수 있다.

 

 

앞서 2개의 연결만으로 20개의 원격 호출을 처리하는 것보다, 20개의 연결으로 20개의 원격 호출을 병렬적으로 처리하는 것이 훨씬 응답성을 높일 것이다.

 

참고로 이 값의 정해진 규칙은 없다. 응용프로그램의 시스템 환경과 사용량을 파악해 적절한 값을 지정해야 할 것이다. 다만 Server to Server 방식에서 멀티 쓰레드로 원격 통신이 빈번한 환경이라면 기본 값인 '2'는 너무 작은 값임에는 분명하다 하겠다. 

 

 

'.NET Framework' 카테고리의 다른 글

SQL Double Split  (6) 2013.11.13
Optimizing IIS Performance  (8) 2013.11.07
비동기 프로그래밍에서의 메모리 누수  (6) 2013.11.06
OAuth 2.0 Flow  (6) 2013.09.05
Custom Configuration  (1875) 2013.08.26

비동기 프로그래밍에서의 메모리 누수

Posted in .NET Framework // Posted at 2013. 11. 6. 11:24
728x90

응용프로그램의 성능, 확장성 향상의 이유로 비동기 프로그래밍 방식이 자주 사용되곤 한다.

 

닷넷 프레임워크에서도 스레드(전용 스레드 or 스레드 풀) 혹은 비동기 프로그래밍 모델을 활용해 비동기 작업을 수행할 수 있다.

 

여기서 비동기 프로그래밍 모델을 APM(Asynchronous Programming Model)이라 칭하는데, 닷넷 프레임워크의 I/O 관련 클래스들은 모두 이 모델을 이용할 수 있도록 설계되어 있다.

 

 

I/O를 위한 동기 방식 메서드 이름과 동일한 BeginXXX, EndXXX 쌍의 비동기 메서드가 제공되는데, 다음과 같은 I/O 클래스들에서 이를 활용할 수 있다.

 

System.Net.WebRequest

System.IO.Stream

System.Data.SqlClient.SqlCommand

System.Net.Sockets.Socket

 

필자의 회사에서 운영하는 시스템도 많은 곳에시 비동기 패턴을 이용하고 있다. 그 중 한 예로, 여러 시스템이 혼재되어 하나의 서비스를 완성하는데 이때 각 시스템의 구간 안정성 현황을 파악하기 위해 원격 서버로 로그를 전송하는 로직이 비동기로 구현되어 있다.

 

그런데 이 로그는 실제 비지니스와는 무관한 부분이라(크로스 커팅 기능), 종속성을 배제하기 위해 로그 서버는 단방향 서비스로 구현하고 이를 소비하는 클라이언트는 APM 방식으로 구현되어 있다.

 

그런데 문제는 로그 저장의 성공/실패에 관심을 두지 않는다는 것이, 비동기 호출 후 콜백을 받지 않는 구현으로 이어졌다는 것이다. 즉 BeginXXX만 호출하고 EndXXX는 구현하지 않은 것이다.

 

사용량이 많지 않는 환경에서는 이것이 그리 큰 문제가 되지는 않는다. 하지만 필자의 시스템 환경은 Server to Server 방식으로 로그서버를 소비하는 클라이언트 역시 서버 응용프로그램이며 이 서버 응용프로그램에 수많은 요청이 동시에 몰리면 메모리 누수는 점차 누적되어 프로그램이 심각한 타격을 입을 수 있다는 것이다.

 

다음의 글에서 밝히고 있듯이, 비동기 호출 결과에 관심이 없다고 해서 EndXXX를 호출하지 않아도 된다는 것은 아니니 주의해야 할 일이다.

 

개발자는 EndXXX 메서드를 꼭 호출해야 한다. 그렇지 않으면 리소스가 누수되기 때문이다. 일부 개발자는 데이터 장치에 자료를 저장하기 위해서 BeginXXX를 호출하고, 이후의 결과 값이나 진행 상태에 관심이 없다면 EndXXX를 호출하지 않는 경우를 종종 봐왔다. 하지만 EndXXX를 호출해야 하는 이유는 두 가지가 있다.

 

첫째 CLR은 이 리소스들을 EndXXX 메서드가 호출될 때까지 유지시킨다. 그리고 EndXXX 메서드가 결국 호출되지 않는다면 이 리소스들은 계속 반환되지 않다가 프로세스가 종료될 때에 반환된다. 둘째, 비동기 작업을 초기화할 때 실제로 개발자는이 작업이 성공할 것인지 알지 못한다. 성공유무를 확실하게 확인하는 방법은 EndXXX 메서드를 호출하는 것이며, 이 메서드를 호출함으로써 결과 값이 정상적으로 반환되는지 아니면 예외가 발생하는지 알 수 있다.

- CLR via C#

 

 

'.NET Framework' 카테고리의 다른 글

Optimizing IIS Performance  (8) 2013.11.07
클라이언트의 동시 연결 수(maxconnection)  (6) 2013.11.06
OAuth 2.0 Flow  (6) 2013.09.05
Custom Configuration  (1875) 2013.08.26
간단한 C# 문법 Quiz  (5) 2013.07.11

OAuth 2.0 Flow

Posted in .NET Framework // Posted at 2013. 9. 5. 16:45
728x90

OAuth 2.0 스펙이 이전 버전과 비교해 덩치가 많이 커져 버린 단점(?)이 있지만, 기본 연동 모델 자체는 보다 심플해진 것 같다. 데이터의 암호화와 무결성 체크를 HTTS에 의존하기 때문에 보안을 위한 추가 장치들이 제거된, 보다 심플해진 흐름을 볼 수 있다.

 

Custom Configuration

Posted in .NET Framework // Posted at 2013. 8. 26. 15:13
728x90

닷넷 기반 응용프로그램은 (그것이 웹이든 콘솔 또는 윈도우 응용프로그램이든) 런타임에 구성 정보를 변경할 수 있도록 하는 설정파일이 존재한다.

 

웹 기반 응용프로그램의 경우 web.config, 윈도우 기반은 App.config 파일이 설정 파일 역할을 하게 된다.

 

1. Built-in AppSettings

이 설정파일에 기본적으로 제공되는 설정값 아닌 응용프로그램 자체에서 요구하는 별도의 설정 정보가 필요할 경우 대부분 <appSettings> 섹션에 의존하곤 한다.

 

<appSettings> 섹션은 '키-값' 형태의 설정 정보를 하나 이상 등록하여 사용할 수 있으며, AppSettingsReader 라는 닷넷 프레임워크가 기본으로 제공하는 리더기에 의해 엑세스 할 수 있다. 

 

* config 파일 설정:

<appSettings>
    <add key="configKeyUsingAppSetting" value="config[Value]UsingAppSetting"/>
</appSettings>

 

* conifig 값 액세스 :

AppSettingsReader appSettingsReader = new AppSettingsReader();
object valueOfAppSetting = appSettingsReader.GetValue("configKeyUsingAppSetting", typeof(string));
if (valueOfAppSetting != null)
{
     string value = valueOfAppSetting.ToString();
     Console.WriteLine(value);   //output: config[Value]UsingAppSetting

 

 

2. CustomSection Using Built-in Type

대부분의 소규모 프로젝트에서는 <appSettings> 섹션만을 사용해서 원하는 커스텀 설정값을 관리할 수 있을 것이다. 그러나 조금 더 구조적인 구분이 필요하거나 큰 프로젝트에서는 설정값의 성격에 따라 카테고리를 분리해서 관리해야 할 경우도 있다.

 

이럴 경우 제일 간단하게 접근할 수 있는 것이 내장된 타입을 이용한 커스텀 섹션을 정의하는 것이다.

이럴 경우 섹션값을 원하는 이름으로 정할 수 있기 때문에 응용프로그램의 설정 정보를 좀 더 스마트하게 구조화할 수 있게 된다.

 

커스텀 섹션을 정의하기 위해서는 <configSections> 요소에 원하는 이름의 섹션을 정의하면 된다.  이때 섹션의 타입을 닷넷 프레임워크가 제공하는 내장 타입을 지정할 수 있다. 

(사소한 주의사항은 <configSections>요소를 정의할 경우 <configuration>의 첫 번째 자식으로 위치해야 한다는 것이다)

 

실제 설정 값은 정의된 섹션의 이름으로 지정할 수 있으며 ConfigurationManager클래스를 통해 설정값을 액세스할 수 있게 된다. 다음 코드는 이러한 예를 보여준다.

 

* config 파일 설정:

<configSections>  
    <section name="mySection" type="System.Configuration.NameValueSectionHandler"/>
</configSections>

 

<mySection>
    <add key="configKeyUsingCustomSection" value="config[Value]UsingCustomSection"/>
</mySection>

 

* config 값 액세스

NameValueCollection valueOfCustomSection =

                                    (NameValueCollection) ConfigurationManager.GetSection("mySection");

string value = valueOfCustomSection["configKeyUsingCustomSection"];
Console.WriteLine(value);  //output: config[Value]UsingCustomSection

 

 

3. CustomSection Using Custom Type

보다 큰 프로젝트인 경우 설정 값 역시 복잡한 구조를 띄게 된다. 앞서 살펴본 설정 정보들은 '키-값' 형태의 단순한 구조만을 지원하므로 복잡한 구조를 수용할 수 없다.

 

간혹, 키 값 형태의 구조틀안에서 한 단계 구조를 더 첨가(?)하기 위해 구분자를 활용하기도 한다.

대략 다음과 같은 형태를 띄게 되는데, 값을 정의하는 영역에 별도의 서브키를 부여하고 구분자로 이를 잘라내서 사용하곤 한다.

<add key="key" value="subKey-value1:subKey2-value2;subKey3-value3....."/>

 

이런 형태의 사용 패턴이 잘못 되었다고는 할 수 없지만, 구조적인 측면과 유지보수성 측면에서 봤을 때 좋은 점수를 줄 수 없다. 그 이유는 설정 값의 패턴 준수를 위한 신경을 써야 하는 점과 구분자로 사용된 문자가 실제 값과 중복되지 않음이 보장되어야 하는 점 등을 들 수 있겠다.

 

좀 더 객체지향적인 접근과 유지보수성을 좋게 하기 위해서는 커스텀 객체를 기반으로 설정 정보가 동작되도록 구성하는 것이 좋다.

 

간단한 샘플을 작성해 보자.

먼저 설정 정보를 추상화 시킨 클래스를 정의한다. 여기서는 '사람'의 정보를 추상화 시킨 클래스를 정의하겠다. 이때 이 클래스가 설정 정보로 사용되기 위해서는 ConfigurationSection을 상속받아야 한다.

그리고 클래스의 멤버들은 ConfigurationProperty Attribute로 선언하며 여기서 실제 config파일의 속성으로 사용할 이름과 기본값/필수유무에 대한 속성값들을 지정한다.

 

다음 코드는 이름, 나이, 결혼유무라는 속성을 가진 PersonInfoSection 클래스를 정의하고 있다.

public class PersonInfoSection : ConfigurationSection
{
        [ConfigurationProperty("isMarried", DefaultValue = "false", IsRequired = false)]
        public bool IsMarried
        {
            get
            {
                return (Boolean)this["isMarried"];
            }
            set
            {
                this["isMarried"] = value;
            }
        }

        [ConfigurationProperty("age", IsRequired = true)]
        public short Age
        {
            get
            {
                return (short)this["age"];
            }
            set
            {
                this["age"] = value;
            }
        }

        [ConfigurationProperty("name", IsRequired = true)]
        public string Name
        {
            get
            {
                return (string)this["name"];
            }
            set
            {
                this["name"] = value;
            }
        }

}

 

이렇게 정의한 클래스 정보를 이전과 같이 커스텀 섹션에 추가하고 사용할 수 있게 된다.

* config 파일 설정

<configSections
      <section
          name="personInfoSection"
          type="CustomConfiguration.PersonInfoSection, CustomConfiguration"
       />  
  </configSections>

 

<personInfoSection name="박종명"  age="20" /> <!-- isMarried는 선택값으로 생략 가능 -->

 

* config 값 액세스

CustomConfiguration.PersonInfoSection personInfo =

  (CustomConfiguration.PersonInfoSection) ConfigurationManager.GetSection("personInfoSection");


 

Console.WriteLine(string.Format("이름:{0}, 나이{1}", personInfo.Name, personInfo.Age));

 


만일 PersonInfoSection의 멤버가 앞서 정의한 값(이름, 나이, 결혼유무)의 범위를 넘어서 또 다른 복합값을 가져야 할 경우 ConfigurationElement 상속받은 클래스를 정의하고 이 타입을 멤버로 사용하면 된다.


예를 위해서 PersonInfoSection 클래스에 자식 정보를 추가한다고 가정해 보자. 다음 코드와 같이 자식의 이름과 나이를 위한 클래스를 정의한다.


public class ChildrenElement : ConfigurationElement

{

        [ConfigurationProperty("name", DefaultValue="Arial", IsRequired = true)]

        public String Name

        {

            get

            {

                return (string)this["name"];

            }

            set

            {

                this["name"] = value;

            }

        }


        [ConfigurationProperty("age", IsRequired = true)]

        public short Age

        {

            get

            {

                return (short)this["age"];

            }

            set

            {

                this["age"] = value;

            }

        }

}


그리고 이렇게 정의한 클래스 타입을 PersonInfoSection 멤버로 사용할 수 있게 된다.

public class PersonInfoSection : ConfigurationSection

    {

       

        .........................


        [ConfigurationProperty("children")]

        public ChildrenElement Children

        {

            get

            {

                return (ChildrenElement)this["children"];

            }

            set

            { this["children"] = value; }

        }

    }

 

마지막으로 config파일에 자식 정보를 추가하고 이를 액세스 할 수 있다.

<personInfoSection name="박종명" age="20" isMarried="true">

    <children name="박길동" age="10" />

</personInfoSection>

 .....

Console.WriteLine(

 string.Format("자식이름:{0}, 자식나이{1}", personInfo.Children.Name, personInfo.Children.Age));


마지막으로 설정파일의 속성으로 사용할 클래스 멤버를 정의할 때,

ConfigurationProperty Attribute를 통해 기본 값(DefaultValue)이나 필수유무(IsRequired)를 지정할 수 있는 것과 더불어 [Type]Validator (StringValidator, IntegerValidator, ...) Attribute를 통해 유효한 문자, 숫자 범위등을 지정하는 등 좀 더 디테일한 제어가 가능하다.


마무리하며....

응용프로그램에서 필요로 하는 설정 정보를 분리하고 이를 잘 구조화해서 사용하면 코드의 직관성이나 유지보수성을 좋게 한다. 앞서 살펴본 대로 설정 정보를 구조화하는 몇 가지 방법이 있는데, 늘 사용하던 편리한 방법도 나름의 가치를 지니겠으나, 복잡한 설정 정보를 유지해야 할 경우 객체 기반으로 동작하는 코드 구성을 고민해 보길 바란다.

 

 

 

간단한 C# 문법 Quiz

Posted in .NET Framework // Posted at 2013. 7. 11. 16:16
728x90

Name 이라는 속성을 가진 Person 클래스가 있다고 가정한다

 

그리고 다음과 같이 ChangeName 메서드를 호출한 후 콘솔에 찍히는 Name 값은??

ChangeName(Person person)
{
        Person newPerson = new Person();
        newPerson.Name = "개명";            
        person = newPerson;
}

 

.....

 

Person person = new Person();
person.Name = "본명";
ChangeName(person);

 

Console.WriteLine(person.Name);  //Name 값은??

 

간단하지만 자칫 헷갈리기 쉬운 문제다.

 

그럼. 이 질문은 어떤가?

 

질문> 객체를 '참조에 의한 전달'로 해야 할 경우는 언제인가??

 

객체 자체는 참조타입으로 메서드 호출 시에도 그 참조가 전달된다. 그런데 명시적으로 참조로 전달해야 할 경우란 어떤 경우인가 하는 것이다.

 

사실 이 문제는 과거에 한번 정리했던 적이 있다.

=> 참조형식을 참조로 전달???

 

중요한 멘트를 하나 남기고 싶어서 다시 끄적여 본다

 

"참조값 자체는 값에 의해 전달 된다"

 

좀 더 풀어서 적어보면, "참조 자체는 값에 의해 전달된다"

더 풀어보면 "참조에 의한 전달에서 참조 값 자체는 값에 의한 복사가 이뤄진다"

 

말 장난 같은 이 말은, 사실 혼돈을 정리하는 핵심 문구이다. ㅎㅎ

'.NET Framework' 카테고리의 다른 글

OAuth 2.0 Flow  (6) 2013.09.05
Custom Configuration  (1875) 2013.08.26
나에게 유용했던 닷넷 서적  (6) 2013.07.04
WCF Service Reference Error(Cannot import wsdl:portType)  (4) 2013.07.03
IOC using Ninject  (2) 2013.06.18

나에게 유용했던 닷넷 서적

Posted in .NET Framework // Posted at 2013. 7. 4. 08:11
728x90

개인적으로 책으로 학습하는 것을 좋아한다.

 

저자의 혼신의 노력이 들어간 책이야말로 그 어떤 학습도구보다도 진실성과 책임감이 있기 때문이다.

그리고 지식을 체계적으로 정립하는 데 책 만한 것이 없다.

 

그간, 관심 있는 여러 기술 분야의 책들을 봐 왔다. 그 중 닷넷 분야의 책을 몇 권 소개할까 한다.

닷넷 책도 수 십권을 봐 왔지만, 여기서 소개하는 책들은 개인적으로 감명을 받았거나 가치를 높이 두거나 어떤 식으로든 나에게 유익하고 많은 도움이 되었던 책들을 엄선한 것이다.

 

온전히 개인적인 기준으로 선정한 것이며, 내가 접한 책들 중에서만 엄선한 것이다.

내가 못 본 책 중에서도 많은 훌륭한 책들이 있을 것이며, 반대로 여기에 소개되지 않은 내가 본 책들도 그 만의 가치는 다 가지고 있다. 모든 책에서 적어도 한 가지 이상은 배울 수 있으며 절대적인면에는 모두 가치 있는 것이다.

 

 

 PROGRAMMING MICROSOFT ASP.NET

 (디노 에스포시토 저/김태영 역 | 정보문화사)

 

정말 감명 깊게 본 책이었다. 2005년도 경에 본 것 같은데, 그 내용이 너무 인상적이어  서 발음이 쉽지 않은 저자 이름을 의도적으로 기억하기까지 했다. 이전에 봐 왔던 ASP.NET 책과는 수준이 다른 책으로 ASP.NET의 내부를 마음껏 음미해 볼 수 있는 좋은 기회를 제공해 주었다. 아주 훌륭한 책이다


 

 

 

 PROGRAMMING MICROSOFT ASP.NET

 (David Sceppa 저/이용훈 역 | 정보문화사)

 

2004년 경에 구입해서 본 것으로 기억한다. 비즈니스 응용프로그램 환경에서 데이타베이스는 땔 수 없는 존재라 닷넷 프레임워크 기반의 ADO의 구현이 궁금해서 구입한 책이었다. 당시 ADO.NET 책이 흔하지 않은 상황에서 선택할 수 있는 카드였고 역시 기대에 저버리지 않았었다. 좋은 책이다.

 

 

 

 

 C#을 이용한 윈도우 폼 프로그래밍

 (크리스 셀즈 저/김지선,탁남수 역 | 피어슨에듀케이션코리아(PTG))

 

2006년 경에 본 것으로 기억한다. 당시 닷넷 기반 윈도우 응용프로그램 제작이 주요 임무라 보게 된 책이다. 윈폼의 원리와 다양한 기술 기반을 체계적으로 습득할 수 있는 좋은 기회였다. 책을 보면서 실무에 적용을 많이 하게 된, 현실적으로 도움이 많이 된 책이었다. 고마운 책이다.

 

 

 

 

 GDI+ Programming

 (에릭 화이트 외 2인 공저/류광 역 | 정보문화사)

 

윈도우 응용프로그램에서 사용할  컨트롤이 필요했으며 나아가 닷넷 기반의 그래픽 프로그래밍을 제대로 배워보고 싶어서 보게 된 책이다. 닷넷 기반의 GDI만을 다룬 서적의 거의 유일한 선택이 아닌가 한다. GDI에 대한 무지를 깨우친 계기를 준 책이며 몇 가지 유용한 실무 적용 기법을 배울 수 있었다.

 

 

 

 

 실버라이트 2 인 액션

 (차드 캠벨,존 스탁턴 공저/박용우,석재헌 공역 | 위키북스)

 

2009년경에 실버라이트로 웹 게임 개발을 하게 된 적이 있다. 그때 본 책인데 내용이 심플하고 깔끔했다. 인터넷 자료로만 접하던 실버라이트 제작 기술을 체계적으로 배울 수 있는 기회였다. 쉽고 깔끔한 책이다.

 

 

 

 ASP.NET 2.0 Website Programming

 (마르코 벨리나소 저/송호중 역 | ITC)

 

2007년에 구입해서 재밌게 본 책이다. ASP.NET 2.0에 새로이 추가된 웹파츠, 멤버십과 프로필, 테마 등의 학습이 필요해서 보게되었으며 책의 진행 방식인 '문제분석 -> 설계 -> 제작'의 흐름을 재미있게 따라 갔던 기억이 난다. 현실적인 도움이 많이 된 책이며 책의 구성과 흐름이 흥미로왔던 책이다.

 

 

 IT EXPERT ASP.NET 2.0

 (조성진 저 | 한빛미디어)

 

역시 2007년에 ASP.NET 2.0 신규 기능들을 습득하고자 위 책과 같이 구매한 책이다. 한국책 특유의 친숙함이 있었으며 실무에 도움이 많이 되었던 책이었다.

 

 

 

 

 

 Taeyo's ASP.NET v1.0 with C#

 (김태영 저 | 영진닷컴)

 

ASP, ASP.NET 으로 이어지는 매우 유명한 태오 시리즈이다. 2004년경에 봤었는데 태오 특유의 쉬운 설명과 위트가 돋보이는 책이었다. 물론 기술의 빠른 습득에도 도움이 많이 된 책이다. 간혹 태오 책은 학문적(?) 깊이가 부족하다고 예기하는 경우가 있는데 나는 그렇게 생각하지 않는다. 한 권의 책으로 모든 것을 달성할 수는 없다. 태오 책은 빠른 실무 적용을 원하는 개발자들에게 쉽게 닷넷 웹 기술을 습득할 수 있도록 도와준다. 학문적 깊이를 원한다면 그 이상은 개인의 몫이지 책의 책임은 아니라고 본다. 기술 습득의 진입 장벽을 낮춰 초보 개발자들의 길을 터 주는 의미있는 책이라고 평가하고 싶으며 초보의 시각에서 예기를 풀어나가는 것도 쉽지 않은 능력이다.

 

 

 

 닷넷 웹 서비스 원리와 구현

 (키스 밸린저 저/엄익천 외 공역 | 피어슨에듀케이션코리아(PTG))

 

2006년 경에 보게 된 책인데, 기존에 그냥(?) 만들었던 닷넷 웹 서비스를 이론적 기반을 알고 만든 첫 번째 계기를 준 책이다. 웹 서비스의 이론적 지식을 습득하기에 좋은 책이다.

 

 

 

 CLR via C#

 (Jeffrey Richter 저/송기수 역)

 

닷넷, C#, CLR... 깊이와 개념, 원리를 원한다면 이 책은 필수다. 수 년전 구입 후 지금까지도 늘 찾아보게 되는 책이다. 저자의 깊이 있는 지식에 감명을 받게 된다. 이 책은 보면 볼 수록 가치가 높이 평가되는 부류의 책이다. 실무 개발 중, 뭔가 제대로 풀리지 않던 문제가 이 책의 힌트로 풀린 경우가 많다. 그건 바로 원리에 대한 이해다.

매우 훌륭한 책이다. 참고로 저자 서문에 자신의 아들이 직접 타이핑한 외계문자(?)를 그대로 실은 위트가 인상적이었다.

 

 

 C#과 닷넷 플랫폼

 (Andrew Troelsen 저/장시형 역 | 지앤선(志&嬋))

 

역시 C#과 닷넷 프레임워크에 대한 원론적 내용을 다루는 책이다. 위의 CLR via C#과 같이 보면 웬만한 이론적 지식, 원리는 모두 섭렵할 수 있다. 역시 훌륭한 책이다.

 

 

 

 

 Effective C#

 (빌 와그너 저/김명신 역 | 한빛미디어)

 

유명한 Effective 시리즈이다. 코드의 효율을 높이고 응용프로그램의 성능과 안정성을 높이기 위한 가이드를 제시한다. 고급 개발자가 되기 위해서 필수로 봐야 할 만큼 심도 있는 주제를 다루고 있다. 역시 훌륭한 책이라 하겠다.

 

 

 

 Professional Enterprise.NET

 (존 아킹,스콧 밀렛 공저/장현희 역 | 제이펍)

 

말 그대로 엔터프라이즈 개발자가 되기 원한다면 꼭 봐야 하는 책이다. 저자가 서문에서 밝혔듯이 본인 역시 코드를 잘 짜는 스마트한 개발자에서 엔터프라이즈 개발자로 변모 하면서 필요했던 지식을 가르치는 책이다. 개발자를 넘어 아키텍터가 되길 원한다면 이 책을 봐야 한다고 과감히 예기할 수 있다. 훌륭한 책이다.

 

 

 유수석의 WCF 바이블

 (유경상 저 | 한빛미디어)

 

2011년에 구입해서 매우 흥미롭게 본 책이다. 2007년에 WCF를 처음 접하면서 그 이후 많은 자료로 학습을 해 왔지만 그 중 최고봉이라 하겠다. 저자가 한국 IT업계에서 경험이 많은 사람이라 그 내용이 매우 현실적이며 실무에 많은 도움이 되는 책이다. 부담스러울 정도로 내용이 꼼꼼하여 많은 걸 배울 수 있었다. 한국 사람이 지은 몇 안되는 훌륭한 기술 서적이라 평가하고 싶다. WCF로 뭔가를 해 보고 싶다면 반드시 보기를 권장한다. 여담이지만 WS-Federation 내용이 빠진 건 조금 아쉽다.

 

 

 프로 ASP.NET MVC 프레임워크

 (스티븐 샌더슨 저/김태영,송원석 공역 | 비제이퍼블릭(BJ퍼블릭))

 

2010년에 구입한 것으로 ASP.NET MVC를 책으로 접한 첫 사례다. Web Form에서 ASP.NET MVC로 이어지는 패러다임 자체가 아주 흥미로왔으며 책 내용도 매우 알차다. MVC의 개념을 제대로 잡아 준 고마운 책이다.

 

 

 

 

 

 프로 ASP.NET MVC 3 프레임워크

스티븐 샌더슨,애덤 프리먼 공저/김태영,김홍석,송원석,안중희 공역 | 비제이퍼블릭(BJ퍼블릭))

 

위의 책과 동일한 저자와 출판사에서 나온 ASP.NET MVC 상위 버전을 다루는 책이다.

역시 앞 선 책의 기대를 저버리지 않는 훌륭한 내용과 구성이 돋보인다. ASP.NET MVC를 제대로 다루고 싶다면 반드시 봐야 할 책이라 하겠다.

 

   

 

'.NET Framework' 카테고리의 다른 글

Custom Configuration  (1875) 2013.08.26
간단한 C# 문법 Quiz  (5) 2013.07.11
WCF Service Reference Error(Cannot import wsdl:portType)  (4) 2013.07.03
IOC using Ninject  (2) 2013.06.18
Security in OAuth  (4) 2013.05.29
728x90

잘 동작하는 WCF 서비스를 웹 프로젝트에서 참조하려고 하니, 다음과 오류를 뱉으면서 참조가 되지 않는다

 

사용자 지정 도구 경고: wsdl:portType을(를) 가져올 수 없음
세부 정보: WSDL 가져오기 확장을 실행하는 동안 예외가 발생했습니다.
System.ServiceModel.Description.DataContractSerializerMessageContractImporter
오류: 파일이나 어셈블리 'DotNetOpenAuth.AspNet, Version=4.0.0.0, Culture=neutral, PublicKeyToken=2780ccd10d57b246' 또는
여기에 종속되어 있는 파일이나 어셈블리 중 하나를 로드할 수 없습니다. 지정된 파일을 찾을 수 없습니다.

 

 

 

이 WCF 서비스는 클라이언트 테스트 용으로 콘솔 응용프로그램과 윈도우 응용프로그램에서 모두 참조해서 잘 사용하던 것이었다.

 

오류를 내뱉은 이번 사례는 'ASP.NET MVC4 인터넷 응용프로그램'이다.

혹시나 해서 ASP.NET MVC3 인터넷 응용프로그램으로 WCF 참조를 시도해 보니.. 이건 잘 된다. 음.. 뭥미..

 

게다가 ASP.NET MVC4 Web API'로 시도해 보니, 이건 안되긴 하는데 오류 내용이 조금 다르다

사용자 지정 도구 경고: wsdl:portType을(를) 가져올 수 없음
세부 정보: WSDL 가져오기 확장을 실행하는 동안 예외가 발생했습니다.
System.ServiceModel.Description.DataContractSerializerMessageContractImporter
오류: 파일이나 어셈블리 'EntityFramework, Version=4.4.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' 또는
여기에 종속되어 있는 파일이나 어셈블리 중 하나를 로드할 수 없습니다. 지정된 파일을 찾을 수 없습니다.

 

결국 WCF를 소비하는 클라이언트가 ASP.NET MVC4 일 경우 (조금씩 다르긴 하지만) 이와 같은 문제가 발생 하는 것을 확인하였다

 

이것은 WCF를 참조할 때 기본으로 설정되어 있는 '참조된 모든 어셈블리의 형식 재사용' 옵션 때문이라 하는데, 이 옵션을 제거하면 더 이상 참조 오류는 발생하지 않는다

 

어셈블리 재사용 옵션은 클라이언트에 동일 타입이 이미 존재할 경우, WCF 참조로부터 자동으로 생성되는 타입에서 제외되어 기존 클라이언트의 것을 재사용할 수 있도록 하는 편리한 기능인데 이 부분이 문제가 된다니 의아스럽다. 이 옵션의 자세한 내용은 아래 블로그 글에서 확인할 수 있다.

 

> 관련 글: VS.NET 2008 - 서비스 참조시 기존 데이터 컨테이너 DLL 사용

 

그리고 오류 내용을 보면, DotNetOpenAuth.AspNet 어셈블리의 종속성 문제인 듯 하여 관련 어셈블리만 재사용하지 않도록 설정해도 문제는 동일하게 발생한다.

 

결국 몇 번 시도 끝에 문제가 되는 어셈블리를 찾게 되었다.

ASP.NET MVC4 인터넷 응용프로그램일 경우: Microsoft.Web.WebPages.OAuth

 

ASP.NET MVC4 Web API일 경우:  System.Web.Providers

 

각각의 환경에서 위에 나열한 어셈블리만 재사용에서 제외해 버리면 잘 동작한다

아래 화면은 ASP.NET MVC4 인터넷 응용프로그램에서 Microsoft.Web.WebPages.OAuth를 제외한 모습이다

 

 

뭐.. 개별 프로젝트의 현상과 바로 적용 가능한 적절한 해결책은 찾았으나, 근원적인 원인에 대한 이해가 필요하다. 일단 여기까지 하고, 다음 기회에 알아 보기로 한다. 

 

 

 

'.NET Framework' 카테고리의 다른 글

간단한 C# 문법 Quiz  (5) 2013.07.11
나에게 유용했던 닷넷 서적  (6) 2013.07.04
IOC using Ninject  (2) 2013.06.18
Security in OAuth  (4) 2013.05.29
Bit Flag of Enum  (4) 2013.05.28

IOC using Ninject

Posted in .NET Framework // Posted at 2013. 6. 18. 23:40
728x90

모니터링 시스템에 알림을 구현하는 독립된 서비스가 존재한다.

이 서비스는 Email, SMS, Messenger와 같은 수단으로 알림 전송을 대행해 주는 역할을 하고 있다.

 

대략적인 내부 구현은,

Mail의 경우 사내 SMTP 서버를 이용하여 메일 전송을 수행하며, SMS의 경우 3rd Party에서 제공하는 모듈을 이용하고 Messenger의 경우 사내 메신저가 노출하는 연동 인터페이스로 메시지 전송을 수행한다.

 

서비스는 잘 동작하고 있지만, 문제는 환경의 변화에 유연하지 못하다는 것이다.

다시말해 Mail, SMS, Messenger의 연동 환경이 달라진 환경에 알림 서비스가 설치되어야 한다면 서비스의 내부 코드도 같이 변경되어야 하고 다시 빌드해서 배포해야 한다는 것이다.

 

따라서 알림 서비스의 유연성을 좋게 하기 위해 인터페이스 기반으로 동작하도록 하는 것이 첫번째 할 일이다.

(코드를 단순화 하기 위해 Email 전송 부분만을 예로 든다. 구차한 매개변수도 역시 생략한다)

 

* NotificationService

//이메일 전송을 위한 인터페이스를 선언한다

public interface IEmailSender
{
     string Send();
}

 

 

//메일 전송기 클래스를 생성한다

//IEmailSender를 매개변수로 받음으로써 모듈간 커플링을 제거한다

public class MailService
{
     private IEmailSender _sender;

     public MailService(IEmailSender sender)
     {
         this._sender = sender;
     }

     public string Send()
     {
         return this._sender.Send();
     }

}

 

이제 실제 이메일 전송을 수행하는 구상 클래스를 생성한다. 이 구상클래스는 환경에 따라 달라질 수 있는 부분으로 교체가능한 모듈이 된다

* EmailSenderA

//메일 전송은 환경에 따라 달라질 수 있는 부분이므로'A' 타입 전송으로 가정한다

public class EmailSenderA : NotifactionService.IEmailSender

{
     string NotifactionService.IEmailSender.Send()
     {
         return "EmailSender-A";
     }
}

 

이제 메일 서비스를 사용해보자.

* NotifcationAgent

//메일 서비스를 이용해서 메일 전송을 수행하는 콘솔 어플리케이션이다

static void Main(string[] args)
{
       NotifactionMethodA.EmailSenderA sender = new NotifactionMethodA.EmailSenderA();
       MailService mailService = new MailService(sender);
       string result = mailService.Send();
       Console.WriteLine(result);

}

 

동작은 잘 하지만, 역시 구상 객체에 의존하게 된다(new NotificationMehodA.EmailSenderA())

 

이제 IOC 컨테이너 유틸리티인 Ninject가 개입할 시기다. Ninject 라이브러리를 참조하고 아래와 같이 코드를 수정한다

static void Main(string[] args)
{
       IKernel ninjectKernel = new StandardKernel();
       ninjectKernel.Bind<IEmailSender>().To<NotifactionMethodA.EmailSenderA>();
       NotifactionService.MailService mailService = ninjectKernel.Get<NotifactionService.MailService>();
       string result = mailService.Send();
       Console.WriteLine(result);

}

 

역시 잘 동작한다. 그러나 좀 더 유연하게 하고 싶다. 위 코드에서도 EmailSenderA가 슬그머니 한 자리를 차지하고 있다. 이것마저 제거하고 싶어 졌다. 즉 Config 파일에 구상객체 정보를 설정해서 보다 유연하게 만드는 것이다. 다음 주소에서 훌륭한 코드를 볼 수 있었다.

http://www.stum.de/2009/12/30/loading-a-type-specified-in-web-config-for-example-a-ninject-module/

 

 

먼저 실제 구상객체가 표현되는 Ninject 바인딩 부분을 NotificationAgent와 분리시킨다

* NinjectMoudleA

public class NinjectMoudleA: NinjectModule
    {
        public override void Load()
        {
            Bind<IEmailSender>().To<NotifactionMethodA.EmailSenderA>();
        }
    }

여기까지만 해도 꽤나 유연해진 모습이다. 필요시 Dll을 교체해서 환경 변화를 수용할 수 있다.

 

그러나 조금 더 나아가 Config에서 위 모듈을 동적으로 불러오도록 변경해 보자.

NotifcationAgent에 다음과 같이 KernelCreator라는 클래스를 생성한다. 이 클래스는 Config 파일로부터 (앞서 생성한) NoinectMoudle을 동적으로 불러들여서 Ninject 커널 객체를 반환한다.

//Config 파일에서 실제 구상객체를 바인딩하는 어셈블리를 로딩하고

public class KernelCreator
{
        public IKernel Create()
        {
            AppSettingsReader appSettingsReader = new AppSettingsReader();
            string moduleName = (string)appSettingsReader.GetValue("ninjectModule", typeof(string));
           
            Type moduleType = Type.GetType(moduleName);

            NinjectModule module;
            if (moduleType != null)
            {
                module = Activator.CreateInstance(moduleType) as NinjectModule;
            }
            else
            {
                throw new Exception(string.Format("Could not find Type: '{0}'", moduleName));
            }

            return new StandardKernel(module);
        }
}  

 

Config 파일 설정은 다음과 같다.

* App.Config

<appSettings>
    <add key="ninjectModule" value="MyNinjectModule.NinjectModuleA, MyNinjectModule" />
  </appSettings>

 

이제 NotificationAgent는 다음과 같이 작성할 수 있다.

static void Main(string[] args)
{
        IKernel ninjectKernel = new KernelCreator().Create();
        NotifactionService.MailService mailService = ninjectKernel.Get<NotifactionService.MailService>();
        string result = mailService.Send();
        Console.WriteLine(result);

}

 

이제 변화에 유연하도록 구성이 완료되었다.

 

만일 환경이 달라저서 다른 이메일 전송 방법을 사용해야 한다면, 다시말해 NotifactionMethodB로 교체되어야 한다면 다음의 과정을 거치면 된다

 

1. MailService의 IEmailSender 인터페이스를 구현한다(NotifactionMethodB)

2. NinjectMoudleB를 생성해 Ninject 바인딩을 정의한다

3. Config 파일을 수정한다

 

이 과정이 복잡해 보일 수 있으나 모든 시스템 환경에 적용할 필요는 없을 것이다. 유연성 사치를 부릴 필요가 없는 시스템이라면 위의 전체 과정에서 몇 가지를 제거할 수도 있다. 위 과정은 이미 개발된 시스템의 변경을 최소화하고 확장을 유연하게 할 수 있도록 하는 만큼 시스템 환경에 따라 적절히 적용할 수 있을 것이다.

 

 

 

'.NET Framework' 카테고리의 다른 글

나에게 유용했던 닷넷 서적  (6) 2013.07.04
WCF Service Reference Error(Cannot import wsdl:portType)  (4) 2013.07.03
Security in OAuth  (4) 2013.05.29
Bit Flag of Enum  (4) 2013.05.28
TimeStampHasCreationTimeInFuture in WCF Security 2  (4) 2013.05.23