Wednesday, August 31, 2011

Adding assemblies at run time to rehosted workflow designer

When creating a rehosted solution for Workflow Foundation 4 you need to make whichever assemblies available at run time in the "Imports" section - but how to do this?  Either you can hard code each in your code behind, which works if you have a relatively static environment and/or access to the code, but in my case where we distribute it out to many clients I would like for them to be able to modify the list at will.  Enter custom config sections.

First, the entry in our .config file.  There are many good articles out there on the meaning behind configSections and how to do it, this is meant as a copy/paste helper. :)

  <configSections>
    <section name="DesignerImportedAssemblies" type="RehostedWorkflowDesigner.Configuration.DesignerImportedAssembliesConfigSection, RehostedWorkflowDesigner"/>
  </configSections>
 
  <DesignerImportedAssemblies>
    <ImportedAssemblies>
      <add assemblyName="mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
      <add assemblyName="PresentationCore, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
      <add assemblyName="PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
      <add assemblyName="System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
      <add assemblyName="System.Activities, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
      <add assemblyName="System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
      <add assemblyName="System.Data, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
      <add assemblyName="System.Data.DataSetExtensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
      <add assemblyName="System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
      <add assemblyName="System.Xml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
      <add assemblyName="WindowsFormsIntegration, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
    </ImportedAssemblies>
  </DesignerImportedAssemblies>


Then the configSection handler:
using System.Configuration;
namespace RehostedWorklowDesigner.Configuration
{
	// Config section
  public class DesignerImportedAssembliesConfigSection : ConfigurationSection
  {
    [ConfigurationProperty("ImportedAssemblies")]
    public DesignerIncludedAssembliesCollection DesignerImportedAssemblies
    {
      get { return (DesignerIncludedAssembliesCollection)base["ImportedAssemblies"]; }
    }
  }
 
  // Collection to hold all imported assemblies
  public class DesignerIncludedAssembliesCollection : ConfigurationElementCollection
  {
    protected override ConfigurationElement CreateNewElement()
    {
      return new ImportedAssemblyElement();
    }
 
    protected override object GetElementKey(ConfigurationElement element)
    {
      return ((ImportedAssemblyElement)(element)).AssemblyName;
    }
 
    public ImportedAssemblyElement this[int idx]
    { get { return (ImportedAssemblyElement)BaseGet(idx); } }
}
 
  // and the assembly information itself.
  public class ImportedAssemblyElement : ConfigurationElement
  {
    [ConfigurationProperty("assemblyName", IsKey = true, IsRequired = true)]
    public string AssemblyName
    {
      get { return (string)base["assemblyName"]; }
set { base["assemblyName"] = value; }
    }
  }
}

And lastly putting the two together and joining them to the workflow designer control via the AssemblyContextControlItem object :

AssemblyContextControlItem acci = new AssemblyContextControlItem();
acci.ReferencedAssemblyNames = new List<AssemblyName>();

acci.LocalAssemblyName = Assembly.GetExecutingAssembly().GetName();
 
 
// Pull all the referenced assemblies from the .config file.
 
Configuration.DesignerImportedAssembliesConfigSection section = (Configuration.DesignerImportedAssembliesConfigSection)ConfigurationManager.GetSection("DesignerImportedAssemblies");
 
foreach (Configuration.ImportedAssemblyElement element in section.DesignerImportedAssemblies)
{
  acci.ReferencedAssemblyNames.Add(new AssemblyName(element.AssemblyName));
}