The Simple Object Access Protocol (SOAP) is an XML-based protocol that allows data to be sent in a structured manner between Web Service providers and consumers. SOAP is one of the key elements that allows the exchange of distributed data between different languages and operating systems. I'll provide you with a general overview of SOAP, but if you want to read the complete specification, it's available at theW3C Web site. For more information on SOAP, see " Get a Handle on SOAP Headers " and " Web Services Interoperability and SOAP ."
Instead of covering several different aspects of SOAP, I'll focus specifically on how you can customize SOAP messages in the .NET platform through the use of SOAP headers to simplify Web Service access. You'll see that .NET provides native SOAP header classes, which can simplify Web Service application development and make using headers a snap.
The SOAP body portion of a message contains the majority of the details and data in a SOAP request or response, but the SOAP header portion also can play an important role in Web Services. You normally use headers when data needs to be sent to a Web Service in a SOAP message but the data is not related directly to a particular Web method's functionality. You could use headers with custom authentication schemes, Web method call tracking and logging, and session management, or you could use them in situations where additional data needs to be returned from a Web Service and the data needs to be separate from any data contained within the SOAP body.To help you understand when to use SOAP headers, I'll demonstrate how to create a custom authentication scheme in a Web Service that exposes customer account data. Each time a request is made for a user's account information, the consumer of the service must prove it has access to the Web method functionality the Web Service exposes. The user's credentials (username, password, etc.) should not be passed to every Web method called because this causes the user's authentication to be tied to the parameters of the Web method. If the authentication scheme changed, you would need to update each Web method, which could result in a large maintenance headache. Instead, you can use SOAP headers to pass the proper user credentials within each SOAP message. By doing this, the credentials are separate from the actual data contained within the SOAP body.
Build a Custom SOAP Header Class
Working with SOAP headers in the .NET platform is relatively straightforward because of the platform's robust support for SOAP. The first step when working with headers is to create a custom header class that derives from the SoapHeader class located within the System.Web.Services.Protocols namespace. This class contains four different properties related to headers. Figure 1 provides a basic description of each property.
Property |
Description |
Actor |
Used to get or set the recipient of the SOAP header. |
DidUnderstand |
Used to get or set whether the Web method understood the SOAP header properly. |
EncodedMustUnderstand |
Used to get or set the MustUnderstand property of the SoapHeader class. When EncodedMustUnderstand is set to a value of 1, the MustUnderstand class will be set to a value of True. |
MustUnderstand |
Used to get or set a Boolean value (True or False), which represents whether the Web Service must process and understand a given SOAP header |
Figure 1. You use the SoapHeader class creating your own custom headers in the .NET platform. SoapHeader contains four properties that help identify whether the header was handled properly. You don't have to use these properties to leverage SOAP headers in .NET.
AuthenticationHeader is the custom SOAP header class that derives from SoapHeader. It provides basic authentication capabilities. AuthenticationHeader resides at the Web Service and is used to extract header information from SOAP messages. The authentication data (also referred to as an authentication token) passed to this custom header class is generated initially by a Web method named Login and sent back to the consumer after he or she first logs in to the Web Service with a proper username and password. See Figure 2 for this login process.
[WebMethod] public string Login(string userName, string password) { User user = new User(); //Validate user. Complete code for the User class is //available with this article's code download. int userID = user.Validate(userName,password,null); if (userID != 0) { //If a user is found, generate auth token (GUID) //and insert it into the database. Return the //authentication token to the consumer. return user.InsertAuthToken(userID).ToString(); } else { //If user is not found, return empty string return String.Empty; } }
Figure 2. The Login Web method validates the username and password against users in a database. If a valid user is found, a unique authentication token is generated and returned to the Web Service consumer.
After logging in, each subsequent SOAP message the consumer sends to the Web Service includes the newly obtained authentication token as a SOAP header. By following this pattern, the actual username and password are sent to the Web Service only once.
Note: A discussion of .NET encryption techniques is beyond the scope of this article, but you probably want to encrypt the username and password passed during the login process so prying eyes can't see them. You can find an in-depth sample of using encryption with Web Services at the XML for ASP.NET Developers website.
Figure 3 shows the complete code for the AuthenticationHeader class. You can see the code contains a property named AuthenticationToken. You can use this property to get or set the value of the authentication token sent back to the Web Service from the logged-in consumer. You also can use the property to ensure any received tokens match a valid user in the database. Invalid tokens cause a SOAP Fault to be thrown.
using System; using System.Web.Services.Protocols; namespace CodeBank.WebServices.SOAPHeaders { //Derive from the SoapHeader class public class AuthenticationHeader : SoapHeader { private string _AuthenticationToken = String.Empty; //This property allows the Web Service to set //and get the SOAP Header authentication token public string AuthenticationToken { get { return _AuthenticationToken; } set { //Ensure we have something other than //an empty Auth Token if (value == String.Empty) { throw new SoapException("No Authentication Token " + "Received",null); //We have a token. Ensure it matches up //with a valid user. } else { User user = new User(); int userID = user.Validate(null,null,value); if (userID == 0) { //Throw SOAP Exception so we don't hit Web Method throw new SoapException("Invalid Authentication " + "Token",null); } } //If no problems are found, set the property value _AuthenticationToken = value; } } } }
Figure 3. The AuthenticationHeader class derives from SoapHeader. As a result, a Web Service consumer can send a SOAP header containing the authentication token, and the Web Service can use this class to read and validate the value.
Put the SoapHeaderAttribute Class to Work
Now that you've created the AuthenticationHeader class, you can apply it to any Web method within the Web Service, so header information sent to the Web Service can be extracted and used. To accomplish this task, you need to add a reference to the custom header class to the Web Service class:
//Web Service Class - SoapHeaderDemo public class SoapHeaderDemo : System.Web.Services.WebService { public AuthenticationHeader AuthHeader; //....More code would follow }
Once you add a reference to AuthenticationHeader, you can use the SoapHeaderAttribute class (located within the System.Web.Services.Protocols namespace) to apply AuthenticationHeader to any Web method, such as the GetAccountDetails method (see Figure 4).
//SOAP header ensures authentication token is //associated with a user [WebMethod] //Header only used as input to a Web Method [SoapHeader("AuthHeader", Direction=SoapHeaderDirection.In, Required=true)] public Account GetAccountDetails(int accountID) { //Pass Auth Token within header into Account constructor //so we can look up UserID Account ad = new Account(accountID,AuthHeader.AuthenticationToken); //Return filled Account object if UserID and //AccountID match up return ad; }
Figure 4. You use the SoapHeaderAttribute class to associate a SOAP header class with a Web method. This example specifies that the AuthHeader object is used to grab the SOAP header request value sent to the Web method and that the header is required.
As the Web Service receives the SOAP request for GetAccountDetails, the code in Figure 3 is invoked, causing the authentication token to be extracted from the SOAP header. Then, this token value is validated against users in the database. Assuming the token matches a user, the Web method is called and the authentication token value (now held in the AuthenticationHeader class's AuthenticationToken property) can be accessed to retrieve the user's account details. You do this by calling the AuthHeader object's AuthenticationToken property:
//Create a new Account object based upon an AccountID //and an authentication token value held in the SOAP //header class. Account ad = new Account(accountID,AuthHeader.AuthenticationToken);
Add SOAP Headers
Now that I've described the Web Service side of things, I'll show you how to take the authentication token received during the login process and add it to the SOAP request made by the Web Service consumer (on the client side). Fortunately, sending the authentication token value to the Web Service as a SOAP header is fairly straightforward because the header details are added automatically to the Web Service proxy the consumer uses. You can generate the proxy by using either VS .NET or the WSDL.exe command-line utility to read the service's WSDL document. This code shows the portion of the proxy class that references the AuthenticationHeader class the Web Service exposes:
//The following public field/property is added //into the proxy class automatically public AuthenticationHeader AuthenticationHeaderValue; //The following class shell is added into the //generated proxy code public class AuthenticationHeader : SoapHeader { public string AuthenticationToken; }
Once the authentication token is received from the Web Service after logging in, the consumer simply needs to reference the AuthenticationHeaderValue property within the proxy and assign a new instance of the AuthenticationHeader class to it. Once this is done, you can assign the token value to pass as a SOAP header by calling the AuthenticationToken property (see Figure 5).
//Create an instance of the proxy object. //Proxy is in //the SoapHeaderDemoService.WebServiceProxy namespace SoapHeaderDemoService.WebServiceProxy.SoapHeaderDemo demo = new SoapHeaderDemoService.WebServiceProxy.SoapHeaderDemo(); //Assign new instance of SOAP header class to the proxy's //AuthenticationHeaderValue property demo.AuthenticationHeaderValue = new SoapHeaderDemoService.WebServiceProxy.AuthenticationHeader(); //Now assign the authentication token value by calling the //AuthenticationHeaderValue object's //AuthenticationToken property demo.AuthenticationHeaderValue.AuthenticationToken = _authToken;
Figure 5. You assign the token value to pass as a SOAP header by calling the AuthenticationToken property.
The code in Figure 5 adds an Authentication header section into the SOAP message that is sent to the Web Service automatically, as you can see in Figure 6.
<?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:Header> <AuthenticationHeader xmlns="http://www.xmlforasp.net"> <AuthenticationToken> a49d0f16-8880-45f3-99dd- 434eb640ecfa </AuthenticationToken> </AuthenticationHeader> </soap:Header> <soap:Body> <GetAccountDetails xmlns="http://www.xmlforasp.net"> <accountID>123456789</accountID> </GetAccountDetails> </soap:Body> </soap:Envelope>
Figure 6. By using the proxy object's AuthenticationHeaderValue property, the .NET Framework adds the header section to the SOAP request sent to the Web Service automatically.
You can see that the Web Service proxy (SoapHeaderDemo in this example) handles nearly every detail involved in adding one or more headers to a SOAP message. This abstraction allows you to focus on developing the application rather than worrying about the different XML technologies involved in creating a properly formatted SOAP message.
SOAP headers can play an important role in SOAP messages. Using headers is as easy as creating a custom class that derives from the SoapHeader class, referencing the custom class in the Web Service, and adding the header to a Web method by using the SoapHeaderAttribute class. On the consumer or client side, little code is required to add a SOAP header into a request. The proxy object does the majority of the work to add header details into SOAP messages. By using headers, you can separate data used by the Web Service but not related directly to functionality exposed by a given Web method.
To see a live demo of the sample application used in this article, visit http://www.xmlforasp.net/codeSection.aspx?csID=73.
The files referenced in this article are available for download.
Get to Know SOAP
SOAP is simply a wire protocol, a way to transfer data between distributed systems using XML technologies and protocols such as HTTP. (For more details, see the W3C SOAP working group's definition at http://www.w3.org/TR/2002/WD-soap12-part1-20020626/.) Each SOAP message is either a request to receive data from a Web Service or a response to the consumer of a Web Service.
SOAP messages generally contain three main parts: an envelope, an optional header, and a body. Figure A shows an example of a SOAP message that contains these three parts.
<?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:Header> <CustomSoapHeader xmlns="http://http://www.xmlforasp.net"> <SoapHeaderDetails> Header Details go here </SoapHeaderDetails> </CustomSoapHeader > </soap:Header> <soap:Body> <GetAccount xmlns="http://www.xmlforasp.net"> <AccountID>123456789</AccountID> </GetAccount> </soap:Body> </soap:Envelope>
Figure A. SOAP messages can contain three different sections. The Body element contains the specifics of the Web Service request or response.
The SOAP Envelope element acts as a wrapper for the data contained within the SOAP message. Envelope normally defines several XML namespaces referenced throughout the message as shown in Figure B. Next, the SOAP Header element allows data not directly associated with a Web method request or response (but needed in the messaging process) to be passed to or from a Web Service. Finally, the SOAP Body element contains the actual data in the SOAP request or response. This data can include the operation or task (called a Web method in .NET) involved in the messaging process, as well as any necessary parameter data passed to or returned by the operation.