Introduction
Recently, I encountered a situation where the solution was to have a common implementation for Exception Handling across all applications. Naturally, Microsoft's Enterprise Library and the Exception Handling Application Block came to mind. To satisfy the need for common handling across applications, the configuration files would need to be the same, or at least the Exception Handling entries would need to be. This could, of course, be handled by linking a common config file via source control, or simple cut & paste. The latter, though, would quickly degenerate into an unsupportable mess. The former isn't perfect either, and would be broken by branching the code base.
One way to handle this situation, while still making use of the Enterprise Library, is to have an assembly that encapsulates the common configuration settings, while still allowing for application level settings.
Exploring Exception Handling Policies
If you have used the Exception Handling Application Block, then you should be familiar with this piece of code:
ExceptionPolicy.HandleException(exception, "Policy to Use");
What happens here is that the static class, ExceptionPolicy
, will search the configuration file for the specified policy under the exceptionHandling/exceptionPolicies
section. If found, it will check if the specified exception type has been defined, and will use any handlers that have been defined to process the exception.
<exceptionHandling>
<exceptionPolicies>
<add name="Policy to Use">
<exceptionTypes>
<add type="System.Exception, ...>
<exceptionHandlers>
The ExceptionPolicy
class, like most of the Enterprise Library blocks, uses the Factory pattern to create an implementation class.
private static readonly ExceptionPolicyFactory defaultFactory =
new ExceptionPolicyFactory();
The ExceptionPolicyFactory
class in derived from the generic class LocatorNameTypeFactoryBase<T>
, which is used to create an instance of the specified type defined by a configuration source. The default implementation of this class uses the ConfigurationSourceFactory
, and the ConfigurationSourceSection
class, which as the code documentation states: Creates a new configuration source based on the configuration information from the application's default configuration file.
From this, it can be seen why the default implementation of ExceptionPolicy
only searches the application configuration file. It is, however, relatively easy to change this.
Using an alternate configuration file
The ExceptionPolicyFactory
class has an overloaded constructor that can be used for creating an instance that uses an alternative configuration source, such as an assembly's configuration file, rather than the application's configuration file.
public ExceptionPolicyFactory(IConfigurationSource configurationSource)
The IConfigurationSource
interface represents a source for getting configuration information. The Enterprise Library contains five classes that implement this interface:
ConfigurationSourceSection
NullConfigurationSource
ManageableConfigurationSource
DictionaryConfigurationSource
FileConfigurationSource
The last one, FileConfigurationSource
, is what we need for this solution. The constructor for this class takes a single parameter, which is an absolute, or relative, path to the configuration file to be used.
public FileConfigurationSource(string configurationFilepath)
HierExceptionPolicy
This static class implements the HandleException
method found in the Enterprise Library's ExceptionPolicy
class, but does so in a hierarchical fashion, starting with the configuration file for the assembly it is defined in, and working upward in the application's path. This allows for some policies to be defined at a high level, while also allowing for others to be local to the application.
The class uses a Dictionary
, which is keyed to file names to prevent duplication, to store any FileConfigurationSource
that has been found. Using lazy initialization, for performance reasons (see warning CA1018), upon first being accessed, the collection is initialized with the configuration file for this assembly. Storing the sources already found eliminates the need to continually search for configuration files on subsequent calls. This still, however, allows for configuration files to be added at runtime, as long as no existing file contains an implementation of the policy trying to be located.
private static Dictionary<string, FileConfigurationSource> ConfigSources
{
get
{
if(m_Sources == null)
{
m_Sources = new Dictionary<string, FileConfigurationSource>();
string filePath = Assembly.GetExecutingAssembly().Location + ".config";
if(File.Exists(filePath))
m_Sources.Add(Path.GetFileName(filePath), new FileConfigurationSource(filePath));
}
return m_Sources;
}
}
The HandleException
method is fairly straightforward; it finds a policy matching the name given, and handles the given Exception
. If a policy name is not passed, it will use a default policy name, cleverly called "Default Policy", in this case. A more extensible implementation would store this in the configuration file, but for demo purposes, it is hard coded, and noted in the documentation.
public static bool HandleException(Exception ex, string policyName)
{
if(ex == null)
throw new ArgumentNullException("ex");
else
ExceptionToHandle = ex;
if(string.IsNullOrEmpty(policyName))
PolicyName = "Default Policy";
else
PolicyName = policyName;
ExceptionPolicyImpl policy = GetPolicy();
if(policy == null)
throw new NoPolicyException("No policy named \"" + policyName +
"\" could be found for the exception " + ex.GetType().Name, ex);
return policy.HandleException(ex);
}
The interesting work happens in the GetPolicy
method. This is where the code attempts to find a policy, matching the name, in the configuration chain which also handles the exception. If one can't be found, a NoPolicyException
is thrown. It's up to the caller to make sure an infinite loop does not occur by trying to handle this exception.
private static ExceptionPolicyImpl GetPolicy()
{
ExceptionPolicyImpl policy = null;
foreach(FileConfigurationSource source in ConfigSources.Values)
{
policy = FindPolicy(source);
if(policy != null)
return policy;
}
string path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
policy = FindConfigFile(path);
if(policy != null)
return policy;
return null;
}
As can be seen here, the Dictionary
that stores FileConfigurationSource
s is iterated over first to find a policy in a file that may have already been found. If no policy can be found in the collection so far, which may be the case for a first run, the code then starts at the current folder level and looks for any sibling configuration files.
The FindConfigFile
method takes a path and searches for any config files it can find. After checking to make sure the file has not already been added to the collection, the FindPolicy
method is called to attempt to find an ExceptionPolicyImpl
that can be used to handle the exception. If an ExceptionPolicyImpl
can't be found at this level, the code gets the folder at the next level, and, after making sure it has not gone beyond the root of the application, calls FindConfigFile
recursively.
private static ExceptionPolicyImpl FindConfigFile(string path)
{
ExceptionPolicyImpl policy = null;
string[] files = Directory.GetFiles(path, "*.config");
foreach(string file in files)
{
if(!ConfigSources.ContainsKey(Path.GetFileName(file)))
{
FileConfigurationSource source = new FileConfigurationSource(file);
ConfigSources.Add(Path.GetFileName(file), source);
policy = FindPolicy(source);
if(policy != null)
return policy;
}
}
string parentPath = Path.GetDirectoryName(path.Substring(0,
path.LastIndexOf('\\')+1));
if(!IsPastRootFolder(parentPath))
return FindConfigFile(parentPath);
return policy;
}
This method will check the FileConfigurationSource
to see if the policy exists, and handles the exception. The ConfigurationException
is thrown if the policy can't be found in the file, in which case, the code eats it and returns null
.
private static ExceptionPolicyImpl FindPolicy(FileConfigurationSource source)
{
ExceptionPolicyFactory factory = new ExceptionPolicyFactory(source);
ExceptionPolicyImpl policy = null;
try
{
policy = factory.Create(PolicyName);
}
catch(ConfigurationException)
{
return null;
}
if(policy.GetPolicyEntry(ExceptionToHandle.GetType()) != null)
return policy;
else
return null;
}
Conclusion
This project was a simple solution for the requirement to have a global, or enterprise wide, definition for Exception Handling rules, while also allowing for the flexibility to define them at the application level. Possible extensions to this methodology would include the ability to override higher level Exception Polices at the application level.
Of course, this is only one possible solution, and I'm sure there is an easier/better method, but it's the only thing I could think of at the time. Enjoy it, and use it as a learning tool or a stepping stone for other applications.
History