Share via


House of Web Services

Mandatory Headers in ASP.NET Web Services

Tim Ewald

Code download available at:WebServices0305.exe(138 KB)

Contents

Mandatory Headers
Mandatory Headers in ASP.NET
Expecting the Unexpected
Solving the Problem Once and for All
What About WSE?
Where are We?

The ASP.NET Web Services infrastructure includes support for programming with SOAP message headers. Unfortunately, the model for handling mandatory headers is flawed in that you need to write additional code so that a Web Service will not execute when a mandatory header is not processed. This column explores a specific problem that arises when you deal with mandatory SOAP handles and presents three solutions.

Mandatory Headers

SOAP messages may contain an optional header element from the SOAP namespace that, as stated in version 1.1 of the SOAP specification, "provides a flexible mechanism for extending a message in a decentralized and modular way without prior knowledge between the communicating parties." If present, the header element contains zero or more extension elements. An extension element may be marked with an optional mustUnderstand attribute, also from the SOAP namespace, indicating that the extension is mandatory. Figure 1 shows a sample message with a mandatory header. There is broad consensus that if a message receiver cannot process a mandatory header, it shouldn't process the message that contains it. Section 4.2.3 of the SOAP 1.1 specification suggests this, though the wording is a little vague:

Figure 1 Mandatory Header Sample

<env:Envelope xmlns:env="https://schemas.xmlsoap.org/soap/envelope/">
    <env:Header>
        <!-- the mustUnderstand attribute's value indicates that this header is mandatory -->
        <ns:Foo xmlns:ns="https://msdn.microsoft.com/hows" env:mustUnderstand="1">
            <ns:Baz>Some info...</ns:Baz>
        </ns:Foo>
    </env:Header>
    <env:Body> ••• </env:Body>
</env:Envelope>

If a header element is tagged with a SOAP mustUnderstand attribute with a value of '1', the recipient of that header entry either [must] obey the semantics (as conveyed by the fully qualified name of the element) and process correctly to those semantics, or [must] fail processing the message.

The Candidate Recommendation of the SOAP 1.2 specification (Part 1, section 2.4) fixes this problem, making the semantics of mandatory header processing very clear:

Mandatory SOAP header blocks are presumed to somehow modify the semantics of other SOAP header blocks or SOAP body elements. Therefore, for every mandatory SOAP header block targeted to a node, that node [must] either process the header block or refrain from processing the SOAP message at all, and instead generate a fault (see 2.6 Processing SOAP Messages and 5.4 SOAP Fault).

The ASP.NET Web Services plumbing includes a model for dealing with SOAP message headers. Unfortunately, it will allow a message to be processed even when it contains a mandatory header that the target method is not aware of or does not understand. While a fault will be generated, that doesn't happen until after your Web Service's code has run.

Mandatory Headers in ASP.NET

When the ASP.NET Web Services message dispatcher pulls a message apart, it uses the Body element's contents as the input parameters to a method invocation. Before the call is invoked, the contents of the header element (if any) may be deserialized into fields or properties of the target object. When the method runs, it can examine those fields or properties to see what headers a request message contained. The mapping of headers to properties or fields is optional. It is controlled using the value of System.Web.Services.Protocols.SoapHeaderAttribute. An example of this attribute is shown in Figure 2.

Figure 2 Mapping Headers to Properties or Fields

// This class represents a SOAP header 
[XmlRoot("Foo", Namespace = "https://msdn.microsoft.com/hows")]
public class Foo: SoapHeader {
    public string Baz;
}
public class SampleService: WebService {
    // If a SOAP message's Header element includes a {https://
    // msdn.microsoft.com/hows}Foo element, it will be deserialized into 
    // this field before the target message runs 
    Foo foo;
    // The [SoapHeader] attribute tells the message dispatcher to use the 
    // field foo to deserialize the appropriate header if it appears; the 
    // dispatcher uses reflection to determine which header (if any) to map 
    // to foo 
    [WebMethod][SoapHeader("foo")] public string Bar() {
        StringBuilder sb = new StringBuilder("Bar method ran, ");
        // The Bar method can access the foo field when it runs; if the 
        // request message did not include 
        // a {https://msdn.microsoft.com/hows}Foo header, foo will be null 
        if (this.foo != null) 
          sb.Append("foo is null");
        else 
          sb.Append(string.Format("foo.Baz is {0}", foo.Baz));
        return sb.ToString();
    }
}

