잊지 않겠습니다.

  ASP .NET에서의 Session의 생명주기는 In-of-Process 단위로 움직이게 된다. 그런데, 이 Session의 처리주기가 In-of-Process 단위로 움직이는 경우에 IIS 6에서 사용되는 process recycling에 의해서 session이 끊기게 된다. Session을 이용한 상태의 저장시에 치명적인 오류를 가지게 되는데. 이러한 에러상황을 피하기 위해서는 다음과 같은 방법들이 사용 가능하다. 


1. IIS의 process recycling을 중지 시킨다.

1) 인터넷 정보 서비스 관리자를 띄운다 
2) 응용 프로그램 풀을 선택하고 새로만들기-응용프로그램 풀을 선택하여 새로운 응용 프로그램 풀을 만들어 준다. 
3) 새 응용프로그램의 등록정보를 열고 재생 탭의 항목을 모두 언체크 한다. 
4) 성능 탭의 웹 가든을 1로 맞춰준다 
5) 인터넷 정보 서비스 관리자에서 세션오류가 난 사이트의 등록정보를 연다 
6) 홈 디렉터리 탭의 응용 프로그램 설정 부분에 있는 응용 프로그램 풀을 2번 과정에서 만든 풀을 선택해준다. 

이 과정을 거치면 IIS 6에서도 세션 사용이 가능하다. 하지만 IIS 6의 강점인 자동 리사이클링이 동작하지 않게 세팅하는 것이므로 적용을 최소화 할 수 있도록 한다. 


2. Session 관리 방법을 변경한다. 

ASP.NET에서는 다양한 Session의 관리 방법을 제공한다. 기본적으로 제공되는 In-of-Process 단위의 Session 뿐 아니라, Out-of-Process 단위의 Session 역시 제공한다. Out-of-Process 단위의 Session의 사용은 ASP .NET State Service를 사용하는 방법과 MS-SQL을 이용한 Session의 저장 방법이 있다. 

1) ASP .NET Session State Service 
a. Windows Service에서 ASP .NET Session State Service를 활성화시킨다. (기본적으로 사용하지 않음으로 stop 되어있는 서비스이다.) 

b. web.config에 다음과 같은 항목을 추가한다. 
<sessionState mode="StateServer" stateConnectionString="tcpip=localhost:42424">
</sessionState>

* 주의할점은 dual core process의 경우에는 session과 working process간에 dead-lock이 일어날 수 있다. 이 경우에는 webgarden 속성이 반드시 false여야지 된다. 이에 대한 자세한 내용은 이곳에서..

2) MS-SQL을 이용한 Session 저장
DB를 이용한 Session의 저장 역시 가능하다. DB를 이용하게 되면 일단 속도는 떨어질 수 있지만, 다른 어떤 방법보다 안전한 Session의 이용이 가능하게 된다. (SQL Agent를 이용하기 때문에 SQL Express 버젼에서는 사용이 애매해진다. 시간이 오래된 Session의 자동정리 등 기능을 많이 사용할 수 없게 된다.)

a. command를 이용해서 ASP .NET Session Database 작성
aspnet_regsql.exe -S <machine> -E -ssadd -sstype p

b. web.config에 다음과 같은 항목을 추가한다.
<sessionState mode="SQLServer" sqlConnectionString="server=localhost; uid=*******; pwd=********* ">
</sessionState>

* 주의할 점은 반드시 Serialize 되는 항목만 Session에 저장 가능하다. 


Posted by Y2K
,
마지막으로 Thread에 safe 하지 못한 약점을 극복하기 위한 방법은 은근히 간단하다.
.NET 에서 Process -> AppDomain -> Thread -> Context 단위로 동작하기 때문에, Thread에 safe 한 상황은 Process에서 역시 safe 한 상황이 되어야지 된다.

이는 여러개의 Process에서 같은 자원을 사용하는 상황과 거의 동일하기 때문에, Thread에서 사용되는 ManualResetEvent, AutoResetEvent, lock, Monitor 등을 사용하게 되면, 절대로 Thread safe가 될 수가 없다.

