# Wednesday, 27 October 2010

It’s not uncommon to want to dynamically add new sections to an XML file, such as a config file, without having to recompile existing code for processing said XML file.  One common example is the way that .NET’s app.config file supports a config section / config section handler model.  You can add new arbitrary XML that is specific to a particular bit of code in its schema, then register a “handler” for that config section so that the standard System.Configuration classes can read and deal with that otherwise unknown schema. 

Rather then explicitly registering “handlers” in the XML file itself, we can use the Managed Extensibility Framework (MEF), which is now a fully baked part of the .NET 4 framework. 

Let’s say I start off with a configuration file that looks like this

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <products>
    <product name="Product1">
      <P1Config>
        <secretP1Value>42</secretP1Value>
      </P1Config>
    </product>
    <product name="Product2">
      <Product2Config>
        <installedFeatures>
          <feature id="1"/>
          <feature id="36"/>
        </installedFeatures>
      </Product2Config>
    </product>
  </products>
</configuration>

I want to be able to add new <product> sections in the future which will contain XML that only that particular product’s plugin will know what to do with. 

MEF is all about the “plugin” pattern.  It allows me to declare “Exported” contracts that plugins essentially publish for use, and to declare that other components “Import” those plugins, meaning they require a runtime instance of one or more of those published exports.  For the sake of the above example, I’ve defined an interface that each product plugin will need to support to process the XML from its config section.

interface IProductPlugin
{
    bool Configure(XElement config);
}

When the overall XML file is processed, an instance of each product’s plugin will be instantiated and “fed” the XElement that represents its config section.  MEF makes it easy to choose which plugin gets loaded for each section using named contracts.  The following two plugins

[Export("Product1", typeof(IProductPlugin))]
public class ProductOnePlugin : IProductPlugin
{
    public bool Configure(XElement config)
    {
        var secret = from el in config.Descendants()
                     where el.Name == "secretP1Value"
                     select el.Value;

        Debug.WriteLine(secret.FirstOrDefault());

        return true;
    }
}

[Export("Product2", typeof(IProductPlugin))]
public class ProductTwoPlugin : IProductPlugin
{
    public bool Configure(XElement config)
    {
        var installed = from el in config.Descendants()
                        where el.Name == "feature"
                        select el.Attribute("id").Value;

        foreach (var off in installed)
        {
            Debug.WriteLine(off);
        }

        return true;
    }
}

both export the IProductPlugin interface, but they also declare a name under which it will be exported.  We can use a corresponding “name” attribute in the <product> element of each section to get the right plugin.  At runtime, the code to load the file reads each product element and instantiates the right plugin by asking MEF for the named instance from MEF’s catalog.

XDocument top = XDocument.Load(s);
var products = from product in top.Root.Element("products").Elements()
               select product;

foreach (var prod in products)
{
    string name = prod.Attribute("name").Value;
    var plugin = _container.GetExport<IProductPlugin>(name);
    plugin.Value.Configure(prod);
}

The key in this case is the GetExport method.  It optionally takes that arbitrary string and tries to find the right instance from the catalog.  In this particular case, for the sake of simplicity, the catalog is loaded from the running assembly.

_container = new CompositionContainer(new AssemblyCatalog(Assembly.GetExecutingAssembly()));
_container.ComposeParts(this);

In practice, I would use the DirectoryCatalog class to build the catalog from all the assemblies in one directory, which would allow new plugin assemblies to be simply dropped into place without anything needing to be compiled.

Wednesday, 27 October 2010 14:32:51 (Pacific Daylight Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  | 

I’ll be speaking at PADNUG next Tuesday about the wonders of WF 4 and how to apply it to real world problems.  We’ll look at what problems WF solves, when it might be appropriate, and how to get started building your own workflows.  We’ll also look at using WF/WCF integration to create “declarative services” or “workflow services” quickly and easily.

Wednesday, 27 October 2010 09:40:47 (Pacific Daylight Time, UTC-07:00)  #    Disclaimer  |  Comments [1]  |