text:
Most people think of workflows as a tool to represent and automate back-end business processes. Back-end business processes normally require some user interaction but their main purpose is not to drive the user experience or manage the UI. However, there is a growing type of application that leverages workflow as a tool to drive the user interaction and drive the user experience of an interactive process. This type of technology is called page flow.
Last year at TechEd, we showed off some bits we had been working on internally that were designed to make that possible, the ability to model the user interaction of an application using workflow. This approach provides developers the ability to continue managing the complexity of their application in a structure and scalable manner. It turned out that the code we showed at TechEd wasn't going to end up in any of the product releases, so the dev team requested permission to release that code as a sample of how one can implement a generic navigation framework using WF that can support multiple UI technologies (i.e. ASP.NET and WPF). This year, I just finished giving a talk showing this off and talking about how it will be available today!
Thanks go to Shelly Guo, the developer and Israel Hilerio, the PM who had worked on this feature, and to Jon Flanders for providing packaging and quality control
Now for the good stuff, download the bits from here!
Navigate to setup.exe and run the setup, this will copy the sample projects and the source code for the sample, as well as some new visual studio project templates.
Now, let's open up a sample project, so navigate to the samples directory and open the ASPWorkflow sample, this will show off both an ASP.NET Front end as well as a WPF Controller (you can actually use the two together). Let's get to the good stuff right away, and open up the workflow file.
Wow… what's going on here? It kind of looks like a state machine, but not really. What has been done here is to create a new base workflow type. Things like SequentialWorkflow and StateMachineWorkflow aren't the only ways to write workflows, they are just two common patterns of execution. A NavigatorWorkflow type has been created (and you can inspect the source and the architecture document to see what this does) and a WorkflowDesigner has been created for it as well (again, this source is available as a guide for those of you who are creating your own workflow types).
Each of the activities you see on the diagram above is an InteractionActivity, representing the interaction between the user (via the UI technology of their choosing) and the process. A nice model is to think of the InteractionActivity as mapping to a page within a UI. The output property is the information that is sent to that page (a list of orders or addresses to display) and the input is the information that is received from the page when the user clicks "submit". The InteractionActivity is a composite activity, allowing one to place other activities within the activity to be executed when input is received. The interesting property of the InteractionActivity is the Transitions collection. By selecting this and opening its designer, we are presented with the following dialog:
This allows us to specify n-transitions from this InteractionActivity or "page" to other InteractionActivities. And we can specify this via a WF activity condition. This way, we could forward orders greater than $1000 to a credit verification process, or orders containing fragile goods through a process to obtain insurance from a shipper. What's cool about this, my page does not know about that process, it just says "GoForward" and my process defines what comes next. This de-couples the pages from the logic of your process.
We then need to wire things up in config:

<NavigationManagerSettings StartOnDemand="false">
<Workflow mode="Compiled" value="ASPUIWorkflow.Workflow1, ASPUIWorkflow"/>

<add type="System.Workflow.Runtime.Hosting.DefaultWorkflowCommitWorkBatchService, System.Workflow.Runtime, Version=184.108.40.206, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
<add type="System.Workflow.Runtime.Hosting.SqlWorkflowPersistenceService, System.Workflow.Runtime, Version=220.127.116.11, Culture=neutral, PublicKeyToken=31bf3856ad364e35" ConnectionString="Initial Catalog=WorkflowStore;Data Source=localhost;Integrated Security=SSPI;" UnloadOnIdle="true"/>
<add bookmark="Page1" location="/WebSite/Default.aspx"/>
<add bookmark="Page2" location="/WebSite/Page2.aspx"/>
<add bookmark="Page3" location="/WebSite/Page3.aspx"/>
<add bookmark="Page4" location="/WebSite/Page4.aspx"/>
<add bookmark="Page5" location="/WebSite/Page5.aspx"/>
<add bookmark="LastPage" location="/WebSite/LastPage.aspx"/>
<add type="Microsoft.Samples.Workflow.UI.WorkflowNotFoundException" location="/WebSite/ErrorPage.aspx"/>
<add type="Microsoft.Samples.Workflow.UI.WorkflowCanceledException" location="/WebSite/ErrorPage.aspx"/>
<add type="System.ArgumentException" location="/WebSite/ErrorPage.aspx"/>
<add type="System.Security.SecurityException" location="/WebSite/ErrorPage.aspx"/>
Finally, let's look inside an ASP.NET page and see what we need to do to interact with the process:
AspNetUserInput.GoForward("Submit", userInfo, this.User);
This code is specifying the action and is submitting a userInfo object (containing various information gathered from the page) to the InteractionActivity (in this case, it submits to the Page2 InteractionActivity). If we look at what we've configured as the Input for this InteractionActivity, we see the following, which we can then refer to in the transition rules in order to make decisions about where to go next:
Plenty of other stuff we could talk about here, support for back button, persistence, etc and I could continue to ramble on about this in another record-length blog post, but I will stop here for now. I will continue to blog about this, look forward to hearing any and all types of feedback, and what you'd be interested in seeing in this. Moving forward, there aren't any formal plans around this, but if there is enough interest in the community, we could get it created as a project on codeplex. If that sounds intriguing either contact me through this blog, leave a comment so that I can gauge the interest in such a scenario.
Go, grab the bits! And, if you have feedback, please contact me.
The wheels of evangelism never stop rolling. Just a few months ago I was blogging that .NET 3.0 was released. I've been busy since then, and now I can talk about some of that. Today, the March CTP of Visual Studio "Orcas" was released to the web. You can get your fresh hot bits here. Samples will be coming shortly. Thom has a high level summary here.
UPDATE: Wendesday, 2/28/2007 @ 11pm. The readme file is posted here, a few minor corrections have been made to the caveats below.
More updates... corrections to another caveat (a post-build event is required to get the config to be read).
Since this is a CTP, it's possible that sometimes the wrong bits end up in the right place at the wrong time. Here are a few things to be aware of (not intended to be a comprehensive list):
The CTP is not something that is designed for you to go into production with, it's designed to let you explore the technology. There is no go-live license associated with this, it's for you to learn more about the technology. Since most of these issues have some work around, this shouldn't prevent you from checking these things out (because they are some kind of neat).
We've been talking about this since we launched at PDC 2005. There was a session at TechEd 2006 in the US and Beijing that mentioned bits and pieces of this. One of the key focus areas is the unification of WCF and WF. Not only have the product teams joined internally, the two technologies are very complementary. So complementary that everyone usually asks "so how do I use WCF services here?" when I show a workflow demo. That's fixed now!
Workflow enabled services allow two key things:
This is accomplished by the following additions built on top of v1:
The Send and Receive activites live inside of the workflow that we define. The cool part of the Receive activity is that we have a contract designer, so you don't have to dive in and create an interface for the contract, you can just specifiy it right on the Receive activity, allowing you a "workflow-first" approach to building services.
Once we've built a workflow, we need a place to expose it as a service. We use the WorkflowServiceHost which is a subclass of ServiceHost in order to host these workflow enabled services. The WorkflowServiceHost takes care of the nitty-gritty details of managine workflow instances, routing incoming messages to the appropriate workflow and performing security checks as well. This means that the code required to host a workflow as a WCF service is now reduced to four lines of code or so. In the sample below, we are not setting the endpoint info in code due to the issue mentioned above.
1: WorkflowServiceHost wsh = new WorkflowServiceHost(typeof(MyWorkflow));
2: wsh.Open();
3: Console.WriteLine("Press <Enter> to Exit");
4: Console.ReadLine();
5: wsh.Close();
To support some of the more sophisticated behavior, such as routing messages to a running workflow, we introduce a new channel extension responsible for managing context. In the simple case, this context just contains the workflowId, but in a more complicated case, it can contain information similar to the correlation token in v1 that allows the message to be delivered to the right activity (think three receives in parallel, all listing on the same operation). Out of the box there is the wsHttpContextBinding and the netTcpContextBinding which implicitly support the idea of maintaining this context token. You can also roll your own binding and attach a Context element into the binding definition.
The Send activity allows the consumption of a service, and relies on configuraiton to detemrine exactly how we will call that service. If the service we are calling is another workflow, the Send activity and the Receive activity are aware of the context extensions and will take advantage of them.
With the Send and Receive actiivty, it gets a lot easier to do workflow to workflow communicaiton, as well as more complicated messaging patterns.
Another nice feature of the work that was done to enable this is that we know have the ability to easily support durable services. These are "normal" WCF services written in code that utilize an infrastructure similar to the workflow persistence store in order to provide a durable storing of state between method calls.
As you can imagine, I'll be blogging about this a lot more in the future.
While there has been a lot of focus on the UI side of AJAX, there still remains the task of creating the sources for the UI to consume. One can return POX (Plain Old Xml) and then manipulate it in the javascript, but that can get messy. JavaScript Object Notation (JSON) is a compact, text-based serialization of a JavaScript object. This lets me do something like:
var stuff = {"foo" : 78, "bar" : "Forty-two"};
document.write("The meaning of life is " + stuff.bar);
In WCF, we can now return JSON with a few switches of config. The following config:
1: <service name="CustomerService">
2: <endpoint contract="ICustomers"
3: binding="webHttpBinding"
4: bindingConfiguration="jsonBinding"
5: address="" behaviorConfiguration="jsonBehavior" />
6: </service>
8: <webHttpBinding>
9: <binding name="jsonBinding" messageEncoding="Json" />
10: </webHttpBinding>
12: <behaviors>
13: <endpointBehaviors>
14: <behavior name ="jsonBehavior">
15: <webScriptEnable />
16: </behavior>
17: </endpointBehaviors>
18: </behaviors>
will allow a function like this:
1: public Customer[] GetCustomers(SearchCriteria criteria)
2: {
3: // do some work here
4: return customerListing;
5: }
to return JSON when called. In JavaScript, I would then have an instance of a CustomerOrder object to manipulate. We can also serialize from JavaScript to JSON so this provides a nice way to send parameters to a method. So, in the above method, we can send in the complex object SearchCriteria from our JavaScript. There is an extension to the behavior that creates a JavaScript proxy. So, by referencing /js as the source of the script, you can get IntelliSense in the IDE, and we can call our services directly from our AJAX UI.
We can also use the JSON support in other languages like Ruby to quickly call our service and manipulate the object that is returned.
I think that's pretty cool.
While we have the RSS Toolkit in V1, we wanted to make syndication part of the toolset out of the box. This allows a developer to quickly return a feed from a service. Think of using this as another way to expose your data for consumption. We have introduced a SyndicationFeed object that is an abstraction of the idea of a feed that you program against. We then leave it up to config to determine if that is an ATOM or RSS feed (and, would it be WCF if we didn't give you a way to implement a custom encoding as well?) So this is cool if you just want to create a simple feed, but it also allows you to create a more complicated feed that has content that is not just plain text. For instance, the digg feed has information about the submission, the flickr feed has info about the photos. Your customer feed may want to have an extension that contains the customer info that you will allow your consumers to have access to. The SyndicationFeed object allows you to create these extensions and then the work of encoding it to the specific format is taken care of for you. So, let's seem some of that code (note, this is from the samples above):
1: public SyndicationFeed GetProcesses()
3: Process[] processes = Process.GetProcesses();
5: //SyndicationFeed also has convenience constructors
6: //that take in common elements like Title and Content.
7: SyndicationFeed f = new SyndicationFeed();
9: //Create a title for the feed
10: f.Title = SyndicationContent.CreatePlaintextTextSyndicationContent("Currently running processes");
11: f.Links.Add(SyndicationLink.CreateSelfLink(OperationContext.Current.IncomingMessageHeaders.To));
13: //Create a new RSS/Atom item for each running process
14: foreach (Process p in processes)
15: {
16: //SyndicationItem also has convenience constructors
17: //that take in common elements such as Title and Content
18: SyndicationItem i = new SyndicationItem();
20: //Add an item title.
21: i.Title = SyndicationContent.CreatePlaintextTextSyndicationContent(p.ProcessName);
23: //Add some HTML content in the summary
24: i.Summary = new TextSyndicationContent(String.Format("{0}", p.MainWindowTitle), TextSyndicationContentKind.Html);
26: //Add some machine-readable XML in the item content.
27: i.Content = SyndicationContent.CreateXmlSyndicationContent(new ProcessData(p));
29: f.Items.Add(i);
30: }
32: return f;
33: }
And the config associated with this would be:
1: <system.serviceModel>
2: <services>
3: <service name="ProcessInfo">
4: <endpoint address="rss"
5: behaviorConfiguration="rssBehavior" binding="webHttpBinding"
6: contract="HelloSyndication.IDiagnosticsService" />
7: <endpoint address="atom"
8: behaviorConfiguration="atomBehavior" binding="webHttpBinding"
9: contract="HelloSyndication.IDiagnosticsService" />
10: </service>
11: </services>
12: <behaviors>
13: <endpointBehaviors>
14: <behavior name="rssBehavior">
15: <syndication version="Rss20"/>
16: </behavior>
17: <behavior name="atomBehavior">
18: <syndication version="Atom10"/>
19: </behavior>
20: </endpointBehaviors>
21: </behaviors>
22: </system.serviceModel>
This config will actually create an RSS and an ATOM endpoint. The feed returned would have the process information embedded as: (in this case in ATOM)
1: <entry>
2: <id>fe1f1d2e-d676-417d-85bf-b7969dd07661</id>
3: <title type="text">devenv</title>
4: Conversations - Microsoft Visual Studio
5: <content type="text/xml">
6: <ProcessData xmlns="http://schemas.datacontract.org/2004/07/HelloSyndication"
7: xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
8: <PeakVirtualMemorySize>552157184</PeakVirtualMemorySize>
9: <PeakWorkingSetSize>146124800</PeakWorkingSetSize>
10: <VirtualMemorySize>456237056</VirtualMemorySize>
11: </ProcessData>
12: </content>
13: </entry>
We can also use the Syndication support to consume feeds!
1: SyndicationFeed feed = new SyndicationFeed();
2: feed.Load(new Uri("http://blogs.msdn.com/mwinkle/atom.xml"));
3: foreach (SyndicationItem item in feed.Items)
4: {
5: // process the feed here
6: }
In the case where there have been extensions to the feed, we can access those as the raw XML or we can attempt to deserialize into an object. This is accomplished in the reverse of the above:
1: foreach (SyndicationItem i in feed.Items)
3: XmlSyndicationContent content = i.Content as XmlSyndicationContent;
4: ProcessData pd = content.ReadContent<ProcessData>();
6: Console.WriteLine(i.Title.Text);
7: Console.WriteLine(pd.ToString());
8: }
In order to enable both of the above scenarios (Syndication and JSON), there has been work done to create the webHttpBinding to make it easier to do POX and HTTP programming.
Here's an example of how we can influence this behavior and return POX. First the config:
1: <service name="GetCustomers">
2: <endpoint address="pox"
3: binding="webHttpBinding"
4: contract="Sample.IGetCustomers" />
5: </service>
Now the code for the interface:
1: public interface IRestaurantOrdersService
3: [OperationContract(Name="GetOrdersByRestaurant")]
4: [HttpTransferContract(Method = "GET")]
5: CustomerOrder[] GetOrdersByRestaurant();
The implementation of this interface does the work to get the CustomerOrder objects (a datacontract defined elsewhere). And the returned XML is the datacontract serialization of CustomerOrder (omitted here for some brevity). With parameters this gets more interesting as these are things we can pass in via the query string or via a POST, allowing arbitrary clients that can form URL's and receive XML to consume our services.
I'm not fully up to date on all of the details here, but there has been some work done to enable some of the WCF functionality to operate in a partial trust environment. This is especially important for situations where you want to use WCF to expose a service in a hosted situation (like creating a service that generates an rss feed off of some of your data). I'll follow up with more details on this one later.
You now get a WCF project template that also includes a self hosting option (similar to the magic Visual Studion ASP.NET hosting). This means that you can create a WCF project, hit F5 and have your service available. This is another are where I will follow up later on.
So, what now?
One common scenario that was often requested by customers of WF 3 was the ability to have templated or "grey box" or "activities with holes" in them (hence the Swiss cheese photo above). In WF4 we've done this in a way that way we call ActivityAction
First I'd like to do a little bit more to motivate the scenario.
Consider an activity that you have created for your ERP system called CheckInventory. You've gone ahead and encapsulated all of the logic of your inventory system, maybe you have some different paths of logic, maybe you have interactions with some third party systems, but you want your customers to use this activity in their workflows when they need to get the level of inventory for an item.
Consider more generally an activity where you have a bunch of work you want to get done, but at various, and specific, points throughout that work, you want to allow the consumer of that activity to receive a callback and provide their own logic to handle that. The mental model here is one of delegates.
Finally, consider providing the ability for a user to specify the work that they want to have happen, but also make sure that you can strongly type the data that is passed to it. In the first case above, you want to make sure that the Item in question is passed to the action that the consumer supplies.
In wf3, we had a lot of folks want to be able to do something like this. It's a very natural extension to wanting to model things as activities and composing into higher level activities. We like being able to string together 10 items as a black box for reuse, but we really want the user to specify exactly what should happen between steps 7 and 8.
A slide that I showed at PDC showed it this way (the Approval and Payment boxes represent the places I want a consumer to supply additional logic):
Very early on the release, we knew this was one of the problems that we needed to tackle. The mental model that we are most aligned with is that of a delegate/ callback in C#. If you think about a delegate, what are you doing, you are giving an object the implementation of some bit of logic that the object will subsequently call. That's the same thing that's going on with an ActivityAction. there are three important parts to an ActivityAction
Let's start with some simple code (this is from a demo that I showed in my PDC talk). This is a timer activity which allows us to time the execution for the contained activity and then uses an activity action to act on the result.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Activities;
using System.Diagnostics;
namespace CustomActivities.ActivityTypes
public sealed class Timer : NativeActivity<TimeSpan>
public Activity Body { get; set; }
public Variable<Stopwatch> Stopwatch { get; set; }
public ActivityAction<TimeSpan> OnCompletion { get; set; }
public Timer()
Stopwatch = new Variable<Stopwatch>();
protected override void CacheMetadata(NativeActivityMetadata metadata)
protected override void Execute(NativeActivityContext context)
Stopwatch sw = new Stopwatch();
Stopwatch.Set(context, sw);
// schedule body and completion callback
context.ScheduleActivity(Body, Completed);
private void Completed(NativeActivityContext context, ActivityInstance instance)
if (!context.IsCancellationRequested)
Stopwatch sw = Stopwatch.Get(context);
Result.Set(context, sw.Elapsed);
if (OnCompletion != null)
context.ScheduleAction<TimeSpan>(OnCompletion, Result.Get(context));
protected override void Cancel(NativeActivityContext context)
if (OnCompletion != null)
context.ScheduleAction<TimeSpan>(OnCompletion, TimeSpan.MinValue);
A few things to note about this code sample:
if (OnCompletion != null)
context.ScheduleAction<TimeSpan>(OnCompletion, Result.Get(context));
It is important to note that I use the second parameter (and the third through 16th if that version is provided) in order to provide the data. This way, the activity determines what data will be passed to the handler, allowing the activity to determine what data is visible where. This is a much better way than allowing an invoked child to access any and all data from the parent. This lets us be very specific about what data goes to the ActivityAction. Also, you could make it so that OnCompletion must be provided, that is, the only way to use the activity is to supply an implementation. If you have something like "ProcessPayment" you likely want that to be a required thing. You can use the CacheMetadata method in order to check and validate this.
Now, let's look at the code required to consume this time activity:
DelegateInArgument<TimeSpan> time = new DelegateInArgument<TimeSpan>();
a = new Timer
Body = new HttpGet { Url = "http://www.microsoft.com" },
OnCompletion = new ActivityAction<TimeSpan>
Argument = time,
Handler = new WriteLine {
Text = new InArgument<string>(
ctx =>
"Time input from timer " + time.Get(ctx).TotalMilliseconds)
At runtime, when we get to the point in the execution of the Timer activity, the WriteLine that the hosting app provided will be scheduled when the ScheduleAction is called. This means we will output the timing information that the Timer observed. A different implementation could have an IfThen activity and use that to determine if an SLA was enforced or not, and if not, send a nasty email to the WF author. The possibilities are endless, and they open up scenarios for you to provide specific extension points for your activities.
That wraps up a very brief tour of ActivityAction. ActivityAction provides a easy way to create an empty place in activity that the consumer can use to supply the logic that they want executed. In the second part of this post, we'll dive into how to create a designer for one of these, how to represent this in XAML, and a few other interesting topics.
It's that time of year that I'll be taking a little bit of time off for the holidays, so I will see y'all in 2010!
Photo Credit
// standard disclaimer applies, this is based on the released Beta 1 bits, things are subject to change, if you are reading this in 2012, things may be, look, smell, work differently. That said, if it's 2012 and you're reading this, drop me a line and let me know how you found this!
As you might have heard, Beta1 of VS is out the door, and available to the public sometime today. As you may know we've done a bunch of work for WF4, and I wanted to give a quick, high level overview of the designer. Here's a good overview for the new WF bits all up.
First, let's start with your existing WF projects. What happens if I want to create a 3.5 workflow? We're still shipping that designer, in fact, let's start there on our tour. This shows of a feature of VS that's pretty cool, multitargeting.
Click New Project
Notice the "Framework Version" dropdown in the upper right hand corner.
This tells VS which version of the framework you would like the project you are creating to target. This means you can still work on your existing projects in VS 2010 without upgrading your app to the new framework. Let's pick something that's not 4.0, namely 3.5. You'll note that the templates may have updated a bit, select Workflow from the left hand tree view and see what shows up.
There isn't anything magical about what happens next, you will now see the 3.5 designer inside of VS2010. You're able to build, create, edit and update your existing WF applications.
Let's move on and switch over to a 4.0 workflow.
Create a new project and select 4.0
Create a new WF Sequential Console application and name it "SampleProject". Click Ok.
We'll do a little bit of work here, but you will shortly see the WF 4.0 designer. It looks a little different from the 3.x days, we've taken this time to update the designer pretty substantially. We've built it on top of WPF, which opens up the doors for us to do a lot of interesting things. If you were at PDC and saw any Quadrant demos, you might think that these look similar. We haven't locked on the final look and feel yet, so expect to see some additional changes there, but submit your feedback early and often, we want to know what you think.
Let's drop some activities into our sequence and see what's there to be seen.
We've categorized the toolbox into functional groupings for the key activities. We heard a lot of feedback that it was tough to know what to use when, so we wanted to provide a little more help with some richer default categories. Add an Assign activity, a WriteLine activity and a Delay activity to the canvas by clicking and dragging over the to the sequence designer.
You'll note that we've now got some icons on each activity indicating something is not correct. This is a result of the validation executing and returning details about what is wrong. Think of these as the little red squiggles that show up when you spell something wrong. You can hover over the icon to see what's wrong
You can also see that errors will bubble up to their container, so hovering over sequence will tell you that there is a problem with the child activities.
What if I have a big workflow, and what if I want to see a more detailed listing of errors? Open up the Error View and you will see the validation results are also displayed here. You'll note there is some minor formatting weirdness. This is a bug that we fixed but not in time for the Beta1 release.
Now, let's actually wire up some data to this workflow. WF4 has done a lot of work to be much more crisp about the way we think about data within the execution environment of a workflow. We divide the world into two types of data, Arguments, and Variables. If you mentally map these to the way you write a method in code (parameters, and state internal to the method), you are one the right track. Arguments determine the shape of an activity, what goes in, what goes out. Variables allocate storage within the context of an activities execution. The neat thing about variables, once the containing activity is done, we can get rid of the variables, as our workflow no longer needs them (note, we pass the important data in and out through the arguments). To do this, we have two special designers on the canvas that contain information about the arguments and variables in your workflow
First, let's click on the Argument designer and pass in some data.
Arguments consist of a few important elements
Most of these are self explanatory, with the one exception being the Direction. You'll note that this has In, Out and Property. Now, when you are editing the arguments, you are actually editing the properties of the underlying type you are creating (I'll explain more about this in a future post). A more appropriate name might be "Property Editor" but the vast majority of what you'll be creating with it is arguments. Anyway, If you select In or Out, this basically wraps the type T in an InArgument, so it becomes a property of type InArgument<T>. We just provide a bit of a shorthand so you don't always have to pick InArgument as the type. The default value takes an expression, but in this case, we won't be using it.
Let's go ahead and add an argument of type TimeSpan named DelayTime. You'll need to select browse for types and then search for the TimeSpan
Variables are similar, but slightly different, variables have a few important elements:
Remember earlier, I mentioned that variable is part of an activity, this is what Scope refers to. Variables will only show up to be the scope of the selected activity, so if you don't see any, make sure to select the Sequence, and then you will be able to add a variable. Let's add new variable, named StringToPrint, of type String.
Now let's do something with these in the workflow. One thing I'm particularly happy with that we've done on the designer side of things is to enable people to build activity designers more easily. There are lots of times where you have activities that have just a few key properties that need to be set, and you'd like to be able to see that "at a glance" The assign designer is like that.
Now, let's dig into expressions. One big piece of feedback from 3.0 was that people really wanted richer binding experiences. You see this as well with the WPF data binding . We've taken it to the next level, and allow any expression to be expressed as a set of activities. What this means is that we do have to "compile" textual expressions into a tree of activities, and this is one of the reasons we use VB to build expressions. In the fullness of time, other languages will come on board. But how to use it, let's see. Click on the "To" text box on the Assign activity. You will see a brief change of the text box, and then you will be in a VB Expression Editor, or what we've come to refer to as "The Expression Text Box" or ETB. Start typing S, and already you will see intellisense begin to scope down the choices. This will pick up all of the variables and arguments in scope.
On the right side, we won't use any of the passed in arguments, we'll show off a richer expression. Now, the space on the right side of the designer is kind of tight for something lengthy, so go to the property grid and click on the "…" button for the Value property
String.Format("Someone wants to wait {0} seconds", TimeToWait.TotalSeconds)
This just touches the surface of what is possible with expressions in WF4, we can really get much richer expressions (3.x expressions are similar to WPF data binding, they are really an "object" + "target" structure).
Not everything makes it to the canvas of the designer surface, and for that, we have the property grid. If you've used the WPF designer in VS2008, this should look pretty familiar to you. Select the delay activity, and use the property grid to set the duration property to the InArgument you created above. This experience is similar, with the ETB embedded into the property grid for arguments.
Finally, repeat with the WriteLine and bind to the StringToPrint variable.
There are two different things that we have to help navigating the workflow, our breadcrumb bar at the top and the overview map (which appears as the "Mini Map" in the beta). Let's look at the overview map. This gives you a view of the entire workflow and the ability to quickly scrub and scroll across it.
Finally, across the top we display our breadcrumbs which are useful when you have a highly nested workflow. Double click on one of the activities, and you should see the designer "drill into" that activity. Now notice the breadcrumb bar, it displays where you have been, and by clicking you can navigate back up the hierarchy. In beta1, we have a pretty aggressive breadcrumb behavior, and so you see "collapsed in place" as the default for many of our designers. We're probably going to relax that a bit in upcoming milestones to reduce clicking and provide a better overview of the workflow.
Finally, there may be times where we don't want to have a designer view, but would rather see the XAML. To get there, just right click on the file and ask to "View Code"
This will currently ask you if you are sure that you want to close the designer, and you will then see the XAML displayed in the XML editor. For the workflow we just created, this is what it looks like:
1: <p:Activity mc:Ignorable=""
2: x:Class="WorkflowConsoleApplication1.Sequence1"
3: xmlns="http://schemas.microsoft.com/netfx/2009/xaml/activities/design"
4: xmlns:__Sequence1="clr-namespace:WorkflowConsoleApplication1;"
5: xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
6: xmlns:p="http://schemas.microsoft.com/netfx/2009/xaml/activities"
7: xmlns:s="clr-namespace:System;assembly=mscorlib"
8: xmlns:sad="clr-namespace:System.Activities.Debugger;assembly=System.Activities"
9: xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
10: <x:Members>
11: <x:Property Name="TimeToWait" Type="p:InArgument(s:TimeSpan)" />
12: </x:Members>
13: <__Sequence1:Sequence1.TimeToWait>
14:
[TimeSpan.FromSeconds(10)]

