오늘 소개하려는 Visual Studio 확장 기능은 멀티 타기팅 팩의 기능을 매우 적극적으로 활용하는 똑똑한 확장 기능이다. 실무에서 3.5세대 혹은 4세대 닷넷 프레임워크를 적극적으로 사용 중인 개발자들이 국내에도 많이 있을텐데 이 경우 숟한 고민에 부딪히게 되는 것 중에 하나는 다중 플랫폼을 지원해야 한다는 점이다. 대충 열거해봐도 세 가지 이상은 된다. ASP.NET, Windows Presentation Foundation, Silverlight, 경우에 따라서는 XNA Game Studio나 Windows Phone 7.1까지 생각해야 하는 셈이다.

물론 신경써서 세심하게 잘 프로그래밍할 수 있을만큼 노련하다면야 이런 도구가 굳이 필요하지는 않겠지만 사람이 하는 일인지라 신경쓸 것이 많아지면 자연스레 귀찮을 수 밖에 없고 실수도 어디선가는 꼭 나온다. 이런 불분명하고 애매한 상황을 도구를 통하여 정확하게 잡아낼 수 있다면 괜찮지 않을까? Microsoft 개발 팀이 이런 문제를 해결해 줄 명쾌한 답을 Visual Studio Gallery에 올려놓았으니 참고하기 바란다. 아래의 URL에서 다운로드할 수 있다.

http://visualstudiogallery.msdn.microsoft.com/b0e0b5e9-e138-410b-ad10-00cb3caf4981/

Portable Library Tools를 설치하면 Visual Studio에 새로운 프로젝트 템플릿이 C#과 Visual Basic .NET에 대하여 Windows 카테고리 아래에 아래 그림과 같이 나타날 것이다.


시험삼아 새 프로젝트를 한 번 만들어보자. 이름에서 알 수 있듯이 특별할 것이 없는 공통되는 코드를 묶기 위한 클래스 라이브러리 프로젝트로서 만들어지며 일상적으로 여러분의 코드를 추가할 수 있다. 그런데 정말 중요한 것은 프로젝트 속성 안에 들어있다. 프로젝트 속성을 열어보면 아래와 같이 재미있는 구성을 볼 수 있다.

목표로 하는 프레임워크를 이전처럼 하나만 선택하는 것이 아니라 하나 이상을 동시에 지정할 수 있다. 이는 다시 말해서 이들 프레임워크의 공통 분모만을 사용하여 클래스 라이브러리를 정확히 프로그래밍할 수 있도록 보증한다는 의미이다. 그리고 Change 버튼을 클릭하면 더 유용한 쓰임새를 찾을 수 있다.

Visual Studio와 함께 설치된 다른 멀티 타기팅 팩이 있을 경우 여기에 모두 열거되어 여러분이 선택할 수 있도록 할 수 있다. 프로젝트의 종류가 많고 정말 일반적이면서도 널리 쓰여야 할 클래스 라이브러리를 만들어야 한다면 여기서 선택을 어떻게 하는가에 따라서 사용할 수 있는 API가 자동으로 필터링된다. 만약 위에서 선택한 플랫폼에서 사용할 수 없는 API에 대한 내용이 발견되면 곧바로 빌드 오류로 나타나기 때문에 문제를 쉽게 찾아낼 수 있다. 그리고 여기서 선택한 대로 해당 프로젝트에서 레퍼런스로 추가할 수 있으므로 생산성도 높일 수 있다. 즉, 솔루션이나 프로젝트를 여러벌로 나눠 관리할 필요 없이 한 곳에서 모두 관리할 수 있다.

이 도구를 사용하기 위해서는 Visual Studio 2010 전체 버전이 필요하며 SP1이 설치되어있어야 한다. 그리고 다른 타기팅 팩이 필요하다면 아래 URL에서 추가적으로 설치할 수 있다. 타기팅 팩은 그 자체로 설치할 수 있는 것은 아니고 보통은 해당 플랫폼에 대한 SDK 안에 같이 수록된다는 점에 유의해야 한다.

http://go.microsoft.com/fwlink/?LinkID=153626

저작자 표시 비영리 변경 금지
크리에이티브 커먼즈 라이선스
Creative Commons License
Posted by Windows Azure MVP 남정현 (rkttu.com)

요즈음 클라우드 서비스들을 이용하다보면, Windows 서버 운영체제를 통해서 확장성있는 클라우드를 만들고자 하는 경우가 자주 있습니다. 일반적인 웹 사이트를 구축할 때에도 마찬가지이고, 당연히 KT UCLOUD나 Amazon과 같은 환경에서도 같은 노력이 뒷받침이 되어야 하지요. 그리고 제가 주 전공으로 하고 있는 Windows Azure 역시, 첫 배포 때에는 간과하기 쉬운 점이 바로 로드 밸런싱 환경이라는 점입니다.

이러한 로드밸런싱 환경을 만들때에는, 이전에 구축해본 경험이 없는 관리자가 개발자 입장에서는 상당히 어려운 문제에 봉착하게 될 가능성이 많습니다. 특히 요즈음 웹 환경에서는 당연하게 사용하는 세션이나 쿠키에 관련된 설정들이 로드밸런싱 환경에서 기대했던 것과 다르게 동작해서 좌절하는 경험을 많이들 하실텐데요, 제가 오늘 블로그에 올리는 것은 ASP.NET에 관한, 그리고 IIS 7에 관한 내용입니다. (PHP나 JSP 개발자분들께서도 공감하실 수 있는 부분이 있을 것입니다.)

로드밸런싱 환경을 잘 알고 구축할 수 있다면, 앞으로 나오게될 어떤 종류의 클라우드 서비스이든 관계없이 문제를 정확하게 해결할 수 있을 것입니다. 사실 클라우드 기반의 웹 서비스는 달리 표현하면, 기본 골자는 로드밸런싱에 기반을 두고 있는 것이고, 그 이후의 확장성 전략을 클라우드 솔루션으로 채우는 것과 같다고 말할 수 있습니다. (어떤 뼈대를 사용할 것인지는 전적으로 여러분들의 선택에 달린 것입니다.)

로드 밸런싱 환경이란?

