数据交换=>Windows_Mobile+WCF+Exchange2007 - part1

相关文章

数据交换=>Windows_Mobile+WCF+Exchange2007 - part2

 

 

数据交换=>Windows_Mobile+WCF+Exchange_2007

 

摘要:WCF作为一个新的特征在.Net Compact Framework中得到实现。在中文MSDN的网站上,你可以找到很多相关的栏目都已经更新。其中有一些我在之前的文章中已经提及,本文将基于Exchange2007继续探讨WCF Compact特有的消息存储和转发特性(Store and Forward messaging)。

Keywords:

WCF,Windows Mobile,.Net Compact Framework,Exchange Server 2007,Virtual Earth

 

 

 (本文英文原文由marteaga发表于Opennetcf Community 原题为“Exchanging Data using Windows Mobile, Windows Communication Foundation, .NET Compact Framework and Exchange 2007”)

 

  • 前言

之前我写了一些有关WCF for .Net Compact Framework的文章, 也许您可以先看看以下几篇来预热:

WCF Mobile--Part 1

WCF Mobile--Part 2

WCF Mobile--Part 3

不过如果您时间紧,只是单纯想快速的上手做实际的开发的话,推荐您先看这一篇:

WCF for .NET CF 快速入门

 

本文结合项目实例阐述了.Net CF,WCF,和Exchange 2007结合的强大魅力, 和本文配套的两个例程你可以在文章末尾找到下载。 第一个例子是一个典型的Line of Business(LOB)的应用,使用消息分派模式(dispatcher)用一个中央调度器来指挥现场工人处理调度器发送的请求。第二个示例是一个p2p的应用,用户可以通过他们的Windows Mobile移动设备来共享各自拍摄的相片。第二个示例中用到的技术当然也可以很轻松的移植到其他一些类似的商业和企业级的应用中去(诸如文件共享与协同)。

 

  • 开发环境/工具
基本资源:

  • Visual Studio 2008
  •  Exchange 2007

移动应用:

  • .NET Compact Framework 3.5
  • Windows Mobile 5.0 or later

桌面应用:

  • .NET Framework 3.5
  • .NET Compact Framework 3.5 SDK

  • Why E-Mail?

如果你在前面提到的那些文章中已经介绍过Compact WCF中特有的Email-Transport了。你也许会问,为什么要使用Email来传送数据呢?为什么不用ASP.NET Web service,然后让设备连接到这个Web service呢?答案很简单,那就是设备的定址问题。移动设备是“移动”的,它们不能保证一个持续可靠的连接。即便是无线AP“泛滥”的公司,也总会有一些盲点。而且很多情况下你的移动设备总是在运营商网络和企业局域网之间切换,通信的稳定性和设备地址的稳定性都得不到保障。因此,考虑使用Email这种唯一标识来为移动设备定址是有一定依据的。现在许多人都有了自己的Email许多企业也已经通过部署Exchange server实现了Direct Push Email。在我们自己的应用程序中利用这一机制并不需要改变现有的这些基础架构,你可以轻松利用Exchange Direct Push Email来投递你的应用程序的数据,不论用户是在防火墙内或外。
 

 

  • 怎么做到的?

要使用WCF和Exchange,我们需要为我们的设备端和桌面应用程序各自添加以下几个程序集:

  • 设备端工程
  1. Microsoft.ServiceModel.Channels.Mail (.NET Compact Framework version)
  2. Microsoft.ServiceModel.Channels.Mail.WindowsMobile
  3. System.Runtime.Serialization
  4. System.ServiceModel
  • 桌面端工程
  1. Microsoft.ServiceModel.Channels.Mail (.NET Framework version)
  2. Microsoft.ServiceModel.Channels.Mail.ExchangeWebService
  3. System.Runtime.Serialization
  4. System.ServiceModel

 

需要注意的是,这里的Microsoft.ServiceModel.Channels.Mail.dll 程序集是两个不同的程序集,一个是设备上使用的,另一个是为桌面端提供编程接口的, 我们在后面内容中会详细介绍。这里我们还设计了两个helper class来方便我们在设备和桌面应用之间使用邮件信道。他们是:WCFMessagingManager和XmlSerializerWrapper。


XmlSerializerWrapper