The Bar method is marked with a [SoapHeader] attribute that refers to the field named foo. The message dispatcher uses reflection and discovers that foo is an instance of Foo, a subclass of System.Web.Services.Protocols.SoapHeader. The dispatcher knows that all SoapHeader-derived types represent XML elements that may appear inside a request message's Header element. In this case, the [XmlRoot] attribute tells the dispatcher that the Foo class represents the Foo header from the https://msdn.microsoft.com/hows namespace. If that header appears in the header of a request message, the dispatcher then deserializes it into foo, thus making it available when Bar runs.

The SoapHeader base class (not to be confused with the SoapHeaderAttribute class) has a Boolean property called MustUnderstand. When the message dispatcher deserializes a header to a field or property, it checks to see if the header is mandatory (that is, it checks to see if the XML element has a mustUnderstand attribute whose value is either 1 or true). If it is, the dispatcher sets the SoapHeader.MustUnderstand property of the corresponding field or property to true. A method can examine this field to determine whether a header is mandatory or optional. A modified version of the Bar method that indicates whether a given Foo header was mandatory is shown in Figure 3.

Figure 3 MustUnderstand Property

[WebMethod][SoapHeader("foo")] public string Bar() {
    StringBuilder sb = new StringBuilder("Bar method ran, ");
    if (this.foo != null) 
      sb.Append("foo is null");
    else {
        // Note that the generated string now includes 
        // information about whether header was mandatory or optional 
        sb.Append(string.Format("foo.Baz is {0} and {1}", 
          foo.Baz, foo.MustUnderstand ? "mandatory" : "optional"));
    }
    return sb.ToString();
}

The Web Services message dispatcher uses the SoapHeader.MustUnderstand property to tell your code that a given header is mandatory. But the SOAP specification says (vaguely or not, depending on the version) that if a mandatory header is not understood, a fault must be returned. That means that your code has to tell the dispatcher which headers it understood. To that end, the SoapHeader class has a second property called DidUnderstand. After a method call completes, the plumbing checks to see that all mandatory headers were understood by examining the DidUnderstand property of every mandatory SoapHeader-derived field or property used during the course of that method. If any of them have a MustUnderstand value of true and a DidUnderstand value of false, a fault is generated indicating that a mandatory header was not understood.

Figure 4 shows a third implementation of the Bar method that sets the DidUnderstand property of the foo field whenever the header is mandatory.

Figure 4 DidUnderstand Property

[WebMethod][SoapHeader("foo")] public string Bar() {
    StringBuilder sb = new StringBuilder("Bar method ran, ");
    if (this.foo != null) 
      sb.Append("foo is null");
    else {
        sb.Append(string.Format("foo.Baz is {0} and {1}", 
          foo.Baz, foo.MustUnderstand ? "mandatory" : "optional"));
        // tell dispatcher that mandatory header 
        // was understood so plumbing doesn't generate a fault 
        if (foo.MustUnderstand) 
          foo.DidUnderstand = true;
    }
    return sb.ToString();
}

Expecting the Unexpected

At first glance, the MustUnderstand/DidUnderstand model may seem quite reasonable, but it has a significant limitation. The fundamental problem is that sometimes a client may send you mandatory headers that you were not expecting. For instance, my sample service may expect that the Foo header is optional, but it is coded to work correctly even if the client makes the header mandatory. This is easy to do with any header that your service is expecting. If you map a header to a field or property, just make sure that you set its DidUnderstand value to true whenever necessary. (Or, for safety's sake, set it to true in all cases—it won't hurt anything when a header is optional.)

But what about headers your service is not expecting? What if your client decides to slip in a mandatory header that your service has never heard of? If your service doesn't know about a header, it can't have mapped it to a specific field or property, and without that mapping there is no way to know if an unknown header is mandatory or to indicate that it was understood. At the same time, the plumbing has to enforce the SOAP specification's requirement that mandatory headers be processed correctly. So what happens?

Optimally, if the dispatcher detects a mandatory header that it can't map to a field or property, it will generate a fault instead of executing the code. Of course, this solution assumes that all mandatory headers are mapped to members within your class. And that isn't a bad assumption, given the general header processing model. Unfortunately, that's not the way it works. Instead, it's your job to map unknown headers to a field or property too and see if there are any mandatory headers you don't understand. The System.Web.Services.Protocols.SoapUnknownHeader class exists for this very purpose.

SoapUnknownHeader is a subclass of SoapHeader that the Web Services message dispatcher uses to represent any header that is not mapped to a specific field or property. Figure 5 shows a new version of my sample service that handles unknown headers.

Figure 5 SOAPUnknownHeader

public class SampleService: WebService {
    Foo foo;
    // array to hold unknown headers 
    SoapUnknownHeader unknownHeaders[];
    // second SoapHeader attribute tells the message dispatcher to map all 
    // headers except Foo to unknown headers array 
    [WebMethod][SoapHeader("foo")][SoapHeader("unknownHeaders")]
    public string Bar() {
        // check all expected headers and mark as understood 
        if (foo != null && foo.MustUnderstand) foo.DidUnderstand = true;
        // check all unexpected headers and fail if any are mandatory 
        foreach(SoapUnknownHeader header in unknownHeaders) {
            // if a header is mandatory... if (header.MustUnderstand) { 
            // build string describing problem string 
            err = string.Format("Mandatory header: {{{0}}}{1}", 
              header.Element.NamespaceURI, header.Element.LocalName);
            // throw mustUnderstand fault, as required by SOAP specification
            throw new SoapException(err, new XmlQualifiedName("mustUnderstand", 
              "https://schemas.xmlsoap.org/soap/envelope/"));
        }
    }
    // do actual work after header processing is done 
    StringBuilder sb = new StringBuilder("Bar method ran, ");
    if (this.foo != null) 
      sb.Append("foo is null");
    else 
      sb.Append(string.Format("foo.Baz is {0} and {1}", foo.Baz, 
      foo.MustUnderstand ? "mandatory" : "optional"));
    return sb.ToString();
}
}

As with other SoapHeader-derived types, you use the [SoapHeader] attribute to tell the message dispatcher what to do with any headers it can't otherwise process. The new version of the Bar method includes an attribute that tells the plumbing to map any unknown headers to the unknownHeaders field (which needs to be an array, in case there is more than one unknown header). The method begins with header processing code. First, it marks any mandatory Foo header (mapped to the foo field) as understood. Second, if the client sent any unknown headers (mapped to the unknownHeaders field), the method checks to see if any of them are mandatory. If it finds a mandatory header that it doesn't understand, it generates a MustUnderstand SOAP fault, as defined by the SOAP specification.

I'm willing to bet that the vast majority of ASP.NET Web Services that have been written and deployed to date do not explicitly check for unknown mandatory headers. That means that if a client sends a message with a mandatory header that has not been mapped to a field or property, there will be no way for a method to mark the header as understood. The result is that the dispatcher will generate a fault, but not until after the service executes! That's a direct violation of the SOAP specification, which says that a message must not be processed if mandatory headers are not understood. Practically, it means that your service will run, think that everything succeeded, and the client will receive a SOAP fault.

In short, if you want your service to conform to the SOAP specification, you need to do some work to handle unknown mandatory headers correctly.

Solving the Problem Once and for All

The SoapUnknownHeader code in Figure 5 solves the unknown mandatory header problem. However, including that code in every method you expose as a Web Service would be tedious. Luckily, a much simpler solution is possible: put it in a SoapExtension. The complete source code for a MustUnderstandExtension that detects the presence of unknown mandatory headers before a service executes is shown in Figure 6.

Figure 6 MustUnderstandExtension

// MustUnderstandExtension checks for unknown mandatory 
// headers and generates a fault if any are found 
public class MustUnderstandExtension: SoapExtension {
    public override object GetInitializer(Type serviceType) {
        return null;
    }
    public override object GetInitializer(LogicalMethodInfo methodInfo, 
      SoapExtensionAttribute attribute) {
        return null;
    }
    public override void Initialize(object initializer) {}
    // ProcessMessage looks for message headers and 
    // and handles unknown mandatory headers correctly 
    public override void ProcessMessage(SoapMessage message) {
        switch (message.Stage) {
            // process headers after they've been deserialized 
            // into data members case 
            SoapMessageStage.AfterDeserialize: {
                foreach(SoapHeader header in message.Headers) {
                    if (header.GetType() != typeof(SoapUnknownHeader)) {
                        // if header is *known* (e.g., Foo), mark it as understood 
                        header.DidUnderstand = true;
                    } else if (header.MustUnderstand) {
                        // if header is of *unknown* AND mandatory, generate a fault 
                        SoapUnknownHeader unknownHeader = 
                          (SoapUnknownHeader) header;
                        string err = 
                          string.Format("Mandatory header: {{{0}}}{1}", 
                          unknownHeader.Element.NamespaceURI, 
                          unknownHeader.Element.LocalName);
                        throw new SoapException(err, 
                          new XmlQualifiedName("mustUnderstand", 
                          "https://schemas.xmlsoap.org/soap/envelope/"));
                    }
                }
            }
            break;
        }
    }
}

The ProcessMessage method scans all the headers found in a request message. It marks any known header as understood (that is, any header that has been mapped to a field or property of some subclass of SoapHeader other than SoapUnknownHeader). If it encounters an unknown mandatory header, it generates a SOAP MustUnderstand fault. This works even if a Web Service class does not include a field or property of type array of SoapUnknownHeader. This solution assumes that any known headers are in fact understood and that all mandatory headers are known, but, again, that's a reasonable assumption.

