在 Windows Mobile 使用 WS-Attachment 传输二进制数据

1. Web Service 和 SOAP

XML Web Service 是通过 SOAP(简单对象访问协议)协议进行通信的,而 SOAP 消息是利用 XML 进行描述的。使用 XML 描述 SOAP 消息的好处是使得 Web Service 可以跨平台调用,成就了 Web Service 的巨大魅力。(关于 Web Service 的介绍请看《XML Web Service 基础》

由于 SOAP 消息是用 XML 进行描述的,如果需要通过 Web Service 传输二进制数据,就必须在传输之前,将二进制数据转换成 Base64 编码的字符串,数据传送到了接收方后,再将 Base64 编码的字符串还原为二进制数据。这样产生了一个问题,经过 Base64 编码后,二进制数据的体积会膨胀,从而影响了数据传输的性能。

2. WSE 和 WS-Attachment

为了解决这个问题,微软在 Web Service Enhancements (WSE) 中实现了 WS-Attachment 规范,从而避免了在 Web Service 传输二进制数据时,需要对二进制数据进行 Base64 编码/解码处理。WS-Attachment 的工作原理是将二进制数据作为 SOAP 消息的附件(类似邮件的附件),而不作为 SOAP 消息的内容进行发送。这样可以避免对二进制数据进行 XML 序列化(XML 序列化过程中会对二进制数据进行 Base64 编码),因为序列化的只是 SOAP 消息的内容。(关于如何使用 WSE 附件请看《Using Web Services Enhancements to Send SOAP Messages with Attachments》

微软发布的 WSE 只支持桌面平台的 .NET Framework,不支持智能设备平台的 .NET Comapct Framework。如果你想在 Windows Mobile 或 Windows CE 平台上使用 WS-Attachment 传输二进制数据,那就要使用 OpenNETCF SDF 库了。OpenNETCF SDF 实现了 WSE2 里面大部分的 WS-* 规范,其中包括 WS-Attachment。为 .NET CF 平台实现这些 WS-* 规范的并非 OpenNETCF,而是 brains-N-brawn。在 OpenNETCF v1.2 之后才加入这部分功能。

3. 在 Windows Mobile 使用 WS-Attachment

我们将通过构建一个上传和下载文件的应用来展示 WS-Attachment 在 Windows Mobile 上如何使用。构建这个应用我们需要实现一个服务器端和一个客户端。服务器端也就是 Web Service 端,可以采用 WSE2 SP3 实现 WS-Attachment;客户端也就是 Windows Mobile 端,采用 OpenNETCF v2.0 实现 WS-Attachment。我们使用 Visual Studio 2005 SP1 开发这个应用。

说到这里,可能对 WSE 比较熟悉的朋友就有疑问了:既然使用 Visual Studio 2005 开发,为什么不用最新的基于 .NET 2.0 的 WSE3,而使用基于 .NET 1.1 的 WSE2 呢?那是因为 WSE3 已经用一种更好的技术代替 WS-Attchment 了,那就是 MTOM。我也很不理解微软为什么不在 WSE3 中保留 WS-Attachment,而是直接用 MTOM 将它替换掉。不过庆幸的是 WSE2 SP3 在 Visual Studio 2005 中依然能够正常工作。

3.1 创建服务器端

1) 打开 Visual Studio 2005,新建一个“ASP.NET Web 服务应用程序”项目,命名为“WSAttachmentService”。如果你找不到这个项目模版,是因为你没有安装 Visual Studio 2005 SP1。

2) 为 WSAttachmentService 项目添加引用,在添加引用对话框的 .NET 选项卡中,选择 Microsoft.Web.Service2 (C:\Program Files\Microsoft WSE\v2.0\Microsoft.Web.Services2.dll),并点击确定

3) 打开 Web.config 文件,添加如下配置项:

<system.web>
  
<webServices>
    
<soapExtensionTypes>
      
<add type="Microsoft.Web.Services2.WebServicesExtension, Microsoft.Web.Services2, Version=2.0.3.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" priority="1" group="0"/>
    
</soapExtensionTypes>
  
</webServices>
</system.web>


4) 打开 Service1.asmx.cs 进行代码编辑,删除默认的 HelloWorld() Web 方法,并引用3个命名空间。

using System.IO;
using Microsoft.Web.Services2;
using Microsoft.Web.Services2.Dime;


5) 添加一个用于下载文件的 Web 方法 DownloadFile()。

[WebMethod]
public void DownloadFile()
{
  SoapContext respContext 
= ResponseSoapContext.Current;
  DimeAttachment dimeAttach 
= new DimeAttachment(
    
"image/jpg", TypeFormat.MediaType, Server.MapPath("img01.jpg"));
  respContext.Attachments.Add(dimeAttach);
}


6) 再添加一个用于上传文件的 Web 方法 UploadFile()。

[WebMethod]
public void UploadFile()
{
  SoapContext reqContext 
= RequestSoapContext.Current; 

  
byte[] buffer;
  
using (Stream stream = reqContext.Attachments[0].Stream)
  {
    buffer 
= new byte[stream.Length];
    stream.Read(buffer, 
0, buffer.Length);
  } 

  
// 将数据写入磁盘文件中(需要设置相应权限)
  using (FileStream fileStream = File.OpenWrite(Server.MapPath("img02.jpg")))
  {
    fileStream.Write(buffer, 
0, buffer.Length);
  }


7) 设置 WSAttachmentService 目录的访问权限,为 Internet 来宾账户添加修改写入权限。



3.2 创建客户端

1) 在当前解决方案新建一个“Windows Mobile 5.0 Pocket PC”智能设备项目,命名为“WSAttachmentMobile”。

2) 打开 Form1.cs 的窗体设计界面,添加一个 PictureBox 控件(pictureBox1)到 Form1 上,再添加 Download 和 Upload 两个菜单项(mniDownload 和 mniUpload)。



3) 为项目添加引用,在添加引用对话框的 .NET 选项卡中,选择 OpenNETCF.Web.Service2 (C:\Program Files\OpenNETCF\Smart Device Framework 2.0\OpenNETCF.Web.Services2.dll),并点击确定

4) 为项目添加 Web 引用,在添加 Web 引用对话框中,输入 URL 地址:http://localhost/WSAttachmentService/Service.asmx,点击地址栏右边的前往按钮,确认 Web Service 是否能够打开,然后点击添加引用按钮。

5) 为项目添加一个类文件 DimeServWrap.cs,代码如下:

using System;
using System.Web.Services.Protocols;
using System.Web.Services.Description; 

using OpenNETCF.Web.Services2.Dime; 

