Hosting a Silverlight 2.0 Application on CE 6.0 – Calling SOAP

Here’s some background reading before we get into the rest of the blog post.

Here’s the cunning plan – you could imagine building a digital picture frame or other type of device that may expose a remote administration user interface through your desktop web browser. In the case of the picture frame you might want to set the RSS feed name that the frame is consuming, or if the device is on a corporate domain network then you might want to set the proxy settings, image transition times, or some other setting for the device.

I recently built a CE 6.0 R2 operating system image for a digital picture frame that exposed some simple console applications through a Telnet interface to configure feeds and stuff, perhaps this method is ok for some users but not ideal for the majority of users who would be far more comfortable using Internet Explorer.

The Windows CE web server (SYSGEN_HTTPD) does support ASP pages (not ASP.NET, just raw ASP), and also supports ISAPI extensions (the SOAP server is a good example of an ISAPI extension), I’m not sure that I want to do raw ASP work, I’d much prefer to build the web UI using Microsoft Expression Blend and then code behind the user experience with C#.

Note that if I build a Silverlight 2.0 application and host this on my Windows CE device that the web application runs in the context of my desktop web browser, not on the Windows CE device. If we need to change settings on the Windows CE device then we will need to expose some kind of interface for the Silverlight application to call into.

CE 6.0 ships with a SOAP server (hosted by the HTTPD web server [which is about 80kb]), creating a native code XML Web Service is fairly straight forward – You write the XML Web Service as a C++ ATL/COM object which is hosted by the SOAP server running on Windows CE. Part of the process of generating the XML Web Service is to generate the WSDL (Web Service Description Language) and WSML (Web Service Meta Language) that link exposed XML Web service functions to the interface ID of the COM object.

Windows CE XML Web Services are bound to RPC style binding and encoded use model. Silverlight 2.0 doesn’t directly support the RPC/Encoded binding/use model, but does support RPC/Literal – so, we have a mismatch. If the CE SOAP model was RPC/Literal then we would be able to directly import the WSDL to the Silverlight project (Add Service Reference) and have a proxy class built for us that would make it super easy to call into the Windows CE exposed web service. Interestingly, desktop C# applications can import/call RPC/Encoded web services.

Silverlight 2.0 does support the ability to manually generate an HTTP POST which is all that’s really needed to call a SOAP service. In this case all I really need to know is what the HTTP POST packet needs to look like. Once I know the HTTP POST structure I can then either manually deconstruct WSDL to HTTP POST, or perhaps write a utility that could import WSDL and spit out Silverlight 2.0 C# code to make the SOAP call. For this exercise I did the manual process of deconstructing WSDL to code.

One way to figure this out is to write a desktop C# application that imports the CE 6.0 WSDL, generates the proxy classes, and then actually call the Windows CE SOAP service. Once you call the SOAP service you can set a breakpoint in the HTTPD web server and look at the inbound message.

If you have the CE 6.0 Shared Source installed take a look at the following file…

\Wince600\private\servers\httpd\Core\Request.cpp – Line 49 (function CHttpRequest::HandleRequest).

When the breakpoint fires, examine the contents of m_bufRequest.m_pszBuf – this contains the inbound message.

My XML Web Service is called WsString – this exposes one function called PassString – the function takes one parameter of type BSTR called MyString.

Here’s the code for the function – nothing interesting to see here, the function simply displays a MessageBox on the Windows CE device, but could easily do anything else on the operating system since the SOAP/XML Web Service is running in user mode, you have access to all of the Windows CE APIs (depending on what’s included in your image).

 STDMETHODIMP CStringTest::PassString(BSTR MyString)
{
  TCHAR *tcString;
  USES_CONVERSION;
  tcString = OLE2T(MyString);
  MessageBox(NULL,tcString,L"StringTesT",MB_OK);
  return S_OK;
}

I set a breakpoint in the HTTPD Web Server code and called my XML Web Service from a desktop C# application – here’s what the HTTP POST looked like.

 POST /wsstring.wsdl HTTP/1.1
Content-Type: text/xml; charset=utf-8

SOAPAction: "https://tempuri.org/action/StringTest.PassString"

Host: 157.56.184.113

Content-Length: 369

Expect: 100-continue

Connection: Keep-Alive
<s:Envelope xmlns:s="https://schemas.xmlsoap.org/soap/envelope/"><s:Body s:encodingStyle="https://schemas.xmlsoap.org/soap/encoding/" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="https://www.w3.org/2001/XMLSchema"><q1:PassString xmlns:q1="https://tempuri.org/message/"><MyString xsi:type="xsd:string">Foo</MyString></q1:PassString></s:Body></s:Envelope>

