共用方式為


Introducing the WF4 Designer

// 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

image

Notice the “Framework Version” dropdown in the upper right hand corner.

image

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.

image

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. 

image

Let’s move on and switch over to a 4.0 workflow.

Create a new project and select 4.0

image

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. 

image

Let’s drop some activities into our sequence and see what’s there to be seen.

image

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.

image

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

image

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.

image

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.

image

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

image

First, let’s click on the Argument designer and pass in some data. 

Arguments consist of a few important elements

  • Name
  • Type
  • Direction
  • Default Value (Optional)

image

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

image

Variables are similar, but slightly different, variables have a few important elements:

  • Name
  • Type
  • Scope
  • Default Value (Optional)

 

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. 

image

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.

image

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. 

image

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

image

 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.

Navigating the Workflow

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.

 

image

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.

image

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”

image

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="https://schemas.microsoft.com/netfx/2009/xaml/activities/design"
    4:       xmlns:__Sequence1="clr-namespace:WorkflowConsoleApplication1;" 
    5:       xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006" 
    6:       xmlns:p="https://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="https://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:      <p:InArgument x:TypeArguments="s:TimeSpan">[TimeSpan.FromSeconds(10)]</p:InArgument>
   15:    </__Sequence1:Sequence1.TimeToWait>
   16:    <p:Sequence>
   17:      <p:Sequence.Variables>
   18:        <p:Variable x:TypeArguments="x:String" Name="StringToPrint" />
   19:      </p:Sequence.Variables>
   20:      <p:Assign>
   21:        <p:Assign.To>
   22:          <p:OutArgument x:TypeArguments="x:String">
   23:              [StringToPrint]
   24:          </p:OutArgument>
   25:        </p:Assign.To>
   26:        <p:Assign.Value>
   27:          <p:InArgument x:TypeArguments="x:String">
   28:                    [String.Format("Someone wants to wait {0} seconds", TimeToWait.TotalSeconds)]
   29:           </p:InArgument>
   30:        </p:Assign.Value>
   31:      </p:Assign>
   32:      <p:Delay>[TimeToWait]</p:Delay>
   33:      <p:WriteLine>[StringToPrint]</p:WriteLine>
   34:    </p:Sequence>
   35:  </p:Activity>

 

Executing the Workflow

Inside the project you will see the Program.cs to execute the workflow, let’s take a look at that.

 

    1:  namespace WorkflowConsoleApplication1
    2:  {
    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;
    9:   
   10:      class Program
   11:      {
   12:          static void Main(string[] args)
   13:          {
   14:              AutoResetEvent syncEvent = new AutoResetEvent(false);
   15:   
   16:              WorkflowInstance myInstance =
   17:                  new WorkflowInstance(new Sequence1(),
   18:                      new Dictionary<string, object>
   19:                      {
   20:                          {"TimeToWait", TimeSpan.FromSeconds(3.5) }
   21:                      }
   22:   
   23:   
   24:   
   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:              };
   37:   
   38:              myInstance.Run();
   39:   
   40:              syncEvent.WaitOne();
   41:              Console.WriteLine("Press <Enter> to exit");
   42:              Console.ReadLine();
   43:   
   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).

image

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.

Comments

  • Anonymous
    May 20, 2009
    Matt Winkler, a Program Manager on the Workflow designer team has kicked off the blogging frenzy with

  • Anonymous
    May 20, 2009
    Thank you for submitting this cool story - Trackback from DotNetShoutout

  • Anonymous
    May 21, 2009
    Visual Studio 2010/.NET Framework 4.0 Scott Hanselman - CLR and DLR and BCL, oh my! - Whirlwind Tour Around .NET 4 (and Visual Studio 2010) Beta 1 Scott Hanselman - Demo Dashboard and IDE Extensions - Whirlwind Tour Around .NET 4 (and Visual Studio 2010)

  • Anonymous
    May 28, 2009
    Hey Matt, What's the difference between System.Activities.WorkflowInstance and System.WOrkflow.Runtime.WOrkflowInstance ? I've seen that you start the WF instance using directly the WorkflowInstance.Run() What about the old style of starting an instance : System.Threading.AutoResetEvent aresetEvent = new System.Threading.AutoResetEvent(false); System.Workflow.Runtime.WorkflowRuntime runtime = new System.Workflow.Runtime.WorkflowRuntime(); System.Activities.WorkflowInstance myinstance = runtime.CreateWorkflow(typeof(MyBrandNewActivity)); runtime.WorkflowCompleted += delegate(object sender, System.Workflow.Runtime.WorkflowCompletedEventArgs e) { aresetEvent.Set(); }; runtime.WorkflowAborted += delegate(object sender, System.Workflow.Runtime.WorkflowEventArgs e) { aresetEvent.Set(); }; runtime.StartRuntime(); myinstance.Start(); aresetEvent.WaitOne(); In MSDN says that is is supported in 4.0 but when try to run there is an exception saying about a type mismach between Activity class. Can you be a little bit more specific about this? Thank's!

  • Anonymous
    June 05, 2009
    @Victor, It sounds like you are mixing types from wf4 (which live in System.Activities...) with the runtime from wf3 (which lives in System.Workflow).   If you want to use the wf3 runtime, you need to use activities which derive from System.Workflow.ComponentModel.Activity, and the runtime and owrkflow instance code as well. If you want to use the wf4 runtime, you need to use activities which dervice from System.Activities.Activity and those types. The type mismatch error that you are seeing is that the call to runtime.CreateInstance expects a type from wf3, and you are passing it a type from wf4. let me know if that helps, matt

  • Anonymous
    July 01, 2009
    The comment has been removed