Loading plugins assemblies in .Net

(first post of the year)

This might seem like a quite complex thing to do, but it’s in fact very simple. Thank you .Net for being so well built.

Note : With .Net 3.5, there is a much more advanced method called Add-In. But it’s also much more complex. You should use it on long-term projects with some evolutions of the plugins API (and no possibility to change the plugins). I’ve used it for a project and that really made us lose a lot of time.

So here is the code for a simple plugins class loading system :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public List<Type> GetPlugins<T>( string folder ) {
	var files = Directory.GetFiles( folder, "*.dll" );
	var tList = new List<Type>();
	foreach ( string file in files ) {
		try {
			var assembly = Assembly.LoadFile( file );
			foreach ( Type type in assembly.GetTypes() ) {
				if ( !type.IsClass || type.IsNotPublic )
					continue;
				var interfaces = type.GetInterfaces();
				if ( ( (IList) interfaces ).Contains( typeof( T ) ) )
					tList.Add( type );
			}
		}
		catch ( Exception ex ) {
			Logger.LogException( ex );
		}
	}
	return tList;
}
 
public List<T> InstanciatePlugins<T>( List<Type> types, object[] args ) where T:class {
	var list = new List<T>();
 
	foreach ( var t in types )
		if ( ( t.GetInterfaces() as IList ).Contains( typeof( T ) ) )
			list.Add( Activator.CreateInstance( t, args ) as T );
 
	return list;
}

Our project can be organized like this :
Project.Project : The application that will load plugins
Project.Common.Plugins : The common types used by the core and the plugins
Project.Plugin.Test1 : One stupid test plugin

In Project.Common.Plugins, we will declare an interface :

1
2
3
4
5
6
namespace Project.Common.Plugins {
	public interface IPlugin {
		String Name { get; }
		void DoSomeStuff();
	}
}

In Project.Plugin.Test1, we will declare a class :

1
2
3
4
5
6
7
8
9
namespace Project.Plugin.Test1 {
	public class PluginTest1 {
		public String Name { get { return "PluginTest1"; } }
		public void DoSomeStuff() {
			for( int i = 0; i < 100; i++ )
				Console.WriteLine("I only count ({0})", i );
		}
	}
}

This assembly has to be generated in a “plugins” directory.

Then, in your project, you will just have to use the methods given in the beginning and do something like that :

1
2
3
4
5
6
var types = GetPlugins<IPlugin>( "plugins" );
var pluginInstances = InstanciatePlugins( types, null );
 
Console.WriteLine("Plugins are :");
foreach( var pi in pluginInstances )
	Console.WriteLine("* {0}", pi.Name );

If you’re worried that you’re stuck with these created objects, you should take a look on the AppDomain (I think I will talk about them pretty soon). This allows to load .Net assemblies and types and then unload them when ever you want. But as it can be easily adapted to some existing code, you should start without it and then add it when you feel your application could benefit from it.

Leave a Reply

Your email address will not be published. Required fields are marked *