It's a nice deployment pattern to isolate "plug-in" code into library files in subfolders under the application folder and run these plug-ins in a separate
AppDomain. By loading libraries and the assemblies they contain into a separate AppDomain it's possible to apply a restrictive security policy them and it's possible to unload them.
The
Assembly Load,
LoadFile and
LoadFrom methods load libraries into one of the contexts in the AppDomain of the caller, and if this is the initial AppDomain of the Process then it cannot be unloaded until the Process terminates.
Organise your projects like this skeleton:
MyApp
MyApp.Plugin.Common
MyApp.Plugin.PluginOne
MyApp.Plugin.PluginTwo
The application and the plugin projects reference the common library, never each other. All of the plugins should implement an interface defined in the common library which defines their public contract. The plugin classes must be derived from
MarshalByRefObject to allow strongly-typed communication via Remoting between the AppDomains.
When the application is deployed, arrange the folders like this:
Application Folder
theapp.exe
MyApp.Plugin.Common.dll
Application Folder\Subfolder One
MyApp.Plugin.PluginOne.dll
MyApp.Plugin.Common.dll
Application Folder\Subfolder Two
MyApp.Plugin.PluginTwo.dll
MyApp.Plugin.Common.dll
The application can search subfolders at runtime to find plugin libraries. You might use a folder naming convention or place a special XML file of instructions beside the plugins to identify and describe them. When a plugin is identified it can be loaded, called and unloaded like this:
var folder1 = new FileInfo("Subfolder One");
var ads1 = new AppDomainSetup();
ads1.ApplicationBase = folder1.FullName;
var dom1 = AppDomain.CreateDomain("Domain-1", null, ads1);
string file1 = Path.Combine(folder1.FullName, "MyApp.Plugin.PluginOne.dll");
string class1 = "MyApp.Plugin.PluginOne.ClassName1";
var plug1 = (IPlugin)dom1.CreateInstanceFromAndUnwrap(file1, class1);
Console.WriteLine(plug1.SayHello());
AppDomain.Unload(dom1);
This code creates an AppDomain with its base folder set to the subfolder where the plugin was found. It then uses
CreateInstanceFromAndUnwrap passing the full path of the plugin's folder and the name of the class to instantiate in the AppDomain. The returned value (actually a proxy) is cast to the common interface and it can be called like a normal class method. The
AppDomainSetup class has many other properties that help configure the AppDomain, such as specifying a config file.
The applications and the plugin projects do not reference each other and at runtime they only communicate using an interface over Remoting between the AppDomains.
While writing the real application that uses the technique described above I accidentally used the CreateInstanceAndUnwrap method (without the From) and I received misleading "Assembly not found" errors. It took me hours of suffering before I realised my dyslexic mistake caused by the similar names. The MSDN documentation describes the subtle differences between the two methods.
The technique described in this article is the manual way of implementing plugin isolation via AppDomains and is suitable for simple scenarios. The
Managed Extensibility Framework (MEF) and the
Managed Add-In Framework (MAF) are worth exploring for more complex scenarios.