in

Foo Theory

Partners in Community - serving up some ice cold Kool-Aid!
Welcome to footheory.com.  The bloggers and contributing members on this site are consultants, project/program managers and software architects working across the US.  Our community will focus on Microsoft technologies, .NET architecture, software patterns & practices and just plain stream of consciousness.

Daily Devscovery

Custom Application Block Development with Enterprise Library 3.0

I would like to start with my full respect to anyone who has been working on developing Custom Application Configuration block. No, its not the toughest thing to do in this world. But it is one which can make you pull out your hair and kick your system and do wired stuff. I have survived the process and I totally acknowledge those who have. Why ? lets start. And oh, by the way I want to mention that I was feeling happy/confidant/kick-ass awesome before I started to work on this block because I had already completed two custom application configuration blocks (which granted were simple - just key-value pairs) using the Ent. Lib. 3.0 Application Block Software Factory (ABSF) tool. I thought, "Oh yeah !!! This is my baby !!!"

Some facts about ABSF that I would like everyone to know. Please do read because if you don't you will face dire consequences on the path of creating a new custom application configuration block. ABSF is a software factory from the Patterns and Practices team to design custom application configuration blocks. Wow !!! that made everything so easy but from the fact that:

File Names

It creates file names so long that you will be scrolling forever to see the end. And I can almost guarantee that if you are a developer like me who wants everything neatly arranged in their folders and hence end up being like four levels deep when you start your designer project: buddy you are already in trouble. How about a taste of it ? How does the following name sound ?

"C:\Projects\MTFGP 2007 Pilot \POC\WebServiceProxy\Configuration\Application Block\Unit Tests\MTFGP.Web.Services.Proxy.Configuration.Configuration.Design.Tests\ bin\Debug\MTFGP.Web.Services.Proxy.Configuration.Configuration.Design.Tests.dll.CodeAnalysisLog.xml"

Tingly ? Well what's wrong with it !!! Its totally fine and it so totally describes what I want to build. But oh see its just 259 characters long which is just above the longest path any windows operating system will support. Hmm... that shouldn't be any problem because it should be me who should cut short the names of my namespaces to be like MTFGP.Web.Serv.Proxy.Config.Config.Design.Tests. Oh come on! what am I talking about... that was easy.

Project Structure

Oh I love structuring my projects. Structuring makes for great reusability and I am all for it. So lets look at the project structure adopted by the ABSF recipe.

1. I create an application settings project lets say with the name, "MyNamespace.MyModule.Configuration" in the directory "C:\Projects\MyModule" (I chose this directory for convenience and some other realistic implementation might very well be deeper)

Image

2. And then I enter the same name on the recipe form (shown below)

2.1. And walla !!! I get the message from the gods themselves !!!!

The detail says

Microsoft.Practices.RecipeFramework.RecipeExecutionException: An exception occurred during the binding of reference or execution of recipe CreateApplicationBlock. Error was: The following arguments are required and don't have values: DesignTimeProject, DesignTimeTestProject, DesignTimeResources. Can't continue execution..

You can remove the reference to this recipe through the Guidance Package Manager.

at Microsoft.Practices.RecipeFramework.Recipe.ThrowIfRequiredArgumentsAreNull(IDictionaryService arguments)

at Microsoft.Practices.RecipeFramework.Recipe.Execute(Boolean allowSuspend)

at Microsoft.Practices.RecipeFramework.GuidancePackage.Execute(String recipe, IAssetReference reference, IDictionary arguments)

at Microsoft.Practices.RecipeFramework.GuidancePackage.ExecuteFromTemplate(String recipe, IDictionary arguments)

at Microsoft.Practices.RecipeFramework.VisualStudio.Templates.UnfoldTemplate.ExecuteRecipe(Boolean executeActions)

at Microsoft.Practices.RecipeFramework.VisualStudio.Templates.UnfoldTemplate.RunFinished()

This exception is brought to me by the all powerful Visual Studio and I know I have screwed up.

