WCF中WindowsMobileMailBinding的应用-Mobile中的即时通讯应用程序

来源:互联网 发布:foxmail邮箱端口 编辑:程序博客网 时间:2024/06/05 17:04

 

WCF中WindowsMobileMailBinding的应用

--Mobile中的即时通讯应用程序

本文讨论:
  • .NET Compact Framework 中的邮件传输
  • 编写简单消息传送应用程序
  • WCF 消息传送内幕探测
  • 消耗 WCF Web 服务
本文使用了以下技术:.NET Compact Framework 3.5, Visual Studio 2008
邮件传输 编写 Windows Mobile 应用程序 WCF 消息传送探测 创建并发送消息 编写桌面应用程序 运行示例 调用 WCF 服务 创建代理类 使用 WCF 服务
移动设备的寻址能力问题一直以来都非常棘手,它会使编写从服务器接收推送数据的 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 资源
  • .NET Compact Framework 团队博客
  • .NET Compact Framework 3.5 Redistributable 和 Power Toys
  • 一些与 .NET Compact Framework 开发相关的个人博客:
    • blogs.msdn.com/andrewarnottms
    • blogs.msdn.com/markprenticems
    • blogs.msdn.com/davidklinems
    • jmpinline.nerdbank.net
.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 所示。
应用程序 UI
图 2 应用程序 UI 
由于要使用 Compact Framework 版本的 WCF,因此需要添加一些程序集引用。在“Solution Explorer”(解决方案资源管理器)中右键单击项目的“References”(引用)文件夹,然后单击“Add Reference”(添加引用)。在 .NET 选项卡中,选择以下程序集:
  • System.ServiceModel.dll
  • System.Runtime.Serialization.dll
  • Microsoft.ServiceModel.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 方法中进行编写。
复制代码
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 对其进行序列化)。
复制代码
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 线程上时,只需将传入的消息附加到会话框中,然后确保历史框向下滚动足够的距离以读取最新消息即可。
复制代码
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
图 6 桌面应用程序的基本 UI 
将数个引用程序集添加到桌面项目中,方法与设备项目的添加方法类似。需要引用的程序集包括:
  • System.ServiceModel.dll
  • System.Runtime.Serialization.dll
  • Microsoft.ServiceModel.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)。下一步非常重要:交换设备应用程序中通道名称的顺序。通过交换顺序,可以使桌面接收来自设备的消息,使设备接收来自桌面的消息。如果未交换通道,则两端都在同一个通道上会话而在另一个通道上侦听,因此永远也不会收到任何消息。
复制代码
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 发送消息
图 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) 目录。
复制代码
C:/demos/MsdnCFServiceSample/NetCFClient>"/Program Files (x86)/Microsoft .NET/SDK/CompactFramework/v3.5/bin/NetCFSvcUtil.exe"http://localhost:53222/Service/Service.svc?wsdlMicrosoft (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.csC:/demos/MsdnCFServiceSample/NetCFClient/CFClientBase.csC:/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)。
利用 WCF 服务运行应用程序
图 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> 类所执行的函数相似,但是它们并不完全相同,在将来的版本中可能会改动。
Andrew Arnott 使用 Microsoft 技术进行编程已经有 12 年了,他最喜欢的消遣框架包括 WCF、WPF 和 ASP.NET。目前他是 Microsoft 的一名软件开发工程师,从事 .NET Compact Framework 方面的编程工作。Andrew 与他深爱的妻子 Cheryl 有一个不到一岁的儿子。