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.
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
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;
}
}
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));
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.
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()
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;
}
// This will ensure the correct assemblies are loaded prior to loading loose XAML
var activity = StrictXamlHelper.ActivityLoad(
"WorkflowLoose.xaml", GetWorkflowLooseReferencedAssemblies());
WorkflowInvoker.Invoke(activity);
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