As with all SOAP messages, the content of the message is included in the body and is processed on the server. In this case, the server is a Web service method. The body element contains the information necessary to invoke a Web service and will also be used to return any values upon successful completion of the business function. However, if any error were to occur, the body can also be used to return errant information in the form of SOAP Faults. The SOAP Fault element is used to carry this error and/or status information back to the calling client from within the SOAP message. If an error occurs, the SOAP Fault element should appear just like any other body element entry and should not appear more than once.
Remember that the SOAP message can be parsed like any other XML document. To determine whether an error occurs, the calling client can simply look for this fault element and read its contents to determine the exact error. Fortunately, the client from the SOAP Toolkit 2.0 provides users with a user-friendly component by which to query for this information. The SOAP client COM object simply wraps the nested contents of the fault, allowing access to the following elements:
faultcode
This element is used to determine the basic error identification and to provide an algorithmic mechanism for identifying the fault. This must be present in a SOAP Fault element, and it must be a qualified name and not the typical numbering schema used in other error determinations. The namespace identifier for these faultcode values is http://schemas.xmlsoap.org/soap/envelope/. The following SOAP faultcodes can be used:
-
VersionMismatch— Used for an invalid namespace in the SOAP Envelope element.
-
MustUnderstand— An element of the SOAP Header that was missing or invalid. See the SOAP Header technology backgrounder in Chapter 7 for more information.
-
Client— Used to indicate that the message was incorrectly passed and/or did not contain the right information used for the called function. This simply means it was the client's “fault” and should be retried. Retrying or repassing the information will not result in the proper processing on the server unless the contents of the passed message are changed. This is not the server's error. You set this when the caller gives the service improper information and not when something goes wrong on the server side.
-
Server— This is used to communicate a server failure (this can mean any failure to process the request from the server's perspective). Retrying or repassing the information may result in the successful completion of the function.
faultstring
This element is used to pass to the caller a descriptive human-readable error. It must be present in a SOAP Fault element and should provide at least some information explaining the nature of the fault. This could contain the high-level error message used to determine generally what went wrong.
faultactor
This element is used to provide information about who caused the fault and usually contains the Uniform Resource Identifier (URI) of the perpetrator. This is similar to the SOAP actor attribute in the SOAP body. Applications that do not act as the ultimate destination of the SOAP message must include this element. The final destination of a message may use this element to indicate that it alone threw the exception.
detail
This is where the most descriptive error information can be included to help determine the root cause of the problem. The detail element can be used for carrying application-specific error information related to the Web service method invoked. It should not contain SOAP header-related errors but only errors related to the message body. The absence of this element shows that the fault is not related to the Body element. This can be used to distinguish whether the Body element was even processed and is your key to show that the message was received but that an application-specific error was indeed thrown. This element is also the key to the implementation pattern: SOAP Exception Fault Builder. According to the SOAP specification, “other SOAP Fault subelements may be present, provided they are namespace-qualified.”
An example of a SOAP Fault generated from the Microsoft SOAP Toolkit 2.0 SoapServer object is as follows (here it places an <errorInfo> element in the <detail> element to convey detailed error information to the client):
Listing 2.7 Sample Soap Fault Detail Block.
<soap:Fault ...> ... <detail> <mserror:errorInfo xmlns:mserror="http://schemas.microsoft.com/soap- toolkit/faultdetail/error/"> <mserror:returnCode>-2147024809</mserror:returnCode> <mserror:serverErrorInfo> <mserror:description>Failure...shutting down</mserror:description> <mserror:source>error-source</mserror:source> <mserror:helpFile> help goes here </mserror:helpFile> <mserror:helpContext>-1</mserror:helpContext> </mserror:serverErrorInfo> <mserror:callStack> <mserror:callElement> <mserror:component>SomeOp</mserror:component> <mserror:description> Executing method ReturnError failed </mserror:description> <mserror:returnCode>-2147352567</mserror:returnCode> </mserror:callElement> </mserror:callStack> </mserror:errorInfo> </detail>
This contains all of the application-specific error information as generated from the Toolkit's SoapServer object. Your own custom detail element does not necessarily need to look like this but this provides a decent example of what can be done. The more information you provide, the more robust your error handling will become, especially when this information is provided in a SOAP Fault-friendly manner.
As mentioned earlier, throwing normal exceptions using the .NET framework's SOAP exception wrapper may not always provide the details you were expecting. In fact, the main error message provides only the same generic text message: “Server was unable to process request.” To provide a slightly more robust SOAP exception with a custom SOAP Fault, you need to throw your own SoapException class. By doing, so you can provide a much richer exception and still provide a SOAP-compliant fault mechanism so that all clients can better determine the error generated by the Web service. Fortunately, there is a simple way to throw SoapExceptions from your Web service without requiring much effort. In the next section, I will show you exactly how to implement this pattern by adding support to your base exception class. See the beginning of this chapter for details on building a base exception class.
Throwing Custom SOAP Exceptions
In some cases, it is difficult to find areas where Microsoft has not provided a feature desired in its framework. In fact, you have look a little closer before realizing where you could truly add benefit by adding custom behavior. Such is the case for processing exceptions from Web services when using SOAP.
As mentioned in the previous section, when you throw an exception from a Web service that is called from SOAP .NET, this automatically wraps your exception class in the SoapException class. For a remote client, receiving that exception will come in the form of a partially populated SOAP fault. I say partially because .NET does not fill in every detail. I've built a simple Web service to demonstrate this, as shown in Figure 2.4.
Figure 2.4. ExceptionThrower.asmx test harnesses a Web service for throwing custom SOAP exceptions.
In Figure 2.4, you'll find a Web service called ExceptionThrower.asmx displayed in a browser. Here you simply specify whether you want to throw a custom SOAP exception using the following implementation pattern or using the framework wrapper. Specifying True in the edit box and selecting Invoke, you should see the output shown in Figure 2.5.
Figure 2.5. ExceptionThrower.asmx exception output when selecting custom soap exception.
If you run this again but select False, you should not be able to view the exception text. This is an apparent side effect to not throwing your own SOAP exceptions, at least for Beta 2. This, however, is not the real reason to throw your SOAP exceptions. To the see the real benefit of throwing your own custom SOAP exceptions, run the exceptionthrower.asmx Web service from a typical SOAP 2.0 client (Figure 2.6).
Figure 2.6. Called from a SOAP 2.0 client written in VB 6.0 (using wrapper SOAP exceptions). Does not show any SOAP Fault details.
Using the test GUI, I am not selecting Custom Soap before I select Execute. This will call the same Web service with a parameter of False, thus using the .NET wrapper method. Notice that .NET wraps our exception in a SoapException but it provides us with a general error text before the actual error message. More critically, it does not provide us with any details (the <detail/> element is empty). Finally, it does not tell us where the error came from in the form of the faultfactory property. Now when we turn on the Custom Soap option, we should receive a much more detailed SOAP Fault (Figure 2.7).
Figure 2.7. Called from a SOAP 2.0 client written in VB 6.0 (using wrapper SOAP exceptions). Shows the SOAP Fault details.
Notice in the previous screen shot that a few things have now been provided. The faultcode property has been set to detail. This is optional but now we have the flexibility to set this faultcode appropriately. For more information on SOAP Fault codes, please refer to the relevant technology backgrounder in this chapter. Also notice that the general description has been replaced with our specific error message. Next, the faultfactory property has been set to the location of the exception. In this case, that location is the actual URI of the Web service. Finally and most important, the details property has been set to the root cause of the error. The information provided in this property can be as detailed as you like. In fact, this would be a great place to display an error stack; as long as the details are properly formatted as SOAP-compliant, the choice is yours. Next I will show you the code behind this implementation pattern.
Adding SOAP Exception Support to BaseException
In this implementation pattern, I will place the custom SOAP exception inside of our BaseException class. This provides two benefits. First it encapsulates the details of the SoapException class from the thrower of the exception, such as a Web service method. Second, it simplifies the mechanism by which all exceptions are thrown. The easier and more standard you can make exception handling, the better. The exceptionthrower.asmx Web service, shown below, shows how to throw or not to throw a custom SOAP exception:
Listing 2.8 Sample Soap Fault Builder for our base exception class.
using System.Collections; using System.ComponentModel; using System.Data; using System.Diagnostics; using System.Web; using System.Web.Services; namespace ExceptionHandling { . . . [WebMethod] public void ThrowAnException(bool bUseSoapFault) { if (bUseSoapFault) throw new BaseException(this, 1234, "This is a test 1,2,3,4,5", new Exception( "this is the chained message", null), false, true); else throw new BaseException(this, 0, "This is a test 1,2,3,4,5", new Exception( "this is the chained message", null), false); } } }
The code in Listing 2.8 simply passes true or false as the last parameter of one of our BaseException class constructors. If you pass true, the first BaseException shown is thrown; otherwise, the same signature we used in the constructor defined in the beginning of this chapter is used.
Passing true will call the following constructor.
Listing 2.9 Sample BaseException ctor for allowing Soap Faults.
/// <summary> /// This ctor for throwing soap exception usually from web service /// methods /// This will format error message in more soap fault friendly /// manner /// filling in fields not filled in by the default wrapper exception /// method in .NET /// </summary> /// <param name="oSource"></param> /// <param name="nCode"></param> /// <param name="sMessage"></param> /// <param name="oInnerException"></param> /// <param name="bLog"></param> /// <param name="bThrowSoap"></param> public BaseException(object oSource, int nCode, string sMessage, System.Exception oInnerException, bool bLog, bool bThrowSoap) : this(oSource, nCode, sMessage, oInnerException, bLog) { string sCause = "no root cause found"; XmlNode oNode = null; if (bThrowSoap) { if (oInnerException != null) sCause = oInnerException.GetBaseException().Message; // now build the details node to use for the soap exception // -- use the root cause for the main text oNode = BuildDetailNode(sCause); Trace.WriteLine("Throwing Custom Soap Exception - Message " + sMessage); // build actor or source uri to set into actor field – // replace .'s with /'s to make a uri from it // NOTE: the web service must namespace match the reqeues // uri if this is to be accurate // we can't use context without passing it so we'll build // one StringBuilder sActor = new StringBuilder("http://"); sActor.Append(Dns.GetHostName()); sActor.Append("/"); sActor.Append("dotnetpatterns"); sActor.Append("/"); sActor.Append(oSource.ToString().Replace(".", "/")); sActor.Append(".asmx"); throw new SoapException(sMessage, SoapException.DetailElementName, sActor.ToString(), oNode, oInnerException); } }
There are two main parts to this pattern. First, there is the building of the details property so that we can fully extract SOAP Fault details from the client. The second is the fact that I am “rethrowing” a SoapException back, once the properties are filled. To rethrow a SoapException, we simply call throw new SoapException like any other exception, and the exception should be thrown back to the calling client. Before I do that, however, I need to fill four properties that will become the SOAP Fault elements.
Each of the four properties that make up the SOAP fault will be passed to the SoapException constructor, as shown in Listing 2.9. The first parameter is any message text. Typically, as is the case here, it will be the high-level error message text we've sent when throwing our main exception. The second parameter designates the SOAP Fault code, as defined by the SOAP specification. Although this can be set to any code and your code will still work, it is considered “well formed” if you comply with standards. For this example, I've set this parameter to the detail property because I will be providing a detail explanation as part of the fourth parameter. The third parameter is the actor and is used to set the location of where the error occurred in the form of a URI. Here I dynamically determine the full URI path based on the source object provided and the machine on which this service is running. The oSource.ToString() will give me a fully qualified path string, including the namespace of the assembly under which the source object resides. This will only work if your Web service path matches this fully qualified name and is used only as an example here.
Finally, the fourth parameter provides the details section of the SOAP Fault and is a little more complicated to build so I've supplied a helper method called BuildDetailNode, as shown here:
Listing 2.10 Sample Soap Fault Detail Node Builder.
/// <summary> /// Build the xml node used for throwing custom soap exceptions, the /// root cause string will be use for the main content /// </summary> /// <param name="sCause"></param> /// <returns></returns> public System.Xml.XmlNode BuildDetailNode(string sCause) { XmlDocument oDoc = new XmlDocument(); XmlNode oNode = oDoc.CreateNode(XmlNodeType.Element, SoapException.DetailElementName.Name, SoapException.DetailElementName.Namespace); // Build specific details for the SoapException. // Add first child of detail XML element. XmlNode oDetailsNode = oDoc.CreateNode(XmlNodeType.Element, "rootcause", "http://www.etier.com/patterns.net"); oDetailsNode.InnerXml = sCause; oNode.AppendChild(oDetailsNode); return oNode; }
This method uses the System.Xml.XmlDocument and XmlNode classes to build a SOAP Fault-compliant XML node. Once built, this node will be passed back and used as our detail SOAP Fault property. This can be built any way you see fit, as long as the parent detail element node exists along with its namespace; otherwise, it will not be properly displayed in your SOAP client. This SoapException.DetailElementName type is used from within our first CreateNode() call for just this purpose. After this step, you can supply your own text or custom-nested elements. In this example, I add another child node called rootcause and specify the text of the root cause or error, as supplied by the System.Exception class. Another variation of this pattern would be to add the full error stack as child nodes so that the SOAP fault client can see every detail of the error. Either way, the choice is yours, so have fun with it.
COM (Interop) Exception Handling
The common language runtime (CLR) seamlessly provides the ability to access COM objects, as well as allowing COM clients to access .NET code. The CLR's interoperability features also provide sophisticated exception handling when the managed layer is crossed during error scenarios. When a COM client calls a managed piece of code that throws an exception, the CLR intercepts the exception and appropriately translates it into an HRESULT. Similarly, if managed code is accessing an unmanaged COM component that returns an HRESULT, the CLR translates it into an exception. While doing so, the CLR incorporates any additional information provided by the COM IErrorInfo interface into the thrown managed exception class. Any unrecognized HRESULT will result in a generic ComException being thrown with the ErrorCode set to the unresolvable HRESULT.
When using COM components from managed code, this does not present many problems. However, when COM clients currently access your managed code, many of the FCL exceptions will return an HRESULT that may be unrecognizable to the calling COM client. Cases where the calling COM client must handle certain HRESULTs in an application-specific manner can cause problems. Fortunately, the System.Exception object has an HResult property that can be overridden and set at any time during your error handling. To avoid problems with legacy COM clients, replace any HRESULT with that of one that is recognized by those clients to avoid such problems.
Using XML
The output format is completely up to you. A proprietary format was used in the previous examples but XML could have been used. Be forewarned, however, that this output may have multiple targets, each with its own viewers. For example, XML may not be the best display format if you are writing to the Windows 2000 Event Log. A large XML stack does not look particularly great in the Windows Event Viewer.
Another point to keep in mind is when building XML documents, be careful how you format any error strings. If the error strings contain characters interpreted as XML syntax, you may have problems formatting such errors. For example, in the previous section we built a detail XML node that made up one of the properties of our SOAP Fault. The text that was used in the detail node was the root cause of the error. If any of the text strings used for the message inside of the detail XML node happened to contain a “<” or “>” character, a format exception would have been thrown. Having errors occur in your own error handling is not something you want to have happen because it may make communicating them impossible. In fact, you may be a little embarrassed that your own error-handling code has bugs. This is not to say that you should avoid XML in your error handling. Rather, it is quite the contrary. I point this out only so that you can be extra careful when building your base exception class. The more you test your code in this case, the better.
Determining When to Log
Determining when to actually log your output can easily become one of those heated design debates. To keep things simple, however, I suggest logging at the most externally visible tier, or what I've been calling the exception boundary. This coincides with what .NET considers an application and where you specify your configuration files. This is also known as the application level. For GUI development, this is simple: Log just before you display your error. For Web service applications or for those that do not necessarily have a visible tier, I typically log just before returning from an external Web method. This refers only to error logging. Random tracing is another story and is completely dependent on how much output you want to receive and when you want to begin seeing it. For those really nasty bugs, providing a real-time, persistent tracing scheme where you trace as much as possible may be your best option. There is a way to have the best of both worlds—the ability to trace verbose information but only during times where it may be deemed necessary. We will be covering this practice in the next section.
Enabling the Trace Option
By default, C# enables tracing with the help of Visual Studio.NET by adding the /d:TRACE flag to the compiler command line when you build in this environment. Building under Visual Studio.NET automatically will provide this switch for both debug and release builds. Therefore, any tracing can be viewed in either release or debug, which is something I am counting on for the following implementation pattern. Another option for adding tracing is to add #define TRACE to the top of your C# source file. The syntax and mechanism to enable tracing is compiler-specific. If you are working with VB.NET or C++, for example, your settings are slightly different. However, each .NET language should support tracing, so refer to your documentation for details.
Challenge 2.4
How would you send logging output (tracing output) to a remote terminal across the Internet?
A solution appears in the upcoming section on remote tracing (page 82).
The target of the trace output is then determined by what are called trace listeners—the subject of the next technology backgrounder. If you are currently already familiar with trace listeners and dynamic tracing using trace switches, you can skip this section.