.NET Web Services IV...
Part IV: More on asynchronous Web Services ans specifying XML wire format.
Knowledge assumed for series: VB.NET, VS.NET; Parts I, II and III.
After reviewing the basics of Web Services in Part I and continuing to look at more advanced specifics in part II, in the last part we continued the progression looking at the first of the three more advanced Web Service related topics below:
In this article we'll finish off our look at Web Services concentrating on the latter two areas.
Asynchronous calls, as introduced in article II, are a client side technique for making more efficient use of Web Services. An asynchronous call does not wait for the result to be returned by the Web Service before proceeding with its work. The call is made, the client proceeds with its work and is either notified when the result arrives or checks that it is available. The proxy class automatically enables asynchronicity but there are various ways of utilising the functionality.
Custom wire formatting allows the developer to specify the format of the SOAP messages used by the Web Service. This may be necessary if client software is expecting messages in a particular format. The formatting is achieved via the use of attributes within your code.
An asynchronous call is one where the client is not blocked from undertaking other activities whilst waiting for the Web Service response. As previously introduced the client proxy facilitates asynchronous calls to a Web Service automatically. The CLR sends the SOAP request and then monitors a system port for the SOAP response.
There are several ways to handle an asynchronous call – using a callback delegate is generally preferred so we'll focus on that before reviewing the alternate approaches.
This is as per the asynchronous example we introduced in article II: we provide a delegate function that the CLR can invoke when the results are returned from the Web Service.
For this example we'll use the Web Service we introduced for the consideration of SOAP extensions in the last article. We'll alter the client to access the Web Service asynchronously. We are going to put a little more output in the forms text box so alter the size of the form and textbox appropriately, make sure the multiline property is set to true and the scrollbars property is set to vertical.
Alter the button click and callback function code as follows:
|
Private Sub btnCallWebService_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _ Handles btnCallWebService.Click Dim track As localhost.Tracker = New localhost.Tracker Dim cb As AsyncCallback = New AsyncCallback(AddressOf WebServiceCallback) track.BeginGetMessage(1, cb, track) Dim intI As Integer For intI = 1 To 10000 txtOutput.AppendText(CStr(intI) & vbCrLf) Next End Sub Private Sub WebServiceCallback(ByVal ar As IAsyncResult) ' Get the returned Web service instance Dim track1 As localhost.Tracker track1 = ar.AsyncState MsgBox("Done: " & track1.EndGetMessage(ar)) End Sub |
If you run the Windows form a msgbox is likely to appear before all 10000 integers have been displayed, though this will depend on your own system setup.
The callback function for an asynchronous Web method call must have this signature – a single argument of type IAsynchResult. For each Web method the proxy class will contain corresponding Begin and End methods. The Begin method takes all the arguments of the original method plus a reference to the Web Service itself (allowing multiple calls to synchronous web methods simultaneously) and the address of the callback method. When the results of the call are available the callback method will be invoked. Within the callback method you can retrieve the original Web Service object and then call its End method to get the result of the method call.
The WaitHandle object essentially lets you turn the asynchronous process back into a synchronous process. The difference with this option is that you can choose when to wait for the response. Thus you can go away and do some processing for a few seconds then wait for the result of the Web Service and if its there already even better. You can imagine that as the response time of a Web Service is non-deterministic generally the callback delegate approach is preferable. However, here is an example of the WaitHandle approach with the supporting Windows form elements as per the last example.
|
Imports System.Threading Public Class tracker_test_waithandle Inherits System.Windows.Forms.Form Private Sub btnCallWebService_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _ Handles btnCallWebService.Click Dim track As localhost.Tracker = New localhost.Tracker Dim ar As IAsyncResult ar = track.BeginGetMessage(1, Nothing, Nothing) Dim intI As Integer For intI = 1 To 100 txtOutput.AppendText(CStr(intI) & vbCrLf) Next Dim wh As WaitHandle = ar.AsyncWaitHandle wh.WaitOne() txtOutput.AppendText(track.EndGetMessage(ar)) For intI = 101 To 200 txtOutput.AppendText(CStr(intI) & vbCrLf) Next End Sub End Class |
Here we import the necessary namespace, start the call with the Begin form of the method (but in this case we don't pass the callback or the original object), do some work, wait for the response to be ready (wh.WaitOne()), retrieve the response and then do some more work.
A further option is polling the value of the IAsynchResult.IsCompleted property. As you might expect this property will return true when the End method is ready to call.
You can simply call the End method if you're ready to wait synchronously for the result. This is equivalent to using the WaitHandle.Waitone method in the last example.
Finally for this section, while VS.NET or wsdl.exe builds the asynchronous proxy methods for you if you want to gain a little better understanding of what is going on feel free to take a look at these methods in the proxy class. As introduced in a previous article in the series you'll find the code (if using VS.NET) under your project web references in the Reference.vb file.
We come to the last topic of this series: the SOAP standard is flexible in converting objects to XML and vice versa. You are able to vary both the parameters within the body of a SOAP message and the format of the body itself. These choices control the 'XML wire format' of the messages – so-called because these message travel 'over the wire' between client and server and vice versa.
Why would you want to control the wire format? Well, if working purely in the .NET world you probably wouldn't but you may need to write code that interoperates with Web Services in other environments that don't support the .NET defaults.
There are several related standards of formatting:
Parameter formatting:
Body formatting:
Given the above there are three alternate legal combinations:
We specify the choice of formats via attributes thus indicating the choices to both Web methods and to proxy classes that need to call these methods. Obviously these choices must match on client and server or errors will result. You can also use attributes to change details about the SOAP message, such as the names used for XML elements representing parameters.
We'll now take a quick look at what these different formats look like and how we produce them. Let's return to our original articles Web Service introduced first in article I:
|
Imports System.Web.Services
<System.Web.Services.WebService(Namespace:="http://www.cymru-web.net/my_articles_WS/Articles")> _ Public Class Articles_wire Inherits System.Web.Services.WebService <WebMethod(CacheDuration:=60, _ Description:="Get the list of articles CMS has authored")> _ Public Function GetArticles() As DataSet Dim dsDMS As DataSet = New DataSet dsDMS.ReadXmlSchema(Server.MapPath("articles_schema.xml")) dsDMS.ReadXml(Server.MapPath("articles.xml")) Return dsDMS dsDMS = Nothing End Function |
If you view the corresponding .asmx in your browser and view the SOAP message, you'll get, for the POST version:
|
POST /my_articles_WS/articles_wire.asmx HTTP/1.1 Host: localhost Content-Type: text/xml; charset=utf-8 Content-Length: length SOAPAction: "http://www.cymru-web.net/my_articles_WS/Articles/GetArticles" <?xml version="1.0" encoding="utf-8"?> <soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Body> <GetArticles xmlns="http://www.cymru-web.net/my_articles_WS/Articles" /> </soap:Body> </soap:Envelope> |
If you modify the declaration of the Articles_wire class to:
<SoapDocumentService(Use:=SoapBindingUse.Literal), _
System.Web.Services.WebService(Namespace:="http://www.cymru-web.net/my_articles_WS/Articles")> _
Public Class Articles_wire
and also add two more Imports directives:
|
Imports System.Web.Services.Description Imports System.Web.Services.Protocols |
you'll get the same SOAP message as all we've done is explicitly specify the defaults. However, if you now change the attribute specification to:
<SoapDocumentService(Use:=SoapBindingUse.Encoded), _
to specify encoded parameter formatting and view the Web Service you'll receive an error:
The type System.Data.DataSet may not be serialized with SOAP-encoded messages. Set the Use for your message to Literal.
This raises the issue that to use more complex .NET types with Web Services you must use Literal formatting.
Let's replace the Web method so we have a working example for comparing the differing formats:
|
Public Function GetArticlesString() As String Dim dsDMS As DataSet = New DataSet dsDMS.ReadXmlSchema(Server.MapPath("articles_schema.xml")) dsDMS.ReadXml(Server.MapPath("articles.xml")) Return dsDMS.ToString dsDMS = Nothing End Function |
Thus returning the article list as a string instead which encoded parameter formatting can cope with.
Let's now compare the literal and encoded parameter formats for this Web Service
Literal:
|
POST /my_articles_WS/articles_wire.asmx HTTP/1.1 Host: localhost Content-Type: text/xml; charset=utf-8 Content-Length: length SOAPAction: "http://www.cymru-web.net/my_articles_WS/Articles/GetArticlesString" <?xml version="1.0" encoding="utf-8"?> <soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Body> <GetArticlesString xmlns="http://www.cymru-web.net/my_articles_WS/Articles" /> </soap:Body> </soap:Envelope> HTTP/1.1 200 OK Content-Type: text/xml; charset=utf-8 Content-Length: length <?xml version="1.0" encoding="utf-8"?> <soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Body> <GetArticlesStringResponse xmlns="http://www.cymru-web.net/my_articles_WS/Articles"> <GetArticlesStringResult>string</GetArticlesStringResult> </GetArticlesStringResponse> </soap:Body> </soap:Envelope> |
Encoded:
|
POST /my_articles_WS/articles_wire.asmx HTTP/1.1 Host: localhost Content-Type: text/xml; charset=utf-8 Content-Length: length SOAPAction: "http://www.cymru-web.net/my_articles_WS/Articles/GetArticlesString" <?xml version="1.0" encoding="utf-8"?> <soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:tns="http://www.cymru-web.net/my_articles_WS/Articles" xmlns:types="http://www.cymru-web.net/my_articles_WS/Articles/encodedTypes" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Body soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> <tns:GetArticlesString xsi:type="tns:GetArticlesString" /> </soap:Body> </soap:Envelope> HTTP/1.1 200 OK Content-Type: text/xml; charset=utf-8 Content-Length: length <?xml version="1.0" encoding="utf-8"?> <soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:tns="http://www.cymru-web.net/my_articles_WS/Articles" xmlns:types="http://www.cymru-web.net/my_articles_WS/Articles/encodedTypes" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Body soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> <tns:GetArticlesStringResponse xsi:type="tns:GetArticlesStringResponse"> <GetArticlesStringResult xsi:type="xsd:string">string</GetArticlesStringResult> </tns:GetArticlesStringResponse> </soap:Body> </soap:Envelope> |
You can see, even from this simple example, that encoded parameter formatting inserts additional information into the SOAP messages. This contrasts with literal parameter formatting which does not insert any information about data types, assuming that the code at server or client will know how to handle the data it sends and receives. In encoded parameter formatting the soap:Envelope element includes additional namespaces to allow each parameter to be explicitly marked with its data type. Elements specific to this Web Service are each explicitly marked with the tns namespace indicator. Note that the increased conformance of encoded parameter formatting to SOAP standards comes at the expenses of additional message sizes often doubling sizes.
Turning to body formatting, modify the declaration of the class as follows:
<SoapRpcService(), _
System.Web.Services.WebService(Namespace:="http://www.cymru-web.net/my_articles_WS/Articles")> _
the SOAP messages of which are exactly the same as the previous example … so I won't bother listing again. Why? Firstly, if you select an RPC message style body the parameters within that body will automatically be encoded according to the SOAP section 5 rules. Secondly, the difference between Document and RPC styles of body formatting is that the Document style allows further choices about how to encode the parameters within the body whereas RPC doesn't.
With both RPC and the default document body formatting all the parameters within the SOAP message are contained within a single XML element identified via the name of the Web method. This is referred to as wrapped parameter style. If you're using document body formatting you can specify the bare parameter style instead. Explaining by example:
<SoapDocumentService(Use:=SoapBindingUse.Literal, _
ParameterStyle:=SoapParameterStyle.Bare), _
System.Web.Services.WebService(Namespace:="http://www.cymru-web.net/my_articles_WS/Articles")> _
gives:
|
POST /my_articles_WS/articles_wire.asmx HTTP/1.1 Host: localhost Content-Type: text/xml; charset=utf-8 Content-Length: length SOAPAction: "http://www.cymru-web.net/my_articles_WS/Articles/GetArticlesString" <?xml version="1.0" encoding="utf-8"?> <soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Body /> </soap:Envelope> HTTP/1.1 200 OK Content-Type: text/xml; charset=utf-8 Content-Length: length <?xml version="1.0" encoding="utf-8"?> <soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Body> <GetArticlesStringResult xmlns="http://www.cymru-web.net/my_articles_WS/Articles">string</GetArticlesStringResult> </soap:Body> </soap:Envelope> |
If you compare closely you'll see that bare parameter formatting makes the parameter elements direct children of the soap:Body element rather than wrapping them in a single element that represents the entire Web method. You can also apply parameter formatting to encoded parameters with similar results.
Finally, I'll just mention the XmlElement attribute, which allows specification of low-level XML formatting details for messages. Ordinarily the parameter names with the SOAP message match the parameter names in the Web method code you've used. The XmlElement attribute is used to override this default and can be used to alter other properties of the generated XML as well. For further detail see the SDK.
That's the series of 4 articles looking at Web Services in .NET in some depth completed. We've covered the following areas albeit in a slightly different manner than originally intended and specified in article I:
Article 1: Introduction: Overview, SOAP, DISCO, UDDI and WSDL; creating and consuming a WebService in VS.NET.
Article 2: Customising the WebMethod attribute, Disco and UDDI practicalities, the disco.exe and wsdl.exe tools, introduction to asynchronous Web Services
Article 3: Creating and using SOAP extensions
Article 4: Creating asynchronous web methods, Controlling XML wire format
I hope working through the series has improved your understanding of Web Services in .NET. If you have any suggestions for future articles, related or not, please let DotNetJohn know. If you have comments or questions concerning this particular article let me know: [ chris.sully@cymru-web.net ].
.NET SDK
Developing XML Web Services and Server Components with VB.NET and the .NET Framework
Mike Gunderloy
Que