Click here to Skip to main content
Click here to Skip to main content
Go to top

Dynamic class loading

, 12 Dec 2008
Rate this:
Please Sign up or sign in to vote.
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.

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)

Share

About the Author

MarkusDoof
Software Developer
Czech Republic Czech Republic
No Biography provided

Comments and Discussions

 
GeneralMy vote of 4 Pinmembersoulprovidergr29-May-11 13:43 
GeneralMy vote of 1 PinmemberdvptUml30-Apr-09 14:40 
No explanation, only code without source.
RantErrors in code Pinmemberhidden_400310-Mar-09 15:37 
GeneralRe: Errors in code PinmemberMarkusDoof11-Mar-09 9:26 
GeneralRe: Errors in code Pinmemberhidden_400311-Mar-09 15:36 
GeneralOvercategorization PinmemberDmitri Nesteruk29-Nov-08 12:16 
GeneralRe: Overcategorization PinmemberMarkusDoof29-Nov-08 21:03 
GeneralThoughts PinmemberPIEBALDconsult29-Nov-08 11:00 
GeneralRe: Thoughts PinmemberMarkusDoof29-Nov-08 21:14 
GeneralRe: Thoughts PinmemberPIEBALDconsult30-Nov-08 6:01 

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

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

| Advertise | Privacy | Mobile
Web01 | 2.8.140916.1 | Last Updated 12 Dec 2008
Article Copyright 2008 by MarkusDoof
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid