Exploring Workflow Foundation Part 2: Custom Activities – Code generation during compilation

This is my second post on custom Workflow Activities. In my first post I made it easy to myself: I’ve given you some links where you can find more information on developing custom activities. Those links have a lot of information and do a very good job at introducing their subjects. I didn’t feel like repeating what was allready written.

Today I’ll introduce you to some more advanced scenario’s: I’ll explain the ActivityCodeGeneratorAtribute , and the WorkflowCompilerParameters.UserCodeCompileUnits property. Both poperties allow you to extend your workflow duing compilation.

The workflow compilation process

To understand what exactly happens when using Activity code generation, we will first see what happens when compiling a normal Workflow project.

Again, I will forward you to two articles allready available on the internet which give a good introduction to the subject. After that I will have a deeper dive into the process.

The two introducory articles are WF – Compile and execute an activity/workflow at runtime and Authoring Workflows

Using code generation to construct your activity

As you may be aware of, you can interfere with the compilation process of your custom activity by attaching a ActivityCodeGenerator attribute to your custom activity:

[ActivityCodeGenerator(typeof(ConvertToGreyscaleCodeGenerator))]
public class ConvertToGreyscale : Activity
{
    // More code here ...
}

The class specified in the typeof-declaration must be a class inherited from the ActivityCodeGenerator class. In your class you must override the base classes GenerateCode method:

public override void GenerateCode(CodeGenerationManager manager, object obj)
{
    // Your code here ...
}

Where does this interfere with the compilation process? As you can see from the signature of the GenerateCode method, you receive as a second parameter an object of the type of Activity you are trying to compile!!! Where does this come from? And what is in the first CodeGenerationManager parameter?

The workflow compilation process: a detailed look

So, what actually happens inside the compilation project? To get an answer to this question I investigated the workflow assemblies with Reflector and this is what I came up with:

  • Step 1: seperate the provided files in two arrays:
    • a first array with XOML files
    • a second array with other files
  • Step 2: execute some setup code. This code generaly makes types needed for compilation, like types you reference in your code, known to the compilation process.
  • Step 3: generate a temporary assembly from those two arrays
  • Step 3.1: generate a CodeCompileUnit unit from the provided xoml files
  • Step 3.2: generate code files from this CodeCompileUnit
  • Step 3.3: generate an assembly in memory from these generated code files and the provided code files
  • Step 4: make the generated assembly and its types known to the compilation process
  • Step 5: perform a definitive compilation
  • Step 5.1: for each type in the temporary assembly (see Step 3) that is an Activity
  • Step 5.1.1: do some setup
  • Step 5.1.2: perform the validation
  • Step 5.1.3: add it to a list of activities
  • Step 5.2: generate, again, a CodeCompileUnit unit from the provided xoml files
  • Step 5.3: for each type in the list
  • Step 5.3.1: if the type (which is an Activity) has no parent, apply each known ActivityCodeGenerator for that type, providing it the temporary generated activity instance and the CodeCompileUnit unit incapsulated in the CodeGenerationManager parameter
  • Step 5.4: generate code files from the UserCodeCompileUnits and the CodeCompileUnit unit from
  • Step 5.5: generate the definitive assembly from these generated code files and the original “other files” array from Step 1
  • Step 6: return the definitive assembly

What can we assert from all this?

Assertion 1: The Activity derived object you receive in the GenerateCode method is incomplete

This was more or less to be expected. The codegeneration is intended to give you the opportunity to extend the type, so by definition the type can not be complete. By analyzing the compilation process we got confirmation of this assumption: the type of the object provided to the ActivityCodeGenerator in Step 5.3.1 is the type generated in the temporary assembly in Step 3.

Assertion 2: The CodeGenerationManager object you receive only contains the types defined in xoml files

As you can see, one of the first things that happen are seperating the XOML files and the other files in different lists during Step 1 of the compilation process. Then, in Step 5.2 of the process, a CodeCompileUnit unit is generated from the list of files containing the XOML files, and this unit is provided to the CodeGenerationManager handed to the ActivityCodeGenerator derived class.

This immedately shows that if you provide now XOML files, then the CodeCompileUnit will be empty and you will receive to CodeDOM elements in the CodeGenerationManager.

This also shows up as an empty CodeNamespaceCollection in the debugger:

Bug or feature?
From the pseudo code of the compilation process you can see that each xoml file is transformed to it’s CodeDOM model. Unfortunately, each namespace of the xoml file is always added to a collection of namespaces, even if an earlier xoml file allready resulted in adding the same namespace.

As a result, the method GetCodeTypeDeclaration of the ActivityCodeGenerator class never returns the CodeTypeDeclaration of any type declared in subsequent namespaces.

For example:
I provide two XOML files to the compiler:

File 1

<ns0:ConvertToGreyscale
  x:Class="HFK.CustomActivities.Activity1"

File 2

<ns0:ConvertToGreyscale
  x:Class="HFK.CustomActivities.Activity2"

Then, when the comilation process arrives at the custom code generation I set a breakpoint and look into the provided CodeGenerationManager, I see the following:

Indeed: two namespaces with the same name. Then when you ask the GetCodeTypeDeclaration method to look for the CodeTypeDeclaration of your second class with name “HFK.CustomActivities.Activity2”, it looks in the collection for the corresponding namespace, then in that namespace for teh class declaration but only finds the Activity1 class and concludes, wrongly, that the Activity2 class is not available. It doesn’t go looking into the other equally named namespace.

Assertion 3: Only the ActivityCodeGenerator for the root activities are called from the compilation process

If you look in the workflow DLL’s and follow the compilation process, you will ultimately get to some lines of code similar to the following:

foreach (Activity activity in list)
{
    if (activity.Parent == null)
    {
        foreach (ActivityCodeGenerator generator 
            in manager.GetCodeGenerators(activity.GetType()))
        {
            generator.GenerateCode(manager, activity);
        }
    }
}

Indeed, before calling the codegenerators, the compilation project checks of the activity has no parents. In a workflow, the activity with no parents is the root activity. So consequently, only the root acivity has it’s codegenerators called.

Assertion 4: Wait a minute, you say that only the ActivityCodeGenerator for the root activities are called from the compilation process?

Aha !! You where paying attention !! Indeed, assertion 3 is really strange because that would mean that codegenerators for classes inside a workflow would never get called, and anyone having used Microsofts WebServiceInputActivity activity knows for a fact this isn’t true.

Well, the assertion is true, but the above conclusion is wrong. I didn’t tell you everything…

Wait, there’s more…

What I didn’t mention was the CompositeActivityCodeGenerator applied to the CompositeActivity. And this CompositeActivity just happens to live in the inheritance tree of the SequentialWorkflowActivity activity which is the root for all sequential activities. And for those using StateMachineWorkflowActivity as the root activity, don’t worry: CompositeActivity activity is also in its inheritance tree.

The GenerateCode method of the CompositeActivityCodeGenerator class has a loop which calls the GenerateCode method for all ActivityCodeGenerator objects of the root activity’s enabled child activities. So, allthough the ActivityCodeGenerators aren’t called directly through the compilation project, they get called through the CompositeActivityCodeGenerator of the root activity.

Assertion 5: You can only adapt classes that are defined in a xoml file

This is actualy a too hard statement. The truth being that you can adapt any class that was originaly defined as partial.

Using Visual Studio too generate files for your Activity derived classes, when generating a class in code that is not a partial class. If you then generate a partial class in the same namespace you will get a compiler message saying that a non partial class was allready defined. Only when you generate a XOML defined activity does Visual Studio generate a partial class and can you extend that with your own partial class CodeDOM statements.

Of course, if you completely define your own Activity classes in code and define them as being partial, you can extend them through the CodeDOM.

Downloads

In the following download you can find code which demonstrates all 5 assertions:

The code

To execute the assertions do the following:

  1. In the Main method, put in comments all lines excepts the one for the assertion you want to test
  2. In the method GenerateCode of the class CustomActivityCodeGenerator, put in comment all lines except the one for the assertion you wanrt to test
  3. Compile the code
  4. Execute the compiler console application and watch the printed messages

Links

Code generation
System.Workflow.ComponentModel.Compiler namespace?

Workflow compilation
WF – Compile and execute an activity/workflow at runtime
Authoring Workflows
Changing the directory where the WorkflowComiler creates the dll

Other
Pure XOML workflows and a custom root activity

Advertisements

Exploring Workflow Foundation Part 1: Custom Activities

I have an idea for a windows workflow foundation application, which could provide an extension to my offline maps application. I am however not completely familiar yet with the detailed workings of workflow foundation, so I’ll need to explore it first.

What will follow are a series of posts about workflow foundation which will describe this exploration. So if you’re interested you can just follow allong.

This first post will describe Custom Activities.

Workflow and workflow activities

Workflow activities can be compared to execution statements in a normal program. Because of this similarity you will find standard activities provided by Microsoft in the Workflow Foundation framework which do things similar to normal program statements:

System.Workflow.Activities.IfElseActivity
System.Workflow.Activities.WhileActivity
etc…

A complete summary of available activities you can find in the MSDN documentation.

If activities can be compared to program statements, then a wokflow can be compared to a pogram.

Custom activities: ways of implemention

To implement your own custom activity, you have a few options:

  • You can use the CodeActivity
  • You can derive a new class from the Activity base class

This post will explore the second possibility.

Deriving them from System.Workflow.ComponentModel.Activity

I will make this a rather easy post. Instead of re-typing everything about activities which has allready been said (or typed), I will forward you to some articles on the internet doing a much better job then I could ever do in a single post.

Kevin Nayyeri’s post on creating custom activities has a good intro on the basics.

If you would like to know more about the ActivityExecutionContext parameter to the Execute method, you can look at this and this post.

And for those who want to known about the ActivityExecutionStatus return type, read the post Workflow Foundation: The ActivityExecutionStatus

As you can see from Nayyeri’s post, properties are implemented using dependecy properties. Now what are they? Well, a basic introduction is provided by the post Avalon: Understanding DependencyObject and DependencyProperty. Allthough it is about the WPF implementation, its concepts are valid for WF too. Following two posts are about dependency properties in WF: Dependency Property Notes and Activity Binding in Windows Workflow Foundation
The use case for determining when to use dependency properties or regular properties is stated explicitly in this post WF Activities—Defining the Workflow Parameters:

The primary advantage to dependency properties is that they allow binding of property values to instance data at runtime. For example, you can bind the input property of one activity to an output property of another. The actual property values resulting from the binding is determined at runtime. Dependency properties are required when you want to bind properties from one activity to another. However, if you are binding an activity property to a workflow property, the workflow property is not required to be a dependency property.

Well, that’s about it for this post.

In the accompaning code you can find a custom activity I implemented regarding image processing. Hope you find it usefull.

Downloads

The Code

More Links

Custom activities
Creating Custom Activities
MVP-Submitted: The Power of Custom Workflow Activities
Build Custom Activities To Extend The Reach Of Your Workflows
Custom workflow activities and the PersistOnClose attribute

Activity Databinding
Windows Workflow Foundation Essentials (cont’d) – Enabling Activity Data Binding
Manage application processes with Windows Workflow Foundation

Custom activity unit testing
Unit testing custom workflow activities
Unit Testing Workflow Activities
A Few Interesting Windows Workflow Links – Provides some references to posts about unit testing Workflow Activities