Click here to Skip to main content
Click here to Skip to main content

Saving and loading Workflow Foundation 4 activities

By , 27 Jul 2012
 

Introduction

In one of my projects, I had the need for user editable workflows. The focus of this article is not how to enable the users to edit the workflows, i.e. how to host the designer. The focus of this article is the seemingly simple task of loading a XAML file that contains a WF4 activity definition in the format created by the Visual Studio designer and create just that format when saving the activity back to XAML.   

This proved harder than expected so I thought about sharing my results. Please note that this is my first article, so constructive criticism is always welcome.

Implementation

The easiest way to load and save activities from and to XAML is to use the class ActivityXamlServices. A look at the documentation reveals no Save method but several Load methods, so lets start with the latter.  

Loading an activity from XAML

Loading the activity is actually very simple:

var activity = ActivityXamlServices.Load(reader);

reader is a TextReader, e.g. an instance of a StreamReader or StringReader.

As far as I am aware, this always returns an instance of type DynamicActivity when loading XAML that has been created by the VS designer or the Save method we are going to implement later.

For a round-trip of loading and saving it is important to have a DynamicActivity, the more general Activity doesn't help us. Because of this, I am trying to cast the result of Load to a DynamicActivity and throw an exception if that doesn't work:

var activity = ActivityXamlServices.Load(reader) as DynamicActivity;
if (activity == null)
    throw new InvalidDataException("The XAML doesn't represent a DynamicActivity.");

Saving a DynamicActivity to XAML

In order to get the same XAML that the VS designer creates, it is necessary to have an instance of ActivityBuilder. Depending on how you do it, saving a normal Activity either throws an exception or leads to XAML in a different format than the one we need. 

Creating an ActivityBuilder from a DynamicActivity

So, in order to save our activity to XAML we need to have an ActivityBuilder but all we do have is a DynamicActivity. The way from a DynamicActivity to an ActivityBuilder is actually very simple, as both classes have a very similar layout. We just new up an ActivityBuilder and assign its properties with the values of our DynamicActivity:

var activityBuilder = new ActivityBuilder();
 
activityBuilder.Implementation = dynamicActivity.Implementation != null ?
                                     dynamicActivity.Implementation() : null;
activityBuilder.Name = dynamicActivity.Name;
 
foreach (var item in dynamicActivity.Attributes)
    activityBuilder.Attributes.Add(item);
 
foreach (var item in dynamicActivity.Constraints)
    activityBuilder.Constraints.Add(item);
 
foreach (var item in dynamicActivity.Properties)
{
    var property = new DynamicActivityProperty
                   {
                       Name = item.Name,
                       Type = item.Type,
                       Value = null
                   };

    foreach (var attribute in item.Attributes)
        property.Attributes.Add(attribute);
 
    activityBuilder.Properties.Add(property);
}
 
VisualBasic.SetSettings(activityBuilder, VisualBasic.GetSettings(dynamicActivity));
 
return activityBuilder;  

This code is an extended version of code presented in Winfried Lötzsch's excellent article about rehosting the WF designer. For the most part, this is straight forward.

But my version of this code has two important differences. The first difference is this line:

VisualBasic.SetSettings(activityBuilder, VisualBasic.GetSettings(dynamicActivity));  

This line has two purposes:

  1. It creates a very important magic string in the XAML file:
    <mva:VisualBasic.Settings>
        Assembly references and imported namespaces for internal implementation
    </mva:VisualBasic.Settings>  

    Without this, the activities our activity is composed of have no access to the input and output parameters of our workflow.

  2. It copies all the namespace imports from our DynamicActivity to the new ActivityBuilder.
    If this is omitted, the routine that we will later use to save the activity to XAML tries to infer the needed namespaces from the types used in the activity. It normally does a pretty good job at that task but it fails when it comes to extension methods that are used in expressions in the workflow.
    This leads to compiler errors when the XAML is later loaded and invoked.

The second difference is how I copy the properties:

foreach (var item in dynamicActivity.Properties)
{
    var property = new DynamicActivityProperty
                   {
                       Name = item.Name,
                       Type = item.Type,
                       Value = null
                   };

    foreach (var attribute in item.Attributes)
        property.Attributes.Add(attribute);
 
    activityBuilder.Properties.Add(property);
} 

I am not simply adding the instances from the DynamicActivity but I am creating new instances of DynamicActivityProperty. For the most part, I assign the values of the old instance to new instance - with one important difference: I assign null to Value.

When an activity is loaded from XAML Value is null. When the activity is then executed, Value will be assigned by the WF engine. The problem now is that an assigned Value will lead to attributes on the root tag of the generated XAML. These attributes then lead to an exception when trying to load the XAML using the method described above.

That's it, now we have an ActivityBuilder instance we can save.

Saving the ActivityBuilder

Saving is not as straight forward as loading, but still not too hard:

var stringBuilder = new StringBuilder();
var xamlXmlWriter = new XamlXmlWriter(new StringWriter(stringBuilder), new XamlSchemaContext());
var builderWriter = ActivityXamlServices.CreateBuilderWriter(xamlXmlWriter);

XamlServices.Save(builderWriter, activityBuilder);
var xaml = stringBuilder.ToString(); 

Conclusion

Once you know what to look out for, it actually is pretty simple. The attached .cs file encapsulates all this functionality in a nice little helper class.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Daniel Hilgarth
Architect fire-development Ltd.
Germany Germany
Member
No Biography provided

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionRe: Dynamic ActivitymemberMember 952046817 Oct '12 - 20:41 
Hi Daniel,
The code you posted is really helpfull. Thanks for that, when using a derived object as input to a activity and while converting that activity to Dynamic Activity its giving a error the type of that inargument is not resolved.
Do you know the cause and can you help to fix it?
AnswerRe: Dynamic ActivitymemberDaniel Hilgarth17 Oct '12 - 21:00 
Please provide a short sample to reproduce that behavior.
SuggestionNice summarymemberWinfried Lötzsch28 Jul '12 - 6:26 
Very nice summary, 5 stars for your article! I want to mention two things:
 
1. I found out, why I never ran into a problem like yours. When you get the ActivityBuilder from a rehosted workflow designer, the "magic string" is already there. As I always load my activities in the designer before I save them, I got around the problem without knowing about it. But nevertheless, thanks for your improvements, I will revise my save routine and post a update as soon as possible.
 
2. I don´t actually consider this a bug, but as this is the place where you started to collect everything about loading and saving activities, you should change your save routine to add a mc:ignorable="sap" (for System.Activities.Presentation) to your Xaml, because you don´t want to have a design reference in every project where you execute your activities (I thought, I experienced some problems with Flowcharts while not writing this line and even having a reference to System.Activities.Presentation, but I could be wrong). The way I do it is by using a custom XamlXmlWriter (see that post[^] for details):
var sb = new StringBuilder();
var xamlWriter = ActivityXamlServices.CreateBuilderWriter(
     new IgnorableXamlXmlWriter(new StringWriter(sb), new XamlSchemaContext()));
XamlServices.Save(xamlWriter, model.RootActivity);

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web04 | 2.6.130523.1 | Last Updated 27 Jul 2012
Article Copyright 2012 by Daniel Hilgarth
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid