The Design of the Exception Handling Application Block


Before the Exception Handling Application Block existed, developers could use the Exception Management Application Block to publish and log exceptions in their applications. Its capabilities let applications abstract away the underlying details of the publication (logging or notification) process. This was accomplished by using the ExceptionManager's Publish method. The ExceptionManager class would retrieve its configuration information to determine how exceptions should be published. If no settings were found, the exception was published in the Windows Event Log by using the DefaultPublisher class. If the application had exception management settings configured, the publishers listed in the configuration file were used to publish the exception.

The Exception Handling Application Block has taken these concepts and combined them with a policy-based approach. Moreover, the Exception Handling Application Block does not constrain an application to just publish exception information. Logging exceptions (which uses the Logging and Instrumentation Application Block to determine where to log the information) is just one of the possible ways to handle an exception; Exception handlers also exist to wrap exceptions and replace exceptions. And just as custom exception publishers could be built for the Exception Management Application Block, so too can custom Exception Handlers be built for Exception Handling Application Block.

The design of the Exception Handling Application Block is focused on providing a simple interface that lets developers care less about how a particular exception is handled and concentrate more on when to handle an exception. The combination of the ExceptionPolicyFactory, the ExceptionPolicy, the ExceptionHandlerFactory, and exception handlers give the Exception Handling Application Block its flexibility. This section defines these classes and describes how they work together to achieve the design goals of the Exception Handling Application Block. It also demonstrates how to extend the application block by creating and configuring a new exception formatter and exception handler.

The ExceptionPolicy Class

With the Exception Handling Application Block, the code for how to handle a particular exception does not need to exist in the application itself. Rather, the exception handlers for the application can be centralized outside of the application and referenced via an exception policy. An exception policy has a name and a one-to-many relationship with the exception han- dlers that must execute when a particular exception type occurs. When an exception occurs in an application, the application just refers to the policy by its name and forwards the exception to the application block.

For example, let's say that it is very important to a company to understand when SqlExceptions occur in a particular application because there have been many instances where the connections to their SQL Servers have been broken. To understand when and why these exceptions are occurring, the company wants to log this information and notify someone in operations when it occurs. Traditionally, this would be accomplished by sticking some code into the application to log the message to a file or event log. There might also be code that would create a WMI event that a product like Microsoft Operation Manager could handle and notify someone in operations that the exception has occurred.

However, by employing exception policies and the Exception Handling Application Block, you don't need to write this code. Instead, you can define and configure an exception policy for SqlExceptions for the application. An exception policy has a name (e.g., SqlPolicy) and is configured to handle a set of exception types (e.g., SqlException).

Each exception type has a list of exception handlers that are executed sequentially when an exception occurs. This policy can be configured to both log an exception and send a WMI event. However, no code in the calling application actually performs either of these operations. Instead, the exception that occurred and the name of the policy that had been configured to handle this exception are forwarded to the Exception Handling Application Block, which then takes care of performing whatever actions it has been configured to perform. In this case, that is to call handlers that will log the exception and send a WMI event. Listing 5.2 shows the code that would need to be written in the application described.

Listing 5.2. Handling a SqlException with the Exception Handling Application Block

