How to correctly load a WF4 workflow from XAML?

Daniel Hilgarth picture Daniel Hilgarth · Jul 17, 2012 · Viewed 11.9k times · Source

Short version:

How do I load a WF4 workflow from XAML? Important detail: The code that loads the workflow shouldn't need to know beforehand which types are used in the workflow.


Long version:

I am having a very hard time loading a WF4 workflow from the XAML file create by Visual Studio. My scenario is that I want to put this file into the database to be able to modify it centrally without recompiling the Workflow invoker.

I am currently using this code:

var xamlSchemaContext = new XamlSchemaContext(GetWFAssemblies());
var xmlReaderSettings = new XamlXmlReaderSettings();
xmlReaderSettings.LocalAssembly = typeof(WaitForAnySoundActivity).Assembly;
var xamlReader = ActivityXamlServices.CreateBuilderReader(
                     new XamlXmlReader(stream, xmlReaderSettings), 
                     xamlSchemaContext);

var activityBuilder = (ActivityBuilder)XamlServices.Load(xamlReader);
var activity = activityBuilder.Implementation;
var validationResult = ActivityValidationServices.Validate(activity);

This gives me a whole lot of errors, which fall into two categories:

Category 1:
Types from my assemblies are not known, although I provided the correct assemblies to the constructor of XamlSchemaContext.

ValidationError { Message = Compiler error(s) encountered processing expression "GreetingActivationResult.WrongPin". 'GreetingActivationResult' is not declared. It may be inaccessible due to its protection level. , Source = 10: VisualBasicValue, PropertyName = , IsWarning = False }

This can be solved by using the technique described here, which basically adds the assemblies and namespaces of all used types to some VisualBasicSettings instance:

var settings = new VisualBasicSettings();
settings.ImportReferences.Add(new VisualBasicImportReference
{
    Assembly = typeof(GreetingActivationResult).Assembly.GetName().Name,
    Import = typeof(GreetingActivationResult).Namespace
}); 
// ...
VisualBasic.SetSettings(activity, settings);
// ... Validate here

This works but makes the whole "dynamic loading" part of the Workflow a joke, as the code still needs to know all used namespaces.
Question 1: Is there another way to get rid of these validation errors without the need to know beforehand which namespaces and assemblies are used?

Category 2:
All my input arguments are unknown. I can see them just fine in activityBuilder.Properties but I still get validation errors saying they are unknown:

ValidationError { Message = Compiler error(s) encountered processing expression "Pin". 'Pin' is not declared. It may be inaccessible due to its protection level. , Source = 61: VisualBasicValue, PropertyName = , IsWarning = False }

No solution so far.
Question 2: How to tell WF4 to use the arguments defined in the XAML file?

Answer

Winfried Lötzsch picture Winfried Lötzsch · Jul 25, 2012

Question 2: You can´t execute an ActivityBuilder, it´s just for design. You have to load a DynamicActivity (only through ActivityXamlServices). It should work that way (without using a special XamlSchemaContext), but you must have loaded all used assemblies in advance (placing them in the bin directory should also work, so far about Question 1, DynamicActivity might make things a little bit easier):

var dynamicActivity = ActivityXamlServices.Load(stream) as DynamicActivity;
WorkflowInvoker.Invoke(dynamicActivity);

In general, I got the impression that you´re trying to implement your own "ActivityDesigner" (like VS). I tried this myself, and it was quite hard to deal with DynamicActivity and ActivityBuilder (as DynamicActivity is not serializable but ActivityBuilder cannot be executed), so I ended up with an own activity type that internally converts one type into the other. If you want to have a look at my results, read the last sections of this article.