在上一篇中講述瞭如何安裝適合自己需要的VS2010 以及相關幫助文檔,由於在VS2010 中幫助文檔的查詢方式不同於以往的VS ,在上篇中還介紹了H3Viewer 這個軟件,它是一個免費的軟件,安裝它之後可以在安裝了VS2010 幫助文檔之後以類似於以前查看MSDN 的方式查看幫助文檔。除此之外,在上一篇還比較了幾種常見的RIA 技術,比如Java Applet Flash Silverlight ,以及它們與普通Windows 應用程序在運行機制上的區別和限制。在這一篇開始涉及到Silverlight 了,這一篇主要是講Silverlight 的開發工具VS2010 以及XAML 語言等知識。
創建Silverlight 項目
當我們創建一個Silverlight 項目時會提示是否創建一個承載項目,如下圖所示:
由於在VS2010 中已經直接支持創建ASP.NET MVC2 Web 項目,所以承載Silverlight Web 項目類型有三種:ASP.NET Web 應用程序項目、ASP.NET 網站及ASP.NET MVC Web 應用程序項目。ASP.NET Web 應用程序項目可以提供與Visual Studio .NET 2003 Web 項目相同的 Web 項目語義,它的編譯模型與 Visual Studio .NET 2003 編譯模型類似。項目中的所有代碼文件(獨立文件、代碼隱藏文件以及類文件)將被編譯成單個程序集並存儲在 Bin 目錄中。由於編譯會創建單個程序集,因此可以指定程序集名稱和版本等屬性。如果我們僅僅是開發Silverlight 的話,這個可以隨便選擇一種類型,以便能在網頁中查看Silverlight 的運行效果。
VS2010 界面
創建Silverlight 項目成功之後可以看到如下的界面:
VS2010 中可以直接從工具箱中向Silverlight 界面中拖控件。在開發ASP.NET 時一個頁面會分爲兩部分.aspx.cs .cs ,前者包含了設計代碼,後者則是包含業務邏輯代碼,和ASP.NET 開發一樣,Silverlight 的項目中每個控件或者頁面也是分爲.xaml .xaml.cs .xaml 代碼也是包含設計代碼,.xaml.cs 則是包含業務邏輯代碼。在每個Silverlight 項目中默認會有一個app.xaml 文件,這個文件有點類似於WinForm 項目中的program.cs 類,包含了項目啓動時的設置,在創建一個Silverlight 項目成功後app.xam.cs 的代碼如下:
 
 
   
  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using System.Net;  
  5. using System.Windows;  
  6. using System.Windows.Controls;  
  7. using System.Windows.Documents;  
  8. using System.Windows.Input;  
  9. using System.Windows.Media;  
  10. using System.Windows.Media.Animation;  
  11. using System.Windows.Shapes;  
  12.  
  13. namespace SilverlightDemo1  
  14. {  
  15.     public partial class App : Application  
  16.     {  
  17.  
  18.         public App()  
  19.         {  
  20.             this.Startup += this.Application_Startup;  
  21.             this.Exit += this.Application_Exit;  
  22.             this.UnhandledException += this.Application_UnhandledException;  
  23.  
  24.             InitializeComponent();  
  25.         }  
  26.  
  27.         private void Application_Startup(object sender, StartupEventArgs e)  
  28.         {  
  29.             this.RootVisual = new MainPage();  
  30.         }  
  31.  
  32.         private void Application_Exit(object sender, EventArgs e)  
  33.         {  
  34.  
  35.         }  
  36.  
  37.         private void Application_UnhandledException(object sender, ApplicationUnhandledExceptionEventArgs e)  
  38.         {  
  39.             // 如果應用程序是在調試器外運行的,則使用瀏覽器的  
  40.             // 異常機制報告該異常。在 IE 上,將在狀態欄中用一個   
  41.             // ×××警報圖標來顯示該異常,而 Firefox 則會顯示一個腳本錯誤。  
  42.             if (!System.Diagnostics.Debugger.IsAttached)  
  43.             {  
  44.  
  45.                 // 注意: 這使應用程序可以在已引發異常但尚未處理該異常的情況下  
  46.                 // 繼續運行。   
  47.                 // 對於生產應用程序,此錯誤處理應替換爲向網站報告錯誤  
  48.                 // 並停止應用程序。  
  49.                 e.Handled = true;  
  50.                 Deployment.Current.Dispatcher.BeginInvoke(delegate { ReportErrorToDOM(e); });  
  51.             }  
  52.         }  
  53.  
  54.         private void ReportErrorToDOM(ApplicationUnhandledExceptionEventArgs e)  
  55.         {  
  56.             try 
  57.             {  
  58.                 string errorMsg = e.ExceptionObject.Message + e.ExceptionObject.StackTrace;  
  59.                 errorMsg = errorMsg.Replace('"''\'').Replace("\r\n", @"\n");  
  60.  
  61.                 System.Windows.Browser.HtmlPage.Window.Eval("throw new Error(\"Unhandled Error in Silverlight Application " + errorMsg + "\");");  
  62.             }  
  63.             catch (Exception)  
  64.             {  
  65.             }  
  66.         }  
  67.     }  
可以在Application_Startup() 方法中通過設置RootVisual 屬性來指定啓動那個頁面作爲Silverlight 的啓動界面。
關於XAML
Silverlight 中使用XAML 作爲Silverlight 的界面設計語言,XAML 是一種特殊的XML 格式(就像XHTML 也是一種特殊的XML 格式一樣),現在我們不用太關注它,經過以後的練習就會慢慢理解XAML 語言的特點。下面是一個普通的XAML 頁面代碼:
 

對於普通的Silverlight 控件或者頁面(也就是除app.xaml ),這個xaml 代碼主要包含了幾部分:它對應的後臺代碼的類名(在上圖所示的是SilverlightDemo1.MainPage );用戶控件的高度和寬度(在上圖中所示的高寬分別是300 400 像素);頂級佈局所使用的容器(在上圖中使用的是Grid )。在Silverlight 中定義了一個Panel 類,這個類是一個抽象類,它是用來界面佈局的,Panel 類有如下子類:System.Windows.Controls.Canvas System.Windows.Controls.DockPanel System.Windows.Controls.Grid System.Windows.Controls.Primitives.TabPanel System.Windows.Controls.Primitives.ToolBarOverflowPanel System.Windows.Controls.Primitives.UniformGrid System.Windows.Controls.StackPanel System.Windows.Controls.VirtualizingPanel System.Windows.Controls.WrapPanel 。在WinForm 中我們直接往界面中拖控件就可以了,因爲在WinForm 中我們使用X Y 座標來定位控件的左上角頂點的位置,在ASP.NET 中則可以使用Table 或者DIV 來佈局,在Silverlight 中界面佈局和ASP.NET WinForm 中都不一樣,相對要稍微複雜一些(個人感覺倒是有些和Java SE 類似,在Java SE 中有FlowLayout BorderLayout CardLayout GridLayout ),在Silverlight 中界面佈局則有上面提到的那些類(莫非和Java SE 一樣,因爲要跨平臺,所以纔會複雜一些)。利用這些界面佈局類及它們的組合可以實現複雜的界面佈局,當然如果你覺得仍不能滿足要求的話,可以界面佈局類。關於這些佈局類的用法將在下一篇講解。
Silverlight 項目的編譯
大家都知道.NET 項目都可以使用csc.exe 編譯,其實VS 在編譯項目是也是調用了csc.exe 的,所以對於平常的簡單的想法的代碼測試周公都是用記事本寫代碼然後在命令行下調用csc.exe 編譯。用VS 編譯項目最終也會調用csc.exe 來編譯,和以前編譯.NET 項目不同的是,編譯Silverlight 項目多加了一個命令行參數,這個命令行參數是「nostdlib 」,它的作用說明如下:
從上圖可以看出,使用「nostdlib 」參數的作用是不引用標準庫。在.NET 類庫中核心類庫是mscorlib.dll ,使用這個參數的作用就是不引用mscorlib.dll 這些類(因爲Silverlight 要跨操作系統要支持多瀏覽器,所以不能帶有Windows 系統的特色)。儘管在Silverlight 中可以使用C# 或者VB.NET 編程,但是Silverlight 中所使用的是.NET 類庫的子集,在某些用法上受到限制。甚至有些類或者有些方法出現在Silverlight 的運行環境中,但是這些類和這些方法並不能爲開發者所用,所幸的是爲了不必要的麻煩在編寫代碼時出現的智能感知中不會出現這些類或者這些方法。
當我們編譯Silverlight 項目成功之後會得到一個dll 文件,它的文件結構如下:
 

也就是除了一個dll 文件之外還會生成如下文件:
一個.pdb 文件。假設Silverlight 項目名爲SilverlightDemo1 ,那個pdb 文件名爲SilverlightDemo1.pdb ,這個文件包含了VS 需要的調試信息。
一個名爲AppManifest.xaml 的文件。不管Silverlight 項目名是什麼,這個文件名總不會變,在這個文件裏包含了當前Silverlight 項目所需要的程序集。在本實例中AppManifest.xaml 的內容如下所示:
 

一個測試用的html 文件。假設Silverlight 項目名爲SilverlightDemo1 ,那個html 文件名爲SilverlightDemo1TestPage.html ,即{Silverlight 項目名}TestPage.html ,也就是會以Silverlight 項目名加TestPage 作爲文件名的html 文件。這個文件文件演示瞭如何在頁面中嵌入Silverlight 控件,它的代碼如下:
 
 
    
  1. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 
  2. <html xmlns="http://www.w3.org/1999/xhtml" > 
  3. <!-- saved from url=(0014)about:internet --> 
  4. <head> 
  5.     <title>SilverlightDemo1</title> 
  6.     <style type="text/css"> 
  7.     html, body {  
  8.         height: 100%;  
  9.         overflow: auto;  
  10.     }  
  11.     body {  
  12.         padding: 0;  
  13.         margin: 0;  
  14.     }  
  15.     #silverlightControlHost {  
  16.         height: 100%;  
  17.         text-align:center;  
  18.     }  
  19.     </style> 
  20.       
  21.     <script type="text/javascript"> 
  22.         function onSilverlightError(sender, args) {  
  23.             var appSource = "";  
  24.             if (sender != null && sender != 0) {  
  25.               appSource = sender.getHost().Source;  
  26.             }  
  27.               
  28.             var errorType = args.ErrorType;  
  29.             var iErrorCode = args.ErrorCode;  
  30.  
  31.             if (errorType == "ImageError" || errorType == "MediaError") {  
  32.               return;  
  33.             }  
  34.  
  35.             var errMsg = "應用程序中未處理的錯誤" +  appSource + "\n" ;  
  36.  
  37.             errMsg += "代碼: "+ iErrorCode + "    \n";  
  38.             errMsg += "類別: " + errorType + "       \n";  
  39.             errMsg += "消息: " + args.ErrorMessage + "     \n";  
  40.  
  41.             if (errorType == "ParserError") {  
  42.                 errMsg += "文件: " + args.xamlFile + "     \n";  
  43.                 errMsg += "行: " + args.lineNumber + "     \n";  
  44.                 errMsg += "位置: " + args.charPosition + "     \n";  
  45.             }  
  46.             else if (errorType == "RuntimeError") {             
  47.                 if (args.lineNumber != 0) {  
  48.                     errMsg += "行: " + args.lineNumber + "     \n";  
  49.                     errMsg += "位置: " +  args.charPosition + "     \n";  
  50.                 }  
  51.                 errMsg += "方法名稱: " + args.methodName + "     \n";  
  52.             }  
  53.  
  54.             throw new Error(errMsg);  
  55.         }  
  56.     </script> 
  57. </head> 
  58. <body> 
  59.     <form id="form1" runat="server" style="height:100%"> 
  60.     <div id="silverlightControlHost"> 
  61.         <object data="data:application/x-silverlight-2," type="application/x-silverlight-2" width="100%" height="100%"> 
  62.           <param name="source" value="SilverlightDemo1.xap"/> 
  63.           <param name="onError" value="onSilverlightError" /> 
  64.           <param name="background" value="white" /> 
  65.           <param name="minRuntimeVersion" value="3.0.40818.0" /> 
  66.           <param name="autoUpgrade" value="true" /> 
  67.           <a href="http://go.microsoft.com/fwlink/?LinkID=149156&v=3.0.40818.0" style="text-decoration:none"> 
  68.               <img src="http://go.microsoft.com/fwlink/?LinkId=108181" alt="獲取 Microsoft Silverlight" style="border-style:none"/> 
  69.           </a> 
  70.         </object><iframe id="_sl_historyFrame" style="visibility:hidden;height:0px;width:0px;border:0px"></iframe></div> 
  71.     </form> 
  72. </body> 
  73. </html> 
從代碼中可以看到,如果瀏覽此頁面的客戶端沒有安裝Silverlight 客戶端的話,就會看到一個下載安裝Silverlight 客戶端的提示,用戶點擊鏈接就可以跳轉到微軟官方網站下載Siverlight 。下載完成會提示用戶是否安裝。也就是在下載和安裝Silverlight 時都會得到明確的提示,用戶可以明確地選擇是否下載或安裝,這一點很值得國內的公司學習。國內很多以「正當」、「安全」自居的公司提供的軟件總是模糊、混淆某些概念來達到自己不可告人的目的,甚至還有幾款裝機量上億的軟件經常掃描用戶的非系統盤之外的敏感區域,這些公司都應該好好學習一下國外軟件的做法。
一些dll 文件。這些dll 文件是運行當前Silverlight 項目所需要的依賴文件。在AppManifest.xaml 文件中我們看到了裏面有除SilverlightDemo1.dll 之外的如下文件:
System.ComponentModel.DataAnnotations.dll
System.Windows.Controls.Data.dll
System.Windows.Controls.Data.Input.dll
System.Windows.Controls.dll
System.Windows.Controls.Navigation.dll
System.Windows.Data.dll
zh-Hans/System.ComponentModel.DataAnnotations.resources.dll
zh-Hans/System.Windows.Controls.resources.dll
zh-Hans/System.Windows.Controls.Data.resources.dll
zh-Hans/System.Windows.Controls.Data.Input.resources.dll
zh-Hans/System.Windows.Controls.Navigation.resources.dll
zh-Hans/System.Windows.Data.resources.dll
在上面的文件都是運行Siverlight 項目的依賴文件。其中zh-Hans 文件夾中的文件都是一些資源文件,當然除了zh-Hans 之中的文件之外,還有其它類似於zh-Hans 文件夾的文件夾,它們裏面的文件文件名和zh-Hans 文件夾下的文件名一樣,這些文件是語言資源文件。
說到Silverlight 項目的依賴文件就不得不說一下Silverlight 項目的核心文件。大家知道Siverlight 程序是被下載到客戶端執行的,所以需要在客戶端安裝Silverlight 運行環境,爲了方便在不同網絡環境下的用戶下載和安裝Silverlight 運行環境,所以這個運行環境要儘可能的精簡(實際上只有5M ),既然它被精簡了所以自然不可能提供.NET Framework 提供的所有功能。在Silverlight 運行環境中包含了如下程序集:
mscorlib.dll :注意此mscorlib.dll .NET Framework 中的mscorlib.dll ,不過在Silverlight 運行環境中它提供了類似於NET Framework 中的mscorlib.dll 的功能,是Silverlight 運行環境中核心類庫(核心中的核心)。
System.dll :提供了泛型、URI 處理及正則表達式相關功能的類。
System.Core.dll :提供了對LINQ 的支持。
System.Net.dll :提供了網絡功能,這樣我們下載Web 文件和創建基於Socket 的網絡連接。
System.Window.dll :提供在Silverlight 中創建UI 的類。
System.Window.Browser.dll :提供了對HTML 元素交互操作的類。
System.Xml.dll :提供了XmlReader XmlWriter 兩個類用以處理Xml
可以看出在AppManifest.xaml 中出現的那些除SilverlightDemo1.dll 之外的dll 都沒有在Silverlight 運行環境所提供的核心類庫中。前面說了Siverlight 運行環境的設計理念是儘可能使其安裝和下載體積小,所以Siverlight 運行環境的設計者在覈心類庫之外又提供了一些類,這些類提供的功能並不是在每個Silverlight 應用中都會用到(如果總會用到就會放到核心類庫中了),所以以附加類庫的形式提供。當利用VS 編譯Silverlight 項目的時候,VS 會自動檢測那些本項目需要但又不在Silverlight 核心類庫中的dll ,所以我們會在bin 目錄下會看到一些「不知道怎麼跑出來的」dll 運行環境所提供的核心類庫中。前面說了Siverlight 運行環境的設計理念是儘可能使其安裝和下載體積小,所以Siverlight 運行環境的設計者在覈心類庫之外又提供了一些類,這些類提供的功能並不是在每個Silverlight 應用中都會用到(如果總會用到就會放到核心類庫中了),所以以附加類庫的形式提供。當利用VS 編譯Silverlight 項目的時候,VS 會自動檢測那些本項目需要但又不在Silverlight 核心類庫中的dll ,所以我們會在bin 目錄下會看到一些「不知道怎麼跑出來的」dll 文件。
在這些附加類庫中經常會用到的dll 有如下幾個:System.Window.Controls.dll System.Window.Controls.Data.dll System.Window.Controls.Data.Input.dll System.Window.Controls.Input.dll System.Windows.Controls.Navigation.dll ,確實這幾個文件都在我們的AppManifest.xaml 文件中出現了。
一個.xap 文件。同樣假設Silverlight 項目名爲SilverlightDemo1 ,那個xap 文件名爲SilverlightDemo1.xap ,這個xap 文件其實是一個壓縮文件,我們可以直接使用WinRAR 或者其它壓縮軟件打開這個xap 文件(如果不嫌麻煩可以將SilverlightDemo1.xap 改名爲SilverlightDemo1.xap.zip 再用解壓軟件打開)。下圖是使用WinRAR 直接打開SilverlightDemo1.xap 的情景:
 

