相关文章 第五回. WCF Mobile(Part 2)
摘要对于Windows Mobile来说,WCF是一个崭新的概念,在.NET CF v3.5中提供了对WCF的支持,它是桌面WCF(Windows Communication Foundation,也称作Indigo)的一个子集。本文阐述了Compact WCF的功能和模型以及如何使用WCF轻松创建通信程序。KeywordsWindows Mobile, WCF, .NET CF, Web Service, BasicHttpBinding ,C#
本文是上一篇随笔的第二部分。本文主要阐述如何使用Compact WCF的HTTP Transport的消息模型在我们的应用程序中进行实际的开发。如果您对WCF for .NET CF还没有概念的话推荐您先看看之前。
WCF for .NET CF-- BasicHttpBinding
上一篇随笔介绍了Compact WCF的一些背景知识,下面我们就通过一个例子来看看WCF For.NET CF的Message工作流程。这个例子采用的是BasicHttpBinding的方式,清晰地展示了该绑定方式下的应答式服务模型。不过非常建议您先看看之前的这两篇随笔作为热身:
Ø
Ø
前面已经提到过,BasicHttpBinding的方式还是比较好理解的,因为它跟传统的Web-Service是如此地类似(本质上其实就是调用WebService来实现的)。如图所示:你的应用程序通过WiFi,有线同步网络或者移动运营商的网络向WCF服务发出一个SOAP消息形式的HTTP请求,并等待服务端的HTTP响应。与Email存储转发的传输方式(见)不同的是,BasicHttpBinding这种应答模型使得服务端仅在客户端发送一个请求的条件下才会发送消息,而不会是客户端一旦在线就主动发送,事实上这种方式下服务端也并不关心客户端的任何状态。
好了,现在我们可以开始试着coding了。
打开VS,创建一个的Visual C#工程(我这里是用一个Windows控制台应用的工程),命名为WCFServer。先为项目添加以下引用:
Ø System.ServiceModel.dll
Ø System.XML.dll
Ø System.Runtime.Serialization.dll
(在后面的代码中你将会看到他们的作用)
在中已经提到,所谓的Message是WCF服务的核心纽带,那么这里我们首先要定义一个我们自己的Message对象,姑且把它叫做消息体对象吧。右键你的项目,添加一个新的类,命名为TransmittedObject,内容如下:
namespace WCFServer { [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(Namespace = http://Microsoft.ServiceModel.Samples)] public class TransmittedObject { [System.Xml.Serialization.XmlElementAttribute(Order = 0)] public string StrContent; [System.Xml.Serialization.XmlElementAttribute(Order = 1)] public int Count; } } 这个可以XML序列化的类就是我们的消息体对象,为了简便,这里只给它两个字段。
接下来,理所当然的,我们需要一个用来在服务两端对这个消息对象进行序列化/反序列化的工具。于是,我们还得再添加一个新类,命名为XMLSerialHelper。首先添加以下using目录:
Ø using System.Xml.Serialization;
Ø using System.Xml;
Ø using System.Runtime.Serialization;
然后让我们的XMLSerialHelper继承自XmlObjectSerializer这个抽象类,并对它做一点点扩展。
Tips:如果你使用的是Visual Studio的集成开发环境,键入如图这样继承一个抽象类的代码时:Visual Studio会在抽象类下面标记来提示您操作,这时只需点击小标记,会有一个“Implement XmlobjectSerializer”的选项,我们选中它,VS会为我们生成需要实现的代码段。
这个类完整的代码如下:
public sealed class XMLSerialHelper:XmlObjectSerializer { Private Members#region Private Members XmlSerializer serializer; String defaultNameSpace; Type objectType; #endregion 我们扩展的构造器重载#region 我们扩展的构造器重载 public XMLSerialHelper(Type type) : this(type, null, null) { } public XMLSerialHelper(Type type, string name, string ns) { this.objectType = type; /**//* * 为指定类型实例化一个XmlSerializer * 并指定存放Xml元素的命名空间 * 下面的类似 */ if (!String.IsNullOrEmpty(ns)) { this.defaultNameSpace = ns; this.serializer = new XmlSerializer(type, ns); } else { this.defaultNameSpace = ""; this.serializer = new XmlSerializer(type); } } #endregion 我们实现的方法#region 我们实现的方法 public override object ReadObject(XmlDictionaryReader reader) { string readersNS; readersNS = (String.IsNullOrEmpty(reader.NamespaceURI)) ? "" : reader.NamespaceURI; if (String.Compare(this.defaultNameSpace, readersNS) != 0) { this.serializer = new XmlSerializer(this.objectType, readersNS); this.defaultNameSpace = readersNS; } return (this.serializer.Deserialize(reader)); } public override void WriteObject(XmlDictionaryWriter writer, object graph) { this.serializer.Serialize(writer, graph); } #endregion 待实现的方法(暂不用)#region 待实现的方法(暂不用) public override bool IsStartObject(XmlDictionaryReader reader) { throw new NotImplementedException(); } public override object ReadObject(XmlDictionaryReader reader, bool verifyObjectName) { throw new NotImplementedException(); } public override void WriteEndObject(XmlDictionaryWriter writer) { throw new NotImplementedException(); } public override void WriteObjectContent(XmlDictionaryWriter writer, object graph) { throw new NotImplementedException(); } public override void WriteStartObject(XmlDictionaryWriter writer, object graph) { throw new NotImplementedException(); } #endregion} 提示:上面两个类是服务双方(Client&Server)都要用到的,以维护同一类型的消息对象。待会儿您可能要Copy这两个类到你的客户端工程中去。
好了,现在可以开始写WCF服务的HOST了:
你需要添加以下两个引用先
Ø using System.ServiceModel.Channels;
Ø using System.ServiceModel;
服务的Main函数如下:
static void Main( string [] args) { //构建序列化消息体所需对象 XMLSerialHelper xmlSerialHelper = new XMLSerialHelper(typeof(TransmittedObject)); //按BasicHttpBinding的方式构建传输信道 BasicHttpBinding binding = new BasicHttpBinding(); BindingParameterCollection parameters = new BindingParameterCollection(); IChannelListener<IReplyChannel> listener = binding.BuildChannelListener<IReplyChannel>(new Uri("http://localhost:10008"), parameters); //启用Channel Listener listener.Open(); //接受请求 IReplyChannel channel = listener.AcceptChannel(); //打开响应信道 channel.Open(TimeSpan.MaxValue); Console.WriteLine("****************************************"); Console.WriteLine("Server is running at: "+listener.Uri.ToString()); Console.WriteLine("Press <ENTER> to terminate"); Console.WriteLine("****************************************"); //接受Message,如果没有消息就等待 RequestContext rContext = channel.ReceiveRequest(TimeSpan.MaxValue); //获得消息体 TransmittedObject to = rContext.RequestMessage.GetBody<TransmittedObject>(xmlSerialHelper); Console.WriteLine(to.StrContent+" | from Freesc"); //处理Message ProcessMsg(to); //以Message的形式发送回应. Message replyMessage = Message.CreateMessage(MessageVersion.Soap11, "urn:test", to, xmlSerialHelper); rContext.Reply(replyMessage, TimeSpan.MaxValue); Console.ReadLine(); channel.Close(); } // 处理消息 private static void ProcessMsg(TransmittedObject tranObj) { if (tranObj != null) { tranObj.StrContent = tranObj.StrContent + " fox23"; tranObj.Count = tranObj.Count + 1; }} WCF
的HOST
相对比较简单,应该说是很少很简单但是很好很强大。WCF
与传统的Web Service
相比一个主要优势就是,在WCF
运行时的支持下,如果你的代码得当,你完全可以让你的WCF
服务跑在任何进程上,而不像原来总是需要依赖IIS
。比如,你可以创建一个Windows
服务或者一个简单的Winform
的应用程序或者控制台程序等等,然后像本文中这样在你的程序中创建信道,监听端口,响应请求,不需要IIS
介入。 至此,我们的服务端写好了已经,运行一下会看到以下输出: Tips如果您跟我一样是Windows Vista的用户,而且跟我一样不嫌UAC麻烦的话,您可能需要在项目所在文件夹下找到刚刚编译出来的可执行程序,右键à以管理员身份运行,不然可能会出现权限问题。
下面来看移动设备部分。在设备端我们首先要搞清楚的是在Compact WCF中,并不支持用配置文件来定义请求发往的地址以及绑定行为,这点与完整版的WCF不同。这就意味着我们可能需要在设备端的工程中引用包含消息对象和帮助序列化的类库或者那个工程。
然后编码定义信道,发送请求,等待响应……
我们首先为原来的解决方案添加一个VC#-->Smart Device的工程,为了方便,我们直接把原先写好的消息对象和用于序列化的类Copy过去(别忘了把他们的命名空间改成刚创建的智能设备工程的命名空间),并在工程中添加对以下项的引用:
Ø using System.Runtime.Serialization;
Ø using System.ServiceModel;
现在来看看背后主要用到的代码:
public void TestMessage() { //创建消息体 TransmittedObject wcfMsg = new TransmittedObject(); wcfMsg.StrContent = "hello"; wcfMsg.Count = 0; //创建序列化器 XMLSerialHelper wrapper = new XMLSerialHelper(typeof(TransmittedObject)); //创建WCF消息对象 Message m = Message.CreateMessage(MessageVersion.Soap11, "urn:test", wcfMsg, wrapper); // 创建一个BasicHttpBinding模式的信道 BasicHttpBinding binding = new BasicHttpBinding(); BindingParameterCollection parameters = new BindingParameterCollection(); IChannelFactory<IRequestChannel> channelFactory = binding.BuildChannelFactory<IRequestChannel>(parameters); channelFactory.Open(); //从指定的服务节点创建并打开信道 IRequestChannel outChannel = channelFactory.CreateChannel( new EndpointAddress( new Uri(http://192.168.0.232:10008))); outChannel.Open(TimeSpan.MaxValue); //发送消息并等待回应. Message reply = outChannel.Request(m, TimeSpan.MaxValue); //获取并处理返回的消息体 TransmittedObject to1 = reply.GetBody<TransmittedObject>(new XMLSerialHelper(typeof(TransmittedObject))); MessageBox.Show(to1.StrContent + " | from Server"); //别忘了释放资源 m.Close(); reply.Close(); outChannel.Close(); channelFactory.Close(); } Tips:代码很简单,不过还有更简单的办法,就是像中提到的使用中的小工具为我们生成上述一开始写的那两段代码。这里我们采用人工编写是为了更清晰的反映事情的经过。 好了,现在来测试一下吧,先让服务端跑起来,再从设备端发送消息。程序运行效果如下: 示例代码 下一部分我们将通过另外一种方式(Email Transport)来实现类似的功能。 Enjoy! ©Freesc Huang 黄季冬<fox23>@HUST 2008/3/30