Given the general applicability of this extension, it makes sense to configure it for an entire Web Service, not just a single method. You can do that by adding the following configuration info to your web.config (or even machine.config) file:

<configuration>
    <system.web>
        <webServices>
            <soapExtensionTypes>
                <add type="MustUnderstand.MustUnderstandExtension, MustUnderstand" 
                  priority="1" group="0" />
            </soapExtensionTypes>
        </webServices>
    </system.web>
</configuration>

Once that's done, the SampleService I just presented can revert to the one shown in Figure 7. Note that the code for processing unknown headers has been removed. With the MustUnderstandExtension in place, it is no longer necessary.

Figure 7 SimpleService with MustUnderstandExtension

[WebMethod][SoapHeader("foo")] public string Bar() {
    StringBuilder sb = new StringBuilder("Bar method ran, ");
    if (this.foo != null) sb.Append("foo is null");
    else {
        // Note that the generated string now includes information about 
        // whether header was mandatory or optional 
        sb.Append(string.Format("foo.Baz is {0} and {1}", 
          foo.Baz, foo.MustUnderstand ? "mandatory" : "optional"));
    }
    return sb.ToString();
}

What About WSE?

A growing number of ASP.NET Web Services use the Web Services Enhancements (WSE) for the Microsoft® .NET Framework class library to add support for advanced Web Service protocols to their applications. The WSE has a very different model for processing headers, so if you're using it, you need to solve the mandatory unknown header problem a different way.

When a message arrives in a process, the WSE passes it through a series of input filters. Each input filter consumes one or more headers, removes them from the SOAP message, and exposes the data they carry as part of the SoapContext object model the WSE associates with the message. While it is possible to mix this header processing model with the ASP.NET Web Services header processing model, it's simpler to use the WSE model alone.

The easiest way to solve the mandatory unknown header problem with the WSE is to implement a custom input filter like the one shown in Figure 8. The MustUnderstandInputFilter uses XPath to see if a message contains a mandatory header. If it does, the filter generates a fault. The assumption is that by the time this input filter runs, previous input filters have processed and removed all mandatory headers from the message. (As a side note, it is important that anyone writing custom input filters for the WSE should adopt this approach; otherwise solving the mandatory unknown header problem will be more difficult than necessary.) If you configure this input filter using the standard WSE technique, it will run after all the WSE's built-in input filters. The configuration entry you need is shown in the following code:

<configuration>
    <microsoft.web.services>
        <filters>
            <input>
                <add type="MustUnderstandInputFilter, MustUnderstand" />
            </input>
        </filters>
    </microsoft.web.services>
</configuration>

Figure 8 Custom Input Filter

public class MustUnderstandInputFilter : SoapInputFilter
{
    public override void ProcessMessage(SoapEnvelope msg)
    {
        // create and initialize namespace manager for XPath engine 
        XmlNamespaceManager namespaces = new XmlNamespaceManager(msg.NameTable); 
        namespaces.AddNamespace("soap", "https://schemas.xmlsoap.org/soap/envelope/");
        // look in message for any mandatory header element 
        XmlNode node = 
            msg.SelectSingleNode("/soap:Envelope/soap:Header/*[@soap:mustUnderstand [. = '1' or . = 'true']]", 
            namespaces);
        // if any mandatory headers are found, generate a fault 
        if (node != null)
        {
            string err = string.Format("Mandatory header: {{{0}}}{1}", 
                node.NamespaceURI, node.LocalName); 
            throw new SoapException(err, 
                new XmlQualifiedName("mustUnderstand", "https://schemas.xmlsoap.org/soap/envelope/"));
        }
    }
}

Where are We?

Much of the power of the SOAP protocol comes from its extensibility. The header element is critical to the further development of Web Services; all the new advanced protocols for security, transactions, and other features are implemented using headers. However, this flexibility comes at a price. In loosely coupled systems, you need to expect that a client will send you things you didn't expect and you need to deal with them appropriately. In the case of mandatory headers, that means detecting and processing them before your Web Service code executes. While the ASP.NET Web Services plumbing does not do this automatically, its header processing model allows you to solve the problem yourself, and the solution is pretty simple.

Send your questions and comments for Tim to housews@microsoft.com.

Tim Ewaldis a Program Manager for XML Web Services at Microsoft. He is currently redesigning the MSDN architecture around Web Services, and writing and speaking about that technology. He is the author of Transactional COM+: Designing Scalable Applications (Addison-Wesley, 2001). Reach Tim at tewald@microsoft.com.