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
Strict Assembly Resolution for Compiled Workflows
Step 1: Download and reference Microsoft.Activities.dll
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
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.
- You can use the <qualifiedAssembly> configuration element to specify the FullName of the assembly you want to use
- Use Microsoft.Activities.StrictXamlHelper.ActivityLoad()
Step 1: Download and reference Microsoft.Activities.dll
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