namespace WSAttachmentMobile
{
  public class DimeServWrap : localhost.Service1, IDimeAttachmentContainer
  {
    public DimeServWrap() : base()
    {
      this.Url = "http://bjb-libo/WSAttachmentService/Service1.asmx"
   

    DimeAttachmentCollection requestAttachments;
    DimeAttachmentCollection responseAttachments; 

    // IDimeAttachmentContainer.RequestAttachments
    public DimeAttachmentCollection RequestAttachments 
   
      get 
      {
        if (requestAttachments == null
          requestAttachments 
= new DimeAttachmentCollection();
        return requestAttachments;
      }
    } 

    // IDimeAttachmentContainer.ResponseAttachments
    public DimeAttachmentCollection ResponseAttachments 
    { 
      get 
      {
        if (responseAttachments == null
          responseAttachments 
= new DimeAttachmentCollection();
        return responseAttachments;
      }
    } 

    [DimeExtension]
    [SoapDocumentMethod(
http://tempuri.org/DownloadFile, RequestNamespace = "http://tempuri.org/", ResponseNamespace = "http://tempuri.org/", Use = SoapBindingUse.Literal, ParameterStyle = SoapParameterStyle.Wrapped)]
    public new void DownloadFile()
    {
      this.Invoke("DownloadFile"new object[0]);
    } 

    [DimeExtension]
    [SoapDocumentMethod(
http://tempuri.org/UploadFile, RequestNamespace = "http://tempuri.org/", ResponseNamespace = "http://tempuri.org/", Use = SoapBindingUse.Literal, ParameterStyle = SoapParameterStyle.Wrapped)]
    public new void UploadFile()
    {
      this.Invoke("UploadFile"new object[0]);
    }
  }

DimeServWrap 类做了几件事:
a. 继承了 localhost.Service1 代理类
b. 实现了 OpenNETCF.Web.Services2.Dime.IDimeAttachmentContainer 接口
c. 设置了 Web Service 的 url 地址
d. 覆盖了基类的 DownloadFile() 和 UploadFile() 方法,并为两个方法都加上了 DimeExtension 属性

6) 打开 Form1.cs 代码进行编辑,并引用两个命名空间:

using System.IO;
using OpenNETCF.Web.Services2.Dime; 


7) 回到 Form1 的窗体设计界面,用鼠标双击 Download 菜单项,并未 mniDownload 添加 Click 事件的处理代码:

private void mniDownload_Click(object sender, EventArgs e)
{
  Cursor.Current 
= Cursors.WaitCursor; 

  DimeServWrap svc 
= new DimeServWrap();
  svc.DownloadFile(); 

  byte[] buffer;
  using (Stream stream = svc.ResponseAttachments[0].Stream)
  {
    buffer 
= new byte[stream.Length];
    stream.Read(buffer, 
0, buffer.Length);
    pictureBox1.Image 
= new Bitmap(stream);
  } 

  using (FileStream fileStream = File.OpenWrite("img01.jpg"))
  {
    fileStream.Write(buffer, 
0, buffer.Length);
  } 

  Cursor.Current 
= Cursors.Default; 

  MessageBox.Show(
"File was downloaded successful!");


8) 采用同样的方法为 mniUpload 添加 Click 事件的处理代码:

private void mniUpload_Click(object sender, EventArgs e)
{
  Cursor.Current 
= Cursors.WaitCursor; 

  using (FileStream fileStream = File.OpenRead("img01.jpg"))
  {
    DimeServWrap svc 
= new DimeServWrap();
    DimeAttachment dimeAttach 
= new DimeAttachment(
    "uuid:" + Guid.NewGuid().ToString("D"), "image/jpg",
    TypeFormatEnum.MediaType, fileStream);
    svc.RequestAttachments.Add(dimeAttach); 

    svc.UploadFile();
  } 

  Cursor.Current 
= Cursors.Default; 

  MessageBox.Show(
"File was uploaded successful!");


9) 到现在为止,所有代码已经编写好了,我们来看看解决方案的文件组织结构,检查一下是否有遗漏的地方:

3.3 调试程序

1) 从 Visual Studio 2005 的工具菜单打开“设备仿真器管理”,并连接 CHS Windows Mobile 5.0 Pocket PC Emulator(我这里使用的是简体中文版的设备仿真器镜像,你也可以使用英文版的),最后将其插入底座,使仿真器可以连接到 ActiveSync,这样我们的仿真器就能通过网络访问到桌面电脑的 Web Service 了。

2) 回到 Visual Studio 2005 中,并在设备工具栏选择 Windows Mobile 5.0 Pocket PC Device。

3) 接下来我们可以按 F5 键开始调试了,在部署过程中会安装 .NET Compact Framework 2.0 到仿真器中(如果还没有安装)。

4) 程序启动后,先点击 Download 菜单项,从服务器端下载一个图片文件,并显示在窗体上,同时将图片文件保存到设备上;接着点击 Upload 菜单项,从设备读取刚才下载的图片文件,并上传到服务器端,服务器端将其保存到根目录下。


4. 总结

在 Windows Mobile 应用程序中利用 WS-Attachment 传输二进制数据,可以减少数据传输量,提高数据传输速度,从而增强用户体验。特别是在 GPRS 和 CDMA 的低速网络条件下,如果再结合数据压缩技术,将会取得显著的效果。不过,如果在局域网的环境下,建议不要对数据进行压缩。因为局域网的网络速度足够快,传输大文件和小文件所需要的时间相差不多,而数据压缩则需要更多的时间,所以速度反而会更慢。毕竟 Windows Mobile 设备的硬件性能是无法跟桌面电脑相比。

示例代码下载:WSAttachmentMobile.rar

作者:黎波
博客:http://upto.cnblogs.com/
日期:2007年3月24日

posted @ 2007-03-24 18:53  黎波  阅读(8830)  评论(58编辑  收藏  举报