Replacing Loaded Assemblies

Recently I’ve been asked if it would be possible to replace the assemblies loaded by a .NET Windows Service application while it was running like with ASP.NET. Like with ASP.NET, an application start and end events where needed.

The solution is quite simple. The Windows Service application is just a loader that has no references to the loaded assemblies that might change have an assembly with an entry point that acts as the start event. This assembly must be loaded in a new AppDomain with ShadowCopyFiles set. The end event is handled by handling the DomainUnload event of the AppDomian where the running assembly is loaded.

If you want to have the running assembly and its referenced assemblies unloaded and reloaded whenever a change occurs in the assembly files, a FileSystemWatcher could be used, although I would prefer to override such behavior in ASP.NET, not copy it.

An assembly loader can be as simple as this:

class Program
{
    private static Thread thread = null;
    private static AppDomain appDomain = null;

    static void Main(string[] args)
    {
        while (true)
        {
            Console.WriteLine();
            Console.WriteLine("Options:");
            if (appDomain == null)
            {
                Console.WriteLine("    L - Load");
            }
            else
            {
                Console.WriteLine("    U - Unload");
            }
            Console.WriteLine("    X - Exit");

            switch (Console.ReadKey().KeyChar)
            {
                case 'l':
                case 'L':
                    thread = new Thread(Load);
                    thread.Name = "Runner";
                    thread.Start(args);
                    while (appDomain == null) ;
                    break;
                case 'u':
                case 'U':
                    Unload();
                    break;
                case 'x':
                case 'X':
                    Unload();
                    return;
            }
        }
    }

    private static void Load(object obj)
    {
        string[] args = obj as string[];

        AppDomainSetup appDomainSetup = new AppDomainSetup();
        appDomainSetup.ApplicationBase = args[0];
        appDomainSetup.PrivateBinPath = args[0];
        appDomainSetup.ShadowCopyFiles = "true";

        appDomain = AppDomain.CreateDomain("Runner", AppDomain.CurrentDomain.Evidence, appDomainSetup);

        appDomain.ExecuteAssemblyByName("ConsoleApplication", AppDomain.CurrentDomain.Evidence, new string[0]);
    }

    private static void Unload()
    {
        AppDomain.Unload(appDomain);
        appDomain = null;
        thread = null;
    }
}



The loader receives the path for the assembly to run and its name and loads it.




And the assembly to run as simple as this:



class Program
{
    static readonly DateTime dateTime = DateTime.Now;

    static void Main(string[] args)
    {
        AppDomain.CurrentDomain.DomainUnload += delegate
        {
            Console.WriteLine();
            Console.WriteLine("Unloading: {0}", dateTime);
        };

        System.Reflection.Assembly assembly = System.Reflection.Assembly.GetExecutingAssembly();

        Console.WriteLine("Loading: {0}", dateTime);
        Console.WriteLine("Assembly: {0}", assembly.FullName);
        Console.WriteLine("Location: {0}", assembly.Location);

        FileInfo fileInfo = new FileInfo(assembly.Location);
        Console.WriteLine("Location Dates: CreationTime={0}, LastWriteTime={1}, LastAccessTime={2}",
            fileInfo.CreationTime, fileInfo.LastWriteTime, fileInfo.LastAccessTime);

        while (true)
        {
            Console.Write('.');
            Thread.Sleep(100);
        }
    }
}



With this “system” you can replace the running assembly and load or unload it whenever you want to.