15: </__Sequence1:Sequence1.TimeToWait>
16:

17:

18:

19:

20:

21:

22:

23: [StringToPrint]
24:

25:

26:

27:

28: [String.Format("Someone wants to wait {0} seconds", TimeToWait.TotalSeconds)]
29:

30:

31:

32:
[TimeToWait]

33:
[StringToPrint]

34:

35:

Inside the project you will see the Program.cs to execute the workflow, let's take a look at that.
1: namespace WorkflowConsoleApplication1
3: using System;
4: using System.Linq;
5: using System.Threading;
6: using System.Activities;
7: using System.Activities.Statements;
8: using System.Collections.Generic;
10: class Program
11: {
12: static void Main(string[] args)
13: {
14: AutoResetEvent syncEvent = new AutoResetEvent(false);
16: WorkflowInstance myInstance =
17: new WorkflowInstance(new Sequence1(),
18: new Dictionary<string, object>
19: {
20: {"TimeToWait", TimeSpan.FromSeconds(3.5) }
21: }
25: );
26: myInstance.OnCompleted = delegate(WorkflowCompletedEventArgs e) { syncEvent.Set(); };
27: myInstance.OnUnhandledException = delegate(WorkflowUnhandledExceptionEventArgs e)
28: {
29: Console.WriteLine(e.UnhandledException.ToString());
30: return UnhandledExceptionAction.Terminate;
31: };
32: myInstance.OnAborted = delegate(WorkflowAbortedEventArgs e)
33: {
34: Console.WriteLine(e.Reason);
35: syncEvent.Set();
36: };
38: myInstance.Run();
40: syncEvent.WaitOne();
41: Console.WriteLine("Press <Enter> to exit");
42: Console.ReadLine();
44: }
45: }
46: }
This is the standard program.cs template with two modifications. The first is passing data into the workflow, indicated by the dictionary we create to pass into the WorkflowInstance. This should look familiar if you have used WF in the past.
WorkflowInstance myInstance =
new WorkflowInstance(new Sequence1(),
new Dictionary<string, object>
{"TimeToWait", TimeSpan.FromSeconds(3.5) }
The second is a break at the end to keep our console window open (lines 41 and 42). Hitting F5 from our project results in the following output (as expected).
This concludes our brief tour through the new WF designer. I'll be talking a lot more in upcoming days about some of the more programmatic aspects of it and how it's put together.
standard beta disclaimer. This is written against the beta1 API's. If this is 2014, the bits will look different. When the bits update, I will make sure to have a new post that updates these (or points to SDK samples that do)
In yesterday's post, we went over the core components of the designer. Let's now take that and build that rehosts the designer, and then we'll circle back around and talk about what we did and what comes next.
Start VS, Create a new project, and select a WPF project
Inside the VS project add references to the System.Activities.* assemblies. For now, that list looks like
You might think the list of design assemblies is excessive. We'll be collapsing probably into two design assemblies, one with the designer infrastructure and one with the activity designers in subsequent milestones.
Create some layout in the WPF window to hold the various designer elements. I usually do a three column grid for toolbox, property grid and designer canvas.
The XAML for this looks roughly like this:
<Window x:Class="BlogPostRehosting.Window1"
Title="Window1" Height="664" Width="831">
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="3*"/>
<ColumnDefinition Width="2*"/>
Now that we've got the layout down, let's get down to business. First let's just get an app that displays the workflow designer and then we will add some other interesting features. We wanted to make it easy to get a canvas onto your host application, and to program against it. The key type that we use is WorkflowDesigner, this encapsulates all of the functionality, and operating context, required. Let's take a quick look at the type definition
The editing context is where we will spend more time in the future, for now the View is probably what's most interesting, as this is the primary designer canvas. There are also some useful methods to load and persist the workflow as well.
Let's start off real simple, and write some code that will display a basic sequence, and we'll get more sophisticated as we go along.
1: using System.Windows;
2: using System.Windows.Controls;
3: using System.Activities.Design;
4: using System.Activities.Core.Design;
5: using System.Activities.Statements;
7: namespace BlogPostRehosting
8: {
9: ///
10: /// Interaction logic for Window1.xaml
11: ///
12: public partial class Window1 : Window
13: {
14: public Window1()
15: {
16: InitializeComponent();
17: LoadWorkflowDesigner();
18: }
20: private void LoadWorkflowDesigner()
21: {
22: WorkflowDesigner wd = new WorkflowDesigner();
23: (new DesignerMetadata()).Register();
24: wd.Load(new Sequence
25: {
26: Activities =
27: {
28: new Persist(),
29: new WriteLine()
30: }
31: });
32: Grid.SetColumn(wd.View, 1);
33: grid1.Children.Add(wd.View);
34: }
35: }
36: }
Let's walk through this line by line:
This gives us the following application:
Now, that was pretty simple, but we're also missing some key things, namely, the property grid. It's important to note however that this has all of the functionality of the designer (the variables designer, the overview map, etc. This will react just the same as if you were building the workflow in VS.
Let's add the property grid by adding the following two lines:
Grid.SetColumn(wd.PropertyInspectorView, 2);
This will let us see the property grid (so things get a little more interesting).
So, we're able to display the workflow and interact with it, but we probably also want to have a constrained authoring experience (not just editing), so that comes in the form of the ToolboxControl. For the sake of this blog post, we'll use this in XAML, but we certainly can code against it imperatively as well.
<Window x:Class="BlogPostRehosting.Window1"
Title="Window1" Height="664" Width="831">
<sys:String x:Key="AssemblyName">System.Activities, Version=126.96.36.199, Culture=neutral, PublicKeyToken=31bf3856ad364e35</sys:String>
<Grid Name="grid1">
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="3*"/>
<ColumnDefinition Width="2*"/>
<sad:ToolboxCategoryItemsCollection CategoryName="Basic">
<sad:ToolboxItemWrapper AssemblyName="{StaticResource AssemblyName}" ToolName="System.Activities.Statements.Sequence"/>
<sad:ToolboxItemWrapper AssemblyName="{StaticResource AssemblyName}" ToolName="System.Activities.Statements.If"/>
<sad:ToolboxItemWrapper AssemblyName="{StaticResource AssemblyName}" ToolName="System.Activities.Statements.Parallel"/>
<sad:ToolboxItemWrapper AssemblyName="{StaticResource AssemblyName}" ToolName="System.Activities.Statements.WriteLine"/>
<sad:ToolboxItemWrapper AssemblyName="{StaticResource AssemblyName}" ToolName="System.Activities.Statements.Persist"/>
This lets me specify the items I want to allow a user to drop.
The thing that is interesting to point out here is that we've built a full featured, constrained editor (with things like copy paste, undo/redo, etc) with not too much code.
Next time, we'll get into to doing some more interesting bits as well to interact with the item being edited, serialize to XAML, and explore the editing context some more. Let me know what you think!
A lot of times people get stuck with the impression that there are only two workflow models available: sequential and state machine. True, out of the box these are the two that are built in, but only because there are is a set of common problems that map nicely into their execution semantics. As a result of these two being "in the box," I often see people doing a lot of very unnatural things in order to fit their problem into a certain model.
The drawing above illustrates the flow of one such pattern. In this case, the customer wanted parallel execution with two branches ((1,3) and (2,5)). But, they had an additional factor that played in here, 4 could execute, but it could only execute when both 1 and 2 had completed. 4 didn't need to wait for 3 and 5 to finish, 3 and 5 could take a long period of time, so 4 could at least start once 1 and 2 were completed. Before we dive into a more simple solution, let's look at some of the ways they tried to solve the problem, in an attempt to use "what's in the box."
The basic idea behind this approach is that we will use a parallel activity, and in the third branch we will place a while loop that loops on the condition of "if activity x is done" with a brief delay activity in there so that we are not busy polling. What's the downside to this approach:
WF V1 does have the idea of synchronizing some execution by using the SynchronizationScope activity. The basic idea behind the SynchronizationScope is that one can specify a set of handles that the activity must be able to acquire before allowing it's contained activities to execute. This let's us serialize access and execution. We could use this to mimic some of the behavior that the polling is doing above. We will use sigma(x, y, z) to indicate the synchronization scope and it's handles (just because I don't get to use nearly as many Greek letters as I used to).
This should work, provided the synchronization scopes can obtain the handles in the "correct" or "intended" order. Again, the downside here is that this gets to be pretty complex, how do we model 4 having a dependency on 3 and 2? Well, our first synchronization scope now needs to extend to cover the whole left branch, and then it should work. For the simple case like the process map I drew at the beginning, this will probably work, but as the dependency map gets deeper, we are going to run into more problems trying to make this work.
WF is intended to be a general purpose process engine, not just a sequential or state machine process engine. We can write our own process execution patterns by writing our own custom composite activity. Let's first describe what this needs to do:
Allow activities to be scheduled based on all of their dependent activities having executed.
So, how do we go about doing this?
In the future, this will be any activity using an attached property, but I want to start small and focus on the execution logic. This one is a simple Activity with a "Preconditions" array of strings where the strings will be the names of the activities which must execute first:
public partial class SampleWithPreconProperty: Activity
public SampleWithPreconProperty()
private string[] preconditions = new string[0];
public string[] Preconditions
get { return preconditions; }
set { preconditions = value; }
Let's first look at the declaration and the members:
public partial class PreConditionExecutor : CompositeActivity
// this is a dictionary of the executed activities to be indexed via
// activity name
private Dictionary<string, bool> executedActivities = new Dictionary<string, bool>();
// this is a dictionary of activities marked to execute (so we don't
// try to schedule the same activity twice)
private Dictionary<string, bool> markedToExecuteActivities = new Dictionary<string, bool>();
// dependency maps
// currently dictionary<string, list<string>> that can be read as
// activity x has dependencies in list a, b, c
// A more sophisticated implementation will use a graph object to track
// execution paths and be able to check for completeness, loops, all
// that fun graph theory stuff I haven't thought about in a while
private Dictionary<string, List<string>> dependencyMap = new Dictionary<string, List<string>>();
We have three dictionaries, one to track which have completed, one for which ones are scheduled for execution, and one to map the dependencies. As noted in the comments, a directed graph would be a better representation of this so that we could do some more sophisticated analysis on it.
Now, let's look at the Execute method, the one that does all the work.
protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
if (0 == Activities.Count)
return ActivityExecutionStatus.Closed;
// loop through the activities and mark those that have no preconditions
// as ok to execute and put those in the queue
// also generate the graph which will determine future activity execution.
foreach (Activity a in this.Activities)
// start with our basic activity
SampleWithPreconProperty preconActivity = a as SampleWithPreconProperty;
if (null == preconActivity)
throw new Exception("Not right now, we're not that fancy");
// construct the execution dictionary
executedActivities.Add(a.Name, false);
markedToExecuteActivities.Add(a.Name, false);
List<string> actDependencies = new List<string>();
if (null != preconActivity.Preconditions)
foreach (string s in preconActivity.Preconditions)
dependencyMap.Add(a.Name, actDependencies);
// now we have constructed our execution map and our dependency map
// let's do something with those, like find those activities with
// no dependencies and schedule those for execution.
foreach (Activity a in this.Activities)
if (0 == dependencyMap[a.Name].Count)
Activity executeThis = this.Activities[a.Name];
executeThis.Closed += currentlyExecutingActivity_Closed;
markedToExecuteActivities[a.Name] = true;
Console.WriteLine("Scheduled: {0}", a.Name);
return ActivityExecutionStatus.Executing;
Basically, we first construct the execution tracking dictionaries, initializing those to false. We then create the dictionary of dependencies. We then loop through the activities and see if there are any that have no dependencies (there has to be one, this would be a good point to raise an exception if there isn't. We record in the dictionary that this one has been marked to execute and then we schedule it for execution (after hooking the Closed event so that we can do some more work later). So what happens when we close?
void currentlyExecutingActivity_Closed(object sender, ActivityExecutionStatusChangedEventArgs e)
e.Activity.Closed -= this.currentlyExecutingActivity_Closed;
if (this.ExecutionStatus == ActivityExecutionStatus.Canceling)
ActivityExecutionContext context = sender as ActivityExecutionContext;
else if (this.ExecutionStatus == ActivityExecutionStatus.Executing)
// set the Executed Dictionary
executedActivities[e.Activity.Name] = true;
if (executedActivities.ContainsValue(false) /* keep going */)
// find all the activities that contain the precondition
// and remove them, and then cycle through any that have 0
// preconditions (and have not already executed or are marked
// to execute
// who contains this precondition?
foreach (Activity a in this.Activities)
// filter out those activities executed or executing
if (!(executedActivities[a.Name] || markedToExecuteActivities[a.Name]))
if (dependencyMap[a.Name].Contains(e.Activity.Name))
// we found it, remove it
// if we now have no dependencies, let's schedule it
if (0 == dependencyMap[a.Name].Count)
a.Closed += currentlyExecutingActivity_Closed;
ActivityExecutionContext context = sender as ActivityExecutionContext;
markedToExecuteActivities[a.Name] = true;
Console.WriteLine("Scheduled: {0}", a.Name);
else //close activity
ActivityExecutionContext context = sender as ActivityExecutionContext;
There are a few lines of code here, but it's pretty simple what's going on.
To actually use this activity, we place it in the workflow, place a number of the child activity types within it (again, with the attached property, you could put nearly any activity in there) and specify the activities that it depends on. Since I haven't put a designer on it, I just use the SequenceDesigner. Here's what it looks like (this is like the graph I drew above but kicks off with the "one" activity executing first:
I'm going to hold off on posting the code, as I've got a few of these and I'd like to come up with some way to put them out there that would make it easy to get to them and use them. You should be able to pretty easily construct your own activity based on the code presented here.
In this post I published an overview of the designer architecture. I'll copy the picture and the description of what I want to talk about today.
There are a few key components here Source In VS, this is xaml, but this represents the durable storage of the "thing" we are editing Instance This is the in memory representation of the item being edited. In vs2010 for the WF designer, this is a hierarchy of System.Activities instances (an object tree) Model Item Tree This serves as an intermediary between the view and the instance, and is responsible for change notification and tracking things like undo state Design View This is the visual editing view that is surfaced to the user, the designers are written using WPF, which uses plain old data binding in order to wire up to the underlying model items (which represent the data being edited). Metadata Store This is a mapping between type and designers, an attribute table for lack of a better term. This is how we know what designer to use for what type
There are a few key components here
One observation that you could make when looking at the diagram above is "I should just be able to bind the view to the instance." This approach could work, but has a couple of implementation problems:
These reasons cause us to think about an abstraction we can use to intermediate the implementation details of the instance and the view with a common layer. If you have programmed at the WPF designer extensibility level, you will likely be familiar with the idea (and some of the types) here.
The way that I think about the ModelItem/ModelProperty tree is that it forms a very thing proxy layer on top of the shape of the instance being edited.
Let's start with a very simple type:
public class Animal
// simple property
public string Name { get; set; }
// complex property
public Location Residence { get; set; }
// list
public List<Animal> CloseRelatives { get; set; }
// dictionary
public Dictionary<string, object> Features { get; set; }
public class Location
public string StreetAddress { get; set; }
public string City { get; set; }
public string State { get; set; }
Ignore for a moment that I just gave an animal Features, I'm a PM, it's how we think :-)
Now, let's create some instances of that, and then actually create a ModelItem.
1: EditingContext ec = new EditingContext();
2: var companion1 = new Animal { Name = "Houdini the parakeet" };
3: var companion2 = new Animal { Name = "Groucho the fish" };
4: var animal = new Animal
5: {
6: Name = "Sasha the pug",
7: Residence = new Location
8: {
9: StreetAddress = "123 Main Street",
10: City = "AnyTown",
11: State = "Washington"
12: },
13: Features = {
14: {"noise", "snort" },
15: {"MeanTimeUntilNaps", TimeSpan.FromMinutes(15) }
16: },
17: CloseRelatives = { companion1, companion2 }
18: };
19: ModelTreeManager mtm = new ModelTreeManager(ec); mtm.Load(animal);
20: ModelItem mi = mtm.Root;
One thing to note here is that I am using ModelTreeManager and EditingContext outside the context (no pun intended) of the designer (see lines 1, 19, and 20 in the above snippet). This isn't the usual way we interact with these, but it's for this sample so that we can focus just on the data structure itself. [as an aside, my brother did have a parakeet named Houdini].
Let's take a quick look at a visualization of what the data structure will look like. Remember to think about the ModelItem tree as a thin proxy to the shape of the instance.
Rather than spend an hour in powerpoint, I'll just include a sketch :-)
On the left, you see the object itself. For that object, there will be one ModelItem which "points" to that object. You can call ModelItem.GetCurrentValue() and that will return the actual object. If you look at the ModelItem type, you will see some interesting properties which describe the object.
Properties is the part where things get interesting. There is a collection of ModelProperty objects which correspond to the shape of the underlying objects. For the example above, let's break in the debugger and see what there is to see.
As we might expect, there are 4 properties, and you will see all sorts of properties that describe the properties. A few interesting ones:
As Value points to another model item, you can see how this begins to wrap the object, and how this can be used to program against the data model. Let's look at a little bit of code.
You might say "hey, that's a little ugly" and I have two bits of good news for you.
Here's a set of tests which show the different things we've talked about:
1: ModelItem root = mtm.Root;
2: Assert.IsTrue(root.GetCurrentValue() == animal, "GetCurrentValue() returns same object");
3: Assert.IsTrue(root.ItemType == typeof(Animal),"ItemType describes the item");
4: Assert.IsTrue(root.Parent == null,"root parent is null");
5: Assert.IsTrue(root.Source == null, "root source is null");
6: Assert.IsTrue(((List<Animal>)root.Properties["CloseRelatives"].ComputedValue)[0] == companion1,
7: "ComputedValue of prop == actual object");
8: Assert.IsFalse(((List<Animal>)root.Properties["CloseRelatives"].ComputedValue)[0] == companion2,
9: "ComputedValue of prop == actual object");
10: Assert.AreEqual(root.Properties["Residence"].
11: Value.
12: Properties["StreetAddress"].
13: Value.GetCurrentValue(), "123 Main Street", "get actual value back out");
14: Assert.AreEqual(root, root.Properties["Residence"].Parent, "property points to owner");
15: ModelItem location = root.Properties["Residence"].Value;
16: Assert.AreEqual(root.Properties["Residence"], location.Source, "sources point to the right place");
Good question, and the truth is that yes, this could get large as you were to spelunk the object graph. The good news is that we're incredibly lazy about loading this, we will only flush out the properties collection on demand, and we won't generate a ModelItem until it is requested. When we combine this with the View virtualization work we have done, we will only ever load as much in the WF designer as you need. This keeps the overhead minimal, and in does not represent a substantial memory overhead.
One might be tempted to just say "Hey, I'm in C#, I'll just call GetCurrentValue() and party on that. If you do that, you are entering dangerous waters where you can mess up the data model. Since the underlying instance doesn't likely support any change notification mechanism, the model item tree will get out of sync with the underlying instance description. This will manifest itself in problems at the designer because our serialization is based off the instance, not the ModelItem tree (note, that's a vs2010 implementation detail that could change in a subsequent release). The net result though is that you will get your view out of sync with your instance and serialization and that's generally considered problematic.
Wow, that's a longer post than I intended. What have we covered:
What haven't we covered yet
I'll get there. In the meantime, if you have questions, let me know.
**** Minor update on 10/29 to fix a bug in the code ****
This is the way to use ModelTreeManager to generate ModelItems (Line 3 is the critical piece that was missing):
2: ModelTreeManager mtm = new ModelTreeManager(ec);
3: mtm.Load(new Sequence());
4: mtm.Root.Properties["Activities"].Collection.Add(new WriteLine());
I've been meaning to throw together some thoughts on attached properties and how they can be used within the designer. Basically, you can think about attached properties as injecting some additional "stuff" onto an instance that you can use elsewhere in your code.
In the designer, we want to be able to have behavior and view tied to interesting aspects of the data. For instance, we would like to have a view updated when an item becomes selected. In WPF, we bind the style based on the "isSelectionProperty." Now, our data model doesn't have any idea of selection, it's something we'd like the view level to "inject" that idea on any model item so that a subsequent view could take advantage of. You can kind of view Attached Properties as a nice syntactic sugar to not have to keep a bunch of lookup lists around. As things like WPF bind to the object very well, and not so much a lookup list, this ends up being an interesting model.
To be clear, you could write a number of value converters that take the item being bound, look up in a lookup list somewhere, and return the result that will be used. The problem we found is that we were doing this in a bunch of places, and we really wanted to have clean binding statements inside our WPF XAML, rather than hiding a bunch of logic in the converters.
First, some types.
in diagram form:
One thing that might look a little funny to some folks who have used attached properties in other contexts (WF3, WPF, XAML), is the "IsBrowsable" property. The documentation is a little sparse right now, but what this will do is determine how discoverable the property is. If this is set to true, the attached property will show up in the Properties collection of the ModelItem to which the AP is attached. What this means is that it can show up in the Property grid, you can bind WPF statements directly to it, as if it were a real property of the object. Attached properties by themselves have no actual storage representation, so these exist as design time only constructs.
One other thing that you see on the AttachedProperty<T> is the Getter and Setter properties. These are of type Func<ModelItem,T> and Action<ModelItem,T> respectively. What these allow you to do is perform some type of computation whenever the get or set is called against the AttachedProperty. Why is this interesting? Well, let's say that you'd like to have a computed value retrieved, such as "IsPrimarySelection" checking with the Selection context item to see if an item is selected. Or, customizing the setter to either store the value somewhere more durable, or updating a few different values. The other thing that happens is that since all of these updates go through the ModelItem tree, any changes will be propagated to other listeners throughout the designer.
Here is a very small console based app that shows how you can program against the attached properties. An interesting exercise for the reader would be to take this data structure, put it in a WPF app and experiment with some of the data binding.
First, two types:
public class Dog
public string Name { get; set; }
public string Noise { get; set; }
public int Age { get; set; }
public class Cat
public string Name { get; set; }
public string Noise { get; set; }
public int Age { get; set; }
Ignore no common base type, that actually makes this a little more interesting, as we will see.
Now, let's write some code. First, let's initialize and EditingContext and ModelTreeManager.
1: static void Main(string[] args)
2: {
3: EditingContext ec = new EditingContext();
4: ModelTreeManager mtm = new ModelTreeManager(ec);
5: mtm.Load(new object[] { new Dog { Name = "Sasha", Noise = "Snort", Age = 5 },
6: new Cat { Name="higgs", Noise="boom", Age=1 } });
7: dynamic root = mtm.Root;
8: dynamic dog = root[0];
9: dynamic cat = root[1];
10: ModelItem dogMi = root[0] as ModelItem;
11: ModelItem catMi = root[1] as ModelItem;
Note, lines 7-9 will not work in Beta2 (preview of coming attractions). To get lines 10-11 working in beta2, cast root to ModelItemCollection and then use the indexers to extract the values
Now, let's build an attached property, and we will assign it only to type "dog"
1: // Add an attached Property
2: AttachedProperty<bool> ap = new AttachedProperty<bool>
3: {
4: IsBrowsable = true,
5: Name = "IsAnInterestingDog",
6: Getter = (mi => mi.Properties["Name"].ComputedValue.ToString() == "Sasha"),
7: OwnerType = typeof(Dog)
8: };
9: ec.Services.Publish<AttachedPropertiesService>(new AttachedPropertiesService());
10: AttachedPropertiesService aps = ec.Services.GetService<AttachedPropertiesService>();
11: aps.AddProperty(ap);
13: Console.WriteLine("---- Enumerate properties on dog (note new property)----");
14: dogMi.Properties.ToList().ForEach(mp => Console.WriteLine(" Property : {0}", mp.Name));
16: Console.WriteLine("---- Enumerate properties on cat (note no new property) ----");
17: catMi.Properties.ToList().ForEach(mp => Console.WriteLine(" Property : {0}", mp.Name));
Let's break down what happened here.
---- Enumerate properties on dog (note new property)----
Property : Name
Property : Noise
Property : Age
Property : IsAnInterestingDog
---- Enumerate properties on cat (note no new property) ----
Ok, so that's interesting, we've injected a new property, only on the dog type. If I got dogMI.Properties["IsAnInterestingDog"], I would have a value that I could manipulate (albeit returned via the getter).
Let's try something a little different:
1: AttachedProperty<bool> isYoungAnimal = new AttachedProperty<bool>
3: IsBrowsable = false,
4: Name = "IsYoungAnimal",
5: Getter = (mi => int.Parse(mi.Properties["Age"].ComputedValue.ToString()) < 2)
6: };
8: aps.AddProperty(isYoungAnimal);
10: // expect to not see isYoungAnimal show up
11: Console.WriteLine("---- Enumerate properties on dog (note isYoungAnimal doesn't appear )----");
12: dogMi.Properties.ToList().ForEach(mp => Console.WriteLine(" Property : {0}", mp.Name));
13: Console.WriteLine("---- Enumerate properties on cat (note isYoungAnimal doesn't appear )----");
14: catMi.Properties.ToList().ForEach(mp => Console.WriteLine(" Property : {0}", mp.Name));
16: Console.WriteLine("---- get attached property via GetValue ----");
17: Console.WriteLine("getting non browsable attached property on dog {0}", isYoungAnimal.GetValue(dogMi));
18: Console.WriteLine("getting non browsable attached property on cat {0}", isYoungAnimal.GetValue(catMi));
Let's break this down:
Let's see the output there:
---- Enumerate properties on dog (note isYoungAnimal doesn't appear )----
---- Enumerate properties on cat (note isYoungAnimal doesn't appear )----
---- get attached property via GetValue ----
getting non browsable attached property on dog False
getting non browsable attached property on cat True
As we can see, we've now injected this behavior, and we can extract the value.
Let's get a little more advanced and do something with the setter. Here, if isYoungAnimal is set to true, we will change the age (it's a bit contrived, but shows the dataflow on simple objects, we'll see in a minute a more interesting case).
1: // now, let's do something clever with the setter.
2: Console.WriteLine("---- let's use the setter to have some side effect ----");
3: isYoungAnimal.Setter = ((mi, val) => { if (val) { mi.Properties["Age"].SetValue(10); } });
4: isYoungAnimal.SetValue(cat, true);
5: Console.WriteLine("cat's age now {0}", cat.Age);
Pay attention to what the Setter does now. We create the method through which subsequent SetValue's will be pushed. Here's that output:
---- let's use the setter to have some side effect ----
cat's age now 10
Finally, let's show an example of how this can really function as some nice sugar to eliminate the need for a lot of value converters in WPF by using this capability as a way to store the relationship somewhere (rather than just using at a nice proxy to change a value):
1: // now, let's have a browesable one with a setter.
2: // this plus dynamics are a mini "macro language" against the model items
4: List<Object> FavoriteAnimals = new List<object>();
6: // we maintain state in FavoriteAnimals, and use the getter/setter func
7: // in order to query or edit that collection. Thus changes to an "instance"
8: // are tracked elsewhere.
9: AttachedProperty<bool> isFavoriteAnimal = new AttachedProperty<bool>
10: {
11: IsBrowsable = false,
12: Name = "IsFavoriteAnimal",
13: Getter = (mi => FavoriteAnimals.Contains(mi)),
14: Setter = ((mi, val) =>
16: if (val)
17: FavoriteAnimals.Add(mi);
18: else
19: {
20: FavoriteAnimals.Remove(mi);
21: }
22: })
23: };
26: aps.AddProperty(isFavoriteAnimal);
28: dog.IsFavoriteAnimal = true;
29: // remove that cat that isn't there
30: cat.IsFavoriteAnimal = false;
31: cat.IsFavoriteAnimal = true;
32: cat.IsFavoriteAnimal = false;
34: Console.WriteLine("Who are my favorite animal?");
35: FavoriteAnimals.ForEach(o => Console.WriteLine((o as ModelItem).Properties["Name"].ComputedValue.ToString()));
Little bit of code, let's break it down one last time:
-- Who are my favorite animals?
I will attach the whole code file at the bottom of this post, but this shows you how you can use the following:
Hopefully this post gave you some ideas about how the attached property mechanisms work within the WF4 designer. These give you a nice way to complement the data model and create nice bindable targets that your WPF Views can layer right on top of.
A few ideas for these things:
using System;
using System.Activities.Presentation;
using System.Activities.Presentation.Model;
using System.Collections.Generic;
using System.Linq;
namespace AttachedPropertiesBlogPosting
class Program
static void Main(string[] args)
EditingContext ec = new EditingContext();
ModelTreeManager mtm = new ModelTreeManager(ec);
mtm.Load(new object[] { new Dog { Name = "Sasha", Noise = "Snort", Age = 5 },
new Cat { Name="higgs", Noise="boom", Age=1 } });
dynamic root = mtm.Root;
dynamic dog = root[0];
dynamic cat = root[1];
ModelItem dogMi = root[0] as ModelItem;
ModelItem catMi = root[1] as ModelItem;
// Add an attached Property
AttachedProperty<bool> ap = new AttachedProperty<bool>
IsBrowsable = true,
Name = "IsAnInterestingDog",
Getter = (mi => mi.Properties["Name"].ComputedValue.ToString() == "Sasha"),
OwnerType = typeof(Dog)
ec.Services.Publish<AttachedPropertiesService>(new AttachedPropertiesService());
AttachedPropertiesService aps = ec.Services.GetService<AttachedPropertiesService>();
Console.WriteLine("---- Enumerate properties on dog (note new property)----");
dogMi.Properties.ToList().ForEach(mp => Console.WriteLine(" Property : {0}", mp.Name));
Console.WriteLine("---- Enumerate properties on cat (note no new property) ----");
catMi.Properties.ToList().ForEach(mp => Console.WriteLine(" Property : {0}", mp.Name));
AttachedProperty<bool> isYoungAnimal = new AttachedProperty<bool>
IsBrowsable = false,
Name = "IsYoungAnimal",
Getter = (mi => int.Parse(mi.Properties["Age"].ComputedValue.ToString()) < 2)
// expect to not see isYoungAnimal show up
Console.WriteLine("---- Enumerate properties on dog (note isYoungAnimal doesn't appear )----");
dogMi.Properties.ToList().ForEach(mp => Console.WriteLine(" Property : {0}", mp.Name));
Console.WriteLine("---- Enumerate properties on cat (note isYoungAnimal doesn't appear )----");
catMi.Properties.ToList().ForEach(mp => Console.WriteLine(" Property : {0}", mp.Name));
Console.WriteLine("---- get attached property via GetValue ----");
Console.WriteLine("getting non browsable attached property on dog {0}", isYoungAnimal.GetValue(dogMi));
Console.WriteLine("getting non browsable attached property on cat {0}", isYoungAnimal.GetValue(catMi));
// now, let's do something clever with the setter.
Console.WriteLine("---- let's use the setter to have some side effect ----");
isYoungAnimal.Setter = ((mi, val) => { if (val) { mi.Properties["Age"].SetValue(10); } });
isYoungAnimal.SetValue(cat, true);
Console.WriteLine("cat's age now {0}", cat.Age);
// now, let's have a browesable one with a setter.
// this plus dynamics are a mini "macro language" against the model items
List<Object> FavoriteAnimals = new List<object>();
// we maintain state in FavoriteAnimals, and use the getter/setter func
// in order to query or edit that collection. Thus changes to an "instance"
// are tracked elsewhere.
AttachedProperty<bool> isFavoriteAnimal = new AttachedProperty<bool>
IsBrowsable = false,
Name = "IsFavoriteAnimal",
Getter = (mi => FavoriteAnimals.Contains(mi)),
Setter = ((mi, val) =>
if (val)
dog.IsFavoriteAnimal = true;
// remove that cat that isn't there
cat.IsFavoriteAnimal = false;
cat.IsFavoriteAnimal = true;
cat.IsFavoriteAnimal = false;
Console.WriteLine("Who are my favorite animals?");
FavoriteAnimals.ForEach(o => Console.WriteLine((o as ModelItem).Properties["Name"].ComputedValue.ToString()));
public class Dog
public string Name { get; set; }
public string Noise { get; set; }
public int Age { get; set; }
public class Cat
public string Name { get; set; }
public string Noise { get; set; }
public int Age { get; set; }
image courtesy of flickr user chodhound
This post comes about after a little conversation on the forums where I was talking about using the xaml stack save and load objects.
Here's what I said:
Bob (it's a little late in Seattle, so I don't have a code editor handy, so there may be some minor errors below), If you want to serialize any object graph to xaml, simply look at the XamlServices.Save() api that's in System.Xaml. I'm sure there are a few SDK samples around that as well. It takes in a file name to output to, a text writer or a stream, so you get to pick your poison for destination. Similarly, if you want to get an object graph deserialized from Xaml, you can just use XamlServices.Load() again, with overloads for files, streams, text, xml, and xaml readers. To see this API, just do something likeSequence s = new Sequence { Activities = { new Persist(), new Persist() } };
XamlServices.Save(fileNameVar, s);
If you want to read, basically do the reverse. Save and Load are convinient helper functions to operate on the whole of the doc, there Xaml stack surfaces much more programmability and has a nice node stream style API that lets you plug in while nodes are being read and written. Now, if you want to deserialize a Xaml file that contains an x:Class directive, you are going to need to do a bit more work (and it depends what you want to serialize to or from). I'll try to blog about that in the next week or so.
Bob (it's a little late in Seattle, so I don't have a code editor handy, so there may be some minor errors below), If you want to serialize any object graph to xaml, simply look at the XamlServices.Save() api that's in System.Xaml. I'm sure there are a few SDK samples around that as well. It takes in a file name to output to, a text writer or a stream, so you get to pick your poison for destination. Similarly, if you want to get an object graph deserialized from Xaml, you can just use XamlServices.Load() again, with overloads for files, streams, text, xml, and xaml readers. To see this API, just do something like
Sequence s = new Sequence { Activities = { new Persist(), new Persist() } };
XamlServices.Save(fileNameVar, s);
If you want to read, basically do the reverse. Save and Load are convinient helper functions to operate on the whole of the doc, there Xaml stack surfaces much more programmability and has a nice node stream style API that lets you plug in while nodes are being read and written. Now, if you want to deserialize a Xaml file that contains an x:Class directive, you are going to need to do a bit more work (and it depends what you want to serialize to or from). I'll try to blog about that in the next week or so.
Now, I want to take a little bit of time to explain the last part.
A convenient way to think about XAML is a way to write down objects, really, instances of objects. That said I am not limited to writing down just instances, I can actually write down type definitions as well. I do this using the x:Class attribute.
Consider the following XAML snippet
<Activity x:Class="foo" ...
This is roughly equivalent to the following C#
public class foo : Activity
Now, "normally" what happens with XAML like this in .xaml files is that it is used in a VS project and it is set up so that there is a build task who has the job of generating a type from that XAML so that you can use that type subsequently in your application. This works basically the same way for WPF as well as WF.
However, if you think about trying simply deserialize this, it's a little confusing what this will actually deserialize to. This is a type definition, so if you simply try to pass it to XamlServices.Load(), you will encounter an exception:
Error System.Xaml.XamlObjectWriterException: No matching constructor found on type System.Activities.Activity. You can use the Arguments or FactoryMethod direct ives to construct this type. ---> System.MissingMethodException: No default constructor found for type System.Activities.Activity. You can use the Arguments or FactoryMethod directives to construct this type. at System.Xaml.Runtime.ClrObjectRuntime.DefaultCtorXamlActivator.EnsureConstructorDelegate(XamlType xamlType) at System.Xaml.Runtime.ClrObjectRuntime.DefaultCtorXamlActivator.CreateInstance(XamlType xamlType) at System.Xaml.Runtime.ClrObjectRuntime.CreateInstanceWithCtor(XamlType xamlType, Object[] args) at System.Xaml.Runtime.ClrObjectRuntime.CreateInstance(XamlType xamlType, Object[] args) --- End of inner exception stack trace ---
So, if we want to deserialize that, we need to ask the question first: "Why do we want to deserialize it?"
If we want to simply use the activity we've created and have it execute, we have a special type, DynamicActivity. DynamicActivity lets you execute the activity, and rather than creating a type for it, will allow you to pass in the arguments in the typical Dictionary<string,object> way that we are used to.
Imagine a xaml file, just sitting on disk somewhere that looks like this (a workflow that concats two strings):
<p:Activity mc:Ignorable=""
<x:Property Name="argument1" Type="p:InArgument(x:String)" />
<x:Property Name="argument2" Type="p:InArgument(x:String)" />

[argument1 + " " + argument2]

The code for this is the following:
object o = WorkflowXamlServices.Load(File.OpenRead("Sequence1.xaml"));
Console.WriteLine("Success {0}", o.GetType());
DynamicActivity da = o as DynamicActivity;da.Properties.ToList().ForEach(ap => Console.WriteLine("argument: {0}", ap.Name));
WorkflowInvoker.Invoke(da, new Dictionary<string, object> { { "argument1", "foo" }, { "argument2", "bar" } });
At design time, we have an even more interesting problem. Our designer is an instance editor, and, as such, it must always edit an instance of "something". In our case, we actually do some work in our design time xaml reader and writer to deserialize into a metatype, an instance of a type whose sole purpose in life is to describe the activity. In Beta1, this is called ActivitySchemaType. ActivitySchemaType simply models the type structure of an activity, complete with properties, etc If you want to construct an instance of an ActivitySchemaType in code, you can, and then you could use the DesignTimeXamlWriter in order to properly serialize out to an Activity x:class xaml file. The following code works on an ActivitySchemaType in memory and then serializes it:
XamlSchemaContext xsc = new XamlSchemaContext();
ActivitySchemaType ast = new ActivitySchemaType()
Name = "foo",
Members =
new Property { Name="argument1", Type = xsc.GetXamlType(typeof(InArgument<string>)) }
Body = new Sequence { Activities = { new Persist(), new Persist() } }
StringBuilder sb = new StringBuilder();
DesignTimeXamlWriter dtxw = new DesignTimeXamlWriter(
new StringWriter(sb),
xsc, "foo", "bar.rock");
XamlServices.Save(dtxw, ast);
Console.WriteLine("you wrote:");
ConsoleColor old = Console.ForegroundColor;
Console.ForegroundColor = ConsoleColor.DarkGray;
Console.ForegroundColor = old;
This is what the output looks like:
<p:Activity mc:Ignorable=""
<x:Property Name="argument1" Type="p:InArgument(x:String)" />

If you want to read in from this, you have to do a little bit of trickery with the designer as DesignTimeXamlReader is not a public type in beta1.
WorkflowDesigner wd = new WorkflowDesigner();
object obj = wd.Context.Services.GetService<ModelService>().Root.GetCurrentValue();
Console.WriteLine("object read type: {0}", obj.GetType());
ActivitySchemaType schemaType = obj as ActivitySchemaType;
Console.WriteLine("schema type name: {0}", schemaType.Name);
schemaType.Members.ToList().ForEach(p => Console.WriteLine("argument: {0}, type: {1}", p.Name, p.Type));
That wraps up our tour of the ways to read (and write) <Activity x:Class xaml
Here's the full text of the little program I put together to execute this, also make sure to drop a sequence1.xaml into your execution directory if you want this to not throw :-)
using System.Xaml;
using System.Activities.Statements;
using System.IO;
using System.Activities.Design.Xaml;
using System.Windows.Markup;
using System.Xml;
using System.Activities.Design;
using System.Activities.Design.Services;
namespace ConsoleApplication2
class Program
static void Main(string[] args)
bool doStuff = true;
Guid g = Guid.NewGuid();
while (doStuff)
Console.WriteLine("What do you want to do?");
Console.WriteLine(" read [x]:class xaml with XamlServices");
Console.WriteLine(" read x:class xaml with W[o]rkflowXamlServices");
Console.WriteLine(" [w]rite an ActivitySchemaType");
Console.WriteLine(" r[e]ad an ActivitySchemaType");
Console.WriteLine(" [q]uit");
char c = Console.ReadKey(true).KeyChar;
switch (c)
case 'w':
XamlSchemaContext xsc = new XamlSchemaContext();
ActivitySchemaType ast = new ActivitySchemaType()
Name = "foo",
Members =
new Property { Name="argument1", Type = xsc.GetXamlType(typeof(InArgument<string>)) }
Body = new Sequence { Activities = { new Persist(), new Persist() } }
StringBuilder sb = new StringBuilder();
DesignTimeXamlWriter dtxw = new DesignTimeXamlWriter(
new StringWriter(sb),
xsc, "foo", "bar.rock");
XamlServices.Save(dtxw, ast);
Console.WriteLine("you wrote:");
ConsoleColor old = Console.ForegroundColor;
Console.ForegroundColor = ConsoleColor.DarkGray;
Console.ForegroundColor = old;
case 'x':
object o = XamlServices.Load(File.OpenRead("Sequence1.xaml"));
Console.WriteLine("Success{0}", o.GetType());
catch (Exception ex)
Console.WriteLine("Error {0}", ex);
case 'o':
object o = WorkflowXamlServices.Load(File.OpenRead("Sequence1.xaml"));
Console.WriteLine("Success {0}", o.GetType());
DynamicActivity da = o as DynamicActivity;
da.Properties.ToList().ForEach(ap => Console.WriteLine("argument: {0}", ap.Name));
WorkflowInvoker.Invoke(da, new Dictionary<string, object> { { "argument1", "foo" }, { "argument2", "bar" } });
case 'e':
WorkflowDesigner wd = new WorkflowDesigner();
object obj = wd.Context.Services.GetService<ModelService>().Root.GetCurrentValue();
Console.WriteLine("object read type: {0}", obj.GetType());
ActivitySchemaType schemaType = obj as ActivitySchemaType;
Console.WriteLine("schema type name: {0}", schemaType.Name);
schemaType.Members.ToList().ForEach(p => Console.WriteLine("argument: {0}, type: {1}", p.Name, p.Type));
case 'q':
doStuff = false;
Console.WriteLine("All done");
private static object CreateWfObject()
return new Sequence { Activities = { new Persist(), new Persist() } };
In WF3, we allowed our customers to rehost the WF designer inside their own applications. This has many reasons, usually about monitoring a workflow or allowing an end user to customize a constrained workflow (visual construction of an approval process, for instance). This article became the gold standard for writing rehosted applications. As we were planning our work for the WF4 designer, this was certainly a scenario we considered, and one we wanted to make easier.
This post consists of a few parts
I'll go into more detail about these pieces and parts in future posts as well, but this is the mental model of the things I will be talking about as we go through the designer.
Stay tuned, part 2 will come tomorrow!
The second in my series of alternate execution patterns (part 1)
I recently worked with a customer who was implementing what I would call a "basic" human workflow system. It tracked approvals, rejections and managed things as they moved through a customizable process. It's easy to build workflows like this with an Approval activity, but they wanted to implement a pattern that's not directly supported out of the box. This pattern, which I have taken to calling "n of m", is also referred to as a "Canceling partial join for multiple instances" in the van der Aalst taxonomy.
The basic description of this pattern is that we start m concurrent actions, and when some subset of those, n, complete, we can move on in our process and cancel the other concurrent actions. A common scenario for this is where I want to send a document for approval to 5 people, and when 3 of them have approved it, I can move on. This comes up frequently in human or task-based workflows. There are a couple of "business" questions which have to be answered as well, the implementation can support any set of answers for this:
The first approach the customer took was to use the ConditionedActivityGroup (CAG). The CAG is probably one of the most sophisticated out of the box activities that we ship in WF today, and it does give you a lot of control. It also gives you the ability to set the Until condition which would allow us to specify the condition that the CAG could complete, and the others would be cancelled (see Using ConditionedActivityGroup)
What are pros and cons of this approach:
This led us to the decision to create a composite activity that would model this pattern of execution. Here are the steps we went through:
The first thing we needed was the approval activity. Since we know this is going to eventually have some complex logic, we decided to take the basic approach of inheriting from SequenceActivity and composing our approval activity out of other activities (sending email, waiting on notification, handling timeouts, etc.). We quickly mocked up this activity to have an "Approver" property, a property for a timeout (which will go away in the real version, but is useful to put some delays into the process. We also added some code activities which Console.WriteLine 'd some information out so we knew which one was executing. We can come back to this later and make it arbitrarily complex. We also added the cancel handler so that we can catch when this activity is canceled (and send out a disregard email, clean up the task list ,etc). Implementing ICompensatableActivity may also be a good idea so that we can play around with compensation if we want to (note, that we will only compensate the closed activities, not the ones marked as canceled).
Properties of the Approval Activity
Placing the Approval Activity inside our NofM activity.
Now that we have our approval activity, we need to determine how this new activity is going to execute. This will be the guide that we use to implement the execution behavior. There are a couple of steps this will follow
I say this is the easy part as this is documented in a number of places, including Bob and Dharma's book. The only trickery occurring here is that we need to clone the template activity, that is the approval activity that we placed inside this activity before we started working on it. This is a topic discussed in Nate's now defunct blog.
protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
// here's what we need to do.
// 1.> Schedule these for execution, subscribe to when they are complete
// 2.> When one completes, check if rejection, if so, barf
// 3.> If approve, increment the approval counter and compare to above
// 4.> If reroute, cancel the currently executing branches.
ActivityExecutionContextManager aecm = executionContext.ExecutionContextManager;
int i = 1;
foreach (string approver in Approvers)
// this will start each one up.
ActivityExecutionContext newContext = aecm.CreateExecutionContext(this.Activities[0]);
GetApproval ga = newContext.Activity as GetApproval;
ga.AssignedTo = approver;
// this is just here so we can get some delay and "long running ness" to the
// demo
ga.MyProperty = new TimeSpan(0, 0, 3 * i);
// I'm interested in what happens when this guy closes.
newContext.Activity.RegisterForStatusChange(Activity.ClosedEvent, this);
return ActivityExecutionStatus.Executing;
Code in the execute method
One thing that we're doing here is RegisterForStatusChange() This is a friendly little method that will allow me to register for a status change event (thus it is very well named). This is a property of Activity, and I can register for different activity events, like Activity.ClosedEvent or Activity.CancelingEvent. On my NofM activity, I implment IActivityEventListener of type ActivityExecutionStatusChangedEvent (check out this article as to what that does and why). This causes me to implement OnEvent which since it comes from a generic interface is now strongly typed to accept the right type of event arguments in. That's always a neat trick that causes me to be thankful for generics. That's going to lead us to the next part.
Now we're getting to the fun part of how we handle what happens when one of these approval activities return. For the sake of keeping this somewhat brief, I'm going to work off the assumption that a rejection does not adversely affect the outcome, it is simply one less person who will vote for approval. We can certainly get more sophisticated, but that is not the point of this post! ActivityExecutionStatusChangedEventArgs has a very nice Activity property which will return the Activity which is the one that caused the event. This let's us find out what happened, what the decision was, who it was assigned to, etc. I'm going to start by putting the code for my method in here and then we'll walk through the different pieces and parts.
public void OnEvent(object sender, ActivityExecutionStatusChangedEventArgs e)
ActivityExecutionContext context = sender as ActivityExecutionContext;
// I don't need to listen any more
e.Activity.UnregisterForStatusChange(Activity.ClosedEvent, this);
GetApproval ga = e.Activity as GetApproval;
Console.WriteLine("Now we have gotten the result from {0} with result {1}", ga.AssignedTo, ga.Result.ToString());
// here's where we can have some additional reasoning about why we quit
// this is where all the "rejected cancels everyone" logic could live.
if (ga.Result == TypeOfResult.Approved)
// close out the activity
if (!approvalsCompleted && (numApproved >= NumRequired))
// we are done!, we only need to cancel all executing activities once
approvalsCompleted = true;
foreach (Activity a in this.GetDynamicActivities(this.EnabledActivities[0]))
if (a.ExecutionStatus == ActivityExecutionStatus.Executing)
// are we really done with everything? we have to check so that all of the
// canceling activities have finished cancelling
if (numProcessed == numRequested)
Code from "OnEvent"
The steps here, in English
I placed the activity in a workflow, configured it with five approvers and set it for two to be required to move on. I also placed a code activity outputting "Ahhh, I'm done". I also put a Throw activity in there to raise an exception and cause compensation to occur to illustrate that only the two that completed are compensated for.
So, what did we do?
This part 6 of my 6 part series on the EditingContext.
I want to wrap up this series of posts by posting some code for an activity designer that functions more as a diagnostic tool, and will display all of the Items and services of the EditingContext within the designer. This will be useful from an investigation perspective, and hopefully as a diagnostic tool. We will use this to help us understand what are the services that are available out of the box in VS, as well as in a rehosted application.
We first need to create an empty activity to attach a designer to.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Activities;
using System.ComponentModel;
namespace blogEditingContext
public sealed class Diagnosticator : CodeActivity
// Define an activity input argument of type string
public InArgument<string> Text { get; set; }
// If your activity returns a value, derive from CodeActivity<TResult>
// and return the value from the Execute method.
protected override void Execute(CodeActivityContext context)
// Obtain the runtime value of the Text input argument
string text = context.GetValue(this.Text);
Now, let's create our designer. We could do fancy treeviews or object browser style UI's, but as this is a blog post, I want to provide you with the basics, and then let you figure out how that is most useful to you. So, we will just create a designer that writes out to debug output the relevant information.
<sap:ActivityDesigner x:Class="blogEditingContext.DiagnosticDesigner"
<Button Click="Button_Click">Debug.WriteLine Context Data</Button>
And now the code
using System.Diagnostics;
using System.Linq;
using System.Windows;
namespace blogEditingContext
// Interaction logic for DiagnosticDesigner.xaml
public partial class DiagnosticDesigner
public DiagnosticDesigner()
private void Button_Click(object sender, RoutedEventArgs e)
// the goal here is to output meaningful and useful information about
// the contents of the editing context here.
int level = Debug.IndentLevel;
Debug.WriteLine("Items in the EditingContext");
foreach (var item in Context.Items.OrderBy(x => x.ItemType.ToString()))
Debug.IndentLevel = level;
Debug.WriteLine("Services in the EditingContext");
foreach (var service in Context.Services.OrderBy(x => x.ToString()))
Let's break this down. The work here happens in the button click where we simply order by types' string representations and output them to the debug writer (a more robust implementation might use a trace writer that could be configured in the app, but for this purpose, this will be sufficient.
So, what output do we get?
We determine this by using the activity in a freshly opened WF project
This wraps up our series on the editing context. We've gone through the basics of why we need it, what we can do with it, and then we moved how to use it, from both the very simple to the very complex. We've finished with a diagnostic tool to help understand what all items I can bind to.
What's Next From Here?
A few ideas for the readers who have read all of these:
Thanks for now!
Frequent forum guest Notre posed this question to the forums the other day noting that the XAML being produced from ActivityXamlServices.CreateBuilderWriter() was slightly different than the XAML being output from WorkflowDesigner.Save(). The reason for this stems from the fact that WorkflowDesigner leverages an additional internal type (which derives from XamlXmlWriter) in order to attach the mc:Ignorable attribute.
From the source at MSDN:
The mc XML namespace prefix is the recommended prefix convention to use when mapping the XAML compatibility namespace http://schemas.openxmlformats.org/markup-compatibility/2006. Elements or attributes where the prefix portion of the element name are identified as mc:Ignorable will not raise errors when processed by a XAML processor. If that attribute could not be resolved to an underlying type or programming construct, then that element is ignored. Note however that ignored elements might still generate additional parsing errors for additional element requirements that are side effects of that element not being processed. For instance, a particular element content model might require exactly one child element, but if the specified child element was in an mc:Ignorable prefix, and the specified child element could not be resolved to a type, then the XAML processor might raise an error.
The mc XML namespace prefix is the recommended prefix convention to use when mapping the XAML compatibility namespace http://schemas.openxmlformats.org/markup-compatibility/2006.
Elements or attributes where the prefix portion of the element name are identified as mc:Ignorable will not raise errors when processed by a XAML processor. If that attribute could not be resolved to an underlying type or programming construct, then that element is ignored. Note however that ignored elements might still generate additional parsing errors for additional element requirements that are side effects of that element not being processed. For instance, a particular element content model might require exactly one child element, but if the specified child element was in an mc:Ignorable prefix, and the specified child element could not be resolved to a type, then the XAML processor might raise an error.
Basically, this lets a XAML reader gracefully ignore any content marked from that namespace if it cannot be resolved. So, imagine a runtime scenario where we don't want to load System.Activities.Presentation every time we read a WF XAML file that may contain viewstate. As a result, we use mc:Ignorable, which means the reader will skip that content when it does not have that assembly referenced at runtime.
This is what the output from the designer usually contains:
mva:VisualBasic.Settings="Assembly references and imported namespaces serialized as XML namespaces"
<scg:Dictionary x:TypeArguments="x:String, x:Object">
<x:Boolean x:Key="IsExpanded">True</x:Boolean>
<Persist sap:VirtualizedContainerService.HintSize="211,22" />
<Persist sap:VirtualizedContainerService.HintSize="211,22" />
<WriteLine sap:VirtualizedContainerService.HintSize="211,61" />
mc:Ignorable will cause the ViewState and HintSize to be ignored.
If you use WorkflowDesigner.Save(), you don't. If you want to be able to serialize the ActivityBuilder and have XAML which is what the designer produces, you need will need to add a XamlXmlWriter into the XamlWriter stack in order to get the right output. You may also worry about this if you are implementing your own storage and plan on writing some additional XAML readers or writers for additional extensibility and flexibility.
The code below describes the same approach you would need to take to implement an XamlXmlWriter that does the same thing our internal type does. While I can't copy and paste code, this does the same thing. We do two things:
What namespaces do we ignore in the designer? Just one: http://schemas.microsoft.com/netfx/2009/xaml/activities/presentation
namespace IgnorableXamlWriter
class IgnorableXamlXmlWriter : XamlXmlWriter
HashSet<NamespaceDeclaration> ignorableNamespaces = new HashSet<NamespaceDeclaration>();
HashSet<NamespaceDeclaration> allNamespaces = new HashSet<NamespaceDeclaration>();
bool objectWritten;
bool hasDesignNamespace;
string designNamespacePrefix;
public IgnorableXamlXmlWriter(TextWriter tw, XamlSchemaContext context)
: base(XmlWriter.Create(tw,
new XmlWriterSettings { Indent = true, OmitXmlDeclaration = true }),
new XamlXmlWriterSettings { AssumeValidInput = true })
public override void WriteNamespace(NamespaceDeclaration namespaceDeclaration)
if (!objectWritten)
// if we find one, add that to ignorable namespaces
// the goal here is to collect all of them that might point to this
// if you had a broader set of things to ignore, you would collect
// those here.
if (namespaceDeclaration.Namespace == "http://schemas.microsoft.com/netfx/2009/xaml/activities/presentation")
hasDesignNamespace = true;
designNamespacePrefix = namespaceDeclaration.Prefix;
public override void WriteStartObject(XamlType type)
// we should check if we should ignore
if (hasDesignNamespace)
// note this is not robust as mc could naturally occur
string mcAlias = "mc";
new NamespaceDeclaration(
XamlDirective ig = new XamlDirective(
objectWritten = true;
One note on the code above, it is noted that the generation of the namespace prefix "mc" is not robust. In the product code we will check to see if mc1, mc2, … are available up to mc1000. In that case we would then append a GUID for the ugliest XML namespace known to mankind. The chance of collision up to 1000 would be a highly extreme edge case.
How would I use this? The following code shows feeding this into a CreateBuilderWriter that is passed to XamlServices.Save()
StringBuilder sb = new StringBuilder();
XamlSchemaContext xsc = new XamlSchemaContext();
var bw = ActivityXamlServices.CreateBuilderWriter(
new IgnorableXamlXmlWriter(new StringWriter(sb), xsc));
In my last post, I covered using ActivityAction in order to provide a schematized callback, or hole, for the consumers of your activity to supply. What I didn't cover, and what I intend to here, is how to create a designer for that.
If you've been following along, or have written a few designers using WorkflowItemPresenter, you may have a good idea how we might go about solving this. There are a few gotcha's along the way that we'll cover as we go through this.
First, let's familiarize ourselves with the Timer example in the previous post:
namespace WorkflowActivitiesAndHost
public sealed class TimerWithAction : NativeActivity<TimeSpan>
public TimerWithAction()
So, let's build a designer for this. First we have to provide a WorkflowItemPresenter bound to the .Body property. This is pretty simple. Let's show the "simple" XAML that will let us easily drop something on the Body property
<sap:ActivityDesigner x:Class="actionDesigners.ActivityDesigner1"
HintText="Drop the body here"
Item="{Binding Path=ModelItem.Body, Mode=TwoWay}"/>
<Rectangle Width="80" Height="6" Fill="Black" Margin="10"/>
hmmm, Handler is an Activity, that looks kind of useful. Let's try that:
[warning, this XAML won't work, you will get an exception, this is by design :-) ]
<sap:ActivityDesigner x:Class="actionDesigners.ActivityDesigner1"
<sap:WorkflowItemPresenter HintText="Drop the body here" BorderBrush="Black" BorderThickness="2" Item="{Binding Path=ModelItem.Body, Mode=TwoWay}"/>
<Rectangle Width="80" Height="6" Fill="Black" Margin="10"/>

<sap:WorkflowItemPresenter HintText="Drop the completion here" BorderBrush="Black" BorderThickness="2" Item="{Binding Path=ModelItem.OnCompletion.Handler, Mode=TwoWay}"/>
While this gives us what we want visually, there is a problem with the second WorkflowItemPresenter (just try dropping something on it):
Now, if you look at the XAML after dropping, the activity you dropped is not present. What's happened here:
How can we fix this?
Well, we need to patch things up in the designer, so we will need to write a little bit of code, using the OnModelItemChanged event. This code is pretty simple, it just means that if something is assigned to ModelItem, if the value of "OnCompletion" is null, initialize it. If it is already set, we don't need to do anything (for instance, if you used an IActivityTemplateFactory to initialize). One important thing here (putting on the bold glasses) YOU MUST GIVE THE DELEGATEINARGUMENT A NAME. VB expressions require a named token to reference, so, please put a name in there (or bind it, more on that below).
using System;
using System.Activities;
namespace actionDesigners
// Interaction logic for ActivityDesigner1.xaml
public partial class ActivityDesigner1
public ActivityDesigner1()
protected override void OnModelItemChanged(object newItem)
if (this.ModelItem.Properties["OnCompletion"].Value == null)
new ActivityAction<TimeSpan>
Argument = new DelegateInArgument<TimeSpan>
Name = "duration"
Well, this works :-) Note that you can see the duration DelegateInArgument that was added.
Now, you might say something like the following "Gosh, I'd really like to not give it a name and have someone type that in" (this is what we do in our ForEach designer, for instance). In that case, you would need to create a text box bound to OnCompletion.Argument.Name, which is left as an exercise for the reader.
Alright, now you can get out there and build activities with ActivityActions, and have design time support for them!
One question brought up in the comments on the last post was "what if I want to not let everyone see this" which is sort of the "I want an expert mode" view. You have two options. Either build two different designers and have the right one associated via metadata (useful in rehosting), or you could build one activity designer that switches between basic and expert mode and only surfaces these in expert mode.
When I go out and I talk to partners and customers about WF there is a lot of interest in leveraging the rules capabilities. Whether they are looking to have declarative rules inside their workflows, or by executing complex policy against a set of custom objects in any .NET code, there's a lot about rules to like.
I'm working on a sample which uses Rules to validate a Sudoku board, namely from Sudoku sample on the community site.
Now, there are plenty of complicated ways to determine if a Sudoku configuration is valid (and plenty of other ways to solve the Sudoku [including Don Knuth's Dancing Links approach to solve the Exact Cover problem for Sudoku]). But I'm going to focus on a relatively simple, validate rows, validate columns and validate boxes approach.
I'm going to need to operate my rules across a collection, namely a 2-dimensional array of integers. So, how do we operate across a collection?
Searching in the SDK, which is full of fantastic information, leads us to the "Using Rule Conditions in Workflows" section. In there you'll find the "Collection Processing" section that outlines how you can process over collections. It goes a little something like this (in descending priority):
Now, I have an array. Sure, I can get the enumerator from this, but I'd like to use an integer which I update to navigate through this array. This is important to me because I need to know where I am at in the process (the 1st element or the 5th element) in order to convey some relevant information out to the user.
For an array, we've got a little simpler pattern that incorporates some shortcuts, we could certainly do the approach above, and if we're sure of our incoming variables (namely the integer we use to keep track of our current position), we can do it in one rule. The rule looks like this:
if (i < ItemCount) THEN total = total + items[i] i = i + 1 <== this is what causes the rule to then re-evaluate itself due to chaining
if (i < ItemCount)
total = total + items[i]
i = i + 1 <== this is what causes the rule to then re-evaluate itself due to chaining
We have an initial rule, with a priority of 2 set to initialize i to 0. But wait, you may say, why does my class need to have this iterator. The short story, it doesn't, but you have to have it somewhere, so what I have done is create a rule helper class that contains an instance of the class I am interested in executing the ruleset on, along with whatever "support" variables I need. So, we use the rules engine chaining capabilities in our second rule, with priority 1, to force the re-execution of the rule as many times as we need. And thus, we iterate over our simple array, the collection we all start from!
Ok, you may say, but I want a sample of this working when I truly have a collection of custom objects. Let's do that as well. There's one tricky thing to note here: The Intell-sense like interface sometimes makes it hard to consistently case stuff. I spent a while chasing down why my ruleset was only executing twice when I had 30 objects in the list. Consistently casing things made that work right. The problem that I had was that the final rule, was cased to the member variables, all lower case. The rule where I set the current item to the next one in the stack was cased to the property. When I changed this, because the symbol names didn't match up (the facts, if you will) the engine did not know to re-execute my final rule, which in turn did not call update to cause everything to re-evaluate. I also had some odd behavior with a typed IEnumerator (from System.Collection.Generics), but it appears now that things are working fine after discovering and correcting the cAsInG issues. So, like I said, pay attention to your casing, especially if you follow a variableName / VariableName naming scheme for C# private variables and properties.
Also, check out Moustafa's post on how you can use rule tracing to see what rules executed, this is what clued me in to what was and wasn't happening.
In the meantime, you can check out the sample that I have posted to the community site here.
Steps to get the application to work:
I look forward to your feedback!
Just got the email the other day that PDC is less than 4 weeks away, and it got me thinking a bit about how I would think about these sessions as an attendee. Searching on the PDC site will yield 8 talks tagged with WF. Here's how I break some of these these down, the first few are about using WF, and the last 3 are about WF itself:
Hosting Workflows and Services Dan Eshner Hear about extensions being made to Windows Server to provide a feature-rich middle-tier execution and deployment environment for Windows Workflow Foundation (WF) and Windows Communication Foundation (WCF) applications. Learn about the architecture of this new extension, how it works, how to take advantage of it, and the features it provides that simplify deployment, management, and troubleshooting of workflows and services.
Hosting Workflows and Services
Dan Eshner
Hear about extensions being made to Windows Server to provide a feature-rich middle-tier execution and deployment environment for Windows Workflow Foundation (WF) and Windows Communication Foundation (WCF) applications. Learn about the architecture of this new extension, how it works, how to take advantage of it, and the features it provides that simplify deployment, management, and troubleshooting of workflows and services.
This talk is all about the host we're building for WF and WCF, which I mentioned earlier, we're calling "Dublin". If you're familiar with either technology, and have built a host of your own, this will be interesting both from the perspective of what is coming, as well as how we are thinking about solving some of the hosting problems.
A Lap around "Oslo" Presenters: Douglas Purdy, Vijaye Raji "Oslo" is the family of new technologies that enable data-driven development and execution of services and applications. Come and learn how to capture all aspects of an application schematized in the "Oslo" repository and use "Oslo" directly to drive the execution of deployed applications.
A Lap around "Oslo"
Presenters: Douglas Purdy, Vijaye Raji
"Oslo" is the family of new technologies that enable data-driven development and execution of services and applications. Come and learn how to capture all aspects of an application schematized in the "Oslo" repository and use "Oslo" directly to drive the execution of deployed applications.
Building declarative or data driven apps is a "thing" in the Oslo world. This talk will give the big picture of all of the various pieces of Oslo, and how existing declarative technologies, like WF and WCF fit into it. Note, this talk is not primarily about WF or WCF, rather it is about Oslo, which you can read about in more detail here and here.
What about all of the things we are doing to WCF and WF in .NET 4? That's the remaining three talks:
Windows Workflow Foundation 4.0: A First Look Presenter: Kenny Wolf Programs coordinate work. The code for coordination and state management often obscures a program's purpose. Learn how programming with Windows Workflow Foundation (WF) 4.0 provides clarity of intent while preserving the functional richness of the .NET framework. See how easy it is to build workflows with the new Visual Studio workflow designer. Learn about text-based authoring options for WF. Hear how WF integrates well with other Microsoft technologies (WCF, WPF, ASP.NET). If you've looked at WF before, come and see the changes to data flow, composition, and new control flow styles. Significant improvements to usability, composability, and performance make Workflow a great fit for a broad range of solutions on both the client and the server. Windows Workflow Foundation 4.0: Extending with Custom Activities Presenter: Matt Winkler Windows Workflow Foundation (WF) coordinates and manages individual units of work, encapsulated into activities. WF comes with a rich library of activities. Learn how to extend this library by encapsulating your own APIs with custom activities. See how to compose those basic activities into higher level units using rules, flowchart, and state machine control flow styles. Learn how to build your own WF control styles. Learn how to customize and re-host the workflow authoring experience using the new WF designer framework. Windows Communication Foundation 4.0: Building WCF Services with Windows Workflow Foundation in Microsoft .NET 4.0 Presenter: Ed Pinto Eliminate the tradeoff between ease of service authoring and performant, scalable services. Hear about significant enhancements in WCF and WF to deal with the ever increasing complexity of communication. Learn how to use WCF to correlate messages to service instances using transport, context, and application payloads. See how the new WF messaging activities enable the modeling of rich protocols. Learn how WCF provides a default host for workflows exposing features such as distributed compensation and discovery. See how service definition in XAML completes the union of WF and WCF with a unified authoring experience that simplifies configuration and is fully integrated with IIS activation and deployment.
Windows Workflow Foundation 4.0: A First Look
Presenter: Kenny Wolf
Programs coordinate work. The code for coordination and state management often obscures a program's purpose. Learn how programming with Windows Workflow Foundation (WF) 4.0 provides clarity of intent while preserving the functional richness of the .NET framework. See how easy it is to build workflows with the new Visual Studio workflow designer. Learn about text-based authoring options for WF. Hear how WF integrates well with other Microsoft technologies (WCF, WPF, ASP.NET). If you've looked at WF before, come and see the changes to data flow, composition, and new control flow styles. Significant improvements to usability, composability, and performance make Workflow a great fit for a broad range of solutions on both the client and the server.
Windows Workflow Foundation 4.0: Extending with Custom Activities
Presenter: Matt Winkler
Windows Workflow Foundation (WF) coordinates and manages individual units of work, encapsulated into activities. WF comes with a rich library of activities. Learn how to extend this library by encapsulating your own APIs with custom activities. See how to compose those basic activities into higher level units using rules, flowchart, and state machine control flow styles. Learn how to build your own WF control styles. Learn how to customize and re-host the workflow authoring experience using the new WF designer framework.
Windows Communication Foundation 4.0: Building WCF Services with Windows Workflow Foundation in Microsoft .NET 4.0
Presenter: Ed Pinto
Eliminate the tradeoff between ease of service authoring and performant, scalable services. Hear about significant enhancements in WCF and WF to deal with the ever increasing complexity of communication. Learn how to use WCF to correlate messages to service instances using transport, context, and application payloads. See how the new WF messaging activities enable the modeling of rich protocols. Learn how WCF provides a default host for workflows exposing features such as distributed compensation and discovery. See how service definition in XAML completes the union of WF and WCF with a unified authoring experience that simplifies configuration and is fully integrated with IIS activation and deployment.
Kenny's talk will be an introduction to all of the new features in WF. If you haven't used WF, or if you looked at WF before and decided it wasn't right for your solution, come to this talk to see how WF makes writing programs easier. If you are using WF today, and want to see what has changed, this will be a good talk for you.
My talk will be a very hands on, write some code, style talk focused on building activities and all of the aspects of WF that impact activity development. If you are using WF today, and want to see what the changes mean for the code you'll write, this is the talk for you. Also, if you attend Kenny's talk and think, "hey, I want to learn more" this will also be the talk for you. My talk won't focus on the "why" or the "where" of workflow, but more the "how to build" parts.
Finally, Ed's talk is the talk to go to if you are a WCF developer. If you are building programs that consume services, where service is very loosely defined integrating external information into your app, you should also make sure to go to this talk. This talk will highlight a number of the enhancements that have been made both to WCF and to the integration between WF and WCF. We believe very strongly that WF and WCF are tremendously complementary technologies.
To help, I've put together the following decision table to help you decide. There are three possible actions, "Must Attend" "Should attend" and "Would Enjoy". I think they are fairly explanatory actions, but if you have questions, let me know.
Can't wait to see you in LA!
Usual msblog disclaimer applies, this represents my opinion!
While I was on break, a number of folks pinged me asking me about this blog post by Tad Anderson.
I find the investment in time to learn how to use 3.0/3.5 has been a complete waste time. So we have release 1.0 and 1.5 of WWF becoming obsolete in favor of version 2.0. These are the real release numbers on these libraries, and that is how they should have been labeled. They are not release 3.0 and 3.5.
I find the investment in time to learn how to use 3.0/3.5 has been a complete waste time. So we have release 1.0 and 1.5 of WWF becoming obsolete in favor of version 2.0. These are the real release numbers on these libraries, and that is how they should have been labeled. They are not release 3.0 and 3.5.
First, your investment in the existing technologies is not a "waste of time." The idea of modeling your app logic declaratively, via workflows doesn't change, nor do the ideas surrounding how one builds an application with workflows. What we are fixing is that we are making it substantially easier to use, and enabling more advanced scenarios (like implicit message correlation). What you will not be able to re-use is some of the things you did the first time and thought, "hmmm, I wonder why I have to do that [activity execution context cloning, handleExternalEvent, I'm looking at you]." From a designer perspective, your not going to have to keep remembering the quirks of the v1 designer. I think about this similarly to the way we went from ASMX web services to WCF. The API's changed, but the underlying thinking of building an app on services did not. Regarding version numbers, all of our libraries are versioned to the version of the framework we ship with (see WPF, WCF, etc). Internally we struggled with what we call the thing we're working on now and decided to stick with framework version (so WF 4.0, rather than WF 2.0).
Secondly, it's important to note, we're not getting rid of the 3.0, 3.5 technologies. We're investing to port them to the new CLR, and work to make the designers operate in VS 2010. If you get sufficient return by using WF in your apps today, use WF today. If WF doesn't meet your needs today, and if we're fixing that by something that we're doing in 4.0, then it makes sense to wait. Note, I'm not defining "return" for you. Depending upon how you define that, you may reach a different conclusion that someone in a similar setting.
Thirdly, activities you write today on 3.0/3.5 will continue to work, even inside a 4.0 workflow by way of the interop activity. Much as WPF has the ability to host existing WinForms content, we have the ability to execute 3.0-based activities.
There is a larger issue of how we (the big, Microsoft "we") handle a combination of innovation, existing application compatibility, and packaging of features. I'm not sure how we avoid the fact that inevitably, any framework in version n+1 will introduce new features, some of which will not be compatible with framework version n, some of which may do similar things to features in framework version n. Folks didn't stop writing WinFroms apps when WPF was announced (they still write WinForms apps). As I mentioned, this is a big issue, but not one I intend to tackle in this post :-)
The feedback we got from customers around WF was centered around the need for a few things:
Most of these would require changes to the existing code base, and breaking changes would become unavoidable. The combination of doing all of these things makes the idea of breaking all existing customers absolutely untenable. We're doing the work to make sure that your WF apps you write today will keep on working, and with services as the mechanisms to communicate between them, one can gradually introduce 4.0 apps as well. Given the commitment we have to our v1 (or netfx3) customers, we don't want to introduce those kinds of breaking changes.
Kathleen's article summarizes this very nicely, and rather than be accused of cherry-picking quotes, I encourage you to read the whole article.
Questions, comments, violent flames, post 'em here or mwinkle_at_largeredmondbasedsoftwarecompany_dot_com
I want to briefly touch on the editing context and give a little introduction to its capabilities. This is part 1 of a 6 part series
The way to think about the editing context is that it is the point of contact between the hosting application, and the designer (and elements on the designer). In my PDC talk, I had the following slide which I think captures the way to think about how these elements are layered together.
The editing context represents the a common boundary between the hosting application and the designer, and the mechanism to handle interaction with the designer (outside of the most common methods that have been promoted on WorkflowDesigner). If you were to look at the implementation of some of the more common methods on WorkflowDesigner, you would see that almost all of these use the editing context in order to get anything done. For instance, the Flush method (and Save which calls Flush) will acquire an IDocumentPersistenceService from the Services collection, and then use that in order to properly serialize the document.
The EditingContext type has two important properties
The Items collection is for data that is shared between the host and the designer, or data that is available to all designers. These need to derive from ContextItem which will provide the mechanism to hook into subscription and change notification. There are a couple of interesting methods on the ContextItemManager type :
Services represent functionality that is either provided by the host for the designer to use, or is used by the designer to make functionality available to all designers within the editor. Generally, these are likely defined as interfaces, but can also be a type. It is then required for an implementation or an instance to be provided. This instance will be shared as a singleton. There are a few interesting methods on the ServiceManager type:
We'll start walking through these over the next few posts.
Dennis has put together a nice sample showing how to host a workflow inside of a Windows Service, a common request for people looking to host long running processes. Check out his posting here. This sample is also available on the community site here.
We're working on some other samples around hosting, so stay tuned for details on those!
This kicks off a set of posts where I'll be discussing some of the interesting features coming out in Orcas.
I want to focus on this post on the Receive Activity, and a nice little feature in the designer that lets you create a contract on the fly, without having to drop into code and write a decorated interface. This allows us to divide the world into two approaches:
This is what most WCF folks will be familiar with:
[ServiceContract]public interface IOrderProcessing{ [OperationContract] bool SubmitOrder(Order order); [OperationContract] Order[] GetOrders(int customerId);}
When I drop a receive activity onto a workflow, I can now import this contract:
This will bring up a type chooser that lets me pick my service contract:
This imports all of the details and we can see the the operation picker
If we look at the activity properties we now see the parameters to the method. The (ReturnValue) is the object that we need to return in the operation. The order parameter is the message that is going to be passed when the method is called. I can take that and bind that to values in my workflow or whatever I want to do with it.
The other approach we can take is to create the contract as we create the workflow. That's right, we don't need to create the contract explicitly in code. To do that, drop a receive activity onto the designer and double click. Instead of selecting "Import Contract" select "Add Contract". This will create a new contract with a basic operation. By selecting the contract or the operation we can name it something a little nicer.
By selecting the operation, I can customize all of its behavior. I can create parameters to be passed in, I can set the types of those parameters (as well as the return type of the operation).
It's relevent to point out that I can select any type that would be valid in a WCF contract. The drop down list displays the basic types, but by selecting "Browse Type" I am brought into a type picker where I can select custom types. As you can see below I have created a "CancelOrder" operation that takes in an order, the reason and who authorized the cancellation.
When I click ok, my activity has had new dependency properties added to it, as can be seen in the property grid for the activity.
In the workflow I created, I used a code separation workflow, so I have an .xoml file which contains the workflow definition. Let's take a quick peek at how the receive activity is defined (note, some of the xml is truncated, if you view in an rss reader or copy and past you can see all the details, I'll work on updating the blog layout):
<ns0:ReceiveActivity x:Name="receiveActivity2"> <ns0:ReceiveActivity.ServiceOperationInfo> <ns0:OperationInfo PrincipalPermissionRole="administrators" Name="CancelOrder" ContractName="MyContract"> <ns0:OperationInfo.Parameters> <ns0:OperationParameterInfo Attributes="Out, Retval" ParameterType="{x:Type p9:Boolean}" Name="(ReturnValue)" Position="-1" xmlns:p9="clr-namespace:System;Assembly=mscorlib, Version=188.8.131.52, Culture=neutral, PublicKeyToken=b77a5c561934e089" /> <ns0:OperationParameterInfo Attributes="In" ParameterType="{x:Type p9:Order}" Name="order" Position="0" xmlns:p9="clr-namespace:WcfServiceLibrary1;Assembly=WcfServiceLibrary1, Version=22.214.171.124, Culture=neutral, PublicKeyToken=null" /> <ns0:OperationParameterInfo Attributes="In" ParameterType="{x:Type p9:String}" Name="reason" Position="1" xmlns:p9="clr-namespace:System;Assembly=mscorlib, Version=22.214.171.124, Culture=neutral, PublicKeyToken=b77a5c561934e089" /> <ns0:OperationParameterInfo Attributes="In" ParameterType="{x:Type p9:String}" Name="authorizedBy" Position="2" xmlns:p9="clr-namespace:System;Assembly=mscorlib, Version=22.214.171.124, Culture=neutral, PublicKeyToken=b77a5c561934e089" /> </ns0:OperationInfo.Parameters> </ns0:OperationInfo> </ns0:ReceiveActivity.ServiceOperationInfo> </ns0:ReceiveActivity>
Here you can see that within the metadata of the activity, we have the definition for the contract and the operation that I defined. When we spin up a WorkflowServiceHost to host this workflow as a service, the host will inspect the metadata for the workflow, looking for all of the endpoints and will create them. You can also see within the OperationInfo element that I am able to define the PrincipalPermissionRole defining the role of allowed callers, taking advantage of the static security checks which I will talk about in another post. So, defined declaratively in the XAML is the contract for the operations. I didn't need to write the interface or the contract explicitly, I was able to write a workflow, specifiy in the workflow how it will communicate and then let the WorkflowServiceHost figure out the nitty gritty details of how create the endpoints. The other part here that is important to mention is that the config will play a part determining what the transport channel and other such details will be. Within the config, when we set up an endpoint we need to specify the contract is "MyContract", or the name that we assigned when we created it.
We talked about the way that we can implement contracts that already exist in a receive activity, as well as how we can use the designer to actually create our contract while we are designing the workflow. The WorkflowServiceHost does the heavy lifting here in order to enable this little bit of nifty-ness.
Every now and then, there's a really cool feature that's buried somewhere that just hits you and makes you say "Wow, that's insanely helpful, why didn't somebody think of this sooner."
I was playing around with the BizTalk Services SDK, specifically the different web programming bits and pieces and stumbled up on the Paste As Serializable visual studio add in (navigate to BizTalk Services SDK\Samples\Web\Tooling).
Let's say you're interested in programming against some services that return normal, plain XML. Well, nobody likes writing code to query XML, Linq to XML is fun, but I'd just really like to mess around with some objects and not really deal with how it looks over the wire or under the hood.
This great little utility lets you select some snippet of XML (say I get it from here, because I am working on a Twitter mashup). Copy the XML to the clipboard (here's a snippet of what the XML looks like).
<created_at>Wed May 02 16:54:45 +0000 2007</created_at>
<text>has advil & beineigts in the back of Steve and Don's talk in 4101B. Anybody needing either is welcome to some, the pastries will go fast.</text>
<name>Matt Winkler</name>
<description>workflow technical evangelist</description>
Go into Visual Studio into a code file and open up the edit menu.
There, clearly indicated by the "this is just an alpha icon," is the option to "Paste XML as Serializable Type". Here's what it outputs:
[System.Xml.Serialization.XmlRootAttribute(Namespace = "", ElementName = "statuses")]
public class StatusesClass
private System.Collections.Generic.List<StatusClass> statusField;
[System.Xml.Serialization.XmlElementAttribute(Namespace = "", ElementName = "status")]
public virtual System.Collections.Generic.List<StatusClass> Status
return this.statusField;
this.statusField = value;
[System.Xml.Serialization.XmlRootAttribute(Namespace = "", ElementName = "status")]
public class StatusClass
private string created_atField;
private string idField;
private string textField;
private UserClass userField;
[System.Xml.Serialization.XmlElementAttribute(Namespace = "", ElementName = "created_at")]
public virtual string Created_at
return this.created_atField;
this.created_atField = value;
Remainder of code truncated for the sake of the readers.
This lets me do some cool stuff like this:
static void Main(string[] args)
WebHttpClient twc = new WebHttpClient();
twc.UriTemplate = "http://twitter.com/statuses/user_timeline/mwinkle.xml";
StatusesClass sc = twc.Get().GetBody<StatusesClass>();
Console.WriteLine("Got {0}", sc.Status.Count);
And get my result back in a nice typed object that I can then use elsewhere in my code (or at least debug)
Steve, thanks, this thing rocks!
So, last week I wrapped up a conversation at TechReady, our internal conference, where I was talking about the integration between WF and WCF in .NET 3.5. This talk was somewhat bittersweet, it's the last conference where I'm scheduled to talk about WF 3.0/3.5, I'll start talking about WF 4.0 at PDC this fall.
There are a series of 4 demos that we'll talk about in this series:
I've gotten a lot of requests to post the code samples, so I want to do that here:
The goal of this sample is to show the way that the context channel works, and how to interact with it from imperative code.
1: public String returnValue = default(System.String);
2: public String inputMessage = default(System.String);
4: private void codeActivity1_ExecuteCode(object sender, EventArgs e)
5: {
6: returnValue = string.Format("first activity {0}", inputMessage);
7: Output(inputMessage + " Activity 1");
10: private void Output(string message)
11: {
12: Console.WriteLine("Workflow {0} : Message {1}", this.WorkflowInstanceId, message);
13: }
15: private void codeActivity2_ExecuteCode(object sender, EventArgs e)
16: {
17: returnValue = string.Format("second activity {0}", inputMessage);
18: Output(inputMessage + " Activity 2");
19: }
class IWorkflowClient : ClientBase<Intro1.IWorkflow1>, Intro1.IWorkflow1
public IWorkflowClient() : base() { }
public IWorkflowClient(Binding binding, EndpointAddress address) : base(binding, address) { }
public string Hello(string message)
return base.Channel.Hello(message);
private static void CheckAndPrintContext(IContextManager icm)
if (null != icm) Console.WriteLine("Context contains {0} elements", icm.GetContext().Count);
if (null != icm)
if (icm.GetContext().Count > 0)
foreach (string xmlName in icm.GetContext().Keys)
Console.WriteLine("key : {0}", xmlName);
Console.WriteLine("value : {0}", icm.GetContext()[xmlName]);
1: private static void DemoOne()
3: Console.WriteLine("Press Enter to Send a Message and reuse proxy");
4: // Console.ReadLine();
5: Debugger.Break();
6: IWorkflowClient iwc = new IWorkflowClient(new NetTcpContextBinding(),
7: new EndpointAddress("net.tcp://localhost:10001/Intro1"));
8: IContextManager icm = iwc.InnerChannel.GetProperty<IContextManager>();
9: if (null != icm) Console.WriteLine("Context contains {0} elements", icm.GetContext().Count);
10: string s = iwc.Hello("message1");
11: Console.WriteLine("the service returned the message '{0}'", s);
12: CheckAndPrintContext(icm);
13: s = iwc.Hello("message2");
14: icm = iwc.InnerChannel.GetProperty<IContextManager>();
15: CheckAndPrintContext(icm);
16: Console.WriteLine("the service returned the message '{0}'", s);
17: Console.WriteLine("Press Enter to Continue");
18: }
1: // show this not working using a second client
2: private static void DemoTwo()
4: Console.WriteLine("Press Enter to Send a Message (it will break this time)");
5: //Console.ReadLine();
6: Debugger.Break();
7: IWorkflowClient iwc = new IWorkflowClient(new NetTcpContextBinding(),
8: new EndpointAddress("net.tcp://localhost:10001/Intro1"));
9: IContextManager icm = iwc.InnerChannel.GetProperty<IContextManager>();
10: if (null != icm) Console.WriteLine("Context contains {0} elements", icm.GetContext().Count);
11: string s = iwc.Hello("message1");
12: Console.WriteLine("the service returned the message '{0}'", s);
13: CheckAndPrintContext(icm);
14: iwc = new IWorkflowClient(new NetTcpContextBinding(),
15: new EndpointAddress("net.tcp://localhost:10001/Intro1"));
16: s = iwc.Hello("message2");
17: Console.WriteLine("the service returned the message '{0}'", s);
18: icm = iwc.InnerChannel.GetProperty<IContextManager>();
19: CheckAndPrintContext(icm);
20: Console.WriteLine("Press Enter to Continue");
21: }
1: // show this working with a second client by caching the context
2: private static void DemoThree()
4: Console.WriteLine("Press Enter to Send a Message (we'll cache the context and apply it to the new proxy)");
5: // Console.ReadLine();
14: IDictionary<string, string> context = icm.GetContext();
15: icm = null;
16: iwc = new IWorkflowClient(new NetTcpContextBinding(),
17: new EndpointAddress("net.tcp://localhost:10001/Intro1"));
19: icm.SetContext(context);
20: s = iwc.Hello("message2");
21: Console.WriteLine("the service returned the message '{0}'", s);
22: icm = iwc.InnerChannel.GetProperty<IContextManager>();
23: CheckAndPrintContext(icm);
24: Console.WriteLine("Press Enter to Exit");
26: }
So, what have we shown:
Steve Martin, a director of product management for CSD, has a blog post containing more information on the work that we are doing for the next versions of WF and WCF that we will release as a CTP at PDC. He also introduces "Dublin," the name for our efforts around creating a manageable and scalable host for WF and WCF applications, something that I know a few customers would be interested in.
For you WF and WCF fans, there some more information about some of the features that you'll hear more about at PDC. I think for customers who are using either today, you'll see something on the list below that gets you interested. And, if you're not using WF or WCF today, I think there are a few things that might make you interested. We think that the features we're introducing (especially in WF, which is close to my heart) will make it easier to use WF, in more places, and by more people. Let us know what you think. What's exciting in the list below, what do you want to hear more about, is there something else you'd like to see on the list?
Significant improvements in performance and scalability
· Ten-fold improvement in performance
New workflow flow-control models and pre-built activities
· Flowcharts, rules
· Expanded built-in activities – PowerShell, database, messaging, etc.
Enhancements in workflow modeling
· Persistence control, transaction flow, compensation support, data binding and scoping
· Rules composable and seamlessly integrated with workflow engine
Updated visual designer
· Easier to use by end-users
· Easier to rehost by ISVs
Ability to debug XAML
RESTful enhancements
· Simplifying the building of REST Singleton & Collection Services, ATOM Feed and Publishing Protocol Services, and HTTP Plain XML Services using WCF
· WCF REST Starter Kit to be released on Codeplex to get early feedback
Messaging enhancements
· Transports - UDP, MQ, Local in-process
· Protocols - SOAP over UDP, WS-Discovery, WS-BusinessActivity, WS-I BP 1.2
· Duplex durable messaging
Correlation enhancements
· Content and context driven, One-way support
Declarative Workflow Services
· Seamless integration between WF and WCF and unified XAML model
· Build entire application in XAML, from presentation to data to services to workflow
So, you might have heard that Beta2 has shipped. I wanted to give a brief overview of the changes that have been made to the designer since beta1.
So, without further ado, here are my top 10 new designer features in Beta 2
We made a decision in Beta2 to do a bit of an overhaul on the designers general theme and feel in order to feel more like part of VS. The UI we had in Beta1 was still the first pass at the designer surface that we showed last year in the first CTP at PDC. Here are some screenshots of that new UI. Our UX PM, Cathy, has started a blog, and I expect she will have some really interesting discussions about the design decisions that we made.
We've rounded out the set of icons as well
We've also created focus around the header, and surface information within that header (like validation errors)
One of the biggest pieces of feedback that we got from the Beta1 release was the need to see what's going on in my workflow. The model we had in Beta1 was optimized around a few levels of composition, and required the user to double-click to drill into the activity. This model works well to focus on an activity, but comes at the expense of the broader view. We've gone ahead and changed that in beta2 that allows the workflow items to expand in place.
I am jazzed that we did this, and there is no good way to put it in a picture. Open up the designer and start playing around, and I think you'll like what you'll see. The other nice part of this is that I can still choose to drill into if I want to have that focus. This should also support fairly deep nesting of activities, meaning you can visualize large, complex workflows on the canvas.
Additionally, you can choose to expand or collapse individual nodes, or go to the "Expand All" command in order to expand out the tree. One thing to note is that we don't allow expansion inside of a flowchart because we don't yet have a mechanism that lets me rearrange all of the items around that newly expanded activity.
If you have an assembly on disk somewhere that contains some activities, you can now add it into the toolbox through the Choose Items dialog.
When you drop these new items on the canvas, the appropriate reference should be wired up for you.
One thing that we heard from folks is that they like the vb expressions (well, they like the expressions, they aren't super excited that we only have one language choice at this point), but they really didn't like fully qualifying the type names. To help out with this, we've introduced the Imports designer which lets you pick out the namespaces you want to import that will be used to make it easier to resolve types.
Go to the Imports designer down at the bottom (next to Arguments and Variables) and add System.Xml
This will now bring in System.Xml into the expression text box:
People seem to like the flowchart model, but one request we heard was that it was tough to see what all the lines meant, especially coming out of the branching constructs. I'll show a few other interesting flowchart things here.
First, hovering over a switch/decision will show the expression
Clicking on the small triangle will "pin" this expression so that I can see it while working on the flowchart. Now, hook up some of the lines:
We also heard that the labels True and False are descriptive for the decision, they may not capture the intent. So, you can click on the decision, open the property grid and set these to some custom value.
Switch will display the switch values as well.
Towards the end of Beta1, we slipped a little feature in called IActivityTemplateFactory as an internal API to generate a configured set of activities (like what happens when I drop the messaging activity pairs). We found a number of folks really liked this idea, so we have made this public. This lets you add these factories to the toolbox to drop a templatized, configured set of activities onto the canvas. A real simple one would look like [this drops a Pick with two branches, like what happens today when you drop a Pick]:
1: public sealed class PickWithTwoBranchesFactory : IActivityTemplateFactory
3: public Activity Create(DependencyObject target)
4: {
5: return new Pick
6: {
7: Branches =
8: {
9: new PickBranch
10: {
11: DisplayName = "Branch1"
12: },
13: new PickBranch
14: {
15: DisplayName = "Branch2"
16: }
17: }
18: };
19: }
20: }
We've made some OM changes (there have been some I'll blog about later). One important one is that we needed to move out of the *.Design namespaces, primarily because *.Design is meant to specifically imply VS designer specific stuff, and our designer stuff is not VS specific. So, we've moved from .Design to .Presentation as our namespace and assembly names of choice. There has also been some reduction in the number of namespaces, and moving some types out of the root namespace (System.Activities.Presentation) into more specialized namespaces (System.Activities.Presentation.Converters).
We've also reduced the number of assemblies from 3 to 2, and here is the way to think about them.
Within System.Activities.Presentation.dll, here are the public namespaces:
We had not originally planned for a designer for WriteLine, we had felt that the default designer was good enough for this. We heard some feedback and one of the testers decided to put one together. This makes it easy to configure the WriteLine on the canvas:
This is a feature you may not see, or ever need to think about, but it's one that I think is pretty neat, and helps us increase performance for large workflows. We've built up some infrastructure inside the designer that supports virtualization of the UI surface. The way to think about this is lazy loading the visuals of the designer until they are actually needed. While this isn't that useful for a small workflow, as a workflow starts to span multiple screens, it makes sense to only load the visual elements that are required for the workflow that are actually in view. You may see artifacts of this if you open a large workflow as you see some of the workflow "draw on the fly." The advantages of doing this are that bigger workflows will open faster with fewer visual elements on the screen. The information used by virtualization is coupled with the viewstate of the workflow in order to cache the size of the elements in order to reduce the need to do complex computations about size, position and overlap.
Now, on hover we will display the information about the arguments and properties of the selected item, and additionally, we will pick up the System.ComponentModel.DesscriptionAttribute data as well. The following code:
1: public sealed class MySimpleActivity : CodeActivity
3: [Description("This is a basic argument which will be used to compute the end value")]
4: public InArgument<string> Text { get; set; }
6: protected override void Execute(CodeActivityContext context)
7: { ...
will show up like this in the property grid:
Rather than go with the "big flat list" approach, we've made the organization of items in the toolbox a little more granular. This also shows some of the updated icons for the activities :-)
I'm really excited that beta2 is out the door, and I can't wait to start hearing about folks using it. I'll start blogging a little more, and there are a few folks on my team that will start talking about some other interesting designer topics.
A continuation of my series of demos from my advanced workflow services talk. Here we focus on duplex message exchange patterns.
Duplex messaging is something that we model at the application level (as opposed to the infrastructure level) because we want to model that message exchange at the level of the application. Here's some scenarios where I could use duplex messaging:
One may ask the question, "But, what about the wsHttpDualBinding, or WCF duplex bindings." That's a valid question, but it's important to point out that those bindings are really used to describe the behavior of a given proxy instance (and associated service artifacts). When my proxy dies, or the underlying connection goes away, I lose the ability for the service to call back to me. Additionally, this binds me to listen in the same way that I sent out the initial message.
By modeling this at the application layer, we do lose some of the "automagicity" of the WCF duplex behavior, but I get more flexibility, and I get the ability to sustain potentially repeated recycling of the services and clients. Also, you could imagine a service that I call that turns around and calls a third party service. That third party service could call back directly to the client that made the initial call. Note, once we start doing duplex communication (and we'll encounter this in part 4, conversations), is that the definition of "client" and "service" become a bit muddier.
So, to the code:
The first thing that we need to do in order to enable this duplex messaging to occur is that the "client" workflow has to explicitly provide its context token to the service so that the service can address the appropriate instance of the client workflow.
Note, in the real world, you'll probably need to supply more than just the context token, you will need some address and binding information.
Note, in the real world, you'll probably need to supply more than just the context token, you will need some address and binding information.
Let's look at the contract of the service:
[ServiceContract(Namespace ="http://microsoft.com/dpe/samples/duplex")]
public interface IOrderProcessing
void SubmitOrder(string customerName, IDictionary<string, string> context);
[OperationContract(IsOneWay = true )]
void AddItem(OrderItem orderItem);
[OperationContract(IsOneWay = true )]
void CompleteOrder();
You'll note that on the SubmitOrder method, I pass in a context token. This is my callback correlation identifier, this is how I will figure out what instance on the client side I want to talk to. Now, I need to do some work to get the context token in order to send, so let's look at how we do this:
On the client side, on the first Send activity, let's hook the BeforeSend event.
Let's look at the implementation of GrabToken:
1: private void GrabToken(object sender, SendActivityEventArgs e)
3: ContextToSend = receiveActivity1.Context;
4: Console.WriteLine("Received token to send along");
7: public static DependencyProperty ContextToSendProperty = DependencyProperty.Register("ContextToSend", typeof(System.Collections.Generic.IDictionary<string, System.String>), typeof(OrderSubmitter.Workflow1));
9: [DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Visible)]
10: [BrowsableAttribute(true)]
11: [CategoryAttribute("Parameters")]
12: public System.Collections.Generic.IDictionary<string, String> ContextToSend
13: {
14: get
16: return ((System.Collections.Generic.IDictionary<string, string>)(base.GetValue(OrderSubmitter.Workflow1.ContextToSendProperty)));
17: }
18: set
19: {
20: base.SetValue(OrderSubmitter.Workflow1.ContextToSendProperty, value);
21: }
22: }
First, note that on lines 7-22 we declare a dependency property call ContextToSend. Think of this simply as a bindable storage space. On line 3, we go and assign to that the value of receiveActivity1.Context. "But Matt, couldn't I just build a context token off the workflow ID?" You could, but you're only going to be correct in the "simple scenario." You can see we then take that ContextToSend, and pass that into the context parameter for the service operation. Always walk up and ask a Receive activity for its context token, don't try to build one on your own.
Now, on the service side, we need to extract that, and we need to apply the value to the send activity in the service workflow that needs to call back. We basically can do the reverse:
private void codeActivity1_ExecuteCode(object sender, EventArgs e)
//set callback context
sendActivity1.Context = callbackContext;
This is inside a code activity in the first receive activity. callbackContext is a dependency property that is bound to the inbound context on the Receive activity.
The final trick is that both workflows have to be hosted inside a WorkflowServiceHost. This makes sense for the "service" workflow, since it will be message activated. On the client side, we have to do a little bit of work in order to get to the workflow runtime to spin up a workflow instance. In the early betas, we had an easy way to get to the runtime, WorkflowServiceHost.WorkflowRuntime. In order to conform more with the extensibility of WCF, this has been moved to the extensions of the service host. We get there by:
1: static void Main(string[] args)
3: using (WorkflowServiceHost wsh = new WorkflowServiceHost(typeof(Workflow1)))
5: Console.WriteLine("Press <ENTER> to start the workflow");
6: Console.ReadLine();
7: wsh.Open();
8: WorkflowRuntime wr = wsh.Description.Behaviors.Find<WorkflowRuntimeBehavior>().WorkflowRuntime;
9: WorkflowInstance wi = wr.CreateWorkflow(typeof(Workflow1));
10: AutoResetEvent waitHandle = new AutoResetEvent(false);
11: wr.WorkflowCompleted += delegate { waitHandle.Set(); };
12: wr.WorkflowTerminated += delegate(object sender, WorkflowTerminatedEventArgs e) { Console.WriteLine("error {0}", e); waitHandle.Set(); };
13: wi.Start();
14: waitHandle.WaitOne();
15: Console.WriteLine("Workflow Completed");
16: Console.ReadLine();
On line 3, you'll see we new up a WorkflowServiceHost based on the service type (it will do this to find and open the respective endpoints). On line 8, we reach in and grab the WorkflowRuntimeBehavior and get the WorkflowRuntime, and we use that to create an instance of the workflow.
So, here's what we have done: