RSS

Speed Development With Custom Application Blocks For Enterprise Library

28 Jan

Code download available at: AppBlocks2006_07.exe (315 KB)
Browse the Code Online

Enterprise Library for Microsoft .NET Framework 2.0 is a library of application blocks, which are modular components designed to help developers deal with common development challenges. It provides an extensible framework for building robust, scalable applications. And while Enterprise Library contains several useful application blocks, you can also integrate your own reusable components. In this article, I will demonstrate how to build a sample application block that integrates with Enterprise Library.
The Microsoft patterns & practices team, which is the creator of Enterprise Library, coined the term “application block” to describe a library that is more than just a DLL file. Although an application block is indeed a DLL, it has further unique characteristics.
Application blocks are configuration-driven, meaning that configuration is used to define their behavior. This configuration can be defined in a normal .NET configuration file or it can be created and injected into the application block at run time. Some of the configuration will be typical configuration values, such as numbers defining conditions for certain behavior. But in regards to application blocks, configuration is also used to define extensions to the basic block.
Application blocks are also modular and extensible. The extensibility is usually achieved through a provider model; the block delegates some of its work to a provider, which implements a base class or interface that the block uses. Although a block typically comes with a few pre-packaged providers (for example, one that lets you log to the Windows® event log or one that lets you read and write data to SQL Server™), other providers can be developed and replaced by defining the provider type in the block’s configuration.
When would you want to create an application block instead of a normal library? If you have no need for configuration of the library and you don’t want to allow others to extend it, you do not need to create an application block. But if these capabilities sound useful, then you should consider creating an application block. And since Enterprise Library already offers a lot of configuration management infrastructure that you can make use of, you may benefit from creating an application block instead of a standalone, configurable library.
An application block adds complexity to the library, as well as a dependency on other blocks in Enterprise Library. In return, however, you gain a common configuration management system and you may be able to use other parts of Enterprise Library. For instance, data access is a very common need. If you integrate your application block with Enterprise Library, you can write an optional provider for your block that uses the Data Access Application Block. When done correctly, this creates an optional dependency on the Data Access Application Block; in cases where you do not need data access, you can avoid this dependency.

 

Creating an Application Block
In this article, I will show you how to build an application block that can be used for managing plug-ins for an application. Note that although I will cover many aspects of application block development, I will cut some corners in the part of the implementation that does not apply to Enterprise Library. As such, the solution I will create in this article is simplistic and not at all secure. For a thorough treatment on how to load plug-ins in a secure and robust fashion, see Shawn Farkas’s MSDN®Magazine article “Do You Trust It? Discover Techniques for Safely Hosting Untrusted Add-Ins with the .NET Framework 2.0“. Because the Plug-In Loader application block I’m creating here is extensible, it’s possible to develop a new provider that utilizes the methods described in Shawn Farkas’s article and add it to this application block.
Now that the disclaimer is out of the way, let’s get started. First I will define the end goal of the application block. Since its purpose is to load plug-ins, I think a reasonable goal is to enable developers to write code like this:

PlugInManager<MyPlugIn> mgr = 
    PlugInFactory.GetManager<MyPlugIn>();
IList<MyPlugIn> plugIns = mgr.GetPlugIns();
// Do something with the plug-ins...
The static PlugInFactory class creates a new PlugInManager<T>, which can be used to acquire the plug-ins. In this article, GetPlugIns is the only member of PlugInManager<T>. In a more complete implementation, other functionality—the ability to enable or disable each plug-in at run-time, for example—would likely be added. PlugInManager<T> delegates the real work to a provider specified in the application block’s configuration. This provider then loads and returns the plug-ins as defined in its implementation.
An application block consists of the run-time component and an optional design-time component. I’ll show you how to develop both of these. The run-time component is where the library’s logic is implemented. This is the file that you need to reference in your project when you want to utilize the application block. Developing the run-time component involves several steps in itself.
The design-time component is another library assembly that you can create and include. Just as the run-time component integrates with the Enterprise Library run-time framework, the design-time component integrates with Enterprise Library’s design-time tools. This design-time functionality exists primarily to make the developer’s job easier when using the application block. But it can also be used by system administrators or other users to provide a graphical tool for editing the configuration of an application.
An application block is a complex collection of code that involves a lot of interacting classes. As you read through this step-by-step guide, you may wonder why it is necessary to have so many moving parts. In part, this is because Enterprise Library provides a lot of flexibility regarding how you implement your application block. In many steps, Enterprise Library uses abstractions, such as interfaces and abstract classes, to accomplish certain tasks. For most such scenarios, an extendable default implementation is included, but you are rarely forced to use that default implementation. If you prefer, you can create your own implementation.
Each time Enterprise Library accomplishes a task—like reading configuration data from a persistent store or instantiating a concrete type when an abstract type is requested—you can extend or deviate from the default behavior. For each extensibility point in Enterprise Library there are typically a few interacting classes involved. Since Enterprise Library has a lot of extensibility points, there are many interacting classes—a fact you’ll soon discover for yourself.

 