로드 밸런싱 기술 자체는 상당히 오래된 것입니다. 이름에서 알 수 있듯이, 몰려오는 트래픽을 내부적으로 분산하여 특정 서버 컴퓨터로 연결이 몰려 서비스가 사용 불가 상태로 빠지는 것을 "지연"시키거나 "완화"시키는 것에 목적이 있습니다. 로드 밸런싱의 기술적 개념도는 다음과 같습니다. (이미지 출처: http://msdn.microsoft.com/en-us/library/ff650667.aspx)

다양한 상황에서 로드밸런싱이 쓰이겠지만 가장 일반적으로는 웹 환경에서 많이 쓰입니다. 연결을 오래 유지할 필요가 없으면서도, 짧은 시간 내에 빠른 연결 회전을 보이는 웹 프로토콜에서 가장 중요한 것은 바로 신속성인데, 분산 처리를 하지 않는 경우에는 필연적으로 서버 컴퓨터가 받아들일 수 있는 동시 연결 한계치에 금방 치닫게 됩니다. 그러나 로드 밸런싱을 정확히 사용하면 이러한 한계치에 치닫게 되는 속도가 로드 밸런싱에 참가하는 컴퓨터의 댓수만큼 반비례하게 됩니다. 그리고 이 때 하나의 웹 사이트를 위한 로드 밸런싱 서비스에 멤버로 참여하는 서버 컴퓨터들을 묶어서 "웹 팜"이라고 정의를 하는 것이지요. 더 일반적으로는 "서버 팜"이라고도 합니다.

잠시 다른 이야기로 넘어가자면, 요즈음 대두되는 클라우드 컴퓨팅은 관리 측면에서 봤을 때, 충분한 대역폭을 보장하는 연결과 매우 뛰어난 성능을 가진 로드 밸런서를 이용하여 연결을 분산하는 작업을 수행하는 것입니다. 그리고 웹 팜 안에 참여하는 컴퓨터의 유형에 있어서는 이전과 다른 점이 하나 있는데, 마치 구름과 같이 수축과 팽창을 자유자재로 한다는 것입니다. 물론 이런 수축과 팽창이 가능함은 내부적으로 가상화 솔루션을 이용했다거나 여기에 대응할 수 있는 알고리즘을 사용했다는 가정이 깔려있는 것입니다.

정말 완벽하고 정확하게 구축했다면, 적은 전원이나 자원 공급으로도 충분히 웹 팜이 유지가 될 수도 있고, 필요하다면 웹 팜의 크기가 엄청나게 커질 수도 있겠지요. 이걸 여러분이 관리하신다면 프라이빗 클라우드, 신뢰할 수 있는 IT 기업이 관리한다면 퍼블릭 클라우드가 된다고 보실 수 있겠습니다. 그러나, 클라우드 컴퓨팅이 만능약처럼 들릴 수 있는 부분이 있지만 정확히 알아야 할 것은 클라우드 컴퓨팅 역시 이 로드 밸런싱을 기초로 만들어지는 것이고, 여러분이 운영할 수 있는 한계에까지 트래픽이 몰리거나, 이런 일을 하는 IT 업체에게 지불할 수 있는 재정의 한계에까지 트래픽이 몰린다면 이것이 여러분이 생각할 수 있는 클라우드의 한계입니다. 무제한이라고 해서 값이 저렴하거나 무료에 수렴하는게 아님을 명확히 이해하고 있어야 합니다.

웹 로드 밸런싱을 위한 이야기

다시 본론으로 돌아와서, 웹을 로드 밸런싱할 수 있으려면 무엇을 검토해야 할까요? 가장 중요한 것은 웹 서버에 참여하는 각각의 컴퓨터 자체에는 "절대로" 컴퓨터의 고유한 정보를 가지고 있으면 안된다는 점입니다. 매우 단순한 이야기같지만 이러한 원칙을 지키지 않도록 설계되어있는 것이 지금 이 시점까지의 서버 컴퓨팅 기술들의 대다수의 원칙입니다. 간단한 예를 들어볼까요?

여러분이 일상적으로 사용하는, 웹을 통한 파일 업로드 기능을 담당하는 간단한 웹 앱이 있다고 가정해 보겠습니다. 이 웹 앱은 서버가 한 대 일때에는 참 쉽고 빠르게 설치해서 쓸 수 있었습니다. 당연히, 설치를 잘 했다면, 사용자가 웹 페이지를 방문해서 파일을 업로드하면 웹 서버가 그것을 알아보고 파일을 회수해서 하드 디스크 어딘가에 저장하겠지요. 그러나 시간이 지나서 이 웹 앱의 기능을 업그레이드하고 좀 더 많은 사용자들이 파일을 저장하고 다운로드할 수 있도록 만들어보고자 해서 로드 밸런싱 환경을 구축하여 베타 테스트를 시작했습니다. 그런데 어떤 문제들이 생겼을까요?

앞서 이야기한 기술적인 특성때문에, 사용자들은 분명히 조금전까지 파일을 업로드했었는데 페이지를 다시 와서보니 파일이 업로드되지 않은 상태로 페이지가 나와서 혼란스러워합니다. 혹은 파일을 어디로 빼돌린거냐며 분노하는 사람들도 있구요. 그래서 몇 번 F5키를 누르다보면 "어라?"하고 놀라게 됩니다. 조금 전에 업로드했던 파일이 다시 나타나니까요. 그러고나서 그 파일을 다운로드하려고 링크를 클릭하면 이번엔 또 다시 404 오류를 만납니다. 이제 사용자들은 이 서비스에 대해서 대단한 분노와 원성을 쏟아낼 것입니다. 서비스 상태에도 일관성이 없을 뿐 아니라 불안정한것 같다. 믿을 수 없다면서요.

이것이 일선 IT 현장에서 로드 밸런싱이나 클라우드를 처음 접목했을 때 겪는 "가장 흔하고 일반적인 장애"입니다. 더 안타까운 것은, 이것을 신 기술에 의한 책임으로 회피하고 문제시하는 것입니다. 문제의 본질을 정확히 알고 있다면 이렇게 말하는 것이 왜 잘못인지도 금방 알 수 있을 것입니다.

여기서 든 예제처럼, 이 웹 앱의 문제는 단순히 업로드한 파일을 자신의 컴퓨터에 저장하려고 했다는 데에 문제가 있습니다. 로드 밸런싱 멤버로 참여하는 컴퓨터가 자신의 상태를 중요하게 여기면, 다음번에 이어받는 다른 서버 컴퓨터의 입장에서는 이전에 그 컴퓨터가 무엇을 했는지 알 길이 없습니다. 그저, 찾고자 하는 내용이 없음을 이야기하는 수 밖에 없습니다. 이런 상황이 반복되면서 서비스 전체는 들어올때와 나갈때가 전혀 다른, 일관성이 없고 이상한 서비스가 되는 것입니다.

이 문제를 해결하기 위하여 어떻게 수정해야 할까요? 답은 간단합니다. 파일 저장소를 로드 밸런싱 멤버 컴퓨터 내부가 아닌, 여러 멤버 컴퓨터들이 같이 이용할 수 있는 공용 저장소로 바꾸는 것입니다. 가장 간단한 방법은 네트워크 UNC 경로로 이용할 수 있는 스토리지가 있을 수 있습니다.

여기서 궁금한 점이 하나 더 있는데, 그렇다면 로드 밸런싱에 의하여 애써 분산한 서비스가 다시 모이는 것이 아니냐고 반문할 수도 있습니다. 그런데 사실, 생각외로 사용자들이나 웹 크롤러와 같이 인터넷 상에서 발생하는 별 뜻없이 바쁘게 만드는 다양한 유형의 트래픽을 웹 팜 수준에서 한 번은 로드 밸런싱을 해주는 것 만으로도 실제 스토리지에 대한 요구 사항은 획기적으로 감소한다는 점입니다. 거기다, 역할 분담도 정확히 할 수 있으며 스토리지 자체에 대한 요구 사항이 폭증하는 것을 방지하기 위하여 기술적으로는 좀 더 복잡해질 수 있지만 캐싱 기능을 사용할 수도 있습니다. 이렇게 함으로서, 우리가 흔히 잘 아는 클라우드 서비스의 시작을 뗄 수 있게 됩니다.

기술적인 이야기 1 - 세션 처리 방법 바꾸기

그렇다면 IIS와 ASP.NET에서는 이런 이상한 상황을 예방하고 신뢰할 수 있는 서비스를 만들기 위해서 어떤 수정 사항을 반영해야 하는 것일까요? 제가 이제까지 인터넷 상으로 자료 조사를 해왔던 것은 모두 제각기 흩어져있는 정보들이었고 이것을 한 번에 취합할 수 있는 방법을 오늘 블로그 포스팅을 통하여 소개할까 합니다.

기본적으로 ASP.NET은 세션 처리를 IIS 프로세스 안에서 수행하도록 되어있습니다. 가장 동선도 짧고, 신속하게 반응하기 때문입니다. 그러나 로드 밸런싱 환경에서 이는 당연히 "채택하면 안되는" 기법입니다. 이 방법은 web.config 파일 안의 <sessionState> 요소에서 변경할 수 있는 부분으로, <configuration> 요소 아래의 <system.web> 요소 아래에서 없는 경우 새로 지정할 수 있습니다. <sessionState> 요소의 mode 속성의 값을 변경하면 됩니다. 지금 이야기한 부분은 mode 속성이 InProc으로 지정되어있거나, 아무것도 지정되어있지 않을 때 .NET Framework의 글로벌 web.config 설정을 바꾸지 않은 경우 기본으로 지정되는 설정입니다.

IIS 7에서 볼 수 있는 아래 그림과 같은 설정도 이 XML 파일의 수정을 텍스트 에디터 없이 수정하는 것입니다.

로드 밸런싱 환경에서 정상적으로 동작하는 웹 사이트를 만들기 위해서는 mode의 설정 값을 InProc 대신 StateServer나 SQLServer로 바꾸어야 하는데, 양쪽 값 모두 장단점이 있습니다. StateServer의 경우 기본적으로는 꺼져있는 ASP.NET State Service라는 NT 서비스가 제공하는 별도의 서버를 이용하는 방식이고, SQLServer는 이름에서 알 수 있듯이 실제 SQL Server를 사용하여 세션을 구현하는 방식입니다. 데이터베이스 서버의 성능이 세션을 모두 수용할 수 있을만큼 획기적으로 뛰어나거나, 세션 서버가 죽었다가 살아나도 로그아웃 처리가 안되게 한다던가, 혹은 여러 로드 밸런싱 사이트 사이에서 세션 공유를 안전하게 할 방법이 필요하다면 이 모드를 사용할 수 있습니다. 이에 비하여 StateServer는 별도의 SQL 서버 없이도 간편하게 구축할 수 있는 방법을 제공하긴 하지만, 세션 서버가 죽었다 살아날 경우 내용이 없어지는 휘발성 세션입니다.

양쪽 모드 모두 중요한 것은 멤버로 참여하는 웹 서버 컴퓨터 밖에 상태를 보관해야 한다는 것이 키 포인트로, 이것을 지키지 않고 멤버 컴퓨터 안에 이런 설정을 구축하면 전혀 나아지는 것이 없습니다. 그리고 당연한 이야기이지만 멤버 컴퓨터로 참여하는 모든 웹 서버가 같은 설정을 가지고 있어야 합니다.

StateServer와 SQLServer 모드를 구현하는 방법에 대한 자세한 내용은 아래 아티클을 참고하시면 되겠습니다.

http://msdn.microsoft.com/ko-kr/library/ms178586.aspx

기술적인 이야기 2 - ASP.NET 사이트 간에 립싱크 맞추기

세션을 공유하는 것 이외에, ASP.NET은 내부적으로 Machine Key라는 것을 사용합니다. Machine Key의 용도는 ASP.NET 안에서 참 다양한데, 가장 대표적으로는 클라이언트와 서버 사이에 쿠키 정보를 주고 받을 때 암호화하기 위한 수단으로 이용하는 것이 유명한 사례입니다. 쿠키를 이용한 취약점 공격은 웹 세계에서 너무나 당연한 공격 방식 중 하나이기 때문에 ASP.NET은 처음부터 이를 보완하기 위한 전략을 구현하고 있었습니다. 그러나 이것이 지금 와서 로드 밸런싱 환경이 되면서는 또 다른 어려운 문제로 바뀐 것입니다.

이 Machine Key라는 것 역시 서버 컴퓨터마다 고유하게 생성할 뿐 아니라, 매번 연결할 때 마다 다른 값을 생성하여 암호화에 사용합니다. 클라이언트 입장에서야, 서버가 "ABC"라는 쿠키를 주니까 "아 그렇구나. 나중에 돌려주면 서버가 날 알아보겠지?"하며 성실하게 반납합니다. 그런데 로드 밸런싱에 참여하는 A라는 서버 대신 C라는 서버가 이 쿠키를 받아들었을 때는 "이거 내것 아님" 하며 클라이언트에게 퇴짜를 놓습니다. 이것이 문제의 핵심인 것이죠.

이 문제를 해결하기 위해서는 아까전에 이야기한 주제보다 좀 더 많은 노력이 필요합니다. 생각보다, 보안을 완벽하게 유지하기 위하여 ASP.NET이 관리자들에게 요구하는 사항이 까다롭기 때문입니다. 이 Machine Key를 만들기 위해서는 별도의 생성 도구를 사용해야 합니다. 그러나 안타깝게도 이 도구를 구한다거나 만들 수 있으려면 개발자들의 조력이 좀 필요합니다. 그리고 개발자 본인들도 이런 방법을 찾아야 하기때문에 꽤나 귀찮습니다. Codeproject에 가면 이러한 방법을 자세히 설명한 아티클도 있습니다만 간단한 도구도 드리고, 코드 조각도 드리니 프로그램에 넣어 활용하시면 더 편리할 것입니다.

using System;
using System.Text;
using System.Security.Cryptography;

/* 중략 */

        public static string getRandomKey(int bytelength)
        {
            byte[] buff = new byte[bytelength];
            RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
            rng.GetBytes(buff);
            StringBuilder sb = new StringBuilder(bytelength * 2);
            for (int i = 0; i < buff.Length; i++)
                sb.Append(string.Format("{0:X2}", buff[i]));
            return sb.ToString();
        }

        public static string getASPNET20machinekey()
        {
            StringBuilder aspnet20machinekey = new StringBuilder();
            string key64byte = getRandomKey(64);
            string key32byte = getRandomKey(32);
            aspnet20machinekey.Append("<machineKey\n");
            aspnet20machinekey.Append(" validationKey=\"" + key64byte + "\"\n");
            aspnet20machinekey.Append(" decryptionKey=\"" + key32byte + "\"\n");
            aspnet20machinekey.Append(" validation=\"SHA1\" decryption=\"AES\"\n");
            aspnet20machinekey.Append("/>\n");
            return aspnet20machinekey.ToString();
        }

        public static string getASPNET11machinekey()
        {
            StringBuilder aspnet11machinekey = new StringBuilder();
            string key64byte = getRandomKey(64);
            string key24byte = getRandomKey(24);

            aspnet11machinekey.Append("<machineKey");
            aspnet11machinekey.Append(" validationKey=\"" + key64byte + "\"\n");
            aspnet11machinekey.Append(" decryptionKey=\"" + key24byte + "\"\n");
            aspnet11machinekey.Append(" validation=\"SHA1\"\n");
            aspnet11machinekey.Append("/>\n");
            return aspnet11machinekey.ToString();
        }

위의 코드를 사용하여 프로그램을 만들거나 ZIP 파일 안의 프로그램을 이용하여 값을 만들도록 하면 아래와 같은 XML 코드 조각을 얻을 수 있을 것입니다. 이 코드 조각을 각각의 서버에 들어있는 web.config에 지정하거나, 특정한 값만 인용하여 아래의 IIS 7 설정 아이콘에서 볼 수 있는 설정 도구를 통해서 직접 설정할 수도 있습니다.

<machineKey
 validationKey="FACBB6C89C44CB8BB7165FC4639BAA7267B...EF297D815E1BDD40E883E3451628CB95D34309"
 decryptionKey="4E95057676CC8DBA9AB...AACC1121B6B962E5AFA7849B0C82"
 validation="SHA1" decryption="AES"
/>

기술적인 이야기 3 - IIS에서 놓치면 안되는 것

ASP.NET을 가장 먼저 사용할 수 있게 된 웹 서버가 IIS이다보니 발생한 일종의 특성입니다만 여러 포럼에 걸쳐서 잘 언급되지 않는 문제점이 하나 있습니다. 바로 IIS에서 사용하는 사이트 ID 값을 통해서 정해지는 Application Path를 Machine Key와 같이 활용된다는 사실입니다. 웹 사이트 관리를 하다보면 로드 밸런싱에 참여하는 컴퓨터들을 다음과 같이 관리하게 되는 경우가 있습니다.

  • 서버 A에서는 기본 웹 사이트를 먼저 지우고 새 웹 사이트를 만들었다.
  • 서버 B에서는 새 웹 사이트를 먼저 만들고 기본 웹 사이트를 지웠다.

혹은 아래와 같은 경우도 있을 수 있습니다.

  • 서버 C에서는 사이트 A를 만들고 사이트 B를 만들었다.
  • 서버 D에서는 사이트 B를 만들고 사이트 A를 만들었다.

별 차이 없이 생각할 수 있지만, IIS에서는 이 경우 각각의 사이트들에 다른 ID 값을 부과하게 됩니다. 이 경우, 분명히 Machine Key를 동일하게 지정했음에도 불구하고 로드 밸런싱 환경에서 세션 상태가 일관성없게 변하는 문제를 만나게 됩니다. 제가 이번에 고민하게 된 부분도 바로 이 부분이었는데요, 이 문제를 해결하기 위해서는 IIS 7에서 전체 웹 사이트 목록에 나타나는 내용 중 다음의 ID 값이 멤버로 참여하는 웹 서버마다 차이가 있지 않은지 우선 검토해야 합니다.

위에있는 그림에서 빨간색으로 그린 부분이 서버 컴퓨터마다 차이가 있다면 이 값을 수정해주어야 합니다. 이 값을 수정하기 위해서는 수정할 사이트를 클릭하고, 고급 설정 링크를 아래 그림과 같이 클릭합니다.

이제 아래와 같은 팝업 대화 상자가 나타나면 강조 표시한 속성인 ID 값이 멤버로 참여하는 웹 서버 모두 같은 값을 가질 수 있도록 통일시켜줍니다.

확인 버튼을 누른 다음, ID 값이 바뀐 서버 컴퓨터에 한해서 IIS 전체를 재시작해주시거나 사이트 재시작을 시켜주시면 정상적으로 작동하게 될 것입니다.

Windows Azure 환경에서의 고려 사항

오늘 살펴본 내용은 IIS 7과 ASP.NET에 관한 부분이었지만, Windows Azure Platform의 경우에도 비슷한 문제가 있습니다. Windows Azure Platform에 VM Role로 웹 사이트를 게시를 하든, Web Role로 웹 사이트를 게시하든 세션을 사용하게 될 경우 비슷한 문제가 있을 수 있습니다.

다행히, Web Role을 이용한다면 내부적으로 사용하는 IIS에서 여러분이 몇 개의 웹 사이트를 추가적으로 구성하든 관계없이 같은 순서로 같은 ID를 사용하는 웹 사이트를 만들 것이므로 세 번째로 이야기한 ID 값 수정과 같은 작업은 할 필요가 없을 것입니다. 그러나 Machine Key에 대한 설정이나 세션 공유를 위한 설정은 SQL Azure를 이용한다거나, Worker Role에서 ASP.NET State Service 혹은 써드파티의 Session State Server를 이용해야 할 수 있습니다.

물론, 최근에 Windows Azure Platform의 일부로 Windows Azure AppFabric Cache가 새로 출시되기는 하였습니다만 상당히 이용 가격이 비싼 편입니다. (비싼만큼 확실한 성능을 제공합니다.) 로드 밸런싱 환경에서 특별한 문제를 일으키지 않는 일반적인 세션 공유가 필요하시다면 오늘 이야기한 주제를 응용한 Azure Project를 구축해보는 것도 의미가 있을 것입니다.

저작자 표시 비영리 변경 금지
크리에이티브 커먼즈 라이선스
Creative Commons License
Posted by Windows Azure MVP 남정현 (rkttu.com)

응용프로그램 작성 도중 C#의 소수점 반올림 관련 코드에서 몇몇 자주 사용되는 기능들을 보완하는 코드를 작성하여 올려봅니다. 이 코드는 http://www.latiumsoftware.com/en/delphi/00033.php 의 코드를 바탕으로 작성된 것입니다.

namespace System
{
    // Original Source: http://www.latiumsoftware.com/en/delphi/00033.php
    public static class MathExtension
    {
        public static int Sign(
#if CS3
            this
#endif // CS3
            decimal x)
        {
            return Math.Sign(x);
        }

        public static int Sign(
#if CS3
            this
#endif // CS3
            double x)
        {
            return Math.Sign(x);
        }

        public static int Integer(
#if CS3
            this
#endif // CS3
            decimal x)
        {
            return (int)Math.Truncate(x);
        }

        public static int Integer(
#if CS3
            this
#endif // CS3
            double x)
        {
            return (int)Math.Truncate(x);
        }

        public static decimal Fraction(
#if CS3
            this
#endif // CS3
            decimal x)
        {
            return x - Math.Truncate(x);
        }

        public static double Fraction(
#if CS3
            this
#endif // CS3
            double x)
        {
            return x - Math.Truncate(x);
        }

        public static decimal RoundUp(
#if CS3
            this
#endif // CS3
            decimal x)
        {
            return Integer(x) + Sign(Fraction(x));
        }

        public static double RoundUp(
#if CS3
            this
#endif // CS3
            double x)
        {
            return Integer(x) + Sign(Fraction(x));
        }

        public static decimal RoundDown(
#if CS3
            this
#endif // CS3
            decimal x)
        {
            return Integer(x);
        }

        public static double RoundDown(
#if CS3
            this
#endif // CS3
            double x)
        {
            return Integer(x);
        }

        public static decimal Round(
#if CS3
            this
#endif // CS3
            decimal x)
        {
            return Integer(x) + Integer(Fraction(x) * 2m);
        }

        public static double Round(
#if CS3
            this
#endif // CS3
            double x)
        {
            return Integer(x) + Integer(Fraction(x) * 2d);
        }

        public static decimal Fix(
#if CS3
            this
#endif // CS3
            decimal x)
        {
            if (x >= 0m || Fraction(x) == 0m)
                return Integer(x);
            else
                return Integer(x) - 1;
        }

        public static double Fix(
#if CS3
            this
#endif // CS3
            double x)
        {
            if (x >= 0d || Fraction(x) == 0d)
                return Integer(x);
            else
                return Integer(x) - 1;
        }

        public static decimal RoundDownFix(
#if CS3
            this
#endif // CS3
            decimal x)
        {
            return Fix(x);
        }

        public static double RoundDownFix(
#if CS3
            this
#endif // CS3
            double x)
        {
            return Fix(x);
        }

        public static int Absolute(
#if CS3
            this
#endif // CS3
            int x)
        {
            return Math.Abs(x);
        }

        public static decimal RoundUpFix(
#if CS3
            this
#endif // CS3
            decimal x)
        {
            return Fix(x) + Absolute(Sign(Fraction(x)));
        }

        public static double RoundUpFix(
#if CS3
            this
#endif // CS3
            double x)
        {
            return Fix(x) + Absolute(Sign(Fraction(x)));
        }

        public static decimal RoundFix(
#if CS3
            this
#endif // CS3
            decimal x)
        {
            return Fix(x + 0.5m);
        }

        public static double RoundFix(
#if CS3
            this
#endif // CS3
            double x)
        {
            return Fix(x + 0.5d);
        }

        public static decimal RoundTo(
#if CS3
            this
#endif // CS3
            decimal x, int d)
        {
            decimal n = (decimal)Math.Pow(10, d);
            x *= n;
            return (Integer(x) + Integer(Fraction(x) * 2m)) / n;
        }

        public static double RoundTo(
#if CS3
            this
#endif // CS3
            double x, int d)
        {
            double n = Math.Pow(10, d);
            x *= n;
            return (Integer(x) + Integer(Fraction(x) * 2d)) / n;
        }
    }
}

사용 예시는 다음과 같습니다.

using System;

namespace ConsoleApplication1
{
    public static class Program
    {
        [STAThread]
        public static void Main(string[] args)
        {
#if CS3
            Console.WriteLine("{0}", (3.3m).RoundUp() == 4m);
            Console.WriteLine("{0}", (-3.3m).RoundUp() == -4m);
            Console.WriteLine("{0}", (3.7m).RoundDown() == 3m);
            Console.WriteLine("{0}", (-3.7m).RoundDown() == -3m);
            Console.WriteLine("{0}", (3.5m).Round() == 4m);
            Console.WriteLine("{0}", (-3.5m).Round() == -4m);
            Console.WriteLine("{0}", (3.1m).Round() == 3m);
            Console.WriteLine("{0}", (-3.1m).Round() == -3m);
            Console.WriteLine("{0}", (3.7m).Integer() == 3);
            Console.WriteLine("{0}", (-3.7m).Integer() == -3);
            Console.WriteLine("{0}", (3.7m).Fix() == 3m);
            Console.WriteLine("{0}", (-3.7m).Fix() == -4m);
            Console.WriteLine("{0}", (3.7m).RoundDownFix() == 3m);
            Console.WriteLine("{0}", (-3.7m).RoundDownFix() == -4m);
            Console.WriteLine("{0}", (3.1m).RoundDownFix() == 3m);
            Console.WriteLine("{0}", (-3.1m).RoundDownFix() == -4m);
            Console.WriteLine("{0}", (3.1m).RoundUpFix() == 4m);
            Console.WriteLine("{0}", (-3.7m).RoundUpFix() == -3m);
            Console.WriteLine("{0}", (3.5m).RoundFix() == 4m);
            Console.WriteLine("{0}", (-3.5m).RoundFix() == -3m);
            Console.WriteLine("{0}", (123.456m).RoundTo(0) == 123.00m);
            Console.WriteLine("{0}", (123.456m).RoundTo(2) == 123.46m);
            Console.WriteLine("{0}", (123456m).RoundTo(-3) == 123000m);

            Console.WriteLine("{0}", (3.3d).RoundUp() == 4d);
            Console.WriteLine("{0}", (-3.3d).RoundUp() == -4d);
            Console.WriteLine("{0}", (3.7d).RoundDown() == 3d);
            Console.WriteLine("{0}", (-3.7d).RoundDown() == -3d);
            Console.WriteLine("{0}", (3.5d).Round() == 4d);
            Console.WriteLine("{0}", (-3.5d).Round() == -4d);
            Console.WriteLine("{0}", (3.1d).Round() == 3d);
            Console.WriteLine("{0}", (-3.1d).Round() == -3d);
            Console.WriteLine("{0}", (3.7d).Integer() == 3);
            Console.WriteLine("{0}", (-3.7d).Integer() == -3);
            Console.WriteLine("{0}", (3.7d).Fix() == 3d);
            Console.WriteLine("{0}", (-3.7d).Fix() == -4d);
            Console.WriteLine("{0}", (3.7d).RoundDownFix() == 3d);
            Console.WriteLine("{0}", (-3.7d).RoundDownFix() == -4d);
            Console.WriteLine("{0}", (3.1d).RoundDownFix() == 3d);
            Console.WriteLine("{0}", (-3.1d).RoundDownFix() == -4d);
            Console.WriteLine("{0}", (3.1d).RoundUpFix() == 4d);
            Console.WriteLine("{0}", (-3.7d).RoundUpFix() == -3d);
            Console.WriteLine("{0}", (3.5d).RoundFix() == 4d);
            Console.WriteLine("{0}", (-3.5d).RoundFix() == -3d);
            Console.WriteLine("{0}", (123.456d).RoundTo(0) == 123.00d);
            Console.WriteLine("{0}", (123.456d).RoundTo(2) == 123.46d);
            Console.WriteLine("{0}", (123456d).RoundTo(-3) == 123000d);
#else // !CS3
            Console.WriteLine("{0}", MathExtension.RoundUp(3.3m) == 4m);
            Console.WriteLine("{0}", MathExtension.RoundUp(-3.3m) == -4m);
            Console.WriteLine("{0}", MathExtension.RoundDown(3.7m) == 3m);
            Console.WriteLine("{0}", MathExtension.RoundDown(-3.7m) == -3m);
            Console.WriteLine("{0}", MathExtension.Round(3.5m) == 4m);
            Console.WriteLine("{0}", MathExtension.Round(-3.5m) == -4m);
            Console.WriteLine("{0}", MathExtension.Round(3.1m) == 3m);
            Console.WriteLine("{0}", MathExtension.Round(-3.1m) == -3m);
            Console.WriteLine("{0}", MathExtension.Integer(3.7m) == 3);
            Console.WriteLine("{0}", MathExtension.Integer(-3.7m) == -3);
            Console.WriteLine("{0}", MathExtension.Fix(3.7m) == 3);
            Console.WriteLine("{0}", MathExtension.Fix(-3.7m) == -4);
            Console.WriteLine("{0}", MathExtension.RoundDownFix(3.7m) == 3m);
            Console.WriteLine("{0}", MathExtension.RoundDownFix(-3.7m) == -4m);
            Console.WriteLine("{0}", MathExtension.RoundDownFix(3.1m) == 3m);
            Console.WriteLine("{0}", MathExtension.RoundDownFix(-3.1m) == -4m);
            Console.WriteLine("{0}", MathExtension.RoundUpFix(3.1m) == 4m);
            Console.WriteLine("{0}", MathExtension.RoundUpFix(-3.7m) == -3m);
            Console.WriteLine("{0}", MathExtension.RoundFix(3.5m) == 4m);
            Console.WriteLine("{0}", MathExtension.RoundFix(-3.5m) == -3m);
            Console.WriteLine("{0}", MathExtension.RoundTo(123.456m, 0) == 123.00m);
            Console.WriteLine("{0}", MathExtension.RoundTo(123.456m, 2) == 123.46m);
            Console.WriteLine("{0}", MathExtension.RoundTo(123456m, -3) == 123000m);

            Console.WriteLine("{0}", MathExtension.RoundUp(3.3d) == 4d);
            Console.WriteLine("{0}", MathExtension.RoundUp(-3.3d) == -4d);
            Console.WriteLine("{0}", MathExtension.RoundDown(3.7d) == 3d);
            Console.WriteLine("{0}", MathExtension.RoundDown(-3.7d) == -3d);
            Console.WriteLine("{0}", MathExtension.Round(3.5d) == 4d);
            Console.WriteLine("{0}", MathExtension.Round(-3.5d) == -4d);
            Console.WriteLine("{0}", MathExtension.Round(3.1d) == 3d);
            Console.WriteLine("{0}", MathExtension.Round(-3.1d) == -3d);
            Console.WriteLine("{0}", MathExtension.Integer(3.7d) == 3);
            Console.WriteLine("{0}", MathExtension.Integer(-3.7d) == -3);
            Console.WriteLine("{0}", MathExtension.Fix(3.7d) == 3);
            Console.WriteLine("{0}", MathExtension.Fix(-3.7d) == -4);
            Console.WriteLine("{0}", MathExtension.RoundDownFix(3.7d) == 3d);
            Console.WriteLine("{0}", MathExtension.RoundDownFix(-3.7d) == -4d);
            Console.WriteLine("{0}", MathExtension.RoundDownFix(3.1d) == 3d);
            Console.WriteLine("{0}", MathExtension.RoundDownFix(-3.1d) == -4d);
            Console.WriteLine("{0}", MathExtension.RoundUpFix(3.1d) == 4d);
            Console.WriteLine("{0}", MathExtension.RoundUpFix(-3.7d) == -3d);
            Console.WriteLine("{0}", MathExtension.RoundFix(3.5d) == 4d);
            Console.WriteLine("{0}", MathExtension.RoundFix(-3.5d) == -3d);
            Console.WriteLine("{0}", MathExtension.RoundTo(123.456d, 0) == 123.00d);
            Console.WriteLine("{0}", MathExtension.RoundTo(123.456d, 2) == 123.46d);
            Console.WriteLine("{0}", MathExtension.RoundTo(123456d, -3) == 123000d);
#endif // CS3
        }
    }
}

위의 코드에서 CS3 라는 매크로를 프로젝트 내에 설정하면 Extension Method로 사용할 수 있으며, 이 매크로의 선언을 해제하면 일반 정적 메서드로 변경하여 사용할 수 있습니다. 여러 메서드들이 있지만 그 중에서도 RoundTo 메서드는 사용 빈도가 매우 많은 함수일 듯 합니다. :-)

