*

Silverlight 2.0 Beta 1 之初體驗

作者:黃忠成

Silverlight 2.0 的特色

Microsoft 於日前釋出了 Silverlight 2.0 Beta 1,由於正巧處於我新書完稿後期,因此一直抽不出時間來試一下這個即將改變 Web 時代的利器,直到書稿交付後,才有時間好好來看一下這個技術。

Silverlight 2.0 是由 Silverlight 1.1 所演化而來,她擁有了 Silverlight 1.1 時的所有特色,包含可用任何 .NET Language 來開發,一個小型的CLR,支援自訂控件等等。除此之外,Silverlight 2.0 的 Runtime 內建了中文、英文、韓文、日文等字型,這意味著以往於 Silverlight 1.1 時期無法循簡單途徑來顯示中文的憂慮已消失了。

Silverlight 2.0 也正式朝 WPF 子集的方向前進,她內建了如 StackPanel、Grid、Button、TextBox、ListBox、DataGrid 等豐富的控件,添加了 Data Binding 及直接網路存取能力,擁有直接存取網路的能力,意味著以往需依賴 AJAX 來與後端溝通的工作,現在可以在沒有 AJAX 的情況下做到。同時! Silverlight 2.0 也支援跨網域的存取能力,這代表著我們將不再受限於只能存取同網域主機了,不過這點我尚未仔細去研究。

WCF Support

擁有了直接網路存取的能力,也代表著我們可以直接透過 WCF 與後端溝通,Silverlight 2.0 中已經包含了 WCF 的支援,如 System.ServiceModel.ClientBase 等類別都已內建,我們只要於 Silverlight 2.0 的專案中,以 Add Service Reference 即可取用遠端的 WCF Service。

LINQ Support

Silverlight 2.0 內建 LINQ 的支援,這代表兩件事,一是有了 LINQ 的支援,於 Silverlight 中對資料進行查詢會相當簡單,二是程式設計師們得好好學習 LINQ 了,未來她將長伴左右。

Data Binding

Silverlight 2.0 終於支援了資料網頁相當倚重的 Data Binding 技術,除了常見的 OneWay (單向) 外,Silverlight 2.0 也支援了 TwoWay (雙向),這讓撰寫資料網頁會變得相當輕鬆。

我們可以做什麼?

那有了 Silverlight 2.0 後,我們可以做什麼呢?想想看,有了 WCF 的支援,我們可以在網站上放置一個 WCF Service,然後添加一個 LINQ To SQL Classes 項目,以 ORM 的方式由資料庫取得資料,然後讓 Silverlight 2.0 透過 WCF Service 來取得資料並顯示、操作,或是於 Silverlight 2.0 程式中以 LINQ 來查詢資料。

是的!這不再是單純的網頁應用程式了,這已經進入了 N-Tier 的範疇,如同以往使用 Windows Form、WPF 般,我們可以在 Silverlight 2.0 中設計美麗且方便的 UI 畫面,不再受限於 HTML、CSS 了,這是真正的 RIA 時代。

建構 WCF Data Service

再多的文字都比不上一個實例來得清楚,在 Silverlight 2.0 下,我們可以運用 WCF 來撰寫 DataService,讓運行於客戶端瀏覽器上的 Silverlight 透過存取 WCF Service 來取得資料並顯示,整個架構如圖 1。

圖 1:

在這個架構中,我們將於 Web 伺服器上架設一個 WCF Servcie,該 WCF Service 會透過 LINQ To SQL 來取得資料,並回傳給客戶端。而瀏覽器端的 Silverlight 應用程式則透過 WCF Service Client 的支援,以 HTTP 通訊協定與後端的 WCF Service 溝通來取得資料,藉助 Data Binding 支援來顯示。

要實作這個例子,你得先安裝好 Silverlight 2.0 的 Runtime 及 SDK,完成後會於 Visual Studio 2008 中看到 Silverlight 的 Template。

Silverlight 2.0 SDK Beta 1 下載位址:
http://www.microsoft.com/downloads/details.aspx?FamilyID=4E03409A-77F3-413F-B108-1243C243C4FE&displaylang=en

圖 2:

點選 OK 按鈕後,Visual Studio 2008 會詢問建立的專案類型。

圖 3:

這裡有兩種專案類型可以選擇,一是 Web Site 模式,二是 Web Application 專案型式,此例採用 Web Site 模式。點選 OK 按鈕,Visual Studio 2008 會產生出兩個專案,一是 Web Site 專案,二是 Silverlight 專案。

圖 4:

目前的 Silverlight Designer 尚未完全支援即視即所得的視覺化設計模式,設計師必須於下方自行鍵入 XAML 程式碼,上方的設計區會即時顯示你所鍵入的 XAML 視覺畫面。

在開始撰寫 WCF Service 前,讓我先解釋一下這兩個專案的意涵,Web Site 專案與一般的 ASP.NET 專案並無不同,差別在於其產生了一個 .HTML 及一個 .ASPX 檔案,.HTML 檔案中直接以 JavaScript 引用了位於另一個 Silverlight 專案,而 .ASPX 則使用 XAML 控件來引用另一個 Silverlight 專案,兩個檔案所做的事是一樣的,差別只在於一個是 .ASPX,一個則是單純的 HTML。

當以瀏覽器來瀏覽這些 .ASPX、.HTML 檔案時,Silverlight 2.0 Runtime 會下載另一個 Silverlight 專案所產生的 .xap 檔案,然後開始執行。

好了,了解了兩個專案的用途後,我們可以做出一個概念上的結論,Silverlight 內嵌於 Web 伺服器端的 HTML 或是 ASP.NET 網頁內,當安裝了 Silverlight 2.0 Runtime 的客戶端於瀏覽這類型網頁時,會依照 HTML、ASP.NET 頁面中的 Silverlight 定義,由 Web 伺服器下載指定的 .xap  檔案來執行。

所以,Silverlight 2.0 完全執行於客戶端,不受 HTML、CSS 等限制,與一般的 Windows Form、WPF 程式的唯一差別是,Silverlight 2.0 CLR 是一個小型的 CLR,僅支援部份的 .NET Framework,如 Windows Form、DataSet 都不支援。

但 Silverlight 2.0 不支援,並不代表我們不能做,因為就 N-Tier 架構而言,前端本來就只需要 UI 及網路的功能,複雜如 DataSet 的產生、ORM 的功能都是在後端完成的,客戶端只需有網路通訊支援,就能透過 HTTP、TCP 等協定來取得後端的資料。

請於 Web 專案上按右鍵,選擇加入一個 LINQ To SQL Classes (中文 IDE 為 LINQ To SQL 類別) 項目,然後於內添加一個北風資料庫之 Customers 資料表的 Entity Class。

圖 5:

要透過 WCF Service 來傳遞 Entity Object,你得先將 LINQ To SQL Designer 所產生的 Entity Class 標示為可序列化。

圖 6:

完成資料層的工作後,現在新增一個 WCF Service 至此 Web 專案中,取名為 DataService.svc,刪除自動產生的 IDataService.cs 檔案,於 DataService.cs 中鍵入程式1的程式碼。


using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;

[ServiceContract]
public interface IDataService
{
    [OperationContract]
    Customer[] GetAllCustomers();
}

public class DataService : IDataService
{
    private NorthwindDataContext _context = new NorthwindDataContext();

    #region IDataService Members

    public Customer[] GetAllCustomers()
    {
        return (from s1 in _context.Customers orderby s1.CustomerID select s1).ToArray();
    }
    #endregion
}

然後開啟 web.config,修改 WCF Service 所使用的通訊協定,由預設的 wsHttpBinding 改為 BasicHttpBinding,要做這個動作的主因是目前 Silverlight 2.0 並不支援 wsHttpBinding。

………
<system.serviceModel>
		<behaviors>
   <serviceBehaviors>
    <behavior name="DataServiceBehavior">
     <serviceMetadata httpGetEnabled="true" />
     <serviceDebug includeExceptionDetailInFaults="true" />
    </behavior>
   </serviceBehaviors>
  </behaviors>
		<services>
   <service behaviorConfiguration="DataServiceBehavior" name="DataService">
	   <endpoint address="" binding="basicHttpBinding" contract="IDataService" >
		   <identity>
			   <dns value="localhost" />
		   </identity>
	   </endpoint>
    <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
   </service>
		</services>
	</system.serviceModel>
………

至此,後端的工作就告一段落,讓我們開始撰寫客戶端,也就是 UI 層。

UI 層的實作

於另一個 Silverlight 專案上按右鍵,選擇 Add Service Reference。

圖 7:

圖 8:

圖 9:

按下 OK 按鈕後,我們就能於 Silverlight 存取此 WCF Servcie 了,請切到 Page.xaml 來鍵入程式 2 的 XAML 碼。

程式 2:

<UserControl x:Class="SilverlightApplication2.Page"
    xmlns="http://schemas.microsoft.com/client/2007" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    Width="650" Height="400">
    <Grid x:Name="LayoutRoot" >
        <Grid.Background>
            <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                <GradientStop Color="#FFEDF0F4" Offset="0"/>
                <GradientStop Color="#FF8C8E92" Offset="1"/>
            </LinearGradientBrush>
        </Grid.Background>
	
	
	        <TextBlock Height="28" HorizontalAlignment="Left" Margin="16,50,0,0" 
Name="label1" VerticalAlignment="Top" Width="91">CustomerID:</TextBlock>
        <TextBox Height="23" Margin="127,49,0,0" BorderThickness="1,1,2,2" 
Text="{Binding CustomerID}" Name="textBox1" VerticalAlignment="Top" 
HorizontalAlignment="Left" Width="84" >
            <TextBox.Background>
                <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                    <GradientStop Color="#FFE6ECF2" Offset="0.031"/>
                    <GradientStop Color="#FFB2BACC" Offset="0.871"/>
                </LinearGradientBrush>
            </TextBox.Background>
        </TextBox>
        <TextBlock Height="28" Margin="225,50,306,0" Name="label2" 
VerticalAlignment="Top">Company Name:</TextBlock>
        <TextBox  Height="23" HorizontalAlignment="Right" BorderThickness="1,1,2,2" 
Text="{Binding CompanyName}" Margin="0,49,40,0" Name="textBox2"  
VerticalAlignment="Top" Width="266" >
            <TextBox.Background>
                <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                    <GradientStop Color="#FFE6ECF2" Offset="0.031"/>
                    <GradientStop Color="#FFB2BACC" Offset="0.871"/>
                </LinearGradientBrush>
            </TextBox.Background>
        </TextBox>
        <TextBlock Height="28" HorizontalAlignment="Left" Margin="16,106,0,0" 
Name="label3" VerticalAlignment="Top">Contact Name:</TextBlock>
        <TextBox Height="23" Margin="127,105,315,0" Name="textBox3" 
BorderThickness="1,1,2,2" 
Text="{Binding ContactName}" VerticalAlignment="Top"  >
            <TextBox.Background>
                <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                    <GradientStop Color="#FFE6ECF2" Offset="0.031"/>
                    <GradientStop Color="#FFB2BACC" Offset="0.871"/>
                </LinearGradientBrush>
            </TextBox.Background>
        </TextBox>
        <Button x:Name="btnPrev" Height="23" Margin="15,15,550,23" 
Click="btnPrev_Click"  />
        <TextBlock x:Name="btnPrevText" Height="23" Text="Prev" Margin="40,15,500,23"/>              
        <Button x:Name="btnNext" Height="23" Margin="125,15,450,23"  
