Pure XOML workflows and a custom root activity
Last week during my Workflow Foundation presentation I was asked a question about using a custom root activity with pure XOML workflows. This is always a bit of a drag to get working, especially because you need to do two things, one for the designer and the other for the runtime.
Let's start with the following setup.
Solution
WorkflowConsoleApplication4
ActualWorkflow.xoml
WorkflowLibrary4
BaseWorkflow.cs
The BaseWorkflow.cs contains the class BaseWorkflow that is derived from SequentialWorkflowActivity and is the base class we want to use for our ActualWorkflow.xoml.
namespace WorkflowLibrary4
{
public
sealed
partial
class
BaseWorkflow : SequentialWorkflowActivity
{
// The actual code
}
}
Next we need to add the following to the AssemblyInfo.cs in the WorkflowLibrary4 project:
[assembly: XmlnsDefinition("urn:WorkflowLibrary4", "WorkflowLibrary4")]
This associates the schema urn:WorkflowLibrary4 with the assembly and is needed by the workflow designer.
Next add a project reference to WorkflowLibrary4 from WorkflowConsoleApplication4.
Next we need to create the workflow with the same namespace declaration:
<ns0:BaseWorkflow
x:Name="ActualWorkflow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/workflow"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ns0="urn:WorkflowLibrary4">
<!-- Add the remainder of the workflow here. -->
</ns0:BaseWorkflow>
The important part is the xmlns:ns0="urn:WorkflowLibrary4"> declaration and the <ns0:BaseWorkflow root element.
Rebuild and you should be able to double click on the xoml file and open it in the workflow designer. Go ahead and add any extra activities.
So far so good but now we need to run it.
In order for the WorkflowRuntime to know about the BaseWorkflow type we need to add a TypeProvider runtime service. Add the following to the Program.cs:
TypeProvider typeProvider = new
TypeProvider(null);
typeProvider.AddAssembly(Assembly.GetAssembly(typeof(BaseWorkflow)));
workflowRuntime.AddService(typeProvider);
Next we are ready to load the workflow definition and start one this way:
try
{
XmlReader reader = XmlReader.Create("ActualWorkflow.xoml");
XmlReader rules = XmlReader.Create("ActualWorkflow.rules");
WorkflowInstance instance = workflowRuntime.CreateWorkflow(reader, rules, null);
instance.Start();
waitHandle.WaitOne();
}
catch (WorkflowValidationFailedException ex)
{
// Display load/validation errors
foreach (ValidationError error in ex.Errors)
Console.WriteLine(error.ErrorText);
}
In this example I used a declarative rule so there is an additional rules file. Another tidbit is the exception of type WorkflowValidationFailedException that is raised if something is wrong when loading the workflow. The Errors collection will provide the details of what is wrong. The most often seen error in a case like this is the error that some object could not be deserialized. If this happens in the designer first rebuild the solution and if the error persists make sure the namespace in the AssemblyInfo.cs and the ActualWorkflow.xoml actually match up. If the same error occurs at runtime also check if there is a TypeProvider with the assembly in question added to the workflow runtime.
Enjoy!