따라서, Process단위의 자원 격리가 필요하다. 이를 위해서, Mutex나 Semaphore를 사용하면 간단히 구현 가능하다. ^^
Posted by Y2K
,
먼저, AppDomain을 이용한 개발을 하는 가장 큰 장점은 Process보다 더 가벼운 단위로의 동작이 가능하고, Programming 적으로 동작이 가능하다는 장점을 가지고 있다. 그렇지만, 이러한 동작 및 Resource의 제어를 하기 위해서 모든 AppDomain를 제어하고, Process당 하나만 존재하는 진정한 Singleton object가 필요하게 된다.

이 Singleton object를 만들기 위한 컨셉은 다음과 같다.

Singleton object가 동작할 working AppDomain의 지정


using System;
using System.Runtime.InteropServices;

namespace ProxyServerForMsn.AppDomainManager
{
    public class CrossAppDomainSingleton<T> : MarshalByRefObject where T : new()
    {
        private const string AppDomainName = "Singleton AppDomain";
        // Singleton Working AppDomain 지정
        private static T instance;

        public static void ListupAppDomains()
        {
            IntPtr enumHandle = IntPtr.Zero;
            mscoree.CorRuntimeHostClass host = new mscoree.CorRuntimeHostClass();
            try
            {
                host.EnumDomains(out enumHandle);

                object domain = null;
                while(true)
                {
                    host.NextDomain(enumHandle, out domain);
                    if(domain == null)
                    {
                        break;
                    }
                    AppDomain appDomain = domain as AppDomain;
                    if(appDomain != null)
                    {
                        Console.WriteLine(appDomain.FriendlyName);   
                    }
                }
            }
            finally
            {
                host.CloseEnum(enumHandle);
                Marshal.ReleaseComObject(host);
                host = null;
            }
        }

        private static AppDomain GetAppDomain(string friendlyName)
        {
            IntPtr enumHandle = IntPtr.Zero;
            mscoree.CorRuntimeHostClass host = new mscoree.CorRuntimeHostClass();
            try
            {
                host.EnumDomains(out enumHandle);

                object domain = null;
                while(true)
                {
                    host.NextDomain(enumHandle, out domain);
                    if(domain == null)
                    {
                        break;
                    }
                    AppDomain appDomain = (AppDomain)domain;
                    if(appDomain.FriendlyName.Equals(friendlyName))
                    {
                        return appDomain;
                    }
                }
            }
            finally
            {
                host.CloseEnum(enumHandle);
                Marshal.ReleaseComObject(host);
                host = null;
            }
            return null;
        }

        public static T Instance
        {
            get
            {
                if (null == instance)
                {
                    AppDomain appDomain = GetAppDomain(AppDomainName);
                    if (null == appDomain)
                    {
                        appDomain = AppDomain.CreateDomain(AppDomainName);
                    }
                    Type type = typeof (T);
                    T createInstance = (T) appDomain.GetData(type.FullName);
                    if (null == createInstance)
                    {
                        createInstance =
                            (T) appDomain.CreateInstanceAndUnwrap(type.Assembly.FullName, type.FullName);
                        appDomain.SetData(type.FullName, createInstance);
                    }
                    instance = createInstance;
                }
                return instance;
            }
        }
    }
}


Generic으로 만들어져있기 때문에, 사용될 constraint class의 경우에도 MarsharByObject class를 상속받거나 Serializable 속성을 가져야지 된다.

사용은 다음과 같다.

[Serializable]
public class CrossAppHello
{
public void ConsoleOut()
{
Console.WriteLine("Hello World");
}
}

class Program
{
public static void Main()
{
CrossAppDomainSingleton<CrossAppHello>.Instance.ConsoleOut();
}
}

Posted by Y2K
,
.NET에서의 Application의 구조는 Process->Application Domain->Thread->Context 로 구성이 된다.

일반적으로 개발되는 console app들은 한개의 Process를 가지고, 이는 실행 파일의 이름과 동일한 Default AppDomain을 가지게 된다. 거의 대부분의 프로그램들이 1개의 AppDomain으로 동작하게 되어서, 일반적으로 사용할때는 Process = AppDomain의 형태로 프로그램의 제작이 가능하다.

