Saturday, July 5, 2014

Dynamic C# code compilation

The ability to compile C# source code at runtime is a very useful technique that seems to be overlooked by a lot of .NET developers. It's a surprisingly easy way to add "scripting" abilities to your apps. I decided it was worth investigating while writing the HashStrings application which has to perform a dozen slightly different maths calculations and I wanted to be able to change them without the tedium of recompiling the project each time.

I have created a small example Visual Studio 2015 solution that shows how to dynamically compile a file of C# source code into an in-memory assembly, create and instance of a Type in that assembly and call one of its methods. The full project and source is available in this repository:

https://dev.azure.com/orthogonal/CsCompile

An interesting trick in the project is to define the symbol ORDINARY which causes the project and source to compile in the simple traditional way. Without the symbol dynamic compilation takes place.

The important part of the example code is worth extracting and displaying here.

var provider = new CSharpCodeProvider();
var parameters = new CompilerParameters();
parameters.GenerateInMemory = true;
parameters.ReferencedAssemblies.Add("System.Core.dll");
parameters.GenerateExecutable = false;
parameters.CompilerOptions = "/define:ORDINARY";
string codeToCompile = File.ReadAllText(@"..\..\SourceCode\Worker.cs");
var results = provider.CompileAssemblyFromSource(parameters, codeToCompile);
if (results.Errors.HasErrors || results.Errors.HasWarnings)
{
  // Display the results.Error collection
  return;
}
Type t = results.CompiledAssembly.GetType("cscompile.Worker");
dynamic worker = Activator.CreateInstance(t);
worker.SayHello();

Don't forget though that there are other ways of generating dynamic code. See the MSDN articles titled Using Reflection Emit and Using the CodeDOM. These techniques are much more difficult but they are strongly-typed and more robust than simply feeding free form text into a compiler.

Friday, July 4, 2014

AppDomains from libraries in subfolders

Early 2022 note: The AppDomain class has been basically deprecated after the introduction of .NET Core and later frameworks. This article is therefore only meaningful for the traditional .NET Framework. For more information see .NET Framework technologies unavailable on .NET Core and .NET 5+.


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.