The world of .NET from a Connected Systems MVP & INETA Speaker

Windows Workflow 101 or 2 Months with WF

I don't really know what to call this post. I'm not as good as Palermo in doing 101 posts and I like to talk about the context and issues. And, I'm not just a pretty link-blogger so away we go :). Way back in October, I talked about Re-Hosting the Windows Workflow Designer in our application. So the version that went out of our baseline Concentration product went without any workflow. I have been playing the Agile Architect the last few months scouting out the WF territory to see if we could accomplish our workflow architectural needs with WF. The short answer is Yes. If I had to summarize my months with WF is that WF is very powerful and capable. But a lot of that power comes from the fact that WF is such a general framework capable of being used in many different scenarios on Windows platform. There is no doubt that Microsoft providing such a powerful and flexible framework for developing workflows is so much better than having to develop our own framework, visual workflow designer, and runtime environment. But, as they say, with great power comes great responsibility. Because it is so general, you may have to do a lot of work to develop your domain-specific model and you will have to learn a lot about WF. You will still have to know about Workflow Architectures and deadlocks and all sorts of stuff. There are some in Redmond that seem to promote the view that you can just "drop" WF into your app and presto. One of the feedback items that us Architect MVPs gave the WF team last summit was this was dangerous. People have to be given guidance here. We have to leverage the decades of work that people like Eric Newcomer have already put into this field. People are using WF to transition UI pages for God's sake.

In my original view of things, I thought I would re-host the WF Designer in our CAB Smart Client application giving Collateral Analysts a whole new power to design workflows. I thought, "wow, I can have analysts just drop Margin Calc and Collateral Demand activities and presto!" Well of course, there is that problem that the designer is one side of the wire and that I wanted to run my workflow "up on the Server" behind my service boundaries. Cool, I'll just take the XOML file (and maybe the code-behind file) and I'll MTOM it up through my super-duper Workflow WCF Service that will call the Workflow Runtime that I will host in my domain layer. My new boss, whose group built their own simple workflow in the Java environment rolled his eyes and wondered about overkill and schedule. My CTO thought similar thoughts. Then you face the realization that banks and Collateral Analysts are not designing these workflows 10 times a day but once every 4 months or something and then that's it. So when we came to planning the next 2 years in NYC for my group, I agreed to drop my custom designer to get agreement to use WF.

But back to WF. It's so freaking deceptly simple. Here's the scenario that many give:

  1. Start up Visual Studio, wait 2 minutes (sorry had to put that in!), New Project - Workflow - Sequential Workflow Console Application
  2. Open up workflow1.cs and dude, whoa, you got this WYSIWYG designer that even a mort could use!
  3. Drop a Code Activity onto the workflow
  4. A Red exclamation mark appears - Right click "Execute Code is Not Set"
  5. You can enter a value for the handler or you can do the VB thing and double-click on the Code Activity to get one created for you
  6. Enter the following:

private void codeActivity1_ExecuteCode(object sender, EventArgs e) {

Console.WriteLine("I'm so cool, I can do workflow!");
}

Who said I wasn't good at this tutorial stuff? There's your first WF program (or you could use MessageBox.Show(). You run it and wow. Ok, it's simple and you could do all sorts of really advanced things with Parallel, If-Else Branch Activities and you could have really amazing powerful workflows. But now you have to figure out how to use all this stuff in the real world in your domain, your architecture. The first thing you have to face is that you are responsible for hosting the WF Runtime.

WF is (rightly) designed to be hosted by your application, an important and correct decision by the WF team as it provides huge flexibility to use WF in the widest variety of scenarios. And hosting the WF Runtime is pretty trivial as you get this console host by default:


using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Workflow.Runtime;
using System.Workflow.Runtime.Hosting;

namespace SamIsAmazing
{
class Program {
static void Main(string[] args)
{
using (WorkflowRuntime workflowRuntime =
          new WorkflowRuntime())
{
AutoResetEvent waitHandle = new AutoResetEvent(false);
workflowRuntime.WorkflowCompleted +=
delegate(object sender,
          WorkflowCompletedEventArgs e) 
          { waitHandle.Set(); };
workflowRuntime.WorkflowTerminated
+= delegate(object sender, WorkflowTerminatedEventArgs e)
{
Console.WriteLine(e.Exception.Message);
waitHandle.Set();
};
WorkflowInstance instance =
           workflowRuntime.CreateWorkflow(typeof
              (SamIsAmazing.Workflow1));
instance.Start();
waitHandle.WaitOne();
}
}
}
}

So its easy to host the WF runtime anywhere in any AppDomain. But of course, you are going to have to make some tough decisions if you really want to host it and have it work in a real app. There is lot wrong with the above with the major problema being that it can handle only one workflow at a time, waiting for only a single workflow to complete. So this week, I wrote a Workflow Manager class and a wrapper around the workflow instance that lets me run many workflows at the same time all having their state separated from each other and as distinct instances. Steve looked at it yesterday, while helping me get out a bug, and pronounced it "very cool", so I know it must be cool now-).

