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;<span style="color: blue">static void </span>Main(<span style="color: blue">string</span>[] args) { <span style="color: blue">while </span>(<span style="color: blue">true</span>) { <span style="color: #2b91af">Console</span>.WriteLine(); <span style="color: #2b91af">Console</span>.WriteLine(<span style="color: #a31515">"Options:"</span>); <span style="color: blue">if </span>(appDomain == <span style="color: blue">null</span>) { <span style="color: #2b91af">Console</span>.WriteLine(<span style="color: #a31515">" L - Load"</span>); } <span style="color: blue">else </span>{ <span style="color: #2b91af">Console</span>.WriteLine(<span style="color: #a31515">" U - Unload"</span>); } <span style="color: #2b91af">Console</span>.WriteLine(<span style="color: #a31515">" X - Exit"</span>); <span style="color: blue">switch </span>(<span style="color: #2b91af">Console</span>.ReadKey().KeyChar) { <span style="color: blue">case </span><span style="color: #a31515">'l'</span>: <span style="color: blue">case </span><span style="color: #a31515">'L'</span>: thread = <span style="color: blue">new </span><span style="color: #2b91af">Thread</span>(Load); thread.Name = <span style="color: #a31515">"Runner"</span>; thread.Start(args); <span style="color: blue">while </span>(appDomain == <span style="color: blue">null</span>) ; <span style="color: blue">break</span>; <span style="color: blue">case </span><span style="color: #a31515">'u'</span>: <span style="color: blue">case </span><span style="color: #a31515">'U'</span>: Unload(); <span style="color: blue">break</span>; <span style="color: blue">case </span><span style="color: #a31515">'x'</span>: <span style="color: blue">case </span><span style="color: #a31515">'X'</span>: Unload(); <span style="color: blue">return</span>; } } } <span style="color: blue">private static void </span>Load(<span style="color: blue">object </span>obj) { <span style="color: blue">string</span>[] args = obj <span style="color: blue">as string</span>[]; <span style="color: #2b91af">AppDomainSetup </span>appDomainSetup = <span style="color: blue">new </span><span style="color: #2b91af">AppDomainSetup</span>(); appDomainSetup.ApplicationBase = args[0]; appDomainSetup.PrivateBinPath = args[0]; appDomainSetup.ShadowCopyFiles = <span style="color: #a31515">"true"</span>; appDomain = <span style="color: #2b91af">AppDomain</span>.CreateDomain(<span style="color: #a31515">"Runner"</span>, <span style="color: #2b91af">AppDomain</span>.CurrentDomain.Evidence, appDomainSetup); appDomain.ExecuteAssemblyByName(<span style="color: #a31515">"ConsoleApplication"</span>, <span style="color: #2b91af">AppDomain</span>.CurrentDomain.Evidence, <span style="color: blue">new string</span>[0]); } <span style="color: blue">private static void </span>Unload() { <span style="color: #2b91af">AppDomain</span>.Unload(appDomain); appDomain = <span style="color: blue">null</span>; thread = <span style="color: blue">null</span>; }
}
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.