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 文件,添加如下配置项:
< 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 Microsoft.Web.Services2;
using Microsoft.Web.Services2.Dime;
5) 添加一个用于下载文件的 Web 方法 DownloadFile()。
public void DownloadFile()
{
SoapContextrespContext = ResponseSoapContext.Current;
DimeAttachmentdimeAttach = new DimeAttachment(
" image/jpg " ,TypeFormat.MediaType,Server.MapPath( " img01.jpg " ));
respContext.Attachments.Add(dimeAttach);
}
6) 再添加一个用于上传文件的 Web 方法 UploadFile()。
public void UploadFile()
{
SoapContextreqContext = RequestSoapContext.Current;
byte []buffer;
using (Streamstream = reqContext.Attachments[ 0 ].Stream)
{
buffer = new byte [stream.Length];
stream.Read(buffer, 0 ,buffer.Length);
}
// 将数据写入磁盘文件中(需要设置相应权限)
using (FileStreamfileStream = 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.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 "
}
DimeAttachmentCollectionrequestAttachments;
DimeAttachmentCollectionresponseAttachments;
// IDimeAttachmentContainer.RequestAttachments
public DimeAttachmentCollectionRequestAttachments
{
get
{
if (requestAttachments == null )
requestAttachments = new DimeAttachmentCollection();
return requestAttachments;
}
}
// IDimeAttachmentContainer.ResponseAttachments
public DimeAttachmentCollectionResponseAttachments
{
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 OpenNETCF.Web.Services2.Dime;
7) 回到 Form1 的窗体设计界面,用鼠标双击 Download 菜单项,并未 mniDownload 添加 Click 事件的处理代码:
{
Cursor.Current = Cursors.WaitCursor;
DimeServWrapsvc = new DimeServWrap();
svc.DownloadFile();
byte []buffer;
using (Streamstream = svc.ResponseAttachments[ 0 ].Stream)
{
buffer = new byte [stream.Length];
stream.Read(buffer, 0 ,buffer.Length);
pictureBox1.Image = new Bitmap(stream);
}
using (FileStreamfileStream = File.OpenWrite( " img01.jpg " ))
{
fileStream.Write(buffer, 0 ,buffer.Length);
}
Cursor.Current = Cursors.Default;
MessageBox.Show( " Filewasdownloadedsuccessful! " );
}
8) 采用同样的方法为 mniUpload 添加 Click 事件的处理代码:
{
Cursor.Current = Cursors.WaitCursor;
using (FileStreamfileStream = File.OpenRead( " img01.jpg " ))
{
DimeServWrapsvc = new DimeServWrap();
DimeAttachmentdimeAttach = new DimeAttachment(
" uuid: " + Guid.NewGuid().ToString( " D " ), " image/jpg " ,
TypeFormatEnum.MediaType,fileStream);
svc.RequestAttachments.Add(dimeAttach);
svc.UploadFile();
}
Cursor.Current = Cursors.Default;
MessageBox.Show( " Filewasuploadedsuccessful! " );
}
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日