Click here to Skip to main content
Email Password   helpLost your password?

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.

You must Sign In to use this message board.
 
 
Per page   
 FirstPrevNext
GeneralMy vote of 1
dvptUml
15:40 30 Apr '09  
No explanation, only code without source.
RantErrors in code
hidden_4003
16:37 10 Mar '09  
BTW code does not compile because of a logical error or a typo I'm not sure on that. The thing is that with Module having:
protected abstract void Run();
It is not possible to make:
public void ExecuteServer()
{
this.rootModule.Run();
}

Simply because the class itself is not derived from Module, therefore Run() is not available. Not to mention the compiler bitching about trying to access protected member from different instance in CompositeModule which also won't let you compile.

The solution is simple, make it look like this:
public abstract void Run();

Hopefully it saves someone a few hours of debugging.
Nice article.
GeneralRe: Errors in code
MarkusDoof
10:26 11 Mar '09  
Sorry my mistake, because i retyped it manualy (because of trimming informations not important for this article). Thanks for it.
GeneralRe: Errors in code
hidden_4003
16:36 11 Mar '09  
No worries.
It made me study the subject better in order to fix it Smile
GeneralOvercategorization
Dmitri Nesteruk
13:16 29 Nov '08  
C# (C# 1.0, C# 2.0, C# 3.0, C#), Windows (Windows, WinXP, Vista), Win Mobile (Win Mobile, WinMobile6, WinMobile5), .NET CF, .NET (.NET, .NET 3.5, .NET 3.0, .NET 1.1, .NET 2.0), Win32, Win64, SQL Server (SQL 2005, SQL Server), IIS (IIS 6, IIS 7, IIS), Visual Studio (VS2005, VS2008, Visual Studio), GDI, DirectX, ASP.NET, ADO.NET, WinForms, WebForms, Ajax, LINQ, Dev
Is there a technology your article does not cover? Smile
GeneralRe: Overcategorization
MarkusDoof
22:03 29 Nov '08  
Sooooo sorry Smile ) o didnt read well what does it mean. I"ll correct it Smile ) thanks for your message
GeneralThoughts
PIEBALDconsult
12:00 29 Nov '08  
I don't see where you load a class dynamically.


This
            catch
            {
}
is generally a very bad idea.


If you're writing a Windows Service, why not dynamically load classes that the Service will pass to System.ServiceProcess.ServiceBase.Run? (That's what I do.)
GeneralRe: Thoughts
MarkusDoof
22:14 29 Nov '08  
Why do you think assembly.CreateInstance is very bad idea? Give me some reason.
If i am writing service so i am using normal service project with installer. And thats the reason why i used this way to do it.
Anyway, thanks for the comment.
GeneralRe: Thoughts
PIEBALDconsult
7:01 30 Nov '08  
I don't, I use it, but I load classes that derive from System.ServiceProcess.ServiceBase


Last Updated 12 Dec 2008 | Advertise | Privacy | Terms of Use | Copyright © CodeProject, 1999-2010