The intent of this article is to highlight a method of declaratively assigning types to application domains so the assemblies implementing those types can be swapped out without the expense of tearing down the hosting process.
Occasionally we come across hosting applications where the components held within them have differing maintenance timelines but the host has to be available for the aggregate of its contained artifacts. A good example of this is a web server which deploys common functionality for dealing with HTTP transport, but also hosts a set of discrete end user functions in the form of virtual directories and URL extension mappers. By hosting concurrent applications in this manner, we can consolidate and coordinate resource usage and access control, which translates to smoother operations. ASP.NET facilitates this by hosting each virtual folder from IIS into a separate AppDomain. As applications targeting specific virtual folders require change, the overall hosting environment can prevail by unloading the variant AppDomain and shadow copying assemblies, releasing any O/S locks on them so they may be replaced with newer versions and reloaded into a new AppDomain in readiness for operation. Our goal is to produce a plug-in type system where identified types can be flagged as being volatile so their assemblies may be updated without terminating the hosting process.
Unity is Microsoft's IoC/DI container framework which sits on top of ObjectBuilder2. There are a number of these types of frameworks which share the common goals of constructing and indexing types within a global container and automatically wiring up or creating instances of dependent types as and when they are required. The Unity framework ships with some reasonable API and quick start documentation and can also be found as part of Enterprise Library 4.0. While useful out of the box, Unity can also be extended to influence construction behaviors within ObjectBuilder.
There are various introductory articles and Webcasts on Unity which can be found here and a useful set of articles on objectbuilder can be found here. I recommend reading those if you're new to IoC/DI or Unity.
Construction Strategies - modifying how Unity resolves types
Unity provides for a set of extension points which can hook additional operations at various points in type instantiation. The framework will then call out to any join point as a type is being constructed and carry out the actions implemented there. All construction strategies implement the interface IBuilderStrategy which isolates the various join points as abstract functions. Hooks can be added directly before object creation, directly afterward, just before teardown and just after. In our case, we're only interested in hooking into the framework before the intended creation time so we can carry out that operation ourselves and marshal the object into our own AppDomain. (Note: this puts a requirement on our dynamically created types that they should all implement MarshalByRefObject. Here is the outline of our custom builder strategy:
public class MyBuildup : IBuilderStrategy
{
public AppDomain AppDomain { set; get; }
public void PreBuildUp(IBuilderContext context)
{
Type targetType = BuildKey.GetType(context.BuildKey);
if (AppDomain != null)
{
context.Existing = AppDomain.CreateInstanceAndUnwrap(targetType.Assembly.FullName, targetType.ToString());
context.BuildComplete = true;
}
}
public void PostBuildUp(IBuilderContext context) {}
public void PreTearDown(IBuilderContext context) {}
public void PostTearDown(IBuilderContext context) {}
}
Dissecting this class gives us the following:
- Implements IBuilderStrategy which obligates us to have implementation for the supported join points within ObjectBuilder2.
- Holds the host AppDomain.
- Performs the object creation and targets the host AppDomain then marks the object as being complete.
Container Extensions
Container extensions give access to the underlying infrastructure of Unity namely ObjectBuilder2. This sub framework is responsible for building up objects and injecting common behavior at creation join points (in AOP parlance). Our container extension provides a natural point at which to manage our new application domain and redirect types managed by the container into it. The extension has access to the strategy and policy chains. We're not interested in policies for our purposes but need to hook into the pre-creation stage of the object so we can marshal objects into our AppDomain. To accomplish inter-AppDomain construction/hosting, all types utilizing this domain specific hosting technique must derive from MarshalByRefObject.
public class MyExtension : UnityContainerExtension, IDisposable
{
private MyBuildup strategy = new MyBuildup();
private bool disposed;
protected override void Initialize()
{
Context.Strategies.Add(strategy, UnityBuildStage.PreCreation);
}
/// <summary>
/// Build the new appdomain allowing
/// </summary>
/// <param name="domain"></param>
/// <param name="shadowFiles"></param>
public void CreateAppDomain(string domain, string shadowFiles)
{
AppDomainSetup appDomainSetup = new AppDomainSetup();
appDomainSetup.ApplicationBase = Environment.CurrentDirectory + @"\" + shadowFiles;
appDomainSetup.ShadowCopyFiles = "true";
strategy.AppDomain = AppDomain.CreateDomain(domain, null, appDomainSetup);
}
#region IDisposable Members
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose (bool disposing)
{
if (!disposed && disposing)
{
AppDomain.Unload(strategy.AppDomain);
disposed = true;
}
}
#endregion
}
The extension sub-classes the standard Unity extension class but adds an implementation of IDisposable. This is important as when we dispose the parent container, Unity will also dispose of all extensions which can be disposed, which gives us an ideal point at which to unload our AppDomain. The AppDomain itself is explicitly setup in the public method CreateAppDomain which takes care of the shadow file copy requirements. The custom builder strategy is added to the strategy chain at initialization and hooked into the the pre-creation object construction phase.
Extension Configuration
The authors of Unity have recognized that custom extensions may require their own operating context to provide custom arguments. In our example we need to be given an application domain name along with a relative folder to hold the shadow copy files. Any argument value can be stored in the Unity section of the application configuration file and deserialized through a custom configuration section handler. The source below describes our implementation:
public class MyExtensionConfigHandler : UnityContainerExtensionConfigurationElement
{
/// <summary>
/// Name of the non-default application domain
/// </summary>
[ConfigurationProperty("domain")]
public string Domain
{
get { return (string)this["domain"]; }
set { this["domain"] = value; }
}
/// <summary>
/// Relative location of replaceable assemblies
/// </summary>
[ConfigurationProperty("shadowFiles")]
public string ShadowFiles
{
get { return (string)this["shadowFiles"]; }
set { this["shadowFiles"] = value; }
}
/// <summary>
/// Assign the new domain name to the custom container
/// </summary>
/// <param name="container"></param>
public override void Configure(Microsoft.Practices.Unity.IUnityContainer container)
{
container.Configure<MyExtension>().CreateAppDomain(Domain, ShadowFiles);
}
}
From a simplicity standpoint, it would have been easier to reuse the container name as the AppDomain. Unfortunately, there doesn't seem to be an easy way to do this so we require an explicit domain name declaration.
Configuration File - what ends up in app.config?
We now wire-up our types inside the containers they are destined to reside in. The types themselves are deliberately simple and implement the BCL type MarshalByRefObject along with a simple interface (IMyInterface) containing a single method Speak. The application configuration file below shows our various type aliases which we qualify by name when exposed within the containers section. The names chosen are purely arbitrary, but local refers to an implementation in the default (local) container while the second implementation (remote) will live in a patchable extension assembly.
The dynamic container illustrates the more interesting aspect to this in that it declares an association with our custom extension and its configuration requirements (MyExtension and MyExtensionConfigHandler). So we end up with Unity two containers, the first holding a mapping between IMyInterface and implementation StdClass defined in AssemblyA, while the second container (customerContainer) maps the same interface to SatClass in AssemblyB and hosts in an AppDomain named CustomAppDomain. The configuration expects a shadow folder called Shadow relative to the execution file path. When we unload the customContainer, we should be able to update AssemblyB without terminating the hosting process.
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="unity"
type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection,
Microsoft.Practices.Unity.Configuration" />
</configSections>
<unity>
<typeAliases>
<typeAlias alias="IMyInterface"
type="NickField.AppDomainUnity.Common.IMyInterface, AppDomainUnity.Common" />
<typeAlias alias="AssemblyAObject"
type="NickField.AppDomainUnity.AssemblyA.StdClass, AppDomainUnity.AssemblyA" />
<typeAlias alias="AssemblyBObject"
type="NickField.AppDomainUnity.AssemblyB.SatClass, AppDomainUnity.AssemblyB" />
</typeAliases>
<containers>
<!-- Default Container -->
<container>
<types>
<type type="IMyInterface" mapTo="AssemblyAObject" name="local" />
</types>
</container>
<!-- Dynamic Container -->
<container name="customContainer">
<types>
<type type="IMyInterface" mapTo="AssemblyBObject" name="remote" />
</types>
<extensions>
<add type="NickField.AppDomainUnity.MyExtension, AppDomainUnity" />
</extensions>
<extensionConfig>
<add name="MyExtensionConfigHandler" type="NickField.AppDomainUnity.MyExtensionConfigHandler, AppDomainUnity"
domain="CustomAppDomain" shadowFiles="Shadow" />
</extensionConfig>
</container>
</containers>
</unity>
</configuration>
Executing within the environment
The remaining code is to implement a test harness and program entry point to prove our initial expectations. Again this kept deliberately simple to keep focus on the main points of the article.
class Program : TestBase
{
private readonly IUnityContainer stdContainer;
private IUnityContainer customContainer;
public Program()
{
stdContainer = new UnityContainer();
customContainer = new UnityContainer();
UnityConfigurationSection section;
section = (UnityConfigurationSection)ConfigurationManager.GetSection("unity");
section.Containers.Default.Configure(stdContainer);
section.Containers["customContainer"].Configure(customContainer);
}
public void Unload()
{
customContainer.Dispose();
}
public void Reload()
{
customContainer = new UnityContainer();
UnityConfigurationSection section;
section = (UnityConfigurationSection)ConfigurationManager.GetSection("unity");
section.Containers["customContainer"].Configure(customContainer);
}
public void WaitForUpdate()
{
Console.WriteLine("Container has been unloaded ... waiting for update");
Console.Read();
}
public void Spill()
{
Console.WriteLine(stdContainer.Resolve<IMyInterface>("local").Speak());
Console.WriteLine(customContainer.Resolve<IMyInterface>("remote").Speak());
}
static void Main(string[] args)
{
Program p = new Program();
p.Spill();
p.Unload();
p.WaitForUpdate();
p.Reload();
p.Spill();
}
}
Starting at the constructor, we create two new unity containers the first being our default and the second containing the custom extension implementation described earlier. Unity has built-in support for handling custom configuration sections and will ultimately consume the document fragment within the <unity> element divisions. We pass each parsed container configuration to individual instances of UnityContainer indexed by name. Note: the default container is anonymous. As a simple proof we call out to the two implementations of IMyInterface and invoke the Speak method on each.
What follows is a simple program wait which gives the opportunity to replace AssemblyB in the shadow folder with an updated version. The obvious thing to do here is to change the implementation of the Speak method and then rebuild the assembly and move it into the shadow folder. Once this is done the main program can be resumed and the new implementation will be invoked. Obviously a real implementation would have a less primitive technique to synchronize the updates with the reload and would probably make use of the FileWatcher class.
In summary we have extended Unity to register specific types into separate and named application domains. Since our setup for those domains indicates that we shadow copy assemblies, we can replace the original copy once the AppDomain is unloaded. This allows us to patch parts of a system without terminating that host process, but still use the Unity framework to manage type creation and dependency injection.