이 기사는 ASP.NET 2.0 시험판 버전을 기준으로 합니다. 여기에 포함된 모든 정보는 변경될 수 있습니다.

이 기사에서는 다음 내용에 대해 설명합니다.
  • 웹 파트를 사용하여 모듈식 웹 포털 응용 프로그램 만들기
  • 개인 설정 및 사용자 지정 기능
  • 사용자 지정 사용자 컨트롤을 웹 파트로 사용
  • 개인 설정 공급자 만들기
이 기사에서 사용하는 기술:
ASP.NET 2.0


코드 다운로드 위치:
WebParts.exe(619KB)

포털 응용 프로그램은 현재 매우 널리 사용되고 있으며, 뛰어난 포털 응용 프로그램에는 몇 가지 공통점이 있습니다. 실용적인 포털에서는 모듈식의 일관성 있고 탐색이 용이한 사용자 인터페이스를 통해 다양한 콘텐츠가 제공됩니다. 보다 복잡한 포털에서는 사이트 구성원이 콘텐츠를 직접 작성하고 문서를 업로드하며 포털 페이지를 개인 설정할 수도 있습니다.

Microsoft에서는 Windows SharePoint Services를 출시하면서 Windows Server™ 2003 플랫폼에 확장 가능한 포털 프레임워크를 추가했습니다. SharePoint Services에서는 사이트 멤버십 지원, 콘텐츠 및 문서 관리, 웹 파트 사용을 통한 모듈식 데이터 표시 등을 포함하여 포털 프레임워크에 필요한 기본 요소를 제공합니다.

웹 파트는 사용자 지정 및 개인 설정을 수행하기 위한 기초가 됩니다. 또한 사용자는 사이트 구성에 따라 웹 파트를 추가, 재구성 및 제거하여 Windows SharePoint Services의 페이지를 손쉽게 개인 설정하거나 사용자 지정할 수 있습니다. 사용자 지정 웹 파트를 개발하면 Windows SharePoint Services를 기반으로 하여 사이트를 간편하고도 효과적으로 확장할 수 있습니다. 사용자 지정 및 개인 설정을 지원하는 사용자 지정 웹 파트를 만들려면 사용자 웹 파트 클래스에 속성을 추가하고 몇 가지 특수한 특성을 적용하면 됩니다. Windows SharePoint Services의 웹 파트 인프라에서는 사이트 사용자 지정 및 구성원 개인 설정에 관련된 번거로운 데이터 serialize, 저장 및 검색 작업을 모두 수행합니다.

ASP.NET 2.0에 새롭게 도입된 웹 파트 컨트롤 집합은 데이터 사용자 지정 및 개인 설정의 serialization, 저장 및 검색을 백그라운드로 처리하도록 만들어졌다는 점에서 Windows SharePoint Services의 웹 파트 컨트롤과 비슷합니다. 그러나 ASP.NET의 웹 파트 컨트롤은 SQL Server™ 또는 Active Directory에 밀접하게 결합되어 있지 않다는 점에서 차이를 보일 뿐 아니라 융통성이 뛰어난 이점이 있습니다. 이는 폼 기반 인증을 사용하여 포털 응용 프로그램을 빌드하거나 특정 데이터베이스 솔루션에 얽매이지 않으려는 업체에게 매우 유용한 특성입니다.

그림 1. 모듈식 웹 파트 디자인을 보여 주는 샘플 포털
그림 1. 모듈식 웹 파트 디자인을 보여 주는 샘플 포털

이 기사에서는 ASP.NET 2.0의 웹 파트를 사용하여 작성한 샘플 응용 프로그램을 살펴봅니다. 이 기사의 주된 목적은 포털 응용 프로그램에 사용할 웹 파트를 개발할 때 발생할 수 있는 중요한 디자인 문제를 설명하는 것입니다. 먼저 ASP.NET 2.0의 새 웹 파트 컨트롤 집합을 사용하는 것과 관련된 기본 개념 및 컨트롤 유형에 대해 알아보겠습니다. 포털의 예는 그림 1을 참조하십시오.


웹 파트 입문
그림 2. 웹 파트 페이지의 일반적인 레이아웃
그림 2. 웹 파트 페이지의 일반적인 레이아웃

웹 파트를 호스트하는 페이지를 웹 파트 페이지라고 합니다. 웹 파트 페이지에는 그림 2와 같이 정확히 하나의 WebPartManager 컨트롤 인스턴스와 하나 이상의 WebPartZone 컨트롤이 있어야 합니다. 또한 선택적으로 EditorZone 또는 CatalogZone 컨트롤을 포함할 수 있습니다. WebPartManager 컨트롤의 태그는 .aspx 파일에서 웹 파트 인프라와 연관된 WebPartZone, EditorZone 및 CatalogZone 같은 다른 컨트롤 태그보다 앞에 놓여야 합니다. 웹 파트 페이지의 레이아웃과 모양을 보다 효과적으로 제어하기 위해 HTML 테이블을 사용하여 .aspx 파일 내에 다른 영역을 배열할 수도 있습니다.

다음과 같은 WebPartManager 및 WebPartZone 컨트롤이 포함된 간단한 웹 파트 페이지 예제를 살펴봅시다.

<asp:WebPartManager ID=" WebPartManager1" runat="server" />

<asp:WebPartZone ID="WebPartZone1" runat="server" HeaderText="Zone 1">
  <ZoneTemplate>
       <!-- time to add a Web Part -->
     </ZoneTemplate>
   </asp:WebPartZone>

WebPartZone이 있으면 웹 파트 정의를 사용하여 웹 파트 인스턴스를 만들 수 있습니다. 웹 파트 정의는 다음과 같은 두 가지 방식으로 만듭니다. 첫 번째는 WebPart 클래스에서 상속되는 사용자 지정 클래스를 만드는 것이고, 두 번째는 사용자 컨트롤을 만드는 것입니다. 이러한 두 가지 방법의 장단점은 이 기사의 뒷부분에서는 좀 더 자세히 살펴보겠습니다. 지금은 일단 WebPart에서 파생되는 간단한 클래스를 작성해 봅니다(그림 3 참조).

각 웹 파트 인스턴스는 특정 인덱스에 있는 특정 WebPartZone 내의 특정 페이지에 있습니다. 하나의 WebPartZone에는 여러 개의 웹 파트가 포함될 수 있습니다. 예를 들어 그림 4에는 WebPartZone1이라는 영역 내에 두 개 웹 파트가 있습니다.

