This section will focus on some of the esoteric areas of web services. We'll cover topics such as integrating with Windows DNA, and shaping the SOAP/XML document exposed by our web services.
ASP.NET provides several additional attributes (not discussed earlier) that allow you to shape the XML generated by the XML serializer used by ASP.NET. There are two separate types of attributes: those that apply to the XML documents generated by the HTTP-GET and HTTP-POST protocols, and those that apply to SOAP. It is not an error to use the attributes together. The following table lists these attributes, along with a brief description of their purpose:
Attribute | Description |
---|---|
XML Documents: XmlAttribute SOAP Documents: SoapAttribute | Allows you to control the XML attribute representation, or convert an XML element into an attribute |
XML Documents: XmlElement SOAP Documents: SoapElement | Allows you to control the XML element representation, or convert an XML attribute into an element |
XML Documents: XmlArray SOAP Documents: SoapArray | Allows you to treat elements as an XML array |
These attributes allow us to have fine granular control over the shape of the XML document used as part of the HTTP-GET, HTTP-POST, or SOAP responses. These attributes are very straightforward, and are most often used when you return an instance of a class. Returning classes have not been discussed yet, so a brief example is necessary. Consider a simple ASP.NET Web service ( Books.asmx ) that returns a Books class:
<%@ WebService Class="ReturnBooks" %> Imports System.Web.Services Public Class ReturnBooks <WebMethod()> Public Function GetBooks() As Books Dim b As New Books b.Title = "Professional ASP.NET" b.Price = 59.99 b.Description = "This book covers Microsoft's ASP.NET technology " & _ "for building Web Applications" ReDim b.Authors(5) b.Authors(0) = "Alex Homer" b.Authors(1) = "David Sussman" b.Authors(2) = "Rob Howard" b.Authors(3) = "Brian Francis" b.Authors(4) = "Rich Anderson" b.Authors(5) = "Karli Watson" Return b End Function End Class Public Class Books Public Title As String Public Description As String Public Price As Double Public Authors() As String End Class
Calls made to the GetBooks WebMethod will return an XML document with a root node of <Book> , and elements within <Book> , such as <Authors> , <Title> , <Price> , and so on, for each of our classes members . Figure 19-17 shows an XML document returned from an HTTP-GET request:
To change the shape of the XML document “ say by returning the title as an attribute of Books, and renaming the <Price> element to <DiscountPrice> “ use the XmlAttribute and XmlElement attributes. To influence the SOAP message, you need to add the SOAP equivalent attributes. Two changes have to be made to the Books class. First, add the System.Xml.Serialization namespace, as it contains both the XmlAttribute and XmlElement attributes. Second, add the attributes to the Books class member variables you want them applied to ( Books2.asmx ):
<%@ WebService Class="ReturnBooks" %> Imports System.Web.Services Imports System.Xml.Serialization Public Class ReturnBooks <WebMethod()> Public Function GetBooks() As Books Dim b As New Books b.Title = "Professional ASP.NET" b.Price = 59.99 b.Description = "This book covers Microsoft's ASP.NET technology " & _ "for building Web Applications" ReDim b.Authors(5) b.Authors(0) = "Alex Homer" b.Authors(1) = "David Sussman" b.Authors(2) = "Rob Howard" b.Authors(3) = "Brian Francis" b.Authors(4) = "Rich Anderson" b.Authors(5) = "Karli Watson" Return b End Function End Class Public Class Books <XmlAttribute> Public Title As String Public Description As String <XmlElement("DiscountedPrice")> Public Price As Double <XmlArray("Contributors")> Public Authors() As String End Class
All of these attributes allow you to rename either the attribute or the element, as shown in the modification of Price to DiscountedPrice and the renaming of the Authors array from Authors to Contributors . Figure 19-18 shows the new XML document:
The default web service Help page is the template used for all ASP.NET Web services when a request is made through the browser to a particular web service. Chapter 16 (ASP.NET Configuration) discussed how each application could support its own web service “ it's simply a configuration option to tell the application which ASP.NET page to use. Since the web service Help page is implemented as an ASP.NET page, it can be modified. For example, the page can be customized with graphics specific to the application, or provide additional details about the web services that the server provides.
Remember that any changes to the server's DefaultWsdlHelpGenerator.aspx will apply to all ASP.NET Web services on that server, unless you alter the ASP.NET configuration. However, there are some changes that can be made to this file, which will be useful in debugging the ASP.NET Web service.
If an ASP.NET Web service is capable of supporting the HTTP-POST protocol, and that functionality needs to be tested , DefaultWsdlHelpGenerator.aspx can be opened and modifications be made to the showPost flag:
// set this to true if you want to see a // POST test form instead of a GET test form bool showPost = false;
By default, this flag is set to false , but when set to true it will generate another HTML form available on the detail page for a particular WebMethod supporting HTTP-POST.
When you drill into a particular WebMethod detail page, you are provided with a view of what the protocol request and response messages should look like. Within DefaultWsdlHelpGenerator.aspx , there are three flags that can be altered to change how these protocol messages are displayed:
Flag | Default Value | Description |
---|---|---|
dontFilterXml | false | By default, the XML shown in protocol messages is not URL-encoded. If the flag is set to true , you can view the URL encoded XML in the message. |
maxObjectGraphDepth | 4 | This setting allows you to control the depth objects to show. This would be applicable for the Books class if it contained a public member Authors that was another class, and Authors contained a public member which was another class, and so on. |
maxArraySize | 2 | This option allows you to control the maximum number of array element examples shown within the protocols. For example, the Books class contained an array of six authors. The array representing these items in the protocol samples would, by default, only show two items. |
Next , let's look at one of the features of SOAP headers, which can be used to send out-of- band information.
The use of headers is supported only within the SOAP protocol, and not HTTP-GET or HTTP-POST. ASP.NET Web services that are created using SOAP as the default protocol for application-to-application communication. For example, consider the SOAP response to a call to the Fibonacci Web service:
<?xml version="1.0"?> <soap:Envelope xmlns:xsi="http://www.w3.org/2000/10/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2000/10/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope"> <soap:Body> <GetSeqNumber xmlns="http://tempuri.org/"> <FibIndex>6</FibIndex> </GetSeqNumber> </soap:Body> </soap:Envelope>
The HTTP headers have been stripped out, and what remains is the body of an HTTP request containing the SOAP message. The SOAP message contains an envelope ( <soap:Envelope ...> ) that encapsulates a body ( <soap:Body> ) and optional headers ( <soap:Headers> ). To use SOAP headers, you need to specify a SoapHeader attribute on the Web-callable method or property.
The SoapHeader attribute allows you to optionally set a SOAP header, on the consumer or provider of the service. SOAP does not define headers for you: they are simply an extensibility mechanism that can be used in your ASP.NET Web services.
For example, consider the following SOAP message for the Fibonacci example along with a simple header:
<?xml version="1.0"?> <soap:Envelope xmlns:xsi="http://www.w3.org/2000/10/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2000/10/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope"> <soap:Header> <SimpleSoapHeader xmlns="http://tempuri.org/"> <value>string</value> </SimpleSoapHeader> </soap:Header> <soap:Body> <GetSeqNumber xmlns="http://tempuri.org/"> <FibIndex>int</FibIndex> </GetSeqNumber> </soap:Body> </soap:Envelope>
To use SOAP headers, it is necessary to create a class that inherits from the SoapHeader base class. In the following code, both a SoapHeader attribute and the SoapHeader class will be used. They are two separate classes “ it's just that with attributes it's not necessary to explicitly add Attribute onto the end of the attribute on declaration (as SoapHeaderAttribute ).
To make use of a SOAP header, you need to create a class that derives from SoapHeader , which can be found in the System.Web.Services.Protocols namespace:
public abstract class SoapHeader{ public bool MustUnderstand{get; set;} public string Actor{get; set;} public bool DidUnderstand{get; set;} }
Here's a simple example of a class that inherits from SoapHeader (written in VB.NET):
Imports System.Web.Services.Protocols Public Class SimpleSoapHeader Inherits SoapHeader Public value As string End Class
Within this class is defined a single public member, value . Applications that wish to use this SOAP header can pass data within value .
Let's add this class to the Fibonacci example ( FibonnaciSOAP.asmx ):
<%@ WebService class="Fibonacci"%> Imports System.Web.Services Imports System.Web.Services.Protocols Public Class SimpleSoapHeader Inherits SoapHeader Public value As string End Class Public Class Fibonacci Public simpleHeader As SimpleSoapHeader <WebMethod, SoapHeader("simpleHeader")> Public Function _ GetSeqNumber (fibIndex as Integer) as Integer If (fibIndex < 2) Then Return fibIndex End If Dim FibArray(2) As Integer Dim i As Integer FibArray(0) = 0 FibArray(1) = 1 For i = 2 To fibIndex FibArray(1) = FibArray(1) + FibArray(0) FibArray(0) = FibArray(1) - FibArray(0) Next Return FibArray(1) End Function End Class
This cannot be tested in the same way as the earlier examples “ you'll see why.later.
In this modified Fibonacci class, we declared a local member variable simpleHeader , of type SimpleSoapHeader :
Public simpleHeader As SimpleSoapHeader
This represents an instance of the custom SOAP header that a client will set. Next, we used the SoapHeader attribute, which has been added to GetSeqNumber , and passed in the name of the member variable, simpleHeader :
<WebMethod, SoapHeader("simpleHeader")> Public Function _ GetSeqNumber (fibIndex as Integer) as Integer
Setting the SoapHeader attribute to simpleHeader creates a SOAP header containing a single item (named value ) that you can set, as defined in the SimpleSoapHeader class. When we created the SimpleSoapHeader class, the class inherited from the SoapHeader class. Let's take a look at the inherited properties the class receives.
The SoapHeader class provides three additional properties. We won't discuss them in detail, but you can review the SOAP 1.2 specification (http://www.w3.org/TR/soap12-part1/) to learn more about why they exist. The properties are:
Actor : Section 5.2.2 of the SOAP 1.2 specification states that a role header message may be used by a SOAP document to name the intended recipient, as a SOAP message can pass through many applications capable of routing the message. The Actor property allows you to set the URI value “ the endpoint that the SOAP message is ultimately going to be routed to. Alternatively, you can set a special URI “ http://www.w3.org/2003/05/soap-envelope/role/next as defined by the SOAP 1.2 specification “ that indicates that the next recipient of the SOAP message should process the message.
Note | Note that the SOAP 1.1 defines the SOAP header attribute as Actor , but this has been renamed to Role . The SoapHeader property will remain as Actor for backward-compatibility. |
mustUnderstand : Section 5.2.3 of the SOAP 1.2 specification states that a SOAP header can use an attribute, mustUnderstand , to indicate whether it is mandatory or optional for a recipient to process the header entry. This value is set to false by default.
DidUnderstand : A Boolean flag that the receiver of the SOAP message may set if the SOAP header was understood .
When we applied the SoapHeader attribute, we set a value that represents a property of a SoapHeader attribute called MemberName . Let's take a look at MemberName and the other properties supported by the SoapHeader attribute.
The SoapHeader attribute is the attribute added to Web-callable methods or properties to instruct those methods and properties to support SOAP headers. You've already seen that to use a SOAP header, you need to create a class that inherits from the SoapHeader base class. After this, you expose a member variable whose type is that of your class (which inherits from SoapHeader ).
Consumers of your web service use the member variable to create an instance of your class and to set values. The SoapHeader attribute is provided with the name of this class “ for example, <SoapHeader("simpleHeader")> “ and is thus able to access the class instance. This constructor sets the MemberName property of the SoapHeader attribute.
The SoapHeader attribute supports three properties:
MemberName
Direction
Required
The MemberName property identifies the name of the member (within the class) for which the header is to be created. This is best demonstrated by revisiting the example code:
public simpleHeader As SimpleSoapHeader <WebMethod, SoapHeader("simpleHeader")> public Function GetSeqNumber (FibIndex as Integer) as Integer
simpleHeader is a variable of type SimpleSoapHeader (which is the class that inherits from the SoapHeader base class).
Next, you see the SoapHeader attribute and the value passed in its constructor “ the name of the SimpleSoapHeader variable, simpleHeader . simpleHeader is the name of the member that represents the SimpleSoapHeader class. When we discuss consuming web services in the next chapter, you'll see how the consumer can create an instance of SimpleSoapHeader , set its value property, and assign that instance to simpleHeader .
In a nutshell , the MemberName property allows the SoapHeader attribute to name the class variable that it will be a type of.
SOAP headers are, by default, inbound only; the server servicing SOAP requests expects to receive SOAP headers, but not to set or send SOAP headers. This becomes apparent if you try to test the example (the Fibonacci Web service with a simple SOAP header, shown in Figure 19-19) using the web service's Help page. The HTML form for testing the results is no longer available:
The Direction property allows this to be configured. This property uses the SoapHeaderDirection enumeration to determine the direction of the header. Using VB.NET, you could write:
<SoapHeader("simpleHeader", Direction:=SoapHeaderDirection.InOut)>
Using C#, you could write:
[SoapHeader("simpleHeader", Direction=SoapHeaderDirection.InOut)]
The SoapHeaderDirection enumeration supports three values. Their names describe their use:
SoapHeaderDirection.In
SoapHeaderDirection.Out
SoapHeaderDirection.InOut
Headers defined by a method are required by default, which means that if they are not present in the request, an exception will be generated. Setting the Required property to false allows headers to be optional. Using VB.NET, you would write:
<SoapHeader("simpleHeader", Required:="false")>
Using C#, you would write:
[SoapHeader("simpleHeader", Required="false")]