Creating Web Service Clients Manually



Creating Web Service Clients Manually

At times, you will want to build your client class by hand, or seriously modify the client class that wsdl.exe creates for you.

Building Document-Literal Clients

By default, Web service servers created with ASP.NET are document-literal services; therefore, also by default, clients created from those services are document literal. These are services that use XML Schema definitions to describe the service interface. They don't use the SOAP Section 5 encoding.

If you want to build a client by hand, then making it document literal is fairly easy. The main thing to remember is to put the [SoapDocumentMethod] attribute on the methods that are supposed to send the message. Because the use will be document literal by default, nothing special needs to be done on that end. Listing 4.5 shows a short example.

Using the [SoapDocumentMethod] Attribute to Create a Document-Literal Client
[WebServiceBinding("MyBinding", "http://MyNS")]
public class CustomWebMethods : SoapHttpClientProtocol
{
      public CustomWebMethods()
      {
            this.Url = "http://localhost:8080";
      }

      [SoapDocumentMethod("http://mySoapAction")]
      public int Add( int x, int y)
      {
            Object[] args = {x, y};
            Object[] responseMessage = this.Invoke( "Add", args );
            return ((int)(responseMessage[0]));
      }
}

Listing 4.5 results in the SOAP request shown in Listing 4.6.

A Document-Literal Client SOAP Request
POST / HTTP/1.1
User-Agent: Mozilla/4.0
Content-Type: text/xml; charset=utf-8
SOAPAction: "http://mySoapAction"
Content-Length: 293
Expect: 100-continue
Connection: Keep-Alive
Host: localhost

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope
     xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soap:Body>
     <Add xmlns="http://MyNS">
          <x>1</x>
          <y>2</y>
     </Add>
</soap:Body>
</soap:Envelope>

Notice that the client in Listing 4.6 derives from the base class System.Web.Services.Protocols.SoapHttpClientProtocol. Each method that is making a Web service call does little work, other than calling the base class method Invoke. You seldom will need to build these client classes yourself, but if you do, the following section explains what you need to know.

Building Document-Encoded Clients

To build a client that sends the message as encoded SOAP, you basically need to do two things. First, you need to make sure that the types you are using are shaped correctly, using the Soap attributes from System.Xml.Serialization instead of the Xml attributes. Then, you need to set the Use property of the [SoapDocumentMethod] attribute to indicate that this client is encoded, not literal XML. Listing 4.7 shows an example.

Setting the Use Property for a Document-Encoded Client
[WebServiceBinding("MyBinding", "http://MyNS")]
public class CustomWebMethods : SoapHttpClientProtocol
{
      public CustomWebMethods()
      {
            this.Url = "http://localhost:8080";
      }

      [SoapDocumentMethod("http://mySoapAction",
      SoapBindingUse.Encoded)]
      public int Add( int x, int y)
      {
            Object[] args = {x, y};
            Object[] responseMessage = this.Invoke( "Add", args );
            return ((int)(responseMessage[0]));
      }
}

Listing 4.7 sends a SOAP message like the one in Listing 4.8.

The SOAP Message for a Document-Encoded Client
POST / HTTP/1.1
User-Agent: Mozilla/4.0Content-Type: text/xml; charset=utf-8
SOAPAction: "http://mySoapAction"
Content-Length: 507
Expect: 100-continue
Connection: Keep-Alive
Host: localhost
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope
     xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
     xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
     xmlns:tns="http://MyNS" xmlns:types="http://MyNS/encodedTypes"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soap:Body
     soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
     <types:Add>
          <x xsi:type="xsd:int">1</x>
          <y xsi:type="xsd:int">2</y>
     </types:Add>
</soap:Body>
</soap:Envelope>

Building RPC-Encoded Clients

RPC-encoded clients are very similar to document-encoded ones. The basic difference is explained in Chapter 9 (The Messaging Protocol: SOAP). In implementation, the difference relates to which attribute you should use. In place of the [SoapDocumentMethod] try using the [SoapRpcMethod] attribute (as shown in Listing 4.9). By default this attribute will be encoded, so you won't need to set the Use property, as was necessary with building regular document-encoded clients.

Using the [SoapRpcMethod] Attribute to Build an RPC-Encoded Client
[WebServiceBinding("MyBinding", "http://MyNS")]
public class CustomWebMethods : SoapHttpClientProtocol
{
      public CustomWebMethods()
      {
            this.Url = "http://localhost:8080";
      }

      [SoapRpcMethod("http://mySoapAction")]
      public int Add( int x, int y)
      {
            Object[] args = {x, y};
            Object[] responseMessage = this.Invoke( "Add", args );
            return ((int)(responseMessage[0]));
      }
}

Listing 4.9 creates a SOAP message like the one in Listing 4.10.

An RPC-encoded SOAP Message
POST / HTTP/1.1
User-Agent: Mozilla/4.0
Content-Type: text/xml; charset=utf-8
SOAPAction: "http://mySoapAction"
Content-Length: 503
Expect: 100-continue
Connection: Keep-Alive
Host: localhost

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope
     xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
     xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
     xmlns:tns="http://MyNS" xmlns:types="http://MyNS/encodedTypes"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soap:Body
     soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
     <tns:Add>
           <x xsi:type="xsd:int">1</x>
           <y xsi:type="xsd:int">2</y>
     </tns:Add>
</soap:Body>
</soap:Envelope>

Building One-Way Clients

Building one-way clients also is simple. Just like a server, you can only create a one-way message operation when the return value of the Web method is void. Keep in mind that the server will not process the message that you send it, but you will get the return right away. Whether you use [SoapDocumentMethod] attributes or [SoapRpcMethod] attributes, you need only to set the OneWay property to true (as shown in Listing 4.11). Your client can be encoded or literal—it doesn't matter.

Setting the OneWay Property to True to Build a One-Way Client
[WebServiceBinding("MyBinding", "http://MyNS")]
public class CustomWebMethods : SoapHttpClientProtocol
{
      public CustomWebMethods()
      {
            this.Url = "http://localhost:8080";
      }

      [SoapDocumentMethod("http://mySoapAction", OneWay=true)]
      public void Add( int x, int y)
      {
            Object[] args = {x, y};
            this.Invoke( "Add", args );
            return;
      }
}

Listing 4.11 sends a SOAP message like the one in Listing 4.12.

A SOAP Message from a One-Way Client
POST / HTTP/1.1
User-Agent: Mozilla/4.0
Content-Type: text/xml; charset=utf-8
SOAPAction: "http://mySoapAction"
Content-Length: 293
Expect: 100-continue
Connection: Keep-Alive
Host: localhost

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope
     xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soap:Body>
     <Add xmlns="http://MyNS">
          <x>1</x>
          <y>2</y>
     </Add>
</soap:Body>
</soap:Envelope>

Building a Client That Uses Asynchronous Methods

Building a client that includes asynchronous methods is fairly trivial. Basically, you add two methods: one called BeginYourMethod (where YourMethod is the actual name of your method) and another one called EndYourMethod. The "End" method doesn't require any special attributes, but the "Begin" method should contain all of the usual attributes that your regular synchronous methods would.

NOTE

Unless you have a compelling reason to do otherwise, always use the asynchronous pattern when you consume Web services in your production code. By doing this, you increase the performance of your application, and allow it to work on other tasks, even if those other tasks are merely for a more responsive user interface (UI).

On the server, this isn't always as important (although usually it still is), but for the Windows-based rich client, having a responsive UI is critical to your application's success. Therefore, I recommend that you think of using this pattern as your first choice, and that you become as comfortable with it as with the synchronous pattern.

Alternatively, your synchronous method can contain all of the attributes. In that case, the asynchronous methods don't need any Web service attributes at all, and the Web service infrastructure will pick up all of the metadata it needs from the synchronous method.

In the "Begin" method, where you usually would call the Invoke method, call the BeginInvoke method instead. Return from your method the IAsyncResult object that BeginInvoke returns.

In the "End" method, call the EndInvoke method, with the AsyncResult object handed to you. The return from this method will be the data you should return, just as you return it from your synchronous method.

Create your callback:

public void MyCallback( IAsyncResult ar) 
{
      localhost.CRService service = (localhost.CRService)ar.AsyncState;
      ds = service.EndGetCustomerRecord(ar);
}

Then, call the "Begin" method:

private void menuItem3_Click(object sender, System.EventArgs e) 
{
      AsyncCallback cb = new AsyncCallback( this.MyCallback );
      IAsyncResult ar = cr.BeginGetCustomerRecord(
                                       txtEmail.Text, cb, cr );
}