要想序列化/反序列化你的数据, WCF需要继承 XmlObjectSerializer. 完成的.Net Framework中可以使用 DataContractSerializer不过这个类在 .NET Compact Framework中是不被支持的. 所以我们需要自己创建一个 XmlSerializerWrapper 类 继承自XmlObjectSerializer而且包含一些XmlSerialzier相关的成员变量:

 

     public class XmlSerializerWrapper : XmlObjectSerializer

    {

    XmlSerializer m_serializer;

    ....

    }

 

在构造器中, 我们为需要序列化的类型new一个XmlSerializer:

 

    public XmlSerializerWrapper(Type type)

    {

    m_serializer = new XmlSerializer(type);

    }

 

然后还需要覆载WriteObject()和ReadObject()方法, WCF 通过调用这两个方法来序列化/范序列化我们的对象:

 

    public override void WriteObject(XmlDictionaryWriter writer, object graph)

    {

    m_serializer.Serialize(writer, graph);

    }

 

    public override object ReadObject(XmlDictionaryReader reader)

    {

        return m_serializer.Deserialize(reader);

    }

 

有关XmlObjectSerializer,和XmlSerializer的更多信息,请参考:Using the XmlSerializer as an XmlObjectSerializer with WCF.

 

WCFMessagingManager

 

WCFMessagingManager 是为了方便开发者使用WCF发送和监听消息而设计的一个类。它继承自Messaging类,你可以在这篇 MSDN article中找到它。该文介绍了如何利用.NET CF和Windows Mobile创建一个聊天系统。WCFMessagingManager 类是一个很宽泛的类,为不同类型对象的消息传递提供了灵活性。比如在这次的Dispatch 应用中,我们使用的是DispatchMessage 类型而后面的图片共享的应用中我们使用的是PhotoData类。 一会儿在后文中会详细介绍。

 

 创建一个WCFMessagingManager

要发送我们的数据,我们需要创建我们的mailBinding对象。在桌面的应用中,我们象这样创建一个ExchangeWebServiceMailBinding 对象:

 

    ExchangeWebServiceMailBinding binding = new ExchangeWebServiceMailBinding(

newUri(Properties.Settings.Default.ExchangeServer),

newNetworkCredential(Email, Password)

);

 
在设备端,我们需要创建一个WindowsMobileMailBinding对象:

 

    WindowsMobileMailBinding binding = newWindowsMobileMailBinding();

 

你可能会注意到在桌面程序中,你必须设定Exchange Server的地址和你Email的网络账户。这些数据是用于查询邮箱发送给特定信道名称的的消息。而在Windows Mobile端,你不必提供身份认证,因为WindowsMobileMailBinding 监视的是设备本地outlook的收件箱。

 

要使用这个WCFMessagingManager类,我们需要创建一个实例并传入相应参数:

 

    WCFMessagingManager<PhotoData> m_messagingManager;

    m_messagingManager = new WCFMessagingManager<PhotoData>(binding,Settings.Default.IncomingChannel);

 

在创建WCFMessagingManager的时候, 我们需要显式地设置PhotoData作为我们绑定的消息对象的类型。在构造器的参数中我们需要提供MailBindingBase这个基类(WindowsMobileMailBinding和ExchangeWebServiceMailBinding都继承于它) ,以及我们要监听的信道,每一个信道都应该是和用户唯一对应的。不过在发送的时候,你可以采取同一个Email来发。

 

在WCFMessagingManager的构造器中,我们首先创建了一个使用Mailbinding的IChannelFactory:


 

 

    //为输入输出的信道创建一个参数集合

    BindingParameterCollection param = new BindingParameterCollection();

    //生成信道工厂

    m_channelFactory = m_mailBinding.BuildChannelFactory<IOutputChannel>(param);

    m_channelFactory.Open();

 

 

我们用之前传入的MailBindingBase 对象来生成一个IChannelFactory,并打开。然后我们需要创建一个监听信道来监听收到的消息:

 

    //生成channelListener

    m_channelListener = m_mailBinding.BuildChannelListener<IInputChannel>(

    MailUriHelper.CreateUri(m_channelNameListen, ""), param);

 

使用的信道名称就是这里传入的channelNameListen

 

发送消息

WCFMessagingManager提供了下面的方法来使用Email的方式发送信息:

 

    ///<summary>

    /// Sends a message to the receiving end

    ///</summary>

    ///<param name="recipient"></param>

    ///<param name="body"></param>

    public void SendMessage(string recipient,string channel, T body)

    {...}

 

 

