Cutting Edge

Silverlight에서의 동적 콘텐츠 전달 관리, 1부

Dino Esposito

코드는 MSDN 코드 갤러리에서 다운로드할 수 있습니다.
온라인으로 코드 찾아보기

목차

Silverlight 응용 프로그램의 크기
동적으로 생성된 XAML
동적으로 생성된 XAP
주문형 콘텐츠
다운로드된 콘텐츠 캐싱
다운로드를 위한 도구
XAML 전용 데이터 다운로드
XAP 패키지 작업
XAP 콘텐츠의 처리
요약

보안과 다운로드 크기에 대한 RIA(다기능 인터넷 응용 프로그램) 사용자의 우려에는 근거가 있습니다. Silverlight 응용 프로그램은 완전한 Microsoft .NET Framework 하위 집합의 지원을 받습니다. 따라서 그만큼 로컬 사용자 시스템에 해로운 작업을 수행할 가능성도 있습니다. 이러한 이유 때문에 Silverlight 팀에서는 응용 프로그램이 Core CLR(.NET Framework의 Silverlight 버전)에서 보안에 중요한 클래스를 호출할 수 없도록 하는 새 보안 모델을 개발했습니다. Silverlight 보안 모델에 대해서는 "CoreCLR을 사용한 Silverlight 프로그래밍" 기사를 참조하고 Silverlight 응용 프로그램을 작성하는 데 대해서는 "심층적인 웹 환경 구축" 기사를 참조하십시오.

Silverlight 플러그 인 다운로드는 사실 문제가 되지 않습니다. 몇 초면 충분하고, 한 번만 하면 되기 때문입니다. 그러나 다운로드하는 응용 프로그램의 크기에 대해서는 생각해 볼 여지가 있습니다.

이달의 칼럼에서는 Silverlight 응용 프로그램 다운로드와 관련된 문제를 알아보겠습니다. 먼저 XAML을 동적으로 생성하는 방법을 설명하겠습니다. 그런 다음 사용자 요청을 기반으로 엄격하게 응용 프로그램 기능을 동적으로 활성화하는 방법을 알아볼 것입니다. 이렇게 하면 주류 코드로 구현하기 부담스러운 일부 응용 프로그램별 기능을 개별적으로 구현할 수 있습니다. 또한 더 중요한 점은 개별적으로 다운로드하여 기본 사용자 인터페이스와 손쉽게 통합할 수 있다는 것입니다.

Silverlight 응용 프로그램의 크기

사용자가 Silverlight 플러그 인을 설치하면 Silverlight 응용 프로그램에 필요할 수 있는 모든 시스템 어셈블리가 시스템에 설치됩니다. 따라서 다운로드는 응용 프로그램을 포함하는 어셈블리와 참조된 사용자 지정 어셈블리로 제한됩니다. 응용 프로그램 다운로드는 수십 KB 정도가 일반적입니다. 이 추정치는 Silverlight 2 RTM 버전과 릴리스 모드로 컴파일된 코드에 적용됩니다.

물론 복잡한 알고리즘, 그래픽 및 미디어 콘텐츠 또는 애니메이션이 포함되어 있는 경우에는 응용 프로그램이 커질 수 있습니다. 다운로드 시간이 문제가 되는 큰 응용 프로그램을 다룰 때 선택할 수 있는 방법은 크게 두 가지입니다. 하나는 silverlight.live.com에서 논의하고 있는 것처럼 Silverlight 콘텐츠를 스트리밍하는 것입니다. 다른 하나는 필요에 따라 개별적으로 다운로드할 수 있는 독립적인 조각으로 응용 프로그램을 분리하는 것입니다.

동적으로 생성된 XAML

Silverlight 플러그 인은 기본적으로 XAML 콘텐츠를 표시하도록 고안되었습니다. XAML에 코드 숨김이 수반되는 경우 플러그 인이 코드를 처리하여 사용자 인터페이스를 생성하고 코딩된 동작이나 효과를 지원합니다. XAML만 다운로드하면 되는 경우에는 URL을 통해 직접 지정할 수 있으며 그렇지 않은 경우에는 XAP 확장을 통해 Silverlight 패키지를 참조할 수 있습니다.

XAP 패키지는 매니페스트와 하나 이상의 어셈블리를 포함합니다. 어셈블리 중 하나는 응용 프로그램의 진입점을 포함하며 다른 어셈블리는 단순히 참조된 어셈블리입니다. 사용자 인터페이스에 대한 XAML은 진입점 어셈블리의 리소스에 저장되어 있습니다. XAP 패키지는 프로젝트를 만들고 빌드할 때 Silverlight 2용 Visual Studio 2008 확장에 의해 생성됩니다.

Silverlight 플러그 인에는 XAML과 XAP 스트림을 처리하는 기능이 있습니다. 그러나 이러한 콘텐츠를 다운로드하기 위해 플러그 인에 서버의 실제 XAML이나 XAP 리소스를 가리킬 필요는 없습니다. 대신 플러그 인에 동적으로 생성되는 XAML이나 XAP 콘텐츠를 반환하는 URL을 가리킬 수 있습니다.

그림 1에는 즉석에서 생성된 XAML을 반환하는 ASP.NET HTTP 처리기 샘플이 나와 있습니다. ProcessRequest 메서드는 Response 개체의 콘텐츠 형식을 설정하고, 구성 데이터, 매개 변수 또는 런타임 조건을 기반으로 동적으로 작성된 XAML 등의 일부 XAML 콘텐츠를 출력합니다. Response 개체의 Expires 속성을 설정하여 클라이언트에서 리소스가 캐시되는 것을 방지할 수도 있습니다. 이렇게 하면 제공하는 콘텐츠가 주기적으로 변경되며 새로 고쳐야 하는 경우 도움이 됩니다.

그림 1 XAML을 반환하는 HTTP 처리기

<%@ WebHandler Language="C#" Class="XamlGenHandler" %>

using System;
using System.Web;

public class XamlGenHandler : IHttpHandler 
{
    public void ProcessRequest (HttpContext context) 
    {
        // Prevent caching of the response
        context.Response.Expires = -1;

        // Set the type of data we're returning
        context.Response.ContentType = "text/xaml";

        // Create some XAML and return it down the wire
        context.Response.Write("<Canvas xmlns=
            'https://schemas.microsoft.com/client/2007' " +
            "xmlns:x='https://schemas.microsoft.com/winfx/2006/xaml'>" +
            "<TextBlock Foreground='black' Padding='10' FontSize='20'>
             <Run>XAML content</Run><LineBreak/>" + 
            "<Run>[generated " + DateTime.Now.ToLongTimeString() + "]</Run>" +
            "</TextBlock></Canvas>");
    }

    public bool IsReusable 
    {
        get {return true;}
    }

}

동적으로 생성된 XAP

XAP 패키지가 일반 텍스트 파일이 아니라는 점을 제외하면 동적으로 생성된 XAP 패키지를 반환하는 것은 원시 XAML 텍스트를 반환하는 것과 크게 다르지 않습니다. XAP 패키지는 XML 매니페스트와 하나 이상의 어셈블리를 포함하는 ZIP 파일입니다. 패키지 형식을 사용함으로써 Silverlight 응용 프로그램에 필요한 전체 콘텐츠를 다운로드하는 데 소요되는 왕복 횟수를 최소화할 수 있습니다. 그림 2에는 XAP 파일의 내용을 HTTP 응답 스트림으로 출력하는 ASP.NET HTTP 처리기가 나와 있습니다.

그림 2 XAP 패키지를 반환하는 HTTP 처리기

<%@ WebHandler Language="C#" Class="XapGenHandler" %>

using System;
using System.Web;

public class XapGenHandler : IHttpHandler 
{
    public void ProcessRequest (HttpContext context) 
    {
        // XAP file to return 
        string xapFile = "...";

        // Set the type of data we're returning
        context.Response.ContentType = "application/octet-stream";

        // Create some XAML and return it down the wire
        content.Response.WriteFile(xapFile);
    }


    public bool IsReusable 
    {
        get {return true;}
    }

}

샘플 코드는 기존 파일에서 XAP 데이터를 읽습니다. 물론 프로젝트에 ZIP 라이브러리를 추가한 경우에는 다양한 DLL을 조합한 후 적절한 XML 매니페스트 파일을 생성하여 손쉽게 즉석에서 패키지를 구성할 수 있습니다.

