Click here to Skip to main content
15,885,757 members
Articles / Desktop Programming / Windows Forms
Article

An Abstract Factory Using app.config and Reflection

Rate me:
Please Sign up or sign in to vote.
4.33/5 (3 votes)
7 Aug 200610 min read 41.5K   239   38   2
An Abstract Factory implementing a Chain Pattern, loaded by reflection from the app.config.

Introduction

An Abstract Factory is a method that allows us to dynamically load a set of custom functionality that conforms to a well defined interface, usually at runtime. Why do we need another factory sample? I have two excellent articles for reference from the CodeProject (see the References section)! For most factories, it suffices to build all possible implementations and have the factory choose the best at runtime. However, sometimes this is not sufficient, and requires that the factory knows nothing, or very little, about what classes it might need. These include things that might be well defined ahead of time, but need to change on a per run basis (app.config), or are not known at all at compile time (different assembly and reflection). In this article, I discuss these together as well as the Chain Pattern, to make the example a little more realistic.

The app.config file

I believe that it is important, in this case, to look at what we want to try to do before we start on the task. Our goal is to create an app.config entry that we can use to inform our Abstract Factory what to load. And since we want to showcase the Chain Pattern too, we must also take that into consideration. Therefore, we have two criteria: define a section of the app.config file that tells us what to load, and in which order to load it.

XML
<section name="AbstractChainFactory" 
   type="AbstractFactory.FactorySectionHandler, AbstractChainFactory" />

This goes in our <configuration><configSections> section. It is probably the first few lines in your app.config, and allows us to meet our first criteria. <section> merely informs the settings parser that we expect this to lead to another section. name="AbstractChainFactory" is the actual name of the section. Sections are referred to by name, which allows you to define a single factory and have that factory select one of many named sections. type="AbstractFactory.FactorySectionHandler, AbstractChainFactory" this is the actual hard part. In order for C# to automatically load up our section, we need a System.Configuration.IConfigurationSectionHandler to do the lifting. The type attribute tells the parser which System.Configuration.IConfigurationSectionHandler handler to use when examining the section. type="" is actually the string System.Type needs to parse to get the class via reflection. Noticed how simple the string can be? If you have looked before at some of these, they can be very long and odd. This is the minimum you need to get going, but I intend to write another article on more of the options to this and how to use them.

XML
<AbstractChainFactory>
  <ChainClass type="AnotherAssembly.ChainClass1, AnotherAssembly" />
  <ChainClass type="AnotherAssembly.ChainClass2, AnotherAssembly" />
  <ChainClass type="AnotherAssembly.ChainClass3, AnotherAssembly" />
</AbstractChainFactory>

This goes in our basic <configuration> section, a sibling of <configSections>. What we notice here is that the name in our section definition is the same as this one. This is on purpose. As long as the names match, they could be anything you want. Looking inside, we see <ChainClass type="AnotherAssembly.ChainClass1, AnotherAssembly" />. This is where we start to make implementation decisions. I decided that since we want to load assemblies in order, a list was appropriate. And so, I named the XML nodes "ChainClass". This is purely a design decision, and so long as they have the same name in here and in our code, we can work with it. Also, notice how the type="" is of the same format? That's because we use System.Type.GetType to do our reflection so this should be no surprise. Again, System.Type allows for a lot of extra parameters in the type string. That is for another article. Now we have the basis of how our app.config should look; there are more examples in the test code, so let's move on.

A quick look at references

references screenshot

System.Configuration

In order to use reflection and the app.config file like I described, we need to add a DLL to our references list. System.Configuration.dll contains System.Configuration.ConfigurationManager (which is our first line of code) and must be added. Note how it is in the System.Configuration namespace? The System.Configuration namespace happens to be in a lot of assemblies, but we need this one. It can be a real headache to see using System.Configuration; but no ConfigurationManager in it.

AbstractFactory

Noticed how our demo project contains the reference AbstractFactory? This makes reasonable sense. Since our demo project will be using the AbstractFactory.AbstractChainFactory, we should see it here. Notice also that the project AnotherAssemblie contains a reference to AbstractFactory? This is also reasonable since the classes in AnotherAssemblie implement the AbstractFactory.IChainable interface. However, there is no reference to AnotherAssemblie anywhere. This means that when we compile the code, no one will be able to get at its classes. This is the point of this exercise. Even after compile time, we can make an assembly that implements AbstractFactory.IChainable, and we will be able to run it in our code without having to compile anything other than our new assembly. How do we get the assembly to where our program can see it, is the next question. In a development environment, this probably means an install package, but for us, adding the post-build event:

copy "$(TargetPath)" "$(SolutionDir)$(OutDir)"

to the anotherassemblie project suffices. A purist will note that this might not necessarily be what we want, but for most purposes, it is good enough.

post build event screenshot

A look at the code

When writing an article, one walks a thin line between an article that is trivial, and in order to be used needs more research, and an article that is overly complicated, usually requiring research into the minim functionality to meet user needs. This is why I decided that my factory will load a Chain Pattern. The Chain Pattern is simple to implement and understand. It merely takes in an object of a certain type, does some processing, and returns that same type to be reprocessed by another link in the chain. This was done so that we can look at some of the errors that arise in abstract Factories, not just a whole lot of parameters that need to be loaded.

IChainable

A simple interface defining a simple Chain Pattern. Chain Patterns can be much more complicated, but we are here to look at the Factory Pattern, not the Chain Pattern.

IChainedClass / ChainedClass

With all things in .NET, Microsoft gives us many ways to do many things. It is entirely possible to use reflection to dynamically load an object, of type Object, and invoke methods on that object using reflection. I strongly encourage you to do this only when it makes sense. What we have here is a factory that should produce an instance of a well defined interface (or abstract class). This forces the compiler to do all sorts of type checking and other goodness for us. I have seen factories return objects, and reflection used to determine what calls on those objects to make; it is not pretty. The IChainedClass is our well defined interface produced by the factory. ChainedClass is the implementation of the interface that knows how a chain is supposed to behave, and provides that functionality. Note: most abstract factory patterns return an abstract class, not an interface. For the Chain Pattern, this is a matter of preference since the Chain Pattern is usually simple and thus you don't need a lot of individual classes with common functionality factored into a base class. Just personal preference.

FactorySectionHandler

This is another one of those many ways / personal preference things. There is no functionality here at all! I prefer to keep all my functionality for dynamic reflection loading all in one place, the AbstractFactory.AbstractChainFactory, so that when something goes wrong, it is all in the same place. If you have several factories that have common functionality, you might strongly consider factoring out the common functionality here. At this point and with this example, there is no need to do so.

ChainClass1 / ChainClass2 / ChainClass3

These are here just to show that we do indeed have different loaded classes in a different assembly. And that they all work in the order we expect.

AbstractChainFactory

This is the real meat of the project. What we have here is a method overloaded.

C#
public static IChainedClass CreateChainedClasses()
public static IChainedClass CreateChainedClasses(string section name)

The reason we want to do this is because it is entirely possible that in our code, we will be calling the factory to produce the same object more than once, but with different parameters (different chain order). Remember, from above, that we can name our sections any name we want? That gives us the ability to have those multiple different objects:

C#
XmlNode node = (XmlNode)ConfigurationManager.GetSection(sectionname);

allowing us to have multiple configuration sections.

C#
if(node == null)
    throw new ConfigurationErrorsException("named section dose not exist");

It is entirely possible to have in code, a section that doesn't exist in our app.config. I have seen this when VS2005/VSS decides to overwrite your app.config with the latest one from source control that someone else, accidentally, checked in that contains their test environment. Also, copy-paste errors account for a lot here. Now onto architecture. Depending on your application's architecture or requirements, it may be acceptable to log this error and continue. Now, you probably need to return null immediately or you will get a null reference exception later.

C#
List<IChainable> list = new List<IChainable>();

This is in here only as part of the Chain Pattern. We do not know ahead of time how long our chain will be, so we need the System.Collections.Generic.List.

C#
Type t = Type.GetType(kid.Attributes["type"].Value); 
...
object o = t.Assembly.CreateInstance(t.FullName);
...
IChainable item = o as IChainable;

This represents the actual processing part of the code, and I have elected to go over it before delving back into architecture and acceptable errors. The string we put into our app.config is the minimum necessary for System.Type to dynamically load the assembly and then make our type. Noticed the t.Assembly.CreateInstance(t.FullName)? We are implicitly using the default parameter-less constructor. There can be issues if our object needs parameters to construct, but fortunately, we don't need it here, and I'm going to do another article, in part, on this. IChainable item = o as IChainable; is more or less equivalent to saying IChainable item = (IChainable)o; except that the latter will throw an error on an invalid cast, but the first will just return null. I think checking for null looks cleaner than catching the exception. Personal option.

C#
if (!kid.Name.Equals("ChainClass"))
    throw new ConfigurationErrorsException("named section is ill defined");
if(kid.Attributes["type"] == null)
    throw new ConfigurationErrorsException("named section is ill defined");
if (kid.Attributes.Count != 1)
    throw new ConfigurationErrorsException("named section is ill defined"); 
...
if(t == null)
    throw new ConfigurationErrorsException("defined class dose not exist"); 
...
if(o == null)
    throw new ConfigurationErrorsException("defined class dose not exist"); 
...
if(item == null)
    throw new ConfigurationErrorsException("defined class" + 
                          " dose not implement IChainable");

Yeah. Error checking. As before, all these errors are architecture dependent. What happens is, at the architecture level, one decides what errors are acceptable and what ones are recoverable. The second is what we focus on here. Every error here attempts to check for a common problem with the Chain Pattern or app.config. There are those that claim that we should use a SAX style approach to get all we can and ignore what we can't deal with. I usually agree, except in the case of object construction. The object is the fundamental building block of Object Orientated Design. If we can't trust that our objects are at least created as we intend, we are in for a world of hurt. I worked on a project that originally decided that it was acceptable to not load all of the chain if there was an error. This might seem reasonable except for the fact that a typo caused me to lose a lot of time and I was wondering why not all of my processing was being completed. All my objects were there. It was just that one of the chain links didn't load and the chain was very order dependent. So in my option, it might be acceptable to return a null if not all of our chain, or in a more general sense, a factory object is loaded, but it is probably a really bad idea to allow a partial construction.

Demo application

Sample screenshot

The demo application is nothing fancy. The five top buttons let you play with dynamically loading either a good chain with the default method, a good chain with the alternate method, or three bad loads stemming from common mistakes that are made in the app.config file.

References

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralAffordable flexibility Pin
parsiphal30-Jan-07 3:56
parsiphal30-Jan-07 3:56 
GeneralNice article Pin
L Hills15-Aug-06 1:42
L Hills15-Aug-06 1:42 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.