Managing State

Defining and Processing SOAP Headers

Recall that SOAP headers are used to contain metadata related to the body of the message. ASP.NET provides a mechanism for defining and processing SOAP headers. In this section, I explain how to formally define SOAP headers that are exposed by an ASP.NET Web service. I also explain how to process SOAP headers that are received from the client.

You can define a new header by deriving from the SoapHeader class. You can associate the new header with a particular endpoint within the Web service by using the SoapHeader attribute. Table 6-7 lists the properties exposed by the SoapHeader class.

Table 6-7  Properties of the SoapHeader Class

Property

Description

Actor

Indicates the intended recipient of the header

DidUnderstand

Indicates whether a header whose mustUnderstand attribute is true was understood and processed by the recipient

EncodedMustUnderstand

Indicates whether a header whose mustUnderstand attribute is true and whose value is encoded was understood and processed by the recipient

MustUnderstand

Indicates whether the header must be understood and processed by the recipient

By default, the name of the class derived from SoapHeader will become the name of the root header element, and any public fields or properties exposed by the class will define elements within the header.

As I mentioned earlier in the chapter, price quotes from other services on the Web are often time-delayed and can be more than 20 minutes old. Price quotes obtained using the InstantQuote Web method are not subject to these delays. Because the InstantQuote Web method obtains the price that a particular stock is currently trading at on the exchange's floor, I feel that I can charge the client $1.50 for each quote. I will therefore require every SOAP request made to the InstantQuote Web method to be accompanied by the Payment SOAP header, which will contain the client's credit card information. This information will be used to pay the $1.50 transaction fee.

SOAP headers are defined by classes derived from the SoapHeader class. Elements within the header are defined by public fields or read/writable properties. Here is the definition of the Payment header:

[XmlRoot("Payment")] public class SoapPaymentHeader : SoapHeader {     private string nameOnCard;     private string creditCardNumber;     private CardType creditCardType;     private DateTime expirationDate;     public enum CardType     {         VISA,         MC,         AMX,         DISCOVER     }     public string NameOnCard      {         get { return nameOnCard; }         set { nameOnCard = value; }     }     public string CreditCardNumber      {         get { return creditCardNumber; }         set { creditCardNumber = value; }     }     public CardType CreditCardType      {         get { return creditCardType; }         set { creditCardType = value; }     }     public DateTime ExpirationDate      {         get { return expirationDate; }         set { expirationDate = value; }     } }

The preceding class definition defines a SOAP header named Payment with four child elements: nameOnCard, creditCardNumber, creditCardType, and expirationDate. The XmlRoot attribute is used to instruct the XML Serializer to name the header element Payment instead of the class name. I will cover the XML Serializer in Chapter 7.

Once the payment has been received and the Web method has been processed, I want to send a header containing a confirmation of the purchase back to the client. SOAP headers sent from the server to the client are defined in the same manner. The following code defines a header containing the amount that was charged as well as the reference number of the credit card transaction:

[XmlRoot("Receipt")] public class SoapReceiptHeader : SoapHeader {     private double amount;     private int referenceNumber;     public double Amount      {         get { return amount; }         set { amount = value; }     }     public int ReferenceNumber      {         get { return referenceNumber; }         set { referenceNumber = value; }     } }

Once you define the headers, the next step is to associate them with the InstantQuote Web method. The SoapHeader attribute is used to associate a SOAP header with a Web method.