從上圖中我們看到了前面提到的AppManifest.xaml SilverlightDemo1.dll 文件以及上面提高的「一些」dll 文件,這些dll 文件都是在AppManifest.xaml 中出現過的、SilverlightDemo1 項目所需的附加dll 。適用xap 的方式發佈Silverlight 有兩個好處:一是xap 文件是壓縮過的文件,可以減少網絡流量以提高下載速度;二是便於部署,如果你想部署Silverlight 部署到別某個網站僅僅需要將這個xap 文件拷貝到網站所在的目錄然後創建一個嵌入Silverlight 的頁面就夠了,超級簡單。
Silverlight 項目的部署
前面講到部署Silverlight 項目需要的僅僅是一個xap 文件和一個包含了如何設置Silverlight 項目的網頁文件。有時候出現這樣的情況,創建項目的時候取了一個名字,到最後發佈的時候覺得這個名字不直觀,那麼我們可以通過VS 來設置最終生成的Silverlight 項目的文件名,具體方法就是在Silverlight 項目上點擊鼠標右鍵選擇「屬性」,出現如下窗口:
 

從上圖可以看出,在這個面板中我們可以指定如下跟Silverlight 項目有關的東西:
應用程序集名稱:即最終生成的dll 文件名;
默認空間名:即創建代碼文件時默認所使用的namespace 名稱;
啓動對象名:可以看出爲什麼在App.xaml 中的Application_Startup() 方法中指定啓動那個窗體就會啓動那個窗體,因爲默認是啓動App 對象的,所以它的制定纔有用,如果在項目中創建了多個類似於App.xaml 的文件,則會在這裏出現多個選擇;
生成的Silverlight 應用的版本:如果安裝了多個Silverlight SDK 的話,這裏會有多種選擇;
最終生成xap 文件名:可以將其改爲更直觀的名字。
 
從本篇前面的講解可以知道Silverlight 最終會被下載到客戶端運行,客戶端下載到的是一個xap 文件,這個xap 文件從本質上來說其實就是一個壓縮文件。如果解壓這個文件就可以得到Silverlight 項目的dll 文件,遇上有心人是可以利用一些反編譯工具得到源代碼的,如下圖就是使用著名的反編譯軟件Reflector 打開本篇中的dll 文件的情形:
 

從圖中可以看到利用這個軟件可以很方便地查看沒有經過任何處理的.NET 程序集或者Silverlight 程序集代碼(以MSIL 形式存儲的),所以爲了安全起見不要再Silverlight 中保存任何隱私的、安全相關的數據。除此之外還可以對生成的程序集進行進行加密或者混淆,加密就是利用MSIL 指令的一些特點使反編譯工具無法反編譯出源代碼,混淆時是對程序集中的變量名ily:'宋體';">項目所需的附加dll 。適用xap 的方式發佈Silverlight 有兩個好處:一是xap 文件是壓縮過的文件,可以減少網絡流量以提高下載速度;二是便於部署,如果你想部署Silverlight 部署到別某個網站僅僅需要將這個xap 文件拷貝到網站所在的目錄然後創建一個嵌入Silverlight 的頁面就夠了,超級簡單。
Silverlight 項目的部署
前面講到部署Silverlight 項目需要的僅僅是一個xap 文件和一個包含了如何設置Silverlight 項目的網頁文件。有時候出現這樣的情況,創建項目的時候取了一個名字,到最後發佈的時候覺得這個名字不直觀,那麼我們可以通過VS 來設置最終生成的Silverlight 項目的文件名,具體方法就是在Silverlight 項目上點擊鼠標右鍵選擇「屬性」,出現如下窗口:
 

