Web Services today have became a common technique. Windows CE-based devices, as usual, were several steps behind desktop systems. A couple of months ago, Microsoft finally re-released SP2 for CE.NET, promising significant performance improvements. Thus, recently mobile developers are also able to use most of Web Services’ cake. I have no intention to discuss the theory or concept of this technology here; you may freely find a lot of articles on the Web. Also, we won’t touch Web Services or Visual Studio.NET basics. What we’ll be focused on is what does it all mean in the case of Windows CE.
Creation of a C# Sample Project
As a test sample, we will create a simple client application that will use Amazon’s Web Services to query information about available books. So, let’s create a new “Smart Device Application” using Visual Studio.NET. The only option actually relevant for Windows CE is to create a C# project. That’s the simpliest way to develop Web Services-related applications. If you’ve used C++, you may either consider using the SOAP toolkit available in WinCE 4.x or, as the most complicated case, write a similar wrapper for SOAP classes for previous versions of Windows CE to assemble SOAP messages manually. The last case may become relevant when you need to achieve high performance benchmarks; for example, when developing an application that performs barcode scanning and then gets item details from a remote server. Our sample is simple, so all such details will be left out of this article’s scope.
Well, to be ready to start using Amazon Web Services, you should download Amazon Web Service Developer’s Kit from http://www.amazon.com/webservices. The last thing is to apply fora Developer’s token, and you’re completely set up. So, now, let’s add a Web Reference to our project to get a wrapper class for the Web Service. A WSDL file describing its API can be found here. Finally, we have a form to enter the author name and display search results Until now, all we’ve done is absolutely similar to desktop application development. Now, it’s time to take a look at the actual search code.
private void button1_Click(object sender, System.EventArgs e) { Cursor.Current = Cursors.WaitCursor; AmazonSearchService srvc = new AmazonSearchService(); WebProxy objProxy = new WebProxy (proxyAddr,proxyPort); objProxy.BypassProxyOnLocal = true; objProxy.Credentials = new NetworkCredential (user, password, domain); srvc.Proxy = objProxy; GlobalProxySelection.Select = objProxy; AuthorRequest req = new AuthorRequest(); req.author = SearchText.Text; req.devtag = DEVTAG; req.mode = "books"; req.type = "lite"; req.page = "1"; try { ProductInfo pi = srvc.AuthorSearchRequest(req); for (int i = 0; i < pi.Details.Length; i++) { string s = pi.Details[i].ProductName; AuthorsLB.Items.Add(s); } } catch(WebException ex) { string message = ex.Message; HttpWebResponse response = (HttpWebResponse)ex.Response; if(null != response) { message = response.StatusDescription; response.Close(); } AuthorsLB.Items.Add(message); } Cursor.Current = Cursors.Default; }
As you see, first we should create an instance of a service object. If you’re connecting to the Internet or an intranet via a proxy, you should also define proxy settings. There was a bug in the CE.NET Web Client; it doesn’t look into the credentials of a global WebProxy object to authenticate it to the proxy server. For a workaround, you may define it directly, as shown in the above sample. All the rest of the code proceeds an actual call to Web Service and gets the search result from the remote server. If you were developing Web Service consuming applications for a PC, that’s the same story here. Like at Microsoft’s presentations, just do a Copy/Paste, and it’ll work.
Let’s now consider another important aspect of online applications. Your program should verify that the network connection is alive; in other words, requested network resources are available during the application’s lifetime. CE.NET framework has no classes to notify applications in response to changes in the network connectivity status. Therefore, mobile apps need to implement their own algorithms of polling active connections and possibly providing caching functionality. The simplest way to verify whether a device is connected to the network is to use a System.Theading.Timer object. When Tick Event occurs, you may use WebRequest and HttpWebResponse objects to send a HTTP GET request to the URL where the Web Service is located and receive an answer. If the status code of the response is equal to HttpStatusCode.OK, the connection to remote server is valid:
// Somewhere in the code timer1.Interval = 1000; timer1.Enabled = true; ......................... // In responase to Tick event of timer WebRequest req = WebRequest.Create(url); HttpWebResponse result = (HttpWebResponse)req.GetResponse(); if ( result.StatusCode != HttpStatusCode.OK ) { // Network connection was lost or remote server is // unavailable or some other HTTP error occurred .... }
Actually that’s all you need to start successfully using Web Services on CE.NET mobile devices. If performance issues aren’t your headaches, you may just move your C# code to a mobile environment, obviously taking into account all existing restictions. For those of you who develop mobile business applications working with heavy data, C# can’t offer a sufficient solution. Therefore, our old buddy C++ is coming from behind the scene.
Creation of a C++ Sample Project
There are cases when you can’t use C# as a project’s programming language due to different reasons, such as performance, existing code, and so forth. I guess you may freely continue this list… Well, as usal, C++ may help us survive in such a situation. The current status is that, under WinCE 4.x, you have a MS SOAP toolkit, equivalent to the SOAP SDK 2.0 for the desktop. Programming it is not as comfortable as using C#, but it hides some low-level details from the developer. The toolkit consists of a set of interfaces that allow you to create SOAP messages, send requests, receive responses back, and finally extract received data. The sample code is listed below (you may find full text, for example, here):
... ISoapSerializerPtr pSerializer; ISoapReaderPtr pReader; ISoapConnectorPtr pConnector; hr = pConnector.CreateInstance(__uuidof(HttpConnector)); if(FAILED(hr)) { DisplayHResult(_T("Cannot create SoapClient."), hr); return; } CString EndPointURL; m_EndPointURLCtl.GetWindowText(EndPointURL); pConnector->Property["EndPointURL"] = _variant_t(EndPointURL); pConnector->Property["SoapAction"] = _variant_t(CString(BASE_SOAP_ACTION_URI) + pMethodName); pConnector->BeginMessage(); hr = pSerializer.CreateInstance(__uuidof(SoapSerializer)); pSerializer->Init((IUnknown*)pConnector->InputStream); pSerializer->startEnvelope("","", ""); pSerializer->startBody(""); pSerializer->startElement(pMethodName, WRAPPER_ELEMENT_NAMESPACE, "", "m"); CString A; m_ACtl.GetWindowText(A); pSerializer->startElement("A", "", "", ""); pSerializer->writeString(_bstr_t(A)); pSerializer->endElement(); CString B; m_BCtl.GetWindowText(B); pSerializer->startElement("B", "", "", ""); pSerializer->writeString(_bstr_t(B)); pSerializer->endElement(); pSerializer->endElement(); pSerializer->endBody(); pSerializer->endEnvelope(); pConnector->EndMessage(); pReader.CreateInstance(__uuidof(SoapReader)); pReader->Load((IUnknown*)pConnector->OutputStream, ""); if(pReader->Fault != NULL) { MessageBox(CString(pReader->faultstring), NULL, MB_ICONEXCLAMATION); } else { m_ResultCtl.SetWindowText(pReader->RPCResult->text); } ...
This example gives you some feeling of how to program it; once again, it’s pretty similar to desktop programming.
Unfortunately, Pocket PC 2003 SDK doesn’t support SOAP the Toolkit yet, even though it’s from the WinCE 4.2 family. So, if you need to use it there (or on Pocket PC 2000/2002 with WinCE 3.x), you should implement several classes similar to those noted above to handle SOAP protocol issues manually. It seems to be not so awful as you may guess. All you actually need is to be familiar with the SOAP protocol and HTTP communications. Well, and XML also, to proceed the parsing of the received result. Such a manual solution may be ideal from several points of view. The compact framework still has bugs in its WebClient, which lead to troubles in case of complex networks with proxy server authentication and so forth. Next, you don’t need to be well versed in CF.NET. Your application will work on almost all recent WinCE-powered devices. And finally, you will definitely achieve better performance and flexibility with C++ code in comparison with all other languages. Well, you will have to pay for it by some additional amount of coding. To evaluate this “override,” look at the next code sample. It does not pretend to be an example of good programming technique, but shows a very simple example of using the same “AuthorSearchRequest” method of Amazon Web Services.
void CAmazonWScppDlg::OnButtonSearch() { HRESULT hr = 0; CSoapWriter objSoapWriter; CSoapReader objSoapReader; CSoapConnector objSoapConnection; UpdateData(); BeginWaitCursor(); hr = objSoapConnection.Connect(L"http://soap.amazon.com/onca/ soap3"); if (FAILED (hr)) { AfxMessageBox(L"Failed to connect to URL"); goto CleanUp; } if (FAILED (hr = objSoapWriter.StartEnvelope ())) { TRACE (L"StartEnvelopen"); goto CleanUp; } if (FAILED (hr = objSoapWriter.StartBody ())) { TRACE (L"StartBodyn"); goto CleanUp; } if (FAILED (hr = objSoapWriter.StartElement (L"AuthorSearchRequest", TEMPURI))) { TRACE (L"StartElementn"); goto CleanUp; } if (FAILED (hr = objSoapWriter.StartElement (L"AuthorRequest"))) { TRACE (L"StartElementn"); goto CleanUp; } objSoapWriter.WriteElementString(_T("author"),m_sAuthor); objSoapWriter.WriteElementString(_T("devtag"), _T("D2TBTDKMUHSOS9")); objSoapWriter.WriteElementString(_T("mode"),_T("books")); objSoapWriter.WriteElementString(_T("type"),_T("lite")); objSoapWriter.WriteElementString(_T("page"),_T("1")); if (FAILED (hr = objSoapWriter.EndElement (L"AuthorRequest"))) { TRACE (L"EndElementn"); goto CleanUp; } if (FAILED (hr = objSoapWriter.EndElement (L"AuthorSearchRequest"))) { TRACE (L"EndElementn"); goto CleanUp; } if (FAILED (hr = objSoapWriter.EndBody ())) { TRACE (L"EndBodyn"); goto CleanUp; } if (FAILED (hr = objSoapWriter.EndEnvelope ())) { TRACE (L"EndEnvelopen"); goto CleanUp; } if (FAILED (hr = objSoapWriter.FinalizeSoap ())) { TRACE (L"FinalizeSoapn"); goto CleanUp; } hr = objSoapConnection.Invoke(objSoapWriter, L"AuthorSearchRequest"); if ( FAILED(hr = objSoapReader.GetSoapResponse(objSoapConnection, m_sResult)) ) { TRACE (L"FinalizeSoapn"); goto CleanUp; } UpdateData(0); CleanUp: EndWaitCursor(); TRACE(L"Exitn"); }
This sample utilizes some simple implementation of CSoapWriter, CSoapReader, and CSoapConnector the author found a while ago somewhere on the Web (probably at the Microsoft site) and redesigned and rewrote to do its job in an MFC environment. You may find the sample project here and use it for you own risk and fun. But basically, as you may see from the sample code, there is nothing complicated there. Well, that’s just a simple test application to illustrate the main idea, so it does not implement any significant error checking or connection status verification. The connection status may be checked by a timer too, with various methods, such asa ‘dummy’ HTTP GET request or ping. But the most important thing: It just works!
And, last but not least, in some cases you may also use the REST protocol (XML over HTTP). Amazon Web Services supports REST along with SOAP access:
void CAmazonWScppDlg::OnButtonSearch2() { CSoapReader objSoapReader; CSoapConnector objSoapConnection; UpdateData(); BeginWaitCursor(); HRESULT hr = objSoapConnection.Connect(L"http://xml.amazon.com/ onca/xml3"); if (FAILED (hr)) { AfxMessageBox(L"Failed to connect to URL"); goto CleanUp; } hr = objSoapConnection.InvokeXml (L"t=Alex&dev-t=D2TBTDKMUHSOS9&AuthorSearch=" + m_sAuthor + L"&mode=books&type=lite&page=1&f=xml"); if ( FAILED(hr = objSoapReader.GetSoapResponse(objSoapConnection, m_sResult)) ) { TRACE (L"FinalizeSoapn"); goto CleanUp; } UpdateData(0); CleanUp: EndWaitCursor(); TRACE(L"Exitn"); }
It looks even simplier than SOAP, but it’s less powerful. Actually, it’s like HTTP GET versus HTTP POST arguing. Use what is more suitable for you, that’s all. Now, you hopefully may quickly and successfully start developing your own Web-Service-consumer applications. Just do it!
About the Author
Alex Gusev started to play with mainframes at the end of the 1980s, using Pascal and REXX, but soon switched to C/C++ and Java on different platforms. When mobile PDAs seriously rose their heads in the IT market, Alex did it too. Now, he works at an international retail software company as a team leader of the Mobile R department, making programmers’ lives in the mobile jungles a little bit simpler.