Click here to Skip to main content
6,595,444 members and growing! (20,377 online)
Email Password   helpLost your password?
Languages » C# » Reflection     Advanced License: The Code Project Open License (CPOL)

Dynamic class loading

By MarkusDoof

Dynamic class loading using a registered section structure in the config file.
C# 2.0, C# 3.0.NET 2.0, .NET 3.0, .NET 3.5, WinForms, Dev
Posted:29 Nov 2008
Updated:12 Dec 2008
Views:8,636
Bookmarked:38 times
Announcements
Loading...
 
Search    
Advanced Search
Add to IE Search
printPrint   add Share
      Discuss Discuss   Broken Article?Report  
5 votes for this article.
Popularity: 2.53 Rating: 3.62 out of 5
1 vote, 20.0%
1

2
1 vote, 20.0%
3
1 vote, 20.0%
4
2 votes, 40.0%
5

Introduction

This article shows how to manipulate and dynamically create objects from classes that have not been referenced in your project yet.

Background

I had to create some Windows services, and I didn't want to modify my current project in the future. I realized that the best way was to create only a service server and divide all executing code into assemblies.

Using the Code

The main point is that all modules (executing assemblies) will contain a class which will be inherited from the base class (in our example, we will call it Module). At this point, we want to execute all the modules in one single thread, so our base class can contain only one method called Run.

public abstract class Module : 
{
    public RegisteredModule ModuleConfiguration
    {
        get;
        set;
    }
    public List<Module> Children
    {
        get;
        set;
    }
    protected bool isRunning;
    public virtual bool IsRunning
    {
        get 
        { 
            return isRunning; 
        }
        set 
        {
            bool prevStatus = this.isRunning;
            this.isRunning = value;
            if (prevStatus != this.isRunning)
            {
                if (ModuleStatusChanged != null)
                    ModuleStatusChanged(this);
            }
        }
    }
    public abstract string Name
    {
        get;
        set;
    }
    public event StatusChanged ModuleStatusChanged;
    public virtual void AddChild(Module module)
    {
        throw new Exception("Cannot add to leaf!");
    }
    public virtual void RemoveChild(Module module)
    {
        throw new Exception("Cannot remove from leaf!");
    }
    
    protected abstract void Run();
    
    public override string ToString()
    {
        return string.Format("{0} - {1}", this.Name, 
          this.IsRunning ? "Running" : "Stopped");
    }
}

For better modules control, we will use the Composite design pattern (child management in this class, and generic lists), so we will create a class for encapsulating all our modules. This will be called CompositeModule, and will have some function overriding.

public class CompositeModule : Module
{
    public CompositeModule()
    {
        this.Children = new List<Module>();
    }
    public Module this[string name]
    {
        get
        {
            Module foundModule = this.Children.Find(delegate(Module m)
                                 { return m.Name == name; });
            return foundModule;
        }
    }
    public override bool IsRunning
    {
        get
        {
            bool running = false;
            foreach (Module module in this.Children)
            {
                if (module.IsRunning)
                    running = true;
            }
            return running;
        }
    }
    public override void AddChild(Module Module)
    {
        this.Children.Add(Module);
    }
    public override void RemoveChild(Module Module)
    {
        this.Children.Remove(Module);
    }
    protected override void Run()
    {
   foreach (Module module in this.Children)
        {     module.Run();
        }
    }
    public override string Name
    {
        get
        {
            return "Composite module";
        }
        set
        {
        }
    }
}

The next step is to create a class that will be used by some concrete application. This class will work with CompositeModule, and will be for raising events. This class is here for a simple purpose. Imagine a situation when we will have to modify our Windows service into a console app or some other type of project. Using this class will enable us to use a simple five line code to run our ServiceServer wherever we want. The next step is to create a class for working with the config file where we will store information about our modules. E.g., what type and in which assembly is it located, if a module is enabled or disabled etc. The following code will show us how to create these classes:

[Serializable]
public class RegisteredModulesSection : ConfigurationSection
{
    [ConfigurationProperty("modules")]
    public RegisteredModules RegisteredModules
    {
        get
        {
            return (RegisteredModules)this["modules"];
        }
        set 
        {
            this["modules"] = value;
        }
    }
}
[ConfigurationCollection(typeof(RegisteredModules), AddItemName = "registeredModule")]
public class RegisteredModules : ConfigurationElementCollection
{
    protected override ConfigurationElement CreateNewElement()
    {
        return new RegisteredModule();
    }
    protected override object GetElementKey(ConfigurationElement element)
    {
        return ((RegisteredModule)element).Id;
    }
    public RegisteredModule this[int index] 
    {
        get { return (RegisteredModule)base.BaseGet(index); }
    }
    public void Add(RegisteredModule module)
    {
        BaseAdd(module);
    }
}

public class RegisteredModule : ConfigurationElement
{
    [ConfigurationProperty("id", IsRequired = true)]
    public string Id
    {
        get
        {
            return (string)this["id"];
        }
        set
        {
            this["id"] = value;
        }
    }
    [ConfigurationProperty("name",IsRequired=true, DefaultValue="Module")]
    public string Name
    {
        get 
        {
            return (string)this["name"];
        }
        set 
        {
            this["name"] = value;
        }
    }
    [ConfigurationProperty("type", IsRequired = true)]
    public string ClassType
    {
        get
        {
            return (string)this["type"];
        }
        set
        {
            this["type"] = value;
        }
    }
    [ConfigurationProperty("assemblyName", IsRequired = true,
        DefaultValue = "Module.dll")]
    public string AssemblyName
    {
        get
        {
            return (string)this["assemblyName"];
        }
        set
        {
            this["assemblyName"] = value;
        }
    }
    [ConfigurationProperty("moduleProperties", IsRequired = true)]
    public ModuleProperties ModProperties
    {
        get 
        {
            return (ModuleProperties)this["moduleProperties"];
        }
        set 
        {
            this["moduleProperties"] = value;
        }
    }
}
public class ModuleProperties : ConfigurationElement
{
    
