One of the more common forms of extending the capabilities of SOAP messages is to add metadata of the request to the SOAP message itself. The metadata is usually added to a section of the SOAP envelope called the SOAP header. Figure 26-18 shows the structure of a SOAP message.
Figure 26-18
The entire SOAP message is referred to as a SOAP envelope. Contained within the SOAP message is the SOAP body - a piece of the SOAP message that you have been working with in every example thus far. It is a required element of the SOAP message.
The one optional component of the SOAP message is the SOAP header. It is the part of the SOAP message in which you can place any metadata about the overall SOAP request instead of incorporating it in the signature of any of your WebMethods. It is important to keep metadata separate from the actual request.
What kind of information does it contain? It could include a lot of things. One of the more common items placed in the SOAP header is any authentication/authorization functionality required to consume your Web Service or to get at specific pieces of logic or data. Usernames and passwords are good examples of what you might include inside the SOAP headers of your messages.
You can build upon the sample HelloWorld Web service presented in the default .asmx page when it is first pulled up in Visual Studio. Name the new .asmx file HelloSoapHeader.asmx. First, add a class that is an object representing what is to be placed in the SOAP header by the client, as shown in the following example:
Public Class HelloHeader Inherits System.Web.Services.Protocols.SoapHeader Public Username As String Public Password As String End Class
The class, representing a SOAP header object, has to inherit from the SoapHeader class from System.Web.Services.Protocols.SoapHeader. The SoapHeader class serializes the payload of the <soap:header> element into XML for you. In this example, the SOAP header requires two elements - a username and a password, both of type String. The names you create in this class are those used for the sub-elements of the SOAP header construction, so it is important to name them descriptively.
The following code shows the Web Service class that instantiates an instance of the HelloHeader class:
<WebService(Namespace:="http://www.wrox.com/helloworld")> _ <WebServiceBinding(ConformsTo:=WsiProfiles.BasicProfile1_1, _ EmitConformanceClaims:=True)> _ Public Class HelloSoapHeader Inherits System.Web.Services.WebService Public myHeader As HelloHeader <WebMethod(), SoapHeader("myHeader")> _ Public Function HelloWorld() As String If (myHeader Is Nothing) Then Return "Hello World" Else Return "Hello " & myHeader.Username & ". " & _ "<br>Your password is: " & myHeader.Password End If End Function End Class
The Web Service, HelloSoapHeader, has a single WebMethod - HelloWorld. Within the Web Service class, but outside of the WebMethod itself, create an instance of the SoapHeader class:
Public myHeader As HelloHeader
Now that you have an instance of the HelloHeader class that you created earlier called myHeader, you can use that instantiation in your WebMethod. Because Web Services can contain any number of WebMethods, it is not necessary for all WebMethods to use an instantiated SOAP header. You specify whether a WebMethod uses a particular instantiation of a SOAP header class by placing the SoapHeader attribute before the WebMethod declaration:
<WebMethod(), SoapHeader("myHeader")> _ Public Function HelloWorld() As String ' Code here End Function
Here, the SoapHeader attribute takes a string value of the name of the instantiated SoapHeader class - in this case, myHeader.
The WebMethod actually makes use of the myHeader object. If the myHeader object is not found (meaning the client did not send a SOAP header with the constructed SOAP message), a simple “Hello World” is returned. However, if values are provided in the SOAP header of the SOAP request, those values are used in the returned string value.
It isn’t difficult to build an ASP.NET application that makes a SOAP request to a Web Service using SOAP headers. As with the Web Services that don’t include SOAP headers, you make a Web reference to the remote Web Service directly in Visual Studio.
For the ASP.NET page, create a simple page with a single Label control. The output of the Web Service is placed in this control. Following is the code for the ASP.NET page:
<%@ Page Language="VB" %> <script runat="server"> Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Dim ws As New localhost.HelloSoapHeader() Dim wsHeader As New localhost.HelloHeader() wsHeader.Username = "Bill Evjen" wsHeader.Password = "Bubbles" ws.HelloHeaderValue = wsHeader Label1.Text = ws.HelloWorld() End Sub </script> <html xmlns="http://www.w3.org/1999/xhtml" > <head runat="server"> <title>Working with SOAP headers</title> </head> <body> <form runat="server"> <div> <asp:Label Runat="server"></asp:Label> </div> </form> </body> </html>
Two objects are instantiated. The first is the actual Web Service, HelloSoapHeader. The second, which is instantiated as wsHeader, is the SoapHeader object. After both of these objects are instantiated and before making the SOAP request in the application, you construct the SOAP header. This is as easy as assigning values to the Username and Password properties of the wsHeader object. After these properties are assigned, you associate the wsHeader object to the ws object through the use of the HelloHeaderValue property. After you have made the association between the constructed SOAP header object and the actual WebMethod object (ws), you can make a SOAP request, just as you would normally do:
Label1.Text = ws.HelloWorld()
Running the page produces the result shown in Figure 26-19.
Figure 26-19
Note that the SOAP request reveals that the SOAP header was indeed constructed into the overall SOAP message, as shown in the following SOAP request:
<?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> <HelloHeader xmlns="http://www.wrox.com/helloworld/"> <Username>Bill Evjen</Username> <Password>Bubbles</Password> </HelloHeader> </soap:Header> <soap:Body> <HelloWorld xmlns="http://www.wrox.com/helloworld/" /> </soap:Body> </soap:Envelope>
This returns the SOAP response shown here:
<?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> <HelloWorldResponse xmlns="http://www.wrox.com/helloworld/"> <HelloWorldResult>Hello Bill Evjen. Your password is: Bubbles</HelloWorldResult> </HelloWorldResponse> </soap:Body> </soap:Envelope>
Most Web Services use SOAP version 1.1 for the construction of their messages. However, SOAP 1.2 became a W3C Recommendation in June 2003 (see www.w3.org/TR/soap12-part1/). The nice thing about XML Web Services in the .NET Framework 2.0 platform is that they are capable of communicating in both the 1.1 and 1.2 versions of SOAP.
In an ASP.NET application that is consuming a Web Service, you can control whether the SOAP request is constructed as a SOAP 1.1 message or a 1.2 message. The next example changes the previous example to use SOAP 1.2 instead of the default setting of SOAP 1.1:
<%@ Page Language="VB" %> <script runat="server"> Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Dim ws As New localhost.HelloSoapHeader() Dim wsHeader As New localhost.HelloHeader() wsHeader.Username = "Bill Evjen" wsHeader.Password = "Bubbles" ws.HelloHeaderValue = wsHeader ws.SoapVersion = System.Web.Services.Protocols.SoapProtocolVersion.Soap12 Label1.Text = ws.HelloWorld() End Sub </script>
This example first provides an instantiation of the Web Service object and uses the new SoapVersion property. The property takes a value of System.Web.Services.Protocols.SoapProtocolVersion.Soap12 to work with SOAP 1.2 specifically. With this bit of code in place, the SOAP request takes the structure shown here:
<?xml version="1.0" encoding="utf-8"?> <soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <soap:Header> <HelloHeader xmlns="http://www.wrox.com/helloworld/"> <Username>Bill Evjen</Username> <Password>Bubbles</Password> </HelloHeader> </soap:Header> <soap:Body> <HelloWorld xmlns="http://www.wrox.com/helloworld/" /> </soap:Body> </soap:Envelope>
One difference between the two examples is the xmlns:soap namespace that is used. The difference actually resides in the HTTP header. Comparing the SOAP 1.1 and SOAP 1.2 messages, you can see a difference in the Content-Type attribute. In addition, the SOAP 1.2 HTTP header does not use the soa paction attribute because this is now combined with the Content-Type attribute.
You can turn off either SOAP 1.1 or SOAP 1.2 capabilities with the Web Services that you build by making the proper settings in the web.config file, as illustrated here in this snippet of configuration code:
<configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0"> <system.web> <webServices> <protocols> <remove name="HttpSoap"/> <!-- Removes SOAP 1.1 abilities --> <remove name="HttpSoap1.2"/> <!-- Removes SOAP 1.2 abilities --> </protocols> </webServices> </system.web> </configuration>