Silverlight 2 Beta 2 now supports Web Services Interoperability
This week, the Silverlight team released beta 2. This is a critical new beta since apart from the many UI-oriented features, the networking stack now offers web services interoperability (WSI).
Why is this important?
Well if you are a Microsoft-only shop and all your web services are .asmx-based then you are using a document-style web service model (as opposed to an RPC-style).
But if your enterprise environment has mixed technologies (e.g. MS, Java, Ruby, etc.) then you will find that many older (and sometime not so old) web services follow the SOAP/RPC style.
E.g.
– if you are Ruby On Rails developer and expose web services using the Action Web Service plugin then your services are RPC-style
– if you are using Apache Axis 1.x (often 3rd party software embeds older version of Axis, not Axis 2 which does offer document-style SOAP) – same thing
In beta 1, Silverlight could only call .asmx web services or services using a document-style SOAP implementation. Unfortunately for me, I had built a simplistic web service using the Rails Action Web Service plugin. So I could not get my web service implementation to even build let alone work.
Here is what my test scenario looked like.
In Rails I declared my web service API like so:
class WsTstApi < ActionWebService::API::Base api_method :get_time, :returns => [:string] end
Then I created an implementation for the API in a Rails controller like so:
require 'date' class WsTstController < ApplicationController wsdl_service_name 'WsTst' web_service_api WsTstApi web_service_scaffold :invoke def get_time return DateTime.now.strftime("%H:%M:%S") end end
This allowed Rails to generate the following WSDL (abbreviated for clarity) when calling the when requested through http://***mywebservicedomain***/ws_tst/service.wsdl:
<definitions name="WsTst" xmlns:typens="urn:ActionWebService" ...> <message name="GetTime"/> <message name="GetTimeResponse"> <part name="return" type="xsd:string"/> </message> <portType name="WsTstWsTstPort"> <operation name="GetTime"> <input message="typens:GetTime"/> <output message="typens:GetTimeResponse"/> </operation> </portType> <binding name="WsTstWsTstBinding" type="typens:WsTstWsTstPort"> <soap:binding transport="http://schemas.xmlsoap.org/soap/http" style="rpc"/> <operation name="GetTime"> <soap:operation soapAction="/ws_tst/api/GetTime"/> <input> <soap:body namespace="urn:ActionWebService" use="encoded" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/> </input> <output> <soap:body namespace="urn:ActionWebService" use="encoded" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/> </output> </operation> </binding> <service name="WsTstService"> <port name="WsTstWsTstPort" binding="typens:WsTstWsTstBinding"> <soap:address location="http://***mywebservicedomain***/ws_tst/api"/> </port> </service> </definitions>
I then created a Silverlight project and added a service reference to http://***mywebservicedomain***/ws_tst/service.wsdl to generate the web service proxy.
However, it seemed like Visual Studio only knew how to generate a proxy assuming a WCF scenario as opposed to a Silverlight-specific WCF stack. This unfortunately generated an serialization attribute (below the statement with the OperationContractAttribute) such as:
[System.ServiceModel.XmlSerializerFormatAttribute(Style=System.ServiceModel.OperationFormatStyle.Rpc, Use=System.ServiceModel.OperationFormatUse.Encoded)]
The Silverlight ServiceModel library did not implement the XML serializer feature or offer any alternatives.
I tried to comment those out and play around with a different set of attributes but at the end of the day I could not set the serialization to be XML RPC-based.
Microsoft indicated at the time in the Silverlight forums that WSI compatibility would be implemented in beta 2.
Well now the wait is over. So this morning I started a brand new Silverlight 2 project. I added my service reference and looked at the generated proxy. Same problem! The XmlSerializerFormatAttribute is not implemented. So I went back to MSDN to look at the new beta 2 documentation for ServiceModel. And oh surprise, there is now an equivalent feature called DataContractFormatAttribute. I replaced the name and rebuilt. Well it seems that the Use property is not implemented so I removed it. So now my full web service proxy decoration looks like this:
[System.ServiceModel.OperationContractAttribute(AsyncPattern=true, Action="/ws_tst/api/GetTime", ReplyAction="*")] [System.ServiceModel.DataContractFormatAttribute (Style=System.ServiceModel.OperationFormatStyle.Rpc)] System.IAsyncResult BeginGetTime(System.AsyncCallback callback, object asyncState); [return: System.ServiceModel.MessageParameterAttribute(Name="return")] string EndGetTime(System.IAsyncResult result);
The solution now built successfully. I modified added a button and a text box to my Page.xaml, then implemented the button click handler to do the following:
1. Instantiate the generated SOAP port client
2. Subscribe to the asynchronous web service call response event
3. Call the web service asynchronously
The code looks like this:
private void btnTest_Click(object sender, RoutedEventArgs e) { btnTest.IsEnabled = false; // Disable the button while the web service call is in progress WsTstWsTstPortClient client = new WsTstWsTstPortClient(); try { client.GetTimeCompleted += new EventHandler<GetTimeCompletedEventArgs>(client_GetTimeCompleted); client.GetTimeAsync(); txtResults.Text = "Calling web svc ..."; } catch (Exception ex) { txtResults.Text = string.Format("Error calling web svc: {0}", ex.ToString()); } }
I then implemented the web service response event handler so that it gets the Result value from the web service response as follows:
void client_GetTimeCompleted(object sender, GetTimeCompletedEventArgs e) { string results = string.Empty; try { results = (e == null) ? "Received null response!" : ((e.Result == null) ? "Received some data but Result is null!" : e.Result); } catch (Exception ex) { results = string.Format("Error: {0}", ex.ToString()); } txtResults.Text = string.Format("Web svc call completed at: {0}nResults: [{1}]", DateTime.Now.ToString(), results); btnTest.IsEnabled = true; }
I built my solution, deployed the .xapp file to my web site and navigated to my test page. The moment of truth was there as I clicked the Test button.
...
... and YES the web service call was successful and the result was displayed in my Silverlight client.
Very cool. This opens up a lot of opportunities for Silverlight client to be used in internal web applications interacting with a broad range of WSI profile web services.