    [ConfigurationProperty("enabled", IsRequired = true, DefaultValue = true)]
    public bool Enabled
    {
        get
        {
            return (bool)this["enabled"];
        }
        set
        {
            this["enabled"] = value;
        }
    }
    [ConfigurationProperty("description")]
    public string Description
    {
        get
        {
            return (string)this["description"];
        }
        set
        {
            this["description"] = value;
        }
    }
}

And of course, do some modifications to the config file.

<configuration>
  <configSections>
    <section name="registeredModules" 
          type="Service.Configuration.RegisteredModulesSection, 
               Service.Configuration, Version=1.0.0.0, 
               Culture=neutral, PublicKeyToken=null" />   
  </configSections>

  <registeredModules>
    <modules>
      <registeredModule id="1" name="SimpleModule1" 
             assemblyName="SimpleModule" type="SimpleModule.SimpleModule">

        <moduleProperties enabled="true" description="SimpleModule" />
      </registeredModule>
    </modules>
  </registeredModules>

</configuration>

Now, we will combine all these classes together in our main class called ServiceServer, as we mentioned before. To ensure this class will be used only once in our appdomain, we will use the Singleton pattern for accessing the class.

public class ServiceServer
{
   #region Singleton
    private static ServiceServer instance;
    public static ServiceServer Instance
    {
        get 
        {
            if (instance == null)
                instance = new ServiceServer();
            return instance;
        }
    }
    #endregion
     public event ModuleChangedEvent ModuleChanged;
 private CompositeModule rootModule;
    public CompositeModule RootModule
    {
           get { return rootModule; }
     set { rootModule = value; }
    }
    private ServiceServer()
    {
        rootModule = new CompositeModule();
    }
    public void LoadEnabledModules(RegisteredModules modules)
    {
        foreach (RegisteredModule regModule in modules)
        {
            if (regModule.ModProperties.Enabled)
            {
                RegisterModule(regModule);
            }
        }
    }
    private void RegisterModule(RegisteredModule regModule)
    {
        try
        {
            Assembly moduleAssembly = Assembly.Load(regModule.AssemblyName);
            Type moduleType = moduleAssembly.GetType(regModule.ClassType);
            if (moduleType != null)
            {
                Module module = 
                  (Module)moduleAssembly.CreateInstance(regModule.ClassType);
                module.Name = regModule.Name;
                module.ModuleStatusChanged += 
                  new StatusChanged(module_ModuleStatusChanged);
                this.rootModule.Children.Add(module);
            }
        }
        catch
        {
        }
    }
    public void module_ModuleStatusChanged(Module sender)
    {
        if (this.ModuleChanged != null)
        {
            this.ModuleChanged(string.Format("{0}{1}", 
                               sender.Name, sender.IsRunning));
        }
    }
    public void ExecuteServer()
    {
        this.rootModule.Run();
    }
}

And, the final step is to put this server calling into the main method of the application (Windows service) and create some simple module for testing.

Here is the service starting method:

protected override void OnStart(string[] args)
{
    RegisteredModulesSection modules = null;
    try
{
      modules = ConfigurationManager.GetSection("registeredModules") 
                as RegisteredModulesSection;
}
catch
{
}
if (modules != null)
{
     ServiceServer.Instance.LoadEnabledModules(modules.RegisteredModules);
     ServiceServer.Instance.ExecuteServer();
}

Here is a simple module in the namespace SimpleModule:

public class SimpleModule : Module
{
    private int counter = 0;
    public override string Name
    {
        get;
        set;
    }
    protected override void Run()
    {
        counter++;
        try
        {
            Log(string.Format("{0} run method fired {1}x", 
                this.Name, counter));
        }
        catch (Exception ex)
        {
        }
    }
}

Of course, some additional steps are needed. E.g., we must declare some delegates for the events too.

Points of interest

For some kind of remote control of our server, it would be good to use .NET Remoting by adding a listener object to our solution.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

MarkusDoof


Member

Occupation: Software Developer
Location: Czech Republic Czech Republic

Other popular C# articles:

Article Top
You must Sign In to use this message board.
FAQ FAQ 
 
Noise Tolerance  Layout  Per page   
 Msgs 1 to 9 of 9 (Total in Forum: 9) (Refresh)FirstPrevNext
GeneralMy vote of 1 PinmemberdvptUml15:40 30 Apr '09  
RantErrors in code Pinmemberhidden_400316:37 10 Mar '09  
GeneralRe: Errors in code PinmemberMarkusDoof10:26 11 Mar '09  
GeneralRe: Errors in code Pinmemberhidden_400316:36 11 Mar '09  
GeneralOvercategorization PinmemberDmitri Nesteruk13:16 29 Nov '08  
GeneralRe: Overcategorization PinmemberMarkusDoof22:03 29 Nov '08  
GeneralThoughts PinmemberPIEBALDconsult12:00 29 Nov '08  
GeneralRe: Thoughts PinmemberMarkusDoof22:14 29 Nov '08  
GeneralRe: Thoughts PinmemberPIEBALDconsult7:01 30 Nov '08  

General General    News News    Question Question    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

PermaLink | Privacy | Terms of Use
Last Updated: 12 Dec 2008
Editor: Sean Ewington
Copyright 2008 by MarkusDoof
Everything else Copyright © CodeProject, 1999-2009
Web09 | Advertise on the Code Project