动态类加载






3.64/5 (6投票s)
使用配置文件中注册的部分结构进行动态类加载。
引言
本文档展示了如何操作和动态创建尚未在您的项目中引用的类中的对象。
背景
我需要创建一些 Windows 服务,并且不想在未来修改我当前的项目。我意识到最好的方法是只创建一个服务服务器,并将所有执行代码分成程序集。
Using the Code
关键点是,所有模块(执行程序集)都将包含一个继承自基类的类(在我们的示例中,我们将称之为 Module
)。 此时,我们希望在一个单独的线程中执行所有模块,因此我们的基类可以只包含一个名为 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");
}
}
为了更好地控制模块,我们将使用组合设计模式(在此类中的子管理和泛型列表),因此我们将创建一个类来封装所有模块。 这将被称为 CompositeModule
,并且将具有一些函数重写。
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
{
}
}
}
下一步是创建一个将被一些具体的应用程序使用的类。 此类将与 CompositeModule
协同工作,并用于引发事件。 此类仅用于一个简单的目的。 想象一下,如果我们需要将我们的 Windows 服务修改为控制台应用程序或其他类型的项目。 使用此类将使我们能够在任何地方使用简单的五行代码来运行我们的 ServiceServer
。下一步是创建一个用于处理配置文件的类,我们将在其中存储有关模块的信息。 例如,类型和它位于哪个程序集中,模块是启用还是禁用等。 以下代码将向我们展示如何创建这些类
[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;
}
}
}
当然,还需要对配置文件进行一些修改。
<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>
现在,我们将把所有这些类组合到我们的主类 ServiceServer
中,如前所述。 为了确保此类在我们的应用域中仅使用一次,我们将使用单例模式来访问该类。
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();
}
}
最后一步是将此服务器调用放入应用程序(Windows 服务)的主方法中,并为测试创建一个简单的模块。
这是服务启动方法
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();
}
这是命名空间 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)
{
}
}
}
当然,还需要一些额外的步骤。 例如,我们必须为事件声明一些委托。
关注点
为了对我们的服务器进行某种远程控制,最好使用 .NET Remoting,方法是在我们的解决方案中添加一个侦听器对象。