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.

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

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

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

/// <summary>
///   Gets the referenced assemblies.
/// </summary>
/// <remarks>
/// The XamlAppDef build task generates a list of referenced assemblies automatically in the (XamlName).g.cs file 
/// You can find the list of assemblies and version that need to be referenced there.
/// This property returns a simple string list of the assemblies that will cause them to be loaded using the 
/// standard Assembly.Load method
/// </remarks>
public static IList<string> ReferencedAssemblies
{
    get
    {
        // 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", 
            };

        // Add the standard list of references
        list.AddRange(StrictXamlHelper.StandardReferencedAssemblies);
        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

/// <summary>
/// Initializes a new instance of the <see cref="WorkflowCompiled"/> class.
/// </summary>
/// <param name="assemblyResolutionOption">
/// The assembly Resolution Option.
/// </param>
public WorkflowCompiled(XamlAssemblyResolutionOption assemblyResolutionOption)
{
    switch (assemblyResolutionOption)
    {
        case XamlAssemblyResolutionOption.Loose:
            this.InitializeComponent();
            break;
        case XamlAssemblyResolutionOption.Strict:
            StrictXamlHelper.InitializeComponent(this, this.FindResource(), ReferencedAssemblies);
            break;
        default:
            throw new ArgumentOutOfRangeException("assemblyResolutionOption");
    }
}

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

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

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", };

    // Add the standard list of references
    list.AddRange(StrictXamlHelper.StandardReferencedAssemblies);
    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.