[C#]     try     catch(SqlException ex)     {         bool rethrow = ExceptionPolicy.HandleException(ex, "SqlPolicy");         if (rethrow)                   throw;     }     [Visual Basic]     Catch ex As SqlException          Dim rethrow As Boolean = ExceptionPolicy.HandleException _                (ex, "SqlPolicy")          If (rethrow) Then               Throw          End If     End Try

When you resolve the instability in this environment, it may be appropriate to change how a SqlException gets handled in this application. This particular exception handler could be removed or replaced with another one. If a change does occur for how to handle a particular exception, no code in the calling application has to change, because the application is decoupled from the actions that occur on exceptions.

The ExceptionPolicy class is vital to this decoupling. It serves as a simple service interface for the Exception Handling Application Block. It contains only one shared, public method: HandleException. Listing 5.2 demonstrates the intended use of the HandleException method; that is, to tell the Exception Handling Application Block that an exception has occurred and which policy needs to be executed to handle it.

The HandleException method expects the exception that occurred and the name of an exception policy that has been configured for the application. HandleException then calls its private GetExceptionPolicy method, which, in turn, uses the ExceptionPolicyFactory to create and return a specific instance of an ExceptionPolicy for the policy name. If no exception occurs, the private Initialize method is called to initialize and cache the exception policy with information about its configured exception handlers and exception types. Lastly, it passes control to this ExceptionPolicy's private HandleException(Exception) method. Figure 5.2 shows the relationship between the ExceptionPolicy class and several of the classes it uses to help handle exceptions.

Figure 5.2. ExceptionPolicy and Related Classes


After the private HandleException method receives the exception, the Exception Handling Application Block attempts to locate an ExceptionPolicyEntry for the type of exception that occurred. For the example shown in Listing 5.2, it would try to locate an ExceptionPolicyEntry for a policy named SqlPolicy and an exception of type SqlException.

An ExceptionPolicyEntry is responsible for looping through all the ExceptionHandlers that have been configured for a policy and calling the HandleException methods with the same HandlingInstanceID. The HandlingInstanceID is a unique identifier that you can use to group multiple exception handlers together. These are discussed in more detail later in this chapter; for now it is just important to understand that an exception policy can be configured to chain multiple exception handlers together. When this occurs, the exception type can change as it gets passed from one handler to another. The first handler will always receive the original exception. However, any handler in the chain can change the exception (for example, by wrapping it or replacing it with another exception). Subsequent handlers receive the exception that is returned from the previous handler. An exception policy can be configured to throw the exception that is returned from the final exception handler (known as THRowNewException); thus, it will be the exception that is returned from the last handler that will be returned to the calling application.

It is also important to understand that the algorithm for matching the exception object to a set of handlers mimics the .NET exception policy algorithm. That is, the specified exception object will be matched to a single ExceptionPolicyEntry by traversing its inheritance hierarchy. This is why it is very important to design an exception hierarchy appropriately. For example, if a SqlException is passed to the HandleException method and the exception type that the exception policy is configured to handle is System.Exception or System.SystemException, the event handlers for one of these would be invoked because SqlException derives from SystemException, which ultimately derives from Exception.

If, however, the only exception type that is configured is a System.Data.DataException, then no exception handlers will be invoked because SqlException does not fall underneath DataException anywhere in the exception hierarchy. This logic allows an application to take different actions depending on the exception type that occurs. For example, exception information can be logged and WMI events can be sent for all SqlExceptions, but only SecurityException may be replaced with another exception before being propagated back.

An exception policy must be configured with a PostHandlingAction for every type of exception that it has been configured to handle. The value for the PostHandlingAction determines what the return value for the HandleException method will be. As Listing 5.2 demonstrates, an application should always check the value that is returned from the HandleException method. If the value is true, then the application should rethrow the original exception. It is necessary for this to be completed in the calling application; it cannot be accomplished in the application block because if the original exception were thrown inside the block, the stack trace would be lost.

The possible values for the PostHandlingAction property are NotifyRethrow, None, and ThrowNewException. The default is NotifyRethrow. Each of these flags corresponds to the guidance outlined in the Exception Management in .NET Architecture Guide on exception propagation and are described in Table 5.1.

Table 5.1. PostHandlingAction Values

PostHandlingAction Value

Description

NotifyRethrow

The default. HandleException returns true to the calling application. This indicates that the application should catch and rethrow the exception. With this approach, an application catches and reacts to the exception and cleans up or performs any other required processing in the scope of the current method. If it cannot recover from the exception, it rethrows the same exception to the caller.

None

HandleException returns false to the application. This indicates that the application should not (re)throw an exception and the exception is effectively swallowed.

THRowNewException

The application block throws the last exception to occur in the exception handling chain. This is recommended whenever the exception is manipulated inside an exception policy (i.e., the exception is wrapped or replaced, resulting in the creation of a new exception).


A value of true can also be returned from the HandleException method for another reason other than those specified in Table 5.1. If a policy entry cannot be located, then HandleException will fire an ExceptionNotHandledEvent and return true to indicate that a rethrow is recommended. This can also occur if an exception occurs during the exception handling process.

Figure 5.3 depicts an exception policy named SqlException Policy. It is configured to chain together two exception handlersa Logging-ExceptionHandler and a ReplaceHandlerwhen an exception of type SqlException occurs. The LoggingExceptionHandler logs the exception information; the ReplaceHandler replaces the exception with an entirely new exception. The order in which the exception handlers are executed is the order in which they appear in the tree; therefore, the Logging-ExceptionHandler runs before the ReplaceHandler. The SqlException Policy has been configured to ThrowNewException; therefore, it is expected that the application block will throw the new exception that gets created by the ReplaceHandler.

Figure 5.3. Configuration for the SqlException Policy


Exception Handlers

An exception handler performs an action based on the existence of a specific exception type. Typically, without the Exception Handling Application Block, these handlers are called directly in an application after a specific exception type has been caught (i.e., handled). For example, if a SqlException is detected, code might exist that calls a logging handler that accepts and logs the exception information. If the way to handle this particular exception needs to change, then the code would also need to change to call a different handler.

Keep in mind, however, that not all exceptions should be caught. Listing 5.1 showed an example of poor exception handling. The Exception Management in .NET Architecture Guide provides detailed guidance on why and how exceptions should be handled in an application. Catching and swallowing exceptions is generally not a recommended best practice. According to the Exception Management in .NET Architecture Guide, exceptions should only be caught when information needs to be gathered for logging, relevant information needs to be added to the exception (i.e., wrap the exception), or code needs to be executed to clean up or recover from the exception.

One more reason can be added that is not directly addressed in this guide: to replace the exception entirely. In today's environment, companies must be more sensitive to security issues than ever before. Therefore, it is often not good enough to simply wrap an exception in another exception. Many times an exception must be completely replaced with another exception so any sensitive information is protected from those who shouldn't see it. For example, if an exception occurs while connecting to a database in an enterprise, it is conceivable that the exception message might have information about the failed attempt. It may provide a message like Cannot open database requested in login 'baddatabase'. Login fails. Login failed for user 'lfenster'.

While this is good information for a person in operations to have, it is probably wise not to display information like the name of a database or a user ID to an end user or partner company. In this case, it would be wise to log the actual exception so that a person in operations can see it, replace the exception with a message that is more user friendly and contains less information about the data connection, and have some way to link the two messages together. The Exception Handling Application Block ships with exception handlers that can log, wrap, and replace exceptions. Additionally, the exception policy can link all of these activities together. These exception handlers, as well as the abstract base ExceptionHandler class from which they all derive, are displayed in Figure 5.4.

Figure 5.4. ExceptionHandlers Provided by the Exception Handling Application Block


The exception handlers themselves are not responsible for determining when, where, or why an exception should be handled. Rather, they are responsible only for how to handle an exception type. For example, the ReplaceHandler that ships with the Exception Handling Application Block does not contain any logic for determining when an exception should be replaced. It is the responsibility of the exception policy to determine which exception types should call through the ReplaceHandler. Instead, the ReplaceHandler is only responsible for replacing one exception type with another. If more then one action needs to be taken upon detection of a specific exception type, then the exception policy can chain several exception handlers together; each exception handler is still only responsible for executing one action, though.

WrapHandler (Using InnerException)

Wrapping exceptions means creating a new exception, storing the handled exception in the InnerException property of this new exception, and propagating the new exception back in place of the original. You should do this when additional contextual information needs to be added to the exception message. The Exception Management in .NET Architecture Guide states that exceptions should only be wrapped "when there is a compelling reason to do so." The WrapHandler class allows an exception type to be configured so that it will be wrapped in another exception type and will contain a predefined string to use as the new exception message. The logic for accomplishing this is contained in two methods: Initialize and HandleException.

The Initialize method reads in the configuration information for this specific instance of the WrapHandler. In the case of the WrapHandler class, the configuration information contains the exception type and exception message to use for the new "outer" exception once the exception has been wrapped. The HandleException method creates and returns the new exception. However, before it creates the new outer exception, it first tries to add a handlingInstanceID to the message. It leverages the ExceptionUtility class to provide this functionality.

The handlingInstanceID

End users are not generally shown all the complex details that are contained in an exception log. Instead, they are usually shown a friendly error message. However, without some way of correlating the user-friendly error message with the more detailed descriptions in the error log, it can be difficult for operations and support staff to help resolve issues as they occur. This is where the handlingInstanceID plays a part. The handlingInstanceID is a unique identifier (i.e., a Guid) for each handled exception in an exception handling chain. The idea behind it is that a user can provide operations and support staff with the handlingInstanceID, and the user-friendly error message can then be correlated with the more detailed descriptions contained in the log.

How an exception policy calls through to each of the exception handlers for which it has been configured was described earlier in this chapter. This list of exception handlers is known as the exception handling chain. Because every handler in an exception handling chain gets the same handlingInstanceID, exceptions can be correlated. There is, however, one requirement that must exist to get the handlingInstanceID to show up in the exception message: the {handlingInstanceID} string must exist with the predefined exception message that will be used when creating the new exception.

When the Exception Handler calls the ExceptionUtility's Format-ExceptionMessage, it passes the configured exception message and the HandlingInstanceID. The ExceptionUtility looks for a {handlingInstanceID} token to replace in the message. Case is important. If the last character is a lowercase d instead of a capital D, the handlingInstanceID will not be populated in the exception message. If the {handlingInstanceID} token isn't found in the message, then the handlingInstanceID will not be inserted in the message and it will be much more difficult to correlate exception information.

After the exception message has been created (with or without the handlingInstanceID), the WrapHandler uses reflection to create the new exception as indicated by the WrapExceptionTypeName property and populates it with the handled exception as the inner exception and the new exception message. Listing 5.3 shows how the new exception gets created.

Listing 5.3. How the WrapHandler Creates a New Outer Exception

        [C#]         try         {               object[] extraParameters = new object[2];               extraParameters[0] = wrapExceptionMessage;               extraParameters[1] = originalException;               return (Exception)Activator.CreateInstance                     (wrapExceptionType, extraParameters);         }         [Visual Basic]         Try              Dim extraParameters As Object() = New Object(1) {}              extraParameters(0) = wrapExceptionMessage              extraParameters(1) = originalException              Return CType(Activator.CreateInstance _                    (wrapExceptionType, extraParameters), Exception)         End Try

Thus, if an exception policy is configured to use a WrapHandler when a SqlException is caught, and the exception message is configured to read An exception was thrown in the data layer: {handlingInstanceID}, the resulting exception information might look like:

An exception was thrown in the data layer: f1b4752a-ab46-4a53-a867-0f9893c7c6e7

with an InnerException message of:

Cannot open database requested in login 'baddatabase'. Login fails.\r\nLogin failed for user 'lfenster'.

ReplaceHandler (Hiding Exception Information)

The point of wrapping an exception is to provide additional relevant information. The original exception is preserved in the InnerException of the newly created message so that it remains possible to dive deeper into the details of how the outer exception may have originated.

Often, however, it will be important to not share these details with others. It's a fairly common practice for hackers to try to cause exceptions on a Web site in order to leverage the details of the exception. For example, hackers will often try to get a Web site to create incorrect SQL syntax in the hope that this will cause an exception to be thrown from the database and provide clues about how to break into the database. This type of exception should not be wrapped because the original exception will still be preserved. Rather, it should be replaced altogether with an exception that contains only the information that needs to be conveyed to the end user. Thus, the Exception Handling Application Block's ReplaceHandler was born.

The ReplaceHandler is no more complex that the WrapHandler. In fact, the only difference between the two is that the ReplaceHandler doesn't populate the inner exception with the exception that is being handled. Instead, it just creates a new exception as indicated by the ReplaceExceptionTypeName and populates it with the new ExceptionMessage.

If the exception policy that was shown for the WrapHandler example is configured to use a ReplaceHandler instead, then the resulting exception will simply contain the message An exception was thrown in the data layer: f15b0a15.7a4d-44d7-abc3-d2d5f53651cf, but it won't have an InnerException.

LoggingExceptionHandler

If the exception is only replaced and no other action is taken, valuable information will be lost that might help the operations staff determine what the original cause of the exception was. Therefore, it is often important to record the information from the original exception before it is replaced entirely. This is one reason for using the LoggingExceptionHandler. Another common reason is to log exception information so developers of the application have a record of the intricate details as to why a particular exception may have occurred.

Continuing with the example started with the WrapHandler and ReplaceHandler, the exception policy could be configured to chain together a LoggingExceptionHandler with the ReplaceHandler. This will result in the original exception being logged for the operations personnel before it is replaced and sent to the client.

Logging exceptions can be extremely useful. Figure 5.5, from the Exception Management in .NET Architecture Guide, suggests ways that exceptions can be logged before they are propagated. However, it doesn't specifically indicate where the exceptions should be logged, nor does it indicate whether exceptions should be written to the event log, a file, or a centralized database.

Figure 5.5. Logging Exception Information


How and where the exceptions get logged is actually not the specific responsibility of the LoggingExceptionHandler. The LoggingExceptionHandler uses the Logging and Instrumentation Application Block to determine exactly how and where the exception will get logged. The Logging and Instrumentation Application Block is covered in depth in Chapter 6, but for now it is just important to understand that it isn't the LoggingException Handler's responsibility to actually log the exception; instead, its responsibility is to pass the exception off to the Logging and Instrumentation Application Block. It is then that block's responsibility to log the exception.

The LoggingExceptionHandler is more complex than the other two exception handlers. One reason is that it needs a lot more information to work properly. When logging a message, the Logging and Instrumentation Application Block needs to have information like the log's category, severity, and priority. All of these settings are configurable. When the LoggingExceptionHandler gets initialized, it needs to obtain and make a copy of this configuration information so that it can pass it off to the Logging and Instrumentation Application Block. Another configurable setting for the LoggingExceptionHandler is the type of ExceptionFormatter that it will use when it writes the exception information. The LoggingExceptionHandler is unique in its ability to use different exception formatters.

ExceptionFormatters

The Exception Handling Application Block provides ExceptionFormatters as a flexible way to configure and format exception messages. For example, an exception message can be formatted as XML or just plain old text. The data for the message is the same, but the ExceptionFormatter that is used will modify its appearance. It is analogous to XSLT for XML in that the data in an XML message stays the same, but the XSLT will change how the XML appears.

When an exception message is created, the AdditionalInfo property for an ExceptionFormatter adds information like the date and time an exception occurred and the machine name on which an exception occurred. The nice thing about this approach is that while this information is important contextual information that can help determine when, where, and why the exception occurred, it isn't actually part of the exception per se. The information that results from using an ExceptionFormatter is the same as deriving from the BaseApplicationException class that was provided with the Exception Management Application Block, but the implementation is different.

When exceptions get handled in the Exception Handling Application Block, the ExceptionHandler calls an ExceptionFormatter's Format method to format the exception message. The Exception Handling Application Block ships with two ExceptionFormatters: the TextException Formatter and the XmlExceptionFormatter. Both derive from the abstract ExceptionFormatter base class. Figure 5.6 displays this class hierarchy.

Figure 5.6. ExceptionFormatters in the Exception Handling Application Block


The ExceptionFormatter formats the message by writing the description of the exception's message, the current time, and then the exception. This order is determined in the ExceptionFormatter's virtual WriteException method. Because this method is virtual, the order can be changed in any derived class. In addition to the WriteException method, the Format method makes calls to several abstract methods. Therefore, a class that derives from the ExceptionFormatter base class must provide specific implementations for how it writes the exception information. Table 5.2 lists all of the ExceptionFormatter methods and properties.

Table 5.2. ExceptionFormatter's Methods and Properties

Method/Property

Access

Description

AdditionalInfo

Public

Gets additional information related to the exception but not stored in the exception (e.g., the time when the exception was thrown).

Exception

Public

Gets the exception object containing the exception that is being formatted.

Format

Public virtual

Controls the formatting of the exception into the underlying stream.

WriteAdditional-Info

Protected

Writes additional properties if available.

WriteDateTime

Protected abstract

Writes the current time.

WriteDescription

Protected abstract

Writes a description of the caught exception.

WriteException

Protected virtual

Formats the exception and all nested inner exceptions.

WriteExceptionType

Protected abstract

Writes the type of the current exception.

WriteFieldInfo

Protected abstract

Writes the value of a FieldInfo object.

WriteHelpLink

Protected abstract

Writes the value of the HelpLink property.

WriteMessage

Protected abstract

Writes the value of the Message property.

WritePropertyInfo

Protected abstract

Writes the value of a PropertyInfo object.

WriteReflectionInfo

Protected

Formats an exception using reflection.

WriteSource

Protected abstract

Writes the value of the Source property.

WriteStackTrace

Protected abstract

Writes the value of the StackTrace property.


TextExceptionFormatter

Since the ExceptionFormatter class is abstract, it cannot be created directly. Instead, one of its derived classes must be instantiated. The Exception Handling Application Block ships with the TextExceptionFormatter class for formatting exception objects as plaintext. The TextExceptionFormatter class writes each of the exception's data elements to a separate line and increments the indentation for each exception as it gets further down the stack.

Both the WrapHandler and the ReplaceHandler use the TextExceptionFormatter to format their exception messages. There's really little need for them to do otherwise as they aren't storing the exception information anywhere, but at most propagating the exception information back. On the other hand, the LoggingExceptionHandler is persisting the log information. Therefore, configuring which ExceptionFormatter it uses makes a lot more sense. Listing 5.4 shows an example of the output from a wrapped exception that uses the TextExceptionFormatter.

Listing 5.4. Example of Output Using TextExceptionFormatter

        An exception of type 'System.ApplicationException' occurred and was         caught.         - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -         05/08/2006 19:55:13         Type : System.ApplicationException, mscorlib, Version=1.0.5000.0,         Culture=neutral, PublicKeyToken=b77a5c561934e089         Message : new exception message         Source :         Help link :         TargetSite :         Stack Trace : The stack trace is unavailable.         Additional Info:         MachineName : LFENSTERBOX         TimeStamp : 5/8/2006 7:55:13 PM         FullName : Microsoft.Practices.EnterpriseLibrary.ExceptionHandling,         Version=1.0.0.0, Culture=neutral, PublicKeyToken=null         AppDomainName :         ThreadIdentity :         WindowsIdentity :              Inner Exception              - - - - - - - -              Type :         Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.Tests.MockEx-         ception, Microsoft.Practices.EnterpriseLibrary.ExceptionHandling,         Version=1.0.0.0, Culture=neutral, PublicKeyToken=null              Message : Value cannot be null.              Parameter name: MOCK EXCEPTION              Source :              Help link :              PropertyString : MockPropertyString              ParamName : MOCK EXCEPTION              TargetSite :              FieldString : MockFieldString              Stack Trace : The stack trace is unavailable.

Because the ExceptionFormatter base class handles most of the needs for any ExceptionFormatter, a derived class like the TextExceptionFormatter only needs to implement the abstract methods that write data in the specific format which it represents. For example, Listing 5.5 shows how the TextExceptionFormatter's WriteDateTime method formats the current date and time.

Listing 5.5. The Text ExceptionFormatter's WriteDateTime Method

        [C#]         protected override void WriteDateTime(DateTime utcNow)         {              DateTime localTime = utcNow.ToLocalTime();              string localTimeString = localTime.ToString                    ("G", DateTimeFormatInfo.InvariantInfo);              this.Writer.WriteLine(localTimeString);         }         [Visual Basic]         Protected Overrides Sub WriteDateTime(ByVal utcNow As DateTime)                  Dim localTime As DateTime = utcNow.ToLocalTime()                  Dim localTimeString As String = localTime.ToString _                        ("G", DateTimeFormatInfo.InvariantInfo)                  Me.Writer.WriteLine(localTimeString)         End Sub

Similar code exists for each of the ExceptionFormatter's abstract methods that the TextExceptionFormatter overrides. The TextExceptionFormatter also includes protected methods that assist in indenting the text for each exception in the call stack and for obtaining the instance of the TextWriter that it uses. Table 5.3 lists all of the methods and properties that the TextExceptionFormatter provides.

Table 5.3. TextExceptionFormatter's Methods and Properties

Method/Property

Access

Description

Writer

Public

Gets the underlying TextWriter that the current formatter is writing to.

InnerDepth

Protected virtual

Gets a value that indicates the depth of the exception currently being written.

Indent

Protected virtual

Indents the underlying TextWriter based on the value of the InnerDepth property.

WriteAdditional Info

Protected override

Writes the additional properties to the underlying stream.

WriteDateTime

Protected override

Writes the current date and time to the underlying text stream.

WriteDescription

Protected override

Writes a generic description of the caught exception.

WriteException

Protected override

Controls the formatting of the specified exception and its inner exception if there is one.

WriteExceptionType

Protected override

Writes the value of the AssemblyQualifiedName property for the specified exception type to the underlying text stream.

WriteFieldInfo

Protected override

Writes the name and value of the specified field to the underlying text stream.

WriteHelpLink

Protected override

Writes the value of the HelpLink property.

WriteMessage

Protected override

Writes the value of the Message property to the underlying text stream.

WritePropertyInfo

Protected override

Writes the name and value of the specified property to the underlying text stream.

WriteSource

Protected override

Writes the value of the Source property.

WriteStackTrace

Protected override

Formats the stack trace.


XmlExceptionFormatter.

The XmlExceptionFormatter also derives from the ExceptionFormatter abstract base class and provides a different way to represent the exception information. It works exactly like the TextExceptionFormatter except that it uses an XmlTextWriter to format the message as XML instead of strings of indented text. This type of format is much more useful when the data from the message needs to be consumed and shared with others. Because the exception data is represented with name/value pairs, you can use XML tools to search, filter, and report the data. Listing 5.6 shows what the same exception message that was shown in Listing 5.5 would look like with the XmlExceptionFormatter.

Listing 5.6. Sample Output Using an XmlExceptionFormatter

<Exception> <Description>An exception of type 'System.ApplicationException'                      occurred and was caught.</Description>      <DateTime>2006-05-08 16:29:47Z</DateTime>      <ExceptionType>System.ApplicationException, mscorlib,                         Version=1.0.5000.0, Culture=neutral,                         PublicKeyToken=b77a5c561934e089</ExceptionType>      <Message>new exception message</Message>      <Source />      <HelpLink />      <Property name="TargetSite"><undefined value></Property>      <StackTrace />      <additionalInfo>           <info name="MachineName" value="LFENSTERBOX" />           <info name="TimeStamp" value="5/8/2006 7:55:13 PM" />           <info name="FullName"                           value="Microsoft.Practices.EnterpriseLibrary.                           ExceptionHandling, Version=1.0.0.0, Culture=neutral,                         PublicKeyToken=null" />         <info name="AppDomainName" value="" />         <info name="ThreadIdentity" value="" />         <info name="WindowsIdentity" value="" /> </additionalInfo> <InnerException>        <ExceptionType>Microsoft.Practices.EnterpriseLibrary.                                 ExceptionHandling.Tests.MockException,                                 Microsoft.Practices.EnterpriseLibrary.                                 ExceptionHandling, Version=1.0.0.0,                                 Culture=neutral,PublicKeyToken=null       </ExceptionType>       <Message>Value cannot be null.\r\nParameter name: MOCK                            EXCEPTION</Message>       <Source />       <HelpLink />       <Property name="PropertyString">MockPropertyString</Property>       <Property name="ParamName">MOCK EXCEPTION</Property>       <Property name="TargetSite"><undefined value></Property>       <Field name="FieldString">MockFieldString</Field>       <StackTrace />   </InnerException> </Exception>

Creating a Custom Exception Formatter

Expressing the exception as XML is useful for many situations. However, the Exception Handling Application Block does not constrain you to only using the TextExceptionFormatter or XmlExceptionFormatter. If you need to express exception information in some other format, you can develop and use a custom ExceptionFormatter for the Exception Handling Application Block to express the exception in that format.

For example, if a company uses Microsoft Excel as a means to share information with its operations staff, it might be advantageous to express the exceptions in a comma-separated format. A CsvExceptionFormatter could be created to suit this purpose. Also, instead of displaying the additional information after the outermost exception, it might be more beneficial to have it written immediately before this exception. This would make it easy for the additional contextual information to appear at the top of an Excel worksheet. Thus, the exception information would appear in the following order.

  1. The exception description

  2. The exception date and time

  3. Any additional information

  4. A list of all exception records

To create such an ExceptionFormatter, the following two steps need to be completed.

1.

Derive a custom ExceptionFormatter class from the ExceptionFormatter abstract base class and override all of its abstract methods so that the data is written out in comma-separated format.

Listing 5.7 shows the WriteAdditionalInfo method for this class. This method calls the protected WriteWithDelimiter method, which writes a string with the predetermined delimiter. In this case, the delimiter is a comma.

Listing 5.7. The CsvExceptionFormatter's WriteAdditionalInfo Method

        [C#]         protected override void WriteAdditionalInfo              (NameValueCollection additionalInfo)         {              StringBuilder sbAddInfo = new StringBuilder();              foreach (string name in additionalInfo.AllKeys)              {                   sbAddInfo.Append(name);                   sbAddInfo.Append(" : ");                   sbAddInfo.Append(additionalInfo[name]);                   sbAddInfo.Append("\n");              }              this.WriteWithDelimter(sbAddInfo.ToString());         }         protected virtual void WriteWithDelimter(string strValue)         {              if (strValue == null)                   this.Writer.Write(Delimiter);              else              {                   string strFormat = "{0}{1}";                   char[] arrCheckDelims = new char[] {',','\n','\r'};                   if (strValue.IndexOfAny(arrCheckDelims) >= 0)                        strFormat = "\"{0}\"{1}";                   this.Writer.Write(strFormat,strValue,Delimiter);              }        }              [Visual Basic]              Protected Overrides Sub WriteAdditionalInfo _                    (ByVal additionalInfo As NameValueCollection)                   Dim sbAddInfo As StringBuilder = New StringBuilder()                   For Each name As String In additionalInfo.AllKeys                        sbAddInfo.Append(name)                        sbAddInfo.Append(" : ")                        sbAddInfo.Append(additionalInfo(name))                        sbAddInfo.Append(Constants.vbLf)                   Next name                   Me.WriteWithDelimter(sbAddInfo.ToString())              End Sub              Protected Overridable Sub WriteWithDelimter(ByVal strValue As String)                   If strValue Is Nothing Then                        Me.Writer.Write(Delimiter)                   Else                        Dim strFormat As String = "{0}{1}"                        Dim arrCheckDelims As Char() = New Char() _                             {","c,ControlChars.Lf,ControlChars.Cr}                        If strValue.IndexOfAny(arrCheckDelims) >= 0 Then                             strFormat = """{0}""{1}"                        End If                        Me.Writer.Write(strFormat,strValue,Delimiter)                   End If              End Sub

2.

If only the abstract methods were overridden, this would still be a functioning ExceptionFormatter. However, there are additional requirements for the CsvExceptionFormatter. The header information must appear before any of the generic data that gets written, and the information for each exception message in the stack should be grouped under a second lower header. To meet these requirements, the virtual Format and WriteException methods must also be overridden.

The base ExceptionFormatter's WriteException method contains a call to the WriteAdditionalInformation method. For this implementation, this call will be moved from the WriteException method to the Format method. Calls to write the header information will also be added to the Format method. It is important to keep in mind that if there were no requirements about formatting the additional exception information after the outermost exception and there was no need to write headers for the columns, then this step would not be needed. Listing 5.8 shows how the virtual Format and WriteException methods have been overridden.

Listing 5.8. CsvExceptionFormatter Format and WriteException Methods

        [C#]         public override void Format()         {              this.WriteHeader1();              this.WriteDescription();              this.WriteDateTime(DateTime.UtcNow);              this.WriteAdditionalInfo(this.AdditionalInfo);              this.WriteHeader2();              this.WriteException(Exception, null);         }         protected override void WriteException             (Exception e, Exception outerException)         {             this.WriteExceptionType(e.GetType());             this.WriteMessage(e.Message);             this.WriteSource(e.Source);             this.WriteHelpLink(e.HelpLink);             this.Writer.Write("\"');             this.WriteReflectionInfo(e);             this.Writer.Write("\",");             this.WriteStackTrace(e.StackTrace);             this.Writer.WriteLine();             Exception inner = e.InnerException;             if (inner != null)             {                  // Recursive call.                  this.WriteException(inner, e);             }        }        [Visual Basic]        Public Overrides Sub Format()             Me.WriteHeader1()             Me.WriteDescription()             Me.WriteDateTime(DateTime.UtcNow)             Me.WriteAdditionalInfo(Me.AdditionalInfo)             Me.WriteHeader2()             Me.WriteException(Exception, Nothing)             End Sub             Protected Overrides Sub WriteException _                   (ByVal e As Exception, ByVal outerException As Exception)                  Me.WriteExceptionType(e.GetType())                  Me.WriteMessage(e.Message)                  Me.WriteSource(e.Source)                  Me.WriteHelpLink(e.HelpLink)                  Me.Writer.Write(""""c)                  Me.WriteReflectionInfo(e)                  Me.Writer.Write(""",")                  Me.WriteStackTrace(e.StackTrace)                  Me.Writer.WriteLine()                  Dim inner As Exception = e.InnerException                  If Not inner Is Nothing Then                       ' Recursive call.                       Me.WriteException(inner, e)                  End If             End Sub

That's all you need to do to create the CsvExceptionFormatter. Now you can configure the CsvExceptionFormatter for use by the LoggingExceptionHandler, and instead of writing the exception information as indented text or XML, the information will be saved to a comma-delimited file. (You will learn how to configure the Formatter for the LoggingExceptionHandler later in this chapter.) Figure 5.7 shows what the comma-delimited file looks like in Microsoft Excel (with some slight formatting to highlight the headers).

Figure 5.7. Results of CsvExceptionFormatter


Since the LoggingExceptionHandler uses an ExceptionFormatter to create the message, it does not allow for a predefined exception message through configuration. A side effect of this is that there is nowhere to set where the handlingInstanceID should appear in the message. Therefore, the LoggingExceptionHandler adds the handlingInstanceID to the beginning of the message before it calls the specific ExceptionFormatter to format the message.

After the exception message has been formatted, the LoggingExceptionHandler calls its private WriteToLog method. WriteToLog creates a LogEntry and calls LogWriter.Write to log the message. Both LogEntry and LogWriter are classes in the Logging and Instrumentation Application Block. How and where this data gets logged is determined by the configuration in that block. This is covered in more detail in Chapter 6.

Listing 5.9 shows the results of using a LoggingExceptionHandler for the scenario described in Listing 5.2 and using an XmlExceptionFormatter.

Listing 5.9. Sample Output Using a LoggingExceptionHandler

[View full width]

- - - - - - - -  Log Entry  - - - - - - - - <EntLibLog> <message>HandlingInstanceID: f15b0a15.7a4d-44d7-abc3-d2d5f53651cf An exception of type 'System.Data.SqlClient.SqlException' occurred and was caught. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 05/08/2006 09:33:52 Type : System.Data.SqlClient.SqlException, System.Data, Version=1.0.5000.0,  Culture=neutral, PublicKeyToken=b77a5c561934e089 Message : Cannot open database requested in login 'baddatabase'. Login fails. Login failed for user 'lfenster'. Source : .Net SqlClient Data Provider Help link : Errors : System.Data.SqlClient.SqlErrorCollection Class : 11 LineNumber : 0 Number : 4060 Procedure : Server : State : 1 TargetSite : System.Data.SqlClient.SqlInternalConnection GetConnection(Boolean ByRef) Stack Trace :    at System.Data.SqlClient.ConnectionPool.GetConnection(Boolean& isInTransaction)    at System.Data.SqlClient.SqlConnectionPoolManager.GetPooledConnection (SqlConnectionString options, Boolean& isInTransaction)    at System.Data.SqlClient.SqlConnection.Open()    at Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.Logging.Tests.Log gingExceptionHandlerFixture.LogHandlerSqlTest() in c:\program files\microsoft enterprise library\src\exceptionhandling\logging\tests\loggingexceptionhandlerfixture.cs:line 63 Additional Info: MachineName : LFENSTERBOX TimeStamp : 5/8/2006 9:33:52 AM FullName : Microsoft.Practices.EnterpriseLibrary.ExceptionHandling, Version=1.0.0.0,           Culture=neutral, PublicKeyToken=null AppDomainName : ThreadIdentity : WindowsIdentity : </message> \t<timestamp>5/8/2006 9:33:52 AM </timestamp> \t<title>TestTitle</title> </EntLibLog> - - - - - - - - - -  End  - - - - - - - - - -

This log becomes very valuable when the LoggingExceptionHandler and ReplaceHandler are chained together for an exception. The exception information in the log can then be correlated with a message that the end user receives. In this scenario, the end user can report receiving the exception An exception was thrown in the data layer: f15b0a15.7a4d-44d7-abc3-d2d5f53651cfs, which can be correlated to the log in Listing 5.9.

Custom Handler

The Exception Management in .NET Architecture Guide points out that "While logging is important in helping you understand what went wrong and what must be done to correct the problem, notification informs you about the condition in the first place. Without correct notification, exceptions can go undetected." Yet, a "notification handler" isn't shipped as part of the Exception Handling Application Block.

However, the documentation for this block does propose a solution for one, and the code for one is supplied by way of a Quick Start. This solution creates a custom ExceptionHandler to notify users when an exception has occurred. Custom ExceptionHandlers are an extension point to the Exception Handling Application Block. They can be developed to perform exception handling in ways other than what the out-of-the-box handlers provide. As the Quick Start details, such an exception handler can be created and used to display an exception message like that shown in Figure 5.8 to an end user.

Figure 5.8. Exception Thrown with the AppMessageExceptionHandler


The Quick Start solution was not shipped as part of the actual application block because it can easily be construed that this exception handler promotes the practice of providing end users with all the exception information about an exception that has occurred. As I pointed out earlier in this chapter in the discussion about the ReplaceHandler, it is generally not a best practice to do this. Thus, a "notification handler" like the one included in the Quick Start should usually be chained with a ReplaceHandler; this will let an end user be notified about the exception, but will prevent that user from seeing all the intricate details about the exception.

Like ExceptionFormatters, ExceptionHandlers are designed for simplicity. This makes it easy to create a new custom ExceptionHandler if you need one. In fact, if no configuration information needs to be obtained to handle an exception, then the HandleException method is the only method that needs to be overridden. The HandleException method determines how an exception handler will handle the exception that gets passed to it. This is exactly the case for the AppMessageExceptionHandler that is included with the Quick Start. Listing 5.10 shows the code that was written for its HandleException method.

Listing 5.10. AppMessageExceptionHandler's HandleException Method

         [C#]          public override Exception HandleException                (Exception exception, string policyName,          Guid handlingInstanceId)          {                DialogResult result = this.ShowThreadExceptionDialog(exception);                return exception;          }          [Visual Basic]          Public Overrides Function HandleException _                (ByVal e As Exception, ByVal policyName As String, _                ByVal correlationID As Guid) As Exception               Dim result As DialogResult = Me.ShowThreadExceptionDialog(e)               Return e          End Function

The support that the Exception Handling Application Block provides to create and configure these types of ExceptionHandlers is nice because it makes it easy for you to create new ExceptionHandlers and configure them for an application without having to worry about any design-time requirements. When you create a custom ExceptionHandler, it uses the design-time properties that have been established for all custom ExceptionHandlers. Namely, these are the TypeName and Attributes properties. The Attributes property is a NameValueCollection of configuration items. If the ExceptionHandler needs to be initialized with configuration information, then the Initialize method will need to be overridden and it will need to access this configuration information from the Attributes property's NameValueCollection.

Sometimes, however, it may be desirable to create an ExceptionHandler that works more like the WrapHandler, ReplaceHandler, and LoggingExceptionHandler; that is, an exception handler that can use its own strongly typed set of design-time attributes.

For example, let's say that an organization wants to wrap exceptions a little bit differently than the WrapHandler does. The Enterprise Library's WrapHandler is sufficient for many situations; however, it cannot be used if context-sensitive information needs to be added to an exception message. The WrapHandler and ReplaceHandler are constrained by the fact that the exception messages they produce will always be the same (aside from the handlingInstanceID).

In the scenario examined earlier in this chapter for the WrapHandler, the new exception message was always An exception was thrown in the data layer: {handlingInstanceID}. There is no way to be more specific in the exception message. For example, for an enterprise portal application, there is no way for the exception message to return "An exception was thrown in the data layer trying to retrieve Announcements: {handlingInstanceID}." when the exception is caught trying to retrieve Announcements data and produce "An exception was thrown in the data layer trying to retrieve Links: {handlingInstanceID}." when the exception is caught trying to retrieve a Links data. This additional, contextual information can be extremely helpful in determining exactly where an exception occurred.

If the new exception message could be sent through the ExceptionPolicy interface, then a new exception handler would not need to be created. However, it cannot. The interaction with the Exception Handling Application Block is through the ExceptionPolicy.HandleException method. There is no argument for passing a new exception message. This is because the intent of the block is to completely decouple the exception handlers from the initiating code in the application. To truly decouple the code from the handling policies, the block only allows the exception that occurred and the name of the policy to handle it as arguments.[2]

[2] In actuality, the ConfigurationContext which represents the configuration information for this application can also be passed to the block. However, it is not relevant to the scenario.

So if additional information that is not known until runtime needs to be passed to the Exception Handling Application Block, you can develop a new ExceptionHandler that "hacks" around the interface. The following are the steps you need to complete to create this specific custom ExceptionHandler.

1.

Create a new exception type that wraps around the caught exception but lets a new exception message be created. Let's call this class WrapperException.

2.

Create a data transfer object similar to the WrapHandler's WrapHandlerData class to hold the configuration information. This class will be called ContextualWrapData.

3.

Create a new exception handler that derives from the WrapHandler but implements the functionality that I have just described. This exception handler will check to see if the exception it is handling is of the type WrapperException. If it isn't handling a WrapperException, it will just work the same way that its base WrapHandler class works. If it is handling a WrapperException, it will create the new exception the same way that the WrapHandler does, but it will set its exception message to the WrapperException's exception message, and it will set its InnerException equal to the WrapperException's InnerException. The result is that there will be no trace that the WrapperException was used to create the new exception message. Let's call this new exception handler ContextualWrapHandler.

4.

Create the design-time classes that make it easier and less error-prone to configure this ExceptionHandler by allowing the Enterprise Library Configuration Tool to be used.

Before we dive too deep into the details for how to do this, I need to go on record as stating that this is not the ideal example for creating an ExceptionHandler. A more straightforward scenario would simply have been to show the steps needed to modify the AppMessageExceptionHandler (the one provided with the Quick Start) so that it could have its own design-time features instead of relying on the design-time for custom ExceptionHandlers. However, I have included this example primarily because I have seen many situations where there is a need to wrap exceptions and still allow the exception message to be determined at runtime. At the same time, it is also advantageous for the application to reap the benefits of being decoupled from how the exception is handled.

While this exception handler provides these benefits, there is, however, a drawback to this solution that would not exist for a typical custom ExceptionHandler. Because I am passing a different exception to the Exception Handling Application Block than the one that occurred, the exception policy needs to be configured to catch a WrapperException, ApplicationException, or Exception. If the policy is configured to just catch a SqlException, the handler will not get called.

Although this sample is a bit more complex than a typical ExceptionHandler would be, I am hopeful that it adequately communicates the steps that need to be completed to create a custom ExceptionHandler with its own design-time features. I will call out which tasks are only necessary for this scenario.

Step 1: Creating the WrapperException Class

This step is only needed for this specific scenario and would not be needed to create a typical custom ExceptionHandler. The WrapperException class is just an exception that serves as a container for the new exception message and InnerException. Nothing unusual needs to be performed when creating the WrapperException; it is created just as any other custom exception would be created. Listing 5.11 shows the WrapperException class.

Listing 5.11. WrapperException Class

        [C#]         [Serializable()]         public class WrapperException : ApplicationException         {         // Default constructor.         public WrapperException() {}         // Constructor with exception message.         public WrapperException(string message) : base(message)         {}         // Constructor with message and inner exception.         public WrapperException(string message, Exception inner) :              base(message, inner)         {}         // Protected constructor to deserialize data.         public WrapperException               (SerializationInfo info, StreamingContext context) :                   base(info,context)         {}         public override void GetObjectData              (System.Runtime.Serialization.SerializationInfo info,              System.Runtime.Serialization.StreamingContext context)         {              //Persist any values into the serialized data stream here              //(e.g., info.AddValue("m_strMachineName", "My machine              //name")).              base.GetObjectData(info, context);         }   }   [Visual Basic]   <Serializable()> _   Public Class WrapperException : Inherits ApplicationException        ' Default constructor.        Public Sub New()        End Sub        ' Constructor with exception message.        Public Sub New(ByVal message As String)        End Sub        ' Constructor with message and inner exception.        Public Sub New(ByVal message As String, ByVal inner As Exception)        End Sub        ' Protected constructor to deserialize data.        Public Sub New(ByVal info As SerializationInfo, _                  ByVal context As StreamingContext)             MyBase.New(info,context)             MyBase.New(message, inner)             MyBase.New(message)        End Sub        Public Overrides Sub GetObjectData _              (ByVal info As _               System.Runtime.Serialization.SerializationInfo, _               ByVal context As _               System.Runtime.Serialization.StreamingContext)               'Persist any values into the serialized data stream here               '(e.g., info.AddValue("m_strMachineName", _               "My machine 'name"))               MyBase.GetObjectData(info, context)          End Sub     End Class

Step 2: Creating the ContextualWrapData Class

The ContextualWrapHandler needs a ContextualWrapData class in the same way that the WrapHandler class needed the WrapHandlerData to encapsulate the configuration information for it. It is needed because when the Exception Handling Application Block determines which handlers to create, it doesn't look for the type of the exception handler in an application's configuration information; instead, it looks for the type of data transfer object that will contain the configuration data for the ExceptionHandler.

The ExceptionHandler is ultimately determined by this class' TypeName property; that is, the value returned from this property is the type of the ExceptionHandler that should be created. For example, the WrapHandlerData's TypeName property returns the WrapHandler type. Custom ExceptionHandlers like the AppMessageExceptionHandler work be-cause the block creates a CustomHandlerData class and sets the value for its TypeName property to the value specified through configuration. This is also why additional strongly typed configuration information cannot be added for this type of ExceptionHandler.

Another important point to keep in mind when creating an ExceptionHandler's data transfer object is that it must be attributed with [XmlRoot("exceptionHandler", Namespace=ExceptionHandlingSettings.ConfigurationNamespace)]. If it isn't attributed this way, then a ConfigurationException will be thrown when the configuration data is being read. The reason is that the ExceptionType's ExceptionHandler property is attributed to be an array of ExceptionHandlerData with the namespace ExceptionHandlingSettings.ConfigurationNamespace.

Once you understand these facts, you can create an ExceptionHandler's data transfer object. Listing 5.12 shows the code for the ContextualWrapData class. The DefaultExceptionMessage property exists so a user can set a default exception message for this exception handler the same way that the WrapHandler allows.

Listing 5.12. ContectualWrapData Class

[C#] [XmlRoot("exceptionHandler", Namespace=ExceptionHandlingSettings. ConfigurationNamespace)] public class ContextualWrapData : WrapHandlerData {      public ContextualWrapData()      {}      public ContextualWrapData(string name) : base(name)      {}      public ContextualWrapData(string name, string exceptionMessage,                string wrapExceptionTypeName) :                     base(name, exceptionMessage,wrapExceptionTypeName)      {}      [XmlAttribute("defaultExceptionMessage")]      public string DefaultExceptionMessage      {           get { return base.ExceptionMessage; }           set { base.ExceptionMessage = value; }      }      [XmlIgnore]      public override string TypeName      {          get          {              return typeof(ContextualWrapHandler).AssemblyQualifiedName;          }          set          {          }     } } [Visual Basic] <XmlRoot("exceptionHandler", _ Namespace:=ExceptionHandlingSettings.ConfigurationNamespace)> _ Public Class ContextualWrapData : Inherits WrapHandlerData      Public Sub New()      End Sub      Public Sub New(ByVal name As String)           MyBase.New(name)      End Sub      Public Sub New(ByVal name As String, _                     ByVal exceptionMessage As String, _                     ByVal wrapExceptionTypeName As String)           MyBase.New(name, exceptionMessage,wrapExceptionTypeName)      End Sub      <XmlAttribute("defaultExceptionMessage")> _      Public Property DefaultExceptionMessage() As String           Get                Return MyBase.ExceptionMessage           End Get           Set                MyBase.ExceptionMessage = Value           End Set      End Property      <XmlIgnore> _      Public Overrides Property TypeName() As String          Get           Return GetType(ContextualWrapHandler).AssemblyQualifiedName          End Get          Set          End Set      End Property End Class

Step 3: Creating the ContextualWrapHandler Class

Creating the custom ExceptionHandler involves deriving a class from the ExceptionHandler base class and overriding the Initialize and HandleException methods. For this custom ExceptionHandler, the Con- textualWrapData object will be populated in the Initialize method instead of a CustomHandlerData. Listing 5.13 shows that the exception is being checked to determine if it is of type WrapperException, and if so the HandleException method calls the private ContextualWrapException method, which sets the new exception message and InnerException. If not, it simply operates the same way the WrapHandler does.

Listing 5.13. The ContextualWrapHandler's HandleException Method

[C#] [ReflectionPermission(SecurityAction.Demand, MemberAccess=true)] public override Exception HandleException       (Exception exception, string policyName, Guid handlingInstanceId) {      if (! (exception is WrapperException))           return base.HandleException            (exception, policyName, handlingInstanceId);      return ContextualWrapException(           exception,           WrapExceptionType,           DefaultExceptionMessage,           handlingInstanceId); } private Exception ContextualWrapException       (Exception originalException, Type wrapExceptionType,      string defaultExceptionMessage, Guid handlingInstanceId) {      string strNewExceptionMessage = originalException.Message;      if ((strNewExceptionMessage == null) ||           (strNewExceptionMessage.Equals(String.Empty)))           strNewExceptionMessage = defaultExceptionMessage;      if (strNewExceptionMessage == null)           strNewExceptionMessage = String.Empty;      strNewExceptionMessage = ExceptionUtility.FormatExceptionMessage            (strNewExceptionMessage, handlingInstanceId);      Exception innerException =            (originalException.InnerException == null)? originalException                : originalException.InnerException;      try      {           object[] extraParameters = new object[2];           extraParameters[0] = strNewExceptionMessage;           extraParameters[1] = innerException;                return (Exception)Activator.CreateInstance                      (wrapExceptionType, extraParameters);       }       catch (Exception ex)       {           throw new ExceptionHandlingException           (SR.ExceptionUnableToWrapException                (wrapExceptionType.Name), ex);       }  }  [Visual Basic]  <ReflectionPermission(SecurityAction.Demand, MemberAccess:=True)> _  Public Overrides Function HandleException _        (ByVal exception As Exception, ByVal policyName As String, _        ByVal handlingInstanceId As Guid) As Exception       If Not(TypeOf exception Is WrapperException) Then            Return MyBase.HandleException _             (exception, policyName, handlingInstanceId)       End If       Return ContextualWrapException _             (exception, WrapExceptionType, DefaultExceptionMessage, _                 handlingInstanceId)  End Function  Private Function ContextualWrapException _             (ByVal originalException As Exception, _              ByVal wrapExceptionType As Type, _              ByVal defaultExceptionMessage As String, _              ByVal handlingInstanceId As Guid) As Exception       Dim strNewExceptionMessage As String = originalException.Message            If (strNewExceptionMessage Is Nothing) OrElse _               (strNewExceptionMessage.Equals(String.Empty)) Then                 strNewExceptionMessage = defaultExceptionMessage            End If       If strNewExceptionMessage Is Nothing Then            strNewExceptionMessage = String.Empty       End If       strNewExceptionMessage = _            ExceptionUtility.FormatExceptionMessage _            (strNewExceptionMessage, handlingInstanceId)       Dim innerException As Exception = _            IIf((originalException.InnerException Is Nothing), _            originalException, originalException.InnerException)       Try            Dim extraParameters As Object() = New Object(1) {}            extraParameters(0) = strNewExceptionMessage            extraParameters(1) = innerException                 Return CType(Activator.CreateInstance _                  (wrapExceptionType, extraParameters), Exception)       Catch ex As Exception            Throw New ExceptionHandlingException _                 (SR.ExceptionUnableToWrapException _                 (wrapExceptionType.Name), ex)       End Try  End Function

Step 4: Creating Design-Time Classes

The last step is to create the design-time classes that let the Enterprise Library Configuration Tool present a user-friendly interface which makes configuring an ExceptionHandler easier and less error-prone than configuring it manually. This step is not absolutely necessary, but without it the configuration information for an ExceptionHandler would need to be entered and modified manually, and the benefits with respect to validating configuration information for it will not be realized.

A ConfigurationNode that wraps around the ContextualWrapData class and a ConfigurationDesignManager class that registers and recognizes this node need to be created. Additionally, the AssemblyInfo file must be modified so the Enterprise Library Configuration Tool can recognize the design-time features for the ContextualWrapHandler. (Chapter 2 provides much more detail about how and why these classes need to be created.)

The source code for the design-time classes for the ContextualWrapHandler is on the book's Web site, so I will not show all the details for creating these classes. Once the design-time classes have been developed and deployed, you can add and modify the ContextualWrapHandler as easily as any of the ones that ship with Enterprise Library. Figure 5.9 displays the list of options for adding an ExceptionHandler in the Enterprise Library Configuration Tool once this new assembly has been deployed.

Figure 5.9. Adding a Contextual WrapHandler to an Application's Configuration


Once an application has been configured to use the ContextualWrapHandler, the Exception Handling Application Block can use it to wrap the exception with the original exception as the InnerException and with a message that is provided at runtime. Listing 5.14 uses a static function to create the WrapperException and pass it to the Exception Handling Application Block. This step is only needed for the scenario shown here because a different exception other than the one that originally occurred is being sent to the Exception Handling Application Block. Normally, ExceptionPolicy.HandleException would just be called the way it is called for all other exception handlers.

Listing 5.14. Using the WrapperException Class with the ContextualWrapHandler

        [C#]         private static bool HandleException(Exception ex,                                             string strNewExceptionMessage,                                             string strPolicy)         {             return ExceptionPolicy.HandleException(new WrapperException                        (strNewExceptionMessage, ex),strPolicy);         }         public Announcements GetAnnouncements(int moduleId)         {             try             {                  return (Announcements) GetObject(moduleId);             }             catch (Exception ex)             {                  string strTestMessage = "An exception was thrown in the data                                 layer trying to retrieve Announcements:                                 {handlingInstanceID}";                bool bRethrow = HandleException(ex,                                   strTestMessage, "Wrap Policy");                   if (bRethrow)                            throw;             }        }        [Visual Basic]        Private Shared Function HandleException(ByVal ex As Exception, _                       ByVal strNewExceptionMessage As String, _                       ByVal strPolicy As String) As Boolean                  Return ExceptionPolicy.HandleException( _                  New WrapperException (strNewExceptionMessage, ex),strPolicy)        End Function        Public Function GetAnnouncements(ByVal moduleId As Integer) _                            As Announcements             Try                  Return CType(GetObject(moduleId), Announcements)             Catch ex As Exception                  Dim strTestMessage As String = "An exception was thrown" & _                            "in the data layer trying to retrieve " & _                            " Announcements: {handlingInstanceID}"                  Dim bRethrow As Boolean = HandleException( _                            ex, strTestMessage, "Wrap Policy")                  If bRethrow Then                       Throw                  End If             End Try        End Function

As Listings 5.15 and 5.16 show, this produces the desired result: a wrapped exception with the new, dynamic exception message and an InnerException that is equal to the exception that was caught and not the WrapperException.

Listing 5.15. Resulting Message from Using the ContextualWrapHandler

        An exception of type 'System.ApplicationException' occurred and was caught.         Type : System.ApplicationException, mscorlib, Version=1.0.5000.0,         Culture=neutral, PublicKeyToken=b77a5c561934e089         Message : An exception was thrown in the data layer trying to retrieve         Announcements: 3518ef0c-ce23-4006-9736-df8251a740b7.

Listing 5.16. Resulting InnerException from Using the ContextualWrapHandler

Type : System.Data.SqlClient.SqlException, System.Data, Ver- sion=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 Message : Cannot open database requested in login 'baddb'. Login fails. Login failed for user 'lfenster'.      Source : .Net SqlClient Data Provider ...




Fenster Effective Use of Microsoft Enterprise Library(c) Building Blocks for Creating Enterprise Applications and Services 2006
Effective Use of Microsoft Enterprise Library: Building Blocks for Creating Enterprise Applications and Services
ISBN: 0321334213
EAN: 2147483647
Year: 2004
Pages: 103
Authors: Len Fenster

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net