技巧:在Silverlight中如何訪問外部xap檔案中UserControl

weixin_34304013發表於2008-07-10

概述

眾所周知,在Silverlight 2開始每個專案編譯後都會打包成為一個xap檔案,如果我們要訪問當前xap檔案中的UserControl比較容易,那我們如何訪問一個外部xap檔案中的內容呢?甚至於如何訪問一個網際網路上的xap檔案呢?

本文將簡單介紹一下在Silverlight中如何訪問外部xap檔案。

需求

現在我們先來看一下需求,大致是這樣子的,在服務端我們有兩個xap檔案,其中MainProject.xap檔案將會在MainProjectTestPage.aspx中引用,而ExternalProject.xap檔案中的UserControl將會在MainProject.xap檔案中訪問,並進行顯示,如下圖所示:

TerryLee_0107

現在我們來建立相關的專案,最終完成的專案結構如下圖所示:

TerryLee_0108 

這樣在編譯後,將會在ClientBin資料夾下產生兩個.xap檔案,現在我們將在MainProject.xap檔案中訪問ExternalProject.xap中的UserControl。

分析

在實現這個過程中,我們將會遇到兩個問題:

1.因為沒有任何頁面引用ExternalProject.xap檔案,所以它不會下載到客戶端,這一點我們可以通過編碼的方式來下載它。

2.訪問ExternalProject.xap中的UserControl,我們需要找到對應的程式集,以便使用反射,我們知道在xap檔案是一個標準的zip檔案,它會包含相關的程式集(接下來我會寫一篇文章專門解釋xap檔案),如下圖所示:

TerryLee_0109

現在解決了xap檔案的下載已經程式集的訪問問題,我們可以著手來實現了。

實現

實現的過程也是相當簡單,首先我們使用WebClient去下載xap檔案,相信大家都知道該怎麼做了,如下程式碼所示

void myButton_Click(object sender, RoutedEventArgs e)
{
    Uri address = new Uri("http://localhost:4161/ClientBin/ExternalProject.xap");
    WebClient webClient = new WebClient();
    webClient.OpenReadCompleted += new OpenReadCompletedEventHandler(webClient_OpenReadCompleted);
    webClient.OpenReadAsync(address);
}

void webClient_OpenReadCompleted(object sender, OpenReadCompletedEventArgs e)
{
    // 得到下載結果
}

這一步比較簡單,接下來我們將根據下載的結果,得到相應的程式集。我們知道在xap檔案中的AppManifest.xaml檔案相當於一個清單,列出了當前xap檔案用到的程式集(下篇文章將會介紹),它的內容如下所示:

<Deployment xmlns="http://schemas.microsoft.com/client/2007/deployment"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
            EntryPointAssembly="ExternalProject" 
            EntryPointType="ExternalProject.App" 
            RuntimeVersion="2.0.30523.6">
  <Deployment.Parts>
    <AssemblyPart x:Name="ExternalProject" Source="ExternalProject.dll" />
  </Deployment.Parts>
</Deployment>

注意,在Deployment.Parts節點下包含了當前應用程式中所有的程式集。首先要根據下載的結果獲取AppManifest.xaml檔案中的內容,如下程式碼所示:

Stream stream = Application.GetResourceStream(
        new StreamResourceInfo(packageStream, null),
        new Uri("AppManifest.xaml", UriKind.Relative)).Stream;

String appManifestString = new StreamReader(stream).ReadToEnd();

有了AppManifest.xaml中內容,就可以根據它來構造一個Deployment物件,Deployment物件提供了當前應用程式的Part和本地化資訊清單,它的定義如下所示:

TerryLee_0110

注意它定義了一個很重要的屬性Parts,通過該屬性我們就可以訪問所有Deployment中的程式集。好了,現在我們看如何通過AppManifest.xaml中的內容構造Deployment物件,以及遍歷其中的程式集,如下程式碼所示:

Deployment deployment = (Deployment)XamlReader.Load(appManifestString);

Assembly assembly = null;
foreach (AssemblyPart assemblyPart in deployment.Parts)
{
    if (assemblyPart.Source == assemblyName)
    {
        String source = assemblyPart.Source;

        StreamResourceInfo streamInfo = Application.GetResourceStream(
            new StreamResourceInfo(packageStream,
            "application/binary"),
            new Uri(source,UriKind.Relative));

        assembly = assemblyPart.Load(streamInfo.Stream);
        break;
    }
}
return assembly;

注意,在遍歷時如果我們找到程式集名等於我們想要訪問的程式集,則直接返回該程式集。最終完整的LoadAssemblyFromXap方法程式碼如下:

Assembly LoadAssemblyFromXap(Stream packageStream,String assemblyName)
{
    Stream stream = Application.GetResourceStream(
            new StreamResourceInfo(packageStream, null),
            new Uri("AppManifest.xaml", UriKind.Relative)).Stream;

    String appManifestString = new StreamReader(stream).ReadToEnd();

    Deployment deployment = (Deployment)XamlReader.Load(appManifestString);

    Assembly assembly = null;
    foreach (AssemblyPart assemblyPart in deployment.Parts)
    {
        if (assemblyPart.Source == assemblyName)
        {
            String source = assemblyPart.Source;

            StreamResourceInfo streamInfo = Application.GetResourceStream(
                new StreamResourceInfo(packageStream,
                "application/binary"),
                new Uri(source,UriKind.Relative));

            assembly = assemblyPart.Load(streamInfo.Stream);
            break;
        }
    }
    return assembly;
}

得到程式集後,再使用反射建立相關的例項,並在頁面上載入,如下程式碼所示:

Assembly assembly = LoadAssemblyFromXap(e.Result, "ExternalProject.dll");

UIElement element = assembly.CreateInstance("ExternalProject.SubPage") as UIElement;
this.holder.Children.Add(element);

執行後效果如下圖所示:

TerryLee_0111 

跨域訪問

在上面的示例中,不涉及到跨域(我會專門寫一篇文章介紹)呼叫的問題,如果大家想訪問的xap檔案與當前xap檔案不在同一站點中,需要新增跨域訪問檔案,如下程式碼所示:

clientaccesspolicy.xml:

<?xml version="1.0" encoding="utf-8"?>
<access-policy>
  <cross-domain-access>
    <policy>
      <allow-from http-request-headers="*" />
      <domain uri="*"/>
      </allow-from>
      <grant-to>
        <resource path="/" include-subpaths="true"/>
      </grant-to>
    </policy>
  </cross-domain-access>
</access-policy>

總結

本文介紹了在Silverlight中如何訪問外部xap檔案這一技巧,希望對大家有所幫助。示例程式碼下載:

相關文章