Add Functionality at Runtime
The PluginFramework
actually loads assemblies at run-time (at first usage of Composable
). This means that you can add more assemblies during run-time. The assemblies does not have to be referenced in the project. As long as the types implement an Exported
interface the PluginFramework
will find the modules.
Adding a New Assembly
So, you could just have a FileSystemWatcher
and then load new assemblies when someone drops an assembly at a specific location.
Adding a new assembly at run-time is very easy.
Composable.LoadAssembly(module);
Composable.ReCompose();
First we add the assembly with LoadAssembly
, and then we tell the PluginFramework
to ReCompose
. This will find and add the new exported modules from the assembly added.
Using Roslyn
*Note: You do not need Roslyn to add functionality to the PluginFramework
at run-time.*
Roslyn
(the .NET Compiler Platform) makes it easy to compile at run-time. To use Roslyn
we need to install a nuget package. You will also need the XSockets.PLugin.Framework
package
Install-Package Microsoft.CodeAnalysis.CSharp
With Roslyn
installed we can now add a new module that implements the IAwesome
interface (used earlier). You can accomplish very complex things with Roslyn
, but in this sample we keep it simple.
1. Create a Runtime Module
We want to create a new module of the IAwesome export we declared earlier. If you do not remember this is what that interface looked like
using XSockets.Plugin.Framework.Attributes;
[Export(typeof(IAwesome))]
public interface IAwesome
{
void DoStuff();
}
So, let get started...
This first step creates compiles some source code into a MemoryStream
and if the compilation goes well it returns the MemoryStream
loaded into a new Assembly
public static Assembly CreateRuntimeModule()
{
using (var ms = new MemoryStream())
{
var compilation = Compile();
EmitResult result = compilation.Emit(ms);
if (!result.Success)
{
IEnumerable<Diagnostic> failures = result.Diagnostics.Where(diagnostic =>
diagnostic.IsWarningAsError ||
diagnostic.Severity == DiagnosticSeverity.Error);
foreach (Diagnostic diagnostic in failures)
{
Console.Error.WriteLine("{0}: {1}", diagnostic.Id, diagnostic.GetMessage());
}
return null;
}
else
{
ms.Seek(0, SeekOrigin.Begin);
return Assembly.Load(ms.ToArray());
}
}
}
2. Compilation Step
The Compile method returns a CSharpCompilation
. The compilation makes sure to load some code (the SyntaxTree
). It also makes sure that all the needed references are included. In this case we need references to assemblies/namespaces System
, IAwesome
and Export
.
public static CSharpCompilation Compile()
{
string assemblyName = Path.GetRandomFileName();
return CSharpCompilation.Create(
assemblyName,
syntaxTrees: new[] { GetSyntaxTree() },
references: GetReferences(),
options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
}
public static IList<MetadataReference> GetReferences()
{
return new MetadataReference[]
{
MetadataReference.CreateFromFile(typeof(object).Assembly.Location),
MetadataReference.CreateFromFile(typeof(IAwesome).Assembly.Location),
MetadataReference.CreateFromFile(typeof(Export).Assembly.Location),
};
}
3. SyntaxTree
This is the actual module implementation of IAwesome. We just parse a string into a Sharp SyntaxTree
public static SyntaxTree GetSyntaxTree()
{
return CSharpSyntaxTree.ParseText(@"
using System;
using XSockets.Plugin.Framework.Attributes;
using PluginDemo; // IAwesome was declared in here
public class AnotherAwesome : IAwesome
{
public void DoStuff()
{
Console.WriteLine(""Do stuff, I was added at runtime"");
}
}");
}
4. Test
To test this we can just:
- Output the
IAwesome
modules from thePluginFramework
- Add a new module at runtime by calling
CreateRuntimeModule
- Add the created assembly to the
PluginFramework
- Output the
IAwesome
modules from thePluginFramework
Console.WriteLine("Before compiling new module");
//
// 1
//
foreach (var awesome in Composable.GetExports<IAwesome>())
awesome.DoStuff();
//
// 2
//
var module = CreateRuntimeModule();
//
// 3
//
if(module != null) {
Composable.LoadAssembly(module);
Composable.ReCompose();
}
Console.WriteLine("After compiling new module");
//
// 4
//
foreach (var awesome in Composable.GetExports<IAwesome>())
awesome.DoStuff();
The output from this would be
Summary
Regardless of how you add new assemblies it is very easy to do it in runtime with the PluginFramework. We actually compiled stuff at runtime without Roslyn
in XSockets in version 1, but Roslyn
is a more convenient way of doing things.
Hopefully you see that it will be very easy to add XSockets modules such as Controllers
etc at runtime if you need to.