Article Author: Tom Fischer
Introduction
Microsoft’s Enterprise Library stands out as one of those rare collections of code with a truly representative name – an enterprise’s library of useful features. Many architects deem them so useful they mandate the library for all of their applications. Unfortunately, there are situations when such decisions can prove painful.
Troubles encountered when running components using the Enterprise Library 1.1 with the ASP.NET 2.0 Beta releases became one such situation. Suddenly that enterprise library was far from a productivity boon. It did not even work with another version of ASP.NET!
The culprit turned out to be changes in ASP.NET 2.0 configuration mechanics. This adversely effected how Enterprise Library 1.1 components load. The immense gaggle of information residing within the many XML nodes of the Library’s numerous configuration files, such as, dataConfiguration.config and loggingConfiguration.config , could not be either properly read or deserialized without some hack or fix.
Organizations leveraging Enterprise Library 1.1 bits within their .NET 1.1 components suddenly faced some tough choices. Avoid ASP 2.0? Wait for Enterprise Library 2.0 and port code? Hack Enterprise Library 1.1? Drop the Enterprise Library altogether from their components? None of these options appeal to an enterprise architect.
This article explores a different solution, based on some code that I’ve referred to as the Enterprise Library Adapter , which is available as the sample download for this article. One based on adapting how Enterprise Library 1.1 obtains its configuration information and instantiates its different application blocks. As seen in Figure 1 the use of the Enterprise Library Adapter provides a little assistance via the Library’s very own public methods to solve the problem.

Figure 1. Use of Enterprise Library Adapter
The Enterprise Library Adapter modifies two of the Enterprise Library 1.1 behaviors. First, it gathers configuration state in a fashion overcoming the kinds of issues suffered with ASP.NET 2.0. Second, it instantiates the desired application block since the Library no longer obtains configuration data as expected.
System Requirements
The sample application does not require any especially unusual tools or technologies, though it does require working simultaneously with ASP 1.1 and ASP 2.0 web applications. In order to try the sample you will need the following:
- Microsoft SQL Server 2005 with the Northwind Database
- Microsoft Internet Information Server (IIS) 6.0
- Microsoft .NET Framework version 1.1 & 2.0
- Microsoft Visual Studio.NET 2003 & 2005
- Microsoft patterns & practices Enterprise Library – June 2005
- NUnit 2.2 (available at http://sourceforge.net/projects/nunit)
About The Sample Code
The following is a list of the solutions found within the sample download available on the ASPToday website. Figure 2 below shows the directory hierarchy of the sample solution.
- EnterpriseLibraryAdapter.sln contains the EnterpriseLibraryAdapter.csproj , the actual adapter component, along with its NUnit test code, EnterpriseLibraryAdapterTest .
- CustomComponent.sln contains NorthwindProductViewer.csproj , a simple component generating a dataset and an error, along with test code, NorthwindProductViewerTest.csproj .
- Asptoday_1_1_Web.sln contains the similarly named web application running under ASP 1.1 which displays top selling Northwind products.
- Asptoday_2_0_Web.sln contains the similarly named web application running under ASP 2.0. It too displays top selling products via code closely mirroring that found in Asptoday_1_1_Web .

Figure 2. File Directory hierarchy of the sample solution
All of the solutions essentially load the same configuration data found in the Configuration folder files. BinComponents serves as the build output folder for the EnterpriseLibraryAdapter and NorthwindProductViewer projects to which the web applications will look for their data.
Unzipping the sample code onto your computer’s c: drive minimizes any additional work required to update numerous paths and references. For example, some of the Visual Studio project post-build events will be most unhappy if they can not copy configuration files from the C:ASPTodayEnterprise Library AdapterConfiguration folder. Along the same lines, if either the Enterprise Library or NUnit default installation paths were not adhered to then component references will need updating.
The Northwind database serves up information to the NorthwindProductViewer component from the sample application. If it is not installed it can be downloaded from Microsoft and created via the instnwnd.sql script. Once installed the SQL Server login account ASPToday with the password password must be created and granted execute permission for the Ten Most Expensive Products stored procedure.
Note: SQL Server 2005 is not critical to this demonstration. Likewise, it is not required to employ SQL Server authentication. Making sure that configured information matches your environment does matter. The Enterprise Library’s help file explains how to set different database configuration properties, such as, the authentication mechanism.
Each of the two web applications requires creating their own virtual directory:
- Asptoday_1_1_Web pointing to src Asptoday_1_1_Webweb configured as an ASP.NET version 1.1
- Asptoday_2_0_Web pointing to src Asptoday_2_0_Webweb configured as an ASP.NET 2.0
Readers new to IIS 6 might want to double check the ASP.NET property page settings as pictured in Figure 3. New virtual directories assume the Default Web Site ASP.NET settings. This means that one of the created virtual directories will likely be improperly configured.

Figure 3. Configuring the ASPToday_1_1__Web Virtual Directories for ASP 1.1
Check both the Windows and Share permissions of the physical file locations containing your ASPX files. Neither IIS 6 nor Visual Studio guarantees setting requisite permissions.
Finally, walking through the demonstration code requires building the solutions in the following order:
- EnterpriseLibraryAdapter.sln
- VS2003Component.sln
- Web Solutions (VS2003Web.sln and VS2005Web.sln)
Aside from simply building each solution readers may also find running the available NUnit tests after each build helpful. Successful tests help ensure that all of the components will behave properly when called upon.
Note: Readers new to configuration files may also find how each project loads its file an interesting exercise. Inspect each NUnit project’s property Post-build event commands.
The Enterprise Library Adapter
Two big issues confront us when bypassing the Enterprise Library’s robust configuration management system. First, how to manually configure the different library blocks of interest for consumers? Second, after identifying these blocks and their related configuration information, how to instantiate them?
Answering the first question involves developing an alternate to the Enterprise Library’s master configuration file, such as, app.config or web.config . This is accomplished with our own custom configuration information.
<ASPTodayEnterpriseLibraryAdapter>
<Database apply="true" path=" dataConfiguration.config"/>
<ExceptionHandling apply="true"
path=" exceptionhandlingconfiguration.config">
<Logging path=" loggingConfiguration.config"/>
<LoggingDistributor path=" loggingDistributorConfiguration.config"/>
</ExceptionHandling>
</ASPTodayEnterpriseLibraryAdapter>
Note: Paths attribute values were truncated for display purposes. Complete paths to the configuration files, physical or UNC, are required.
This simplistic collection of elements bolstered with the supporting IConfigurationSectionHandler reader code avoids the pesky ASP.NET 2.0 configuration enhancement which induced issues challenging the Library’s use of its very own <enterpriselibrary.configurationSettings> marked up information.
What may not be apparent from the above snippet is that this solution still relies upon the Library’s application block configuration files. This allows developers to keep using existing tools, such as, EntLibConfig.exe , for manipulating and managing configuration settings.
Loading the blocks with the desired configuration turns out to be equally simple even if implementation is not obvious. Leveraging the Enterprise Library ConfigurationContext objects provides the magic. Before discussing this and customized configuration sections, a quick description of the key EnterpriseLibraryAdapter.csproj classes is in order.
- AdapterType enumerates which of the Enterprise Library 1.1 application blocks the adapter supports. In the sample this includes portions of the database and error handling blocks. Different values parallel different block features. For example, AdapterType.Database equates to those features of the Data block that interest us.
- IAdapterConfiguration specifies objects describing which Library features our solution adapts along with the path or paths to their configuration files.
- AdapterConfigurationSettings implements IConfigurationSectionHandler . Based on the provided configuration information it produces an array of IAdapterConfiguration objects relevant to the current application domain.
- AdapterConfiguration implements IAdapterConfiguration . In our application it holds configuration information for the data application block.
- AdapterExceptionConfiguration extends AdapterConfiguration . In the sample application it contains paths to other Library blocks’ configuration files that the exception handler needs for logging.
- Adapter provides ConfigurationContext objects that load a specific Library blocks.
- ConfigurationDatabase generates the database block’s ConfigurationContext .
- ConfigurationExceptionHandling constructs the exception handler’s ConfigurationContext .
- Utility collects several miscellaneous methods, such as, some XML attribute node value readers.
Figure 4 shows the class diagram for the Adapter class to help clarify its design.
Image4.gif Partial Adapter class diagram
How the pieces play together may become more apparent with the assistance of a liberally interpreted UML sequence diagram as seen in Figure 5. It starts when a custom component, NorthwindProductViewer in this case, wants to use Enterprise Library 1.1 but first checks if Adapter.GetContext() has a ConfigurationContext object for the specific feature.

Figure 5. Sample solution sequence diagram
Before Adapter can satisfy the component’s query it must not only know whether it supports the feature but whether the current AppDomain allows it! The IAdapterConfiguration objects contain just this sort of data. So Adapter nicely asks the AppDomain via GetConfig() for an array of IAdapterConfiguration objects. AppDomain in turn asks AdapterConfigurationSettings to convert its configuration data into such an array.
Based on the IAdapterConfiguration objects the Adapter can build the necessary ConfigurationContext object for the requested Library feature. If for any reason it cannot, the Adapter returns nothing, or more precisely a null . It is then up to the custom component to decide how to load the desired feature.
IAdapterConfiguration and its Implementations
Judging from the interface, Adapter is not very needy. It only wants to know (1) what Library feature it describes, AdapterType ; (2) whether or not the current AppDomain wants to use it, Apply ; and (3) where to find the feature’s configuration file, Path .
public interface IAdapterConfiguration
{ AdapterType AdapterType {get;} bool Apply {get;} string Path {get;}
}
AdapterConfiguration is the first implementation of IAdapterConfiguration you’ll explore. It turns out to be quite adequate for loading the Enterprise Library’s Data block as currently configured.
public class AdapterConfiguration : IAdapterConfiguration
{ AdapterType adapterType; bool apply; string path; private AdapterConfiguration() {} public AdapterConfiguration(AdapterType adapterType, bool apply, string path) { this.adapterType = adapterType; this.apply = apply; this.path = path; }
Properties are all read-only since they reflect read-only configuration data.
public AdapterType AdapterType
{
get { return adapterType; }
}
public bool Apply
{
get { return apply; }
}
public string Path
{
get { return path; }
}
}
The sample application’s intended usage of the Enterprise Library’s ExceptionHandling block is not so simple. The demonstration plans to use the Logging and ExceptionHandling.Logging blocks are indicated in the Configurationapp.config file. This entails loading other configuration files as shown below in AdapterExceptionConfiguration .
public class AdapterExceptionConfiguration : AdapterConfiguration
{ string loggingPath; string loggingDistributorPath;
Calling the parent’s constructor levers the AdapterConfiguration object. All that remains is adding the property code dealing with configuration file paths for Logging and Exception Handling Logging.
public AdapterExceptionConfiguration(
AdapterType adapterType, bool apply, string path,
string loggingPath, string loggingDistributorPath)
: base(adapterType, apply, path)
{
this.loggingPath = loggingPath;
this.loggingDistributorPath = loggingDistributorPath;
}
public string LoggingPath
{
get { return loggingPath; }
}
public string LoggingDistributorPath
{
get { return loggingDistributorPath; }
}
Reading Custom Configuration Data
Converting XML-based configuration data into an array of IAdapterConfiguration objects is the first significant task of the Enterprise Library Adapter. AdapterConfigurationSettings implements IConfigurationSectionHandler ‘s sole method, Create() , for the job.
The .NET Framework 1.1 SDK "Creating New Configuration Sections" topic discusses and demonstrates IConfigurationSectionHandler usage.
public class AdapterConfigurationSettings : IConfigurationSectionHandler
{ public AdapterConfigurationSettings() {} public object Create( object parent, object configContext, XmlNode section) {
The AdapterType enumeration details the subset of Library blocks supported by the adapter. Using this detail allows us to safely predict the size of adapterConfigurations , an ArrayList collection temporarily housing IAdapterConfiguration objects as generated.
ArrayList adapterConfigurations = new ArrayList(
Enum.GetValues(typeof(AdapterType)).Length);
foreach (XmlNode childNode in section.ChildNodes )
{
if (childNode.NodeType XmlNodeType.Element)
Each of the child XML element nodes generates an AdapterConfiguration via GenerateAdapterConfiguration() , a method that will be examined shortly.
adapterConfigurations.Add(
GenerateAdapterConfiguration(childNode));
}
With all of the neatly congregated IAdapterConfiguration objects, it is time to return the expected array. ArrayList makes this a simple call. The only tricky part of exploiting this nifty feature is (1) supplying the correct type, in this case an AdapterConfiguration ; and (2) casting the return to the desired type array.
return (AdapterConfiguration[])
adapterConfigurations.ToArray(typeof(AdapterConfiguration));
}
GenerateAdapterConfiguration() reads the XML-based data into a new AdapterConfiguration instance.
private AdapterConfiguration GenerateAdapterConfiguration(
XmlNode node)
{
bool apply = false;
string path = string.Empty;
AdapterType adapterType = (AdapterType)Enum.Parse(
typeof(AdapterType), node.Name);
The following two utility methods read the specified attribute of the provided node.
Utility.GetAttributeNodeValue(node, "apply", ref apply); Utility.GetAttributeNodeValue(node, "path", ref path);
With all the required configuration information in hand, it is time to build an AdapterConfiguration .
AdapterConfiguration adapterConfiguration =
new AdapterConfiguration(adapterType, apply, path);
But before returning you need to deal with one little hiccup. Exception handling requires additional information not provided by AdapterConfiguration as discussed previously. Therefore, the Adapter includes code to fabricate a more specialized child class containing it.
if (adapterConfiguration.AdapterType
AdapterType.ExceptionHandling)
adapterConfiguration = ConvertForExceptionHandling(
adapterConfiguration, node);
return adapterConfiguration;
}
ConvertForExceptionHandling() takes the newly minted AdapterConfiguration and extracts its information to create the child object containing the information required by the exception handler blocks.
private AdapterConfiguration ConvertForExceptionHandling(
AdapterConfiguration adapterConfiguration, XmlNode node)
{
string loggingPath = string.Empty;
string loggingDistributorPath = string.Empty;
The two children nodes of the exception handler element contain paths for their respective configuration files, readily extracted with Utility.GetAttributeNodeValue() .
foreach (XmlNode childNode in node.ChildNodes )
{
if (childNode.NodeType XmlNodeType.Element)
{
switch(childNode.Name)
{
case "Logging":
Utility.GetAttributeNodeValue(
childNode, "path", ref loggingPath);
break;
case "LoggingDistributor":
Utility.GetAttributeNodeValue(
childNode, "path", ref loggingDistributorPath);
break;
default:
Oh, a few words on error handling
Despite the dearth of such code in this class, checking for and throwing informative ConfigurationException objects will save you the time and hassle of explaining to a frustrated network administrators how they goofed up a web.config file. The following snippet does just that in cases where an unknown exception handling child node creeps into the fold.
throw new ConfigurationException(
childNode.Name + "not supported.");
}
}
}
return new AdapterExceptionConfiguration(
adapterConfiguration.AdapterType,
adapterConfiguration.Apply,
adapterConfiguration.Path,
loggingPath,
loggingDistributorPath);
}
Loading the Configuration Context
The Enterprise Library Adapter stands ready to generate ConfigurationContext objects once it knows how to adapt them courtesy of the IAdapterConfiguration information. Of course, the custom component requiring the blocks remains blissfully ignorant of all this soul searching. It only asked Adpter.GetContext() for a specific adapter type's ConfigurationContext , whose code I'll now review.
public sealed class Adapter
{
private Adapter() {}
public static ConfigurationContext GetContext(AdapterType adapterType)
{
One can only avoid the best practice of checking parameters for so long! It almost seems required to make sure that the requested adapterType is not just any integer. Enum.IsDefined() eases the parameter check.
if (!Enum.IsDefined(typeof(AdapterType),adapterType))
throw new ArgumentException(
"Invalid AdapterType provided.");
It turns out that the Enterprise Library enjoys its very own GetConfig() method. So to avoid any confusion you can apply the full namespace to the System's GetConfig() since it returns the desired AppDomain 's current configuration.
AdapterConfiguration[] configurations = (AdapterConfiguration[])
System.Configuration.ConfigurationSettings.GetConfig(
Utility.AdapterSectionName);
It is not an error if there are not any configurations to adapt. Returning a null makes this clear.
if (configurations null || configurations.Length 0)
return null;
The next few lines of code search for the desired AdapterConfiguration by trying to match its AdapterType with the requested AdapterType .
ConfigurationContext context = null;
foreach(AdapterConfiguration configuration in configurations)
{
if (configuration.AdapterType adapterType)
{
context = GetContext(configuration);
break;
}
}
return context;
}
GetContext() is not private for two reasons. First, it allows clients to manually bypass the Enterprise Library Adapter’s implicit configuration mechanism via direct programmatic access. Second, direct access sure makes unit testing of the ConfigurationContext objects easy.
public static ConfigurationContext GetContext(
IAdapterConfiguration configuration)
{
ConfigurationDictionary dictionary = null;
Each AdapterType relies on a specialized class that puts together its ConfigurationContext object. Later I’ll look at one of these specialized classes.
switch(configuration.AdapterType)
{
case AdapterType.Database:
dictionary = ConfigurationDatabase.GetDictionary(configuration);
break;
case AdapterType.ExceptionHandling:
ConfigurationExceptionHandling.GetDictionary() expects an AdapterExceptionConfiguration parameter. Casting it as such satisfies this expectation.
dictionary = ConfigurationExceptionHandling.GetDictionary(
(AdapterExceptionConfiguration)configuration);
break;
}
If the above lines forged a dictionary you can use the Enterprise Library’s ConfigurationManager.CreateContext() to construct the requested ConfigurationContext . It’s important to remember that failing to put together a dictionary is not necessarily an error. (Well, it might be, but let’s not think about that possibility.) There may be situations where the current AppDomain elects not to allow an adapted Library block via <ASPTodayEnterpriseLibraryAdapter> settings.
if (dictionary null)
return null;
else
return ConfigurationManager.CreateContext(dictionary);
}
}
As shown in the code above a ConfigurationDictionary object constitutes the heart of the Adpter.GetContext() . Therefore, let us turn our attention to its fabrication.
ConfiguratonDictionary Fabrication
The application exposes two major Library features - database ( AdapterType.Database ) and exception handling ( AdapterType.ExceptionHandling ). Each of them ultimately calls upon a dedicated class for composing a personalized ConfigurationDictionary for their ConfigurationContext objects. They both follow similar steps so you only need to look at one of them to get the gist of implementing any Enterprise Library feature. For this discussion I'll focus on the AdapterType.ExceptionHandling 's ConfigurationExceptionHandling class.
Fabricating most Library ConfigurationDictionary objects involves two steps. First, adding information about what the dictionary is configuring. This includes a wide variety of bits, such as namespaces and type information required by the Enterprise Library. Second, having said what is being configured we need to specify the detail that needs loading. For our application this equates to information found in the various Library configuration files stored in the Configuration folder.
One last warning before delving into ConfigurationDictionary object breeding: Like several other Enterprise Library 1.1 animals, care and feeding is marginally documented in the SDK. Fortunately, unit test code shipped with the Library provides a feast of "how to" information.
The top of the ConfigurationExceptionHandling class file merits a few comments. The different EnterpriseLibrary namespaces suggests much about its mission. Notice they all end with Configuration ? The code does not really care about the feature itself, only its configuration!
using System;
using System.IO;
using System.Collections;
using System.Xml;
using System.Xml.Serialization;
using Microsoft.Practices.EnterpriseLibrary
.Configuration;
using Microsoft.Practices.EnterpriseLibrary
.ExceptionHandling.Configuration;
using Microsoft.Practices.EnterpriseLibrary
.ExceptionHandling.Logging.Configuration;
using Microsoft.Practices.EnterpriseLibrary
.Logging.Configuration;
using Microsoft.Practices.EnterpriseLibrary
.Logging.Distributor.Configuration;
Note: The line breaks above exist for formatting purposes only. The source code does not include them.
namespace ASPToday.EnterpriseLibrary
{
internal sealed class ConfigurationExceptionHandling
{
private ConfigurationExceptionHandling() {}
One public method, GetDictionary() , does the work, and all it needs is configuration information buried in the adapterConfiguration to put together the dictionary.
public static ConfigurationDictionary GetDictionary(
AdapterExceptionConfiguration adapterConfiguration)
{
ConfigurationDictionary dictionary =
new ConfigurationDictionary();
The first step is the load of information describing the dictionary. The details of which are handled by the internal method GenerateConfigurationSettings() .
dictionary.Add(
ConfigurationSettings.SectionName,
GenerateConfigurationSettings());
The second step loads each block's configuration file's data. A little later on you'll explore one of them, GenerateExceptionHandlingSettings() , in detail.
dictionary.Add(
ExceptionHandlingSettings.SectionName,
GenerateExceptionHandlingSettings(
Utility.LoadXmlDocument(
adapterConfiguration.Path)));
dictionary.Add(
LoggingSettings.SectionName,
GenerateLoggingSettings(
Utility.LoadXmlDocument(
adapterConfiguration.LoggingPath)));
dictionary.Add(
DistributorSettings.SectionName,
GenerateDistributorSettings(
Utility.LoadXmlDocument(
adapterConfiguration.LoggingDistributorPath)));
At this point you can say mission (really) accomplished and return the dictionary .
return dictionary;
}
Not too surprisingly, given the use of three different Library blocks, GenerateConfigurationSettings() stuffs three different chucks of meta-configuration data into the dictionary.
private static ConfigurationSettings
GenerateConfigurationSettings()
{
ConfigurationSettings settings = new ConfigurationSettings();
The EnterpriseLibrary.ExceptionHandling.config section comes first.
settings.ConfigurationSections.Add(
new ConfigurationSectionData(ExceptionHandlingSettings.SectionName,
false,
new XmlFileStorageProviderData("XmlStorage",
"EnterpriseLibrary.ExceptionHandling.config"),
new XmlSerializerTransformerData(
"exceptionHandlingConfiguration")));
The EnterpriseLibrary.Logging.config section comes next.
settings.ConfigurationSections.Add(
new ConfigurationSectionData(
LoggingSettings.SectionName,
false,
new XmlFileStorageProviderData(
"XmlStorage",
"EnterpriseLibrary.Logging.config"),
new XmlSerializerTransformerData(
"DataBuilder")));
Finally, add the EnterpriseLibrary.LoggingDistributor.config section.
settings.ConfigurationSections.Add(
new ConfigurationSectionData(
DistributorSettings.SectionName,
false,
new XmlFileStorageProviderData(
"XmlStorage",
"EnterpriseLibrary.LoggingDistributor.config"),
new XmlSerializerTransformerData(
"DataBuilder")));
return settings;
}
With the dictionary now able to describe itself, it's time to consider the actual loading of the configuration data. ExceptionHandlingSettings() exemplifies such a pursuit.
private static ExceptionHandlingSettings
GenerateExceptionHandlingSettings(XmlDocument xmlDocument)
{
ExceptionHandlingSettings settings = null;
XmlNodeList nodeList = xmlDocument.GetElementsByTagName(
"enterpriseLibrary.exceptionHandlingSettings");
The following few lines deserialize the configuration file into the desired ExceptionHandlingSettings type. When performing this task, it is important to avoid memory leaks when dealing with unmanaged resources, hence the using wrapper for the StringReader instance.
using(StringReader sr = new StringReader(nodeList[0].OuterXml))
{
XmlTextReader xmlReader = new XmlTextReader(sr);
XmlSerializer xmlSerializer =
new XmlSerializer(typeof(ExceptionHandlingSettings),
If there are any tricks to the Enterprise Library Adapter, some of them are buried in the GetSettingsExtraTypes() method. It contains the .NET types allowing the deserialize magic to occur.
GetSettingsExtraTypes());
settings = (ExceptionHandlingSettings)
xmlSerializer.Deserialize(xmlReader);
}
return settings;
}
The other two configuration files deserializer methods, GenerateLoggingSettings() and GenerateDistributorSettings() , follow the same basic steps, so I won't discuss them.
As it turns out the sample application's ExceptionHandlingSettings requires little assistance to deserialize XML as shown by the private method GetSettingsExtraTypes() .
private static Type[] GetSettingsExtraTypes()
{
return new Type[1]
{
typeof(LoggingExceptionHandlerData)
};
}
The simple single element Type array may not always be enough. Peeking at the ConfigurationDatabase 's GetSettingsExtraTypes() suggests things can get complicated. It incorporates at least three types. Figuring out which type is missing for deserialization can prove difficult. Inspecting the Library's configuration files may help when attempting to uncover this requirement.
Working the Adapter
All of our work obtaining a ConfigurationContext object before calling an Enterprise Library block now bears fruit. Exploiting its existence requires minimal changes to the consuming component code, which I'll show shortly. As for the web sites using the component, the required work varies between nothing to adding our custom configuration information to the web.config file. I'll touch upon these two scenarios after discussing the NorthwindProductViewer .
Before coding it may be worth remembering that our solution adapted Enterprise Library 1.1, not the components calling it. Therefore, these components require a little tweaking to leverage the adapter. These minor updates give us a great deal of flexibility. The components will now run in both ASP 1.1 and 2.0 without any significant issues.
NorthwindProductViewer
This simple .NET 1.1 component contains one useful class, SalesSummary , which does not have a lot of code to review. The first method, GetMostExpensiveProducts() , returns a dataset based on the Northwind database Ten Most Expensive Products stored procedure.
public class SalesSummary
{
public SalesSummary() {}
public DataSet GetMostExpensiveProducts()
{
return UtilityData.Database.ExecuteDataSet(
CommandType.StoredProcedure,
"dbo.Ten Most Expensive Products");
}
The other class method generates an error handler ala the Enterprise Library's recommendation.
public DataSet GetCheapProducts()
{
try
{
throw new NotImplementedException(
"Nothing cheap at Northwind!");
}
catch(Exception ex)
{
if(UtilityExceptionPolicy.HandleException(
ex, UtilityExceptionPolicy.Propagate))
throw ex;
}
return null;
}
The nice part about these methods is the use of the two adapted blocks, UtilityData and UtilityExceptionPolicy . UtilityData exposes the Enterprise Library's Database object via a static property.
internal class UtilityData
{
private UtilityData() {}
The DatabaseNameNode constant helps us to keep an eye on the right database when accessing it.
private const string DatabaseNameNode = "Northwind";
private static Database db;
public static Database Database
{
get
{
The internal placeholder for the Database object, db , is only created once. As part of this creation process the code asks the Adapter whether or not it provides a ConfigurationContext object for db .
if (db null)
{
ConfigurationContext context =
Adapter.GetContext(AdapterType.Database);
If the Adapter supplied a ConfigurationContext object, you use it. Otherwise the code creates the db via the default Enterprise Library magic. It is this simple conditional test that allows your Enterprise Library 1.1 component to live happily with or without explicit configuration as exposed via the Enterprise Library Adapter. If the current AppDomain does not stumble across the right <ASPTodayEnterpriseLibraryAdapter> information, the DatabaseFactory runs by default.
if (context null)
db = DatabaseFactory.CreateDatabase(
DatabaseNameNode);
else
db = new DatabaseProviderFactory(
context).CreateDatabase(
DatabaseNameNode);
}
return db;
}
}
}
The UtilityExceptionPolicy follows a different path to expose the exception handler block. It just keeps a private reference to the ConfigurationContext for the block's use as shown below.
public static bool HandleException(Exception ex, string policyName)
{
if (ConfigurationContext null)
return ExceptionPolicy.HandleException(ex, policyName);
else
return ExceptionPolicy.HandleException(
ex, policyName, configurationContext);
}
Scenario #1: NorthwindProductViewer and ASP 1.1
The ASP.NET 1.1 application calls SalesSummary() to populate a data grid. As the sample output shown in Figure 6suggests there is not much code behind the curtain.

Figure 6. Sample ProductViewer page using NorthwindProductViewer and ASP 1.1
Stepping through the code is considerably more interesting. The first walk through the NorthwindProductViewer.UtilityData ‘s Database property code does not use a homegrown ConfigurationContext . Since the Enterprise Library 1.1 works well within ASP 1.1, there is no need to bypass its default configuration management tools.

Figure 7. First Walk down the NorthwindProductViewer.UtilityData Database Property
This figure has been reduced in size to fit in the text. To view the full image Click here
Scenario #2: NorthwindProductViewer and ASP 2.0
Code for displaying the Ten Most Expensive Products in the ASP.NET 2.0 closely resembles that from the APS.NET 1.1 application above. The web.config contains the difference. It not only contains <ASPTodayEnterpriseLibraryAdapter> configuration nodes, but information instructing the ASP.NET engine how to read it as shown below.
<?xml version="1.0"?>
<configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0"> <configSections> <section name="ASPTodayEnterpriseLibraryAdapter" type="ASPToday.EnterpriseLibrary.AdapterConfigurationSettings, EnterpriseLibraryAdapter" /> </configSections> <ASPTodayEnterpriseLibraryAdapter>
And as with the first scenario, stepping through the code tells all. Except in this second stroll Adpater.GetContext() does use a ConfigurationContext to access the Database object as shown in Figure 8.

Figure 8. Second walk down the NorthwindProductViewer.UtilityData Database Property
This figure has been reduced in size to fit in the text. To view the full image Click here
Conclusion
The Enterprise Library Adapter solved the problem of running .NET 1.1 components under both ASP 1.1 and ASP.NET 2.0 It also demonstrates how to bypass the Enterprise Library 1.1’s automatic configuration and instantiation mechanisms. This understanding allows us to potentially handle future configuration issues resulting from an ASP.NET version change.
Side stepping Enterprise Library 1.1 configuration woes does not make these issues disappear. In fact, bypassing some of its baked in configuration and instantiation mechanisms generates its own issues. The Enterprise Library Adapter now contains code that will need testing and tweaking as Library usage changes or ASP.NET versions change.
Finally, good news comes to us from the same folks providing the Enterprise Library, Microsoft’s Patterns and Practices group. The next release includes a more flexible configuration mechanism. Readers can visit the Enterprise Library Home page for more details.

