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

Dynamic class loading

Rate me:
Please Sign up or sign in to vote.
3.64/5 (6 votes)
12 Dec 2008CPOL2 min read 33.1K   43   10
Dynamic class loading using a registered section structure in the config file.

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.

C#
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.

C#
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:

C#
[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.

XML
<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.

C#
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:

C#
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:

C#
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)


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

Comments and Discussions

 
GeneralMy vote of 4 Pin
soulprovidergr29-May-11 13:43
soulprovidergr29-May-11 13:43 
GeneralMy vote of 1 Pin
dvptUml30-Apr-09 14:40
dvptUml30-Apr-09 14:40 
RantErrors in code Pin
hidden_400310-Mar-09 15:37
hidden_400310-Mar-09 15:37 
GeneralRe: Errors in code Pin
MarkusDoof11-Mar-09 9:26
MarkusDoof11-Mar-09 9:26 
GeneralRe: Errors in code Pin
hidden_400311-Mar-09 15:36
hidden_400311-Mar-09 15:36 
GeneralOvercategorization Pin
Dmitri Nеstеruk29-Nov-08 12:16
Dmitri Nеstеruk29-Nov-08 12:16 
GeneralRe: Overcategorization Pin
MarkusDoof29-Nov-08 21:03
MarkusDoof29-Nov-08 21:03 
GeneralThoughts Pin
PIEBALDconsult29-Nov-08 11:00
mvePIEBALDconsult29-Nov-08 11:00 
GeneralRe: Thoughts Pin
MarkusDoof29-Nov-08 21:14
MarkusDoof29-Nov-08 21:14 
GeneralRe: Thoughts Pin
PIEBALDconsult30-Nov-08 6:01
mvePIEBALDconsult30-Nov-08 6:01 

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.