In Chapter 3 you learned how SOAP headers can be used to extend SOAP by including information in the SOAP message outside of the message payload. Applications of SOAP headers include custom session keys (when your application is managing its own sessions), security keys, and digital signatures. .NET makes it easy to send and receive SOAP headers by providing a framework of objects that you use to define your headers then specify which Web methods will use those headers. In this chapter you will learn how to implement SOAP headers with .NET to send additional information between client and service. You’ll also learn to communicate error information in a rich, standard way using SOAP Fault elements.
The first step in using SOAP headers is to define a class that represents the header you want to send or receive. This class must inherit from the base class System.Web.Services.Protocols.SoapHeader and add public members that represent the header information you need. Listing 7-1 shows two classes that represent two different SOAP headers each with two public fields.
Listing 7‑1 Two SOAP header classes. (VBWSBook\Chapter7\DataService.asmx.vb)
Public Class HeaderClass1
Inherits SoapHeader
Public field1 As Integer
Public field2 As String
End Class
Public Class HeaderClass2
Inherits SoapHeader
Public fielda As Integer
Public fieldb As String
End Class
Because your SOAP headers are simply classes, they could also contain properties and methods. In addition, you can use XML serialization attributes such as XmlElement and XmlArrayItem to control how public members of your header classes get serialized into SOAP messages.
After defining your header classes, you need to specify which Web methods will use these headers. You first declare public member variables of the types of headers you want to use. For example, listing 7-2 shows a Web service with two member variables one of type HeaderClass1 and one of type HeaderClass2. Note that the member variables must be public.
Listing 7‑2 A Web service with two member variables representing two SOAP headers. (VBWSBook\Chapter7\DataService.asmx.vb).
Public Class DataService
Inherits System.Web.Services.WebService
'header fields must be public
Public inputHeader As HeaderClass1
Public outputHeader As HeaderClass2
End Class
You then add a SoapHeaderAttribute attribute to each Web method that needs to send or receive SOAP headers. This attribute lets you specify the name of the member variable that represents the SOAP header, the direction of the header (i.e. in, out, or in/out) and whether the header is required. Listing 7-3 shows two Web methods that use the two headers defined above.
Listing 7‑3 Two Web methods that use the two SOAP headers defined above. (VBWSBook\Chapter7\DataService.asmx.vb).
<WebMethod(), _
SoapHeaderAttribute("inputHeader", _
Direction:=SoapHeaderDirection.In, _
Required:=True)> _
Public Function ProcessHeader() As String
Return "The information the service received in the header is """ + _
inputHeader.field1.ToString() + """ and """ + _
inputHeader.field2 + """"
End Function
<WebMethod(), _
SoapHeaderAttribute("outputHeader", _
Direction:=SoapHeaderDirection.Out, _
Required:=True)> _
Public Sub ReturnHeader()
outputHeader = New HeaderClass2()
outputHeader.fielda = 122
outputHeader.fieldb = "the data sent back in the header"
End Sub
Looking at the first method, ProcessHeader, you’ll note that SoapHeaderAttribute’s constructor takes in the name of the member variable that represents the SOAP header. For the first method, ProcessHeader, the member variable name is inputHeader which we declared earlier. This means when a header is received in the SOAP request, it will be automatically deserialized into an instance of HeaderClass1 and stored in the inputHeader variable. SoapHeaderAttribute also has a Direction property which is set to SoapHeaderDirection.In indicating that incoming SOAP messages will contain this header. Finally, the Required property is set to True which means the header must be present in an incoming SOAP message otherwise an exception is returned to the client before your Web method is executed.
When the code inside ProcessHeader executes, you can assume that the input message contained a valid SOAP header that was deserialized into inputHeader. The example in listing 7-3 reads field1 and field2 from inputHeader and returns them in a string to demonstrate that it received the SOAP header.
The second method, ReturnHeader is different in that it returns a SOAP header instead of receiving one. To indicate this, it has the SoapHeaderAttribute’s direction property set to SoapHeaderDirection.Out. It also has the Required property set to True which, for out headers, means that the service must return this header. Therefore after ReturnHeader executes, if it did not return the required header, the client will get an exception indicating this.
To return a header, the ReturnHeader code in first instantiates a new HeaderClass2 object and stores it in the member variable called outputHeader. It then sets the members of this new object with the header information. outputHeader is automatically serialized to XML and included as a SOAP header in the response message.
You can use more than one SOAP header in a Web method by applying multiple SoapHeader attributes to that Web method. For example, a Web method could have input and output headers defined using multiple SoapHeader attributes.
When you add a Web reference to a Web service that uses SOAP headers, the generated code contains a class for each SOAP header defined in the Web service. Listing 7-4 shows an example proxy class for the above service. To improve readability, I removed all attributes and other code not relevant to this discussion.
Listing 7‑4 An example proxy class that calls a Web service with SOAP headers. (VBWSClientCode\Chapter7\CustomClient\Web References\VBWSServer1\Reference.vb).
Public Class DataService
Inherits SoapHttpClientProtocol
Public HeaderClass1Value As HeaderClass1
Public HeaderClass2Value As HeaderClass2
<SoapHeaderAttribute("HeaderClass2Value", _
Direction:=SoapHeaderDirection.Out)> _
Public Sub ReturnHeader()
Me.Invoke("ReturnHeader", New Object(-1) {})
End Sub
<SoapHeaderAttribute("HeaderClass1Value")> _
Public Function ProcessHeader() As String
Dim results() As Object = _
Me.Invoke("ProcessHeader", New Object(-1) {})
Return CType(results(0), String)
End Function
End Class
<XmlRootAttribute( _
[Namespace]:="http://tempuri.org/", IsNullable:=False)> _
Public Class HeaderClass1
Inherits SoapHeader
Public field1 As Integer
Public field2 As String
End Class
<XmlRootAttribute( _
[Namespace]:="http://tempuri.org/", IsNullable:=False)> _
Public Class HeaderClass2
Inherits SoapHeader
Public fielda As Integer
Public fieldb As String
End Class
The proxy class in listing 7-4 has two member variables that represent the two headers used by the Web service. The classes HeaderClass1 and HeaderClass2 are both automatically generated and placed at the bottom of the file. Each Web method that uses headers, e.g. ReturnHeader, has a SoapHeaderAttribute that mimics the one on the service side except it uses the names of the member variables defined on the client, i.e. HeaderClass1Value and HeaderClass2Value. Although the Required property is not set for each SoapHeaderAttribute in this example, the default is True. SoapHttpClientProtocol is smart enough to recognize when the client does not send a required input header and will throw an exception without even attempting to send the SOAP request to the service.
Listing 7-5 shows how a client can use this proxy to send and receive SOAP headers.
Listing 7‑5 A client that uses the proxy class in listing 7-4. (VBWSClientCode\Chapter7\CustomClient\Form1.vb).
Private Sub Button6_Click( _
ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles Button6.Click
Dim ws As New vbwsserver1.DataService()
Dim theheader As New vbwsserver1.HeaderClass1()
theheader.field1 = 77
theheader.field2 = "some information the service needs"
ws.HeaderClass1Value = theheader
MessageBox.Show(ws.ProcessHeader())
End Sub
Private Sub Button7_Click( _
ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles Button7.Click
Dim ws As New vbwsserver1.DataService()
ws.ReturnHeader()
Dim msg As String
msg = "The header that came back contains """ + _
ws.HeaderClass2Value.fielda.ToString() + _
""" and """ + ws.HeaderClass2Value.fieldb + """"
MessageBox.Show(msg)
End Sub
The first procedure, Button6_Click calls ProcessHeader which expects an input SOAP header. To send this header, the first step is to instantiate an object, in this case called theheader, from localhost1.HeaderClass1. Note that vbwsserver1 is the namespace in which VS put the proxy code. Next, you set the new header object’s properties with the information you want to send in this header. You then tell the proxy about this header object by setting the proxy’s member variable HeaderClass1Value to theheader. When you call ProcessHeader, the header object will be automatically serialized and included in the request message.
Button7_Click calls ReturnHeader which returns a SOAP header. The returned header is deserialized and stored in the proxy’s HeaderClass2Value member variable so you can get to the header information by simply reading the properties of this variable. The example code in listing 7-5 calls ReturnHeader then reads HeaderClass2Value.fielda and HeaderClass2Value.fieldb and displays them in a message box.
So far you’ve seen how a service can declare the headers it expects and how a client can send it those headers. It’s possible for a client to send headers to the service that the service didn’t know about at design time. If this happens, the service will most likely not care about those headers since it doesn’t know what to do with them to begin with. However, you might have a scenario where you want to retrieve those headers and log them or otherwise process them. What you need is a catch-all member variable that can be used to access all unknown headers. This member variable must be either a single instance or an array of SoapUnknownHeader. A single instance will capture the first unknown header while an array will capture all unknown headers. You then add a SoapHeaderAttribute with this member variable’s name to each method where you want to access unknown headers. Listing 7-6 shows an example of this.
Listing 7‑6 A Web method that reads all SOAP headers from the request message. (VBWSBook\Chapter7\DataService.asmx.vb).
Public Class DataService
Inherits System.Web.Services.WebService
'the catch all header variable
Public mysteryHeaders As SoapUnknownHeader()
<WebMethod(), _
SoapHeader("mysteryHeaders", _
Direction:=SoapHeaderDirection.In, _
Required:=False)> _
Public Function WhatHeaders() As String
Dim msg As String
Dim unknownHeader As SoapUnknownHeader
For Each unknownHeader In mysteryHeaders
msg = msg + "Header xml: " + _
unknownHeader.Element.OuterXml + vbCrLf
Next
Return msg
End Function
End Class
When WhatHeaders is called, all incoming SOAP headers will be deserialized into SoapUnknownHeader objects and added to the mysteryHeaders array. The code inside WhatHeaders loops through all headers in this array and gets each header’s XML using SoapUnknownHeader’s Element property which returns the header as an XML element. WhatHeaders then returns the headers XML back to the client as a demonstration that it received the header.
Listing 7-7 shows an example proxy for invoking WhatHeaders with a couple of undefined headers.
Listing 7‑7 An example proxy that invokes WhatHeaders with two different SOAP headers. (VBWSClientCode\Chapter7\CustomClient\Web References\VBWSServer1\Reference.vb).
'The following two classes were not auto-gen'd they were manually created
Public Class SessionInfo
Inherits SoapHeader
Public SessionId As String
Public LastUsed As DateTime
End Class
Public Class TransactionInfo
Inherits SoapHeader
Public TransactionId As String
Public MyVote As Boolean
End Class
<System.Web.Services.WebServiceBindingAttribute(Name:="DataServiceSoap", [Namespace]:="http://tempuri.org/")> _
Public Class DataService
Inherits System.Web.Services.Protocols.SoapHttpClientProtocol
'The following two lines were not auto-gen'd they were manually created
Public theSession As SessionInfo
Public tx As TransactionInfo
<System.Diagnostics.DebuggerStepThroughAttribute()> _
Public Sub New()
MyBase.New()
Me.Url = "http://VBWSServer/vbwsbook/Chapter7/DataService.asmx"
End Sub
'The following 2 SoapHeaderAttributes
'were added manually
'and not auto gen'd
<SoapHeaderAttribute("theSession"), _
SoapHeaderAttribute("tx"), _
SoapDocumentMethodAttribute("http://tempuri.org/WhatHeaders")> _
Public Function WhatHeaders() As String
Dim results() As Object = Me.Invoke("WhatHeaders", New Object(-1) {})
Return CType(results(0), String)
End Function
End Class
The classes SessionInfo and TransactionInfo both inherit from SoapHeader and represent the two header types that we will send to the service. The proxy class, which is called DataService, has a member variable called theSession and another one called tx, these represent the instances of the headers that we will send to the service. I’ve manually added two SoapHeaderAttributes on the WhatHeaders method to indicate that I want to send theSession and tx as SOAP headers. Listing 7-8 shows client code using this proxy to invoke the service.
Listing 7‑8 Example client code that uses the proxy in listing 7-7. (VBWSClientCode\Chapter7\CustomClient\Form1.vb).
Private Sub Button8_Click( _
ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles Button8.Click
Dim ws As New localhost1.DataService()
ws.theSession = New localhost1.SessionInfo()
ws.theSession.LastUsed = DateTime.Now
ws.theSession.SessionId = "abcbdef1234e"
ws.tx = New localhost1.TransactionInfo()
ws.tx.MyVote = True
ws.tx.TransactionId = "0fa9eb0374"
MessageBox.Show(ws.WhatHeaders())
End Sub
The client first instantiates the proxy then sets theSession member to a new instance of SessionInfo and populates its properties. Similarly, it sets the tx member to a new instance of TransactionInfo and populates its properties. Finally, it calls WhatHeaders and displays the returned string in a messagebox. Figure 7-1 shows the result which is simply the XML representation of the SessionInfo and TransactionInfo headers as echoed back from the service.
![]() |
Figure 7‑1 WhatHeaders echoes the XML content of each header it receives
If the client can send you arbitrary headers, there’s a good chance your service will not understand what to do with those headers. This can cause problems if the client is counting on your service to process the headers. To solve this problem, the client can mark those headers with soap:mustUnderstand=”1” indicating that the service has to understand and process the header or return a <soap:Fault>.
You can do this using the SoapHeader’s DidUnderstand property which is a Boolean indicating whether the service understood the specified header.
Listing 7-9 shows an example Web method that looks through all undefined attributes and sets each attribute’s DidUnderstand property to False unless the attribute is called TransactionInfo and belongs to the http://tempuri.org namespace.
Listing 7‑9 An example Web method that reads all headers but understands only a header called TransactionInfo. (VBWSBook\Chapter7\DataService.asmx.vb).
<WebMethod(), _
SoapHeader("mysteryHeaders", _
Direction:=SoapHeaderDirection.In, _
Required:=False)> _
Public Sub DoYouUnderstand()
Dim unknownHeader As SoapUnknownHeader
For Each unknownHeader In mysteryHeaders
If unknownHeader.Element.Name = "TransactionInfo" _
And unknownHeader.Element.NamespaceURI = _
"http://tempuri.org/" Then
unknownHeader.DidUnderstand = True
Else
unknownHeader.DidUnderstand = False
End If
Next
End Sub
On the client side, the proxy looks like the one in listing 7-10 indicating that theSession header should be sent with calls to this Web method.
Listing 7‑10 Proxy class for invoking DoYouUnderstand. (VBWSClientCode\Chapter7\CustomClient\Web References\VBWSServer1\Reference.vb).
'The following SoapHeaderAttribute
'was added manually
<SoapHeaderAttribute("theSession"), _
SoapDocumentMethodAttribute("http://tempuri.org/DoYouUnderstand")> _
Public Sub DoYouUnderstand()
Me.Invoke("DoYouUnderstand", New Object(-1) {})
End Sub
The client code is in listing 7-11. The client first instantiates the Web service proxy and sets theSession member variable as you saw above. In addition, the client sets theSession’s MustUnderstand property to True. MustUndertand is another property of SoapHeader that the client uses to tell the service it has to understand this header.
Listing 7‑11 Client code using proxy class in listing 7-10. (VBWSClientCode\Chapter7\CustomClient\Form1.vb.
Private Sub Button9_Click( _
ByVal sender As System.Object, _
ByVal e As System.EventArgs) _
Handles Button9.Click
Dim ws As New localhost1.DataService()
ws.theSession = New localhost1.SessionInfo()
ws.theSession.LastUsed = DateTime.Now
ws.theSession.SessionId = "abcbdef1234e"
'the service must understand this header
ws.theSession.MustUnderstand = True
Try
ws.DoYouUnderstand()
Catch ex As Exception
MessageBox.Show(ex.Message)
End Try
End Sub
If the service doesn’t understand a header that has soap:mustUnderstand=”1”, a <soap:Fault> is automatically returned which is translated to an exception on the client side. The Catch block in listing 7-11 will display this exception in a message box as shown in figure 7-2.
Figure 7‑2
A SoapHeaderException is thrown indicating that the service did not understand
a header marked with mustUnderstand=”1”
SOAP provides a standard mechanism for reporting error information using the <soap:Fault> element which was discussed in Chapter 3. When using .NET, any exceptions not caught in the Web method get reported to the client as a <soap:Fault>. Similarly, .NET clients throw a SoapException whenever they receive a <soap:Fault>. In the simplest case, you can communicate errors from a service by throwing an exception, for example:
Throw New Exception("This is the error information")
For more sophisticated scenarios, you can send structured error information as XML elements inside <soap:Fault>. To do this you create a new SoapException and pass it an array of XML elements that you want to send to the client. SoapException has 6 overloaded constructors two of which take in an XML node that represents the returned <detail> element. For example:
Public Sub New( ByVal message As String, _
ByVal code As System.Xml.XmlQualifiedName, _
ByVal actor As String, _
ByVal detail As System.Xml.XmlNode)
The first parameter, message, is the error message that you want to send. This string gets sent as the text of the <faultstring> element. The second parameter is the value of <faultcode>, it can be any namespace-qualified name. The code value is usually the standard soap:Client and soap:Server codes, which you can send by passing in SoapException.ClientFaultCode or SoapException.ServerFaultCode as the value of the code parameter. actor is a string that indicates the SOAP node which is sending this fault information. You usually set actor to an empty string unless you have a chain of Web services and you want to indicate which service within that chain is sending the error. The detail parameter is an XML element that represents the <detail> child of <soap:Fault>.
According to the SOAP specification, you’re allowed to send a <detail> element only if the error occurred while processing the contents of <soap:Body>. If however an error occurred while processing a SOAP header, it is illegal to send a <detail> element. In that case you throw a SoapHeaderException which works very similarly to SoapException except it doesn’t have a detail property.
Listing 7-12 shows an example Web method that receives an invoice document (the one from Chapter 2), validates it using the invoice schema and returns detailed error information by calling another method that throws a SoapException.
Listing 7‑12 An example method that receives an invoice document. (VBWSBook\Chapter7\CustomWS.asmx.vb).
<WebMethod()> _
Public Sub ReceiveInvoice(ByVal inv As XmlNode)
'validate the invoice
Dim proc As New InvoiceProcessor()
Try
proc.ValidateInvoice(inv)
Catch ex As XmlSchemaException
ThrowCustomEx("Validation", _
ex.LineNumber, _
ex.LinePosition, _
"A validation error occurred " + _
ex.Message)
End Try
End Sub
ReceiveInvoice takes in an invoice document then creates a new InvoiceProcessor object and calls its ValidateInvoice method to validate it. The code in InvoiceProcessor.ValidateInvoice is the same as the validation code using XmlValidatingReader that you saw in Chapter 2. Any validation errors result in an XmlSchemaException which is caught by the Catch block. The code in the Catch block calls ThrowCustomEx, which is in listing 7-13, passing it the procedure that caused the error, the exact line and column position of the input invoice that caused the error, and a string that indicates the exception message which provides more detailed information.
Listing 7‑13 ThrowCustomEx throws a SoapException with detailed error information. (VBWSBook\Chapter7\CustomWS.asmx.vb).
'this procedure throws the SoapException
Public Sub ThrowCustomEx(ByVal procName As String, _
ByVal LineNumber As Integer, _
ByVal LinePosition As Integer, _
ByVal errInfo As String)
Const MY_NS As String = "http://services.vbws.com/Supplier17/"
Dim doc As New System.Xml.XmlDocument()
Dim detail As System.Xml.XmlNode = _
doc.CreateNode(XmlNodeType.Element, _
SoapException.DetailElementName.Name, _
SoapException.DetailElementName.Namespace)
'the procedure name
Dim procNode As System.Xml.XmlNode = _
doc.CreateNode(XmlNodeType.Element, _
"Procedure", MY_NS)
procNode.InnerText = procName
'the line number where the error occurred
Dim lineNode As System.Xml.XmlNode = _
doc.CreateNode(XmlNodeType.Element, _
"Line", MY_NS)
lineNode.InnerText = LineNumber.ToString()
'the position within the line where the error occurred
Dim posNode As System.Xml.XmlNode = _
doc.CreateNode(XmlNodeType.Element, _
"Position", MY_NS)
posNode.InnerText = LinePosition.ToString()
detail.AppendChild(procNode)
detail.AppendChild(lineNode)
detail.AppendChild(posNode)
'Throw the exception
Dim ex As New SoapException(errInfo, _
SoapException.ClientFaultCode, _
"", detail)
Throw ex
Return
End Sub
In ThrowCustomEx, I start by creating a new XML DOM document, called doc, used to create the XML elements that will contain the custom error information. The next line uses doc.CreateNode to create a <detail> element. To specify the detail element’s name and namespace, you use SoapException.DetailElementName.Name and SoapException.DetailElementName.Namespace. Then I create another element called <Procedure> and set its InnerText property to the name of the procedure which caused the error. Similarly, I create a <Line> element for the line number where the error occurred and a <Position> element for the column position. To send these custom elements I append them as children of the detail element.
Now that I have the detail element ready to be sent, I create a new SoapException passing it the error message in errInfo, SoapException.ClientFaultCode as the faultcode, an empty string for the faultactor, and the detail element (along with its child nodes). Finally, I throw this new SoapException which then gets serialized and sent to the client as a <soap:Fault> shown in listing 7-14.
Listing 7‑14 The serialized SoapException results in a <soap:Fault>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<soap:Fault>
<faultcode>soap:Client</faultcode>
<faultstring>System.Web.Services.Protocols.SoapException: A validation error occurred: The 'http://www.vbws.com/nwind.net/schemas/invoice:subTotal' element has an invalid value according to its data type. An error occurred at (1, 530).
at Chapter7.CustomWS.ThrowCustomEx(String procName, Int32 LineNumber, Int32 LinePosition, String errInfo)
at Chapter7.CustomWS.ReceiveInvoice(XmlNode inv)</faultstring>
<detail>
<Procedure xmlns="http://services.vbws.com/Supplier17/">Validation</Procedure>
<Line xmlns="http://services.vbws.com/Supplier17/">1</Line>
<Position xmlns="http://services.vbws.com/Supplier17/">530</Position>
</detail>
</soap:Fault>
</soap:Body>
</soap:Envelope>
Examining the SOAP message in listing 7-14 you’ll notice it contains all the information sent by ThrowCustomEx. First, the faultcode is soap:Client indicating that the fault was caused by something the client did wrong (sent a bad invoice document). faultstring contains the short error message, “A validation error occurred ...” along with the exception’s message. <detail> contains the custom elements I created: Procedure, Line, and Position.
The client gets acccess to this rich error information through a SoapException object which can be used to prompt the user with a detailed, user-friendly error message. On the client side, you’d write code to catch and process this SoapException as shown in listing 7-15.
Listing 7‑15 Catching SoapExceptions on the client side. (VBWSClientCode\Chapter7\CustomClient\frmMain.vb).
Private Sub btnInvoice_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles btnInvoice.Click
Dim doc As New Xml.XmlDocument()
doc.Load("BadInvoice.xml")
Dim ws As New vbwsserver.MyWebService()
ws.Proxy = New System.Net.WebProxy("http://localhost:8080")
Try
ws.ReceiveInvoice(doc.DocumentElement)
Catch ex As SoapException
Dim sb As New System.Text.StringBuilder("The <faultstring> is: ")
sb.Append(vbCrLf)
sb.Append(ex.Message)
sb.Append(vbCrLf)
Dim detail As Xml.XmlElement = ex.Detail
Dim node As Xml.XmlNode
For Each node In detail.ChildNodes
If node.NodeType = Xml.XmlNodeType.Element Then
sb.Append(node.LocalName)
sb.Append(": ")
sb.Append(node.InnerText)
sb.Append(vbCrLf)
End If
Next
MessageBox.Show(sb.ToString())
Catch ex As Exception
MessageBox.Show(ex.Message)
End Try
End Sub
In the example in listing 7-15, I create a new DOM document called doc and uses it to load an invoice document from the file BadInvoice.xml. This document has an error in the <subTotal> element which contains a string instead of a number. Next, I instantiate the Web service proxy called ws, set it to use ProxyTrace just so I can capture the SOAP mesasges, and then I call ReceiveInvoice passing it the document element of the invoice document (i.e. the topmost element with all its children). I pass the document element rather than the document itself because I don’t want to pass the XML declaration. That’s because XML declarations, e.g. <?xml version="1.0" encoding="utf-8"?> are not allowed in a SOAP message per the SOAP specification.
The interesting part is the first Catch block where I handle any SoapExceptions. First I create a stringbuilder then append to it the exception’s message. I then loop through each child element of detail and append the element’s name and text. When done, I report the entire string in a MessageBox which looks like the one in figure 7-3.
If the type of exception anything other than SoapException, the second Catch block catches that and displays the exception’s message. Other exception types you might get include System.Net.WebException which indicates a Web-related error such as server down or file not found errors.

Figure 7‑3
Displaying custom error information from SoapException
It’s always a good idea to present rich error information to your clients to help them figure out the cause of the problem. Using custom XML elements and SoapException, you can communicate rich, structured error information from the service in a standard, SOAP-compliant way.
In this chapter you learned how to use SOAP headers to extend the SOAP protocol by sending extra information in request and response messages and indicating whether those headers are required. You also learned how to communicate error information in a standard way using <soap:Fault> so that Web service clients on any platform can access this error information.
The next chapters build on this knowledge and discuss common tasks such as handling relational and structured data in Web services and more advanced tasks such as extending .NET Web services Framework using SoapExtensions.