What is also difficult about WF is frankly getting data in and out to the workflow. WF wisely has an architecture that lets you plug in services and it's an opt-in system that you only specify the services you actually need. But in order to get any real work with your workflow, you will get intimately aquatinted with WF Local Services. Another name for this is Data Exchange Service. You got to code one of these bad boys yourself and you can do all sorts of stuff with one but the main use of one is to get data in and out of your workflow. You have to create a .NET interface, anoint it with the [ExternalDataExchange] attribute, implement it and then do some binding with Dependency Properties in your workflow.

 
using System;
using System.Threading;
using System.Workflow.ComponentModel;
using System.Workflow.Runtime;
using System.Workflow.Activities;
using System.Windows.Forms;
namespace CommunicationsWorkflow
{
[Serializable]
public class VotingEventArgs : ExternalDataEventArgs
{
public VotingEventArgs(Guid instanceID, string alias)
: base(instanceID)
{
this.alias = alias;
}

private string alias;

public string Alias
{
get { return alias; }
set { alias = value; }
}
}

[ExternalDataExchange]
internal interface IVotingService
{
event EventHandler<VotingEventArgs> ApproveProposal;

event EventHandler<VotingEventArgs> RejectProposal;

void CreateBallot(string alias);
}

internal class VotingService : IVotingService
{
public event EventHandler<VotingEventArgs> ApproveProposal;

public event EventHandler<VotingEventArgs> RejectProposal;

public void CreateBallot(string alias)
{
Console.WriteLine("Ballot created for " + alias + ".");
ThreadPool.QueueUserWorkItem(ShowVotingDialog,
new VotingEventArgs(WorkflowEnvironment.WorkflowInstanceId,
        alias));
}

public void ShowVotingDialog(object o)
{
DialogResult result;
VotingEventArgs votingEventArgs = o as VotingEventArgs;
Guid instanceID = votingEventArgs.InstanceId;
string alias = votingEventArgs.Alias;
result = MessageBox.Show("Approve Proposal, "
       + alias + "?", alias + " Ballot", MessageBoxButtons.YesNo);
if (result == DialogResult.Yes)
{
if (ApproveProposal != null)
ApproveProposal(null, votingEventArgs);
     }
else
{
if (RejectProposal != null)
RejectProposal(null, votingEventArgs);
}
}
}
}
namespace CommunicationsWorkflow {
public sealed partial class VotingWorkflow :
   SequentialWorkflowActivity
{
public VotingWorkflow()
{
InitializeComponent();
}

private void OnRejected(object sender, ExternalDataEventArgs e)
{
Console.WriteLine(string.Format("Proposal Rejected by {0}",
              votingArgs.Alias));
}

private void OnApproved(object sender, ExternalDataEventArgs e)
{
Console.WriteLine(string.Format("Proposal Approved by {0}",
              votingArgs.Alias));
}

public static System.Workflow.ComponentModel.DependencyProperty
votingArgsProperty = DependencyProperty.Register
     ("votingArgs", typeof(CommunicationsWorkflow.VotingEventArgs),
typeof(CommunicationsWorkflow.VotingWorkflow));

[System.ComponentModel.DesignerSerializationVisibilityAttribute(
DesignerSerializationVisibility.Visible)]
[System.ComponentModel.BrowsableAttribute(true)]
[System.ComponentModel.CategoryAttribute("Parameters")]
public VotingEventArgs votingArgs
{
get
{
return((CommunicationsWorkflow.VotingEventArgs)
(base.GetValue(CommunicationsWorkflow.
           VotingWorkflow.votingArgsProperty)));
}
set { base.SetValue(CommunicationsWorkflow.VotingWorkflow.
               votingArgsProperty, value); }
}
}
}
}

So that's all well and good but there's a big problem in certain scenarios including mine. What if you want to give your users the general ability to design workflows and then you need to communicate in and out of that workflow and then indeed drive your domain objects? What are you going to do? Generate a Local Service on the fly without any idea what activities they are designing??? Is having all sorts of Custom Actvities with "per-built" local services an answer? This is where I am currently stuck wrestling with WF. So this is Part 1, there is so much more to say. Next time around, if you are nice to me, I'll talk about Workflow Persistence with Oracle-).

I'm listening to Hear My Train A Comin' by Jimi Hendrix on the album Voodoo Child: The Jimi Hendrix Collection

 

kick it on DotNetKicks.com

» Similar Posts

  1. Windows Workflow 103 or WF Part 3 - Introduction to Workflow
  2. How to use the Service BAT, Exception Handling and Logging Blocks with WCF and CAB
  3. Writing Maintainable Code

Comments are closed