开发SmartPhone应用程序以及调用WebService

类别:.NET开发 点击:0 评论:0 推荐:
Orange SPV是第一款进入市场的运行Smartphone 2002的移动电话,现在我们可以开始开发Smartphone应用程序了。本文我们建立了一个可以使用ASP.NET Web服务的Smartphone客户端来提供地理信息。本文假定读者有Web服务、ASP.NET和Win32编程的基本知识。

.NET框架组件使我们有了一组类帮助使用Web服务,从分析WSDL文档到自动建立代理类再到程序依赖。这使大多数依靠Web服务的编程比较琐碎,可是对于Smartphone来说简单框架组件(Compact Framework)还不能使用,因此我们需要作其它的选择。

典型的COM能够使用Soap工具包,对Smartphone来说它也不能使用,但是我们将使用它来帮助跟踪调用。

我们可以依靠Web服务编程仍然不使用这些技术,为了实现它,我们需要查看Web服务的格式和传送。在.NET 1.0 Web服务中使用两种标准来通讯:SOAP和WSDL。

Web服务基础

如果没有基于SOAP的API帮助使用Web服务,我们就需要直接处理SOAP消息和WSDL文档指定的格式。请注意使用WSDL使用的好处之一是类型元素中的概要信息。这些信息指定了SOAP正文的格式,启动WSDL察觉客户端(例如.NET)来定义SOAP包可以进行串并转换的类。

对于Smartphone来说,我们必须构造自己的SOAP消息以确保它们可以被Web服务接受。这些消息通过HTTP POST发送到服务器,我们的客户端等待SOAP响应。这需要按次序分析、检查soap错误,返回提取的值。看起来这需要大量的工作,但是在实际中它与Soap 工具包提供的低层API相近。

手动地从纯WSDL构造SOAP消息是复杂的事务(如map.wsdl显示)。这需要定位正确的操作元素,找到输入消息,映射正确类型。如果Web服务用的是ASP.NET,简单的途径是查看产生的asmx页,选择Webmethod,并查看SOAP请求示例(图1)。



图1 从ASP.NET产生的Web服务文档

如果你不能访问这些信息,有另一个机制用于支持WSDL的客户端调用Web服务,并跟踪SOAP包。SOAP工具包提供了一个工具Trace Utility(MsSoapT3.exe),它对调试Web服务很有用。



图2 Trace Utility,来自Soap工具包3.0

Smartphone Web服务

在上面的请求中很容易看出怎样构造SOAP消息。在这个例子中,我建立了一个SoapWriter类提供写SOAP消息的低层功能。

#include "Soap.h" SoapWriter *pSoap = new SoapWriter(); pSoap->StartEnvelope(); pSoap->StartBody(); pSoap->StartElement(L"GetLatLong", L"http://mapmobile"); pSoap->WriteElementString(L"addressLine", L"new bond street"); pSoap->WriteElementString(L"city", L"bath"); pSoap->StartElement(L"postCode"); pSoap->EndElement(L"postCode"); pSoap->WriteElementString(L"country", L"UK"); pSoap->EndElement(L"GetLatLong"); pSoap->EndBody(); pSoap->EndEnvelope(); pSoap->FinalizeSoap();

为了向Web服务发送SOAP请求,我将使用WinInet API。WinInet一般用于HTTP和FTP通讯。它为Internet访问提供了高层次API而没有采用WinSock 编程。幸运的是Web服务使用SOAP调用,在我们的例子中使用HTTP调用。

为了使发送SOAP请求更加容易,我建立了SoapConnector类,它允许我们发送SOAP请求和载入响应信息。一旦连接到了Web服务,客户端就可能作多个调用,使用存取程序检索SOAP响应信息。

#include "Soap.h" SoapWriter *pSoap = new SoapWriter(); //在此处建立SOAP 请求 SoapConnector *pCon = new SoapConnector(); pCon->Init(); //连接到服务器和Web服务 pCon->Connect(L"http://chungw02:8080/mapmobile/map.asmx"); //同SOAP 消息和SoapAction 一起调用 pCon->Invoke(pSoap, L"http://mapmobile/GetLatLong"); //提取响应信息 int iLen = 0; pCon->GetSoapLength(&iLen); TCHAR *pResponse = new TCHAR[iLen+1]; pCon->GetSoap(&pResponse);

客户端接收到的XML文件接着被载入DOM分析器并返回提取的值。本例中,我建立了一个将msxml中的IXMLDOMDocument接口暴露的包装类,这需要COM存在。

#include "Soap.h" //读取SOAP 响应信息 pSoapReader = new SoapReader(); pSoapReader->Init(); //通过传递SoapConnector或者Xml字符串载入SOAP响应信息 pSoapReader->LoadXml(pCon);

一旦它被载入了,可以通过m_pDom成员访问DOM,选择节点值或运行XPath查询。

