Implementing a Server Asynchronously



Implementing a Server Asynchronously

If you are familiar with .NET, then you may also be familiar with the asynchronous programming pattern that it uses. This pattern provides an easy way of calling a method that will perform its work on a different thread. This is most useful in situations in which the other thread will be blocked on some kind of I/O (input/output) operation, such as reading a file, or making a SOAP request across the network.

Chapter 4, Creating Web Service Clients, looks at how to build and use clients of Web services with .NET. One of the largest lessons of that chapter is the importance of using these asynchronous patterns whenever you make a SOAP request.

However, you may at times also wish to write your Web method asynchronously. Why? Well, if your method will be doing any kind of I/O, such as accessing the hard drive, then you will probably want your server to be able to service other requests. If every thread in your application is blocked on I/O calls, which can take a while, then you can easily end up with a nonresponsive server.

So, ASP.NET will recognize any asynchronous Web methods, and then use that pattern correctly. This means that while your I/O operation is out, the available threads in your application will continue to work.

NOTE

Don't implement the asynchronous pattern if your Web method isn't doing any work that uses IOCompletion ports. You will end up hurting your performance if you do.

Note that regardless of whether you implement your method asynchronously or synchronously, there will still be a single request and response over the wire. In other words, the SOAP will look exactly the same.

Using SOAP Headers

SOAP headers are one of the most interesting things about SOAP. They allow for all kinds of useful extensibility. At this point, I'll bet you are wondering just what a SOAP header is. Chapter 9 (The Messaging Protocol: Soap) covers SOAP headers along with the rest of the SOAP specification in detail. In a nutshell, a SOAP header is an extensible piece of XML that may or may not be intended for the final recipient of the SOAP message. Usually, it contains out-of-band information that is not directly related to the message found within the SOAP body itself. One possibility is authentication information, as in the example from Listing 3.15.

Adding the authentication headers with ASP.NET Web Services is simplicity itself. You need to:

  1. Create a class that derives from the System.Web.Services.Protocols. SoapHeader class that accurately serializes into the SOAP you are looking for.

  2. Add a member variable of that type to your class.

  3. Use the [SoapHeader] attribute on any methods with which you want the header to be used.

  4. Interact with the member variable to read and write data into the header.

Listing 3.15 presents a small code example that adds headers. This example implements a method called WhoAmI that returns the username sent in the header.

<%@ WebService Language="C#" Class="HeaderTest" %> 
Using SOAP Headers
using System;
using System.Web.Services;
using System.Web.Services.Protocols;
using System.Xml.Serialization;

public class AuthHeader : SoapHeader
{

     public String Username;
     public String Password;

     [XmlAttribute]
     public String Domain;
}

public class HeaderTest
{
     public AuthHeader auth;

     [WebMethod]
     [SoapHeader("auth")]
     public String WhoAmI( )
     {
          return auth.Username;
     }
}

The request message sent to this service will look something like Listing 3.15, and the return message will look like Listing 3.16.

A Request Message with Authentication Headers
POST /test/headers.asmx HTTP/1.1
Host: localhost
Content-Type: text/xml; charset=utf-8
Content-Length: length
SOAPAction: "http://tempuri.org/WhoAmI"

<?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:Header>
    <AuthHeader Domain="MyDomain" xmlns="http://tempuri.org/">
      <Username>Keith</Username>
      <Password>RightOn</Password>
    </AuthHeader>
  </soap:Header>
  <soap:Body>
    <WhoAmI xmlns="http://tempuri.org/" />
  </soap:Body>
</soap:Envelope>
A Response Message
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>
    <WhoAmIResponse xmlns="http://tempuri.org/">
      <WhoAmIResult>Keith</WhoAmIResult>
    </WhoAmIResponse>
  </soap:Body>
</soap:Envelope>

Notice that the header isn't returned to the client by default. This can easily be changed. The [SoapHeader] attribute contains a number of properties, including one that specifies the direction of the header. The default can be set to out, or to in and out. Listing 3.18 is a modification of Listing 3.16 that makes the header go both in and out.

A Modified SOAP Header
<%@ WebService Language="C#" Class="HeaderTest" %>

using System;
using System.Web.Services;
using System.Web.Services.Protocols;
using System.Xml.Serialization;

public class AuthHeader : SoapHeader
{

     public String Username;
     public String Password;
     [XmlAttribute]
     public String Domain;
}

public class HeaderTest
{
     public AuthHeader auth;

     [WebMethod]
     [SoapHeader("auth", Direction=SoapHeaderDirection.InOut)]
     public String WhoAmI( )
     {
          return auth.Username;
     }
}

Now, the response message contains the same header:

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:Header>
    <AuthHeader Domain="MyDomain" xmlns="http://tempuri.org/">
      <Username>Keith</Username>
      <Password>RightOn</Password>
    </AuthHeader>
  </soap:Header>
  <soap:Body>
    <WhoAmIResponse xmlns="http://tempuri.org/">
      <WhoAmIResult>Keith</WhoAmIResult>
    </WhoAmIResponse>
  </soap:Body>
</soap:Envelope>

The SoapHeader class and the [SoapHeader] attribute also allow you to specify whether the header is a "must understand" header—that is, whether it contains information that the message sender feels is required for understanding. The ASP.NET Web services infrastructure handles this via the mustUnderstand attribute.

When a header comes in with this attribute set, it will look something like Listing 3.19.

A mustUnderstand Header
POST /test/headers.asmx HTTP/1.1
Host: localhost
Content-Type: text/xml; charset=utf-8
Content-Length: length
SOAPAction: "http://tempuri.org/WhoAmI"

<?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:Header>
    <AuthHeader
       soap:mustUnderstand="1"
       Domain="MyDomain"
       xmlns="http://tempuri.org/">
      <Username>Keith</Username>
      <Password>RightOn</Password>
    </AuthHeader>
  </soap:Header>
  <soap:Body>
    <WhoAmI xmlns="http://tempuri.org/" />
  </soap:Body>
</soap:Envelope>

Because there is a specific class for this authentication header, ASP.NET will assume it is understood. But you also can get headers that are unknown to you—headers for which you have no classes—by using the SoapUnknownHeader class. Now, when the Web method is called, if the DidUnderstand property is not set to true, a SOAP fault will automatically be returned to the client indicating this. But with this modified method, you can indicate that you understood the message, as shown in Listing 3.20.

A Modified SOAP Header
<%@ WebService Language="C#" Class="HeaderTest" %>

using System;
using System.Web.Services;
using System.Web.Services.Protocols;
using System.Xml.Serialization;

public class AuthHeader : SoapHeader
{

     public String Username;
     public String Password;

     [XmlAttribute]
     public String Domain;
}

public class HeaderTest
{
     public AuthHeader auth;
     public SoapUnknownHeader unknown;

     [WebMethod]
     [SoapHeader("auth", Direction=SoapHeaderDirection.InOut)]
     public String WhoAmI( )
     {
          unknown.DidUnderstand = true;
          return auth.Username;
     }
}

The SoapUnknownHeader class is very useful for checking out any headers sent to a service, because it has a System.Xml.XmlElement to use to look at the XML of the header. Imagine that the example request from Listing 3.20 included a new header called Foo, as shown in Listing 3.21.

A Modified Request SOAP Message
POST /test/headers.asmx HTTP/1.1
Host: localhost
Content-Type: text/xml; charset=utf-8
Content-Length: length
SOAPAction: "http://tempuri.org/WhoAmI"

<?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:Header>
     <Foo xmlns:"http://foo.com">I am the Foo header.</Foo>
     <AuthHeader Domain="MyDomain" xmlns="http://tempuri.org/">
      <Username>Keith</Username>
      <Password>RightOn</Password>
     </AuthHeader>
  </soap:Header>
  <soap:Body>
    <WhoAmI xmlns="http://tempuri.org/" />
  </soap:Body>
</soap:Envelope>

If you wanted to read this information, you could do so in your modified method very easily: You would merely need to examine the Element property of the unknown header in the same manner that you would examine any XmlElement object.