XAP 콘텐츠를 반환하는 경우에는 응답의 콘텐츠 형식을 일반 바이너리 콘텐츠를 식별하는 MIME 형식인 application/octet-stream으로 설정해야 합니다.

플러그 인을 HTTP 처리기나 원하는 다른 끝점과 연결하는 데는 일반적인 Silverlight 프로그래밍 기술을 사용합니다. 예를 들어 ASP.NET 페이지에서 Silverlight 서버 컨트롤을 사용할 수 있습니다.

<asp:Silverlight ID="Xaml1" runat="server" 
    Source="~/xap.ashx" 
    MinimumVersion="2.0.30523" 
    Width="100%" 
    Height="100%" />

두 예에서 모두 Silverlight 응용 프로그램의 팩터리는 웹 서버에 있습니다. 이는 호스트 페이지가 어떤 콘텐츠를 다운로드할지 동적으로 알아내야 하는 경우 매우 좋은 방법입니다.

그러나 이것은 가능한 시나리오 중 하나일 뿐입니다. 현재 Silverlight 응용 프로그램을 위한 선택적인 구성 요소를 다운로드해야 하는 경우가 더 일반적인 시나리오일 것입니다. 이 경우 외부 콘텐츠를 선택하고 다운로드하는 논리는 모두 클라이언트의 Silverlight 플러그 인에서 실행됩니다.

주문형 콘텐츠

Silverlight 2는 주문형으로 코드 및/또는 XAML을 다운로드하기 위한 풍부하고 강력한 API를 제공하며 이를 사용하여 콘텐츠를 다운로드하여 기존 XAML 문서 개체 모델에 삽입할 수 있습니다.

XAML 트리의 모든 시각적인 요소는 Children이라는 속성을 가집니다. 이 속성은 크기에 관계없이 자식 요소를 프로그래밍 방식으로 추가 또는 제거하는 데 사용할 수 있습니다. 예를 들어 동일한 서버에서는 물론 신뢰되는 옵트인(opt in) 원격 서버에서 다운로드한 전체 사용자 컨트롤을 추가할 수 있습니다. 다음 줄은 이에 대한 예를 보여 줍니다

StackPanel1.Children.Add(downloadedContent);

인수가 나타내는 사용자 컨트롤은 StackPanel XAML 요소의 Children 컬렉션에 추가됩니다. 렌더링은 즉시 이루어지며 사용자 인터페이스는 실시간으로 업데이트됩니다.

콘텐츠를 다운로드하고 기존 문서 개체 모델에 추가하는 것 이상의 많은 작업을 할 수 있습니다. 예를 들어 응용 프로그램의 로컬 저장소에 로컬로 캐시하고 서버에 대한 새 요청을 수행하기 전에 자체 캐시에 주문형 콘텐츠가 있는지 확인할 수 있습니다.

이렇게 하면 다운로드한 콘텐츠를 위한 영구적인 저장소가 마련됩니다. 그러나 일부 경우에는 이것이 과도한 것일 수 있습니다. 추가 작업이 필요 없는 보다 간단한 방법으로 브라우저가 XAP 리소스를 캐시하도록 하는 방법이 있습니다.

다운로드된 콘텐츠 캐싱

웹 서버에서 받는 XAP 패키지는 브라우저에게 특별한 의미는 없습니다. 따라서 브라우저는 웹 서버에서 다른 것을 받을 때와 같은 방법으로 이를 캐시하며, 캐시-컨트롤에 의해 결정되는 캐시 요청 정책을 준수하고 요청의 HTTP 헤더나 호스트 HTML 페이지의 비슷한 메타 태그를 "만료"합니다.

XAP 리소스가 브라우저에서 다운로드되도록 하면 일반적으로 메타 태그나 ASP.NET 지시문 특성을 사용하여 삽입하는 페이지의 설정을 통해 캐싱을 제어할 수 있습니다. 이전 예처럼 HTTP 처리기를 통해 XAP 리소스를 다운로드하는 경우 특정 요청에 대한 캐싱을 제어할 수 있습니다.

한 가지 알아둘 것은 캐시되는 항목이 어셈블리와 XAML을 포함하는 원래의 XAP 콘텐츠라는 것입니다. 따라서 응용 프로그램을 실행하면 프로그래밍 방식으로 원래 XAML이 수정될 수 있습니다. 그러나 이러한 변경은 자동으로 캐시되지 않으며, 마찬가지로 XAP 패키지에서 추출하는 리소스(미디어, 이미지 등)는 별도로 캐시되지 않습니다. 따라서 사용자가 페이지에 방문할 때마다 XAP 패키지가 다시 다운로드되지는 않습니다(만료될 때까지). 그러나 리소스는 모두 다시 추출됩니다. 또한 이전 세션에서 이러한 리소스에 대해 수행했던 변경은 모두 손실됩니다. XAML 문서 개체 모델에 대한 변경을 유지하려면 자체 맞춤 제작 캐시를 준비해야 합니다. 이 훌륭한 기술에 대해서는 2부 기사에서 다루도록 하겠습니다.

마지막으로 브라우저의 캐시에 저장된 XAP 패키지는 사용자에 의해 좌우됩니다. 언제라도 사용자가 캐시를 삭제하기로 결정한 경우에는 XAP 패키지를 포함하여 캐시에 포함된 모든 내용이 손실됩니다. Silverlight XAP 패키지를 영구적으로 저장하려면 격리된 저장소를 사용해야 합니다. 이에 대한 내용은 2부에서 다루겠습니다.

다운로드를 위한 도구

Silverlight 응용 프로그램을 작성할 때 응용 프로그램의 XAP에 패키징되지 않은 모든 리소스는 명시적으로 서버에서 다운로드해야 합니다. WebClient 클래스는 추가 리소스 다운로드를 준비하기 위해 사용할 수 있는 가장 기본적인 Silverlight 도구이며, 웹 리소스로 데이터를 전송하고 수신하는 몇 가지 비동기 메서드를 제공합니다. 이 클래스를 사용하는 방법은 다음과 같습니다.

WebClient wc = new WebClient();
wc.DownloadStringCompleted += 
     new DownloadStringCompletedEventHandler(callback);
wc.DownloadStringAsync(address);

DownloadStringAsync 메서드는 HTTP GET을 작동시키고 URL 응답을 문자열로 캡처합니다. 문자열은 다음과 같이 연결된 콜백으로 수신됩니다.

void callback(object sender, DownloadStringCompletedEventArgs e)
{
  if (e.Error != null)
     return;

  string response = e.Result;
...
}

곧 살펴보겠지만 이 메서드는 연결된 코드 숨김이 없는 일반 XAML을 다운로드하는 데 최적입니다. XAP 패키지와 같은 바이너리 데이터를 프로그래밍 방식으로 다운로드하려면 스트림을 사용해야 하며 조금 다른 방식이 필요합니다. WebClient 클래스는 OpenReadAsync에 유용한 메서드를 제공하므로 여전히 유용합니다.

WebClient wc = new WebClient();
wc.OpenReadCompleted += 
     new OpenReadCompletedEventHandler(callback);
wc.OpenReadAsync(address);

연결된 콜백의 구조는 이전 사례와 동일합니다. 결과적으로 간단한 문자열을 얻는 데는 DownloadStringAsync 메서드를 사용하고 임의 데이터의 스트림을 얻는 데는 OpenReadAsync 메서드를 사용합니다. 문자열이나 스트림 중 어떤 것을 다운로드할지는 대부분 개발자의 선호 사항과 수신하는 데이터를 사용하는 방법에 따라 다릅니다.

WebClient는 원격 URL에 쓰기 위한 두 개의 메서드로 문자열 게시를 위한 UploadStringAsync와 스트림을 사용하여 URL로 데이터를 업로드하기 위한 OpenWriteAsync를 제공합니다.

기본적으로 클래스는 헤더를 지정하지 않으므로 Headers 속성을 사용하여 추가 헤더를 지정할 수 있습니다. 그러나 여러분이 설정하는 헤더 중 일부는 Framework에 의해 제거되고 내부적으로 관리됩니다. 이러한 헤더에는 Referer, Connection 및 User-Agent가 있습니다. Content-Type 헤더는 설정된 경우 보존됩니다.

리소스를 다운로드할 때 WebClient 클래스는 연결을 시도하기 전에 투명하게 브라우저의 캐시를 사용합니다. XAML이나 XAP 리소스가 캐시에 없는 경우 클래스는 다운로드를 진행합니다. Silverlight 응용 프로그램에서 콘텐츠를 다운로드하는 프로세스는 Silverlight 런타임과 호스트 브라우저가 플러그 인에 제공하는 내부 API의 작업이 결합되어 수행됩니다. 즉, WebClient 내부에서 Silverlight 런타임은 요청된 리소스가 이미 캐시에 있는지 여부를 확인하기 위해 브라우저 샌드박스와 통신합니다. 캐시에 리소스가 없는 경우 Silverlight는 요청에 권한을 부여하기 위해 자체 보안 정책을 사용하여 진행합니다. 끝점에서 최종적으로 데이터가 반환되면 Silverlight 런타임은 현재 캐싱 정책을 완전하게 준수하면서 브라우저의 서비스를 통해 데이터를 로컬로 캐시합니다.

WebClient 클래스와 Silverlight System.Net 네임스페이스의 다른 HTTP 클래스에는 몇 가지 보안 제한이 적용됩니다. 특히 WebClient 클래스는 스트림을 통해 다운로드 할 때는 HTTP 및 HTTPS 방식만 지원하며 순수 XAML을 다운로드 할 때는 FILE 방식만 지원합니다. 체계 간(Cross-Scheme) 액세스는 항상 금지되므로 예를 들어 HTTP를 통해 호스트 페이지가 다운로드되는 경우 WebClient에 HTTPS 리소스를 가리킬 수 없으며 그 반대도 불가능합니다. WebClient 요청은 일반적으로 다른 브라우저 영역의 URL에 연결할 수 있지만 인터넷 영역에서 더 제한적인 영역으로 이동할 수는 없습니다. Silverlight는 현재 Windows 운영 체제에서만 영역을 지원합니다.

마지막으로 도메인 간 액세스는 원격 사이트가 해당 루트 디렉터리에 올바른 XML 파일을 호스팅하여 옵트인(opt in)을 수행하는 경우에만 지원됩니다. 또한 HTTPS-to-HTTPS 시나리오에서는 도메인 간 액세스가 작동하지 않습니다.

동일한 WebClient 개체가 여러 요청을 동시에 처리할 수 없습니다. 코드에서 동일한 WebClient 인스턴스를 통해 새 요청을 수행해도 안전한지 알아보려면 부울 값인 IsBusy 속성을 확인해야 합니다. 각기 다른 스레드에서 여러 WebClient 개체를 사용하는 경우에는 동시에 다운로드를 여러 개 시작할 수 있습니다.

XAML 전용 데이터 다운로드

WebClient를 사용하여 XAML 데이터를 다운로드하고 이를 시각적 트리에 통합하는 방법을 살펴보겠습니다. Silverlight 2 응용 프로그램에서 일반 XAML 데이터를 동적으로 다운로드할 수 있더라도 그 자체로 필요한 프로그래밍 기능이 생기는 것은 아닙니다. XAML 문자열은 스타일, 리소스 또는 이벤트에 대한 바인딩이나 참조와 같이 런타임에 확인해야 하는 참조가 없는 일반 XAML이어야 합니다.

XAML 문자열을 다운로드한 다음에는 XamlReader 클래스를 사용하여 이를 기존 문서 개체 모델로 추가할 수 있는 UI 요소로 변환할 수 있습니다. 다음 코드는 프로그래밍 방식으로 URL에서 XAML 문자열을 다운로드하는 방법을 보여 줍니다. URL은 Uri 개체로 제공해야 합니다.

WebClient client = new WebClient();
client.DownloadStringCompleted += 
   new DownloadStringCompletedEventHandler(OnDownloadCompleted);
Uri uri = new Uri("xaml.ashx", UriKind.Relative);
client.DownloadStringAsync(uri);

URL은 일반 XAML 리소스를 가리키거나 text/xaml 응답을 반환하는 끝점을 가리킬 수 있습니다. 다음은 다운로드한 XAML을 처리하고 이를 시각적 트리의 자리 표시자에 추가하는 코드입니다.

