Introduction
My main project at work involves a fairly large application using Windows Forms as the front end, communicating via Web Services with business logic and data access code on the server. I�ve found it very troublesome to track down the cause of unhandled exceptions on the server side, because of the limited exception information you get back in the SOAP message, so I have created a SOAP extension to provide more information.
How it Works
This is a fairly simple piece of code, the SOAP extension listens to every message which is passed between the two machines. If an exception is detected in a response about to be sent back to the client, the extension notices this, and gets the full information about the exception, which it inserts into the XML stream heading back for the client.
The client, after receiving the SOAP response, will notice that an exception is present in the stream, and again the extension will step in to retrieve the additional information which we inserted into the stream on the server. Once it has this, it throws a new exception containing that extended information.
The ProcessMessage
method is at the heart of any SOAP extension, it gets called before and after (de)serialization on the server and the client for both the request and the response. Its job is to copy information between the input and output streams at the correct points, and optionally step in and examine or manipulate the SOAP message. Our implementation of it looks like this:
Public Overrides Sub ProcessMessage( _
ByVal message As System.Web.Services.Protocols.SoapMessage)
Select Case message.Stage
Case SoapMessageStage.BeforeSerialize
Case SoapMessageStage.AfterSerialize
If Not message.Exception Is Nothing Then
InsertExceptionDetails(message.Exception)
End If
newStream.Position = 0
StreamCopy(newStream, oldStream)
Case SoapMessageStage.BeforeDeserialize
StreamCopy(oldStream, newStream)
newStream.Position = 0
Case SoapMessageStage.AfterDeserialize
If Not message.Exception Is Nothing Then
Dim DetailString As String
DetailString = GetExceptionDetails()
Throw New Exception(DetailString)
End If
Case Else
Throw New ArgumentException("Invalid message stage")
End Select
End Sub
The other significant methods in this class are called InsertExceptionDetails
and GetExceptionDetails
. They are responsible for the task of inserting the extended exception information into the stream, and retrieving it. The extended information is inserted into an XML element called ExtendedExceptionDetails
within the standard soap:Fault
element.
InsertExceptionDetails
implementation:
Private Sub InsertExceptionDetails(ByVal ex As Exception)
newStream.Position = 0
Dim Reader As New XmlTextReader(newStream)
Dim MessageDoc As New XmlDocument
MessageDoc.Load(Reader)
Dim NsMgr As New XmlNamespaceManager(MessageDoc.NameTable)
NsMgr.AddNamespace("soap", _
"http://schemas.xmlsoap.org/soap/envelope/")
Dim ErrorInfo As String
ErrorInfo = ex.ToString()
Dim ExceptionNode As XmlNode
ExceptionNode = MessageDoc.SelectSingleNode( _
"//soap:Fault", NsMgr)
Dim ExceptionDetail As XmlElement
ExceptionDetail = MessageDoc.CreateElement( _
"ExtendedExceptionDetails")
ExceptionDetail.InnerText = ErrorInfo
ExceptionNode.AppendChild(ExceptionDetail)
newStream = New MemoryStream
MessageDoc.Save(newStream)
End Sub
GetExceptionDetails
implementation:
Private Function GetExceptionDetails() As String
newStream.Position = 0
Dim Reader As New XmlTextReader(newStream)
Dim MessageDoc As New XmlDocument
MessageDoc.Load(Reader)
Dim NsMgr As New XmlNamespaceManager(MessageDoc.NameTable)
NsMgr.AddNamespace("soap", _
"http://schemas.xmlsoap.org/soap/envelope/")
Dim ExceptionDetailNode As XmlNode
ExceptionDetailNode = MessageDoc.SelectSingleNode( _
"//soap:Fault/ExtendedExceptionDetails", NsMgr)
If Not ExceptionDetailNode Is Nothing Then
Return ExceptionDetailNode.InnerText
Else
Return ""
End If
End Function
Using the code
To use the code, add references to the ExceptionHandlingSoapExtension
assembly, in both the client and web service projects. Add the following section to both App.Config (on the client) and Web.Config (on the server):
<webServices>
<soapExtensionTypes>
<add
group="0"
priority="1"
type="ExceptionHandlingSoapExtension.
ExceptionManagementSoapExtension,
ExceptionHandlingSoapExtension"
/>
</soapExtensionTypes>
</webServices>
This code instructs ASP.NET on both the client and server to use this SOAP extension to process messages.
With the SOAP extension set up, every time an exception occurs during the execution of a web method, an exception will be thrown on the client which will have the message field populated with a full stack trace showing what happened on the server.
Screenshot showing the standard information returned from a web method exception.
Screenshot showing the extended information returned courtesy of the SOAP extension.
Real-World Usage
Security
It would be quite a security risk to have publicly accessible web services set up like this; exposing information about the internal workings of your web services can be very useful to malicious users, and a full stack trace exposes a lot! We use this extension in conjunction with various encryption and authentication systems, and the web services are not made accessible on the public Internet so there is little danger of this information falling into the wrong hands.
Cross-platform usage
I believe that non .NET clients consuming web services with this extension will simply ignore the additional exception information, however I cannot guarantee this. In my case, the web services we create will never be accessed by clients other than those we develop so I have not investigated this.
Conclusion
I think it's debatable whether Web Services are the right choice for the type of application I'm developing, but the choice was made well before I started work on the project.
Learning how to write a SOAP extension was interesting for me, I found the MSDN documentation a little misleading to say the least, but they are certainly a simple and powerful way to manipulate web service messages.
I hope this article is helpful to somebody, and I would welcome any feedback - I know I still have a lot to learn!
History