Factories for Objects
Enterprise Library creates objects using factories. It has its own attribute-based approach in which you attribute classes with the type of their factory and then implement the factory by deriving from base classes that are provided by Enterprise Library. Figure 1 provides an overview of the typical object-creation process. As you can see, this is a complex process, but you must keep in mind that it accomplishes more than just creating an arbitrary instance of a type. You request an abstract type and, based on configuration data, you get the correct implementation of that type, appropriately configured and instrumented.
Figure 1 Object-Creation Process
Step Procedure Notes
1 Inspect the type to be created for the presence of a CustomFactory attribute. This attribute identifies a type that must be used as a custom factory for the class. If this attribute is not present, an exception is thrown.
2 Instantiate the custom factory identified in Step 1. This factory must implement ICustomFactory.
3 Call the custom factory’s CreateObject method to create an instance of the requested type. The default implementation of ICustomFactory is the abstract AssemblerBasedCus-tomFactory<TObject, TConfiguration> class. If this class is used as a base for implementing ICustomFactory, Enterprise Library continues with the following steps.
4 Call the AssemblerBasedCustomFactory<TObject, TConfiguration>.GetConfiguration method. This configuration data is used to create the requested instance and identifies the implementation type to be created.
5 Inspect the implementation type for the presence of the ConfigurationElementType attribute. This attribute identifies the type that contains configuration data for the implementation type. If this attribute is not present, an exception is thrown.
6 Instantiate the configuration type. The configuration type is populated with configuration data from the configuration source.
7 Inspect the configuration data class for the requested type, looking for the presence of the Assembler attribute. This attribute identifies a type that must be used to create the requested type from its configuration data—a so-called assembler. If this attribute is not present, an exception is thrown.
8 Instantiate the assembler. The assembler identified in Step 7 must implement IAssembler<TObject, TConfiguration> or an exception is thrown.
9 The assembler creates the requested object. The configuration data defines the parameters for the object.
This object creation framework is highly extensible and you should use it as an object creation mechanism for your own application block, but you will soon find yourself creating a lot of factory classes. Figure 2 provides an overview of the interaction between these classes. When using the Plug-In Loader application block, the static PlugInFactory class is typically the entry point. This class is just a convenience class, which uses PlugInManagerFactory<T> to create instances of PlugInManager<T>. Most Enterprise Library application blocks provide such a static factory class as an entry point to the application block. Implementing a static factory class is optional when creating an Enterprise Library application block, but I’ve decided to include such a class for Plug-In Loader since it makes sense here.

Figure 2 Interaction of Object Factory Classes
Enterprise Library takes a factory-based approach to creating configurable objects, and although that framework is very flexible, it does not support generics since types are unknown at compile time. For that reason, the generic Plug-In Loader classes delegate most of their work to non-generic classes, which can be created and managed by Enterprise Library. PlugInManager<T> wraps the abstract PlugInProvider class, and PlugInManagerFactory<T> wraps PlugInProviderFactory. If your application block does not need to support generics, you don’t have to implement classes like PlugInManager<T> and PlugInManagerFactory<T>.
PlugInProviderFactory, which creates PlugInProvider instances, derives from a class provided by Enterprise Library. Using the CustomFactory attribute described in Step 1 of Figure 1, PlugInProvider identifies PlugInProviderCustomFactory, which Enterprise Library uses together with configuration classes to create instances of PlugInProvider.
This may seem complex, but Enterprise Library does most of the heavy lifting. You need to implement quite a few classes, but all of them are quite simple. You may be asking yourself why all this complexity is required. As I mentioned earlier, you are doing more than just newing up an instance of PlugInProvider. For starters, you cannot instantiate PlugInProvider, since it is an abstract class. When you request an instance of PlugInProvider, the application block must really serve up an implementation of that abstract class, based on configuration data. Enterprise Library uses this mechanism to identify what to create and how to create it.

 

Creating Factories
If you look at the intended target code I showed earlier, you can see that the first classes I need to implement are PlugInManager<T> and PlugInFactory. Both of these classes delegate most of their work to other classes. PlugInManager<T> uses PlugInProvider to perform the real work, as shown in Figure 3. Since the Enterprise Library object creation mechanism does not support generics, PlugInProvider is not a generic class, so to return IList<T>, the IList returned by PlugInProvider must be converted to the appropriate type-safe collection.
Figure 3 Getting a PlugInProvider
public class PlugInManager<T>
{
    private PlugInProvider provider_;

    internal PlugInManager(PlugInProvider provider)
    {
        this.provider_ = provider;
    }

    public IList<T> GetPlugIns()
    {
        IList plugIns = this.provider_.GetPlugIns();
        List<T> returnList = new List<T>(plugIns.Count);
        foreach (T item in plugIns) returnList.Add(item);
        return returnList;
    }
}
PlugInProvider itself is the simple class shown in Figure 4. Since it is abstract, it provides the extensibility point for Plug-In Loader. If you want to extend Plug-In Loader, you can derive a new class from PlugInProvider and implement your custom logic there. The most notable feature of PlugInProvider is the CustomFactory attribute, which instructs Enterprise Library how to create a new instance of a class that derives from PlugInProvider. Also note the abstract GetPlugIns method, which is the method that inheritors have to implement.
Figure 4 Implementation of PlugInProvider
[CustomFactory(typeof(PlugInProviderCustomFactory))]
public abstract class PlugInProvider
{
    private Type plugInType_;

    protected PlugInProvider(Type plugInType)
    {
        this.plugInType_ = plugInType;
    }

    public abstract IList GetPlugIns();

    protected Type PlugInType
    {
        get { return this.plugInType_; }
    }
}
PlugInProviderCustomFactory derives from AssemblerBased-CustomFactory<TObject, TConfiguration>, which is an abstract class provided by Enterprise Library. The only thing you have to do when deriving from this class is implement the GetConfiguration method. A couple of new classes, PlugInLoaderSettings and PlugInProviderData, make their first appearance here, as you can see in the following code:

public class PlugInProviderCustomFactory :
    AssemblerBasedCustomFactory<PlugInProvider, PlugInProviderData>
{
    protected override PlugInProviderData GetConfiguration(
        string name, IConfigurationSource configurationSource)
    {
        PlugInLoaderSettings settings =
            (PlugInLoaderSettings)configurationSource.
                GetSection(PlugInLoaderSettings.SectionName);
        return settings.PlugInProviders.Get(name);
    }
}

These classes are configuration classes and I will discuss them in more detail in the next section.

For now, the most important thing to notice is that the GetConfiguration method returns the appropriate configuration data, enabling Enterprise Library to construct a new PlugInProvider object, as described in Figure 1. With this custom factory in place, I can create a factory class that I can then use to create PlugInProvider instances, as shown here:

public class PlugInProviderFactory : 
    NameTypeFactoryBase<PlugInProvider>
{
    public PlugInProviderFactory(IConfigurationSource configSource) :
        base(configSource) { }
}

Although this is yet another factory class, all I need to do is derive from the NameTypeFactoryBase<T> class and provide a public constructor. PlugInManagerFactory<T> simply wraps PlugInProviderFactory. PlugInFactory creates and maintains a dictionary of these factories, and delegates the work to the appropriate factory, as shown in the code in Figure 5.

Figure 5 Creating Factories and Sending Them Work
public class PlugInManagerFactory<T>
{
    private PlugInProviderFactory factory_;

    public PlugInManagerFactory(IConfigurationSource 
            configurationSource)
    {
        this.factory_ = new PlugInProviderFactory(configurationSource);
    }

    public PlugInManager<T> Create()
    {
        string name = typeof(T).AssemblyQualifiedName;
        return new PlugInManager<T>(this.factory_.Create(name));
    }
}

public static class PlugInFactory
{
    private readonly static Dictionary<Type, object> factories_ =
        new Dictionary<Type, object>();

    public static PlugInManager<T> GetManager<T>()
    {
        if (!PlugInFactory.factories_.ContainsKey(typeof(T)))
        {
            IConfigurationSource configSource =
                ConfigurationSourceFactory.Create();
            PlugInManagerFactory<T> factory =
                new PlugInManagerFactory<T>(configSource);
            PlugInFactory.factories_.Add(typeof(T), factory);
        }

        object piFactory = PlugInFactory.factories_[typeof(T)];
        return ((PlugInManagerFactory<T>)piFactory).Create();
    }
}
One item of note here is a naming convention that is specific to Plug-In Loader. Enterprise Library polymorphic collections are keyed by their items’ names, so each name must be unique within the collection. For Plug-In Loader, the type of plug-in would have been the most intuitive key. However, that would have required me to create my own collection class keyed on type, and thus I would not have been able to reuse the classes provided by Enterprise Library.
As the design-time component requires a unique name in any case, the convention for Plug-In Loader is for each PlugInProvider to be named by the assembly-qualified name of the plug-in type, which is what you see in effect in Figure 5. It is a bit of a hack, but the user will never notice, because I will also deal with this convention in the design-time component. On the other hand, if you feel like editing the raw XML, everything is a string in that world in any case.
That’s it for the first step of creating an application block. If you refer back to Figure 1, you may wonder why I have not defined any assembler classes. This is because assemblers are tied to implementations of PlugInProvider, not the abstract PlugInProvider class itself. Now I will describe how to define configuration classes, and then I will describe how to implement a PlugInProvider, which will include a discussion of creating the appropriate assembler.

 

Defining Plug-In Configuration
The configuration framework of Enterprise Library builds on top of System.Configuration and works very similarly. To define the configuration section for Plug-In Loader, I create the PlugInLoaderSettings class as shown in Figure 6. But instead of deriving directly from System.Configuration.ConfigurationSection, an application block’s configuration section should derive from Microsoft.Practices.EnterpriseLibrary.Common.Configuration.SerializableConfigurationSection. This adds Enterprise Library functionality to the class; among other things, this lets you store the configuration section somewhere other than the application configuration file.
Figure 6 PlugInLoaderSettings
public class PlugInLoaderSettings : SerializableConfigurationSection
{
    public const string SectionName = "plugInConfiguration";

    private const string providersProperty_ = "plugInProviders";

    public PlugInLoaderSettings() : base() {}

    [ConfigurationProperty(PlugInLoaderSettings.providersProperty_)]
    public NameTypeConfigurationElementCollection<PlugInProviderData>
        PlugInProviders
    {
        get
        {
            return (NameTypeConfigurationElementCollection
                    <PlugInProviderData>)
                    this[PlugInLoaderSettings.providersProperty_]; 
        }
    }
}
The PlugInLoaderSettings class contains only a collection of PlugInProviderData classes. The PlugInProviderData class contains the data used to configure an instance of PlugInProvider, as shown in the following:

public class PlugInProviderData : NameTypeConfigurationElement
{
    public PlugInProviderData() : base() { }
    public PlugInProviderData(string name, Type type) :
        base(name, type) { }
}
This class represents a configuration element and derives indirectly from System.Configuration.ConfigurationElement. Had I wanted to create a simple configuration element, I could have derived PlugInProviderData directly from ConfigurationElement, but Enterprise Library gives me a couple of other options. One is the NamedConfigurationElement class, the other is NameTypeConfigurationElement. The former adds a name to the configuration element, which is useful when implementing the design-time features of the application block. This name also acts as the unique key in the generic configuration collection classes provided by Enterprise Library.
NameTypeConfigurationElement adds an additional Type property to the configuration element, and this is used to support polymorphic collections, which is exactly what I want in this case—to specify different plug-in providers, each with unique configuration settings, for different plug-in types. Where the name of the configuration element acts as a key to that element, the Type property identifies the type that the element configures. In the case of Plug-In Loader, the Type property identifies a type that implements PlugInProvider. Recall that, by convention, Plug-In Loader uses the Name property to store the assembly-qualified name of the type of plug-in. It is easy to mix up these two types, but the name identifies which plug-in type should be served by the provider, whereas the Type property identifies the type of the provider. Since the name is the key, you can only have a type of plug-in defined once, but many different plug-in types can be served up by the same provider type; in fact, most often they will probably all be served up by the same provider.
Due to the way Enterprise Library constructs these configuration element classes, it is not possible to make the PlugInProviderData class abstract, but you should think about it as being abstract. Notice that it does not really do anything, so in this particular implementation, I could have omitted it and created my configuration elements for the different plug-in providers by deriving them directly from NameTypeConfigurationElement. However, the abstract PlugInProvider class does contain some implementation, and it is easier to understand the structure of the application block’s code if there is a one-to-one relationship between providers and their configuration elements.

 

Implementing PlugInProvider
At this point, the abstract framework of the run-time component is complete, but there is still no functionality. It is now to time to create an implementation of PlugInProvider. This is a naïve implementation that is not secure and does not support plug-ins that can be unloaded from memory. For that reason, I will call it NaivePlugInProvider.
Shown in Figure 7, the NaivePlugInProvider class derives from PlugInProvider. Its main functionality is implemented in the GetPlugIns method, which simply loads and reflects over all types in all assemblies located in the configured folder. If a type implements the desired plug-in type, a new instance of that type is created and added to the list of plug-ins to return. Note that this implementation requires all plug-ins to have a default constructor; a more robust implementation might use a more sophisticated approach.
Figure 7 Implementing the Actual Plug-In Provider
[ConfigurationElementType(typeof(NaivePlugInProviderData))]
public class NaivePlugInProvider : PlugInProvider
{
    private string plugInFolder_;

    public NaivePlugInProvider(Type plugInType, string plugInFolder)
        : base(plugInType)
    {
        this.plugInFolder_ = plugInFolder;
    }