그림 4. 특정 영역의 웹 파트
그림 4. 특정 영역의 웹 파트

웹 파트는 프로그래밍 방식으로 또는 선언적으로 영역에 추가할 수 있습니다. 잠시 후 파트 카탈로그에 웹 파트를 추가하는 기법에 대해서도 살펴보겠습니다. CatalogPart를 만들고 나면 사용자가 런타임에 WebPartZone에 새 웹 파트를 추가할 수 있습니다.

코드를 사용하여 WebPartZone에 웹 파트를 추가하는 방법은 사용하는 웹 파트 유형에 따라 달라집니다. WebPart에서 상속되는 클래스가 있는 경우 프로그래밍 방식으로 클래스 인스턴스를 만들고 WebPartManager 클래스의 AddWebPart 메서드를 호출하면 됩니다. AddWebPart를 호출하려면 웹 파트 인스턴스, 대상 WebPartZone, 그리고 웹 파트가 대상 영역에서 표시될 인덱스를 지정하는 매개 변수를 전달해야 합니다.

// WebPart 파생 클래스에서 웹 파트 인스턴스를 만듭니다.
WebPart wp1 = new WingtipWebParts.HelloWorld();
WebPartManager1.AddWebPart(wp1, WebPartZone1, 0);(참고: 프로그래머 코멘트는 샘플 프로그램 파일에는 영문으로 제공되며 기사에는 설명을 위해 번역문으로 제공됩니다.)

이와 같이 프로그래밍 방식으로 WebPartZone에 웹 파트를 추가할 수 있습니다. 선언적 방식에서는 웹 파트 페이지의 .aspx 파일 내에 컨트롤 태그를 사용합니다. 사용자가 페이지를 처음 검색할 때 특정 WebPartZone에 웹 파트가 표시되도록 하려면 WebPartZone에 ZoneTemplate을 추가합니다.

<%@ Register Assembly="WingtipWebParts" Namespace="WingtipWebParts"
TagPrefix="Wingtip" %>

<asp:WebPartManager ID=" WebPartManager1" runat="server" />

<asp:WebPartZone ID="WebPartZone1" runat="server" HeaderText="Zone 1">
  <ZoneTemplate>
    <Wingtip:HelloWorld runat="server" id="HelloWorld" />
  </ZoneTemplate>
</asp:WebPartZone>

이 때 몇 가지 내용을 추가하지 않으면 웹 파트 페이지의 웹 파트가 너무 건조해 보일 것입니다. 인트라넷 포털 응용 프로그램에서 일반적으로 요구되는 기능은 사이트에 "스킨"을 적용하여 사용자의 취향에 따라 모양을 바꾸는 기능입니다. 예전에는 이를 위해 직접 인프라를 빌드하여 컨트롤 렌더링 변경을 지원해야 했으며, 여기에는 많은 작업이 수반되었습니다. ASP.NET 2.0에는 "테마"라는 개념이 도입되었습니다. 테마란 개별 컨트롤, 전체 페이지 또는 전역 응용 프로그램 수준까지 적용할 수 있는 스타일 및 컨트롤 특성 모음입니다.

포털 응용 프로그램을 보다 세련되고 전문적으로 만들려면 WebPartZone, EditorZone 및 CatalogZone 컨트롤의 모양을 모두 사용자 지정합니다. 웹 파트, 편집기 파트 및 카탈로그 파트의 본문, 제목 표시줄 및 동적 메뉴가 표시되는 모양에 스킨을 지정하고 사용자의 고유한 특성을 적용해야 하기 때문에 이 작업은 처음에 매우 지루할 수 있습니다. 다행히도 ASP.NET 2.0의 새 테마 기능을 사용하면 .aspx의 시각 효과 작업을 고려하여 다시 사용 가능한 .skin 및 .css 파일에 적용할 수 있습니다. 이 기사의 샘플 포털 응용 프로그램은 스킨 및 테마를 사용하여 웹 파트 모양을 사용자 지정하며, MSDN® Magazine 웹 사이트에서 이를 사용할 수 있습니다.


모드 및 페이지 범위 표시

하나의 WebPartManager 컨트롤 인스턴스가 각 웹 파트 페이지에서 실행되며, 웹 파트 인스턴스 및 해당 인스턴스가 WebPartZone 컨트롤에 관련되는 방식을 관리합니다. 또한 WebPartManager 컨트롤에서 제공하는 프로그래밍 방식 인터페이스를 사용하여 찾아보기, 디자인 및 편집 디스플레이 모드 사이에서 웹 파트 페이지를 전환할 수 있습니다. 예를 들어 프로그래밍 방식으로 현재 페이지를 디자인 모드로 전환하려면 다음과 같이 DisplayMode 속성을 DesignDisplayMode로 설정하는 이벤트 처리기가 있는 링크 컨트롤을 추가하면 됩니다.

WebPartManager1.DisplayMode = WebPartManager.DesignDisplayMode;

또한 웹 파트 페이지의 각 디스플레이 모드별 차이점을 이해해야 합니다. 기본적으로 웹 파트 페이지는 찾아보기 모드에서 작동하며, 이 모드에서 사용자는 웹 파트를 변경할 수 없습니다. 웹 파트 페이지를 디자인 모드로 전환하면 사용자는 WebPartZone 내에서 또는 영역 간에 웹 파트를 이동할 수 있습니다. 그림 5에는 사용 가능한 디스플레이 모드가 나와 있습니다.

ASP.NET 2.0 웹 파트 컨트롤은 필요한 DHTML 및 JavaScript를 백그라운드로 생성하여 브라우저 내에서 끌어서 놓는 작업을 수행할 수 있도록 합니다. 필요한 DHTML 및 JavaSript 기능을 지원하지 않는 브라우저에서도 끌어서 놓기 편집 기능을 제외한 모든 기능을 사용할 수 있습니다. 그렇기 때문에 모든 클라이언트에서 Microsoft Internet Explorer 5.0 이상을 사용하지 않아도 됩니다. ASP.NET 2.0 웹 파트 컨트롤은 또한 개인 설정 데이터의 저장 및 검색 작업을 관리하여 사용자가 이전 세션에서 웹 파트를 배치한 위치를 기억합니다.

WebPartManager에서는 디스플레이 모드 간에 코드를 전환할 수 있는 기능 외에도 사용자 범위 및 공유 범위 간에 웹 파트 페이지를 변경하는 방법을 제공합니다. 페이지 범위에 따라 웹 파트 변경 내용이 사용자 지정인지 개인 설정인지 여부가 결정됩니다. 사용자 지정 변경 내용은 모든 사용자에게 표시되지만, 개인 설정 변경 내용은 해당 변경 작업을 수행한 사용자에게만 표시됩니다. 웹 파트 변경 작업은 다음과 같이 WebPartManager 컨트롤의 ToggleScope 메서드를 호출하여 수행됩니다.

WebPartManager1.Personalization.ToggleScope();

기본 범위는 사용자 범위이며 여기서 웹 파트 변경 내용은 현재 사용자에게만 표시되는 개인 설정으로 기록됩니다. ToggleScope 메서드를 제대로 호출하면 웹 파트 페이지가 공유 범위에 배치되며, 변경 사항이 사용자 지정으로 기록됩니다. 공유 범위의 용도는 관리자나 사이트 디자이너가 모든 사용자에게 표시되는 웹 파트 페이지에서 웹 파트에 대해 사용자 지정 변경 작업을 일괄적으로 수행할 수 있도록 하는 것입니다. 사용자가 수행한 모든 개인 설정 변경 내용이 전역 사용자 지정과 중복되는 경우에는 개인 설정 변경 내용이 항상 우선합니다.

현재 사용자가 항상 공유 범위로 들어갈 수 있는 것은 아닙니다. 기본적으로 공유 범위로 들어가는 데 필요한 권한을 가진 사용자는 없습니다. 공유 범위로 들어가려면 Web.config 파일에서 해당 권한을 부여 받아야 합니다. 다음은 admin 또는 site_designer 역할로 모든 사용자에 대해 공유 범위로 들어가 전역 사용자 지정을 수정할 수 있는 권한을 부여하는 예제입니다.

<webParts>
  <personalization>
    <authorization>
      <allow roles="admin, site_designer" verbs="enterSharedScope" />
    </authorization>
  </personalization>
</webParts>

속성 및 개인 설정

각 웹 파트 개체에는 사용자 지정 또는 개인 설정할 수 있는 표준 속성 집합이 있습니다. 예를 들어 각 웹 파트에는 WebPartZone에 추가한 후 사용자 지정할 수 있는 Title 속성이 있습니다. EditorZone 컨트롤 및 다양한 편집기 파트를 사용하면 사용자가 웹 파트 속성을 변경할 수 있습니다.

사용자가 웹 파트 페이지를 EditDisplayMode로 설정하면 웹 파트 메뉴에 Edit 명령이 제공됩니다. 특정 웹 파트에서 Edit 명령을 호출하면 해당 영역에 배치한 편집기 파트와 함께 EditorZone이 표시됩니다. ASP.NET 2.0에는 표준 웹 파트 모양, 동작 및 레이아웃을 수정하기 위한 여러 개의 편집기 파트가 기본적으로 제공됩니다.

웹 파트 개인 설정 스키마는 개인 설정 가능한 사용자 지정 속성을 추가하여 간편하게 확장할 수 있습니다. 웹 파트 클래스 정의에 속성을 추가하고 Personalizable, WebBrowsable 및 WebDisplayName 같은 특성을 적용하면 됩니다. 이 작업을 완료하고 나면 웹 파트 컨트롤에서 사용자 지정 또는 개인 설정된 속성 값을 저장 및 검색합니다.

사용자 지정 속성을 만들 때는 보통 다음과 같이 개인 필드를 정의합니다.

private bool _HR = true;

[Personalizable(PersonalizationScope.User), 
 WebBrowsable, WebDisplayName("HR 뉴스 표시"),
 WebDescription("이 속성을 사용하여 HR 뉴스 표시/숨김")]
public bool HR 
{
  get { return _HR; } set { _HR = value; }
}

사용자가 이와 같이 속성을 개인 설정할 수 있도록 하려면 PropertyGridEditorPart를 현재 페이지의 EditorZone에 추가하면 됩니다. 그림 6은 이 작업을 수행할 때 사용자에게 표시되는 화면을 보여줍니다.

그림 6. 사용자가 웹 파트를 개인 설정할 수 있는 편집기 파트
그림 6. 사용자가 웹 파트를 개인 설정할 수 있는 편집기 파트

문자열 또는 숫자 형식을 기반으로 웹 파트 속성을 정의하는 경우 PropertyGridEditorPart에서는 사용자가 값을 변경하도록 입력란을 제공합니다. 부울 값을 기반으로 웹 파트 속성을 정의하는 경우에는 PropertyGridEditorPart에서 그림 6과 같은 확인란을 제공합니다.

그림 7에는 Windows SharePoint Services를 사용한 웹 파트 개발에서 사용할 수 있는 유용한 프로그래밍 기법이 나와 있어, 해당 유형을 열거 유형을 기반으로 하는 개인 설정 속성을 정의할 수 있습니다.

열거를 기반으로 하는 개인 설정 속성인 WebBrowsable을 만들면 그림 7에서 Timeframe 속성에 대해 표시한 것처럼 PropertyGridEditorPart에서 사용자가 사용 가능한 속성 설정의 드롭다운 목록을 생성하므로 매우 유용합니다. 그러므로 사용자는 편리하게 작업을 수행하고 올바른 속성 값을 선택할 수 있습니다.

Timeframe 속성 정의에 대해서 알아두어야 할 사항이 한 가지 더 있습니다. 이 속성 정의는 PersonalizationScope.Shared 설정이 있는 Personalizable 특성으로 정의되었습니다. 이러한 방식을 사용하여 공유 속성으로 정의된 속성은 사용자 지정할 수 있지만 개인 설정할 수는 없습니다. 공유 속성은 개인 설정에 사용할 수 없으므로 현재 웹 파트 페이지가 사용자 범위에 있으면 해당 속성은 PropertyGridEditorPart에 표시되지 않으며, 페이지가 공유 범위에 있을 때만 표시됩니다.


웹 파트 카탈로그

지금까지 웹 파트를 WebPartZone으로 채우는 웹 파트 페이지를 살펴보았습니다. 이 기법은 사용자가 런타임에 새 웹 파트를 추가하도록 하는 다른 방식을 통해 보다 수준 높게 활용할 수 있습니다. 이러한 작업은 PageCatalogPart 및 DeclarativeCatalogPart 같은 CatalogZone 및 CatalogPart를 사용하여 수행할 수 있습니다(그림 8 참조).

이와 같은 방식으로 CatalogZone 및 CatalogPart를 추가하고 나면 사용자는 그림 9와 같은 사용자 인터페이스를 통해 런타임에 동적으로 웹 파트를 추가할 수 있습니다.

그림 9. 사용자가 동적으로 웹 파트를 추가할 수 있도록 하는 CatalogZone
그림 9. 사용자가 동적으로 웹 파트를 추가할 수 있도록 하는 CatalogZone

먼저 PageCatalogPart의 용도를 이해해야 합니다. 디자인 디스플레이 모드 또는 편집 디스플레이 모드에서 사용자는 웹 파트에 대해 Close 명령을 호출할 수 있습니다. 사용자가 웹 파트를 닫으면 나중에 사용자가 해당 WebPart를 다시 추가할 수 있도록 웹 파트 및 개인 설정 또는 사용자 지정 설정이 보존됩니다. 그러므로 PageCatalogPart에는 닫혀 있으며 페이지에 추가할 수 있는 모든 웹 파트 목록이 표시됩니다.

Close 명령은 Delete 명령과는 다릅니다. Delete 명령은 편집 디스플레이 모드에서만 사용할 수 있습니다. 사용자가 웹 파트를 삭제하면 사용자 지정 및 개인 설정 데이터를 포함하여 웹 파트의 존재에 관한 모든 정보가 저장소에서 삭제됩니다.

웹 파트를 사용하여 DeclarativeCatalogPart를 선언적으로 채울 수 있습니다. 그림 8의 코드에서는 사용자 지정 이름의 WeatherWebPart로 이 카탈로그를 채우는 방법을 보여 줍니다. 이 방법을 사용하면 사용자가 모든 종류의 웹 파트를 사용하도록 할 수 있습니다.

이 기사의 내용을 보충하고 웹 파트 페이지 빌드에 대해 보다 자세히 알아보려면 Stephen Walther의 기사인 "Introducing the ASP.NET 2.0 Web Parts Framework"(영문)를 참조하십시오. 이 기사에는 EditorZone 및 CatalogZone을 사용한 웹 파트 페이지 빌드에 대한 보다 자세한 내용과 실제 예제가 나와 있습니다. 또한 Verbs, Connections, 웹 파트 가져오기/내보내기 등 보다 수준 높은 주제에 대해서도 설명하고 있습니다.


ASP.NET 2.0 포털 개발

ASP.NET 2.0에는 WebPart 인프라 자체 외에도 인트라넷 포털 사이트 개발에 필요한 여러 가지 유용한 새 기능이 있습니다. 이 기사 앞부분에서 언급했듯이 테마 및 스킨이 도입되어 포털 페이지에서 스타일 속성을 구분하기가 매우 간편해졌으며, 개별 페이지를 변경하지 않고도 일괄적으로 스타일 변경 사항을 적용할 수 있게 되었습니다. 그러나 보다 맘에 드는 것은 마스터 페이지의 도입입니다. 마스터 페이지를 사용하면 모든 WebPartZone 및 전체 WebPartManager 컨트롤을 단일 템플릿 페이지로 분리할 수 있습니다. 이 템플릿 페이지에서 개별 페이지는 기본 모양 및 기능을 상속합니다. 이 기사의 샘플 포털에서는 마스터 페이지에 있는 각 WebPartZone의 ZoneTemplate 내에 ContentPlaceholder를 추가하는 흥미로운 기법을 사용합니다. 이 방법을 사용하면 해당 마스터 페이지를 사용하는 콘텐츠 페이지는 각각의 ContentPlaceHolder 컨트롤로 매핑되는 Content 컨트롤을 사용하여 자체 웹 파트를 추가할 수 있습니다.

포털 사이트용 마스터 페이지를 디자인할 때 결정해야 하는 사항 중 하나는 사용자가 사용자 지정 기능을 사용할 수 있도록 하는 방법입니다. 앞서 살펴본 것처럼 페이지에 추가하는 영역 유형에 따라 사용자는 다양한 사용자 지정 모드를 선택하여 페이지를 변경할 수 있습니다.

사용자에게 사용자 지정 옵션이 표시되도록 하는 한 가지 옵션은 WebPartManager 컨트롤 및 단추 컬렉션(보통 LinkButton)을 모두 사용자 컨트롤에 래핑하는 것입니다. 그러면 마스터 페이지에 해당 사용자 컨트롤이 추가되어 사이트의 모든 페이지에 대해 사용자 지정 옵션을 제공합니다. 웹 사이트에 마스터 페이지를 두 개 이상 만들려는 경우에는 이러한 컨트롤을 사용자 컨트롤(여기서는 WebPartManagerPanel이라고 함)에 캡슐화하는 방법도 유용합니다. 그러면 페이지의 일부 하위 집합에 대해 대체 레이아웃이 제공됩니다. 앞서 그림 1에서 살펴보았던 응용 프로그램의 메뉴 모음에는 WebPartManager의 현재 모드 및 범위를 표시하는 샘플 사용자 컨트롤이 표시되어 있습니다. 이 샘플 컨트롤에는 또한 페이지 모드를 WebPartManager에서 지원하는 편집 모드 중 하나로 변경하는 LinkButton도 제공됩니다(이것은 샘플 포털 응용 프로그램에서 사용하는 컨트롤임).

WebPartManagerPanel은 현재 사용자 및 페이지를 기준으로 사용 가능한 디스플레이 모드를 동적으로 표시하거나 숨기는 등의 다른 유용한 기능도 제공합니다. WebPartManager의 SupportedDisplayModes 컬렉션을 확인하고 이 정보를 사용하여 해당 모드를 표시하는 메뉴 항목을 설정하거나 해제함으로써 지원되는 디스플레이 모드를 쿼리할 수 있습니다. 예를 들어 현재 페이지에서 CatalogDisplayMode를 지원하는지 여부를 확인하려면 다음을 수행하십시오.

if (WebPartManager1.SupportedDisplayModes.Contains(
    WebPartManager.CatalogDisplayMode)) {
  //여기에 카탈로그 디스플레이 모드 LinkButton 표시
}

또한 적절한 권한이 없는 사용자 컨텍스트 내에서 실행하는 경우 ToggleScope 호출은 실패합니다. WebPartManager 컨트롤의 Personalization 속성이 제공하는 CanEnterSharedScope 속성을 쿼리하는 코드를 제공하여 사용자가 공유 범위로 들어올 수 있도록 하는 사용자 인터페이스 요소를 숨길 것인지 또는 표시할 것인지 여부를 결정하는 것이 좋습니다.

if (WebPartManager1.Personalization.CanEnterSharedScope)  {
  // 사용자가 공유 범위로 들어오도록 하는 UI 요소를 표시합니다.
}

이 기사에 나와 있는 샘플의 WebPartManagerPanel 사용자 컨트롤에는 현재 사용자 및 페이지 기능을 기반으로 디스플레이를 동적으로 조정하는 완전한 패널 구현이 포함되어 있습니다.


웹 파트로서의 사용자 컨트롤

Windows SharePoint Services를 사용하여 웹 파트를 빌드하는 과정에서 혼란을 야기하는 사항 중 하나는, 디자이너 기능은 전혀 활용하지 못하는 상태로 컨트롤의 전체 사용자 인터페이스를 프로그래밍 방식으로 만들어야 한다는 점입니다. 웹 파트는 대부분 상호 작용하는 서버측 컨트롤 컬렉션이기 때문에 웹 파트를 만들 때 Visual Studio 디자이너 기능을 사용하지 못하는 것은 상당히 아쉬운 점입니다. 이 문제를 확실하게 해결하는 방법은 개발자가 ASP.NET 사용자 컨트롤을 만들어 웹 파트로 사용하도록 하는 것입니다. SmartPart라는 타사 도구를 사용하면 사용자 컨트롤을 Windows SharePoint Services의 웹 파트로 적용할 수 있습니다.

ASP.NET 2.0 웹 파트를 사용하면 모든 컨트롤을 수정하거나 래핑할 필요없이 직접 웹 파트로 사용할 수 있으므로 이 문제가 해결됩니다. 이 방법은 사용자 컨트롤을 웹 파트 컬렉션에 통합하는 데 유용할 뿐 아니라, 이를 통해 현재 ASP.NET 페이지용으로 빌드한 사용자 컨트롤을 손쉽게 통합할 수 있습니다.

이 방법은 표준 컨트롤(비웹 파트)이 WebPartZone에 추가되면 WebPartManager.CreateWebPart가 암시적으로 호출되고, GenericWebPart 클래스 인스턴스가 할당되며 추가된 컨트롤을 사용하여 해당 컨트롤을 초기화하는 방식으로 내부적으로 작동합니다. GenericWebPart 클래스는 WebPart 기본 클래스에서 파생되며 핵심 웹 파트 속성 구현을 제공합니다. GenericWebPart가 구성되면 초기화에 사용된 컨트롤이 자식 컨트롤로 추가됩니다. 렌더링을 수행하는 동안 GenericWebPart는 응답 버퍼 자체로는 아무것도 렌더링하지 않으며 대부분의 복합 컨트롤이 그러하듯이 자식 컨트롤에 렌더링을 위임합니다. 그러면 최종적으로 페이지의 WebPartZone에 원하는 컨트롤을 모두 추가할 수 있으며 페이지는 제대로 작동합니다. 예를 들어 다음 페이지에서는 사용자 컨트롤과 표준 Calendar 컨트롤이 있는 WebPartZone을 정의합니다. 사용자 컨트롤과 Calendar 컨트롤은 모두 만들어질 때 GenericWebPart 클래스에 의해 암시적으로 래핑됩니다.

<%@ Register Src="webparts/CustomerList.ascx" 
    TagName="CustomerList" TagPrefix="Wingtip" %>

<asp:WebPartManager ID=" WebPartManager1" runat="server" />

<asp:WebPartZone ID="WebPartZone1" runat="server" HeaderText="Zone 1">
  <ZoneTemplate>
    <Wingtip:CustomerList runat="server" id="CustomerList" />
    <asp:Calendar runat="server" id="CustomerCalendar" />
  </ZoneTemplate>
</asp:WebPartZone>

표준 웹 파트와 마찬가지로, GenericWebPart에 의해 래핑된 컨트롤을 동적으로 만들 수 있습니다. 이 컨트롤이 사용자 컨트롤인 경우에는 Page.LoadControl을 호출하여 사용자 컨트롤 인스턴스를 동적으로 로드하고 만들어야 하며, 그런 다음에는 컨트롤에 고유한 ID를 명시적으로 지정해야 합니다. 그리고 나서 WebPartManager 개체의 CreateWebPart 메서드를 호출하여 GenericWebPart 클래스 인스턴스를 만들어야 합니다. 이 인스턴스는 사용자 컨트롤 인스턴스 주위의 래퍼 역할을 합니다. 마지막으로 CreateWebPart를 호출하여 반환된 GenericWebPart 참조를 가져와 AddWebPart 호출로 전달하여 추가될 WebPartZone을 지정해야 합니다.

// 사용자 컨트롤 파일에서 웹 파트 인스턴스를 만듭니다.
Control uc = this.LoadControl(@"webparts\CompanyNews.ascx");
uc.ID = "wp2";
GenericWebPart wp2 = WebPartManager1.CreateWebPart(uc);
WebPartManager1.AddWebPart(wp2, WebPartZone1, 1);
그림 10. GenericWebPart
그림 10. GenericWebPart

이 기술을 사용할 때의 단점은 GenericWebPart 클래스가 컨트롤이 아닌 WebPart에서 상속되는 클래스이기 때문에 컨트롤의 웹 파트 관련 기능을 제어할 수 없다는 것뿐입니다. GenericWebPart에 의해 래핑된 컨트롤이 있는 페이지를 실행해 보면 이 단점을 확실하게 파악할 수 있습니다. 이러한 페이지의 기본 제목은 제목 없음이며 대부분이 웹 파트처럼 아이콘이나 연결된 설명이 없기 때문입니다. 그림 10은 GenericWebPart에 의해 래핑되었으며 기본 제목(제목 없음) 및 아이콘이 지정되어 있는 샘플 사용자 컨트롤을 보여줍니다.

이 문제를 해결할 수 있는 방법은 사용자 컨트롤의 Init 이벤트에 대한 처리기를 추가하는 것입니다. 컨트롤이 GenericWebPart에 의해 래핑된 경우(Parent 속성 유형을 쿼리하여 확인할 수 있음) GenericWebPart 클래스 특성을 다음과 같이 설정해야 합니다.

void Page_Init(object src, EventArgs e) {
  GenericWebPart gwp = Parent as GenericWebPart;
  if (gwp != null)  {
    gwp.Title = "내 사용자 지정 사용자 컨트롤";
    gwp.TitleIconImageUrl = @"~\img\ALLUSR.GIF";
    gwp.CatalogIconImageUrl = @"~\img\ALLUSR.GIF";
  }
}

사용자 컨트롤이 GenericWebPart에 의해 래핑되는 경우 해당 페이지를 다시 실행하면 GenericWebPart 상위 속성에 대해 수행된 변경이 컨트롤이 포함된 웹 파트 렌더링에 반영됩니다. 그림 11은 특성이 새롭게 지정된 사용자 컨트롤을 보여줍니다. 제목과 아이콘을 살펴보십시오.

그림 11. 제목 및 아이콘
그림 11. 제목 및 아이콘

또 다른 유용한 해결 방법은 사용자 컨트롤 클래스에서 직접 IWebPart 인터페이스를 구현하는 것입니다. GenericWebPart 클래스에서 세부 작업을 처리하기 때문에 사용자 컨트롤은 웹 파트 속성에 대해 직접 쿼리되지 않아 이 방법은 별로 도움이 되지 않는다고 생각될 수도 있습니다. 다행히도 GenericWebPart 클래스 디자이너들은 이러한 요구 사항을 예상하고 래핑된 컨트롤이 IWebPart 인터페이스를 구현하는 경우 자동으로 해당 컨트롤로 위임되도록 GenericWebPart 클래스의 속성을 구현했습니다.

따라서 사용자 컨트롤의 웹 파트 기능을 사용자 지정하려면 IWebPart 인터페이스를 구현하고 해당 인터페이스가 정의하는 7가지 속성을 채우면 됩니다. 그림 12의 코드에서는 GenericWebPart 속성을 동적으로 변경하여 이전에 수행했던 작업과 같은 결과를 얻을 수 있는 사용자 컨트롤에 대한 코드 숨김 클래스의 예제를 보여 줍니다.

사용자 컨트롤에 대해 대체 기본 클래스를 만들 수도 있습니다. 이 클래스는 UserControl에서 파생되고 IWebPart를 구현하며, 포털의 모든 사용자 컨트롤에서 이 클래스를 상속할 수 있습니다. 이 기사의 샘플 포털 솔루션에서는 이 작업을 수행합니다. 이 솔루션을 사용하면 사용자 컨트롤은 생성자에서 처리되는 속성을 초기화할 수 있으며 나머지는 자체적으로 처리됩니다. 그림 13은 IWebPart를 구현하는 사용자 컨트롤의 대체 기본 클래스와 해당 클래스를 사용하여 제목 및 아이콘 속성을 설정하는 사용자 컨트롤의 해당 코드 숨김 클래스를 보여줍니다.

이와 같이 사용자 컨트롤에 융통성이 높기 때문에 디자이너에서 사용자 컨트롤을 지원하고 WebPart 기능을 사용자 지정할 수 있는데 사용자 지정 컨트롤을 만들 필요가 있느냐는 의문이 생길 수도 있습니다. 실제로 여기에는 여러 가지 이유가 있습니다. 우선, 사용자 컨트롤에는 사용자 지정 verbs를 추가할 수 없습니다. verbs를 추가하려면 WebPart에서 직접 파생시켜 Verbs 속성을 다시 정의해야 합니다. 컨트롤에서 IWebEditable을 구현하는 방법도 고려해 볼 수 있습니다.

또 다른 이유는 사용자 컨트롤의 범위가 기본적으로 응용 프로그램 디렉터리 내라는 점입니다. .ascx 파일을 여러 프로젝트에 실제로 복사하지 않고는 여러 웹 응용 프로그램에서 사용자 컨트롤 구현을 공유할 수 없습니다. 반면 WebPart 클래스에서 파생되는 사용자 지정 웹 파트는 다시 사용 가능한 DLL로 컴파일하여 GAC(전역 어셈블리 캐시)에서 전역으로 배포할 수 있습니다. 또한 사용자 지정 웹 파트를 사용하면 컨트롤에 대해 사용자 지정 디자이너를 작성하여 Visual Studio 내에서 컨트롤의 기본 모양을 변경할 수 있으며, 도구 상자 위로 끌었을 때 웹 파트와 연결될 아이콘을 만들 수 있습니다. 그림 14는 사용자 지정 웹 파트 및 사용자 컨트롤 중에서 선택하는 데 도움이 되는 기능 비교을 보여 줍니다.


웹 파트 및 개인 설정 공급자

공급자는 ASP.NET 2.0의 새로운 기능으로, 이번 릴리스에서 완전한 기능을 갖춰 코드 작성 및 실행 작업이 거의 또는 전혀 필요하지 않은 다양한 컨트롤을 사용할 수 있도록 해줍니다. 공급자의 기본 개념은 특정 기능에 대해 일반적인 데이터 관련 작업 집합을 정의하고 해당 작업을 일반 ProviderBase 클래스에서 상속되는 추상 클래스 선언으로 묶는 것입니다. 이 기사에서는 개인 설정 기능에서 반드시 사용해야 하는 다음 데이터 관련 작업을 살펴봅니다.

  • 특정 페이지 및 사용자에 대한 웹 파트 속성 및 레이아웃 저장
  • 특정 페이지 및 사용자에 대한 웹 파트 속성 및 레이아웃 로드
  • 특정 페이지에 대한 일반 웹 파트 속성 및 레이아웃(일반 사용자 지정의 경우) 저장
  • 특정 페이지에 대한 일반 웹 파트 속성 및 레이아웃(일반 사용자 지정의 경우) 로드
  • 특정 페이지 및 사용자에 대한 웹 파트 속성 및 레이아웃을 기본값으로 다시 설정
  • 특정 페이지에 대한 웹 파트 속성 및 레이아웃(일반 사용자 지정의 경우)을 기본값으로 다시 설정

개인 설정 인프라의 일부이며 지속적으로 필요한 일부 다른 부가 기능도 있지만, 기본적으로는 위의 여섯 가지 기능으로 요약할 수 있습니다. 이러한 여섯 가지 작업을 수행할 수 있으며 데이터를 성공적으로 저장 및 복원할 수 있는 클래스가 있다면, 각 페이지의 WebPartManager는 해당 클래스를 사용하여 사이트가 실행되는 동안 모든 개인 설정 및 사용자 지정 데이터를 저장 및 복원할 수 있습니다. 이러한 메서드를 정의하는 추상 클래스는 PersonalizationProvider이며, 기본적으로 사용되는 이 클래스의 구체적 파생 클래스는 SqlPersonalizationProvider입니다. 앞서 확인한 여섯 가지 기능을 나타내는 세 가지 메서드는 그림 15에 나와 있습니다. 각 메서드는 들어오는 userName 매개 변수가 null인지 여부에 따라 사용자 개인 설정 또는 공유 사용자 지정을 수행할 수 있습니다.

그림 16. 상호 작용
그림 16. 상호 작용

모든 개인 설정 데이터는 단순 이진 데이터(byte[])로 저장되며, 기본 SqlPersonalizationProvider에서 데이터베이스의 이미지 필드에 기록됩니다. ASP.NET 2.0은 이러한 메서드를 제공하는 클래스가 있음을 인지하기 때문에 과거보다 훨씬 많은 논리를 기본 컨트롤 집합으로 빌드할 수 있습니다. 이 기사에서 웹 파트를 사용하는 각 페이지의 WebPartManager는 현재 PersonalizationProvider 클래스를 올바르게 호출하여 각 페이지의 개인 설정 관련 설정을 serialize 및 복원하는 작업을 담당합니다. 그림 16의 다이어그램에서는 EditorZone 컨트롤과 기본 SqlPersonalizationProvider 간의 상호 작용을 보여줍니다.

ASP.NET 2.0을 많이 사용할수록 이러한 공급자 아키텍처의 예제를 많이 확인할 수 있습니다. 구성원, 역할 관리, 사이트 맵 데이터, 상태 모니터링 등 여러 항목에 대한 공급자가 있으며, 이들은 모두 서로 상호 작용할 컨트롤에 대해 비슷한 핵심 메서드 집합을 정의합니다.


개인 설정 데이터 저장소 변경

ASP.NET 2.0에서 제공되는 대부분의 공급자와 마찬가지로, 기본 개인 설정 공급자는 SQL Server 백 엔드를 대상으로 하도록 구현됩니다. 구성 파일을 변경하지 않으면 기본 SqlPersonalizationProvider는 로컬 파일 기반 데이터베이스를 지원하는 SQL Server 2005 Express Edition 연결 문자열을 사용합니다. 이 연결 문자열은 다음과 같습니다.

data source=.\SQLEXPRESS; Integrated Security=SSPI; 
AttachDBFilename=|DataDirectory|aspnetdb.mdf; User Instance=true

SQL Server 2005 Express Edition 파일 기반 데이터베이스를 사용하는 이점은 사용자의 추가적인 설치 작업 없이도 즉시 만들 수 있다는 것입니다. 따라서 새 사이트를 만든 후 데이터베이스를 설치하지 않고 바로 개인 설정 기능을 사용할 수 있습니다. 사이트와 처음으로 상호 작용하면 사이트의 App_Data 디렉터리에 새 aspnetdb.mdf 파일이 만들어지며, 해당 파일은 모든 기본 공급자를 지원하는 데 필요한 테이블 및 저장 프로시저를 사용하여 초기화됩니다.

이는 확장하거나 다수의 동시 사용자를 지원할 필요가 없는 소규모 사이트에는 매우 효과적이지만, 엔터프라이즈 시스템의 경우에는 전체적으로 관리되는 전용 데이터베이스 서버에 데이터를 저장해야 합니다. 다행히도 SqlPersonalizationProvider에서 사용하는 데이터베이스를 변경하는 작업은 간단합니다. SqlPersonalizationProvider 구성은 연결 문자열을 LocalSqlServer로 초기화합니다. 즉, 구성 파일의 <connectionStrings> 섹션에서 이름이 LocalSqlServer인 항목을 찾아 관련된 연결 문자열을 사용하여 데이터베이스에 대한 연결을 엽니다. 기본적으로 이 문자열은 앞서 살펴보았던 연결 문자열, 즉 로컬 SQL Server 2005 Express Edition의 .mdf 파일에 기록되는 문자열입니다. 이 문자열을 변경하려면 먼저 LocalSqlServer 연결 문자열 컬렉션을 지운 다음 Web.congif 파일에 새 연결 문자열 값을 다시 지정합니다. 또는 컴퓨터 차원의 Machine.config 파일에서 이를 변경하여 해당 컴퓨터의 모든 사이트에 적용되도록 할 수도 있습니다. 다음은 공급자 데이터베이스가 로컬 SQL Server 2000 인스턴스를 가리키도록 변경하는 예제 Web.config 파일입니다.

<configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/
   V.2.0">
  <connectionStrings>
    <clear />
    <add name="LocalSqlServer" connectionString=
        "server=.;integrated security=sspi;database=aspnetdb"/>
  </connectionStrings>
  ...
</configuration>

이와 같은 변경 사항이 적용되도록 하려면 이름이 aspnetdb이며 SqlPersonalizationProvider에 필요한 테이블 및 저장 프로시저가 갖춰진 데이터베이스가 로컬 서버에 있어야 합니다. 이 데이터베이스를 만들려면 ASP.NET 2.0과 함께 제공되는 aspnet_regsql.exe라는 유틸리티를 사용합니다. 이 유틸리티를 기본 설정으로 실행하면 모든 공급자에게 필요한 테이블이 들어 있는 aspnetdb라는 데이터베이스가 로컬에 만들어집니다. 또는 기존 데이터베이스에 테이블 및 저장 프로시저를 설치할 수도 있습니다. 기존 테이블과 충돌하지 않도록 테이블 및 저장 프로시저 이름 앞에는 모두 "aspnet"이라는 접두사가 붙습니다.

ASP.NET 2.0의 모든 공급자와 마찬가지로, 이러한 간접 작업을 수행하면 포함된 페이지 또는 웹 파트를 수정하지 않아도 백 엔드 데이터 저장소를 완전하게 변경할 수 있는 매우 융통성 있는 아키텍처를 만들 수 있습니다.


고유한 개인 설정 공급자 만들기