그렇지만, 1개의 Process에서 여러개의 AppDomain을 가지게 되는 프로그램들이 꼭 필요하게 되는데. 대표적인 프로그램이 IIS라고 할 수 있다. IIS의 동작은 iis_work에 대한 한개의 Process에서 각 Http Application의 접속 Connection에 따라서, 각각 AppDomain을 작성하게 되는데. Application Domain으로 동작하게 되면, 다음과 같은 장점이 있다.

1. Process 단위로 동작하는 것으로 가정하고 프로그래밍이 가능하다.
2. Process로 동작되는 것보다 시스템 리소스 소모가 작다.
3. 관리된 이름으로의 접근이 가능하다.

string appDomainName = string.Format("Server[{0:HH:mm:ss}_{1}]", DateTime.Now, DateTime.Now.Millisecond);
AppDomain appDomain = AppDomain.CreateDomain(appDomainName);
- AppDomain 생성

Console.WriteLine("Start AppDomain : {0}", appDomainName);
ProxySocketHandler proxySocketHandler = (ProxySocketHandler)appDomain.CreateInstanceAndUnwrap(
                                                                 Assembly.GetExecutingAssembly().FullName,
                                                                 "ProxyServerForMsn.SocketHandler.ProxySocketHandler");
- 생성된 AppDomain에서 Instace 생성

AppDomain 단위로 개발을 하게 되면 가장 큰 장점이라고 할 수 있는 것이.. 결국은 리소스의 관리에서 좀 더 원활한 개발을 할 수 있게 된다는 점이다. 그렇지만, AppDomain단위로 개발하는 것이 언제나 좋은 것은 아니다. 개발에서 귀찮은 점이 두가지가 생기게 된다. 하나는 static instance에 대한 사항이고, 나머지 하나는 thread safe에 대한 고려사항이다.

먼저, static instance는 모든 Process당 한개의 고정된 Instance를 보장하지 않는다. 정확히 .NET에서 static intance는 Process 당이 아닌 모든 AppDomain에 해당되는 single instance를 의미한다. 우리가 일반적으로 사용하는 application에서 이와 같은 점이 고려되지 않는 이유는 거의 대부분의 application들이 하나의 AppDomain만을 가지고 있기 때문이다. 이 점이 IIS에서 아무리 class들을 static으로 만들어도, 모든 web page나 connection에서 static instance 또는 method로 사용할 수 없는 이유가 된다.

그리고, thread safe에 대한 고려사항이다. 결론적으로 이야기 한다면, 일반적으로 thread safe를 보장하기 위해서 사용되는 lock 키워드나, ManualResetEvent, AutoResetEvent를 모두 사용하지 못한다. 이러한 방법들은 모두 한개의 AppDomain에서 동작하는 Thread들에서 서로간의 간섭을 알 수 있지, 다른 AppDomain에서의 간섭을 전혀 알지 못한다. Thread에 대한 friendly name은 내부적으로 AppDomainName과 ThreadName의 조합으로 만들어지기 때문에 이와 같은 사항을 전혀 고려할 수 없게 된다.


Posted by Y2K
,

2월 26일

2009. 2. 26. 10:53

보호되어 있는 글입니다.
내용을 보시려면 비밀번호를 입력하세요.

Scrum

Book 2009. 2. 21. 03:04


  Scrum을 소개를 받은 것은 약 2년전즈음에, 잘 아는 개발자들과 XP에 대해서 토론을 하다가 Microsoft와 NCsoft에서 적용하고 있는 Agile방법론이라는 것을 들을 때였다. 그 뒤에 이 책이 소개가 되었을때는 Agile 방법론을 팀에 적용하는 것에 대하여 회의를 가지고 있어, "뭐.. 뻔한 XP에 대한 서적이 또하나 나왔구나" 라는 생각으로 한동안 읽을 생각을 전혀 하지 않다가, 우연한 기회로 책을 받아 읽게 되었다. 

  읽고 난 후의 느낌이란 C로 Hello World를 만들다가 C#으로 Hello World를 만든 느낌이라고 해야지 될까. 내가 지금까지 고민하던 여러가지 사항을  해결해주면서, 또한 내 자신에게 많은 고민을 안겨준 속이 시원하기도 하면서 약간은 답답한 느낌을 주는 책이라는 느낌이다.

  사실 Scrum이란 추천글의 김창준님이 적으신 것 처럼 "전체 규칙을 설명하는데 10분도 안걸리는 방법이다. 그렇지만 이러한 방법을 실천해나가기 위해서는 아직 우리나라에서는 험난한 과정을 거쳐야지 되는 것이 사실이다. 자신의 소스를 감추는 개발자, 지나가는 길에 간단히 물어본 대답에 프로젝트의 전체 일정이 결정되고,
