Chapter 3 - Error Management Made Exceptionally Easy
Introduction
Let's face it, exception handling isn't the most exciting part of writing application code. In fact, you could probably say that managing exceptions is one of those necessary tasks that absorb effort without seeming to add anything useful to your exciting new application. So why would you worry about spending time and effort actually designing a strategy for managing exceptions? Surely there are much more important things you could be doing.
In fact, a robust and well-planned exception handling plan is a vital feature of your application design and implementation. It should not be an afterthought. If you don't have a plan, you can find yourself trying to track down all kinds of strange effects and unexpected behavior in your code. And, worse than that, you may even be sacrificing security and leaving your application and systems open to attack. For example, a failure may expose error messages containing sensitive information such as: "Hi, the application just failed, but here’s the name of the server and the database connection string it was using at the time." Not a great plan.
The general expectations for exception handling are to present a clear and appropriate message to users, and to provide assistance for operators, administrators, and support staff who must resolve problems that arise. For example, the following actions are usually part of a comprehensive exception handling strategy:
- Notifying the user with a friendly message
- Storing details of the exception in a production log or other repository
- Alerting the customer service team to the error
- Assisting support staff in cross-referencing the exception and tracing the cause
So, having decided that you probably should implement some kind of structured exception handling strategy in your code, how do you go about it? A good starting point, as usual, is to see if there are any recommendations in the form of well-known patterns that you can implement. In this case, there are. The primary pattern that helps you to build secure applications is called Exception Shielding. Exception Shielding is the process of ensuring that your application does not leak sensitive information, no matter what runtime or system event may occur to interrupt normal operation. And on a more granular level, it can prevent your assets from being revealed across layer, tier, process, or service boundaries.
Two more exception handling patterns that you should consider implementing are the Exception Logging pattern and the Exception Translation pattern. The Exception Logging pattern can help you diagnose and troubleshoot errors, audit user actions, and track malicious activity and security issues. The Exception Translation pattern describes wrapping exceptions within other exceptions specific to a layer to ensure that they actually reflect user or code actions within the layer at that time, and not some miscellaneous details that may not be useful.
In this chapter, you will see how the Enterprise Library Exception Handling block can help you to implement these patterns, and become familiar with the other techniques that make up a comprehensive exception management strategy. You'll see how to replace, wrap, and log exceptions; and how to modify exception messages to make them more useful. And, as a bonus, you'll see how you can easily implement exception shielding for Windows® Communication Foundation (WCF) Web services.
When Should I Use the Exception Handling Block?
The Exception Handling block allows you to configure how you want to manage exceptions, and centralize your exception handling code. It provides a selection of plug-in exception handlers and formatters that you can use, and you can even create your own custom implementations. You can use the block when you want to implement exception shielding, modify exceptions in a range of ways, or chain exceptions (for example, by logging an exception and then passing it to another layer of your application). The configurable approach means that administrators can change the behavior of the exception management mechanism simply by editing the application configuration without requiring any changes to the code, recompilation, or redeployment.
Note
The Exception Handling block was never intended for use everywhere that you catch exceptions. The block is primarily designed to simplify exception handling and exception management at your application or layer boundaries.
How Do I Use the Exception Handling Block?
Like all of the Enterprise Library application blocks, you start by configuring your application to use the block, as demonstrated in Chapter 1, "Introduction." Then you add one or more exception policies and, for each policy, specify the type of exception it applies to. Finally, you add one or more exception handlers to each policy. The simplest approach is a policy that specifies the base type, Exception, and uses one of the handlers provided with the block. However, you'll see the various handlers, and other options, demonstrated in the examples in this chapter.
What Exception Policies Do I Need?
The key to handling exceptions is to apply the appropriate policies to each type of exception. You can pretend you are playing the well-known TV quiz game that just might make you a millionaire:
Question: How should I handle exceptions? |
|
---|---|
A: Wrap them |
B: Replace them |
C: Log and re-throw them |
D: Allow them to propagate |
You can, of course, phone a friend or ask the audience if you think it will help. However, unlike most quiz games, all of the answers are actually correct (which is why we don't offer prizes). If you answered A, B, or C, you can move on to the section "About Exception Handling Policies." However, if you answered D: Allow them to propagate, read the following section.
Allowing Exceptions to Propagate
If you cannot do anything useful when an exception occurs, such as logging exception information, modifying the exception details, or retrying the failed process, there is no point in catching the exception in the first place. Instead, you just allow it to propagate up through the call stack, and catch it elsewhere in your code—either to resolve the issue or to display error messages. Of course, at this point, you can apply an exception policy; and so you come back to how you should choose and implement an appropriate exception handling strategy.
About Exception Handling Policies
Each policy you configure for the Exception Handling block can specify one or more exception types, such as DivideByZeroException, SqlException, InvalidCastException, the base class Exception, or any custom exception type you create that inherits from System.Exception. The block compares exceptions that it handles with each of these types, and chooses the one that is most specific in the class hierarchy.
For each policy, you configure:
- One or more exception handlers that the block will execute when a matching exception occurs. You can choose from four out-of-the-box handlers: the Replace handler, the Wrap handler, the Logging handler, and the Fault Contract exception handler. Alternatively, you can create custom exception handlers and choose these (see "Extending your Exception Handling" near the end of this chapter for more information).
- A post-handling action value that specifies what happens after the Exception Handling block executes the handlers you specify. Effectively, this setting tells the calling code whether to continue executing. You can choose from:
- NotifyRethrow (the default). Return true to the calling code to indicate that it should throw an exception, which may be the one that was actually caught or the one generated by the policy.
- ThrowNewException. The Exception Handling block will throw the exception that results from executing all of the handlers.
- None. Returns false to the calling code to indicate that it should continue executing.
Figure 1 shows an example policy named MyTestExceptionPolicy in the Enterprise Library configuration console. This policy handles the three exception types—DivideByZeroException, Exception (shown as All Exceptions in the configuration tool), and InvalidCastException—and contains a mix of handlers for each exception type. The tool automatically adds the logging section to the configuration with the default settings when you add a Logging exception handler to your exception handling configuration.
Figure 1
Configuration of the MyTestExceptionPolicy exception handling policy
Notice how you can specify the properties for each type of exception handler. For example, in the previous screenshot you can see that the Replace Handler has properties for the exception message and the type of exception you want to use to replace the original exception. Also, notice that you can localize your policy by specifying the name and type of the resource containing the localized message string.
Choosing an Exception Handling Strategy
So let's get back to our quiz question, "How should I handle exceptions?" You should be able to see from the options available for exception handling policies how you can implement the common strategies for handling exceptions:
- Replace the exception with a different one and throw the new exception. This is an implementation of the Exception Shielding pattern. In your exception handling code, you can clean up resources or perform any other relevant processing. You use a Replace handler in your exception handling policy to replace the exception with a different exception containing sanitized or new information that does not reveal sensitive details about the source of the error, the application, or the operating system. Add a Logging handler to the exception policy if you want to log the exception. Place it before the Replace handler to log the original exception, or after it to log the replacement exception (if you log sensitive information, make sure your log files are properly secured). Set the post-handling action to ThrowNewException so that the block will throw the new exception.
- Wrap the exception to preserve the content and then throw the new exception. This is an implementation of the Exception Translation pattern. In your exception handling code, you can clean up resources or perform any other relevant processing. You use a Wrap handler in your exception-handling policy to wrap the exception within another exception that is more relevant to the caller and then throw the new exception so that code higher in the code stack can handle it. This approach is useful when you want to keep the original exception and its information intact, and/or provide additional information to the code that will handle the exception. Add a Logging handler to the exception policy if you want to log the exception. Place it before the Wrap handler to log the original exception, or after it to log the enclosing exception. Set the post-handling action to ThrowNewException so that the block will throw the new exception.
- Log and, optionally, re-throw the original exception. In your exception handling code, you can clean up resources or perform any other relevant processing. You use a Logging handler in your exception handling policy to write details of the exception to the configured logging store such as Windows Event Log or a file (an implementation of the Exception Logging pattern). If the exception does not require any further action by code elsewhere in the application (for example, if a retry action succeeds), set the post-handling action to None. Otherwise, set the post-handling action to NotifyRethrow. Your event handler code can then decide whether to throw the exception. Alternatively, you can set it to ThrowNewException if you always want the Exception Handling block to throw the exception for you.
Remember that the whole idea of using the Exception Handling block is to implement a strategy made up of configurable policies that you can change without having to edit, recompile, and redeploy the application. For example, the block allows you (or an administrator) to:
- Add, remove, and change the types of handlers (such as the Wrap, Replace, and Logging handlers) that you use for each exception policy, and change the order in which they execute.
- Add, remove, and change the exception types that each policy will handle, and the types of exceptions used to wrap or replace the original exceptions.
- Modify the target and style of logging, including modifying the log messages, for each type of exception you decide to log. This is useful, for example, when testing and debugging applications.
- Decide what to do after the block handles the exception. Provided that the exception handling code you write checks the return value from the call to the Exception Handling block, the post-handling action will allow you or an administrator to specify whether the exception should be thrown. Again, this is extremely useful when testing and debugging applications.
Process or HandleException?
The Exception Handling block provides two ways for you to manage exceptions. You can use the Process method to execute any method in your application, and have the block automatically perform management and throwing of the exception. Alternatively, if you want to apply more granular control over the process, you can use the HandleException method. The following will help you to understand which approach to choose.
- The Process method is the most common approach, and is useful in the majority of cases. You specify either a delegate (the address of a method) or a lambda expression that you want to execute. The Exception Handling block executes the method or expression, and automatically manages any exception that occurs. You will generally specify a PostHandlingAction of ThrowNewException so that the block automatically throws the exception that results from executing the exception handling policy. However, if you want the code to continue to execute (instead of throwing the exception), you can set the PostHandlingAction of your exception handling policy to None.
- The HandleException method is useful if you want to be able to detect the result of executing the exception handling policy. For example, if you set the PostHandlingAction of a policy to NotifyRethrow, you can use the return value of the HandleException method to determine whether or not to throw the exception. You can also use the HandleException method to pass an exception to the block and have it return the exception that results from executing the policy—which might be the original exception, a replacement exception, or the original exception wrapped inside a new exception.
You will see both the Process and the HandleException techniques described in the following examples, although most of them use the Process method.
Using the Process Method
The Process method has several overloads that make it easy to execute functions that return a value, and methods that do not. Typically, you will use the Process method in one of the following ways:
To execute a routine or method that does not accept parameters and does not return a value:
exManager.Process(method_name, "Exception Policy Name");
To execute a routine that does accept parameters but does not return a value:
exManager.Process(() => method_name(param1, param2), "Exception Policy Name");
To execute a routine that accepts parameters and returns a value:
var result = exManager.Process(() => method_name(param1, param2), "Exception Policy Name");
To execute a routine that accepts parameters and returns a value, and to also supply a default value to be returned should an exception occur and the policy that executes does not throw the exception. If you do not specify a default value and the PostHandlingAction is set to None, the Process method will return null for reference types, zero for numeric types, or the default empty value for other types should an exception occur.
var result = exManager.Process(() => method-name(param1, param2), default_result_value, "Exception Policy Name");
To execute code defined within the lambda expression itself:
exManager.Process(() => { // Code lines here to execute application feature // that may raise an exception that the Exception // Handling block will handle using the policy named // in the final parameter of the Process method. // If required, the lambda expression defined here // can return a value that the Process method will // return to the calling code. }, "Exception Policy Name");
Note
The Process method is optimized for use with lambda expressions, which are supported in C# 3.0 on version 3.5 of the .NET Framework and in Microsoft® Visual Studio® 2008 onwards. If you are not familiar with lambda functions or their syntax, see https://msdn.microsoft.com/en-us/library/bb397687.aspx. For a full explanation of using the HandleException method, see the "Key Scenarios" topic in the online documentation for Enterprise Library 4.1 at https://msdn.microsoft.com/en-us/library/dd203198.aspx.
Diving in with a Simple Example
The code you can download for this guide contains a sample application named ExceptionHandling that demonstrates the techniques described in this chapter. The sample provides a number of different examples that you can run. They illustrate each stage of the process of applying exception handling described in this chapter. However, this chapter describes an iterative process of updating a single application scenario. To make it easier to see the results of each stage, we have provided separate examples for each of them.
Note
If you run the examples under the Visual Studio debugger, you will find that the code halts when an exception occurs—before it is sent to the Exception Handling block. You can press F5 at this point to continue execution. Alternatively, you can run the examples by pressing Ctrl-F5 (non-debugging mode) to prevent this from happening.
To see how you can apply exception handling strategies and configure exception handling policies, we'll start with a simple example that causes an exception when it executes. First, we need a class that contains a method that we can call from our main routine, such as the following in the SalaryCalculator class of the example application.
public Decimal GetWeeklySalary(string employeeId, int weeks)
{
String connString = string.Empty;
String employeeName = String.Empty;
Decimal salary = 0;
try
{
connString = ConfigurationManager.ConnectionStrings
["EmployeeDatabase"].ConnectionString;
// Access database to get salary for employee here...
// In this example, just assume it's some large number.
employeeName = "John Smith";
salary = 1000000;
return salary / weeks;
}
catch (Exception ex)
{
// provide error information for debugging
string template = "Error calculating salary for {0}."
+ " Salary: {1}. Weeks: {2}\n"
+ "Data connection: {3}\n{4}";
Exception informationException = new Exception(
string.Format(template, employeeName, salary, weeks,
connString, ex.Message));
throw informationException;
}
}
You can see that a call to the GetWeeklySalary method will cause an exception of type DivideByZeroException when called with a value of zero for the number of weeks parameter. The exception message contains the values of the variables used in the calculation, and other information useful to administrators when debugging the application. Unfortunately, the current code has several issues. It trashes the original exception and loses the stack trace, preventing meaningful debugging. Even worse, the global exception handler for the application presents any user of the application with all of the sensitive information when an error occurs.
If you run the example for this chapter, and select option Typical Default Behavior without Exception Shielding, you will see this result generated by the code in the catch statement:
Exception type System.Exception was thrown.
Message: 'Error calculating salary for John Smith.
Salary: 1000000. Weeks: 0
Connection: Database=Employees;Server=CorpHQ;
User ID=admin;Password=2g$tXD76qr Attempted to divide by zero.'
Source: 'ExceptionHandlingExample'
No inner exception
Applying Exception Shielding
It's clear that the application as it stands has a severe security hole that allows it to reveal sensitive information. Of course, we could prevent this by not adding the sensitive information to the exception message. However, the information will be useful to administrators and developers if they need to debug the application. For example, if the data connection had failed or the database contained invalid data, they would have seen this through missing values for the employee name or salary; and they could see if the configuration file contains the correct database connection string. Alternatively, in the case shown here, they can immediately tell that the database returned the required values for the operation, but the user interface allowed the user to enter the value zero for the number of weeks.
To provide this extra information, yet apply exception shielding, you may consider implementing configuration settings and custom code to allow administrators to specify when they need the additional information. However, this is exactly where the Exception Handling block comes in. You can set up an exception handling policy that administrators can modify as required, without needing to write custom code or set up custom configuration settings.
The first step is to create an exception handling policy that specifies the events you want to handle, and contains a handler that will either wrap (hide) or replace (remove) the exception containing all of the debugging information with one that contains a simple error message suitable for display to users or propagation through the layers of the application. You'll see these options implemented in the following sections. You will also see how you can log the original exception before replacing it, how you can handle specific types of exceptions, and how you can apply exception shielding to WCF services.
Wrapping an Exception
If you want to retain the original exception and the information it contains, you can wrap the exception in another exception and specify a sanitized user-friendly error message for the containing exception. This is the error message that the global error handler will display. However, code elsewhere in the application (such as code in a calling layer that needs to access and log the exception details) can access the contained exception and retrieve the information it requires before passing the exception on to another layer or to the global exception handler. This intermediate code could alternatively remove the contained exception—or use an Exception Handling block policy to replace it at that point in the application.
Configuring the Wrap Handler Policy
So, the first stage is to configure the exception handling policy you require. You need to add a policy that specifies the type of exception returned from your code (in this case, we'll specify the base class Exception), and set the PostHandlingAction property for this exception type to ThrowNewException so that the Exception Handling block will automatically throw the new exception that wraps the original exception. Then, add a Wrap handler to the policy, and specify the exception message and the type of exception you want to use to wrap the original exception (we chose Exception here again). Figure 2 shows the completed configuration.
Figure 2
Configuration of the Wrap handler
To make it easier to use the objects in the Exception Handling block, you can add references to the relevant namespaces to your project.
Now you can resolve an instance of the ExceptionManager class you'll use to perform exception management. You can use the dependency injection approach described in Chapter 1, "Introduction" and Appendices A and B, or the GetInstance method. This example uses the simple GetInstance approach.
// Global variable to store the ExceptionManager instance.
ExceptionManager exManager;
// Resolve the default ExceptionManager object from the container.
exManager = EnterpriseLibraryContainer.Current.GetInstance<ExceptionManager>();
Editing the Application Code to Use the New Policy
Now you can update your exception handling code to use the new policy. You have two choices. If the configured exception policy does everything you need to do, you can actually remove the try...catch block completely from the method that may cause an error, and—instead—use the Process method of the ExceptionManager to execute the code within the method, as shown here.
public Decimal GetWeeklySalary(string employeeId, int weeks)
{
string employeeName = String.Empty;
Decimal salary = 0;
Decimal weeklySalary = 0;
exManager.Process(() => {
string connString = ConfigurationManager.ConnectionStrings
["EmployeeDatabase"].ConnectionString;
// Access database to get salary for employee.
// In this example, just assume it's some large number.
employeeName = "John Smith";
salary = 1000000;
weeklySalary = salary / weeks;
},
"ExceptionShielding");
return weeklySalary;
}
The body of your logic is placed inside a lambda function and passed to the Process method. If an exception occurs during the execution of the expression, it is caught and handled according to the configured policy. The name of the policy to execute is specified in the second parameter of the Process method.
Alternatively, you can use the Process method in your main code to call the method of your class. This is a useful approach if you want to perform exception shielding at the boundary of other classes or objects. If you do not need to return a value from the function or routine you execute, you can create any instance you need and work with it inside the lambda expression, as shown here.
exManager.Process(() =>
{
SalaryCalculator calc = new SalaryCalculator();
Console.WriteLine("Result is: {0}", calc.GetWeeklySalary("jsmith", 0));
},
"ExceptionShielding");
If you want to be able to return a value from the method or routine, you can use the overload of the Process method that returns the lambda expression value, like this.
SalaryCalculator calc = new SalaryCalculator();
var result = exManager.Process(() =>
calc.GetWeeklySalary("jsmith", 0), "ExceptionShielding");
Console.WriteLine("Result is: {0}", result);
Notice that this approach creates the instance of the SalaryCalculator class outside of the Process method, and therefore it will not pass any exception that occurs in the constructor of that class to the exception handling policy. But when any other error occurs, the global application exception handler sees the wrapped exception instead of the original informational exception. If you run the example Behavior After Applying Exception Shielding with a Wrap Handler, the catch section now displays the following. You can see that the original exception is hidden in the Inner Exception, and the exception that wraps it contains the generic error message.
Exception type System.Exception was thrown.
Message: 'Application Error. Please contact your administrator.'
Source: 'Microsoft.Practices.EnterpriseLibrary.ExceptionHandling'
Inner Exception: System.Exception: Error calculating salary for John Smith.
Salary: 1000000. Weeks: 0
Connection: Database=Employees;Server=CorpHQ;User ID=admin;Password=2g$tXD76qr
Attempted to divide by zero.
at ExceptionHandlingExample.SalaryCalculator.GetWeeklySalary(String employeeI
d, Int32 weeks) in ...\ExceptionHandling\ExceptionHandling\SalaryCalculator.cs:
line 34
at ExceptionHandlingExample.Program.<WithWrapExceptionShielding>b__0() in ...\ExceptionHandling\ExceptionHandling\Program.cs:line 109
at Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.ExceptionManagerIm
pl.Process(Action action, String policyName)
This means that developers and administrators can examine the wrapped (inner) exception to get more information. However, bear in mind that the sensitive information is still available in the exception, which could lead to an information leak if the exception propagates beyond your secure perimeter. While this approach may be suitable for highly technical, specific errors, for complete security and exception shielding, you should use the technique shown in the next section to replace the exception with one that does not contain any sensitive information.
Note
For simplicity, this example shows the principles of exception shielding at the level of the UI view. The business functionality it uses may be in the same layer, in a separate business layer, or even on a separate physical tier. Remember that you should design and implement an exception handling strategy for individual layers or tiers in order to shield exceptions on the layer or service boundaries.
Replacing an Exception
Having seen how easy it is to use exception handling policies, we'll now look at how you can implement exception shielding by replacing an exception with a different exception. This approach is also useful if you need to perform cleanup operations in your code, and then use the exception to expose only what is relevant. To configure this scenario, simply create a policy in the same way as the previous example, but with a Replace handler instead of a Wrap handler, as shown in Figure 3.
Figure 3
Configuring a Replace handler
When you call the method that generates an exception, you see the same generic exception message as in the previous example. However, there is no inner exception this time. If you run the example Behavior After Applying Exception Shielding with a Replace Handler, the Exception Handling block replaces the original exception with the new one specified in the exception handling policy. This is the result:
Exception type System.Exception was thrown.
Message: 'Application Error. Please contact your administrator.'
Source: 'Microsoft.Practices.EnterpriseLibrary.ExceptionHandling'
No Inner Exception
Logging an Exception
The previous section shows how you can perform exception shielding by replacing an exception with a new sanitized version. However, you now lose all the valuable debugging and testing information that was available in the original exception. Of course, the Librarian (remember him?) realized that you would need to retain this information and make it available in some way when implementing the Exception Shielding pattern. You preserve this information by chaining exception handlers within your exception handling policy. In other words, you add a Logging handler to the policy.
That doesn’t mean that the Logging handler is only useful as part of a chain of handlers. If you only want to log details of an exception (and then throw it or ignore it, depending on the requirements of the application), you can define a policy that contains just a Logging handler. However, in most cases, you will use a Logging handler with other handlers that wrap or replace exceptions.
Figure 4 shows what happens when you add a Logging handler to your exception handling policy. The configuration tool automatically adds the Logging Application block to the configuration with a set of default properties that will write log entries to the Windows Application Event Log. You do, however, need to set a few properties of the Logging exception handler in the Exception Handling Settings section:
- Specify the ID for the log event your code will generate as the Event ID property.
- Specify the TextExceptionFormatter as the type of formatter the Exception Handling block will use. Click the ellipsis (...) button in the Formatter Type property and select TextExceptionFormatter in the type selector dialog that appears.
- Set the category for the log event. The Logging block contains a default category named General, and this is the default for the Logging exception handler. However, if you configure other categories for the Logging block, you can select one of these from the drop-down list that is available when you click on the Logging Category property of the Logging handler.
Figure 4
Adding a logging handler
The configuration tool adds new exception handlers to the end of the handler chain by default. However, you will obviously want to log the details of the original exception rather than the new exception that replaces it. You can right-click on the Logging handler and use the shortcut menu to move it up to the first position in the chain of handlers if required.
In addition, if you did not already do so, you must add a reference to the Logging Application block assembly to your project and (optionally) add a using statement to your class, as shown here.
using Microsoft.Practices.EnterpriseLibrary.Logging;
Now, when the application causes an exception, the global exception handler continues to display the same sanitized error message. However, the Logging handler captures details of the original exception before the Exception Handling block policy replaces it, and writes the details to whichever logging sink you specify in the configuration for the Logging block. The default in this example is Windows Application Event Log. If you run the example Logging an Exception to Preserve the Information it Contains, you will see an exception like the one in Figure 5.
Figure 5
Details of the logged exception
Creating a Fault Contract
A fault contract for a WCF service will generally contain just the most useful properties for an exception, and exclude sensitive information such as the stack trace and anything else that may provide attackers with useful information about the internal workings of the service. The following code shows a simple example of a fault contract suitable for use with the Fault Contract exception handler:
[DataContract]
public class SalaryCalculationFault
{
[DataMember]
public Guid FaultID { get; set; }
[DataMember]
public string FaultMessage { get; set; }
}
Configuring the Exception Handling Policy
Figure 6 shows a sample configuration for the Fault Contract exception handler. This specifies the type SalaryCalculationFault as the target fault contract type, and the exception message that the policy will generate to send to the client. Note that, when using the Fault Contract exception handler, you should always set the PostHandlingAction property to ThrowNewException so that the Exception Handling block throws an exception that forces WCF to return the fault contract to the client.
Figure 6
The Fault Contract exception handler configuration
Notice that we specified Property Mappings for the handler that map the Message property of the exception generated within the service to the FaultMessage property of the SalaryCalculationFault class, and map the unique Handling Instance ID of the exception (specified by setting the Source to "{Guid}") to the FaultID property, as shown in Figure 6.
Editing the Service Code to Use the New Policy
After you specify your fault contract and configure the Fault Contract exception handler, you must edit your service code to use the new exception policy. If you did not already do so, you must also add a reference to the assembly that contains the Fault Contract exception handler to your project and (optionally) add a using statement to your service class, as shown here:
using Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.WCF;
You can now call the Process method of the ExceptionManager class from code in your service in exactly the same way as shown in the previous examples of wrapping and replacing exceptions in a Windows Forms application. Alternatively, you can add attributes to the methods in your service class to specify the policy they should use when an exception occurs, as shown in this code:
[ServiceContract]
public interface ISalaryService
{
[OperationContract]
[FaultContract(typeof(SalaryCalculationFault))]
decimal GetWeeklySalary(string employeeId, int weeks);
}
[ExceptionShielding("SalaryServicePolicy")]
public class SalaryService : ISalaryService
{
public decimal GetWeeklySalary(string employeeId, int weeks)
{
SalaryCalculator calc = new SalaryCalculator();
return calc.GetWeeklySalary(employeeId, weeks);
}
}
You add the ExceptionShielding attribute to a service implementation class or to a service contract interface, and use it to specify the name of the exception policy to use. If you do not specify the name of a policy in the constructor parameter, or if the specified policy is not defined in the configuration, the Exception Handling block will automatically look for a policy named WCF Exception Shielding.
The Fault Contract Exception Handler
The Exception Handling block executes the Fault Contract exception handler that you specify in your policy when an exception occurs. Effectively, the Fault Contract handler is a specialized version of the Replace handler. It takes the original exception, generates an instance of the fault contract, populates it with values from the exception, and then throws a FaultException<YourFaultContractType> exception. The handler performs the following actions:
- It generates a new instance of the fault contract class you specify for the FaultContractType property of the Fault Contract exception handler.
- It extracts the values from the properties of the exception that you pass to the method.
- It sets the values of the new fault contract instance to the values extracted from the original exception. It uses mappings between the exception property names and the names of the properties exposed by the fault contract to assign the exception values to the appropriate properties. If you do not specify a mapping, it matches the source and target properties that have the same name.
The result is that, instead of a general service failure message, the client receives a fault message containing the appropriate information about the exception.
The example Applying Exception Shielding at WCF Application Boundaries uses the service described above and the Exception Handling block WCF Fault Contract handler to demonstrate exception shielding. You can run this example in one of three ways:
- Inside Visual Studio by starting it with F5 (debugging mode) and then pressing F5 again when the debugger halts at the exception in the SalaryCalculator class.
- Inside Visual Studio by right-clicking SalaryService.svc in Solution Explorer and selecting View in Browser to start the service, then pressing Ctrl-F5 (non-debugging mode) to run the application.
- By starting the SalaryService in Visual Studio (as described in the previous bullet) and then running the executable file ExceptionHandlingExample.exe in the bin\debug folder directly.
The result is shown below. You can see that the exception raised by the SalaryCalculator class causes the service to return an instance of the SalaryCalculationFault type that contains the fault ID and fault message. However, the Exception Handling block captures this exception and replaces the sensitive information in the message with text that suggests the user contact their administrator. Research shows that users really appreciate this type of informative error message.
Getting salary for 'jsmith' from WCF Salary Service...
Exception type System.ServiceModel.FaultException`1[ExceptionHandlingExample.Sal
aryService.SalaryCalculationFault] was thrown.
Message: 'Service error. Please contact your administrator.'
Source: 'mscorlib'
No Inner Exception
Fault contract detail:
Fault ID: bafb7ec2-ed05-4036-b4d5-56d6af9046a5
Message: Error calculating salary for John Smith. Salary: 1000000. Weeks: 0
Connection: Database=Employees;Server=CorpHQ;User ID=admin;Password=2g$tXD76qr
Attempted to divide by zero.
You can also see, below the details of the exception, the contents of the original fault contract, which are obtained by casting the exception to the type FaultException<SalaryCalculationFault> and querying the properties. You can see that this contains the original exception message generated within the service. Look at the code in the example file, and run it, to see more details.
Handling Specific Exception Types
So far, all of the examples have used an exception policy that handles a single exception type (in the examples, this is Exception so that the policy will apply to any type of exception passed to it). However, you can specify multiple exception types within a policy, and specify different handlers—including chains of handlers—for each exception type. Figure 7 shows a section of the configuration console with three exception types defined for the policy named NotifyingRethrow (which is used in the next example). Each exception type has different exception handlers specified.
Figure 7
Three exception types defined
Executing Code around Exception Handling
So far, all of the examples have used the Process method to execute the code that may cause an exception. They simply used the Process method to execute the target class method, as shown here.
SalaryCalculator calc = new SalaryCalculator();
var result = exManager.Process(() =>
calc.GetWeeklySalary("jsmith", 0), "ExceptionShielding");
Console.WriteLine("Result is: {0}", result);
However, as you saw earlier in this chapter, the Process method does not allow you to detect the return value from the exception handling policy executed by the Exception Handling block (it returns the value of the method or function it executes). In some cases, though perhaps rarely, you may want to detect the return value from the exception handling policy and perform some processing based on this value, and perhaps even capture the exception returned by the Exception Handling block to manipulate it or decide whether or not to throw it in your code.
In this case, you can use the HandleException method to pass an exception to the block as an out parameter to be populated by the policy, and retrieve the Boolean result that indicates if the policy determined that the exception should be thrown or ignored.
The example Executing Custom Code Before and After Handling an Exception, demonstrates this approach. The SalaryCalculator class contains two methods in addition to the GetWeeklySalary method we’ve used so far in this chapter. These two methods, named RaiseDivideByZeroException and RaiseArgumentOutOfRangeException, will cause an exception of the type indicated by the method name when called.
The sample first attempts to execute the RaiseDivideByZeroException method, like this.
SalaryCalculator calc = new SalaryCalculator();
Console.WriteLine("Result is: {0}", calc.RaiseDivideByZeroException("jsmith", 0));
This exception is caught in the main routine using the exception handling code shown below. This creates a new Exception instance and passes it to the Exception Handling block as the out parameter, specifying that the block should use the NotifyingRethrow policy. This policy specifies that the block should log DivideByZero exceptions, and replace the message with a sanitized one. However, it also has the PostHandlingAction set to None, which means that the HandleException method will return false. The sample code simply displays a message and continues.
...
catch (Exception ex)
{
Exception newException;
bool rethrow = exManager.HandleException(ex, "NotifyingRethrow",
out newException);
if (rethrow)
{
// Exception policy setting is "ThrowNewException".
// Code here to perform any clean up tasks required.
// Then throw the exception returned by the exception handling policy.
throw newException;
}
else
{
// Exception policy setting is "None" so exception is not thrown.
// Code here to perform any other processing required.
// In this example, just ignore the exception and do nothing.
Console.WriteLine("Detected and ignored Divide By Zero Error "
+ "- no value returned.");
}
}
Therefore, when you execute this sample, the following message is displayed.
Getting salary for 'jsmith' ... this will raise a DivideByZero exception.
Detected and ignored Divide By Zero Error - no value returned.
The sample then continues by executing the RaiseArgumentOutOfRangeException method of the SalaryCalculator class, like this.
SalaryCalculator calc = new SalaryCalculator();
Console.WriteLine("Result is: {0}",
calc.RaiseArgumentOutOfRangeException("jsmith", 0));
This section of the sample also contains a catch section, which is—other than the message displayed to the screen—identical to that shown earlier. However, the NotifyingRethrow policy specifies that exceptions of type Exception (or any exceptions that are not of type DivideByZeroException) should simply be wrapped in a new exception that has a sanitized error message. The PostHandlingAction for the Exception type is set to ThrowNewException, which means that the HandleException method will return true. Therefore the code in the catch block will throw the exception returned from the block, resulting in the output shown here.
Getting salary for 'jsmith' ... this will raise an ArgumentOutOfRange exception.
Exception type System.Exception was thrown.
Message: 'An application error has occurred.'
Source: 'ExceptionHandlingExample'
Inner Exception: System.ArgumentOutOfRangeException: startIndex cannot be larger
than length of string.
Parameter name: startIndex
at System.String.InternalSubStringWithChecks(Int32 startIndex, Int32 length,
Boolean fAlwaysCopy)
at System.String.Substring(Int32 startIndex, Int32 length)
at ExceptionHandlingExample.SalaryCalculator.RaiseArgumentOutOfRangeException
(String employeeId, Int32 weeks) in ...\ExceptionHandling\ExceptionHandling\Sala
ryCalculator.cs:line 57
at ExceptionHandlingExample.Program.ExecutingCodeAroundException(Int32 positi
onInTitleArray) in ...\ExceptionHandling\ExceptionHandling\Program.cs:line 222
Assisting Administrators
Some would say that the Exception Handling block already does plenty to make an administrator's life easy. However, it also contains features that allow you to exert extra control over the way that exception information is made available, and the way that it can be used by administrators and operations staff. If you have ever worked in a technical support role, you'll recognize the scenario. A user calls to tell you that an error has occurred in the application. If you are lucky, the user will be able to tell you exactly what they were doing at the time, and the exact text of the error message. More likely, he or she will tell you that they weren't really doing anything, and that the message said something about contacting the administrator.
To resolve this regularly occurring problem, you can make use of the HandlingInstanceID value generated by the block to associate logged exception details with specific exceptions, and with related exceptions. The Exception Handling block creates a unique GUID value for the HandlingInstanceID of every execution of a policy. The value is available to all of the handlers in the policy while that policy is executing. The Logging handler automatically writes the HandlingInstanceID value into every log message it creates. The Wrap and Replace handlers can access the HandlingInstanceID value and include it in a message using the special token {handlingInstanceID}.
Figure 8 shows how you can configure a Logging handler and a Replace handler in a policy, and include the {handlingInstanceID} token in the Exception Message property of the Replace handler.
Figure 8
Configuring a unique exception handling instance identifier
Now your application can display the unique exception identifier to the user, and they can pass it to the administrator who can use it to identify the matching logged exception information. This logged information will include the information from the original exception, before the Replace handler replaced it with the sanitized exception. If you select the option Providing Assistance to Administrators for Locating Exception Details in the example application, you can see this in operation. The example displays the following details of the exception returned from the exception handling policy:
Exception type System.Exception was thrown.
Message: 'Application error. Please advise your administrator and provide them
with this error code: 22f759d3-8f58-43dc-9adc-93b953a4f733'
Source: 'Microsoft.Practices.EnterpriseLibrary.ExceptionHandling'
No Inner Exception
In a production application, you will probably show this message in a dialog of some type. One issue, however, is that users may not copy the GUID correctly from a standard error dialog (such as a message box). If you decide to use the HandlingInstanceID value to assist administrators, consider using a form containing a read-only text box or an error page in a Web application to display the GUID value in a way that allows users to copy it to the clipboard and paste into a document or e-mail message. Figure 9 shows a simple Windows Form displayed as a modal dialog. It contains a read-only TextBox control that displays the Message property of the exception, which contains the HandlingInstanceID GUID value.
Figure 9
Displaying and correlating the handling instance identifier