저작자 표시 비영리 동일 조건 변경 허락
크리에이티브 커먼즈 라이선스
Creative Commons License
Posted by Windows Azure MVP 남정현 (rkttu.com)

Windows Forms, ASP .NET Web Form 디자이너의 경우, 디자인 타임에 대한 의존도가 상당히 높은 편입니다. 그리고 이러한 디자인 타임 상의 구현을 완료하는데에 있어서 흔히 겪게 되는 문제가 있는데, 바로 컬렉션에 대한 처리입니다.

디자인 타임을 위한 컬렉션의 초기화 방법은 달라야 합니다.

디자인 타임을 위한 컬렉션의 초기화 방법은 런타임때와는 달라야 합니다. 객체를 생성하고 호출하는 방법이 우리가 이해하는 런타임 때와는 다르며, 컬렉션은 이를 준수하기 위해서 지연된 초기화 과정을 거쳐야 합니다. 다음은 디자인 타임용 컬렉션을 초기화하는 프로퍼티의 한 예시입니다.

internal ArrayList internalObjCollection = new ArrayList(); // 나중에 설명할 부분입니다.
private ObjectCollection objCollection = null; // 생성자나 인라인 식에서 초기화하지 않습니다. 대신...

[Browsable(true)]
[Category(ForexRuntime.ForexCategoryName)]
[Editor(typeof(CollectionEditor), typeof(UITypeEditor))]
[Description("속성에 대한 설명을 여기에 지정합니다.")]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public ObjectCollection Collection {
    get {
        if (objCollection == null) objCollection = new ObjectCollection(this);
        return objCollection;
    }
}

