Changing Code In A Running Application
posted on 09/03/09 at 11:53:32 pm by Joel Ross
As part of a new application I'm building, we wanted it to work together with an existing application already in place - in fact, we wanted it to share the bulk of the implementation for saving certain types of data. The main requirement we had was that it would always be up. It could live through upgrades of the main application, and, at the same time, recognize changes to the main application, and use that new functionality when the main app came back online.
We considered a few different approaches. One was a file based approach. We could drop the data in a file, and the main application would pick it up and process it. But we wanted an interactive process - when data comes in, our main application might have messages that needed to be sent back to the caller. At least part of the process needed to be synchronous. Web services was another option, but that didn't solve our "always up" requirement during upgrades.
Eventually, I had the idea of dynamically loading the parts of our application that we need. As long as we kept the interface from this new application into the main application simple and flexible, we could get away without having to change the new application (that often), and just load in new versions of the main application. So I set about proving out the concept. As it turns out, it's very do-able, once you piece the right things together.
One of the key pieces is to have a shared project that both the main application and the new application know about. It'll be where you put your contract that the two apps will agree on. I did it with an abstract class:
1: public abstract class BaseType : MarshalByRefObject
2: {
3: public abstract string WriteSomething();
4: }
Since this was just a dummy application, my naming is a bit unrealistic, but I'm sure we can get past that. Now, we inherit from that in our main application, and, well, write something:
1: public class DynamicLoadedType : BaseType
2: {
3: public override string WriteSomething()
4: {
5: return "Version 1 was called.";
6: }
7: }
Simple enough. Now let's look at the other side. First, a quick note about application domains in .NET. You can't load a DLL into the current App Domain and still be able to unload it. It doesn't work that way, and people a lot smarter than I am can explain it better than I could. The bottom line is that you have to load the DLL into its own App Domain, and rather than unloading just the DLL, you unload the whole App Domain. Also, since we'll be loading a type from another App Domain, we're essentially using remoting, which if you paid close attention, would be why our BaseType inherits from MarshalByRefObject.
So, we create an App Domain, load our DLL into it and grab the type we want:
1: var setup = new AppDomainSetup
2: {
3: ShadowCopyFiles = "true",
4: ApplicationBase = @"C:\Bins"
5: };
6: _domain = System.AppDomain.CreateDomain("MainAppDomain", null, setup);
7: _baseType = (BaseType)_domain
8: .CreateInstanceFromAndUnwrap(@"C:\Bins\AppDomain.Dynamic.dll",
9: "AppDomain.Dynamic.DynamicLoadedType");
There's a lot going on here, and some of it deserves some explanation. First, most samples you find ignore creating your own AppDomainSetup class. I created my own because I wanted to use Shadow Copy. I didn't want to have the DLL that I loaded be locked. Now, I can drop in a new DLL over the old one, have my application recognize that, and automatically reload it.
Once the type is loaded, calling a method on it is as simple as calling any other method:
1: Console.WriteLine(_baseType.WriteSomething());
Using a FileSystemWatcher, I can watch the DLL I loaded, and when the DLL is changed, I can unload the App Domain:
1: _baseType = null;
2: System.AppDomain.Unload(_domain);
3: _domain = null;
Then, I load a new App Domain with the new DLL, and I'm back to processing calls - the whole time able to respond to incoming requests, even if it is to tell the caller that I can't handle the request and to try again later.
To prove this all out, I created a simple application that loops every second and if the DLL is loaded, it calls WriteSomething(). If it's not loaded, it just says so.
1: while(true)
2: {
3: if(manager.IsLoaded)
4: {
5: Console.WriteLine(manager.BaseType.WriteSomething());
6: }
7: else
8: {
9: Console.WriteLine("No File Loaded!");
10: }
11: Thread.Sleep(1000);
12: }
Also, when the DLL is loaded or unloaded, a message is written as well.
I started the application, changed DynamicLoadedType.WriteSomething() to return "Version 2 was called.", and dropped the updated DLL into the folder. Here's what I saw:
Without ever stopping the application, I was able to make code changes, drop them in a running application, and see those changes immediately. Not bad for an evening's work!
Categories: Development, C#