Using C# Reflection for a simple Plugin System
.NET Reflection is a very, very powerful tool. One thing I used it for is making a basic plugin system for my web server.
I needed some sort of way to know what assemblies to load, so I used a configuration file with a simple format to parse. For each module I wanted to load, I put a line that was the path to the DLL, preceded by the word “mod”
mod C:\modules\module.dll
I figured that me or someone else might write a plugin that used another external library, so I used “dep” to mark DLL’s that were needed for the modules.
dep C:\dependencies\dependency.dll
When the server starts up, it reads through this configuration file, takes all the lines that start with mod or dep and assigns puts their path in module and dependency ArrayList’s respectively. Once the config file is read, it goes through and loads the dependencies first.
Assembly.LoadFile(DependencyPath);
Loading the modules is a little more complicated. For this I created a simple Module class to hold some information I would need about it, and be able to give me access to the Assembly object that loading the dll returns. In this case, when I load the module, I want to go into a class called ModuleMap that contains the url mapping info that I require for the plugin, and call its GetUrlMap method.
public class Module
{
public string ModulePath;
public string ModuleNamespace;
public Assembly ModuleAssembly;
public List<UrlMapItem> UrlMap;
public Module(string modulepath)
{
ModulePath = modulepath;
}
public void Load()
{
if (!File.Exists(ModulePath))
{
throw new NoSuchModuleException("Error: No such module at '" + ModulePath + "'.");
}
ModuleAssembly = Assembly.LoadFile(ModulePath);
int lastslash = ModulePath.LastIndexOf(@"\");
string assemblynamespace = ModulePath.Substring(lastslash+1, ModulePath.LastIndexOf('.') - lastslash-1);
ModuleNamespace = assemblynamespace;
Type t = ModuleAssembly.GetType(assemblynamespace+".ModuleMap");
if (t != null)
{
MethodInfo m = t.GetMethod("GetUrlMap");
if (m != null)
{
UrlMap = (List<UrlMapItem>)m.Invoke(null, (new object[]{}));
}
else
{
throw new InvalidModuleMapException("Error: The ModuleMap class is incorrect!");
}
}
else
{
throw new InvalidModuleMapException("Error: The ModuleMap class is missing!");
}
}
}
When I use the Invoke() method, I passed it two arguments. In this case, since it is a static method, the first argument is null. The second argument is an object array that contains the arguments for the invoked method. The Invoke() method returns an instance of an object class, so I casted it into the object type I needed, which in this case was a List.
Now, that UrlMapClass, that I now have a list of, contains two pieces of information:
- A url
- A method with the full namespace/class path (ex. “testnamespace.testclass.methodname”)
Then I took a Hashtable and used the namespace.class.method path as the key, and the Module.ModuleAssembly as the value object. This way when I go to call it, I can enter the path that I know is being called, take the Assembly object, and do what I did before and call MethodInfo.Invoke().
Assembly assembly = (Assembly)ModuleList[methodnamespace];
Type t = assembly.GetType(method.Substring(0, method.LastIndexOf('.')));
MethodInfo m = t.GetMethod(method.Substring(method.LastIndexOf('.') + 1));
Page p = (Page)m.Invoke(null, (new object[] { rq }));
The method variable contained the whole method.class.methodname path and the methodnamespace variable contained just the namespace. To get the class for GetType() I used a substring of the whole namespace.class.methodname string that was from 0 to the last . which would give you just namespace.class.
This is a pretty simple example. I structured my plugin DLL’s in such a way that it gave me all the info I needed to call the corresponding methods in a very simple manner. There are obviously much more complex ways to do this.