위의 예시에서처럼 지연된 초기화를 사용한다는 점을 기억해야 합니다. 또한, 디자인 타임 컬렉션이 갖추어야 할 추가적인 조건이 한 가지 더 있는데, 디자인 타임 컬렉션은 철저히 프록시 역할을 수행해야 한다는 점입니다. 위에서 언급한 ObjectCollection의 생성자 호출 시 부모 객체의 참조를 넘겨받는 다는 점을 유심히 살펴보아야 합니다. 그리고 ObjectCollection은 컬렉션으로서 준수해야 할 기본적인 인터페이스 구현만을 포함해야 하며 실제로 모든 객체 관리는 다시 internalObjCollection 이라는 별도의 컬렉션에서 관리가 이루어지게 된다는 점입니다.

[Serializable]
[DesignTimeVisible(true)]
public class OutputFieldDefineCollection : IList { ... }

컬렉션 클래스 자체에는, 디자인 타임과의 원활한 상호 작용을 위하여 DesignTimeVisibleAttribute 속성이 추가된 것을 확인해 둡니다. 그리고 이 컬렉션의 생성자 형식에서 수용하기로 한 원본 객체의 참조는, 원본 객체 내부의 ArrayList에 접근하기 위한 목적으로 사용되고, 이 클래스가 구현하기로 한 IList 및 다른 인터페이스는 원본 객체 내부의 ArrayList를 기준으로 컬렉션의 기능을 제공하도록 코드를 작성합니다.

생성자와 IContainer 인터페이스의 역할은 매우 중요합니다.

추가하기로 한 컴포넌트에서 각별히 신경써야 할 것은, 바로 컨테이너의 기본 생성자와 더불어서 IContainer 객체의 참조를 받는 생성자로 적어도 2가지 생성자가 항상 제공되어야 합니다. 예를 들어, BookComponent가 있다고 가정해 보겠습니다.

public class BookComponent : Component {
    public BookComponent() : this(null) { }
    public BookComponent(IContainer container) : base() {
        if (container != null) { container.Add(this); }
        // 이곳에 생성자 코드를 지정하거나, this.InitializeComponent() 메서드를 호출합니다.
    }
}

위와 같은 코드가 있다고 하였을 때, 보통 container의 Add 메서드를 별 다른 생각없이 부릅니다. 하지만 여기에 숨겨진 기능이 하나 더 있는데, Add 메서드에는 디자인 타임에서 사용할 변수의 이름을 지정할 수 있는 인자가 제공된다는 점입니다. Add 메서드의 두 번째 오버로드를 사용하면 쉽게 변수의 이름을 다시 정의할 수 있는 것입니다. 단, 중복되는 변수 이름이 발생하지 않도록 정교한 명명 규칙이 필요함을 유의해야합니다.