개인 설정 공급자의 연결 문자열을 변경하는 기능을 사용하면 작업을 융통성 있게 수행할 수 있지만, SqlPersonalizationProvider는 System.Data.Sql.Client 네임스페이스를 사용하여 해당 데이터 검색을 수행합니다. 즉, 이 작업은 SqlServer 데이터베이스로 제한됩니다. 개인 설정을 다른 데이터베이스 또는 완전히 다른 데이터 저장소에 저장해야 하는 경우에는 다음 단계를 수행하여 직접 사용자 지정 개인 설정 공급자를 빌드해야 합니다. 다행히도 힘든 작업은 대부분 완료되었으므로 이를 간편하게 활용할 수 있습니다. 개인 설정 데이터를 다른 데이터 저장소에 기록하는 작업의 예로, 이 기사에 나오는 샘플 포털 사이트에서는 응용 프로그램의 App_Data 디렉터리에 있는 로컬 이진 파일에 모든 개인 설정 및 사용자 지정 데이터를 보관하는 FileBasedPersonalizationProvider라는 사용자 지정 개인 설정 공급자를 완전하게 구현합니다. 이진 파일 이름은 각 사용자 및 경로에 대해 고유하게 생성되며 일반 사용자 설정에 대해 고유한 경로마다 파일이 하나씩 있습니다.

사용자 지정 개인 설정 공급자를 빌드하려면 먼저 PersonalizationProvider 기본 클래스에서 상속을 받는 새 클래스를 만든 다음 기본 클래스에서 상속된 모든 추상 메서드를 다시 정의해야 합니다. 그림 17에 나와 있는 클래스 선언을 통해 이 작업을 수행하는 방법을 확인할 수 있습니다.

개인 설정 공급자가 작동하도록 하기 위해서는 두 가지 중요한 메서드, 즉 LoadPersonalizationBlobs 및 SavePersonalizationBlob만 구현하면 됩니다. 이 두 메서드는 이진 serialization 및 개인 설정 데이터를 나타내며, 개인 설정 인프라에 의해 호출되어 페이지 로드 시 데이터를 검색하고 웹 파트가 있는 페이지에서 데이터가 편집, 카탈로그 또는 디자인 보기에서 변경될 때 보통 특정 사용자를 대신하여 데이터를 다시 씁니다.

다운로드한 샘플 코드의 SavePersonalizationBlob 구현은 전달된 userName 및 경로를 기반으로 만들어진 고유한 이름의 파일에 dataBlob 매개 변수를 기록합니다. 마찬가지로 LoadPersonalizationBlobs 구현은 동일한 명명 스키마를 사용하여 파일을 검색해 사용자 데이터 blob 또는 공유 데이터 blob를 반환합니다. 이러한 두 메서드는 들어오는 userName 매개 변수가 null인 경우 기본적으로 공유 데이터를 저장 또는 로드하며, 그렇지 않은 경우에는 사용자 데이터를 저장 또는 로드합니다. 그림 18은 샘플 FileBasedPersonalizationProvider에서의 이 두 메서드 구현과 함께, 사용자 이름 및 경로 정보를 기반으로 고유한 파일 이름을 생성하기 위한 도우미 메서드 쌍을 보여줍니다.

공급자를 완전히 구현하고 나면 개인 설정 구성 섹션의 공급자 섹션을 사용하여 해당 공급자를 등록된 개인 설정 공급자로 추가할 수 있습니다. 실제로 공급자를 사용하려면 Web.config 파일에서 이를 개인 설정에 대한 기본 공급자로 지정해야 합니다. 다음은 사용자 지정 파일 기반 공급자를 응용 프로그램의 기본 공급자로 지정하는 예제입니다.

<webParts>
  <personalization defaultProvider="FileBasedPersonalizationProvider">
    <providers>
      <add name="FileBasedPersonalizationProvider"
           type="Wingtip.Providers.FileBasedPersonalizationProvider" />
    </providers>
  </personalization>
</webParts>

사이트를 다시 실행하면 모든 개인 설정 데이터가 로컬 이진 파일에 저장되어 있습니다. 이 기사의 샘플이 가장 확장이 용이한 솔루션이라고 할 수는 없지만, 이 샘플을 통해 원하는 백 엔드에서 사용자의 고유한 개인 설정 공급자를 구현하는 방법에 대해 파악할 수 있습니다. 그림 19에서는 전체 웹 파트 인프라에 연결된 새로운 공급자를 보여줍니다.

그림 19. FileBasedPersonalizationProvider 사용
그림 19. FileBasedPersonalizationProvider 사용

추가 정보

지금까지 ASP.NET 2.0 및 여기에 포함된 새로운 웹 파트 컨트롤 집합을 사용하여 사용자 지정 및 개인 설정을 지원하는 유용한 포털 응용 프로그램을 비교적 쉽게 만드는 방법을 알아보았습니다. 이 인프라의 가장 중요한 기능은 연결성일 것입니다. 공급자 인프라를 사용하면 특정 serialization 구현 및 데이터 저장소에 제한되는 대신 사용자 사이트에 적합한 백 엔드 데이터 저장소에 개인 설정 데이터를 비교적 쉽게 쓸 수 있습니다. 이제 ASP.NET 2.0 웹 파트를 사용하여 사용자 지정 가능한 사이트를 만들어 보십시오.

이 기사가 유용했다면 ASP.NET 2.0 포털 응용 프로그램에서 웹 파트를 사용한 빌드에 관한 더욱 자세한 정보를 살펴보실 수 있습니다. MSDN Magazine 웹 사이트에서 함께 제공하는 샘플을 꼭 다운로드하시기 바랍니다. ASP.NET 2.0 QuickStart 자습서 (영문)에는 작업에 사용할 수 있는 샘플이 다수 마련되어 있습니다. 또한 ASP.NET 2.0의 웹 파트 사용과 관련된 여러 가지 흥미로운 예제를 확인할 수 있는 Fredrik Normen의 블로그 (영문)도 방문해 보시기 바랍니다.



Ted PattisonPluralsight (영문)를 통해 실용적인 교육 과정을 제공할 뿐 아니라 Ted Pattison Group (영문)에서 컨설팅 서비스를 제공하는 강사로 활동하며 여러 권의 저서를 집필하기도 했습니다.

Fritz Onion은 교육 및 콘텐츠 제작 회사인 Pluralsight의 공동 설립자이며, ASP.NET을 통한 웹 개발을 담당하고 있습니다. Fritz는 또한 Essential ASP.NET(Addison Wesley, 2003)의 저자입니다. Fritz의 블로그 www.pluralsight.com/fritz(영문)를 방문해 보십시오.

MSDN Magazine 2005년 9월호 (영문)에서 발췌

+ Recent posts