    public override IList GetPlugIns()
    {
        ArrayList plugIns = new ArrayList();

        string[] assemblyFileCandidates =
            Directory.GetFiles(this.plugInFolder_, "*.dll");
        foreach (string assemblyFileCandidate in assemblyFileCandidates)
        {
            Assembly a = Assembly.LoadFrom(assemblyFileCandidate);
            foreach (Type t in a.GetTypes())
            {
                if((t.IsPublic) && (this.PlugInType.IsAssignableFrom(t)))
                {
                    plugIns.Add(Activator.CreateInstance(t));
                }
            }
        }

        return plugIns;
    }
}
NaivePlugInProvider has two other traits that are not quite so apparent, but that are interesting in the context of creating an Enterprise Library application block: the use of the ConfigurationElementType attribute, and the lack of a default constructor.
When configuring the Plug-In Loader application block, you should only need to be concerned about which PlugInProvider you want to use, not about which class provides configuration data for that provider. The ConfigurationElementType attribute contains that information, which means that the configuration data only contains information about which PlugInProvider to create, and the Enterprise Library infrastructure figures out which class contains configuration data for that provider. In this case, it is the NaivePlugInProviderData class shown in Figure 8. This class derives from PlugInProviderData and provides an extra configuration property that allows you to specify the folder that will contain the plug-in assemblies.
Figure 8 NaivePlugInProviderData and NaivePlugInProviderAssembler
[Assembler(typeof(NaivePlugInProviderAssembler))]
public class NaivePlugInProviderData : PlugInProviderData
{
    private const string folderProperty_ = "plugInFolder";

    public NaivePlugInProviderData() : base()
    {
        this.Type = typeof(NaivePlugInProvider);
    }

    public NaivePlugInProviderData(string name) :
        this(name, "PlugIns")
    {
    }

    public NaivePlugInProviderData(string name, string plugInFolder) :
        base(name, typeof(NaivePlugInProvider))
    {
        this.PlugInFolder = plugInFolder;
    }

    [ConfigurationProperty(NaivePlugInProviderData.folderProperty_,
        IsRequired = true)]
    public string PlugInFolder
    {
        get
        {
            return (string)this[NaivePlugInProviderData.folderProperty_];
        }
        set { this[NaivePlugInProviderData.folderProperty_] = value; }
    }
}

public class NaivePlugInProviderAssembler :
    IAssembler<PlugInProvider, PlugInProviderData>
{
    public PlugInProvider Assemble(IBuilderContext context,
        PlugInProviderData objectConfiguration,
        IConfigurationSource configurationSource,
        ConfigurationReflectionCache reflectionCache)
    {
        NaivePlugInProviderData naiveConfiguration =
            objectConfiguration as NaivePlugInProviderData;
        Type plugInType = Type.GetType(naiveConfiguration.Name);
        string folder = naiveConfiguration.PlugInFolder;
        return new NaivePlugInProvider(plugInType, folder);
    }
}
The other interesting thing about NaivePlugInProvider is its lack of a default constructor. How does Enterprise Library create a new instance of NaivePlugInProvider when it has no default constructor? NaivePlugInProviderData is outfitted with an Assembler attribute. This attribute identifies a type that can create a NaivePlugInProvider instance from a NaivePlugInProviderData instance.
The NaivePlugInProviderAssembler class is also shown in Figure 8. An assembler class must implement IAssembler<TObject, TConfiguration>, which contains the single Assemble method. It uses the supplied configuration data to pull out the relevant information and create a new NaivePlugInProvider instance.
At this point, Plug-In Loader contains a fully functional, albeit simplistic, implementation and is ready for use. You can now go and create more providers to create different plug-in discovery behaviors for the application block. An obvious extension is a provider that follows secure practices for discovering and loading plug-ins. Another possible extension is one that pulls plug-ins from blobs in a SQL Server table, possibly creating an optional dependency on the data access application block.
If you do not mind having to write the entire configuration in XML by hand, you can stop right there and ship your application block. Otherwise, you can create a design-time component that takes care of that work for you.

 

Design-Time Behavior
The design-time component plugs into the Enterprise Library configuration application. This is an extensible Windows Forms application that allows you to edit application configuration files using a rich UI, instead of having to write raw XML. The component has three responsibilities: it must provide behavior for the application UI itself, it must allow the application to serialize the user’s settings, and it must be able to deserialize configuration data when the user opens an existing application configuration file.
Since application configuration files are based on XML, they are hierarchical in nature. The configuration application models this as a tree consisting of nodes. Each configuration element in the run-time component must be represented by a design-time node class, which provides additional design-time behavior to the configuration class. Figure 9 illustrates this relationship for the Plug-In Loader application block.

Figure 9 Mapping Run-Time and Design-Time Classes
To integrate with the Enterprise Library configuration application, the design-time component must register with it. The assembly and its dependencies must be placed in the same directory as the configuration application itself, and it must be marked with the ConfigurationDesignManager attribute, like this:

[assembly: ConfigurationDesignManager(
    typeof(PlugInLoaderConfigurationDesignManager))]
This assembly-level attribute registers the PlugInLoaderConfigurationDesignManager with the configuration application. This class derives from the abstract ConfigurationDesignManager class.
Defining the design-time behavior involves specifying which actions are possible in which contexts. This is done by overriding ConfigurationDesignManager’s Register method:

public override void Register(IServiceProvider serviceProvider)
{
    PlugInLoaderCommandRegistrar cmdRegistrar =
        new PlugInLoaderCommandRegistrar(serviceProvider);
    cmdRegistrar.Register();

    // Node map code goes here...
}
PlugInLoaderCommandRegistrar derives from the abstract CommandRegistrar class; its purpose is to register design-time actions with the configuration application. The first action I need to implement is the command to add the application block to an application configuration file. When the Plug-In Loader application block is added to an application configuration, a PlugInLoaderSettingsNode, along with its child Plu gInProviderCollectionNode, must be added to the hierarchy.
First, these node classes must be defined, as such:

public class PlugInLoaderSettingsNode : ConfigurationNode
{
    public PlugInLoaderSettingsNode() :
        base("Plug-In Loader Application Block") {}

    [ReadOnly(true)]
    public override string Name
    {
        get { return base.Name; } set { base.Name = value; }
    }
}
PlugInProviderCollectionNode is almost identical, since PlugInLoaderSettingsNode does not contain other properties than the collection of PlugInProviders. While you may think I could have used a common class for both nodes, this is not the case. Both nodes occupy different places in the hierarchy, and I am going to attach different actions to each of them. If you wonder why I have overridden the Name property, it is merely so I can mark it with the Read-only attribute. This will make the nodes read-only in the configuration application.
When the command to add the Plug-In Loader application block to an application configuration file is invoked by the user, these two nodes must be added to the hierarchy. To accomplish this, I create the AddPlugInLoaderSettingsNodeCommand class, shown in Figure 10. It derives from AddChildNodeCommand and overrides the ExecuteCore method to implement the desired logic. The command class must be associated with a node class so that the base class knows that it should create an instance of PlugInLoaderSettingsNode and add it to the hierarchy. This is already done after the call to the base implementation of ExecuteCore, so all I need to do is create a new PlugInProviderCollectionNode and add it to the settings node.
Figure 10 Adding Nodes to the Hierarchy
public class AddPlugInLoaderSettingsNodeCommand : AddChildNodeCommand
{
    public AddPlugInLoaderSettingsNodeCommand(
        IServiceProvider serviceProvider) : base(serviceProvider, 
            typeof(PlugInLoaderSettingsNode)) {}

    protected override void ExecuteCore(ConfigurationNode node)
    {
        base.ExecuteCore(node);

        PlugInLoaderSettingsNode settingsNode =
            this.ChildNode as PlugInLoaderSettingsNode;
        if (settingsNode == null) return;

        PlugInProviderCollectionNode providersNode =
            new PlugInProviderCollectionNode();
        settingsNode.AddNode(providersNode);
    }
}
The AddPlugInLoaderSettingsNodeCommand class defines what happens when the user invokes the command. But I still need to define when and where this command is available. It should only be available when the user has selected the application configuration root node, and it should only be possible to invoke the command once. I will accomplish this from the PlugInLoaderCommandRegistrar class by overriding the abstract Register method:

public override void Register()
{
    this.AddPlugInLoaderCommand();
    // Add other commands here...
}

The AddPlugInLoaderCommand method only contains three statements, as shown in the following:

private void AddPlugInLoaderCommand()
{
    ConfigurationUICommand cmd =
        ConfigurationUICommand.CreateSingleUICommand(
        this.ServiceProvider, "Plug-In Loader Application Block",
        "Add the Plug-In Loader Application Block",
        new AddPlugInLoaderSettingsNodeCommand(this.ServiceProvider),
        typeof(PlugInLoaderSettingsNode));
    this.AddUICommand(cmd, typeof(ConfigurationApplicationNode));
    this.AddDefaultCommands(typeof(PlugInLoaderSettingsNode));
}
By calling CreateSingleUICommand, I specify that this command can only be invoked once. In this method call, I also provide display texts and an instance of AddPlugInLoaderSettingsNodeCommand, which is invoked if the user selects to perform this action. With the call to AddUICommand I associate this command with the ConfigurationApplicatonNode type, which is the type of the application configuration root node. The AddDefaultCommands method adds default commands, such as Add and Delete, to the newly created PlugInLoaderSettingsNode.

 

Provider Nodes
PlugInProviderData must be enhanced by PlugInProviderNode, and NaivePlugInProviderData by NaivePlugInProviderNode, as illustrated in Figure 9. The abstract PlugInProviderNode in Figure 11 provides design-time functionality to PlugInProviderData. Several attributes from the System.ComponentModel namespace are in play here: Category, Editor, and ReadOnly. These provide the same functionality as in the Visual Studio® property grid.
Figure 11 Design-Time Support for PlugInProviderData
public abstract class PlugInProviderNode : ConfigurationNode
{
    private PlugInProviderData configurationData_;
    private Type plugInType_;

    protected PlugInProviderNode(PlugInProviderData configurationData) :
        base(configurationData == null ? null : configurationData.Name)
    {
        if (configurationData == null)
        {
            throw new ArgumentNullException("configurationData");
        }
        this.configurationData_ = configurationData;
    }

    [ReadOnly(true)]
    public override string Name
    {
        get { return base.Name; } set { base.Name = value; }
    }

    [BaseType(typeof(object), TypeSelectorIncludes.AbstractTypes |
        TypeSelectorIncludes.BaseType |
        TypeSelectorIncludes.Interfaces)]
    [Category("Plug-In Loader")]
    [Editor(typeof(TypeSelectorEditor), typeof(UITypeEditor))]
    public Type PlugInType
    {
        get { return this.plugInType_; }
        set
        {
            this.plugInType_ = value;
            if (value != null) this.Name = value.AssemblyQualifiedName;
        }
    }

    public abstract string Provider { get; }

    protected override void OnRenamed(
        ConfigurationNodeChangedEventArgs e)
    {
        base.OnRenamed(e);
        this.PlugInProviderData.Name = e.Node.Name;
    }

    internal PlugInProviderData PlugInProviderData
    {
        get { return this.configurationData_; }
    }
}

Figure 9 Mapping Run-Time and Design-Time Classes
PlugInProviderNode wraps a PlugInProviderData instance, which provides and stores all the configuration data except the plug-in type. Plug-In Loader uses a special naming convention in which the configured name of the PlugInProvider is the assembly-qualified name of the plug-in type. Since there’s no guarantee that the Name property can be resolved to a type, PlugInProviderNode stores the plug-in type separately in a member variable.
The PlugInType property also contains the BaseType attribute, which is used by the TypeSelectorEditor identified in the Editor attribute to filter the available types. When this editor shows available types, only those that are abstract, base types, or interfaces are listed. When selecting a plug-in type, these are the only types worth listing since you cannot base a plug-in on a sealed type.
Another notable feature of PlugInProviderNode is the read-only Provider property. I find it is always nice to be able to inspect which provider is configured, and this provides the most direct way of informing the user. Otherwise, this can be really difficult to tell when using the configuration application.
The last thing to note about PlugInProviderNode is that I use OnRenamed to keep the name of the node and the name of the underlying data synchronized.
A NaivePlugInProviderNode class extends PlugInProviderNode by providing the PlugInFolder property. Adding a NaivePlugInProvider command to the configuration application is a bit simpler than adding the application block itself, since this command does not need to create additional sub-nodes under a NaivePlugInProviderNode. So I don’t need to create a separate command class for this action. Instead I can just let PlugInLoaderCommandRegistrar handle all of the command registration:

this.AddMultipleChildNodeCommand(
    "Naive Plug-In Provider", "Add a new naive Plug-In Provider",
    typeof(NaivePlugInProviderNode),
    typeof(PlugInProviderCollectionNode));
this.AddDefaultCommands(typeof(NaivePlugInProviderNode));
By calling AddMultipleChildNodeCommand, I specify that this command can be invoked an arbitrary number of times to create new NaivePlugInProviderNodes as child nodes of a PlugInProviderCollectionNode. Other PlugInProvider node types can be added using a similar mechanism.

 

Serializing and Deserializing XML
When you work with the configuration application, settings are retained only in memory until you choose to save the changes. Once you save changes, the data configured in each node must be serialized to XML. Fortunately, Enterprise Library and System.Configuration take care of serializing configuration elements to and from XML. The only thing you need to do is specify how to map from nodes to configuration classes.
This is done by overriding PlugInLoaderConfigurationDesignManager’s GetConfigurationSectionInfo method. This implementation acquires the PlugInLoaderSettingsNode from the hierarchy, but delegates the real work to the PlugInLoaderSettingsBuilder class shown in Figure 12. This internal class creates a new, empty PlugInLoaderSettings instance and then uses the node hierarchy to add configuration data to that instance.
Figure 12 Preparing Settings for Serialization
internal class PlugInLoaderSettingsBuilder
{
    private PlugInLoaderSettingsNode settingsNode_;
    private IConfigurationUIHierarchy hierarchy_;

    internal PlugInLoaderSettingsBuilder(
        IServiceProvider serviceProvider,
        PlugInLoaderSettingsNode settingsNode)
    {
        this.settingsNode_ = settingsNode;
        this.hierarchy_ = 
            ServiceHelper.GetCurrentHierarchy(serviceProvider);
    }

    internal PlugInLoaderSettings Build()
    {
        PlugInLoaderSettings settings = new PlugInLoaderSettings();
        this.BuildPlugInProviders(settings);
        return settings;
    }

    private void BuildPlugInProviders(PlugInLoaderSettings settings)
    {
        PlugInProviderCollectionNode providersNode =
            this.hierarchy_.FindNodeByType(this.settingsNode_,
                typeof(PlugInProviderCollectionNode))
                    as PlugInProviderCollectionNode;

        if (providersNode != null)
        {
            foreach (PlugInProviderNode pipNode in providersNode.Nodes)
            {
                settings.PlugInProviders.Add(pipNode.PlugInProviderData);
            }
        }
    }
}
Since Plug-In Loader’s configuration hierarchy is so shallow, this only involves looping through all PlugInProviders and adding the configuration data from their nodes. If the hierarchy had been deeper, this operation would have entailed traversing the node hierarchy and building up the equivalent hierarchy of configuration classes.
Just as Enterprise Library and System.Configuration take care of serializing to XML, the framework also performs the work of deserializing from XML to configuration class instances. However, you must provide the code that maps from configuration classes to a hierarchy of nodes. The initial step is to override PlugInLoaderConfigurationDesignManager’s OpenCore method:

protected override void OpenCore(IServiceProvider serviceProvider,
    ConfigurationApplicationNode rootNode, ConfigurationSection section)
{
    if (section != null)
    {
        PlugInLoaderSettingsNodeBuilder builder =
            new PlugInLoaderSettingsNodeBuilder(serviceProvider,
                (PlugInLoaderSettings)section);
        rootNode.AddNode(builder.Build());
     }
}
Like when serializing to XML, here I delegate the real work to another class: PlugInLoaderSettingsNodeBuilder. This class derives from NodeBuilder, which provides some utility methods for mapping between configuration classes and nodes. The idea is that the root node and all collection nodes have associated node builder classes, so a node builder class only contains code to create its own type of node, and delegates the work of creating the rest of the node hierarchy to other node builder classes. This is also the case for PlugInLoaderSettingsNodeBuilder, which creates a new PlugInLoaderSettingsNode and delegates the job of creating the collection of PlugInProviders to another class. The code download for this article shows how this is done.
This code uses a NodeCreationService to create a new PlugInProviderNode from the configuration data, and adds this node to the collection node. To enable the NodeCreationService to perform the mapping, a node map must be registered by PlugInLoaderConfigurationDesignManager in the Register method.
In order to create and register the node map, I create the PlugInLoaderNodeMapRegistrar class (which derives from NodeMapRegistrar) and override its abstract Register method. Here, I simply create the map between the configuration data class and the corresponding node class by calling the inherited AddMultipleNodeMap method:

this.AddMultipleNodeMap("Naive Plug-In Provider",
    typeof(NaivePlugInProviderNode), typeof(NaivePlugInProviderData));
Using the mechanism of node builder classes, I can build a hierarchy of nodes to an almost arbitrary depth and complexity while keeping the implementation of each node builder relatively simple. This approach may seem overly intricate for the very simple example shown here, but it scales very well to more complex configuration schemes where it becomes far more useful.

 

Conclusion
Creating an Enterprise Library application block is not trivial, but for the right kind of project, it may prove to be more than worth the effort. The core of creating an Enterprise Library application block, or turning a normal configuration-driven library into an application block, lies in developing the run-time component. But the optional extras are equally vital. I have described the design-time component, but this is only the first step among several.
Packaging the application block in a Microsoft® Installer (MSI) package is not difficult to do, and makes the block much easier to download and install. You should also consider creating comprehensive documentation—not only API documentation generated from XML comments, but also a quick introduction, an architectural overview, guidance on how to get started, and so on.
Most developers learn by example, so a few good QuickStart sample applications will make adoption of the application block easier. Even if your audience is only internal to your organization, samples may very well be worth the investment.

 

Mark Seemann works for Microsoft Consulting Services in Copenhagen, Denmark, where he helps Microsoft customers and partners architect, design and develop enterprise-level applications. He thanks Tom Hollander for providing valuable help for this article. Mark can be reached via his blog at blogs.msdn.com/ploeh.
Advertisements
 
Leave a comment

Posted by on January 28, 2011 in C#

 

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

 
%d bloggers like this: