I use reflection to accomplish this (and other) tasks. Let me show you how it works.
The first thing to do is to define an interface that allows us to identify classes that perform initialization tasks:
public interface IConfigurationTask { void Configure(); }
Next, create one or more classes that implement this interface. These classes will be distributed throughout your projects, which is another way of saying that you can put them โwhere they belongโ.
public class RepositoryInitializer : IConfigurationTask { public void Configure() {
The last piece of the puzzle is to find classes that implement the IConfigurationTask interface, create an instance from them, and execute the Configure method. This is the purpose of the ConfigurationTaskRunner:
public static class ConfigurationTaskRunner { public static void Execute( params string[] assemblyNames ) { var assemblies = assemblyNames.Select( Assembly.Load ).Distinct().ToList(); Execute( assemblies ); } public static void Execute( IEnumerable<Assembly> assemblies ) { var tasks = new List<IConfigurationTask>(); assemblies.ForEach( a => tasks.AddRange( a.CreateInstances<IConfigurationTask>() ) ); tasks.ForEach( t => t.Configure() ); } }
The code here uses a custom extension to iterate over all the items in the list and perform an action for each item (ForEach method). I also use the reflection library to do the task of finding and instantiating instances with a single layer (CreateInstances method), but you could achieve the same thing using simple reflection (as shown in the code below).
public static IList<T> CreateInstances<T>( this Assembly assembly ) { var query = from type in assembly.GetTypes().Where( t => typeof(T).IsAssignableFrom( t ) && typeof(T) != t ) where type.IsClass && ! type.IsAbstract && type.GetConstructor( Type.EmptyTypes ) != null select (T) Activator.CreateInstance( type ); return query.ToList(); }
The final piece of the puzzle is launching the execution of the ConfigurationTaskRunner. For example, in a web application, this will be in Application_Start in Global.asax:
// pass in the names of the assemblies we want to scan, hardcoded here as an example ConfigurationTaskRunner.Execute( "Foo.dll", "Foo.Domain.dll" );
I also found it useful with the IPrioritizedConfigurationTask derivative (which adds the Priority property) to ensure that tasks are ordered correctly before they are executed. This is not shown in the above code example, but it is pretty trivial to add.
Hope this helps!