MSXML::IXMLDOMNode *pNode = NULL; MSXML::IXMLDOMNodeList *pNodeList = NULL; MSXML::IXMLDOMNode *pTextNode = NULL; TCHAR *lpNodeValue = NULL; TCHAR *XPath = new TCHAR[50]; _tcscpy(XPath, L"/soap:Envelope/soap:Body/Node"); VARIANT vNodeVal; HRESULT hr; //使用前面建立的pSoapReader try { //选择节点 hr = pSoapReader ->m_pDom->selectSingleNode(XPath, &pNode); if (FAILED(hr)) __leave; if (pNode == NULL) __leave; //获取子节点 hr = pNode->get_childNodes(&pNodeList); if (FAILED(hr)) __leave; //获取下一个节点 hr = pNodeList->get_item(0, &pTextNode); if (FAILED(hr)) __leave; //获取文本节点值 VariantInit(&vNodeVal); hr = pTextNode->get_nodeValue(&vNodeVal); if (FAILED(hr)) { VariantClear(&vNodeVal); __leave; } //将值指定给lpNodeValue lpNodeValue = TCHAR[SysStringLen(vNodeVal.bstrVal) + 1]; _tcscpy(lpNodeValue, vNodeVal.bstrVal); VariantClear(&vNodeVal); //使用lpNodeValue处理事务 } __finally { if (pNode != NULL) pNode->Release(); if (pNodeList != NULL) pNodeList->Release(); if (pTextNode != NULL) pTextNode->Release(); if (lpNodeValue !=NULL) delete[] lpNodeValue; }

作为选择,我提供了一个辅助方法SelectSingleTextNode来提取文本节点。它分配了TCHAR大小空间来适应文本节点的值。

//注意这些有辅助方法SelectSingleTextNode分配 TCHAR *latitude = NULL; TCHAR *longitude = NULL; //使用前面建立的pSoapReader pSoapReader->SelectSingleTextNode(L"/soap:Envelope/soap:Body/GetLatLongResponse /latitude", &latitude); pSoapReader->SelectSingleTextNode(L"/soap:Envelope/soap:Body/GetLatLongResponse /longitude", &longitude); //使用返回值处理事务 //清除 if (latitude != NULL) delete[] latitude; if (longitude != NULL) delete[] longitude;

即将调用的Web服务返回包含文本节点数据的SOAP消息,因此SelectSingleTextNode方法提供了从Web服务调用中提取数据的一个有用的机制。如果你经常需要提取数据的不同方式,例如属性数据和评估节点集合,SoapReader类可以扩充包含其它的辅助方法。
到此为止SoapWriter、SoapConnector和SoapReader提供了构造、调用和从Web服务读取响应信息的功能。为了用这些类实现客户端,我们需要了解SOAP消息的格式。下面将看到建立Web服务和Smartphone客户端。

MapMobile和MapPoint .NET Web服务

例程包含一个能从Web服务检索地图信息并在Smartphone上的显示的客户端。地理搜索和地图数据由MapPoint .NET提供,它是一个商用Web服务。尽管Smartphone终端用户并没有MapPoint .NET帐号,我建立了一个叫MapMobile的包装Web服务来依靠MapPoint .NET进行调用和认证。对于Smartphone 2002来说没有摘要(digest)认证提供者,因此如果我要直接与MapPoint .NET对话,需要手工实现认证头(header)。包装的其它原因是包含了改变地理数据提供者的能力,而不需要影响终端用户,例如从MapPoint .NET 2.0升级到3.0。



图3. MapMobile结构

我们的包装服务暴露了两个简单的Web服务方法,使客户端能够定位和显示地图。第一个调用返回地址的经度和纬度,第二个调用返回包含匹配的GIF图象的字节数组。

[WebMethod] [SoapHeader("_phoneNumber", Direction=SoapHeaderDirection.In)] public void GetLatLong(string addressLine, string city, string postCode, string country, out double latitude, out double longitude) [WebMethod] [SoapHeader("_phoneNumber", Direction=SoapHeaderDirection.In)] public byte[] GetMap(double latitude, double longitude, double zoom, int width, int height, string tag)

为了防止MapMobile Web服务的无限制调用,所有的调用都包含一个SOAP头,它包含了Smartphone的电话号码。在服务器端,检测头信息是否为有效的数字。如果失败了,SOAP故障将返回客户端。这种认证机制只供例子使用,不禁止有效号码的欺骗。由于提供了很多安全选项,它们依靠你的Web服务,你可以选择不认证,使用数字署名,或使用新全局XML Web服务结构(Global XML Web Services Architecture,GXA)。

SmartMap客户端

客户端应用程序提供了一个输入屏幕用于输入地址。接着调用MapMobile Web服务并检索存储在文件系统中的地图。这使用户可以查看下载的最新地图。



