Pages

Thursday, December 5, 2013

How to change an assmebly in runtime

If you develop an application it often comes up "how to deliver patches?". In case of a client application it might be an easy way to check updates at start up like ClickOnce does or simply ask user to update and restart the client. But when it comes to a service it is a more difficult question. When can you stop the service? Can you stop it at all? When you stop it what is going to happen with the clients or with the arriving data? Of course there are some solutions like load balancing, hearth-beat systems, MAF, etc. The best would to replace the DLL, of course it is not possible, at least overwrite isn't. What you can do, put an other version of the file into the folder and let the application to discover it.

My tiny demo application writes "hello" on the console in every second. To make it modular and "replaceable" it is implemented in an other library, client accesses it's interface only. I use MEF to discover the actual implementations.

private DirectoryCatalog PluginCatalog { get; set; }

public Program()
{
   this.Compose(@".\debugger");
}

private void Compose(string path)
{
   this.PluginCatalog = new DirectoryCatalog(path);                           

   var catalog = new AggregateCatalog(
          new AssemblyCatalog(System.Reflection.Assembly.GetEntryAssembly()),
          new AssemblyCatalog(System.Reflection.Assembly.GetExecutingAssembly()),
          this.PluginCatalog);         
   

   var container = new CompositionContainer(catalog);
   container.ComposeParts(this);
}

public void Log(string msg)
{
}


static void Main(string[] args)
{
   var p = new Program();

   while (Console.KeyAvailable == false)
   {
      p.Log("HeLLo");
      Thread.Sleep(1000);
   }
}

It does (at least now) nothing, but creates a MEF container from assemblies in the ./debugger folder and calls the Log method in every second.
We need a logger interface, which has to be an other assembly, lets name it "Debugger.SDK". It only contains an interface (what a waste).

namespace MEF.DebuggerSDK
{
   using System.ComponentModel.Composition;

   [InheritedExport(typeof(IDebugger))]
   public interface IDebugger
   {
      void Log(string msg);
      int Version { get; }
   }
}

Don't forget to decorate with InheritedExport attribute, to allow MEF to find it. IDebugger interface exposes one method and one property. Log logs, but what is Version? We need to distinguish the implementations to know a newer version is available. Reference this DLL into the main app.

Now, we got an IDebugger, let's import it into our demo client.

[ImportMany(typeof(IDebugger), AllowRecomposition=true)]
private IEnumerable<IDebugger> Debugger { get; set; }

Like I said, we cannot overwrite or unload loaded DLL-s (unless they are loaded into a different app domain), so we need to prepare to might have more then one implementation of IDebugger. However we only need one at the time. Now we can implement the Log method. It checks whether any implementation of IDebugger is available and use one with the highest version number, otherwise use Console.WriteLine to show the message.

public void Log(string msg)
{
   if (this.Debugger.Any())
   {
      var debugger = this.Debugger.OrderByDescending(d => d.Version).First();
      debugger.Log(msg);
   }
   else
   {
      Console.WriteLine(msg);
   }
}

We are almost done. Somehow we need to detect new files into the debugger folder and recompose IDebugger import. To achieve, use FileSystemWatcher.

private void Watch()
{
   FileSystemWatcher watcher = new FileSystemWatcher(@".\debugger", "*.dll");
   watcher.Created += this.OnPluginFolderChanged;
   watcher.EnableRaisingEvents = true;                  
}

private void OnPluginFolderChanged(object sender, FileSystemEventArgs e)
{        
   this.PluginCatalog.Refresh();
   Console.WriteLine("Plugin folder changed, recompose...");
}

PluginCatalog was created once we composed the MEF container, all we have to do is refresh.

If you start the app, it's going to write "HeLLo" on the console until you press any key. To replace this logger style create a new project, include DebuggerSDK, implement IDebugger interface and copy the compiled file into the ./debugger folder. E.g:

namespace MEF.LowerDebugger
{
   using MEF.DebuggerSDK;

   public class LowerDebugger : IDebugger
   {
      public void Log(string msg)
      {
         Console.WriteLine("[lower] " + msg.ToLower());
      }

      public int Version
      {
         get
         {
            return 1;
         }
      }
   }
}

You don't need to export it extra, since interface has an InheritedExport attribute, which exports every inherited class under the type of the interface.

Runtime
Replace implementation in runtime

Please keep it mind, that this kind of implementation cannot be an official patch deliver method, since it has to load new DLL(s) which consume(s) more and more memory. It can be also security critical, due an inner functionality is simple replaceable.

Source code can be downloaded from here.