Tag Archives: wf4

Windows Workflow 4 Designer WPF Application

One of the coolest things about Windows Workflow Foundation is the workflow designer. It’s visual programming.  You drag and drop your components onto the design surface and configure the properties of each module to create a functional application. Microsoft makes it easy to re-host the workflow designer in your own applications, providing you with an easy way to include customization and configuration tools for your workflows.

I found a great bare-bones tutorial at MSDNRehosting the Workflow Designer.

There’s some important functionality missing from this tutorial, though. There are no mechanisms for opening or saving workflow files, and the toolbox is only populated with two controls. So, I’ve taken the MSDN tutorial, compressed the steps, and added some additional key functionality.

  1. Create a new WPF Application. I called mine “WorkflowEditor.”
  2. Add references
    • System.Activities
    • System.Activities.Core.Presentation
    • System.Activities.Presentation
  3. Copy/paste the XAML below into MainWindow.xaml
  4. Copy/paste the code-behind below into MainWindow.xaml.cs

That’s it! Once you’re this far, you should be able to run the program. If you’re looking for a more in-depth, step-by-step explanation of the code, follow the link above to the MSDN tutorial. The only differences below are that I added some reflection code to extract the “standard” workflow activities, and I added Open and Save buttons.

<Window x:Class="adamprescott.net.WorkflowEditor.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="500" Width="800">
    <Grid Name="grid1">
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition Width="4*" />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="36" />
            <RowDefinition />
        </Grid.RowDefinitions>

        <ToolBar Grid.Row="0" Grid.ColumnSpan="3">
            <Button Click="OnOpenClick">Load</Button>
            <Button Click="OnSaveClick">Save</Button>
        </ToolBar>
    </Grid>
</Window>
using System;
using System.Activities;
using System.Activities.Core.Presentation;
using System.Activities.Presentation;
using System.Activities.Presentation.Toolbox;
using System.Activities.Statements;
using System.Linq;
using System.Reflection;
using System.Windows.Controls;
using Microsoft.Win32;

namespace adamprescott.net.WorkflowEditor
{
    public partial class MainWindow
    {
        private WorkflowDesigner _designer;

        public MainWindow()
        {
            InitializeComponent();
            RegisterMetadata();
            InitializeDesigner();
            AddToolBox();
        }

        private void AddDesigner()
        {
            _designer = new WorkflowDesigner();
            Grid.SetColumn(_designer.View, 1);
            Grid.SetRow(_designer.View, 1);
            grid1.Children.Add(_designer.View);
        }

        private void RegisterMetadata()
        {
            var dm = new DesignerMetadata();
            dm.Register();
        }

        private ToolboxControl GetToolboxControl()
        {
            var toolbox = new ToolboxControl();
            PopulateToolboxCategoryFromAssembly(toolbox, typeof(Assign).Assembly, "Standard");
            return toolbox;
        }

        private void PopulateToolboxCategoryFromAssembly(ToolboxControl toolbox, Assembly assembly, string categoryName)
        {
            var tools = assembly.GetTypes()
                .Where(t => t.IsSubclassOf(typeof(Activity))
                    && t.IsPublic
                    && !t.IsAbstract
                    && HasParameterlessContructor(t))
                .Select(t => new ToolboxItemWrapper(t.FullName, t.Assembly.FullName, null, t.Name))
                .OrderBy(t => t.DisplayName);

            var category = new ToolboxCategory(categoryName);
            foreach (var t in tools)
            {
                category.Add(t);
            }
            toolbox.Categories.Add(category);
        }

        private bool HasParameterlessContructor(Type t)
        {
            var ctors = t.GetConstructors();
            var parameterless = ctors.Where(c => c.GetParameters().Count() == 0)
                .FirstOrDefault();
            return parameterless != null;
        }

        private void AddToolBox()
        {
            var tc = GetToolboxControl();
            Grid.SetColumn(tc, 0);
            Grid.SetRow(tc, 1);
            grid1.Children.Add(tc);
        }

        private void AddPropertyInspector()
        {
            Grid.SetColumn(_designer.PropertyInspectorView, 2);
            Grid.SetRow(_designer.PropertyInspectorView, 1);
            grid1.Children.Add(_designer.PropertyInspectorView);
        }

        private void OnOpenClick(object sender, System.Windows.RoutedEventArgs e)
        {
            var dlg = new OpenFileDialog();
            if (dlg.ShowDialog().Value)
            {
                InitializeDesigner();
                _designer.Load(dlg.FileName);
            }
        }

        private void OnSaveClick(object sender, System.Windows.RoutedEventArgs e)
        {
            var dlg = new SaveFileDialog();
            if (dlg.ShowDialog().Value)
            {
                _designer.Save(dlg.FileName);
            }
        }

        private void InitializeDesigner()
        {
            AddDesigner();
            AddPropertyInspector();
        }
    }
}
Advertisements

Windows Workflow 4: Adding Items to a Collection

I wanted to create a workflow that executes several custom activities in parallel and adds the results from each to a shared collection. This is easily accomplished by using WF4’s AddToCollection activity.

The XAML below demonstrates the use of both the Parallel and AddToCollection activities to accomplish this task. I’m also using of the ForEach activity to iterate through my collection to display the results.

<Activity xmlns="http://schemas.microsoft.com/netfx/2009/xaml/activities" 
          xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
          xmlns:scg="clr-namespace:System.Collections.Generic;assembly=mscorlib">
  <Sequence>
    <Sequence.Variables>
      <Variable x:TypeArguments="scg:List(x:String)" Default="[New List(Of String)]" Name="MyList" />
    </Sequence.Variables>

    <Parallel>
        <AddToCollection x:TypeArguments="x:String" Collection="[MyList]" Item="item1" />
        <AddToCollection x:TypeArguments="x:String" Collection="[MyList]" Item="item2" />
        <AddToCollection x:TypeArguments="x:String" Collection="[MyList]" Item="item3" />
    </Parallel>
    
    <ForEach x:TypeArguments="x:String" Values="[MyList]">
        <ActivityAction x:TypeArguments="x:String">
            <ActivityAction.Argument>
                <DelegateInArgument x:TypeArguments="x:String" Name="item" />
            </ActivityAction.Argument>
            <WriteLine Text="[item]" />
        </ActivityAction>
    </ForEach>
  </Sequence>
</Activity>

Here’s the output produced by running this workflow:

Some notes about this example:

  • The XAML was written without the use of Visual Studio’s designer
  • There is a collection variable of type List<string> declared named “MyList”; this requires us to import the namespace System.Collections.Generic
  • The AddToCollection activities’ Collection properties are bound to MyList
  • The ForEach activity’s Values property is bound bound to MyList
  • The ForEach activity creates a delegate argument named “item” which is then passed in to the ForEach activity’s WriteLine activity

Windows Workflow 4: Creating a Simple Custom Activity

The Windows Workflow Foundation project that I’m working on relies on my ability to create a library of custom activities that can be used together to handle many different customer scenarios. Creating a simple custom activity is a very easy and straightforward task with WF4. This post will walk you through the steps of creating a custom send-email activity.

The first thing you’ll need to do is create a new class that inherits from NativeActivity. (Note that NativeActivity is not your only option–check out this post for an overview of the different choices.) NativeActivity is an abstract class that requires us to implement an Execute method. This is the method that runs when the activity executes.

public sealed class EmailActivity : NativeActivity
{
    protected override void Execute(NativeActivityContext context)
    {
        throw new NotImplementedException();
    }
}

We’ll need some additional information in order to send an email, and we can use input arguments to get what we need. To add an input argument, create a public property of type InArgument<T>. Here are my input properties. Notice that I’ve decorated some of them with the [RequiredArgument] attribute to indicate that they are required. Output arguments can be added in a similar fashion by using OutArgument<T>, though we do not need any for this example.

[RequiredArgument]
public InArgument<string> Host { get; set; }

[RequiredArgument]
public InArgument<string> Sender { get; set; }

[RequiredArgument]
public InArgument<string> Recipients { get; set; }

public InArgument<string> Subject { get; set; }

public InArgument<string> Body { get; set; }

Now that we have all the information we need, we just need to implement the Execute method to send an email. Once again, it’s very simple.

protected override void Execute(NativeActivityContext context)
{
    // create the email message
    var mailMessage = new MailMessage();
    mailMessage.From = new MailAddress(Sender.Get(context));
    mailMessage.To.Add(new MailAddress(Recipients.Get(context)));
    mailMessage.Subject = Subject.Get(context);
    mailMessage.Body = Body.Get(context);

    // send the message
    var smtpClient = new SmtpClient(Host.Get(context));
    smtpClient.Send(mailMessage);
}

This activity can now be added to your workflow XAML through the designer or any text editor. In the example below, I have bound the Body property to a variable named StatusText that will be set in an earlier activity.

<my:EmailActivity
  Host="mail.mydomain.com"
  Sender="myworkflow@mydomain.com"
  Recipients="someuser@mydomain.com"
  Subject="Workflow Status Update"
  Body="[StatusText]" />

Here is the complete class:

public sealed class EmailActivity : NativeActivity
{
    [RequiredArgument]
    public InArgument<string> Host { get; set; }

    [RequiredArgument]
    public InArgument<string> Sender { get; set; }

    [RequiredArgument]
    public InArgument<string> Recipients { get; set; }

    public InArgument<string> Subject { get; set; }

    public InArgument<string> Body { get; set; }

    protected override void Execute(NativeActivityContext context)
    {
        // create the email message
        var mailMessage = new MailMessage();
        mailMessage.From = new MailAddress(Sender.Get(context));
        mailMessage.To.Add(new MailAddress(Recipients.Get(context)));
        mailMessage.Subject = Subject.Get(context);
        mailMessage.Body = Body.Get(context);

        // send the message
        var smtpClient = new SmtpClient(Host.Get(context));
        smtpClient.Send(mailMessage);
    }
}

Windows Workflow 4: Dynamically Load Workflows

I’m starting a new project where I plan to use Windows Workflow Foundation to load custom jobs from external XML files like plug-ins. In order for this to work, I’ll have a sub-directory that my application watches. If files are found, they will be loaded and executed.

The reason I want to do this is because Windows Workflow Foundation 4 makes it easy to keep the workflows as human-readable XML files that can be edited by technical-but-not-developer users. This allows for easy, efficient customization of workflows. The processes we have now require developer interaction for any customization or modification since everything is compiled into DLLs making our development team an unnecessary bottleneck for warranty and enhancement.

Over the next days and weeks, I’ll be publishing a number of posts that demonstrate small nuggets of WF4 functionality. I’ve been impressed with what I’ve been able to accomplish in just a few days of fooling around, so this I’m very excited about this endeavor!

I’m keeping it short and simple with this post: loading and running an XML (XAML) workflow from a file at runtime. This very powerful functionality can be accomplished in a single line of code:

WorkflowInvoker.Invoke(ActivityXamlServices.Load("HelloWorld.xml"));

One of my main goals is encapsulating all of my functionality in human-readable XML, so I also wanted to provide the contents of my HelloWorld.xml file. This workflow is unimpressive and simply writes “Hello, world!” to the console, but here it is, nonetheless:

<Activity xmlns="http://schemas.microsoft.com/netfx/2009/xaml/activities" 
          xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <WriteLine Text="Hello, world!" />
</Activity>