There are a few interesting things to take out from the HTTP POST.

  1. Notice on the first line the path to the XML Web Services WSDL, and the name of the WSDL (wsstring.wsdl)
  2. The custom header – SOAPAction
  3. The SOAP envelope

The interesting part of the SOAP envelope is this…

 <q1:PassString xmlns:q1="https://tempuri.org/message/">
    <MyString xsi:type="xsd:string">Foo</MyString>
</q1:PassString>

Notice the function name “PassString”, the parameter name “MyString” (type xsd:string), and the actual string being passed “Foo”.

We now have everything we need to call our SOAP service from the Silverlight 2.0 application!

Here’s my amazing Silverlight 2.0 application user interface.

ExpressionBlend_App

The textBox is rotated and is still completely functional, I can type/edit the contents of the textBox.

In order to call the SOAP service running on the Windows CE device we need to know the host IP address of the device – I covered this in a recent article SL2, calling the CE host

Here’s the code to call the Windows CE SOAP/XML Web Service – there are some interesting things to pull out of the code, see below.

 private String soap = @"<s:Envelope xmlns:s=""https://schemas.xmlsoap.org/soap/envelope/""><s:Body s:encodingStyle=""https://schemas.xmlsoap.org/soap/encoding/"" xmlns:xsi=""https://www.w3.org/2001/XMLSchema-instance"" xmlns:xsd=""https://www.w3.org/2001/XMLSchema""><q1:PassString xmlns:q1=""https://tempuri.org/message/""><MyString xsi:type=""xsd:string"">Foo</MyString></q1:PassString></s:Body></s:Envelope>";
private void Button_Click(object sender, RoutedEventArgs e)
{
    HttpWebRequest request = (HttpWebRequest)WebRequest.Create(new Uri(HostAddress + "/wsstring.wsdl", UriKind.Absolute));
    request.ContentType = "text/xml; charset=utf-8";
    request.Accept = "text/xml";
    request.Method = "POST";
    request.Headers["SOAPAction"] = "\"https://tempuri.org/action/StringTest.PassString\"";
    request.BeginGetRequestStream(new AsyncCallback(RequestReady), request);
}
// Sumbit the Post Data

void RequestReady(IAsyncResult asyncResult)
{
    HttpWebRequest request = asyncResult.AsyncState as HttpWebRequest;
    Stream stream = request.EndGetRequestStream(asyncResult);
    this.Dispatcher.BeginInvoke(delegate()
    {
        StreamWriter writer = new StreamWriter(stream);
        writer.WriteLine(soap);
        writer.Flush();
        writer.Close();
        request.BeginGetResponse(new AsyncCallback(ResponseReady), request);
    });
}
// Get the Result

void ResponseReady(IAsyncResult asyncResult)
{
    HttpWebRequest request = asyncResult.AsyncState as HttpWebRequest;
    HttpWebResponse response = (HttpWebResponse)request.EndGetResponse(asyncResult);
    this.Dispatcher.BeginInvoke(delegate()
    {
        Stream responseStream = response.GetResponseStream();
        StreamReader reader = new StreamReader(responseStream);
        // get the result text
        string result = reader.ReadToEnd();
        tb1.Text = result;
    });
}

 

  1. I’m passing the SOAP envelope “as is”, unmodified from the original SOAP envelope generated from my desktop C# application – this would be easy to modify to allow me to pass the contents of my amazing UI textBox, or some other data.
  2. Notice the setup of the WebRequest – I pass the hostaddress and the path to the wsdl
  3. Notice the setup for ContentType (text/xml) and Method (POST)
  4. Notice the SOAPAction is being passed as a header, not as the body of the post.
  5. The only content being passed in the body of the POST is the SOAP envelope.

And here’s the proof that the SOAP service is getting called from the Silverlight 2.0 application, hosted on the Windows CE HTTPD Web Server, and running in the context of my desktop Internet Explorer application (running on Windows 7 RC).

Foo

[UPDATE] – I forgot one thing… Your device/service needs to “opt in” to Silverlight cross domain security policies. This is fairly straight forward, you need a file called clientaccesspolicy.xml in the root folder of your HTTPD web server (exposed through \windows\www\wwwpub in the Windows CE file system). Here’s a sample clientaccesspolicy.xml file.

 <?xml version="1.0" encoding="utf-8"?>
<access-policy>
  <cross-domain-access>
    <policy>
      <allow-from http-request-headers="*">
        <domain uri="*"/>
      </allow-from>
      <grant-to>
        <resource path="/" include-subpaths="true"/>
      </grant-to>
    </policy>
  </cross-domain-access>
</access-policy>

More information on Silverlight 2.0 and making services available across domain boundaries can be found in this MSDN article.

- Mike