Zero-Downtime Code Deployments with MEF

Adding production binaries to a .NET WebApp at runtime requires the application pool to restart. Unless you have the infrastructure to address this (ideally multiple servers and application instances setup for automatic fail-over, and a true Service Oriented Architecture with router services in front of your processing services), this means downtime for your users. For internal enterprise development, legacy applications, or proof of concept projects that outgrew their initial goals, not having the requisite infrastructure is a common scenario.

The approach described below quickly mitigates this issue by allowing your application to load some of its code-components during runtime using the Managed Extensiblity Framework. It relies on MEF for loading assemblies that share an interface at runtime, and RequestRouter object that routes the different requests to the appropriate implementation. However, it is no substitute for a more robust architecture – especially since MEF would be running these assemblies within the application domain of your main application (with access to all of its in-memory objects, and the possibility that bad component code could cause your entire application to crash).

Setting Up Extensible Components

None of this would work without using components which share an interface, for example:

Report interface for application driven reports:

interface IReportDefinition
    string ReportName; // unique identifier for this report definition
    ReportResponse ExecuteReport(ReportParameters paramaters);
    bool AcceptsExecutionRequest(ReportParameters parameters);

Integration message handler interface:

interface IMessageHandler
    string MessageName; // unique identifier for this type of message
    MessageResponse HandleMessage(Message msg);
    bool AcceptsExecutionRequest(ReportParameters parameters);

Calculation handler interface:

interface ICalculationHandler
    string CalculationName; // unique identifier for this type of calculation
    CalculationResult PerformCalculation(CalculationInputParameters paramaters);
    bool AcceptsExecutionRequest(CalculationInputParameters parameters);

In a similar manner, each interchangeable component collection can be setup as an interface. Another important detail is to keep the definition of the interface in a different assembly (ie project) than the implementations of the interface. This means the two would get compiled into different DLL’s, and different report definitions wouldn’t need to depend on each other. So far, this is all part of standard object-oriented design.

Using MEF and Handling Requests

To tie MEF into the solution, we need to setup the correct attributes on the reportdefinition implementations:

[Export(typeof(IReportDefinition)), PartCreationPolicy(CreationPolicy.NonShared)]
public class TestReportDefinition : IReportDefinition

The [Export] attribute specifies instances of this class will be exported by MEF (to be imported elsewhere). A corresponding [ImportMany] attribute with the same type in the RequestRouter class identifies the list which will contain all the instances of the different IReportDefinition object:

class RequestRouter{
   [ImportMany(typeof(IReportDefinition), AllowRecomposition = true)]
   protected IEnumerable<IReportDefinition> ReportDefinitions { get; set; }
   ReportResponse RouteReport(ReportParameters parameters)
       foreach(var repDef in ReportDefinitions)
          if ((repDef.AcceptsExecutionRequest(parameters)) // find first match
              return repDef.ExecuteReport(parameters);
       throw new ReportNotFoundException(parameters); // couldn't find the report

The collection is iterated through whenever a request gets to the RouteReport method.

The magical (it uses .NET reflection) command that instantiates and loads the IReportDefinition objects into the ReportDefinitions IEnumerable is the CompositionContainer.Compose() method. This is how you can tie it to two different directories:

var catalog = new AggregateCatalog();           
catalog.Catalogs.Add(new DirectoryCatalog("C:\\ExtensionsPath1\\"));
catalog.Catalogs.Add(new DirectoryCatalog("C:\\ExtensionsPath2\\"));
var batch = new CompositionBatch();
batch.AddPart(this); //populates the collection of composable parts in this object
CompositionContainer container = new CompositionContainer(catalog); 

Note: the compose method can fail(if the assemblies are not loaded correctly, if the .NET versions dont match, etc.) so it would be a good idea to wrap it in a Try..Catch statement and explicitly handle reflection exceptions. 

Once we setup MEF to load assemblies from a set of folders, all we need to do is attach a FileSystemWatcher to those folders’ change and added events, and trigger the ‘Compose’ method above to update our collection of report definitions.

This method will work correctly for any new assemblies dropped in the extension paths. But it will not work for multiple instances of the same assembly unless the assembly is strongly named. The only workaround I could find is to rename the assembly before dropping it into the extensions folder (so it is treated as a new assembly) and update the RouteRequest method to pick the assembly with the latest modified date for the request.

Next Steps:

  • Put together a working example of the above in .NET Fiddle using a very small assembly (loaded from an in-line defined byte array)
  • Explore the Microsoft Addin Framework for better code isolation
  • Explore convention-based MEF (instead of using attributes)