void OnDownloadCompleted(object sender, DownloadStringCompletedEventArgs e)
{
    // Parse XAML to a UI element 
    string xaml = e.Result;
    UIElement dom = XamlReader.Load(xaml) as UIElement;

    // Append to the DOM
    Placeholder.Children.Clear();
    Placeholder.Children.Add(dom);
}

언급한 것처럼 자리 표시자는 플러그 인에서 현재 렌더링되는 문서 개체 모델의 어떤 요소라도 될 수 있습니다. UI 요소의 자식은 컬렉션을 형성하며 시퀀스로 렌더링됩니다. 이것은 원하지 않는 요소 중첩을 방지하려면 업데이트 시에 먼저 요소를 제거한 다음 추가해야 한다는 것을 의미합니다.

XamlReader 및 XamlWriter 클래스를 통해 수행되는 XAML 직렬화는 확장 참조가 역참조되며 런타임 값이 디자인 타임 설정보다 우선하여 저장된다는 원칙에 기반을 둡니다. XAML 콘텐츠를 다운로드하고 이를 표시하기 전에 동적 데이터 바인딩과 같은 방법으로 사용자 지정하려는 경우에는 어떻게 해야 할까요? XAML 원본에 바인딩을 삽입할 수는 없지만 다운로드되는 XAML에 자리 표시자를 정의하고, 구문 분석으로 이를 검색한 다음, 이를 프로그래밍 방식으로 원하는 값으로 설정할 수 있습니다. 그러나 Silverlight 2에서는 XAP 패키지를 다운로드하는 것이 더 나은 방법입니다.

XAP 패키지 작업

XAP 패키지에는 XAML 태그와 코드의 컨테이너인 사용자 컨트롤로 사용자 인터페이스가 구성된 전체 Silverlight 응용 프로그램이 포함됩니다.

언급한 것처럼 XAP 패키지에는 매니페스트 파일에 의해 추적되는 하나 이상의 어셈블리가 포함됩니다. XAP 패키지를 처리하기 위해서는 다운로드 이후에도 몇 가지 추가 단계가 필요합니다. 주 어셈블리를 추출해야 하며 다운로드된 응용 프로그램을 시작하는 진입점 클래스를 인스턴스화해야 합니다. 물론 이 경우에는 XAML에서 바인딩, 스타일, 이벤트 및 다른 원하는 모든 것을 사용할 수 있습니다. XAP 패키지 작업을 하는 경우 XAML을 처리하고 참조를 확인하는 것은 직렬화 API가 아니라 Silverlight 런타임입니다. 이에 따라 프로그래밍 능력 면에서 상당한 차이점이 발생합니다.

XAP 패키지를 다운로드 및 처리하기 위해서는 단순히 문자열로부터 개체를 작성하는 것 이상의 작업이 필요합니다. 이러한 작업 중 일부, 특히 콘텐츠 다운로드와 어셈블리 추출은 재사용이 가능한 다운로더 클래스(그림 3 참조)로 옮길 수 있습니다.

그림 3 동적으로 XAP 패키지 다운로드

public partial class Page : UserControl
{
    private UIElement content = null;
    private TabItem item = null;

    public Page()
    {
        InitializeComponent();
    }

    private void chkNewContent_Click(object sender, RoutedEventArgs e)
    {
        bool shouldDisplay = (sender as CheckBox).IsChecked.Value;   

        if (shouldDisplay)
        {
            if (!IsContentAvailable())
                DownloadContent();
            else
                ShowContent();
        }
        else
        {
            HideContent();
        }
    }

    private bool IsContentAvailable()
    {
        return (content != null);
    }

    private void DownloadContent()
    {
        Downloader dl = new Downloader();
        dl.XapDownloaded += 
            new EventHandler<XapEventArgs>(OnPackageDownload);
        dl.LoadPackage("more.xap", "more.dll", "More.ExtraTab");
    }

    void OnPackageDownload(object sender, XapEventArgs e)
    {
        content = e.DownloadedContent;
        ShowContent();
    }

    private void HideContent()
    {
        this.TabList.Items.Remove(item);
    }

    private void ShowContent()
    {
        item = new TabItem();
        item.Header = "Extra tab";
        item.Content = content;
        this.TabList.Items.Add(item);
    }
}

그림 3의 코드에는 사용자가 확인란을 처음 클릭하면 새 탭을 로드하는 탭 기반 응용 프로그램 샘플이 나와 있습니다. 이 경우에 새 XAP 패키지가 다운로드되며 새로 생성된 TabItem에 자체 사용자 인터페이스에 포함된 사용자 컨트롤이 삽입됩니다. 새로 다운로드한 패키지에서 콘텐츠를 숨기는 것은 간단한 클라이언트 작업입니다. 그리고 콘텐츠는 메모리에 캐시되며 이 콘텐츠를 작성하는 데 사용된 패키지는 브라우저 캐시에 캐시되므로 콘텐츠를 다시 표시하기 위해 다시 왕복할 필요는 없습니다.

그림 4에는 이 샘플 응용 프로그램의 사용자 인터페이스가 나와 있습니다. 추가 탭에 삽입된 콘텐츠는 Silverlight 응용 프로그램 프로젝트에서 가져온 것입니다. 배포를 위해 해야 할 일은 호스트 웹 사이트의 ClientBin 폴더나 WebClient가 안전하게 연결할 수 있는 다른 위치에 XAP 패키지를 저장하는 것이 전부입니다.

그림 4 UI의 일부로 동적으로 다운로드할 수 있는 응용 프로그램

다음은 도우미 Downloader 클래스의 코드를 살펴보겠습니다. 이 예에서 사용된 Downloader 클래스는 내부 클래스이며 JavaScript 호출자에서 사용할 수 있는 Downloader 개체와는 아무 관련이 없습니다. JavaScript Downloader 개체는 근본적으로 WebClient를 위한 스크립트 호출이 가능한 래퍼입니다.

XAP 콘텐츠의 처리

Downloader 클래스는 WebClient를 사용하여 XAP 패키지를 다운로드한 다음 ZIP으로 압축된 스트림에서 응용 프로그램을 포함하는 어셈블리를 추출하고 지정된 루트 클래스를 인스턴스화합니다. 이 모든 논리는 LoadPackage 메서드와 XapDownloaded 이벤트로 구성된 비교적 간단한 프로그래밍 외관의 배후에 숨겨집니다. 메서드의 용법은 다음과 같습니다.

public void LoadPackage(
    string xapURL, string assemblyName, string className);

이 메서드는 패키지에 대한 URL, 패키지에서 추출할 어셈블리 이름, 그리고 인스턴스화할 클래스 이름을 받습니다. 언급한 것처럼 이 클래스는 XAML 인터페이스 파일의 코드 숨김입니다.

Downloader 클래스가 XAP 처리를 완료하고 클라이언트 응용 프로그램에 대한 사용자 컨트롤이 준비되면 XapDownloaded 이벤트가 발생합니다. 이벤트의 정의는 다음과 같습니다.

public event 
    EventHandler<XapEventArgs> XapDownloaded;

이벤트 인수 클래스는 다음 정보를 반환합니다.

public class XapEventArgs : EventArgs
{
  public UserControl DownloadedContent;
}

LoadPackage 메서드의 논리를 자세히 살펴보겠습니다. 이 메서드는 XAP 패키지를 가리키기 위해 WebClient의 바이너리 인터페이스를 사용합니다.

void LoadPackage(string package, string asm, string cls)
{
    assemblyName = asm;
    className = cls;

    Uri address = new Uri(package, UriKind.RelativeOrAbsolute); 
    WebClient client = new WebClient();
    client.OpenReadCompleted += 
       new OpenReadCompletedEventHandler(OnCompleted);
    client.OpenReadAsync(address);
}

다운로드는 비동기적으로 진행되며 완료되면 OpenReadCompleted 이벤트가 발생합니다. 그림 5에는 이벤트 처리기의 구현이 나와 있습니다.

그림 5 다운로드 완료

void OnCompleted(object sender, OpenReadCompletedEventArgs e)
{
    if (PackageDownloaded == null)
        return;

    if (e.Error != null)
        return;

    // Load a particular assembly from the XAP package
    Assembly a = GetAssemblyFromPackage(assemblyName, e.Result);

    // Get an instance of the XAML object
    object page = a.CreateInstance(className);  

    // Fire the event
    XapEventArgs args = new XapEventArgs();
    args.DownloadedContent = page as UserControl;
    XapDownloaded(this, args);
}

오류가 발생하지 않으면 도우미 함수는 지정한 어셈블리를 패키지로부터 추출하고 XAML 블록의 코드 숨김에서 추가할 사용자 컨트롤 클래스의 인스턴스를 얻습니다. 그런 다음 XAML 하위 트리를 처리하기 위해 호출자에 대한 사용자 지정 이벤트가 발생합니다. 이제 설명이 필요한 남은 부분으로 URL에서 다운로드한 ZIP 압축 스트림에서 어셈블리를 추출하는 방법이 있습니다. 이 코드는 그림 6에 나와 있습니다. 이 코드는 XAP 패키지에서 어셈블리를 추출하기 위해 사용하는 기본 구성 요소 코드입니다. StreamResourceInfo와 Application.GetResourceStream도 현재 XAP에서 이미지나 미디어와 같은 리소스 패키지를 추출하기 위한 일반적인 도구입니다.

그림 6 XAP 패키지로부터 어셈블리 추출

Assembly GetAssemblyFromPackage(string assemblyName, Stream xap)
{
    // Local variables
    Uri assemblyUri = null;
    StreamResourceInfo resPackage = null;
    StreamResourceInfo resAssembly = null;
    AssemblyPart part = null;

    // Initialize
    assemblyUri = new Uri(assemblyName, UriKind.Relative);
    resPackage = new StreamResourceInfo(xap, null);
    resAssembly = Application.GetResourceStream(resPackage, assemblyUri);

    // Extract an assembly 
    part = new AssemblyPart();
    Assembly a = part.Load(assemblySri.Stream);
    return a; 
}

요약

Silverlight 응용 프로그램은 사용자의 시스템에 신속하게 다운로드 및 설치되어야 하며 빠르게 실행되어야 합니다. 관리 코드는 해석되는 JavaScript보다 빠르므로 당연히 성능은 우수합니다. 그러나 큰 Silverlight 응용 프로그램이나 큰 외부 그래픽이나 미디어 콘텐츠가 필요한 응용 프로그램을 다운로드하려면 원하지 않는 지연이 발생할 수 있기 때문에 다운로드를 단계별로 분리하면 도움이 됩니다. 프로그램이 이러한 단계를 주문형으로 수행할 수 있다면 더 좋을 것입니다.

WebClient 클래스는 인터넷을 통해 액세스 가능한 모든 종류의 리소스를 다운로드할 수 있는 효과적인 비동기 프로그래밍 API를 제공합니다. Silverlight 개체 모델의 확장 가능한 모델을 사용하면 기존 구조에 어떤 변경이라도 신속하게 적용할 수 있습니다. 마지막으로 StreamResourceInfo 클래스가 주축이 되는 스트림 기반 API를 사용하면 어셈블리와 XAP 패키지의 리소스로부터 손쉽게 콘텐츠를 추출할 수 있습니다.

다운로드 가능한 모든 리소스는 브라우저에 의해 캐시됩니다. 그러나 이러한 유형의 캐시가 영구적인 것은 아닙니다. 2부 기사에서는 다운로드한 콘텐츠를 격리된 저장소를 사용하여 사용자의 시스템에서 더 오래 보존하여 잠재적인 왕복을 크게 줄이는 방법을 설명하겠습니다. Silverlight에 대한 추가적인 정보는 Jeff Prosise의 Wicked Code 칼럼 "Silverlight 팁, 트릭 및 최선의 방법"을 참조하십시오.

Dino에게 질문이나 의견이 있으면 cutting@microsoft.com으로 보내시기 바랍니다.

Dino Esposito는 IDesign의 설계자이며 Microsoft .NET: Architecting Applications for the Enterprise(Microsoft Press, 2008)의 공동 저자이기도 합니다. 이탈리아에 거주하고 있는 Dino는 전 세계의 IT 업계 관련 행사에서 많은 활동을 펼치고 있습니다. 문의 사항이 있으면 블로그 weblogs.asp.net/despos를 방문하시기 바랍니다.