SendMessage方法, 需要三个参数: 

recipient, 是标识目标(收信方)Email地址

channel , 是目标所监听的信道

body ,是消息体对象. 注意这里的body是一个灵活的泛型对象.前面已经提及.

 

SendMessage的实现也是很直观的: 

 

    //使用channelfactory创建一个outputchannel

    IOutputChannel outputChannel = m_channelFactory.CreateChannel(

    new EndpointAddress(MailUriHelper.Create(channel, recipient)));

     

    //打开信道

    outputChannel.Open();

     

    //创建待发送的消息对象

    Message message = Message.CreateMessage(

        MessageVersion.Default, UrnInternal,

        body, m_xmlSerializerWrapper);

     

    //发送消息

    outputChannel.Send(message);

     

    //关闭发送信道

    outputChannel.Close();

 

 

消息发出后, 你会在收件箱中得到一条信息,并带有象下面这样的加密的标题

SM:v=3.5;CN=treochannel;ID=cbb5d3c798c84af3b1daf7615b780fb3;SD=633472436040000000;

 实际上完整的消息是这样的:

    <s:Envelopexmlns:a="http://www.w3.org/2005/08/addressing"xmlns:s="http://www.w3.org/2003/05/soap-envelope">

        <s:Header>

            <a:Actions:mustUnderstand="1">urn:photoMessage</a:Action>

            <a:Tos:mustUnderstand="1">net.mail://treochannel/#marteaga@opennetcf.com</a:To>

        </s:Header>

        <s:Body>

        </s:Body>

    </s:Envelope>

 

这段XML由System.ServiceModel.Channels.Message 自动创建.

在后面提到的PhotoData 对象中,下面的消息信息将会被添加到消息体"<body>"标签中。

 

    <PhotoDataxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:xsd="http://www.w3.org/2001/XMLSchema">

        <Id>9873fd22-189f-4525-97a5-4572dbcd7ad0</Id>

        <FileName>/cf/200806/WCF Windows Mobile Exchange and NETCF_files/img002.jpg</FileName>

        <Latitude>43.6919</Latitude>

        <Longitude>-79.5782</Longitude>

        <FileSize>6776</FileSize>

        <Base64Data>...</Base64Data>

    </PhotoData>

 

监听消息

WCFMessagingManager提供了一个BeginListening方法来启动一个单独的线程,在后台监听接收信息。 监听线程的主要实现方法如下:

 

    //打开信道监听

    m_channelListener.Open();

     

    //接受并打开请求的信道

    m_inputChannel = m_channelListener.AcceptChannel();

    m_inputChannel.Open();

     

    Message message;

 

    //调用IInputChannel.Receive将会阻塞当前线程直至接收到一条信息

    while (true)

    {

        message = m_inputChannel.Receive();

        if (message == null)

            break;

        try

        {

            T body = message.GetBody<T>(m_xmlSerializerWrapper);

            OnIncomingMessage(body);

        }

        catch (Exception e)

        {

            //调用异常处理

            OnListenException(e);

        }

    }

 

这里我们首先打开了在WCFMessagingManager 构造器中创建的IChannelListener 。然后我们使用IChannelListener 创建了一个IInputChannel 。然后我们在一个while循环中调Receive()方法。

当接收到一条消息的时候,我们用GetBody来获取消息体对象(传入我们的XmlSerializerWrapper),并触发接收消息的事件处理程序。

WCFMessagingManager 还提供了StopListening()方法来停止监听,它将关闭所有通信信道和线程。

 

 WCFMessagingManager 的事件

WCFMessagingManager定义了两个事件,在设备端或者桌面端接收到一条新消息的时候,用户可以从它们得到通知:

 

    ///<summary>

    /// 收到新消息时触发

    ///</summary>

    public event IncomingMessageEventHandler IncomingMessage;

 

    ///<summary>

    /// 监听线程出错时发生

    ///</summary>

    public event ExceptionEventHandler ListenException;

 

下面的应用程序中我们会用到这两个事件.

 

代码的跨平台共享

一种在Windows Mobile和桌面应用程序间共享代码的方式就是生成一个基于.NET Compact Framework的程序集. 由于.Net Compact Framework的程序集是可以不需要多次编译就可以跨平台复用的(因为它和完整版的.Net Fx有许多同样的托管API). 不幸的是, 在WCF的应用程序中,我们并不能这么做,因为.Net Framework和.Net Compact Framework中关于Microsoft.ServiceModel.Channels.Mail.dll的实现是完全不同的.

