本文討論:
.NET Compact Framework 中的郵件傳輸
編寫簡單消息傳送應用程序
WCF 消息傳送內幕探測
消耗 WCF Web 服務
本文使用了以下技術:
.NET Compact Framework 3.5, Visual Studio 2008
移動設備的尋址能力問題一直以來都非常棘手,它 會使編寫從服務器接收推送數據的 Windows Mobile® 應用程序變得非常困難。小型設備一般都不具 有與其綁定的靜態 IP 地址或動態 DNS 項。對於此類設備,常見的解決方法是在設備聯機時向服務器發 送一個 HTTP 請求,然後服務器使該請求進入等待狀態,直到有內容要推送給設備為止。此時服務器使用 更新內容來響應這個一直在等待的請求,而設備則在開始處理更新內容同時發出另一個請求以等待下一次 更新。
此解決方法會給服務器的可伸縮性帶來影響,因為它必須同時掛起許多請求,而不是立即響應它們並 隨即關閉連接。這還會縮短設備的電池使用壽命,因為設備必須始終保持連接狀態。如果在服務器的更新 內容准備就緒時設備未處於開啟狀態,服務器將無法發送更新,它必須丟棄更新內容或繼續保留此狀態, 即都為哪些設備提供了哪些更新。最後,如果網絡不可用,應用程序也無法發送或接收消息。
Visual Studio® 2008 為 Windows Mobile 應用程序的開發人員提供了通過 Microsoft® .NET Compact Framework 3.5 訪問 Windows® Communication Foundation (WCF) 功能子集的能力, 由於此工具包括的兩個新 WCF 綁定元素非常有利於使用電子郵件傳輸來收發消息,因而解決了上述的許 多問題。由於許多設備已經具備電子郵件同步功能,因此這些傳輸可借助電子郵件的固有隊列特性和已在 Internet 上建立的電子郵件服務器來創建可尋址的消息隊列,這些消息隊列能夠以真正的消息推送方式 進行點對點、設備對服務器以及服務器對設備的消息級別的通信。在本文中,我將概述 .NET Compact Framework 3.5 所支持的 WCF 子集,並介紹如何在移動應用程序中利用這些傳輸和工具。
郵件傳 輸
.NET Compact Framework 3.5 包括在 Windows Mobile 平台上進行消息傳送的 WindowsMobileMailTransport 以及在 Windows 桌面平台上進行消息傳送的 ExchangeWebServiceMailTransport。這兩種傳輸可進行交互,因此設備之間、計算機之間或設備與計算 機之間的通信都是以透明方式工作的。由於對某個接收方而言,可能有多個應用程序同時對其使用郵件傳 輸方式,因此可在其中包括一個郵件通道名稱,以區別來自不同應用程序的消息。此通道名稱隨後將包括 在每條消息的主題行中。因此,郵件傳輸將僅打開主題名稱與您所監聽的通道名稱相對應的消息。
對於 .NET Compact Framework 3.5 附帶的 Windows Mobile 設備,其郵件傳輸功能在支持 DirectPush 的 Windows Mobile 設備上運行時表現最佳,DirectPush 功能可使關聯的 Exchange 服務器 在 Exchange 收到消息後馬上將消息推送到 Windows Mobile。要了解有關 DirectPush(也稱為 Always -Up-To-Date)或有關在 Windows Mobile 設備上安裝 DirectPush 的更多信息,請訪問 msexchangeteam.com/archive/2004/04/26/120520.aspx。
如果您熟悉“Microsoft 消息隊 列”(MSMQ),就會發現郵件傳輸與其非常類似,但它沒有設備尋址能力方面的問題。存儲在設備上 和 Exchange 服務器上的郵件相當於消息隊列。每個設備和服務器的電子郵件地址相當於隊列的 URI。
但請注意,雖然理論上說這兩個使用 WindowsMobileMailTransport 的設備可以使用任何 SMTP/POP3 服務器進行通信,但是目前 .NET Compact Framework 3.5 中支持的唯一方案是使用 Exchange Server 2007 作為郵件服務器。此外,自定義的 Windows CE 設備也不受支持。
為演示 傳輸是如何工作的,我將構建一對小巧的示例應用程序,來使用電子郵件在 Windows Mobile 設備與計算 機之間發送類似於郵件的即時消息。郵件傳輸只支持單向消息傳送(與“請求–答復” 流程不同),這非常適合於即時消息應用程序。
首先要執行的兩個操作是安裝 Visual Studio 2008 和確定目標設備。此目標設備(無論是實體設備 還是仿真設備)必須被配置為能夠訪問 Exchange Server 2007 郵件帳戶。在測試應用程序前務必要執行 此操作。如果配置的是仿真設備,請使其繼續運行或在關閉前保存其狀態,否則它將恢復到沒有郵件帳戶 設置的狀態。如果忘記這一點,您可能會發現郵件都堆積在設備的發件箱中而無法發送出去。
在 開始之前,請確保安裝了 Windows Mobile Device Center 6.1 或 ActiveSync® 4.5。可從 microsoft.com/activesync 下載它們。
如果運行的是 Windows Vista®,則在仿真器上設置 郵件的最簡便方法是打開“設備仿真器管理器”並插入仿真器。Windows Mobile Device Center (WMDC) 在設備連接後會打開。如果安裝了 WMDC 但卻無法識別插入的仿真器,請使用“開 始”菜單啟動 WMDC。在“Mobile Device Settings”(移動設備設置)下面,選中 “Connection Settings”(連接設置)並確保選中“Allow connections to one of the following:”(允許連接到以下其中一個端口:),然後從下拉列表中選中 DMA。
單擊 “Set up your device”(設置設備)並保持至少一個電子郵件選項處於選中狀態。然後,繼 續操作並提供相關信息,以允許設備(或仿真器)直接連接到 Exchange。這將允許程序在網絡可用的情 況下隨時發送和接收消息 — 即使是在未插入時。完成設備或仿真器的合作關系設置。
如果 想使用自己的個人電子郵件地址進行郵件傳輸,可選擇建立一條 Exchange 規則,將所有主題行以 "SM": 開頭的消息重新定向到其他文件夾。.NET Compact Framework 3.5 郵件傳輸只在兩個 文件夾(收件箱和服務電子郵件)中檢查 WCF 消息,因此請務必將 WCF 消息保存在這兩個文件夾的其中 一個內。
編寫 Windows Mobile 應用程序
讓我們從創建一個 Visual C#® 智能設備項 目開始編寫 Windows Mobile 應用程序。將項目命名為 DeviceMessagingApp,將解決方案命名為 MsdnMessagingSample,如圖 1 所示。將進入另一個對話框,在其中可以選擇目標平台和 .NET Compact Framework 版本。選擇 Windows Mobile 6 Professional SDK 平台並將 .NET Compact Framework 版本 設置為 3.5。如果沒有看到 Windows Mobile SDK 選項,請訪問 go.microsoft.com/fwlink/? LinkID=81684 並下載 Windows Mobile SDK,然後安裝它並重新回到此步驟。
圖 1設置項目
您會發現您已進入 Visual Studio 2008 窗體設計器,其中顯示有 Windows Mobile 設備和您的 空白主窗體。要編寫一個簡單的聊天程序,可將幾個 TextBox 和 Label 控件拖放到窗體中。可在歷史文 本框中設置 TextBox.ReadOnly = True 以防止意外加入條目。確保在歷史框中設置 TextBox.Multiline = True。向菜單中添加一個“發送”按鈕,以便可以使用硬件按鈕點擊它。接下來進行一些喜 好設置:將 Form.MinimizeBox 設置為 False,使角上的 X 變成 "ok",這樣在按下去時,應 用程序將退出而不是隱藏起來。最後的結果應類似於圖 2 所示。
圖 2 應用程序 UI
由於要使用 Compact Framework 版本的 WCF,因此需要添加一些程序集引用。在 “Solution Explorer”(解決方案資源管理器)中右鍵單擊項目的 “References”(引用)文件夾,然後單擊“Add Reference”(添加引用)。在 .NET 選項卡中,選擇以下程序集:
System.ServiceModel.dll
System.Runtime.Serialization.dll
Microsoft.Servic eModel.Channels.Mail.dll
Microsoft.ServiceModel.Channels.Mail.WindowsMobile.dll
WCF 依靠抽象類 XmlObjectSerializer 的任何一種實現來填充其消息正文。在桌面版本中,WCF 附帶有 DataContractSerializer,它即源於此類。Compact WCF 不包括 DataContractSerializer,因此將使用 XmlSerializer。由於 XmlSerializer 並不是按照 WCF 所要求的從 XmlObjectSerializer 衍生而來,因 此可編寫一個從中衍生而來的小類然後只調用 XmlSerializer 來完成繁重的任務。
將一個從 XmlObjectSerializer 衍生而來的名為 MessageSerializer 的新類添加到項目中。如果在源文件中右鍵 單擊基類名稱,Visual Studio 會試圖自動執行您需要實現的所有功能。對此基類您需要做的唯一一件事 情是添加一些新代碼,如圖 3 所示。它們將定義一個構造函數來初始化 XmlSerializer 並實現 ReadObject 和 WriteObject 方法。您需要自己在 WriteObject 方法中進行編寫。
Figure 3 XmlObjectSerializer
using System; using System.Runtime.Serialization; using System.Xml.Serialization; class MessageSerializer : XmlObjectSerializer { XmlSerializer serializer; public MessageSerializer(Type type) { serializer = new XmlSerializer(type); } public override void WriteObject( System.Xml.XmlDictionaryWriter writer, object graph) { serializer.Serialize(writer, graph); } public override object ReadObject( System.Xml.XmlDictionaryReader reader, bool verifyObjectName) { return serializer.Deserialize(reader); } // ... }
這些由 Visual Studio 添加到類中的方法我在此並未列出,它們可能只是拋出 NotImplementedException,因為 WCF 不會以任何方式調用它們。
WCF 消息傳送探測
要發 送和接收消息,必須初始化 WCF 郵件通道。由於 .NET Compact Framework 3.5 並不實現桌面 WCF 中包 括的許多“服務模型”類,因此必須使用需要較多代碼的消息傳送 API。確保使用與平台無關 的方式(只調用 .NET Compact Framework 提供的 WCF API)來編寫此代碼,以便能夠在設備和桌面應用 程序之間共享,從而使各個單獨的應用程序變得小巧而簡單。
添加一個名為 Messaging.cs 的新 的類文件。這將包含 Messaging 類,其中會包括發送和接收消息所必需的全部探測。在實際應用程序中 ,為了得到更出色的線程安全性和可伸縮性,此類的規模可能會非常大,但是我會盡可能縮減我的示例以 突出本例中的任務。
我選擇使用 Messaging 類作為普通 Messaging <T> 類,其中 T 是在 應用程序間來回傳送的數據類型(參見圖 4)。在一個較復雜的應用程序中,可能要傳遞多種類型的對象 並使用消息的 Action 字符串加以區別,但是我在此不打算這樣做。任何 T 都可以用於此類(只要能夠 使用 XmlSerializer 對其進行序列化)。
Figure 4 簡單消息傳送類
using Microsoft.ServiceModel.Channels.Mail; class Messaging<T> { public delegate void IncomingMessageCallback(T body); public const string DeviceSendChannel = "toDesktop"; public const string DesktopSendChannel = "toDevice"; public Messaging(MailBindingBase binding, string sendChannelName, string listenChannelName, IncomingMessageCallback incomingMessageCallback) { // ... } public void SendMessage(string recipient, T body) { // ... } public void Close() { // ... } }
Messaging<T> 類將提供兩個方法來發送和接收消息。接收消息包括對消息進行後台線 程偵聽,還包括在有消息到達時調用回調方法。它需要在構造函數中構建通道,還需要提供一個 Close 方法,以在應用程序關閉時消除通道。
由於存在兩個郵件綁定類(一個用於桌面,一個用於移動 設備),所以此類將使用綁定來作為其構造函數的參數。為了能夠對桌面和設備使用相同的電子郵件帳戶 而無需這兩個應用程序讀取彼此的傳入消息,需要對桌面和設備使用不同的通道名稱(因而構造函數也將 用這些作為參數)。
至此我們已經為我們的類設計了框架結構,如圖 4 所示。在編寫了該框架的 代碼後,我們可以繼續編寫設備和桌面應用程序的剩余代碼。Messaging.cs 的完整實現包含在為本文下 載的源代碼中。其中幾乎全部都是普通的 WCF 消息傳送層代碼,而未涉及任何郵件傳輸內容。
下 一步是構建和消除郵件通道。打開主窗體 (Form1.cs) 後面的代碼。您需要在構造函數中初始化此郵件通 道並在窗體關閉時消除它。在開始時,首先添加一個字段來跟蹤 Messaging<T> 實例。由於應用程 序使用文本消息進行通信,因此 T 將是一個字符串:
Messaging<string> messaging;
您應馬上初始化此字段。由於 Messaging<T> 構造函數需要一個回調,因此 首先要定義一個兼容的方法:
void incomingMessage(string message) { }
現在在構造函數中初始化消息傳送字段。對兩個通道名稱進行排序時要注意,應使設備應用程 序使用 DeviceToDesktopChannel 名稱來發送消息,而偵聽的名稱應該是 DesktopToDeviceChannel。這 樣,設備就不會收到它發給桌面的消息,即使桌面和設備共享一個電子郵件地址:
messaging = new Messaging<string>( new WindowsMobileMailBinding(), Messaging<string>.DeviceToDesktopChannel, Messaging<string>.DesktopToDeviceChannel, (Messaging<string>.IncomingMessageCallback)incomingMessage);
當應用程序在 Form.Closed 事件處理程序中退出後,關閉通道。Form1_Closed 方法只調用 Messaging<T> 對象 的 Close 方法:
void Form1_Closed(object sender, EventArgs e) { messaging.Close(); }
創建並發送消息
設備應用程序馬上就要完成了。現在需要做的只是發送和響應消息。 您需要為“發送”按鈕添加一個處理程序。該事件處理程序應調用 Messaging<T> 對象 的 SendMessage,然後清除消息文本框以使用戶知道消息已發出,類似於下面所示:
void sendMenuItem_Click(object sender, EventArgs e) { messaging.SendMessage(toTextBox.Text, messageTextBox.Text); messageTextBox.Text = ""; }
它非常簡單。現在只需通過實現 incomingMessage 方法來響應傳入的消息即可(參見圖 5) 。在此要注意,此回調是從偵聽新消息的後台線程調用的,但您必須從 UI 線程將文本添加到會話歷史文 本框中。檢查 InvokeRequired 屬性以確定您是否在後台線程中;如果在,則調用 Invoke 來調用 UI 線 程中的回調方法。當在 UI 線程上時,只需將傳入的消息附加到會話框中,然後確保歷史框向下滾動足夠 的距離以讀取最新消息即可。
Figure 5 響應傳入的消息
void incomingMessage(string message) { // Invoke ourselves on the UI thread if (InvokeRequired) { Invoke((Messaging<string>.IncomingMessageCallback) incomingMessage, message); } else { // append incoming message to message history historyTextBox.Text += message + Environment.NewLine; // scroll to end of history window historyTextBox.Select(historyTextBox.Text.Length, 0); historyTextBox.ScrollToCaret(); } }
這樣就完成了設備應用程序。如果兩個不同的設備具有不同的電子郵件帳戶並已為其設置了正 確的通道名稱,則此應用程序完全可以為您提供在設備間進行 IM 聊天的功能。但是讓我們繼續以一台設 備和一個桌面對等物並且只有一個電子郵件地址的情況為例。
編寫桌面應用程序
將一個新 的 WPF 應用程序項目添加到解決方案中。如果未看到它作為一個選項出現,請檢查“Add New Project”(添加新項目)對話框右上角的下拉框中是否已設置為 .NET Framework 3.5。Windows 窗體應用程序也完全能夠勝任,但是對於這些示例,我們將使用更新的技術。將項目名稱設置為 DesktopMessagingApp。
在窗體設計器上,拖出對設備應用程序使用的控件。即使以前未使用過 WPF,也不要被設計器的外觀變化所嚇倒。而且,在 WPF 中無需為歷史框設置 TextBox.Multiline。
最後的結果應類似於圖 6 所示。如果只是想為此窗體輸入 XAML,則可在下載的源代碼中進行尋 找。
圖 6桌面應用程序的基本 UI
將數個引用程序集添加到桌面項目中,方法與設備項目的添加方法類 似。需要引用的程序集包括:
System.ServiceModel.dll
System.Runtime.Serialization.dll
Microsoft.Servic eModel.Channels.Mail.dll
Microsoft.ServiceModel.Channels.Mail.ExchangeWebService.dll
接下來,將您在設備項目中創建的 MessageSerializer.cs 和 Messaging.cs 文件鏈接到桌面項目 中。在“Solution Explorer”(解決方案資源管理器)中右鍵單擊 DesktopMessagingApp, 然後單擊“Add”(添加)|“Existing Item”(現有項)。導航到設備應用程序 目錄。選中 MessageSerializer.cs 和 Messaging.cs,但現在不要單擊“Add”(添加)按鈕 。此時應單擊按鈕右邊緣的下拉箭頭,然後選擇“Add As Link”(作為鏈接添加)。這將允 許在兩個項目之間共享源文件。
在此唯一要注意的是,由於您已在設備項目中創建了源文件,因 此共享類的命名空間會與設備項目的命名空間匹配。如果這讓您感到困惑,您完全可以將這些類的命名空 間改為一些更普通的空間。此外,您可以將這兩個源文件放入共享的庫項目中(注意不要引用 ExchangeWebService.dll 或 WindowsMobile.dll 程序集)。
構建和消除桌面應用程序的郵件通道與在設備應用程序中所使用的方法類似,除了需要實例化 ExchangeWebServiceMailBinding 而不是 WindowsMobileMailBinding 以外。還要注意的一點是,雖然 WindowsMobileMailBinding 只是使用設備(或仿真器)上的郵箱,但 ExchangeWebServiceMailBinding 需要網絡憑據。假定該憑據與您的網絡登錄憑據相同,則您可以只向 ExchangeWebServiceMailBinding 構造函數傳遞一個空值,Exchange 將使用“Windows Integrated Security”(Windows 集成 安全性)進行驗證。您可以繼續保持此模式(更安全),也可以硬編碼用戶名和密碼,還可以在構建 Messaging<T> 實例前,在運行時提示用戶輸入憑據。
兩個郵件綁定類之間的另一個不同點 是:在 ActiveSync 轉入新消息時 WindowsMobileMailBinding 會立即得到通知,而 ExchangeWebServiceMailBinding 必須定期調用 WebMethods 來查詢是否有新消息,您可以接受默認的 30 秒周期,也可以自行設置周期。
首先將下面兩個字段添加到 Window1.xaml.cs 源代碼文件中 。將 Exchange 服務器改為自己的 Microsoft Outlook® Web Access URL:
Messaging<string> messaging; readonly Uri exchangeUrl = new Uri("https://mail.wingtiptoys.com");
與設 備應用程序中一樣,創建消息接收方法,以便可以將其作為回調傳遞到 Messaging<T> 構造函數中 :
void incomingMessage(string message) { }
您可以在 Window1 構造函數中實例化 Messaging<T> 類,將其傳遞到 Exchange 郵件 綁定中(參見圖 7)。下一步非常重要:交換設備應用程序中通道名稱的順序。通過交換順序,可以使桌 面接收來自設備的消息,使設備接收來自桌面的消息。如果未交換通道,則兩端都在同一個通道上會話而 在另一個通道上偵聽,因此永遠也不會收到任何消息。
Figure 7 啟動郵件傳輸綁定
public Window1() { InitializeComponent(); ExchangeWebServiceMailBinding binding = new ExchangeWebServiceMailBinding(exchangeUrl, null); ((ExchangeWebServiceMailTransport) binding.Transport).ServerQueryInterval = 2000; messaging = new Messaging<string>( binding, Messaging<string>.DesktopToDeviceChannel, Messaging<string>.DeviceToDesktopChannel, incomingMessage); }
不需半分鐘您就可以看到結果,在本示例中,我強制每隔兩秒輪詢一次 Exchange 服務器。請 注意:在產品應用程序中將查詢間隔設為兩秒可能會對可伸縮性帶來影響。
要關閉通道,必須連 接一個方法以響應 Window.Closed 事件。在 Window1.xaml 文件中打開 XAML 代碼,然後將 Closed="Window_Closed" 屬性添加到開放的 <Window> 標記中。它看上去應類似於:
<Window x:Class="DesktopMessagingApp.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Desktop Messaging App" Height="331" Width="440" Closed="Window_Closed">
將 Window_Closed 方法添加到 Window1.xaml.cs 源代碼文件中,然後在 Messaging<T> 對象中調用 Close 方法,如下所示:
void Window_Closed(object sender, EventArgs e) { messaging.Close(); }
同樣,現在需要做的只是發送和響應消息。在設計器中通過雙擊“發送”菜單按鈕 來為其添加處理程序。調用 SendMessage 並清除用戶從中鍵入消息的文本框,以使其能夠看到消息已發 送:
void sendButton_Click(object sender, RoutedEventArgs e) { messaging.SendMessage(toBox.Text, messageBox.Text); messageBox.Clear(); }
再次重申,要實現 incomingMessage 方法,請務必記住它將從後台線程被調用,而您需要在 UI 線程中調用它。WPF 對此的處理方式與 Windows 窗體略有不同。您調用的不是 InvokeRequired 屬性 ,而是 CheckAccess;不是在控件上調用 Invoke,而是在控件的 Dispatcher 對象上調用它。最後,WPF 在 TextBox 控件上有一些便利的方法,可以幫我們完成我們想做的事情,因此要善加利用它們:
void incomingMessage(string message) { if (historyBox.CheckAccess()) { historyBox.AppendText(message + Environment.NewLine); historyBox.ScrollToEnd(); } else { historyBox.Dispatcher.Invoke(DispatcherPriority.Normal, (Messaging<string>.IncomingMessageCallback)incomingMessage, message); } }
至此大功告成!讓我們運行它看一看結果!
運行示例
構建並部署設備項目。確保將設備應用程序部署到配置了郵件帳戶的仿真器或設備上。單獨啟動每個 項目。在“Solution Explorer”(解決方案資源管理器)中,右鍵單擊設備項目的具體項,然後選擇 “Debug”(調試)|“Start new instance”(啟動新實例)。對桌面項目重復此步驟。設備應用程序看 上去應類似於圖 8 所示。
圖 8 發送消息 繼續進行操作並將您的 Exchange 電子郵件地址輸入到各應用程序的“收件人:”字 段中。鍵入消息並單擊“發送”。然後等待該消息出現在其他應用程序中,在此期間,讓我來解釋一下將 要發生的細節以及如何進行觀察。
任何一個應用程序發送消息時,所發送的文本字符串都被納入 SOAP 封裝中並存到發件箱內。 Exchange 提取該消息並將其發送到收件人的收件箱中(在本例中,是您自己的收件箱)。每個應用程序 都輪詢您的收件箱,查看是否有與通道名稱匹配的消息。發現了所等待的消息後,應用程序將下載該消息 並從服務器中將其刪除(或將其移到“已刪除郵件”文件夾中),然後對其進行處理,通過反序列化操作 從封裝的正文標記得到字符串並將其附加到會話歷史文本框中。
如果想知道經過 SOAP 處理過的電子郵件是什麼樣的,您可以檢查您的發件箱或收件箱(在應用程序 發現並刪除它之前)或查看“已刪除郵件”文件夾(在應用程序已經處理了消息後)。
在編寫或配置此應用程序時可能會遇到許多容易出錯的事情。如果消息沒有出現在預期位置,請在 Outlook 或 Pocket Outlook 中檢查“收件箱”和“發件箱”文件夾,看它們是否正在發送和/或接收過 程中。如果消息雖然在 Pocket Outlook 發件箱或 Outlook 收件箱中但卻不同步,請檢查設備是否已連 接到 Exchange 郵件帳戶,並強制設備上的 ActiveSync 發送和接收消息。如果消息看上去已經發送到相 應的收件箱中,但應用程序並沒有提取它們,請檢查發送和接收消息的通道名稱是否匹配正確。
由於用來接收消息的方法在後台線程中運行,因此您的 UI 看不到用來中止該線程的任何例外。有可 能因某些部分出錯而導致應用程序不再偵聽消息。檢查調試器“Output”(輸出)窗口中的例外堆棧跟蹤 ,看是否有不正常之處。您可能需要使用調試器來查看是否仍有線程在等待消息。在商業應用程序中,可 能需要在此後台線程中加入例外處理和診斷記錄。
調用 WCF 服務
.NET Compact Framework 3.5 默認不支持 WCF 服務模型(此模型可支持托管和調用服務,而無需發 送和接收消息的所有探測代碼)。但是借助正確的幫助器類,在 ASP.NET Web 服務、WCF 服務及其他 WSDL 兼容服務中使用 Compact WCF 來調用 WebMethods 像調用任何方法一樣,都非常容易。
.NET Compact Framework 3.5 PowerToys 附帶了一個名為 NetCFSvcUtil.exe 的工具,可以為您生成 這些服務代理類。由於 NetCFSvcUtil.exe 沒有包括在 Visual Studio 2008 中,因此您需要自行下載 Power Toys for .NET Compact Framework 3.5,網址為 msdn2.microsoft.com/aa497280。
因為 .NET Compact Framework 3.5 附帶了可在桌面中找到 WCF 的綁定子集,所以服務必須提供一個 使用 .NET Compact Framework 所支持的綁定的端點。您也可以使用 Compact WCF 編寫自己的綁定,但 這已超出了本文的范圍。
現在讓我們在桌面上構建一個 .NET Compact Framework 兼容的 WCF 服務,然後編寫一個應用程序, 使用 NetCFSvcUtil.exe 所生成的代理類來調入該服務。對於服務應用程序,在 Visual Studio 2008 中 創建一個新的 WCF 服務網站,並確保“New Web Site”(新網站)對話框右上角的 .NET Framework 3.5 被選中。為簡便起見,我們繼續執行操作並接受 Visual Studio 2008 模板給出的 WCF 服務而不做任何 改動。它會給出自動創建的 GetData 和 GetDataUsingDataContract 方法。
由於 .NET Compact Framework 3.5 不支持 wsHttpBinding,因此需要將 Visual Studio 創建的默認 端點改為 basicHttpBinding 或為該類型添加一個新端點。由於利用 WCF 可以很方便地提供多個端點, 而且 wsHttpBinding 為那些支持它的應用程序提高了安全性,因此我們將保留它不動並添加一個使用 basicHttpBinding 的新端點。
打開 WCF 服務的 web.config 文件。找到 <endpoint> 標記並添加一個新行,其中包括 basic 地址和 basicHttppBinding 綁定:
<!-- Service Endpoints -->
<endpoint address="" binding="wsHttpBinding" contract="IService"/>
<endpoint address="mex" binding="mexHttpBinding"
contract="IMetadataExchange"/>
<endpoint address="basic" binding="basicHttpBinding"
contract="IService"/>
在“Solution Explorer”(解決方案資源管理器 )中,右鍵單擊 Web 項目的 Service.svc 項目項,然後在“Browser”(浏覽器)中單擊“View”(查 看)來檢查您的服務。
請注意,BasicHttpBinding 引入了一些對服務功能的限制。例如,它不支持雙向服務契約、事務處理 和回調。如果試圖使用 BasicHttpBinding 端點來提供使用其中任何功能(或一些其他功能)的服務,會 收到從 WCF 發出的運行時錯誤。
在您的服務中還有一個限制,它所帶來的局限性要超過只使用 BasicHttpBinding 帶來的局限性:即 NetCFSvcUtil.exe 不支持自定義標頭。如果服務需要在消息中包括自定義標頭,則需要修改由工具生成 的代碼或編寫自己的代理類來添加對這些自定義標頭的支持。
現在將一個 .NET Compact Framework 應用程序添加到您的解決方案中。使用“智能設備項目”模板 創建一個新項目並將其命名為 NetCFClient。將下列 Compact WCF 程序集添加到您的項目引用中: System.ServiceModel.dll 和 System.Runtime.Serialization.dll。然後在 Form1.cs 設計圖面中添加 一個按鈕,並將其標題設置為 "Call Service"。雙擊此按鈕,創建一個事件處理程序。現在 只需要一個代理類即可調用服務。
創建代理類
針對該服務運行 NetCFSvcUtil.exe 工具,生成要包括在設備項目中的代理類(參見圖 9)。如果要 使生成的源文件進入設備應用程序的源目錄,可在此處運行此工具。此工具(在安裝後)位於 % PROGRAMFILES%\Microsoft .NET\SDK\CompactFramework\v3.5\bin 目錄下,但如果是在 64 位的機器上 運行,則查看 Program Files (x86) 目錄。
Figure 9 生成代理類
C:\demos\MsdnCFServiceSample\NetCFClient>"\Program Files (x86) \Microsoft .NET\SDK\CompactFramework\v3.5\bin\NetCFSvcUtil.exe"
http://localhost:53222/Service/Service.svc?wsdl
Microsoft (R) .NET Compact Framework Service Model Metadata Tool
[Microsoft (R) Windows (R) Communication Foundation, Version 3.5.0.0]
Copyright (c) Microsoft Corporation. All rights reserved.
Attempting to download metadata from 'http://localhost:53222/Service/Service.svc?wsdl' using WS-Metadata Exchange or DISCO.
Generating files...
C:\demos\MsdnCFServiceSample\NetCFClient\Service.cs
C:\demos\MsdnCFServiceSample\NetCFClient\CFClientBase.cs
C:\demos\MsdnCFServiceSample\NetCFClient>
要運行此工具,只需將 URL 傳入服務元數據即可(服務必須正在運行)。如果希望此工具生成 Visual Basic項目的代理類,可添加 /language:vb。請注意,在命令行傳遞給此工具的 URL 將作為設備 用來聯系服務的 URL,因此 http://localhost 可能會生成代理,但設備應用程序會嘗試與其自身進行對 話而不是聯系台式計算機。
還要注意,此工具不會像桌面 svcutil.exe 工具那樣生成 output.config 文件。.NET Compact Framework 3.5 不支持通過配置文件對 WCF 進行配置,因此服務的所有端點信息都需要在代碼中。 ASP.NET 開發服務器不會響應任何設備請求,除非設備或仿真器已插入。我在這裡執行的操作是使用 localhost 來生成代理,然後查找在 ServiceClient 類中定義的 EndpointAddress 字段並將 URL 改為 使用我的主機名(服務地址)而不是本地主機,以此來修改生成的 Service.cs 文件。代碼看上去應類似 於:
public static System.ServiceModel.EndpointAddress EndpointAddress =
new System.ServiceModel.EndpointAddress(
"http://mycomputername:53222/Service/Service.svc/
basic");
此外,還要注意 URL 的 /basic 後綴。客戶端將通過這種方式來指明在所 提供的三個端點中該服務要使用哪一個。生成了代理類源文件後,只需在 Visual Studio 中將其添加到 設備項目中即可。
使用 WCF 服務
在前面您已經為單個的“調用服務”按鈕創建了一個事件處理程序。現在讓我們來實現它。該代碼看 上去類似於從 WCF 桌面應用程序調用 WCF 服務:
void callServiceButton_Click(object sender, EventArgs e) {
ServiceClient client = new ServiceClient();
MessageBox.Show(client.GetData(5));
}
部署並運行設備應用程序。單擊“調用服務”按鈕。片刻後會彈出一個消息框,其中顯 示“You entered:5”(參見圖 10)。
圖 10 利用 WCF 服務運行應用程序
如果收到錯誤消息提示無法訪問主機,請嘗試插入設備。如果您的設備是仿真器,可在 Visual Studio 中通過“設備仿真器管理器”來插入它。
此工具生成的代理類將方法調用轉換到 WCF 消息中,然後使用 .NET Compact Framework 消息傳送層 進行發送。響應消息隨後被反序列化並通過該方法的返回值傳回應用程序。
NetCFSvcUtil 工具生成的代碼與在完整版本的 WCF 下生成的代碼一樣,都可以正常運行,此時從 WCF 類衍生的代理類被稱為 ClientBase<T>。由於 .NET Compact Framework 3.5 沒有附帶 ClientBase<T>,因此 NetCFSvcUtil 工具會為必須包括在應用程序中的 CFClientBase<T> 生成代碼。盡管此類與在桌面 WCF 中找到的 ClientBase<T> 類所執行的函數相似,但是它們並不 完全相同,在將來的版本中可能會改動。
本文配套源碼