WF4 Activity Versioning Solution

In my last post I showed you how the _XamlStaticHelper class uses different semantics when loading assemblies referenced by XAML files.

Today I’m going to show you a solution I’ve built into the Microsoft.Activities library that can help you apply standard CLR behavior when loading referenced assemblies.

endpoint.tv - How To Make WF4 Load the Assemblies You Really Want

  Get Microsoft Silverlight

Strict Assembly Resolution for Compiled Workflows

Step 1: Download and reference Microsoft.Activities.dll

Download the latest version

Step 2: Create a partial class definition for your compiled workflow

In my example project, I have a compiled workflow named WorkflowCompiled.xaml.  I have added a partial class with the same name and .cs extension

image_thumb

Step 3: Create a ReferencedAssemblies static property in your partial class

Add the FullName of any assemblies that you are referencing from your XAML

 public static IList<string> ReferencedAssemblies
{
    get
    {
        // Create a list of activities you want to reference here
        // You must add the currently executing assembly
        var list = new List<string> {
                    "ActivityLibrary1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=c18b97d2d48a43ab", 
                    Assembly.GetExecutingAssembly().GetName().FullName
                };

        // Add the standard list of references
        list.AddRange(StrictXamlHelper.StandardCSharpReferencedAssemblies);
        return list;
    }
}

Step 4: Create a Constructor in your partial class with a XamlAssemblyResolutionOption argument

The default constructor in your partial class will use the loose assembly loading behavior.  You must supply an alternate constructor to get the strict behavior

 public WorkflowCompiled(XamlAssemblyResolutionOption assemblyResolutionOption)
{
    switch (assemblyResolutionOption)
    {
        case XamlAssemblyResolutionOption.VersionIndependent:
            this.InitializeComponent();
            break;
        case XamlAssemblyResolutionOption.FullName:
            StrictXamlHelper.InitializeComponent(this, this.FindResource(), ReferencedAssemblies);
            break;
    }
}

Step 5: Construct your workflow using the new constructor passing XamlAssemblyResolutionOption.FullName

 WorkflowInvoker.Invoke(new WorkflowCompiled(XamlAssemblyResolutionOption.FullName));

Result

Your compiled workflow will now behave like any other CLR object and construct successfully if and only if it can resolve the specific version of all referenced assemblies.  If it cannot locate the assembly file it will throw a FileNotFoundException and if it can locate the file but it is not the correct version or public key token it will throw a FileLoadException.

Strict Assembly Resolution for Loose XAML

Loose XAML is an activity loaded from a XAML file using ActivityXamlServices.Load().  Unless the XAML file has FullName references (which it does not by default) ActivityXamlServices.Load will load with a partial name.  This means it could load any assembly it finds with a matching name without regard to the version or public key token.

There are two ways to fix this. 

  1. You can use the <qualifiedAssembly> configuration element to specify the FullName of the assembly you want to use
  2. Use Microsoft.Activities.StrictXamlHelper.ActivityLoad()

Step 1: Download and reference Microsoft.Activities.dll

Download the latest version

Step 2: Create a list of referenced assemblies

 public static IList<string> GetWorkflowLooseReferencedAssemblies()
{
    // Create a list of activities you want to reference here
    var list = new List<string> {
                    "ActivityLibrary1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=c18b97d2d48a43ab", 
                    Assembly.GetExecutingAssembly().GetName().FullName
                };

    // Add the standard list of references
    list.AddRange(StrictXamlHelper.StandardCSharpReferencedAssemblies);
    return list;
}

Step 3: Load the activity using StrictXamlHelper.ActivityLoad()

 // This will ensure the correct assemblies are loaded prior to loading loose XAML
var activity = StrictXamlHelper.ActivityLoad(
    "WorkflowLoose.xaml", GetWorkflowLooseReferencedAssemblies());
WorkflowInvoker.Invoke(activity);

Summary

Loading the correct version of a referenced assembly is vital.  The default behavior of both compiled workflows and loading loose XAML may result in unexpected behavior due to loading a newer version of the referenced assembly.

I recommend that you use StrictXamlHelper to ensure that you load the expected version of referenced assemblies.

Update 1/12/2011: Be aware that Assembly.Load does not respect version numbers when loading unsigned assemblies.  For more details see this post