또한, 별도의 디자이너가 존재하는 경우, 가능하면 컴포넌트의 생성자 메서드의 실행이 끝나기 전에 필요한 모든 초기화 작업을 수행하는 것이 좋으며 여기에는 앞서 설명한 Add 메서드의 활용은 물론 컴포넌트 자체의 설정에 관한 부분들도 포함됩니다.

저작자 표시 비영리 동일 조건 변경 허락
크리에이티브 커먼즈 라이선스
Creative Commons License
Posted by Windows Azure MVP 남정현 (rkttu.com)

커뮤니티 뉴스 레터를 구독하다가 우연히 발견한 좋은 리소스 소개 페이지가 있어서 한글로 간단히 번역하여 블로그 아티클로 올려봅니다. Client App Dev MVP이신 Anoop Madhusudana님의 블로그로부터 발췌하여 인용한 것임을 밝혀둡니다. (http://amazedsaint.blogspot.com/2010/09/7-freely-available-e-booksguides-i.html)

Foundations Of Programming
다운로드 링크: http://codebetter.com/media/p/179694.aspx

이 e-Book은 단순 명료하면서도 이해하기 쉽게 구성되어있습니다. 특히, 프로그래밍에 첫발을 막 내딛은 - 혹은 - 아직 숙련되지 않은 개발자들에게 더 좋은 코드와 설계력을 부여할 수 있도록 도움을 줄 것입니다. 이 블로그를 운영하시는 MVP 분의 마음에 아주 쏙 드는 책이라는 인상깊은 설명도 덧붙여져있습니다. :-)

Microsoft Application Architecture Guide, 2nd Edition
다운로드 링크: http://www.microsoft.com/downloads/en/details.aspx?FamilyID=ce40e4e1-9838-4c89-a197-a373b2a60df2&displaylang=en

Microsoft에 의하여 작성된 e-Book이며, .NET Framework를 사용하는 개발자 - 또는 - 설계자들이 반드시 읽어보아야할 내용들로 구성되어있습니다. 이 도서를 통하여, Microsoft .NET 기반 소프트웨어 개발 환경에서, 디자인 기본 원칙이나 패턴을 성공적으로 개발하고 적용할 수 있도록 할 수 있습니다. 또한, .NET 기반에서 개발하는 동안 흔히 부딪힐 수 있는 문제점들에 대해 최적의 해결 방안을 언급하고 있으므로 엔터프라이즈 기반의 .NET 개발 환경에서 특히 유용합니다.

Rob Miles C# Yellow Book 2010
http://www.csharpcourse.com/

C#과 .NET의 컨셉을 쉽고 빠르게 이해할 수 있도록 도와줍니다. 이 e-Book을 통하여 최상의 구현 방법을 학습하고 패턴과 컨셉을 빠르게 습득하실 수 있습니다.

Threading in C#
http://www.albahari.com/threading/

제 블로그에서, 그리고 SQL Azure와 LINQ 세미나를 진행하는 동안에 자주 언급했던 무료 개발 도구인 LINQpad의 제작자이자 Microsoft MVP인 Joe Albahari님의 리소스입니다. 복잡하고 까다로울 수 있는 주제인 멀티 스레딩에 대해 알기 쉽게 쓴 책으로, 스레드에 대한 다양한 컨셉, 예를 들어 스레드 풀링이나 동기화, Non-blocking 동기화 등 다양한 주제들을 다룹니다. 그리고 특별히 요즈음 .NET Framework 4.0에서 강조되는 병렬 프로그래밍에 대한 이야기도 같이 소개하고 있습니다. e-Book으로도 다운로드 가능합니다.

Improving .NET Application Performance and Scalability
http://www.microsoft.com/downloads/en/details.aspx?FamilyID=8A2E454D-F30E-4E72-B531-75384A0F1C47

이번에도 역시 Microsoft의 e-Book입니다. 여러분의 응용프로그램에서 발생할 수 있는 다양한 성능 상의 문제를 점검하고 해결할 수 있도록 도움을 주는 최상의 리소스로, 설계자, 개발자, 테스터, 관리자에 이르는 다양한 범위를 포괄합니다. e-Book의 후반에 수록된 부록에는 체크리스트가 있으므로, 체크리스트만을 따로 인쇄하고 책상 옆에 놓아두면 여러분의 응용프로그램을 스스로 점검해볼 때 매우 유용할 것입니다.

Applying Design Patterns
http://amazedsaint.blogspot.com/2009/06/software-design-patterns-for-everyone.html

디자인 패턴을 활용해보고 싶지만 어렵게 느껴지시나요? 그렇다면 이 리소스를 활용해보시면 좋을것 같습니다. 이 리소스에서는 디자인 패턴을 닷넷 개발 환경에서 어떻게하면 좀 더 쉽게 적용할 수 있을지 자세한 내용을 단계별로 소개하고 있습니다. 역시 e-Book으로도 다운로드 가능합니다.

RefCardz from DZone: Getting started with WCF 4.0
http://refcardz.dzone.com/refcardz/getting-started-windows-0

RefCardz from DZone: Getting started with Silverlight + Expression Blend
http://refcardz.dzone.com/refcardz/getting-started-silverlight

RefCardz from DZone: Essential F#
http://refcardz.dzone.com/refcardz/essential-f