不过幸运的是,我们可以在这两个不同工程中(WM和PC)引用同样的代码文件 .下图显示了Photo Sharing的应用程序的文件结构:


 

  •  消息分派的应用场景

Dispatch应用于一个中央调度器发送消息请求给现场服务工人的场景. 这个示例解决方案是通过Exchange Server 2007从桌面应用程序向Windows Mobile的应用程序发送和接收消息。


桌面部分的实现

桌面的应用是一个中央调度器。它发送新的请求给现场设备。UI很简明:


 

我们定义了一个类来描述我们的发送和接收时的业务对象:

 

    ///<summary>

    /// 通信的消息对象

    ///</summary>

    public class DispatchMessage

    {

        ///<summary>

        /// 消息ID

        ///</summary>

        public Guid Id { get; set; }

     

        ///<summary>

        ///消息当前状态

        ///</summary>

        public DispatchStatus DispatchStatus { get; set; }

     

        ///<summary>

        ///请求的客户名

        ///</summary>

        public string CustomerName { get; set; }

     

        ///<summary>

        ///对请求的描述

        ///</summary>

        public string Request { get; set; }

     

        ///<summary>

        ///目的地址

        ///</summary>

        public string Address { get; set; }

    }

 

发送时DispatchMessage会在XmlSerializerWrapper序列化后被加在前面提到的body标签内。构造一个新的消息过程如下:

DispatchMessage dm = newDispatchMessage();

dm.Address = txtAddress.Text;

dm.CustomerName = txtCustomerName.Text;

dm.DispatchStatus = DispatchStatus.SentToServiceRep;

dm.Id = Guid.NewGuid();

dm.Request = txtRequest.Text;

 

//添加到消息队列中

m_messagesSent.Add(dm);

UpdateListbox();

 

//发送消息

m_messagingManager.SendMessage(txtServiceRep.Text, ChannelNames.ServerChannelName, dm);

 


当IncomingMessage事件发生的时候,我们去更新主窗体的listbox:

   

void m_messagingManager_IncomingMessage(DispatchMessage body)

    {

        this.Invoke(newEventHandler(delegate(object sender, EventArgs ea)

        {

            //find the message in the internal list

            var message = from tmsg in m_messagesSent

            where tmsg.Id.Equals(body.Id)

            select tmsg;

         

            if (message.Count() == 1)

            {

                //Update the dispatch status

                message.First().DispatchStatus = body.DispatchStatus;

            }

            else

            {

                //add the item to the list

                m_messagesSent.Add(body);

            }  

     
            UpdateListbox();

        }));

    }


Windows Mobile设备端的实现

Windows Mobile 的应用程序监听着新的场外客户调度请求,UI还是很简明,可以显示 DispatchMessage的细节:

 

 

当一个消息到达设备的时候,他会自动将状态调整为‘DeviceConfirmReceipt’ :


    void m_messagingManager_IncomingMessage(DispatchMessage body)

    {

        this.Invoke(newEventHandler(delegate(object sender, EventArgs ea)

        {

            this.statusBar1.Text = string.Format("New Message Received for {0}",body.CustomerName);

            Application.DoEvents();

            this.txtAddress.Text = body.Address;

            this.txtCustomerName.Text = body.CustomerName;

            this.txtRequest.Text = body.Request;

            this.cmbStatus.Text = body.DispatchStatus.ToString();

            this.txtId.Text = body.Id.ToString();

            

            if (body.DispatchStatus == DispatchStatus.SentToServiceRep)

            {

                //Respond with a confirm receipt

                 CreateAndSendMessage(txtAddress.Text, txtCustomerName.Text, DispatchStatus.DeviceConfirmReceipt, txtRequest.Text, txtId.Text);

            }

        }));

    }

 

这条状态确认消息将会被显式在桌面的应用中:

 

设备端的应用还可以将回复的消息改为如下几种:

 

 

这样,中央调度器可以随时了解到工作任务的完成情况。

 

下一部分我们来看Photo Share的应用。

 

Enjoy!

 

 黄季冬

 

 

 

 

posted on 2008-08-02 12:12 J.D Huang 阅读(...) 评论(...) 编辑 收藏

统计