A public member variable is added to the WebService class to hold an instance of the class derived from the SoapHeader class. The name of the member variable is then communicated to the ASP.NET runtime via the SoapHeader attribute. Here is the Securities class definition:

    public class Securities : WebService     {         public PaymentHeader paymentHeader;         public ReceiptHeader receiptHeader = new ReceiptHeader();         public enum CurrencyType         {             US_DOLLARS,             UK_POUNDS,             GE_DEUTSCHMARKS         }         [WebMethod]         [SoapHeader("paymentHeader",          Direction=SoapHeaderDirection.In, Required=true)]         [SoapHeader("receiptHeader",          Direction=SoapHeaderDirection.Out, Required=true)]         public double InstantQuote(string symbol, CurrencyType targetCurrency)         {             double price = 0;             // Implementation ...             return Convert(price, targetCurrency);         }         private double Convert(double usPrice, CurrencyType targetCurrency)         {             double targetCurrencyPrice = usPrice;             // Implementation...             return targetCurrencyPrice;         }     }

I create two member variables to hold the data contained in the Payment and the Receipt SOAP headers. I create an instance of the SoapReceiptHeader class because the Receipt header will be passed to the client. I do not create an instance of the SoapPaymentHeader class because the ASP.NET runtime is responsible for creating this object and populating its properties with the data contained within the Payment header received from the client.

Next I add two SoapHeader attributes to declare that the headers should formally be described as part of the Web method. The constructor of the SoapHeader attribute takes a string that contains the name of the public member variable that should be associated with the SOAP header.

I also set two optional properties, Direction and Required. The Direction property indicates whether the client or the server is supposed to send the header. The Required property indicates whether the property must appear within the SOAP message. Let's discuss each property in detail.

The Direction property indicates whether the header is received from the client, sent to the client, or both. The Payment header is received from the client, and the Receipt header is sent to the client, so I set the Direction property to SoapHeaderDirection.In and SoapHeaderDirection.Out, respectively. If a SOAP header is received from the client and then sent back to the client, the value of the Direction property should be set to SoapHeaderDirection.InOut.

The Required property indicates whether the header must appear within the SOAP message to be considered valid by the Web service. If the Required property is not set within the attribute tag, the default value is true. Inbound headers marked as required must be included in the request message; otherwise, a SoapException will be thrown by the ASP.NET runtime.

Because a Payment header must be included in every request and a matching Receipt header must be included in every response, I set the Required property to true for both SOAP headers. The Required property has no bearing on whether the header will be processed or even understood by the recipient of the message. For example, the Receipt header must always be passed back to the client, but the client is not required to process the header.

Now that I have associated the Payment and Receipt headers with the Web method, the next task is to process the Payment headers. The following code uses the information within the Payment header to bill the client's credit card using an arbitrary credit card processing component:

public class Securities : WebService {     public SoapPaymentHeader paymentHeader;     public SoapReceiptHeader receiptHeader = new SoapReceiptHeader();     public enum CurrencyType     {         US_DOLLARS,         UK_POUNDS,         GE_DEUTSCHMARKS     }     [WebMethod]     [SoapHeader("payment", Direction=SoapHeaderDirection.In, Required=true)]     [SoapHeader("receipt", Direction=SoapHeaderDirection.Out, Required=true)]     public double InstantQuote(string symbol, CurrencyType targetCurrency)     {         // Declare and initialize variables.         double    price = 0;         int       merchantNumber = 123456789;         double    fee = 1.50;         int       referenceNumber = 0;         // Apply the fee to the client's credit card.         CreditCardProcessor creditCardProcessor =          new CreditCardProcessor(merchantNumber);         referenceNumber =          creditCardProcessor.Bill(fee, paymentHeader);         // Verify that the credit card was processed.         if(referenceNumber > 0)         {             // Set the return header information.             receiptHeader.ReferenceNumber = referenceNumber;             receiptHeader.Amount = fee;         }         else         {             throw new SoapException("The Payment header was either              missing or contained invalid information.",              SoapException.ClientFaultCode);         }         // Implementation...         return Convert(price, targetCurrency);     } }

The preceding code uses the information within the Payment header to charge the required fee to the client's credit card. If the credit card is successfully processed, the Receipt header will be populated with the reference number of the transaction as well as the amount that was charged to the card. If the credit card is not successfully processed, a SoapException will be raised. Because the exception is a result of insufficient information sent from the client, the fault code is set to Client.

The current implementation has one problem related to processing headers. In Chapter 3, I said that the client has the ability to send additional headers other than what was expected. The client can also set the mustUnderstand attribute to true on these additional headers. I will discuss how to process headers you were not expecting shortly. But let's first discuss setting and analyzing the mustUnderstand attribute for a particular SOAP header.

The MustUnderstand property exposed by the SoapHeader class is fundamentally different from the Required property set by the SoapHeader attribute (which I discussed earlier). The Required property specifies whether a header must be included within a message. If the header resides within a message, the MustUnderstand property is used to specify whether the recipient of the message must understand and process the header. Let's discuss these two properties in detail.

The Required property specifies whether the header must be included within the message exchanged between the client and the server for a particular Web method. Because this property is specific to the interface of a Web service, changes to it are reflected in the WSDL document. If the Required property is set to true, the required attribute within the header element defined by the SOAP binding extensibility elements will be set to true. Finally, if a Web method defines a SOAP header as required, ASP.NET cannot support the HTTP GET/POST bindings.

The MustUnderstand property specifies whether a specific header within a message must be understood and processed by the client. Because this property is specific to a particular exchange between the client and the server, changes to it are reflected in the SOAP message itself. If the MustUnderstand property is set to true, the mustUnderstand attribute within an instance of the header will be set to true.

The DidUnderstand property of an object derived from SoapHeader notifies the ASP.NET runtime to tell the client which headers were processed by the Web method.

The default value of the DidUnderstand property is true for headers formally defined by the Web method, so make sure that there cannot be a code path in which the method returns without processing a header. The client might have set the mustUnderstand attribute to true. If so, this is considered an error if the Web method does not throw a SoapException.

In the case in which a header might not be processed, you might want to set the DidUnderstand property to false at the beginning of the Web method. Once the header is processed, set the DidUnderstand property back to true.

Another option is to include the value of the MustUnderstand property in the decision about whether to process the header. For example, the InstantQuote method sets the Required property of the Payment header to true. However, the InstantQuote method is responsible for processing the header only if the MustUnderstand property is true. Let's say that if the administrator invokes the InstantQuote Web method, the Payment header should not be processed unless the MustUnderstand property is true, as shown here:

// Apply the fee to the client's credit card only if the user is not  // the administrator or if the header must be processed. if(User.Identity != "Administrator"   paymentHeader.MustUnderstand) {     CreditCardProcessor creditCardProcessor =      new CreditCardProcessor(merchantNumber);     referenceNumber = creditCardProcessor.Bill(fee, payment); }

I want to discuss one final point about the MustUnderstand and DidUnderstand properties. After the Web method returns, the ASP.NET runtime will determine whether any headers passed by the client containing a must Understand attribute set to true also have their associated DidUnderstand property set to false. If this is the case, the ASP.NET runtime will automatically throw a SoapException. The Web method might have code that attempts to undo actions done on a client's behalf before throwing the exception. Because this exception is thrown after the Web method has returned, this code will never execute.

Let's say a client calls the InstantQuote Web method within the context of a transaction. The client passes a Transaction header along with the Payment header and sets its mustUnderstand attribute to true. Because the previous implementation does not check for the presence of a Transaction header, the Web service processes the request, including billing the client's credit card. After the method returns, the ASP.NET runtime notices that the Transaction header's DidUnderstand property is set to false and throws an exception. In this case, the client does not receive the quote but will still be billed the $1.50 transaction fee. This scenario would result in one unhappy customer.

There are at least two ways to avoid this adverse side effect. If the affected resources are all managed by a DTC Resource Manager, you can set the TransactionOption property of the WebMethod attribute to Required. Once the ASP.NET runtime throws an exception, the transaction will be aborted and all changes rolled back. If the CreditCardProcessor component can participate in a DTC-controlled distributed transaction, the fee charged to the card will automatically be rolled back.

Another option is to verify that all headers received by the Web service with a mustUnderstand attribute set to true have been processed before the Web method returns. Catching headers that must be understood but cannot be processed by the Web service early on within the Web method can potentially save unnecessary processing cycles. If the Web method does not know how to process one of the headers passed to it, it can take appropriate action before throwing an exception. In the next section, I discuss how to examine the Must Understand property of unknown headers.

Processing Unknown Headers

The ASP.NET page framework provides a mechanism for inspecting and processing headers that are not formally defined by the Web method. You can, for example, determine up front whether there are any unknown headers that have their mustUnderstand attribute set to true. If there are any headers that must be understood, but that the Web method does not know how to process, you can throw an appropriate SoapException up front.

The SoapUnknownHeader class is derived from SoapHeader and can be used to inspect or process headers not formally defined by the Web method. Because the SoapUnknownHeader class is derived from SoapHeader, it exposes properties such as MustUnderstand and DidUnderstand.

An object of type SoapUnknownHeader is loosely typed because the only additional property defined is Element, which is of type XmlElement. The Element property serves as an entry point to the root element of the header. You can use the XML DOM to interrogate the contents of the header.

You can associate the SoapUnknownHeader class with a Web method using the SoapHeader attribute (just as you can with any other class that derives from SoapHeader). If more than one header can be received by the Web method, as is the case with unknown headers, the property associated with the Web method can be an array.

Recall that the previous implementation of the InstantQuote Web method had a flaw. If an unknown header that must be understood by the Web service is received by the client, credit card users will be charged the fee but will receive a SOAP fault automatically generated by the ASP.NET runtime. To solve this problem, the following example obtains a list of unknown headers, iterates through the list, and then throws a SoapException once the first SoapUnknownHeader is encountered that has its MustUnderstand property set to true:

[WebMethod] [SoapHeader("paymentHeader", Direction=SoapHeaderDirection.In, Required=true)] [SoapHeader("receiptHeader", Direction=SoapHeaderDirection.Out, Required=true)] [SoapHeader("unknownHeaders", Required=false)] public double InstantQuote(string symbol, CurrencyType targetCurrency) {     // Declare and initialize variables.     double  price = 0;     int     merchantNumber = 123456789;     double  fee = 1.50;     int     referenceNumber = 0;     // Check to see whether any unknown headers must be processed.     foreach(SoapUnknownHeader header in unknownHeaders)     {         if(header.MustUnderstand)         {             string message = "The " + header.Element.Name +              " header could not be processed.";             throw new SoapException(message,              SoapException.MustUnderstandFaultCode);         }     }     // The rest of the implementation... }

The Web method checks for unknown headers that must be understood by the client before the credit card is processed. If a header that must be understood cannot be processed, the client will not be charged the fee for using the Web service.



Building XML Web Services for the Microsoft  .NET Platform
Building XML Web Services for the Microsoft .NET Platform
ISBN: 0735614067
EAN: 2147483647
Year: 2002
Pages: 94
Authors: Scott Short

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net