위의 세 리소스는 e-Book은 아니지만 책상 옆에 놓고 쓸 수 있는 단어장 카드의 형태로 구성된 리소스입니다. 위 사이트에서 회원 가입을 하고 파일을 다운로드받아 카드로 오려서 바인더 링 등으로 매달아두면 생각날 때 마다 찾아볼 수 있는 여러분만의 리소스가 될 것입니다. 위에서 소개한 세 가지 카드 말고도 다른 카드들이 더 있으니 사이트 이곳저곳을 둘러 보시길 권합니다.

저작자 표시 비영리 동일 조건 변경 허락
크리에이티브 커먼즈 라이선스
Creative Commons License
Posted by Windows Azure MVP 남정현 (rkttu.com)

Windows Internet Explorer 9 (이하 IE9)은 대부분의 컴퓨터에서 현재 Beta 버전으로 설치와 사용에 문제가 없습니다. (Windows Vista와 Windows 7 모두 잘 동작할 것입니다.) 하지만 설치되어있는 업데이트의 수가 많거나 복잡한 구성을 가진 일부 운영 체제 상태에 따라서는, 설치 프로그램에서 안내한대로 모든 업데이트를 운영 체제 버전에 맞게 설치하였음에도 업데이트가 필요하다는 메시지를 나타내면서 설치를 진행시키지 않을 수 있습니다. 이 경우 사용할 수 있는 방안을 블로그로 올리며, 이 내용은 Internet Explorer 9 Beta를 반드시 설치하셔야 할 경우에만 사용하실 것을 권합니다.

IE9-Windows7-x[86|64]-kor.exe 설치 패키지의 압축을 해제하는 방법

  1. 이 작업을 수행하기 전에 반드시 모든 업데이트들이 설치되어있어야 합니다. 필요한 업데이트에 대한 안내는 설치 프로그램을 직접 기동하였을 때 볼 수 있습니다.
  2. 시작 메뉴 - 프로그램 및 파일 검색 (또는 시작 - 실행 클릭 후 나타나는 텍스트 상자)에 %comspec% 을 입력합니다.
  3. Internet Explorer 9 설치 패키지 파일이 있는 곳까지 디렉터리 및 드라이브를 전환합니다.
  4. 다음의 지침에 따라 명령어를 입력합니다.
    1. Windows Vista 사용자의 경우: IE9-WindowsVista-x86-kor.exe /x:setup
    2. Windows 7 (32비트) 사용자의 경우: IE9-Windows7-x86-kor.exe /x:setup
    3. Windows 7 (64비트) 사용자의 경우: IE9-Windows7-x64-kor.exe /x:setup
  5. setup 디렉터리로 이동하여 다음의 순서대로 독립 실행형 Windows Update 설치 패키지 2본과 Windows Installer 패키지 1본을 설치합니다. 설치 도중에 시스템 다시 시작 여부를 묻는 경우 일단은 모두 아니오로 선택하여 보류합니다.
    1. Windows Vista 사용자의 경우: IE9-Win-Vista.msu
    2. Windows 7 사용자의 경우: IE9-Win-7.msu
    3. ielangpack-KOR.msu
    4. FeedbackTool.msi
  6. 시스템을 다시 시작하면, 시스템 종료 전에 30% 정도의 진척을, 시스템 기동 후에 나머지를 처리하여 기존의 Internet Explorer 7이나 8을 9로 업그레이드하는 프로세스를 진행하게 됩니다.
  7. 다시 시작한 후 새 버전의 Internet Explorer로 정상적으로 업그레이드되었는지 아이콘, UI 및 버전 정보를 확인하십시오. 버전 정보는 Internet Explorer 시작 후 키보드의 Alt 키를 누르면 나타나는 전체 메뉴에서 도움말 - Internet Explorer 정보 메뉴를 클릭하면 나타납니다.

설치 문제를 해결하고 Internet Explorer 9을 테스트해보시면 이전과는 확연히 달라진 것을 느낄 수 있을 것입니다. HTML 5 기반의 웹 사이트를 방문하셔서 새로워진 Internet Explorer 9의 성능과 기능성을 체감해보시면 좋겠습니다. (더불어서, 탭 브라우징 기능도 이전보다 더 좋아졌습니다. ^^)

저작자 표시 비영리 동일 조건 변경 허락
크리에이티브 커먼즈 라이선스
Creative Commons License
Posted by Windows Azure MVP 남정현 (rkttu.com)

안녕하세요. 블로그에 오랫만에 글을 남깁니다. 요즈음 날씨가 매우 덥네요. :-)

최근에 VS2010 Live에서 소개된 Visual Studio LightSwitch라는 신 제품에 대해서 블로그 포스팅을 조촐하게 해봅니다. Visual Studio LightSwitch는 기존의 Visual Studio Express Edition과는 별도로 구분되는 제품으로 매우 신속하고 빠르게 새로운 형태의 소규모 데이터베이스 시스템과 통합하거나, 기존의 WCF RIA Service, SQL Server Database 등과 상호 작용하면서 C/R/U/D (Create/Read/Update/Delete) 및 검색 기능, Office 연동 기능을 지원하는 Rich Application의 생산을 가능하게 해주는 개발 도구입니다. Visual Studio LightSwitch는 2010년 8월 23일 이후부터 베타 버전으로 처음 http://www.microsoft.com/visualstudio/en-us/lightswitch 에서 공개될 예정에 있습니다.

가장 많은 웹 사이트와 가장 많은 도서에서 보여주는 C/R/U/D 기반의 응용프로그램들의 패턴을 Visual Studio LightSwitch는 최신의 UI 기술을 사용하여 그 어떤 도구들보다도 더 쉽게 프로그램이 제작될 수 있도록 도와줍니다. 단순히 Grid Control에서 데이터를 추가, 편집, 삭제할 수 있는 UI가 아니라, 여러분이 원하는 화면의 디자인을 미리 제공되는 데이터 바인딩과 함께 손쉽게 프로그래밍할 수 있도록 돕습니다.

위의 그림에서 보시는것과 같이, Visual Studio LightSwitch는 데이터 가공 방법과 범주에 따라 일반적으로 결정되는 화면의 패턴을 템플릿으로 제공하고 있습니다. 대부분의 경우, 이러한 요구 사항과 부합하는 화면들 내에서 선택이 가능하므로 단순한 데이터 조회 및 검색 화면에 들어가는 작업 소요 기간을 획기적으로 단축시킬 수 있습니다. 뿐만 아니라, 데이터 모델링에서는 단순한 시스템 데이터 형식 뿐만 아니라, 유효성 검사를 염두에 둔 모델링과 UI 연동이 가능하여 세세한 검사를 위한 코드 작성은 하지 않아도 되며, 기본으로 제공되는 Grid Control의 경우 필터링, 열의 재 배치, 행 정렬, 검색, 페이징이 Built-in 기능입니다. 그리고, 요즈음 Microsoft Application UI의 핵심적인 Trademark인 Ribbon Interface도 빠지지 않습니다. :-)

기존에 Windows Forms, Windows Presentation Foundation 기반의 개발 도구를 이용하여 Desktop 및 Office Application을 개발하는 동안 고객으로부터 가장 많이 받게 되는 요구 사항 중 하나인 Excel Export 기능이 Visual Studio LightSwitch를 기반으로 만들어지는 응용프로그램에서는 미리 제공되는 Built-in 기능 중의 하나입니다. 더 이상 Excel 상호운용성 코드나 Primary Interop Assembly를 찾아서 방황하지 않아도 됩니다. :-)

그리고 Visual Studio LightSwitch는 앞으로도 다양한 확장 패키지를 지원해 나갈 수 있으며, 이러한 확장 패키지를 기반으로하여 가까이는 시각 상의 심미적인 효과를 위한 확장, 하드웨어와의 연동 기능 뿐만 아니라 향후에는 Cloud Computing을 위한 응용프로그램 디자인까지도 염두에 둘 수 있습니다.