3. So what do I do now. Well I create a directory called "1" on my root to create my solution. (Can't get smaller than that) and repeat the steps 1 and 2 and holy cow !!! the same thing !!!

4. Umm... I don't have any other option other than to shorten my project name because in that's the only variable in "$ProjectName$.Configuration.Design.Tests". Well ok, I am going to call it 2 Bob "MyModule". Ok lets try again. Woot !!! "MyModule" Worked. here is how the solution looks in Visual Studio and on disk.

Hmm... what I have is a structure which generally doesn't fit in how I started thinking my projects structure would be. If we look at the file structure then we notice that the solution is placed inside the "C:\1\MyModule" directory. There are "Application Block" and "Lib" folders to segregate the code and the output, but I won't be using it because I have my own bin into which I want this solution to build but this is fine. Inside the "Application Block" Folder we see the sub-folders "Design", "MyModule" and "Unit Tests". I am somehow not very comfortable with my Unit Tests being at the same level as the project since now I have been trying to follow the VSTS project structuring guidance and How To: Structure Your Source Control Folders In Team Foundation Server give a very clear and rational approach to arrange your projects. So according to the VSTS guidance I rearrange my project. It requires some manual changes to the .sln file and I would advise please learn how to do it before you try that. Now it looks as shown below.

I did away with the "Lib" folder too and since I will be building to my "bin" folder somewhere in the depths of my file-system. Ok so if you have a project that is simple enough you can start with this skeleton and add more stuff on to it. There are some videos available that show you how to make a simple application configuration block. I think they are a great place to start from because I did not know anything about developing configuration blocks when I started and these videos and the forums really helped me. Everyone tries to answer and help as much as possible and make Ent. Lib. development a truly community effort. Cheers to the P&P team !!!

A lot of files too fast !!!

Another thing that a n00b to the application configuration development would have to learn is to comprehend the fact that you would be looking at a lot of files and that too being created in a wink of the eye. When you work on the designer project you add different parts one by one using recipes. These recipes can create new files/restructure your project (add new partial classes)/Change existing files in the order of 5-15 files in a go all over your solution (multi-project changes). This is something I had no experience with when I started and I would hold my breath when I would see my solution doing all the gymnastics that it does when it goes through a recipe. You would have to get used to it. My suggestion: Try to get your hands wet on a few some many sample projects first and carefully look at what each recipe that you run does to your solution. This is so important because of two reasons.

1. You don't want to be looking all over the place when you make a change because you want to get to the real guts as soon as possible.

2. You want to know exactly what changed because you are going to do this in a real solution in some time.

3. There is no "Undo this recipe" !!! (Can I have a "I am really scared" smiley here ?)

References

Many who have worked on Ent. Lib. 3.0 know that it ships with two sets of binaries: Strongly named (in your Program Files directory) and unsigned (in your Ent. Lib. Source installation folder). When you run an recipe it creates a lot of files and a lot of references. All these references to Microsoft.Practices.* assemblies point to the Microsoft strongly named assemblies. So if you are have strongly named the Ent. Lib. assemblies using your own key then you have to take the trouble of removing these references and replacing them with references to your own strongly named binaries.

Complex Configuration Application Blocks

This time my challenge was a bit different. The configuration block that I had to develop had to depict the following hierarchy for an auditing piece.

Main Block Node =(can/cannot have)=(multiple)=> Entities (which is collection<Entity>)

Entity =(has)=> Entity Name

Entity =(can have)=(multiple)=> Properties (which is collection<Property>)

Property =(has)=> Property Name

Property =(can have)=(multiple)=> Mappings to other Systems (which is collection<Mapping to other system>)

Mapping to other system =(has)=> System (which is an Enumeration)

Mapping to other system =(has)=> Property Name in other system

This was a fairly complex application block and I didn't know what to do and where to start from at this time. I realized that there are many things that I need to take out of the general mix of all these files I get for free when I use the ABSF. I cut short a few, a few more and that way I came down to the bare minimum I needed. These usually were partial classes of ApplicationBlockSettings class and classes like AuditingProviderDataRetriever implementing IConfigurationNameMapper and types like CustomAuditingProviderData implementing some of my classes AuditingProviderData and IHelperAssistedCustomConfigurationData<CustomAuditingProviderData> which essentially brings in the ObjectBuilder block into picture. It also used attributes usage which looked like [Assembler(typeof(CustomProviderAssembler<IAuditingProvider, AuditingProviderData, CustomAuditingProviderData>))] into picture which is required for the ObjectBuilder to figure out what assembler is required to create a runtime instance of a type correctly. It was all ok till the time I was working with simple application blocks but with this kind of a project, using object builder when I had no need for any kind of object building policy looked like a lot of work for nothing. So I decided to work with the bare minimum.

I am not going to write the whole code here since I intend this document to be a guidance for someone who wants to understand the realities of working with the application block. The details what methods to implement in the classes listed below would be in the same spirit as done by the ABSF recipes so to get a better idea one would have to play with it for a while and then use a sample block as a reference on what all methods to implement and how to do that. So the config block I made ended up with the following components.

1. A data class for the root node:

Derives out of the SerializableConfigurationSection class.

Has methods like AuditMapConfigBlock GetConfigSettings(IConfigurationSource configurationSource) and AuditMapConfigBlock GetConfigSettings()

Can have members which hold data.

2. A node class for the root node:

Derives out of the ConfigurationNode class.

This class should not have any members that hold data. If this class has any data then it will have problems while saving settings to the config file for the second time (e.g. you press save, change something and again press save)

Make sure you override the Name property.

3. A data class for every node in the hierarchy,

Derives out of the NamedConfigurationElement class.

Can have data members.

4. A node class every node in the heirarchy.

Derives out of the ConfigurationNode class

Has a private members of the type of its data class.

Exposes the properties of the private member as its public properties.

Make sure you override the Name property.

Make sure you update/change the name property when one of the properties of the node (in my case say the Entity Name) changes. If this is not done then during save there will be multiple nodes with no names (string.empty) and that will cause problems.

5. A class that will take the settings from the config file (cast to the data class object for the root node) and will make a complete tree out of it. In my case it was called AuditMapConfigBlockNodeBuilder and derives from NodeBuilder

6. A class that will take a complete tree starting from the root node and convert it to an data object for that node that can be serialized to the config file. That is why the data class for the root node needs to derive out of the SerializableConfigurationSection class.

7. A class that will register for every node what kind of commands (mouse actions/keyboard actions) are available for every node. it derives out of the CommandRegistrar class.

8. A class that will be queried by the Designer tool and the framework for any of its needs related to your config block, weather it be making the class in step number 7 register the commands for the different nodes or making nodes out of settings in config file or the opposite. It will be called the DesignManager and it derives from ConfigurationDesignManager class.

There are some important points that I would like to list here that might save someone some time.

1. Make sure that all the custom properties of the data classes that you want to see being read and written from and to the config file are being saved to the property bag provided in the NamedConfigurationElement base class. Only the properties that have been put in the property bag will be written to the config file. e.g.

private const string m_EntityNameProperty = "EntityName";

[ConfigurationProperty(m_EntityNameProperty, IsRequired = true)]

public string EntityName

{

get { return (string)this[m_EntityNameProperty]; }

set { this[m_EntityNameProperty] = value; }

}

2. To make sure your application block gets loaded into the Ent. Lib. Config. tool place the assemblies for your blocks into the same directory as the config. tool.

3. In the design project DO NOT FORGET to mark the assembly with the following attribute. Without this attribute the Enterprise Library designer will fail to load your assembly when it starts up.

[assembly: ConfigurationDesignManager(typeof(<Your Design Manager class name here>))]

4. Icons for the nodes can only be bmp images that are 16 pixels by 16 pixels whose build action is set to "Embedded Resource". 16 and 256 colors work fine but I have not gone to true color. Use the following attributes on top of the classes to bind the images to them. The parameter in the attribute specifies the type defined in the assembly that contains the image as an embedded resource. (Here the name of my node class was AuditMapConfigBlockNode and the name of the corresponding bmp file was AuditMapConfigBlockNode.bmp.)

[Image(typeof(AuditMapConfigBlockNode))]

[SelectedImage(typeof(AuditMapConfigBlockNode))]

5. If you put the images in a solution folder then use the correct path. Another overload of the constructors of the above shown attributes allows specifying the name of embedded resource.

I know that all this is not enough to get a complex block going but please let me know of any questions and I will try my best to answer. Hope you have a great time learning how to develop custom application configuration blocks.

Comments

No Comments

Leave a Comment

(required)  
(optional)
(required)  
Add
Copyright ASIQS Corporation © 2006, All rights reserved.
Powered by Community Server (Commercial Edition), by Telligent Systems