개발이 될 뿐 유지보수가 전혀 되지 않는 환경자체에서 우리가 Scrum을 지혜롭게 적용할려면 어떻게 해야지 될까. 라는 생각이 될때. 10분도 안걸리는 방법이라는 Scrum을 설명하는 이 책에서 보다 더 많은 내용을 얻을 수 있다고 생각된다. 

  이 책은 Scrum을 개인에게 적용하는 방법부터 팀에게 적용하는 방법까지 Scrum에 대한 많은 사례들과 To-Do List로 채워져있다. 가장 인상깊게 본 내용을 뽑으라면 개인에게 Scrum을 적용하기 위한 가장 작은 발걸음이자 가장 중요한 일인 BackLog 작성에 관한 내용이다. 자신이 해야지 될 일들에 대한 Sprint BackLog를 작성하고, 그 Sprint BackLog를 하나둘씩 제거해나가는 것이다. 어찌보면 가장 쉬우면서도 자신이 해야지 될  일을 정확히 판단하고 기술 할 수 있어야지 된다. 쉬우면서 어려운 방법이라고 해야지 될까. 이러한 시작이 되는 작은 발걸음 뿐 아니라, 팀내에서 Scrum을 적용하는 방법에 대해서도 많은 것들을 알려주고 있는데... 이러한 많은 방법들과 다양한 사항들을 볼때, 과연 팀에서 이렇게 적용이 가능한 것일까? 라는 의문이 개인적으로는 계속해서 생긴다. 

  이 책에서 말하는 내용은 이미 XP에 대해서 기술한 애자일 프랙티스나 린 소프트웨어 개발방법론에서 기술된 내용들도 꽤나 많다. 이것은 애자일에 대한 공통적인 항목이니 당연히 그럴만도 하겠지만, 읽다보면.. 결국은 프로젝트의 성공을 위해서는 끊임없는 자기 혁신, 의사소통, 보다 인간다움, 지치지 않는 열정이 필요하다는 말을 달리 표현 해놓은 것 같아서 약간은 씁쓸하기도 하다. 그리고, "이렇게 정의적인 느낌이 드는 것 조차 현장에서는 전혀 안되고 있는데.." 라는 생각이 들면서 기분이 나뻐지는 것은 이 분야에서 일하는 사람들은 다들 같은 생각들이 아닐까? 

  그렇다고, 내가 이 책을 비하가거나 낮은 점수를 주는 것은 절대로 아니다. 이 책은 프로젝트를 성공하기 위해서 무엇이 필요한지에 대한 지혜를 제공한다. 팀단위 뿐 아니라, 자기자신 개인에게도 말이다. 무엇보다도 이 책은 프로젝트 관리에 대한 가장 자연스럽고 단순하며 상징적인 방법을 재조명하고 있다는 점에서 높게 살 수 있다. 그리고 약간은 비관론적으로 생각되는 것이 결국은 "용기"가 부족해서 하지를 못하고 있다는 증거가 아닐까 싶다.

  국민이 모여서 나라가 되듯이, 개인이 모여서 팀이 이루어진다. 비록 적용하기 힘든 현실이긴 하지만, 자신의 Sprint BackLog부터. 옆자리의 동료와의 일일 스크럼 회의부터 시작될 수 있다면 보다더 Happy한 Project를 할 수 있지 않을까. 

PS : 불행히도 내가 아는 대부분의 조직은 개발자가 약자다. 다른 팀에서의 방해를 막을 힘이 있는 개발팀을 거의 본 기억이 없다. 거의 모든 개발팀은 외부로부터의 혼란, 복잡성과 불확실성에서부터 보호받지 못한다. 보호를 받고 성공한 프로젝트(국내의!!)에 대한 홍보나 한국에서의 애자일 방법의 적용 같은 주제를 다룬 책이 있다면 어떨까.. 하는 생각이 무척 많이 든다.


Posted by Y2K
,

한국과학기술원(KAIST)이 차세대 콘솔 게임기 개발에 나선다.

16일 KAIST 엔터테인먼트공학연구소(소장 원광연)는 KAIST 문화기술대학원과 함께 사용자와 미디어 간 상호작용을 조사해 차세대 콘솔 게임기를 개발한다고 밝혔다.

이명박 대통령의 ‘닌텐도’ 발언 이후 콘솔 게임기에 대한 관심이 높아진 가운데 국내 최고의 연구대학에서 개발 의사를 밝혀 ‘포스트 닌텐도’가 현실화될지 관심이 집중된다.

KAIST 엔터테인먼트공학연구소는 단순히 첨단 기술이 집적된 콘솔 게임기를 개발하는 것이 아니라 인문 계열인 문화기술대학원과 협동으로 사용자의 취향과 심리를 파악해 이를 게임기에 반영할 계획이다. 닌텐도가 기존 게임기와 달리 게임을 하지 않던 여성 등의 인구를 전혀 다른 소재의 게임으로 끌어들일 수 있었던 이유는 이런 인문학적 연구에 기반한다.

KAIST 엔터테인먼트공학연구소가 개발할 콘솔 게임기는 ‘닌텐도DS’와 같은 휴대형 게임기는 아니다. 연구소는 마이크로소프트의 ‘X박스’나 소니의 ‘플레이스테이션’ 같은 가정용 콘솔 게임기를 개발할 예정이다. 

연구소는 안경처럼 쓰면 눈앞에 3차원 가상현실이 펼쳐지는 HMD(Head Mount Display) 기반 기술을 보유하고 있는데 이를 콘솔 게임기에 적용할 계획이다. 연구소는 모바일과 IPTV 등 미래 핵심기술을 아우르는 플랫폼으로 차세대 콘솔 게임기의 구상을 마쳤다. KAIST는 콘솔 게임기 핵심 기술을 개발해 시제품을 만들고 기업에 이전할 방침이다.

우탁 KAIST 엔터테인먼트공학연구소 선임연구원은 “이번에 개발할 차세대 콘솔 게임기는 ‘융합(Convergence)’이 핵심 개념으로, 사용자와 게임기 간 물리적 상호작용에 대한 연구를 기반으로 한다”며 “공간과 콘텐츠의 제약을 벗어나는 차세대 콘솔 게임기가 개발되면 우리나라 게임 산업에 재도약의 기회가 올 것”이라고 말했다.

김인순기자 insoon@etnews.co.kr

도대체.. 쥐새끼 말 한마디에 이런 식으로 가는 나라가 어디에 있냐고...
MS, SONY, NINTENDO 3개 회사로 포화가 되어있는 게임기 시장에 이제와서 3DO 와 같은 기기 하나 가지고 가서 박살이 날것 같은 예언같은 예상만이 팍팍 들고 있다. 

MS가 Xbox를 출시하고 쏟아부은 7천억원이라는 마케팅 비용같은 것은 어떻게 할려고. --;

차라리 저럴봐에는 게임 개발자들 처우를 생각하는 것이 더 나은 길인데. IT는 실업자를 양산해왔다. 라는 말이나 지꺼리는 쥐새끼가 통치하는 나라에서 성공하는 콘솔이 나온다? 

손톱먹고 사람된 주제에 정말로 기가 막하다. 정말. --;

하긴.. 쥐새끼 뽑은 국민이나.. 쥐새끼나.. -_-;;;;;; (너무 패배주의적인가.;)

여기에서 명텐도 관련 링크~
밤에 보다가 뒤집어졌음. ^^


Posted by Y2K
,

IronPython 2.0

.NET Framework 2009. 2. 12. 22:34
예전부터 관심이 있었지만, 일단 깔아두기만 하고 간단한 string 처리 부분만 알아보고 있다가.. 이번에 회사에서 계속해서 바뀌게 되는 Scheduler 내용을 좀 편하게 업데이트해보고자 사용해봤다. 

일단, 첫 느낌은 매우 만족. 
다른 것보다 .NET Class 들을 간단히 들고와서 모두 사용할 수 있기때문에 너무나 편하긴 한데. 
아직까지는 좀 걸리는 점이 많아서 조금은 아쉬운 상태. 

무엇보다도. 개인적으로 가장 많이 사용하는 feedparser.py 에서 버그를 가지고 있다. 
CPython에서 동작되는 것과 다르게 동작이 되고, 에러가 나서 MIME mail Message를 해석하지를 못한다. 

다른 것들은.. 개인적으로 Python에 대한 지식이 미천하여 사용은 그다지 잘 못해봤지만 Windows Scheduler에 올릴 간단한 Script 적 프로그램을 Compile 시키는 것보다는 훨씬 나은 선택임은 틀림없을 것 같다. 

IronPython을 사용하는 가장 큰 이유가 될 .NET Library Load는 다음과 같다.
1. clr의 Import
2. clr.AddReference를 통해 Library 파일 로드 (파일 이름대로 Load를 시킨다.)
3. namespace를 이용해서 필요한 Class들을 로드시키면 된다. 

import clr
clr.AddReference('MyLIB1.dll')
clr.AddReference('MyLIB2.dll')
clr.AddReference('MyLIB3.dll')

import System
from com.hostway.lib.Configuration import Configuration
from com.hostway.lib.Database import CSDatabase
from com.hostway.lib.Database.Structure import SmsQueueItem
from com.hostway.lib.Communications.SKT.SKTComm import SmsSender

Posted by Y2K
,
Unit Test에서 사용되는 NUnit를 보면 [Test] attribute와 [TestFixture] attribute를 이용해서 각각의 Test Class와 Method를 모두 얻어내게 된다.

먼저, Assembly의 Load는 간단하게 Assembly의 static method를 통해서 얻어올 수 있다.

Assembly assem = Assembly.LoadFile(PlugInFileName);

여기에서 Assembly의 각 Class의 Attribute의 조건을 얻어오기 위해서 static class를 하나 생성

public static bool HasAttribute(MemberInfo member, string attrName, bool inherit)
{
    object[] attributes = member.GetCustomAttributes(inherit);
    foreach(Attribute attribute in attributes)
    {
        if(IsInstanceOfType(attrName, attribute))
        {
            System.Diagnostics.Debug.WriteLine(attribute);
            return true;
        }
    }
    return false;
}

얻어진 결과를 linq를 이용해서 검색. (이런 곳에서도 linq는 이용 가능하다. ^^)

var types = from t in assem.GetTypes()
            where Reflect.HasAttribute(t, typeName, true) 
            select t;

이렇게 얻어온 Class를 Activator를 이용해서 Instance를 생성해주면 외부 assembly가 가지고 있는 class의 동적 이용 및 NUnit과 비슷한 동작을 하는 것이 가능해진다. 

ISYNCmailPlugIn plugIn = Activator.CreateInstance(type) as ISYNCmailPlugIn;

Remote에서 사용하게 될 경우에 장점은 다음과 같다. 
1. Compile시에 Dll이 필요하지 않다. 
: 동적 load가 되기 때문에 잦은 변경이 있는 lib인 경우에 사용이 편리하다. 