Visual Studio LightSwitch는 System Integration 개발 부문, Business Intelligence 확장 부문 등 기존에 특별한 기술적 이득 없이 기계적인 반복 작업을 바탕으로 하는 대부분의 소프트웨어 개발 작업에서 더 큰 진가를 발휘할 수 있을 것으로 예상됩니다. 향후에는 Chart, Graph, HTML Editing Control, Built-in Web Browser와 같이 일반적인 Desktop 및 Office Application에서 많이 사용되는 기능에 대한 Extension Pack이 추가될 것도 염두에 두어본다면 반복적인 작업으로부터 많은 일손을 덜어내어줄 수 있는 멋진 도구가 될 수 있을 것 같이 미래가 기대됩니다.

Cloud Computing과의 연계성에 있어서도 Visual Studio LightSwitch의 역할은 매우 클 것으로 보이며, 향후에 Cloud Computing과의 연계 기능에 대해서도 본격적으로 Topic을 다룰 수 있을 때, 상세하게 정보를 전달할 수 있도록 하겠습니다.

오늘도 좋은 하루 되세요. 감사합니다. :-)

저작자 표시 비영리 동일 조건 변경 허락
크리에이티브 커먼즈 라이선스
Creative Commons License
Posted by Windows Azure MVP 남정현 (rkttu.com)

Java로 작성된 코드를 C#으로 옮길 필요가 있어 살펴보던 중 눈에 익숙하지 않은 Java 고유의 Bitwise Shift 연산자를 발견했습니다. Triple Shift Operator를 어떤 의미로 해석해야 가장 정확할지 확실히 알 수 없어서 인터넷을 검색하던 중 저와 같은 고민을 하는 개발자의 질문이 역시 stackoverflow.com에 있었고 명쾌한 해답을 얻을 수 있었습니다. :-)

 질문: What is the equivalent (in C#) of Java's >>> operator? Just to clarify, I'm not referring to the >> and << operators.
답변: In C#, you can use unsigned integer types, and then the << and >> do what you expect. The MSDN documentation on shift operators gives you the details. Since Java doesn't support unsigned integers (apart from char), this additional operator became necessary.

위의 질문과 답변 내용에 입각하여 코드를 작성한다면 아래와 같습니다.

/* Java Code */
int x = 3;
int y = x >>> 3;

/* Equivalent C# Code */
int x = 3;
int y = (int)((uint)x >> 3);

Java에는 C#과는 달리 Unsigned Integer Type이 따로 정의된 것이 없기 때문에 이와 같은 특수한 연산자를 하나 더 정의하게 된 것이라는 설명도 스레드 하단부에서 찾을 수 있었습니다.

자료 출처: http://stackoverflow.com/questions/1880172/equivalent-of-java-triple-shift-operators-and-in-c

저작자 표시 비영리 동일 조건 변경 허락
크리에이티브 커먼즈 라이선스
Creative Commons License
Posted by Windows Azure MVP 남정현 (rkttu.com)

이미 알고 계신분들도 계시겠지만, Visual Studio 2010부터는 더 이상 Crystal Report가 번들링되지 않게 되었습니다. Visual Studio 2010과 함께 제공되는 Microsoft Report Viewer로 마이그레이션하는 것이 가장 좋은 선택이지만, 이미 개발된 많은 수의 레포트를 정해진 기간 내에 모두 마이그레이션할 수 없는 상황이라면, 이 포스트의 내용을 참고하셔서 무료로 제공되는 Visual Studio 2010용 Crystal Report Free Product를 설치하시면 되겠습니다.

http://www.businessobjects.com/jump/xi/crvs2010/default.asp 에서 Crystal Report for Visual Studio 2010을 다운로드받으실 수 있으며 이 글을 작성하는 현 시점에서 이 제품은 베타 버전으로 발표되어있습니다. 페이지에 접속하면 미국 내 사용자와 기타 지역 사용자용으로 다운로드 링크가 나누어져있으므로 해당되는 링크로 접속하면 자동으로 파일 다운로드가 시작됩니다. 파일 크기는 약 200~300MB 정도입니다.

관련된 블로그 및 포럼 스레드

참고로, Crystal Report를 개발하던 회사인 Business Objects는 2007년에 SAP사에 인수 합병되었습니다. 이에 따라서 전통적으로 Microsoft의 Visual Studio에 번들링되어오던 Crystal Report 제품 개발 일정 상에 큰 변동이 있었고 이와 같은 방침이 SAP에 의하여 결정되고 수행된 것으로 보입니다. 개인적인 소견을 덧붙이면, Visual Studio 2010 이후의 버전에서는 더 이상 Crystal Report가 무료로 제공되지 않을 가능성이 높을 것으로 보이며, 가능한한 Microsoft의 Report Viewer를 사용하여 기존의 모든 Crystal Report 기반 레포트 기술을 교체하는 것을 권장합니다.

저작자 표시 비영리 동일 조건 변경 허락
크리에이티브 커먼즈 라이선스
Creative Commons License
Posted by Windows Azure MVP 남정현 (rkttu.com)

최근 Microsoft 제품군이 새롭게 발표되면서, IIS 6.0과 같이 오래된 웹 서버에는 등록되지 않은 새로운 유형의 MIME Type으로 인해서 호환성에 문제가 발생하는 경우가 자주 있습니다. 특히 IIS 6.0의 경우는 의도된 동작 (By Design)임을 설명하는 기술 문서 (http://support.microsoft.com/kb/326965/ko)로 이에 대한 해명을 하기도 하였습니다.

IIS 6.0 뿐만 아니라, 최근에는 실버라이트 컨텐츠를 개인 웹 호스팅 계정에 올려서 테스트하시는 분들도 많이 계신데요, 단순히 실버라이트 컨텐츠를 재생하기 위함이라면 필요가 없겠지만, 실버라이트 컨텐츠와 상호작용하는 기능을 구현할 때에는 반드시 MIME 설정이 웹 서버에 올바르게 구현되어있어야 합니다.

아래는 Microsoft가 근래에 들어서 발표한 최신 기술에서 사용하는 파일 형식들의 MIME Type 목록들을 정리한 것입니다. 아래의 목록을 참조하여 IIS 6.0이나 Apache 등의 웹 서버에서 설정을 변경할 수 있습니다.

MIME 확장명

파일 확장명 

 application/x-silverlight-app  .xap
 application/manifest  .manifest 
 application/x-ms-application  .application 
 application/x-ms-xbap  .xbap
 application/octet-stream  .deploy
 application/vnd.ms-xpsdocument  .xps 
 application/xaml+xml  .xaml
 application/vnd.ms-cab-compressed  .cab
 application/vnd.openxmlformats-officedocument.wordprocessingml.document  .docx
 application/vnd.openxmlformats-officedocument.wordprocessingml.document  .docm
 application/vnd.openxmlformats-officedocument.presentationml.presentation  .pptx
 application/vnd.openxmlformats-officedocument.presentationml.presentation  .pptm
 application/vnd.openxmlformats-officedocument.spreadsheetml.sheet  .xlsx
 application/vnd.openxmlformats-officedocument.spreadsheetml.sheet  .xlsm
 application/msaccess  .accdb
 application/x-mspublisher  .pub
 image/svg+xml  .svg
 application/xhtml+xml  .xht
 application/xhtml+xml  .xhtml

아래는 Apache 기반의 웹 호스팅에서 서버의 설정을 변경하지 않고 손쉽게 MIME 형식을 추가할 수 있는 .htaccess 파일입니다. 다른 .htaccess 파일이 있을 경우 적당한 위치에 파일을 병합하시고, 다른 응용프로그램을 사용하지 않고 있는 경우 이 파일을 그대로 업로드하면 곧바로 반영됩니다.

IIS 7.x (Windows Vista 및 Server 2008 이상)에서는 위의 형식들 중 상당수가 이미 기본으로 등록되어있으므로 IIS 7.x 이상의 웹 응용프로그램 서버를 사용하고 있을 경우에는 문제가 되지 않습니다. 자체적인 웹 서버를 구현하고 있을 경우 (예를 들어 WCF나 .NET Framework based HTTP Listener 같은 경우) MIME Type에 대해서 올바른 처리가 추가 구현되어있어야 합니다.

저작자 표시 비영리 동일 조건 변경 허락
크리에이티브 커먼즈 라이선스
Creative Commons License
Posted by Windows Azure MVP 남정현 (rkttu.com)