Click="btnNext_Click" />
        <TextBlock x:Name="btnNextText" Height="23" Text="Next" Margin="145,15,300,23"/>      
        <TextBlock Text="Power by Silverlight 2.0 With WCF And LINQ To SQL" 
Margin="260,350,0,0" Foreground="Beige"/>
        <TextBlock Text="And 黃忠成 ^__^" Margin="500,380,0,0" Foreground="SkyBlue"/>
    </Grid>
</UserControl>

接著切到 Page.xaml.cs 中,鍵入程式 3 的程式碼。

程式 3:


using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.ServiceModel;

namespace SilverlightApplication2
{
    public partial class Page : UserControl
    {
        DataService.DataServiceClient _client = null;
        private List _stores = null;
        private int _cursor = -1;

        private void InitializeBinding()
        {
            BasicHttpBinding binding = new BasicHttpBinding();
            binding.MaxReceivedMessageSize = 10000000;
            _client = new SilverlightApplication2.DataService.DataServiceClient(binding,
                new EndpointAddress("http://"+Application.Current.Host.Source.Host + ":" 
+Application.Current.Host.Source.Port.ToString()+
"/SilverlightApplication2_Web/DataService.svc"));        
}

        public Page()
        {
            InitializeComponent();
            InitializeBinding();
            _client.GetAllCustomersCompleted +=
new EventHandler(
_client_GetAllCustomersCompleted);
            _client.GetAllCustomersAsync();            
        }

        void _client_GetAllCustomersCompleted(object sender, 
SilverlightApplication2.DataService.GetAllCustomersCompletedEventArgs e)
        {
            _stores = (from s1 in e.Result.ToList() 
                       where s1.CustomerID.Contains("V") 
                       select s1).ToList();
            DataContext = _stores[0];
            _cursor = 1;
        }

        private void btnPrev_Click(object sender, RoutedEventArgs e)
        {
            if (_cursor - 1 >= 0)
            {
                _cursor--;
                DataContext = _stores[_cursor];
            }
        }

        private void btnNext_Click(object sender, RoutedEventArgs e)
        {
            if (_cursor + 1 < _stores.Count)
            {
                _cursor++;
                DataContext = _stores[_cursor];
            }
        }
    }
}

執行此專案後,結果如圖 10。

圖 10:

由此畫面可以看到, Silverlight 2.0 對於中文的支援已經相當完善了,內建的中文字型可用於顯示及輸入中文。透過 WCF 的支援,我們也可以不透過 AJAX 直接與後端溝通,透過 Data Binding 機制,顯示及輸入資料更加簡便了。

Two-Way Binding

預設情況下,Silverlight 2.0 是以 OneWay (單向) Binding 為主,透過修改 Binding 的定義字串,我們可將其改成 TwoWay (雙向)。

<TextBox Height="23" Margin="127,105,315,0" Name="textBox3" BorderThickness="1,1,2,2" 
Text="{Binding ContactName, Mode=TwoWay}" VerticalAlignment="Top"  >

一旦標示為 TwoWay 後,使用於 TextBox 上所輸入的資料,會直接反應到繫結的物件上。有了這個機制,我們可將使用者修改的資料,再次透過 WCF Service 來更新回後端資料庫中,只是!這還需要一個小型的 Tracking Changes 機制,這是另一個故事了。

範例下載:
http://www.dreams.idv.tw/~code6421/files/SL20Demo.zip

PS:執行此程式前,你必須修改 web.config 中的 Connection String,連結至你的 SQL Server

LINQ、LINQ To SQL 參考書目:

極意之道-次世代 .NET 資料庫開發聖典 ASP.NET 篇 – 黃忠成 (將於四月出版)

WCF 參考書目:

Microsoft Windows Communication Foundation 新一代應用程式通訊架構 – 李漢宗