2. 특정한 Interface나 Attribute를 가지고 있는 경우에 외부에서 사용이 가능하다.
Posted by Y2K
,
Exception을 mail로 보내야지 될 때가 많아서, Exception handler를 간단하게 제작
가장 Key Point는 HttpContext static Member를 이용해서, web에서 발생된 Error도 같이 표현하고 있다는 것이 가장 좋은 점 같다. 

    /// <summary>HtmlError Object.</summary>
    public class ExceptionToHtmlConverter
    {
        public enum ErrorInfoType
        {
            Exception,
            Context
        };
        private delegate string ErrorRendererDelegate(string headerString, NameValueCollection collection);
        static private Hashtable GetErrorInfo(Exception exception)
        {
            Hashtable errorTable = new Hashtable();
            if(exception == null)
            {
                throw new ArgumentNullException("exception");
            }

            // Populate Error Information Collection
            ArrayList errorInfoArray = new ArrayList();
            while(exception != null)
            {

                // Populate Error Information Collection
                NameValueCollection errorInfo = new NameValueCollection();
                errorInfo.Add("Exception Type", exception.GetType().ToString());
                errorInfo.Add("Message", exception.Message);
                errorInfo.Add("Source", exception.Source);
                errorInfo.Add("TargetSite", exception.TargetSite.ToString());
                errorInfo.Add("StackTrace", exception.StackTrace);

                errorInfoArray.Add(errorInfo);
                exception = exception.InnerException;
            }

            errorTable.Add(ErrorInfoType.Exception, errorInfoArray.ToArray());

            if(HttpContext.Current != null)
            {
                Hashtable info = new Hashtable();

                info.Add("QueryString", HttpContext.Current.Request.QueryString);
                info.Add("Form", HttpContext.Current.Request.Form);
                info.Add("Cookies", HttpContext.Current.Request.Cookies);
                info.Add("Session", HttpContext.Current.Session);
                info.Add("Server Variables", HttpContext.Current.Request.ServerVariables);

                errorTable.Add(ErrorInfoType.Context, info);
            }

            return errorTable;

        }
        /// <summary>Returns HTML an formatted error message.</summary>
        static public string GetHtmlError(Exception Ex)
        {
            if(Ex == null)
            {
                throw new ArgumentNullException("Ex");
            }

            // Error Message Header
            StringBuilder sb = new StringBuilder();
            Hashtable errorTable = GetErrorInfo(Ex);
            ErrorRendererDelegate renderer = CollectionToHtmlTable;

            if(HttpContext.Current != null)
            {
                sb.AppendFormat("<font face=\"Arial\" size=\"4\" color=\"red\">An error occurred at {0}<br><font size=\"3\">&nbsp;&nbsp;&nbsp;&nbsp;{1}</font></font><br><br>", HttpContext.Current.Request.Url.ToString(), Ex.Message);
            }

            // Populate Error Information Collection
            foreach(NameValueCollection errorInfo in errorTable[ErrorInfoType.Exception] as object[])
            {
                // Error Information
                sb.Append(renderer("Error Information", BuildCollection(errorInfo)));
            }

            if(errorTable[ErrorInfoType.Context] != null)
            {
                Hashtable info = errorTable[ErrorInfoType.Context] as Hashtable;

                foreach(DictionaryEntry entry in info)
                {
                    sb.AppendFormat("<BR><BR>\n{0}", renderer(entry.Key as string, BuildCollection(entry.Value as ICollection)));
                }
            }

            return sb.ToString();
        }

        /// <summary>Returns HTML an formatted error message.</summary>
        static public string GetTextError(Exception Ex)
        {
            if(Ex == null)
            {
                throw new ArgumentNullException("Ex");
            }

            // Error Message Header
            StringBuilder sb = new StringBuilder();
            Hashtable errorTable = GetErrorInfo(Ex);
            ErrorRendererDelegate renderer = CollectionToTextTable;

            // Populate Error Information Collection
            foreach(NameValueCollection errorInfo in errorTable[ErrorInfoType.Exception] as object[])
            {
                // Error Information
                sb.Append(renderer("Error Information", BuildCollection(errorInfo)));
            }

            if(errorTable[ErrorInfoType.Context] != null)
            {
                Hashtable info = errorTable[ErrorInfoType.Context] as Hashtable;

                foreach(DictionaryEntry entry in info)
                {
                    sb.AppendFormat("\r\n{0}", renderer(entry.Key as string, BuildCollection(entry.Value as ICollection)));
                }
            }

            return sb.ToString();
        }

        #region Private Methods
        static private string CollectionToHtmlTable(string headerString, NameValueCollection collection)
        {

            // Heading Template
            const string heading = @"
  <tr>
    <td bgcolor=""#666666"" colspan=""2"">
      <font face=""Arial"" color=""white""><b>&nbsp;{0}</b></font>
    </td>
  </tr>
";
            // <TD>...</TD> Template
            const string TD = "<td><font face=\"Arial\" size=\"2\"><!--VALUE--></font></td>";

            StringBuilder sb = new StringBuilder();
            // Table Header
            sb.Append("\n<table width=\"100%\" bgcolor=\"#d1d9e4\" cellspacing=\"1\" cellpadding=\"3\">\n");
            sb.AppendFormat(heading, headerString);

            sb.AppendFormat("  <tr bgcolor=\"#d0d0d0\">\n");
            sb.AppendFormat("    {0}\n", TD.Replace("<!--VALUE-->", "&nbsp;<b>Name</b>"));
            sb.AppendFormat("    {0}\n", TD.Replace("<!--VALUE-->", "&nbsp;<b>Value</b>"));
            sb.Append("  </tr>\n");

            // No Body? -> N/A
            if(collection.Count == 0)
            {
                collection = new NameValueCollection();
                collection.Add("N/A", "");
            }

            // Table Body
            for(int i = 0; i < collection.Count; i++)
            {
                sb.AppendFormat("  <tr valign=\"top\" bgcolor=\"{0}\">\n", (i % 2 == 0) ? "white" : "#f4f4f4");
                sb.AppendFormat("    {0}\n", TD.Replace("<!--VALUE-->", collection.Keys[i]));
                sb.AppendFormat("    {0}\n", TD.Replace("<!--VALUE-->", CleanHtml(collection[i])));
                sb.Append("  </tr>\n");
            }

            // Table Footer
            sb.Append("</table>");

            return sb.ToString();
        }
        static private string CollectionToTextTable(string headerString, NameValueCollection collection)
        {

            // Heading Template
            const string heading = "{0}\r\n\r\n";


            StringBuilder sb = new StringBuilder();
            sb.AppendFormat(heading, headerString);


            // No Body? -> N/A
            if(collection.Count == 0)
            {
                collection = new NameValueCollection();
                collection.Add("N/A", "");
            }

            // Table Body
            for(int i = 0; i < collection.Count; i++)
            {
                sb.AppendFormat(" {0} : {1}\r\n", collection.Keys[i], collection[i]);
            }

            return sb.ToString();
        }
        
        static private NameValueCollection BuildCollection(ICollection collection)
        {
            // Overload for HttpCookieCollection collection.
            // Converts HttpCookieCollection to NameValueCollection
            if(collection == null)
            {
                collection = new NameValueCollection();
            }
            NameValueCollection nvCollection = new NameValueCollection();
            IEnumerator en = collection.GetEnumerator();

            object entry;
            while(en.MoveNext())
            {
                entry = en.Current;

                if(collection is HttpCookieCollection)
                {
                    nvCollection.Add(entry.ToString(), (collection as HttpCookieCollection)[entry.ToString()].Value);
                }
                    // Min
                    // CANNOT_MAKE_TEST_CONDITION
                else if(collection is HttpSessionState)
                {
                    nvCollection.Add(entry.ToString(), (collection as HttpSessionState)[entry.ToString()].ToString());
                }
                    // END-CANNOT_MAKE_TEST_CONDITION
                else if(collection is NameValueCollection)
                {
                    if(entry != null)
                    {
                        nvCollection.Add(entry.ToString(), (collection as NameValueCollection)[entry.ToString()].ToString());
                    }
                }
                else
                {
                    throw new NotSupportedException(collection.GetType().ToString());
                }
            }
            return nvCollection;
        }


        static private string CleanHtml(string html)
        {
            // Cleans the string for HTML friendly display
            return (html.Length == 0) ? "" : html.Replace("<", "&lt;").Replace("\r\n", "<BR>").Replace("&", "&amp;").Replace(" ", "&nbsp;");
        }
        #endregion
    }

Posted by Y2K
,