图4. SmartMap用户界面

我们的客户端应用程序包含一个启动屏幕(主窗体类)和三个对话框(输入屏幕、Web服务调用状态和地图显示)。起初启动屏幕显示,跟着显示Find Location对话框。设备电话号码也被提取和保存,用于soap头中。在仿真程序中这些调用不会工作,因此在我们的例子中,如果调用失败就将数字设为1234567890以供测试。

TCHAR *g_PhoneNumber = new TCHAR[40]; SMS_ADDRESS pAddr; //获取电话号码 SmsGetPhoneNumber(&pAddr); _tcscpy(g_PhoneNumber, pAddr.ptsAddress);

当用户选择Search菜单选项,应用程序为MapMobile Web服务构造正确的SOAP调用。这包括包含设备电话号码的SOAP头。

SoapWriter *pSoap = new SoapWriter(); sw->StartEnvelope(); //构造头 sw->StartHeader(); sw->StartHeaderElement(L"MapMobileHeader", L"http://mapmobile"); sw->WriteHeaderElementString(L"PhoneNumber", g_PhoneNumber); sw->EndHeaderElement(L"MapMobileHeader"); sw->EndHeader(); //Soap正文 sw->StartBody(); //此处构造SOAP正文 sw->EndBody(); sw->EndEnvelope(); sw->FinalizeSoap();

这完成后SoapConnector将被载入SoapReader中用于提取64位编码数据。

TCHAR *map64; //分配内存到目标变量 hrSearch = pSoapReader->SelectSingleTextNode(L"/soap:Envelope/soap:Body/GetMapResponse/GetMap Result", &map64);

我们需要不断检查HRESULT,如果SoapReader失败了,就检查响应信息是否包含Soap故障。

TCHAR &soapfault; hrSearch = pSoapReader->SelectSingleTextNode(L"/soap:Envelope/soap:Body/soap:Fault /faultstring", &soapfault); if (SUCCEEDED(hrSearch)) MessageBox(hDlg, soapfault, L"Soap Error", MB_OK | MB_ICONWARNING); else MessageBox(hDlg, L"Call failed", L"Error", MB_OK | MB_ICONWARNING);



图5. Soap故障传回客户端

如果所有调用成功完成,64位字符串将解码成为一个字符数组并作为SmartMap.gif保存。我们希望该文件在电话关闭也保存在My Documents文件夹中。但是固化存储器的文件结构可以被OEM的改变,因此调用SHGetSpecialFolderPath API来检索这些信息。

CHAR szMapFile[MAX_PATH]; SHGetSpecialFolderPath(hwnd, szMapFile, CSIDL_PERSONAL , FALSE); _tcscat(szMapFile, L"\\SmartMap.gif"); //在Orange SPV上 szMapFile包含在 \IPSM\My Document\SmartMap.gif //在模拟器上为: \My Document\SmartMap.gif

最后建立map对话框,它包含一个图象控件(IDC_IMAGE)。



图6.选择其它数据

当该对话框载入后,保存的地图图象将从文件系统中读入并且STM_SETIMAGE传递到对话框句柄。该图象使用IMGDECMP.dll图象库载入,它时CE 3.0平台的一部分。

HBITMAP g_hbm = NULL; ReadPictureFile(hDlg, szMapFile, & g_hbm); SendDlgItemMessage(hDlg, IDC_IMAGE, STM_SETIMAGE, IMAGE_BITMAP, (LPARAM) g_hbm);



图7.选择其它数据

这时SmartMap客户端几乎已经完成。如果可能的话,用户需要能查看最新地图。为了实现功能,使用了一个弹出菜单,在Find Location对话框的Option按钮中。如果文件存在就会捕获一个WM_INITMENUPOPUP消息,由此弹出菜单被激活或者禁止。

//菜单弹出前调用 case WM_INITMENUPOPUP: { //查看是否文件存在和是否激活最新地图菜单 DWORD dwFile = GetFileAttributes(szMapFile); BOOL bEnabled = (dwFile == 0xFFFFFFFF ? FALSE : TRUE); EnableMenuItem((HMENU)wParam, ID_EXIT_LASTMAP, MF_BYCOMMAND | (bEnabled?MF_ENABLED:MF_GRAYED)); break; }

如果用户可以选择该菜单,接着map对话框将再次被建立,它从文件系统自动载入图象。

总结

我曾经查阅过Web服务、工具和技术所包含的内容来帮助查看SOAP包。使用提供的例程,我们看到了在Smartphone 2002上,在例子MapPoint .NET中使用Web服务的一条途径。通过建立Web服务察觉(service-aware)客户端,我们获得了工作于混合连接环境,连接和断开连接,合计和储存数据的移动设备上的新一代应用程序的好处,并提供了新的经验。

本文地址:http://com.8s8s.com/it/it42901.htm