One of the most powerful features of the popular application framework Enterprise Library 2.0 (EL2) is the Configuration Console. The EL2 Configuration Console provides a visual representation of the different configuration settings, the actions that can be performed, and validation of the current settings. Simply stated, the configuration console allows you to create and modify the configuration files that drive the EL2 application blocks. Along with the configuration console, EL2 also provides consumers with out-of-the-box extension points that can be used to customize the framework for domain specific tasks. This can be accomplished easily by using the configuration console to plug in new extensions.
However, this technique of plugging in new extensions is less than ideal because custom extensions by default show up as "Custom xxx" in the configuration files and in the console (see Figure 1). Although this lack of an explicit name is not a showstopper, it does go against the strongly typed declarative programming model that we all love and can be complicated for beginners to understand.
In this article, I have decided to demonstrate a method of resolving this deficiency by using the Exception Handling Application block to illustrate a deep integration into EL2's configuration system. Although the focus of this example is the integration of a custom Exception Handler, the technique can be used for any application block in EL2 because the underlying architecture is consistent among all blocks. Why choose the Exception Handling Block? Well, unlike some of the other application blocks in the EL2 January 2006 release, the Exception Handling Block has very few consumer changes. Therefore, the Exception Handling Block is an ideal candidate to demonstrate some of the cross cutting architectural aspects in EL2 such as the configuration block. Additionally, exception handling can easily manifest itself as a point of contention in organizations. On one hand, exception handling should be very simple: Create a try catch block and log the exception somewhere. However, it is the finer points of exception policy management and implementation that really matter. The Exception Handling block simplifies this process by enabling effective creation of an exception handling policy as well as custom extensions.
To begin, I'll discuss a hypothetical situation in which you have created a new ExceptionHandler that uses System.Windows.Forms to display an error message to the client. This hypothetical situation may sound familiar because it is the example used in the Exception Handling Quickstart.
Now, before looking at the details of the example, it is important to examine the architecture of the configuration system and to set expectations first. The purpose of this analysis is not to go through each line of code (there are too many), but rather focus on the concepts that you will need to customize the configuration console. Nonetheless, a functional code sample can be downloaded at the end of this article to help you connect the dots.
EL2 application blocks have run-time and design-time support for configuration settings. In the run-time scenario, an application block has classes that represent configuration settings; these classes are used by the EL2 infrastructure to read the configuration settings from storage, create the objects that contain the settings data, and return them back to the originating application block. The design-time configuration, the focus of this article, has classes that enable you to use the Enterprise Library Configuration Console to change configuration settings. A dependency exists between the design-time classes and the configuration run-time classes because they obtain the current configuration settings from the run-time configuration objects. This is a really powerful aspect of EL2 because, when changing the design-time objects (in other words, changing the configuration settings), they update the run-time objects that are then saved and cached. Although a dependency exists from design-time to run-time classes, the alternate is not true. The run-time classes do not have any dependency on the design-time classes, making the entire configuration process very loosely coupled. Furthermore, the design-time classes are only required for the configuration console, not for the execution of the run-time application block.
Insuring Deep Integration in the Console
Some of you may be asking: What about dependency injection, polymorphic collections, and all the granular geek details in EL2 and the configuration block? In fact, there are many blogs, articles, and so forth that industry pundits have written on these topics. I recommend Martin Fowler's article about DI (http://www.martinfowler.com/articles/injection.html) and reiterate that the purpose of this article is to provide you with an example and an overview of integrating an extension into the Configuration Application Block.
As previously stated, the extension in this example is similar to the one found in the Exception Handling quickstart that shipped with EL2. There are a few modifications that allow you to wire it into the configuration block, and there is an additional property added, "Exception Message," that is configurable through the UI. I began by creating a new project called ExceptionHandlerExtensions and put the AppMessageExceptionHandler and the AppTextExceptionFormatter classes into it. As it is defined in the quickstart, the AppMessageExceptionHandler has an attribute that tells the configuration block to wire the handler to the CustomerHandlerData:
To have a deep integration into the console, this has to change. You have to create a new class to represent the configuration data of your custom handler and a new assembler class for AppMessageExceptionHandler. The resulting attribute will look like this:
The new class next is added to the ExceptionHandlerExtensions project and named AppMessageHandlerData. This class inherits from Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.Configuration.ExceptionHandlerData. The class should contain a required configuration property called ExceptionMessage and is the run-time representation of your configuration information. In addition, you will need to create a new assembler AppMessageHandlerAssembler to enable EL2's infrastructure to build an instance of AppMessageHandler, based on the configuration information stored in the AppMessageHandlerData class. This may be a bit confusing, but once you review the source it should make more sense. The class signature of the AppMessageHandlerAssembler will look like this:
public class AppMessageHandlerAssembler : IAssembler
It contains a single method called Assemble, which will create the AppMessageHandler and the AppMessageHandlerData classes through the EL2 infrastructure. The final piece in this project is the decoration of the AppMessageHandlerData class with the following attribute:
Creating Design Time Classes
The next step is to create the design time classes used by the Configuration Console. At the heart of the design time classes is the ConfigurationDesignManager (called AppMessagerConfigurationDesignManager in the source code). The ConfigurationDesignManager is the mechanism used to "register" a configuration section in the design time environment (also known as the configuration console). The ConfigurationDesignManager "registers" the CommandRegistrar (called AppMessageCommandRegister in the source code) and the ConfigurationNode (called AppMessageHandlerNode in the source code). The CommandRegistrar is an encapsulation for registering commands in the design time, and it contains the behaviors for actions such as deleting nodes, validation, and custom actions. The ConfigurationNode represents the design-time representation of a node in the context of the UI; this would include strong named properties such as ExceptionMessage, which you also configured in the run-time classes. In this specific case, the AppMessageHandlerNode inherits from the ExceptionHandlerNode, so the plumbing work has been done for you. The final noteworthy task in this project is the configuration of the AssemblyInfo.cs file. As you all know, the AssemblyInfo.cs is used to define a number of assembly specific configurations to the CLR. Because the configuration console loads all design-time classes through reflection, it is vital that you decorate the AssemblyInfo.cs class with this setting: [assembly: ConfigurationDesignManager(typeof(AppMessageConfigurationDesignManager), typeof(ExceptionHandlingConfigurationDesignManager))]
This setting tells the EL2 configuration block that a DesignManager exists in the assembly and also its type. Please note that these new assemblies must exist in the same directory as the executable for the configuration console. I found it easiest to add the example to the Ent Lib solution and match up the build directories; this enables you to debug the entire solution.
The First Class Integration of Your Custom EL2 Extension
The final result (see Figure 2) shows the first class integration of your custom EL2 extension, which in this case has been called "Vijay's Handler," but is configurable in a resource file in the design-time solution. For any named extension, the compiled assemblies must be located in the same directory as the Configuration Console executable. The reason is simple: The configuration console searches its base directory and uses reflection to bind to assemblies that fit into its plug-in architecture. When building the example, I found it easiest to add the new extension projects directly to the Enterprise Library solution, thereby making debugging a little easier.
Overall, the Configuration Console is an extremely powerful tool that can be used to simplify creating and editing .config files. However, the learning curve can be steep for new developers working with custom extensions, so named extension may be the solution for you.
Download the accompanying zip file here.
About the Author
Vijay P. Mehta works as a technical consultant in Indiana, where he uses VS.NET to design, develop, and architect enterprise solutions. Reach him at firstname.lastname@example.org.