從上圖可以看出,在這個面板中我們可以指定如下跟Silverlight 項目有關的東西:
應用程序集名稱:即最終生成的dll 文件名;
默認空間名:即創建代碼文件時默認所使用的namespace 名稱;
啓動對象名:可以看出爲什麼在App.xaml 中的Application_Startup() 方法中指定啓動那個窗體就會啓動那個窗體,因爲默認是啓動App 對象的,所以它的制定纔有用,如果在項目中創建了多個類似於App.xaml 的文件,則會在這裏出現多個選擇;
生成的Silverlight 應用的版本:如果安裝了多個Silverlight SDK 的話,這裏會有多種選擇;
最終生成xap 文件名:可以將其改爲更直觀的名字。
 
從本篇前面的講解可以知道Silverlight 最終會被下載到客戶端運行,客戶端下載到的是一個xap 文件,這個xap 文件從本質上來說其實就是一個壓縮文件。如果解壓這個文件就可以得到Silverlight 項目的dll 文件,遇上有心人是可以利用一些反編譯工具得到源代碼的,如下圖就是使用著名的反編譯軟件Reflector 打開本篇中的dll 文件的情形:
 

從圖中可以看到利用這個軟件可以很方便地查看沒有經過任何處理的.NET 程序集或者Silverlight 程序集代碼(以MSIL 形式存儲的),所以爲了安全起見不要再Silverlight 中保存任何隱私的、安全相關的數據。除此之外還可以對生成的程序集進行進行加密或者混淆,加密就是利用MSIL 指令的一些特點使反編譯工具無法反編譯出源代碼,混淆時是對程序集中的變量名和方法名加以變化,使其不再具有望文知義的特點從而達到了即使反編譯得到了源代碼也難以閱讀的目的。在VS 中本身就提供了一個代碼混淆工具Dotfuscator 的社區版,這是一個免費但功能有限的.NET 代碼混淆工具,它的界面如下:
 

如果是一般的項目,可以使用Dotfuscator 進行混淆,這樣可以使一般的窺探者望而生畏。
對於軟件的代碼保護是一個長期以來一直在探討的話題,在本篇裏提出來只是讓大家有這方面的意識,而不是讓大家把大部分精力都放在如何組織窺探自己的源代碼上了。對軟件源代碼的反保護和保護一直是矛和盾的問題,二者相互競爭相互發展,不管使用什麼語言都難以徹底有效地避免這個問題。
總結
在本篇講解了在VS 中開發Silverlight 的界面、Silverlight 項目的組成以及編譯和部署Silverlight 項目時涉及到的一些知識和注意事項。本篇仍是在做準備,下一篇就會講到Silverlight 的界面佈局,學習開發Silverlight 面臨的第一個問題就是界面佈局問題,下一篇就會講解常見的界面佈局。
2010-09-13