Team XSockets.NET

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:

  1. Output the IAwesome modules from the PluginFramework
  2. Add a new module at runtime by calling CreateRuntimeModule
  3. Add the created assembly to the PluginFramework
  4. Output the IAwesome modules from the PluginFramework
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

Runtime Compilation

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.

results matching ""

    No results matching ""