CHAPTER 10

image

Rehosting the Workflow Designer

Each chapter thus far has focused on building workflows within VS2012, which is primarily the integrated development environment (IDE) for building .NET software. There are several development tools within Visual Studio, including WF, that also provide their own visual software designers. For instance, ASP.NET/MVC has its own web designer for visually designing and changing a web page’s HTML. Windows Presentation Framework (WPF) also has a designer for adding user controls and changing the XAML used to render rich client user interfaces (UI). Finally, Entity Framework (EF), which abstracts the data plumbing code between applications and databases, also has a designer for modeling relationships among table structures within a database.

These Microsoft technologies rely on Visual Studio or other third party software development tools for developing software applications. However, authoring workflows should not be limited to only using Visual Studio like some of the technologies just mentioned. WF allows rehosting of its WF designer outside of Visual Studio and hosted within a custom application. This is how WF stands out from other development technologies mentioned earlier, because these technologies embed their visual designer within Visual Studio for building custom solutions by developers and are not geared to the basic end user for customizing or adding additional functionality to software. A higher level of development skills than most end users possess is required for customizing software and this is where WF crosses over the boundary between technical users and basic end users—by letting non-developers modify business logic within applications.

Many savvy business people who are non-technical are well versed in the day-to-day business processes they follow. End users do not have to be technical to understand business processes, so why not provide the end user with a solution for modeling business processes while at the same time providing more flexibility to software because it can be easily extended by end users? WF focuses on specifying the business process and separating it out from an application by proposing a new architecture where business processes are instead hosted within applications rather than developed internally within applications.

In order for non-developers to design workflows, the WF designer can be rehosted within a custom application to take advantage of not requiring a Visual Studio license for building workflows. Although workflows provide a natural way to model business processes, end users should not be provided the same technical functionality that WF provides within Visual Studio. The goal should be to abstract most of the inner workings of WF, such as management of workflows within the WF runtime, tracking, and persistence. Providing too much technical flexibility and letting end users make mistakes while building custom workflows could be a recipe for disaster, so even though WF provides a way for rehosting the WF designer, developers that implement solutions that rehost the WF designer should strategically focus on the level of functionality that is needed; thus end users will have parameters around what functionality that can be performed while rehosting the WF designer.

One important thought for rehosting workflows is aligning the technical level required for authoring workflows with the technical level anticipated for the end users who will be building workflows. In most cases, rehosting the WF designer will be built within custom software for empowering non-technical users to perform tasks that are extremely technical without requiring that they understanding the technical abstraction of what is being performed as a workflow runs. Custom workflow activities can be built and rehosted that pertain to a particular business domain, one that the end user should be familiar with. A business domain refers to a specific type of business (for example, healthcare or banking) and WF activities can be written to help manage a particular business domain.

Particular WF activities that only provide a level of anticipated flow for the logic of workflows should also be included. Some of these WF activities are out-of-the-box activities that provide conditions that a majority of end users can understand and feel comfortable using. It is probably not a good idea to provide out-of-the-box activities that pertain to development functionality like interacting with services, looping through collections, error handling, etc. This functionality can be provided, but it should be abstracted in such a way that all the end user has to do is drag and drop the activities that provide this functionality without having to think about how configure the workflow activities. When thinking about what workflow activities should be rehosted, make sure that it is as simple as possible to configure the activities without being too technical.

This chapter demonstrates the components that can be rehosted within custom software. A major goal of the WF team was to make the steps for rehosting parts of the WF designer simple. Because WF4 was rewritten from scratch, WPF was used for all of the visualization provided. Rehosting the WF designer carries over into WF4.5, so this chapter will walk you through the following:

  • WF components that are rehostable
  • Rehosting the WF designer
  • Managing workflows built within a rehosted WF designer
  • Running the workflows on the fly

Rehosting Components

This section will discuss WF components that can be rehosted outside of Visual Studio. The different components focused on are

  • WF designer
  • WF toolbox
  • WF activities
  • WF properties

WF Designer

The WF designer is the main component required to be rehosted for authoring workflows within custom software instead of Visual Studio. Figure 10-1 shows the WF designer being hosted within a WPF application, which looks similar to the WF designer that is provided within Visual Studio. The rehosted designer shows that a Sequence activity has been used as the default activity within the WF designer, so it will be used as the parent activity for all other activities that are added to the workflow. The WF designer also includes the Expand All and Collapse All commands for viewing the details for parent activities that have additional child activities. Also included are a Variables tab for managing WF variables within workflows and an Import tab for importing additional referenced libraries that can be used within workflows.

9781430243830_Fig10-01.jpg

Figure 10-1.  Rehosting the designer

Designer Metadata

In order to get the drag-and-drop capabilities for building workflows, metadata for the designer must be included as well. Without adding the designer metadata, the Sequence activity in Figure 10-1 would resemble the same Sequence activity in Figure 10-2 except without any drag-and-drop capabilities.

9781430243830_Fig10-02.jpg

Figure 10-2.  Designer metadata required for drag-and-drop capabilities

DesignerMetadata is provided from the System.Activities.Core.Presentation.DesignerMetadata namespace. Calling the Register() method registers metadata for the runtime, which will be demonstrated in the first exercise in this chapter, Rehosting WF Components.

WF Toolbox

After rehosting the WF designer, the WF toolbox can be rehosted so that activities can be used for building workflows, just as workflows are built using WF activities within Visual Studio.

Figure 10-3 shows the WF toolbox on the left-hand side of the WF designer. Activity Category illustrates a category of workflow activities that can be included for building workflows. Even the Search textbox is included with the WF toolbox for searching for workflow activities within itself. The next step is to provide out-of-the-box workflow activities that can be added to the rehosted WF toolbox (see Figure 10-4).

9781430243830_Fig10-03.jpg

Figure 10-3.  Rehosting the WF toolbox

9781430243830_Fig10-04.jpg

Figure 10-4.  WF activities included within the toolbox

Figure 10-4 illustrates rehosting the WF toolbox, which includes the If and WriteLine activities. The workflow being built includes the WriteLine activity, which resides as a child activity within the default Sequence activity. By including the WF toolbox, workflow activities can now be dragged and dropped within a workflow.

WF Properties

There are times when the WF property component also needs to be rehosted so that properties for the workflow can be configured. Rehosting the WF property window dynamically represents the properties for the workflow itself or for selected workflow activities so they can be configured. Figure 10-5 illustrates the rehosting of the WF toolbox; by selecting the WriteLine activity, the Text and TextWriter properties that are provided with the WriteLine activity are dynamically displayed so values can be assigned to them.

9781430243830_Fig10-05.jpg

Figure 10-5.  Rehosting the WF toolbox

image Note   A new feature in WF4.5 is using C# expressions for workflows designed using VS2012. Because the C# compiler is not rehostable like the VB compiler, rehosting the WF designer still only allows VB expressions to be added to a workflow rather than using C# expressions. Be on the lookout for additional patches of VS2012 that will probably use Microsoft’s “Roslyn” project to bring C# syntax to a rehosted WF designer

REHOSTING WF COMPONENTS

Now that you have had a chance to get familiar with each of the WF components that can be rehosted within a custom application, this exercise will walk you through the steps and demonstrate the C# code required to build a custom WPF application so that workflows can be authored outside of Visual Studio.

  1. Open VS2012 and click on the File menu. Select New and then Project.
  2. Within the installed project templates, select Windows and then select the WPF Application project template.
  3. Set the solution name to Apress.Chapter10 and the name of the project to Exercise1.
  4. Rename the existing WPF window to RehostedWF designer.xaml.

    Replace the existing XAML contents with the XAML in Listing 10-1. This XAML markup 10sets the window height to 350, width to 650, and title to WF Designer. More importantly, it sets up three columns where the different rehosted WF components will reside once they are added to the WPF window. The XAML markup in Listing 10-1 is all that is needed for setting up the layout of the WPF window.

    Listing 10-1.  Rehosted WF Designer XAML

    <Window x:Class="Exercise1.RehostedWF designer"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Title="WF Designer" Height="350" Width="650">
        <Grid Name="MainGrid1">

            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="1*"/>
                <ColumnDefinition Width="4*" />
                <ColumnDefinition Width="1.5*"/>
            </Grid.ColumnDefinitions>
        </Grid>
    </Window>
  5. Now that the layout of the window is complete, C# code can be used to programmatically rehost the WF components into the window. A couple of references need to be made first. Add the following references, as illustrated in Figure 10-6:
    System.Activities
     System.Activities.Core.Presentation
     System.Activities.Presentation

    9781430243830_Fig10-06.jpg

    Figure 10-6.  Referencing the WF assemblies

  6. Expand out the RehostedWF designer.xaml file within the Solution Explorer and open the RehostedWF designer.xaml.cs file. Make sure the using statements in Listing 10-2 exist at the top of code file.

    Listing 10-2.  Included using Statements

    using System;
     using System.Collections.Generic;
     using System.Linq;
     using System.Text;
     using System.Threading.Tasks;
     using System.Windows;
     using System.Windows.Controls;
     using System.Windows.Data;
     using System.Windows.Documents;
     using System.Windows.Input;
     using System.Windows.Media;
     using System.Windows.Media.Imaging;
     using System.Windows.Navigation;
     using System.Windows.Shapes;

     //WF Namespaces
     using System.Activities.Core.Presentation;
     using System.Activities.Presentation;
     using System.Activities.Presentation.Toolbox;
     using System.Activities.Statements;
  7. Add private WorkflowDesigner WF designer; just after the class declaration but before the RehostedWF designer() constructor.
  8. Use the DesignerMetaData object for adding metadata about the WF designer. Change the constructor of the RehostedWF designer class, as shown in Listing 10-3.

    Listing 10-3.  Adding Designer Metadata

    public RehostedWF designer()
     {
            InitializeComponent();

            //Register WF designer metadata
            var WF designerMetaData = new DesignerMetadata();
            WF designerMetaData.Register();
     }
  9. Since the WF designer variable for representing the WF designer was created in step 7, it can be used for rehosting the WF designer within the second ColumnDefinition for the MainGrid1 grid. Add the code in Listing 10-4 for the method that takes a WorkflowDesigner object and adds it within the WPF window.

    Listing 10-4.  Setting Up the WF Designer to Be Rehosted

    private void BuildDesigner()
     {
            //instantiates the designer
            WF designer = new WorkflowDesigner();
            //places the designer within the column
            //with an index of 1
            Grid.SetColumn(this.WF designer.View, 1);
            //Loads a default Sequence activity as the first
            //activity for the workflow
            WF designer.Load(new Sequence());
            //shows a rehosted declaritive workflow
            //that can be changed
            MainGrid1.Children.Add(this.WF designer.View);
     }
  10. Pressing F5 will compile the project. After the project compiles correctly, the WF designer will appear hosted within a new window, as illustrated within Figure 10-7.

    9781430243830_Fig10-07.jpg

    Figure 10-7.  Rehosted WF Designer

  11. Add a method for building a WF toolbox so it can also be rehosted. The toolbox will be placed to the left of the WF designer, as illustrated in Figure 10-7. After the code in Listing 10-5 is added, modify the constructor once more and add the call BuildWFToolbox();.

    Listing 10-5.  Building the WF Toolbox That Will Be Rehosted

    private void BuildWFToolbox()
          {
             var WFToolbox = new ToolboxControl();
             //set the category for the rehosted activities
             ToolboxCategory category = new ToolboxCategory("Native Activities");
             //Add an if activity
             var act1 =
                 new ToolboxItemWrapper("System.Activities.Statements.If",
                 typeof(If).Assembly.FullName, null,
                 "If");
             //Add a WriteLine activity
             var act2 =
                 new ToolboxItemWrapper("System.Activities.Statements.WriteLine",
                 typeof(WriteLine).Assembly.FullName, null, "WriteLine");
             //Add a DoWhile activity
             var act3 =
                 new ToolboxItemWrapper("System.Activities.Statements.DoWhile",
                 typeof(DoWhile).Assembly.FullName, null, "DoWhile");
             //Add an Assign activity
             var act4 =
                            new ToolboxItemWrapper("System.Activities.Statements.Assign",
                            typeof(Assign).Assembly.FullName, null, "Assign");
                       //Add the activities under the new category, "Native Activities"
                       category.Add(act1);
                       category.Add(act2);
                       category.Add(act3);
                       category.Add(act4);
                       //Add the category to the toolbox
                       WFToolbox.Categories.Add(category);
                      
                       //Add the toolbox to the grid column that has a 0 index.
                       Grid.SetColumn(WFToolbox, 0);
                       //Show the WF toolbox
                       MainGrid1.Children.Add(WFToolbox);
              }
  12. Pressing F5 will compile the project and show the WF toolbox and four workflow activities included within it. At this point, any of the four workflow activities can be dragged and dropped into the WF designer to build a workflow (see Figure 10-8).

    9781430243830_Fig10-08.jpg

    Figure 10-8.  Rehosting the WF toolbox

  13. The last WF component to rehost is the WF Property window, which is used for managing the workflow and its activities. Add the code in Listing 10-6. Then 10include AddWFPropertyWindow(); as another call within the constructor.

    Listing 10-6.  Code for Rehosting the Property Window

    private void AddWFPropertyWindow()
            {
            //Add the property window within the column that has 2 as its index
               Grid.SetColumn(WF designer.PropertyInspectorView, 2);
            //Show the property window within the grid
               MainGrid1.Children.Add(WF designer.PropertyInspectorView);
            }
  14. Pressing F5 again will compile and run the code, which will now show that the WF Property window is now also rehosted. Figure 10-9 illustrates that the WF toolbox, designer, and Property window have all been rehosted within the application.

    9781430243830_Fig10-09.jpg

    Figure 10-9.  Rehosting the WF designer, toolbox, and Property window


    This exercise walked through the code and XAML markup to build a simple application that can be used to build workflows outside of Visual Studio. As you saw, it’s quite easy to actually rehost the WF components.

Rehosting WF Controls in XAML

The previous exercise demonstrated how C# code can be used to rehost WF controls, but it is not the only option for rehosting WF controls. WF controls can also be specified in XAML. The first line of XAML that is important for working with the WF controls is

xmlns:WFC="clr-namespace:System.Activities.Presentation.Toolbox;assembly=System.Activities.Presentation"

It adds the namespace for listed WF assemblies. WFC serves as the prefix for the WF controls that will be used to design the rehosted designer. A Window.Resources tag is added to specify information about the System.Activities namespace so it can be used to add out-of-the-box WF activities to the WF Toolbox, like so:

<Window.Resources>
        <sys:String x:Key="WFAssembly">System.Activities, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35</sys:String>

Listing 10-7 shows the XAML markup that is required for building the same application as in the Rehosting WF Components exercise, except specifying the WF toolbox layout with XAML.

Listing 10-7.  WF Designer Rehosted Using XAML

<Window x:Class="Exercise1.RehostedWFThroughMarkup"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:WFC="clr-namespace:System.Activities.Presentation.Toolbox;assembly=System.Activities.Presentation"
        xmlns:sys="clr-namespace:System;assembly=mscorlib"
        Title="WF designer" Height="350" Width="650">
    <Window.Resources>
        <sys:String x:Key="WFAssembly">System.Activities, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35</sys:String>
    </Window.Resources>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="1*"/>
            <ColumnDefinition Width="4*"/>
            <ColumnDefinition Width="1.5*"/>
        </Grid.ColumnDefinitions>
        <Border Grid.Column="0">
            <WFC:ToolboxControl>
                <WFC:ToolboxCategory CategoryName="Basic">
                    <WFC:ToolboxItemWrapper AssemblyName="{StaticResource WFAssembly}">
                        <WFC:ToolboxItemWrapper.ToolName>
                            System.Activities.Statements.Sequence
                        </WFC:ToolboxItemWrapper.ToolName>
                    </WFC:ToolboxItemWrapper>
                    <WFC:ToolboxItemWrapper  AssemblyName="{StaticResource WFAssembly}">
                        <WFC:ToolboxItemWrapper.ToolName>
                            System.Activities.Statements.WriteLine
                        </WFC:ToolboxItemWrapper.ToolName>

                    </WFC:ToolboxItemWrapper>
                    <WFC:ToolboxItemWrapper  AssemblyName="{StaticResource WFAssembly}">
                        <WFC:ToolboxItemWrapper.ToolName>
                            System.Activities.Statements.If
                        </WFC:ToolboxItemWrapper.ToolName>

                    </WFC:ToolboxItemWrapper>
                    <WFC:ToolboxItemWrapper  AssemblyName="{StaticResource WFAssembly}">
                        <WFC:ToolboxItemWrapper.ToolName>
                            System.Activities.Statements.While
                        </WFC:ToolboxItemWrapper.ToolName>

                    </WFC:ToolboxItemWrapper>
                </WFC:ToolboxCategory>
            </WFC:ToolboxControl>
        </Border>
        <Border Grid.Column="1" Name="DesignerBorder"/>
        <Border Grid.Column="2" Name="PropertyBorder"/>
    </Grid>
 </Window>

The necessary C# code is reduced to only specifying the WF designer and WF Properties window. The following namespaces are required:

 using System.Activities.Core.Presentation;
 using System.Activities.Presentation;
 using System.Activities.Statements;

Then the DesignerMetadata can be specified along with the  WorkflowDesigner, which specifies views to the exiting grid columns DesignerBorder and PropertyBorder identified in Listing 10-8

Listing 10-8.  C# Code for Rehosting WF Controls within XAML

 using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Text;
 using System.Threading.Tasks;
 using System.Windows;
 using System.Windows.Controls;
 using System.Windows.Data;
 using System.Windows.Documents;
 using System.Windows.Input;
 using System.Windows.Media;
 using System.Windows.Media.Imaging;
 using System.Windows.Shapes;

 using System.Activities.Core.Presentation;
 using System.Activities.Presentation;
 using System.Activities.Statements;

 namespace Exercise1
 {
    /// <summary>
    /// Interaction logic for RehostedWFThroughMarkup.xaml
    /// </summary>
    public partial class RehostedWFThroughMarkup : Window
    {
        public RehostedWFThroughMarkup()
        {
            InitializeComponent();
        }

        protected override void OnInitialized(EventArgs e)
        {
            base.OnInitialized(e);
            // registering metadata
            (new DesignerMetadata()).Register & Property window
            WorkflowDesigner wd = new WorkflowDesigner();
            wd.Load(new Sequence());
            DesignerBorder.Child = wd.View;
            PropertyBorder.Child = wd.PropertyInspectorView;

        }
    }
 }

Viewing Workflow XAML

As workflows are being built through the rehosted WF designer, it would be nice to see the XML that the WF designer generates as changes to the workflow are declaratively made. The custom workflow designer can be extended so that it adds a tab for representing a declarative workflow as XAML, too. The functionality for viewing the XAML produced while authoring workflows in Visual Studio is also provided by right-clicking on a workflow file that has a .XAML file extension and selecting View Code (see Figure 10-10).

9781430243830_Fig10-10.jpg

Figure 10-10.  Viewing a XAML representation of a workflow

Instead of needing to right-click on a workflow file, maybe a better experience would be to provide a way for clients to tab back and forth from a declarative model for the workflow and to its XAML representation. Figure 10-11 shows how tabs can be added to handle this functionality tastefully.

9781430243830_Fig10-11.jpg

Figure 10-11.  Adding tabs for toggling views for a workflow

Clicking on the XAML tab will show the XAML that the WF designer builds behind the scene while authoring a workflow declaratively through activities (see Figure 10-12).

9781430243830_Fig10-12.jpg

Figure 10-12.  XAML representing the workflow being built outside of Visual Studio

Gaining WF4.5 Designer Features

The only features provided with WF4 for visually exploring a workflow within the WF designer were

  • Magnify a workflow
  • Fit to screen
  • Overview

In addition to the features illustrated in Figure 10-13, WF 4.5 offered some new features. You might think that the new features would automatically be inherited when rehosting the WF designer in VS2012 when using the WF4.5 assemblies, but this is not the case. In order for a rehosted WF designer to take advantage of these features, the code in Listing 10-9 must be set after instantiating a WorkflowDesigner object.

9781430243830_Fig10-13.jpg

Figure 10-13.  Selecting different ways for viewing workflow

Listing 10-9.  Enabling the New Features in WF 4.5

WorkflowDesigner WF designer = new WorkflowDesigner();

 WF designer .Context.Services.GetService<DesignerConfigurationService>().AnnotationEnabled = true;
 WF designer .Context.Services.GetService<DesignerConfigurationService>().TargetFrameworkName = new
 System.Runtime.Versioning.FrameworkName(".NET Framework", new Version(4, 5));

 WF designer.Load(new Sequence());

The three new lines of code represent setting the AnnotationEnabled property to true and indicating to the WF designer that target .NET Framework is set to the .NET 4.5 version. Running the application built in the Rehosting WF Components exercise after adding the lines of code in Listing 10-9 enables the following features:

  • Panning for workflows
  • Multi-select of activities
  • Auto-surround with the Sequence activity
  • Toolbox search (available without the code in Listing 10-9)
  • Designer annotations
  • Workflow outline view

These features provide functionality for visually managing workflows through the WF designer. Since WF invests heavily in declaratively building workflows, features released for building workflows in WF4.5 provide essential tools that allow workflow authors to manage the visibility of large workflows.

Panning Workflows

Panning the workflow is a new feature within the WF4.5 designer where a workflow can be grabbed by clicking on a part of the workflow, holding down the left mouse button, and moving the mouse around to view different parts of the workflow. As the left mouse button is selected and released, you can actually see the mouse icon used to indicate that the workflow is being manipulated. To begin panning a workflow, select the hand icon from the toolbar below the workflow, as illustrated in Figure 10-14.

9781430243830_Fig10-14.jpg

Figure 10-14.  Hand icon for panning a workflow

Multi-Selecting Activities

WF 4.5 offers another much needed feature: multi-selecting activities. Selecting multiple activities applies the same functionality that UI designers like ASP.NET and WPF have for selecting multiple UI controls within their respective designers. A user can select more than one UI control and then drag them to another location while keeping the same format intact. After the controls are pasted, they keep the same properties and layout as they did before they were selected. A workflow activity can be selected by clicking it using the left mouse button. Another activity can be selected by pressing the Ctrl button on the keyboard and then using the left mouse button to click it. Figure 10-15 illustrates selecting a Flowchart activity and two WriteLine activities. After selecting workflow activities, they become highlighted, indicating that they have been selected. Right-clicking on a selected workflow activity pops up a dialog menu that will allow all of the selected activities to be cut, copied, pasted, or deleted.

9781430243830_Fig10-15.jpg

Figure 10-15.  Multi-selecting workflow activities

Once the workflow activities are selected, they can be pasted within a new workflow or existing activity within the same workflow. Figure 10-16 indicates that the workflow activities selected in Figure 10-15 have been placed within the Then branch of the If activity, which is also contained within the same workflow.

9781430243830_Fig10-16.jpg

Figure 10-16.  Pasting multiple workflow activities

Auto-Surround Sequencing

A Sequence activity is used for holding more than one child activity, but in WF4 adding a Sequence activity for holding child activities is a manual task when a Sequence activity is not currently available within a workflow and more than one activity is needed for building business logic. WF4.5 provides functionality called auto-surround that allows activities to be dropped onto a workflow and indicates if the new activity should be placed above or below an existing activity, which is not already within a Sequence activity. To understand auto-surround with a Sequence activity better, think about how the If activity is used. There is a Then and Else branch for the If activity; however, if a WriteLine activity is added to the either branch, WF4 does not allow for another activity to be placed within the same branch (see Figure 10-17).

9781430243830_Fig10-17.jpg

Figure 10-17.  Adding more than one activity within the branches of an If activity

WF4.5 allows for another workflow activity to be placed within the Then branch even when an existing WriteLine activity exists within the same branch. In WF4, the existing WriteLine activity would have to be cut from the Then branch so a Sequence activity could be manually added for allowing multiple activities. Instead, WF designer in WF4.5 behaves just as if a Sequence activity already existed and prompts for the new workflow activity as it is dragged within the Then branch in Figure 10-17, indicating if the new workflow activity should be placed above or below the current WriteLine activity. Once a new workflow activity is dropped within the Then branch, both workflow activities are automatically placed within a Sequence activity.  Figure 10-18 indicates that an existing WriteLine activity already existed within the Then branch of the If activity. The WF4.5 WF designer allowed another WriteLine activity to be dragged and dropped within the same Then branch and gave the choice of adding the new WriteLine activity either before or after the existing WriteLine activity. Once the new WriteLine activity was added to the workflow, a Sequence activity was automatically added by the WF designer so both WriteLine activities could exist within the Then branch.

9781430243830_Fig10-18.jpg

Figure 10-18.  Sequence is automatically added while adding a new WriteLine activity

Textbox Search

Textbox search allows properties of a selected workflow activity to be searched, and this functionality is automatically provided while using the WF toolbox in WF4.5. Therefore, the code in Listing 10-8 is not necessary to gain search functionality. Instead it is provided by using the PropertyInspectorView or property grid within the WorkflowDesigner object in WF4.5.

Figure 10-19 illustrates that the If activity has been selected and its two properties are displayed within the property grid. Using the Search textbox located at the top right-hand corner of the WF designer, properties can be searched for the selected workflow activity. This can be an important feature if there are many properties provided for a workflow activity.

9781430243830_Fig10-19.jpg

Figure 10-19.  Sequence is automatically added while adding a new WriteLine activity

Designer Annotations

Being able to add annotations to workflow activities is a feature that has been missing since the initial release of WF. Annotations allow documenting parts of a workflow by adding descriptions to child activities that make up a workflow. Once annotations are added, they can be shown, hidden, or deleted by right-clicking on a workflow activity and selecting Annotations. Figure 10-20 illustrates these options for annotations.

9781430243830_Fig10-20.jpg

Figure 10-20.  Options for annotations in a Flowchart workflow

Hiding annotations provide more designer real estate for displaying a workflow; however, when the annotations are hidden, activities that contain annotations are indicated by an annotation icon. When the mouse is hovers over the icon, the annotation pops up, much like a tooltip (see Figure 10-21). When focus is lost from the annotation icon, the annotation goes away.

9781430243830_Fig10-21.jpg

Figure 10-21.  Hovering over an annotation that is hidden

While hovering over an annotation, you also get 10the ability to pin the annotation to the workflow activity by clicking on the pin within the pop up annotation. Once an annotation is pinned, it shows up within the activity, as illustrated in Figure 10-22.

9781430243830_Fig10-22.jpg

Figure 10-22.  Pinning an annotation to the Flowchart activity

Workflow Outline View

The WF designer offers a new view that allows workflows to be viewed within a tree view, based on the relationship of the activities that make up the workflow. Instead of the workflow illustrated as a logical flow, the activities are arranged logically as an outline. The code for rehosting the outline view is fairly simple. There is an OutlineView property on the WorkflowDesigner object that can be set to another Border element within the XAML, just as the WorkflowDesigner object’s View property is set to show the designer itself. After setting the OutlineView property, the outline view is rehosted within the application.

9781430243830_Fig10-23.jpg

Figure 10-23.  Outline view of workflow

Figure 10-23 models a workflow that contains a Sequence activity that has an If and Flowchart activity. The If activity contains an Assign activity as well. Figure 10-24 shows the workflow that the outline in Figure 10-23 represents.

9781430243830_Fig10-24.jpg

Figure 10-24.  Workflow used for building the outline

The outline view for workflows provides an easier way to see and understand the relationship of the activities that make up very large workflows. As activities within the workflow are selected, the corresponding activities within the outline view are highlighted as well. The same behavior occurs when activities within the outline view are selected first instead of selecting activities within the WF designer. The Property window is also correlated, so as activities are selected, the properties for the selected activity also appear within the workflow Property window. Figure 10-25 shows how the Assign activity is selected within the Outline view, which highlights the workflow in the WF designer and displays the properties for the Assign activity within the WF Property window.

9781430243830_Fig10-25.jpg

Figure 10-25.  Selecting the Assign activity within the Outline view

Rehosting Arguments

At this point, thanks to the custom WPF application that was built in the Rehosting WF Components exercise plus the new features for WF 4.5 and the WF designer that were added in Figure 10-25, the custom application for building workflows is starting to look similar and provide some of the same features for authoring workflows within Visual Studio.  The main difference is that now the developer is in control of what features are provided to the end user for building workflows. Before going any further, Listing 10-10 shows the complete XAML for building the application illustrated in Figure 10-25. The added markup for the XAML includes

  • Tabs for toggling the XAML that represents the workflow and the workflow itself.
  • New ColumnDefinitions and RowDefinitions for laying out the different WF components within the application.

Listing 10-10.  Complete XAML for Rehosting the Designer

<Window x:Class="Exercise1.RehostedWFThroughMarkup"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:WFC="clr-namespace:System.Activities.Presentation.Toolbox;assembly=System.Activities.Presentation"
        xmlns:sys="clr-namespace:System;assembly=mscorlib"
        Title="WF designer" Height="350" Width="650">
    <Window.Resources>
        <sys:String x:Key="WFAssembly">System.Activities, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35</sys:String>
    </Window.Resources>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="1.6*"/>
            <ColumnDefinition Width="1.6"/>
            <ColumnDefinition Width="4*"/>
            <ColumnDefinition Width="1.5*"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition></RowDefinition>
            <RowDefinition></RowDefinition>
        </Grid.RowDefinitions>
        <Border Grid.Column="0" Grid.Row="0">
            <WFC:ToolboxControl>
                <WFC:ToolboxCategory CategoryName="Basic">
                    <WFC:ToolboxItemWrapper AssemblyName="{StaticResource WFAssembly}">
                        <WFC:ToolboxItemWrapper.ToolName>
                            System.Activities.Statements.Sequence
                        </WFC:ToolboxItemWrapper.ToolName>
                    </WFC:ToolboxItemWrapper>
                    <WFC:ToolboxItemWrapper  AssemblyName="{StaticResource WFAssembly}">
                        <WFC:ToolboxItemWrapper.ToolName>
                            System.Activities.Statements.WriteLine
                        </WFC:ToolboxItemWrapper.ToolName>

                    </WFC:ToolboxItemWrapper>
                    <WFC:ToolboxItemWrapper  AssemblyName="{StaticResource WFAssembly}">
                        <WFC:ToolboxItemWrapper.ToolName>
                            System.Activities.Statements.If
                        </WFC:ToolboxItemWrapper.ToolName>

                    </WFC:ToolboxItemWrapper>
                    <WFC:ToolboxItemWrapper  AssemblyName="{StaticResource WFAssembly}">
                        <WFC:ToolboxItemWrapper.ToolName>
                            System.Activities.Statements.Flowchart
                        </WFC:ToolboxItemWrapper.ToolName>

                    </WFC:ToolboxItemWrapper>
                </WFC:ToolboxCategory>
            </WFC:ToolboxControl>
        </Border>
        <Border Grid.Column="0" Grid.Row="1" Name="WFOutline" />
        <Grid Grid.Column="2" Grid.RowSpan="2"  Margin="2" Width="Auto">
            <TabControl x:Name="DesignerXamlTabDeck"  SelectionChanged="DesignerXamlTabDeck_SelectionChanged">
                <TabItem Name="DesignerTab" Header="Designer">
                    <Border Name="CenterBorder" />
                </TabItem>
                <TabItem Name="XamlTab" Header="XAML">
                    <TextBox AcceptsReturn="True" VerticalScrollBarVisibility="Auto" x:Name="XamlEditor"/>
                </TabItem>
            </TabControl>
        </Grid>
        <Border Grid.Column="3" Grid.RowSpan="2" Name="PropertyBorder"/>
    </Grid>
 </Window>

Listing 10-11 contains the code used to write the XAML representation of workflows and to add the XAML and workflow within tabs. It also shows the new code for hosting the new features provided by WF4.5 within the WF designer and the new Outline View for managing activities within a workflow.

Listing 10-11.  OnIntialized Event for Rehosting the Designer and WF Components

public partial class RehostedWFThroughMarkup : Window
 {
        WorkflowDesigner _wd = new WorkflowDesigner();
        public RehostedWFThroughMarkup()
        {
            InitializeComponent();
        }

        protected override void OnInitialized(EventArgs e)
        {
            base.OnInitialized(e);
            // registering metadata
            (new DesignerMetadata()).Register();

            Activity wf = new Sequence(){ Activities ={new Assign<int>()}};
            
           //Adding annotations
            _wd.Context.Services.GetService<DesignerConfigurationService>().AnnotationEnabled = true;
           //Targeting the .NET 4.5 Framework for the configuration service for the WF Designer control
            _wd.Context.Services.GetService<DesignerConfigurationService>().TargetFrameworkName = new
             System.Runtime.Versioning.FrameworkName(".NET Framework", new Version(4, 5));
            _wd.Load(wf);
            PropertyBorder.Child = _wd.PropertyInspectorView;
            DesignerTab.Content = _wd.View;
           //Loads the outline view for the workflow
        //Adds the Outline View
            WFOutline.Child = _wd.OutlineView;
        }
 }

One thing that you may have noticed through all of the illustrations up to this point is that WF arguments have not been available in the rehosted designer. In some cases, business users might need the ability to customize or create WF arguments, so I want to describe how to add functionality of providing arguments and give a technical explanation as to why WF arguments have not been available within the rehosted WF designer application thus far. The code in Listing 10-11 shows the WF designer control loading an Activity object that contains a Sequence activity that contains an Assign activity:

WorkflowDesigner _wd = new WorkflowDesigner();
 //Activity object requires using System.Activities
 Activity wf = new Sequence(){ Activities ={new Assign<int>()}};
 _wd.Load(wf);

By passing in an Activity object to the WF designer, the designer thinks that the workflow is nothing more than an activity, even though additional activities can be added for creating a workflow that looks no different than any other workflow. The difference is in the XAML that is created. Without making any changes to the default activities that the designer loads up, the activities within the designer represent a basic workflow (see Figure 10-26).

9781430243830_Fig10-26.jpg

Figure 10-26.  Default Sequence and Assign activities added within the WF designer

Clicking the XAML tab to look at the XAML that was created reveals that the root element is <Sequence>. This indicates to the WF designer that this is not a workflow but a logical connection of activities (see Figure 10-27).

9781430243830_Fig10-27.jpg

Figure 10-27.  XAML representing the Sequence and Assign activities within the WF designer

Now let’s check out the XAML for a new workflow within Visual Studio and add a Sequence activity with a child Assign activity. To view the XAML for a workflow in Visual Studio, right-click on the XAML file within the Solution Explorer that represents the workflow and select View Code. Notice that Figure 10-28 shows different XAML for the same activities added within the WF designer as represented in Figure 10-25.

9781430243830_Fig10-28.jpg

Figure 10-28.  XAML representing the Sequence and Assign activities within the WF Designer

The XAML in Figure 10-28 has a starting element of <Activity>, which means that the WF designer interprets the added activities as generic workflow activities rather than a specific activity, depending if the activity is out of the box or custom. Even though a particular activity can have arguments, the WF designer uses the Arguments tab for adding arguments that are meant for adding or returning data from a workflow rather than a workflow activity. Instead of loading an Activity object within a rehosted WF designer, an ActivityBuilder object can be loaded instead. Loading an ActivityBuilder object into the WF designer will generate the same XAML as in Figure 10-29 and will use the <Activity> root for the XAML instead of using a particular activity as the root, as illustrated in Figure 10-27.

9781430243830_Fig10-29.jpg

Figure 10-29.  XAML produced by loading an ActivityBuilder object instead of an Activity object

Now the when you tab back to the Designer tab, the Arguments tab appears, indicating that WF arguments can be added to the workflow for trafficking data in and out of the workflow. Figure 10-30 shows how the Arguments tab is now viewable and how a new WF argument can be added to the workflow. An In argument called inArgEmployee is being created and the Argument Type is being searched on within the solution.

9781430243830_Fig10-30.jpg

Figure 10-30.  Adding a WF Argument within a rehosted WF designer

The code for creating the ActivityBuilder object is not too difficult. Listing 10-12 illustrates how to add a new Sequence activity and a child Assign activity to the workflow.

Listing 10-12.  OnIntialized Event for Rehosting the Designer and WF Components

private ActivityBuilder BuildBaseActivity()
 {
    try
    {
        ActivityBuilder builder = new ActivityBuilder
        {

            Name = "CustomWorkflow",
            Implementation = new Sequence()
            {
                Activities =
                 {
                    new Assign<Int32>()
                 }
            }
        };

        return builder;
    }
    catch (Exception)
    {
        throw;
    }
 }

The code in Listing 10-12 is a function that returns an ActivityBuilder object. Once the ActivityBuilder object is instantiated, the first things to set are the Name and Implementation properties. The Name property will identify the workflow, as illustrated in Figure 10-30 at the top of the workflow and just underneath the Designer tab. The Implementation property sets the activities that will be used within the workflow. First, a Sequence activity is instantiated and then its Activities property is used to add child activities to the Sequence activity. In this case, an instantiated Assign<Int32> activity is added. Finally, to get the WorkflowDesigner to load the ActivityBuilder, the line of code in Figure 10-9,

 _wd.Load(wf)

can be changed to load the ActivityBuilder object that is returned in Listing 10-10 by using

 _wd.Load(BuildBaseActivity())

instead. Most importantly, in order to use the ActivityBuilder object, you must add System Activities as a using statement.

Programmatically Adding Arguments

When asking the WF Team at Microsoft what they think is the main use for applications that rehost the WF designer, most of the feedback indicates that majority of the requirements are for applications by domain users or end users who are not as technical as developers, but still require the ability to build custom logic through workflows. Providing these types of users the ability to add WF arguments might not be something you want to do; at the same time, you don’t want to lose the ability to provide WF arguments to workflows. Instead, a simple solution might be to programmically add them instead. This would provide an abstraction to the end users. To hide the options for WF arguments and imports, you must add the following using statement:

 using System.Activities.Presentation.View;

The options can then be hidden by this line of code:

        _wd.Context.Services.GetService<DesignerView>().WorkflowShellBarItemVisibility = ShellBarItemVisibility.None;

The line of code should be added after calling the Load method for loading default activities for the WorkflowDesigner object. Running the application after these lines of code are added removes the entire toolbar so end users are not made aware of arguments, variables, and imports for the workflow (see Figure 10-31).

9781430243830_Fig10-31.jpg

Figure 10-31.  Removing the toolbar for the Variables, Arguments, and Imports tabs

Next, WF arguments can be programmatically created and automatically applied to a workflow. The ActivityBuilder object also provides a Properties property that can be set to WF arguments that are intended to be used within the workflow. Listing 10-13 illustrates how WF arguments can be loaded into a workflow through code.  First, a new DynamicActivityProperty must be added to the Properties property for the ActivityBuilder. The DynamicActivityProperty has four properties that need to be set:

  • Name: Name of the argument
  • Type: The typeof argument (In, Out, both)
  • Attributes: Dictates behavior (such as Required or not)
  • Value: An instantiation of the object type as set within the Type property

Listing 10-13.  Programmically Adding WF Arguments to a Workflow

ActivityBuilder builder = new ActivityBuilder
         {

            Name = "CustomWorkflow",
            Implementation = new Sequence()
            {
                Activities =
                 {
                     new Assign<Int32>()
                 }
            },
            Properties =
            {
                 new DynamicActivityProperty
                 {
                      Name = "inArgFullName",
                      Type = typeof(InArgument<string>),
                      Attributes =
                      {
                          new RequiredArgumentAttribute(),
                      },
                      Value
                      = new InArgument<string>
                         {
                             Expression =
                             new InArgument<string>("Bayer White")
                         }
                }
            }
        };

The InArgument object that is set to the Value property has an Expression property. Since the InArgument<string> argument accepts a string, it is natural to think that the Expression property can simply be set to a string; however this is not the case. The Expression property should be set to new InArgument<string>("Bayer White") instead.

Managing Workflows

Visual Studio provides a developers experience in building and managing workflows that are either created using C# code or XAML, and each workflow is associated to a file that is created and added to a .NET project. When rehosting the WF designer, managing workflows are built, but it’s different because the purpose is to either run workflows within the same custom application that is used to build workflows or to build workflows that are meant to be run within other external client applications. Both scenarios are legitimate reasons for rehosting the WF designer; however, there can be a greater impact in building workflows that can be reused in more than one application.

Building workflows that can be distributed and run within other software is a very powerful feature of WF. In order for the workflows to be used within other software applications, the application rehosting the WF designer needs to provide a way to save the workflows. As workflow activities are added to the WorkflowDesigner object, the XAML built behind the scene represents the logical relationship of the workflow activities. WF provides the same functionality to rung workflows built using XAML, similar to how workflows that are built using C# are executed, but first the XAML needs to be saved and stored. This section will show you how to save a workflow as XAML on the file system so it can be retrieved by other applications.

Setting Up the UI for Managing Workflows

First, you must make some UI changes to handle the functionality for creating, saving, and loading a workflow. Listing 10-14 demonstrates how to add a ToolBar control within a DockPanel control so it can be placed at the top left corner of the application. The Toolbar control contains four buttons for creating a new workflow or updating, loading, or deleting an existing workflow. There is also a Label and Textbox control used to indicate the name of a workflow that is intended to be managed.

Listing 10-14.  Adding a Toolbar for Managing Workflows

<Grid Grid.Column="0" Grid.Row="0">
            <DockPanel LastChildFill="True" VerticalAlignment="Top">
                <ToolBar DockPanel.Dock="Top">
                    <Button Name="cmdNew" Click="cmdNew_Click">Create</Button>
                    <Button Name="cmdSave" Click="cmdSave_Click">Save</Button>
                    <Button Name="cmdDelete" Click="cmdDelete_Click">Delete</Button>
           <Button Name="cmdLoad" Click="cmdLoad_Click">Load</Button>
                </ToolBar>
                <Label DockPanel.Dock="Top">Workflow Name:</Label><TextBox Name="txtWorkflowName"  DockPanel.Dock="Top"></TextBox>
                <WFC:ToolboxControl DockPanel.Dock="Bottom">
                    <WFC:ToolboxCategory CategoryName="Basic">
                        <WFC:ToolboxItemWrapper AssemblyName="{StaticResource WFAssembly}">
                            <WFC:ToolboxItemWrapper.ToolName>
                                System.Activities.Statements.Sequence
                            </WFC:ToolboxItemWrapper.ToolName>
                        </WFC:ToolboxItemWrapper>
                        <WFC:ToolboxItemWrapper  AssemblyName="{StaticResource WFAssembly}">
                            <WFC:ToolboxItemWrapper.ToolName>
                                System.Activities.Statements.WriteLine
                            </WFC:ToolboxItemWrapper.ToolName>

                        </WFC:ToolboxItemWrapper>
                        <WFC:ToolboxItemWrapper  AssemblyName="{StaticResource WFAssembly}">
                            <WFC:ToolboxItemWrapper.ToolName>
                                System.Activities.Statements.If
                            </WFC:ToolboxItemWrapper.ToolName>

                        </WFC:ToolboxItemWrapper>
                        <WFC:ToolboxItemWrapper  AssemblyName="{StaticResource WFAssembly}">
                            <WFC:ToolboxItemWrapper.ToolName>
                                System.Activities.Statements.Flowchart
                            </WFC:ToolboxItemWrapper.ToolName>

                        </WFC:ToolboxItemWrapper>
                    </WFC:ToolboxCategory>
                </WFC:ToolboxControl>
            </DockPanel>
        </Grid>

Code can now be added to create the functionality for managing workflows through the UI. The code has been changed a bit to accommodate functionality for managing workflows. Workflows will be stored as XAML files within the same file path as the application’s execution file. Listing 10-15 shows the code for managing the folder where the workflows will be stored.

Listing 10-15.  Managing the Workflow Folder

public partial class RehostedWFThroughMarkup : Window
    {
        const string WorkflowFolder = "\CreatedWorkflows\";
        WorkflowDesigner _wd = null;

Next, you need a method to instantiate the WorkflowDesigner object each time a new workflow is loaded. This includes workflows that already exist and are loaded into the WF designer, and workflows that are being created. The WF designer no longer needs to be created as OnInitialized (see Listing 10-16).

Listing 10-16.  Instantiating the WorkflowDesigner Object Each Time a New Workflow is Loaded

private void BuildNewDesigner()
        {//Designer must be instantiated each time a workflow is loaded

            try
            {
                _wd = new WorkflowDesigner();

                _wd.Context.Services.GetService<DesignerConfigurationService>().AnnotationEnabled = true;
                _wd.Context.Services.GetService<DesignerConfigurationService>().TargetFrameworkName = new
                 System.Runtime.Versioning.FrameworkName(".NET Framework", new Version(4, 5));
                //_wd.Load(wf);
                //_wd.Context.Services.GetService<DesignerView>().WorkflowShellBarItemVisibility = ShellBarItemVisibility.None;
                PropertyBorder.Child = _wd.PropertyInspectorView;
                DesignerTab.Content = _wd.View;
                WFOutline.Child = _wd.OutlineView; // Adds the Outline View

            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
        protected override void OnInitialized(EventArgs e)
        {
            base.OnInitialized(e);
            // registering metadata
            (new DesignerMetadata()).Register();
        }

Functionality for each of the buttons can now be implemented based on each of the button’s click event. Listing 10-17 illustrates code to create a new workflow. The code calls the BuildNewDesigner code illustrated in Listing 10-16 to instantiate the WorkflowDesigner object. This is important for calling the Load method on a WorkflowDesigner, because each time it is anticipating loading a new workflow, the WorkflowDesigner must be instantiated. The BuildBaseActivity defined in Listing 10-12 is then called within the Load method of the WorkflowDesigner, which returns an ActivityBuilder object that is to be loaded within the rehosted WF designer.

Listing 10-17.  Button Click Functionality for Creating a New Workflow

private void cmdNew_Click(object sender, RoutedEventArgs e)
         {
            try
            {
                if (txtWorkflowName.Text.Length == 0)
                    txtWorkflowName.Text = "CustomWorkflow";
                
                BuildNewDesigner();
                 _wd.Load(BuildBaseActivity());
            }
            catch (Exception ex)
            {
                throw ex;
            }
         }

Listing 10-18 illustrates the code for saving a workflow that has been loaded into a WorkflowDesigner. The code first checks to see if a workflow name has been provided; if not, the user is prompted to provide a workflow name. The application’s executable file path is used for the location by declaring Directory.GetCurrentDirectory() along with the a particular folder and name provided for storing the workflow. The Flush method is executed on the WorkflowDesigner object so that the XAML representing the workflow is pushed into the WorkflowDesigner’s Text property. If the file path for saving a workflow does not exist, it is created. The WorkflowDesigner’s Save method is called to save the XAML file to the specified file path.

Listing 10-18.  Button Click Functionality for Saving a Workflow

private void cmdSave_Click(object sender, RoutedEventArgs e)
 {
    try
    {
        if (!string.IsNullOrWhiteSpace(txtWorkflowName.Text))
        {
            if (_wd!=null)
            {
                _wd.Flush();
                if (_wd.Text.Length>0)
                {
                    var wfFile = Directory.GetCurrentDirectory() + WorkflowFolder +txtWorkflowName.Text + ".xaml";

                    if (!Directory.Exists(Directory.GetCurrentDirectory() + WorkflowFolder))
                        Directory.CreateDirectory(Directory.GetCurrentDirectory() +WorkflowFolder);
                    
                    _wd.Save(wfFile);
                   MessageBox.Show("Workflow has been saved!");
                }
            }
            else
                MessageBox.Show("Please press 'Create' to build a new workflow!");
        }
        else
        {
            MessageBox.Show("Please enter a workflow name!");
        }
    }
    catch (Exception ex)
    {
        throw ex;
    }
 }

Workflows can also be deleted once they are created. Listing 10-19 illustrates that a workflow name must be provided; if the file exists, then it is deleted.

Listing 10-19.  Button Click Functionality for Deleting a Workflow

private void cmdDelete_Click(object sender, RoutedEventArgs e)
 {
    try
    {
        if (!string.IsNullOrWhiteSpace(txtWorkflowName.Text))
        {
            
            var wfFile = Directory.GetCurrentDirectory() + WorkflowFolder + txtWorkflowName.Text + ".xaml";

            if (!File.Exists(wfFile))
                MessageBox.Show("Workflow does not exist!");
            else
            {
                File.Delete(wfFile);
                MessageBox.Show("Workflow has been removed!");
            }
        }
        else
        {
            MessageBox.Show("Please enter a workflow name!");
        }
    }
    catch (Exception ex)
    {
        throw ex;
    }
 }

Finally, an existing workflow can be loaded so it can be modified further. Listing 10-20 illustrates how a workflow’s XAML file is checked to see if it exists. If the workflow exists, a new WorkflowDesigner object is again instantiated because the XAML file has to be loaded within the WorkflowDesigner. Once the workflow is loaded, the rehosted WF designer reflects the loaded workflow.

Listing 10-20.  Button Click Functionality for Loading a Workflow

        private void cmdLoad_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                if (!string.IsNullOrWhiteSpace(txtWorkflowName.Text))
                {

                    var wfFile = Directory.GetCurrentDirectory() + WorkflowFolder + txtWorkflowName.Text + ".xaml";

                    if (!File.Exists(wfFile))
                        MessageBox.Show("Workflow does not exist!");
                    else
                    {
                        BuildNewDesigner();
                        _wd.Load(wfFile);
                    }
                }
                else
                {
                    MessageBox.Show("Please enter a workflow name!");
                }
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
    }
 }

Running the application after implementing the functionality for managing workflows will display the toolbox so that workflows can be created (see Figure 10-32).

9781430243830_Fig10-32.jpg

Figure 10-32.  Toolbar for managing workflows

Figure 10-33 shows how selecting the Create button loads a default workflow into the designer. Once the workflow is loaded, the Outline view for the workflow becomes visible based on the default activities loaded into the WF designer. I also changed the value of the Implementation property of the ActivityBuilder to Implementation = new Flowchart(). As a new workflow is loaded, it only loads a Flowchart activity that can be extended.

9781430243830_Fig10-33.jpg

Figure 10-33.  Creating a new workflow

CustomWorkflow is the name given to the new workflow and it can now be modified by adding a new If activity to the workflow. Clicking on the Save button within the toolbar saves the workflow to the file system (see Figure 10-34).

9781430243830_Fig10-34.jpg

Figure 10-34.  Saving a new workflow

The workflow has now been saved on the local file path where the executable running the application is located and within the specified folder of CreatedWorkflows. Figure 10-35 shows the file opened in Notepad and the XAML used to save the workflow. Shutting down the application and then restarting it again, the workflow illustrated in Figure 10-34 is loaded into the WF designer so it can be modified. After modifying the workflow, it can be saved again and reloaded again at a later time. Clicking the Delete button will remove the workflow file from the file path.

9781430243830_Fig10-35.jpg

Figure 10-35.  Viewing the XAML that represents the workflow

Workflows for Client Applications

Now that that the application can manage workflows, it’s time to demonstrate how the workflows that are created through the rehosted WF designer and stored on the file system can be used within other applications. In this case, you’ll create a simple console application that simulates receiving orders from customers. Every three seconds a new order is received; however there is no business logic that specifies what should be done after an order is received. This is a basic example for how an application and business logic can be separated out because business logic is almost guaranteed to change over time.

Figure 10-36 illustrates how each order is generated and displayed within the console window. Listing 10-21 shows the code used to simulate random orders. First, products and prices are specified through arrays, then an infinite loop is run to create an order based on generating a random number between 0 and 5 to indicate the index for an item within the products or prices array. After each order is written out to the console, the thread executing the console application is put to sleep for three seconds. After three seconds are up, an Order object is built with the product and a price value indicated by the random index value is passed to the method GetLatestWorkflow. GetLatestWorkflow checks the file path for where workflows are stored via the rehosted WF designer application to look for the latest workflow file that was saved to the directory. Once the latest file is found, the name of the file and workflow file path are used to create an Activity object by calling ActivityXamlServices.Load. This Activity object is then be passed to the WF runtime by calling WorkflowInvoker and processing synchronously within the console application. The Order object is also passed with the workflow as the WF argument, indicating the order that initializes the business logic within the workflow.

9781430243830_Fig10-36.jpg

Figure 10-36.  Simulating receiving orders from customers

Listing 10-21.  Simulating Random Orders

   using System;
   using System.Collections.Generic;
   using System.IO;
   using System.Linq;
   using System.Text;
   using System.Threading.Tasks;
   using System.Activities;
   using System.Activities.XamlIntegration;
   using Chapter10  .Model;

    namespace ProcessOrders
   {
      class Program
      {
          static void Main(string[] args)
          {
              string[] products =
                  new string[]
                  {
                      "Widget1",
                      "Widget2",
                      "Widget3",
                      "Widget4",
                      "Widget5"
                  };
              decimal[] prices =
                  new decimal[]
                  {
                      5.40m,
                      10.25m,
                      2.00m,
                      4.30m,
                      6.45m
                  };

              Console.WriteLine("Starting Order Processor…");

              while (1 == 1)
              {
                  var rand = new Random();
                  var currentIndex = rand.Next(0, 4);
                  Console.WriteLine("Order for {0}, costing {1}"
                      , products[currentIndex]
                      , string.Format("{0:C}", prices[currentIndex]));

                  System.Threading.Thread.Sleep(3000);
                  GetLatestWorkflow(
                      new Order
                              {
                                  Price = prices[currentIndex],
                                  Product = products[currentIndex]
                              });
              }
          }

          private static void GetLatestWorkflow(Order processedOrder)
          {
              string wfPath
                  = @"C:UserswhiteDocumentsVisual Studio 11ProjectsApress.Chapter  10  Example1Example1inDebugCreatedWorkflows";
              try
              {
                  var dirInfo = new DirectoryInfo(wfPath);
                  var latestWF = (from wfFile in dirInfo.GetFiles()
                                  orderby wfFile.LastWriteTime descending
                                  select wfFile.Name).FirstOrDefault();

                  if (!string.IsNullOrWhiteSpace(latestWF))
                  {
                      var wf
                          = ActivityXamlServices.Load(wfPath + latestWF);

                      WorkflowInvoker.Invoke(wf, new Dictionary<string, object>() { { "inArgNewOrder", processedOrder } });
                  }
              }
              catch (Exception ex)
              {
                  throw ex;
              }
          }
      }
   }

Listing 10-22 defines the Order object that is created and passed into the workflow as arguments. It is also referenced within the same project used to create the application that rehosts the WF designer.

Listing 10-22.  Order Object Used to Pass Parameters into the Workflow

using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Text;
 using System.Threading.Tasks;

 namespace Chapter10.Model
 {
    public class Order
    {
        public string Product { get; set; }
        public decimal Price { get; set; }
    }
 }

The XAML file hosting the WF designer is changed so that the WF toolbox of activities now includes a WriteLine activity. The XAML in Listing 10-23 includes the activity.

Listing 10-23.  Including a WriteLine Activity when Rehosting the WF Designer

<WFC:ToolboxItemWrapper  AssemblyName="{StaticResource WFAssembly}">
        <WFC:ToolboxItemWrapper.ToolName>
                System.Activities.Statements.WriteLine
        </WFC:ToolboxItemWrapper.ToolName>
 </WFC:ToolboxItemWrapper>

The last bit of code changes the ActivityBuilder to include a Flowchart activity within the workflow and creates an InArgument<Order> that indicates the Order object specified in Listing 10-24 as the type of object that will be passed in as an argument of the workflow.

Listing 10-24.  Building the ActivityBuilder for the Default Workflow

ActivityBuilder builder = new ActivityBuilder
                {

                    Name = txtWorkflowName.Text!=string.Empty?txtWorkflowName.Text:txtWorkflowName.Text,
                    Implementation = new Flowchart(),
                    Properties =
                    {
                        new DynamicActivityProperty
                        {
                             Name = "inArgNewOrder",
                             Type = typeof(InArgument<Order>),
                             Attributes =
                             {
                                 new RequiredArgumentAttribute(),
                             },
                             Value = new InArgument<Order>()
                        }
                    }
                };

The next time the application that is rehosting the WF designer is opened and the Created button is clicked, a new Flowchart workflow will be created within the WF designer and a WriteLine activity will be added within the WF toolbox. Clicking on the Arguments tab at the bottom of the workflow indicates a default parameter of type Order, which can be passed into the workflow while it is being loaded within the WF runtime (see Figure 10-37).

9781430243830_Fig10-37.jpg

Figure 10-37.  Default Flowchart activity and default parameter

Adding a new WriteLine activity to the workflow and setting its Text property to

"Passed in order product "+inArgNewOrder.Product+" and price: "+inArgNewOrder.Price.ToString()

the workflow can be saved. Running the order processor again now shows the description written from the workflow that was loaded into the application (see Figure 10-38).

9781430243830_Fig10-38.jpg

Figure 10-38.  Console application processing the workflow

Dynamic Business Logic

Now you’re ready to see the power of building workflows within a rehosted WF designer. Leave the console application running so it can continue to collect orders. At the same time, you should reload the workflow from the file system and modify it by adding another WriteLine activity to the workflow as the last activity to be processed. Set the Text property to "Workflow has been changed!" After saving the workflow back to the file system, the console application will immediately pick up the change by writing out to the console, "Workflow has been changed!" without the need to recompile the console application. This represents a true separation between the business process and application (see Figure 10-39).

9781430243830_Fig10-39.jpg

Figure 10-39.  Implementing workflow changes during runtime

Decisions on how to manage orders that are received can now be incorporated and defined during runtime. Using the If activity provides flexibility in determining how an order is handled. In this next example, you are going to use the If activity to control the flow of a received order and use the WriteLine activity to simulate an activity by writing out to the console what should happen. This is a good way of letting a non-technical user provide a model for how a process should be performed. As an order is received, the flow of the order will be indicated through the console using the WriteLine activities that are executed.

Here is the business logic that will be applied:

  1. Orders over $10.00 must be approved.
  2. Customers who place orders between $2 and $5 dollars should be sent current sales reminders included in the order.
  3. Customer’s that order Widget2 should receive a one year warranty for Widget2.

For orders that are over $10, an If activity is added to the workflow and its Condition property is set to inArgNewOrder.Price>10. A WriteLine activity can then be added within the Then branch of the If activity to indicate what needs to happen. In a real world application, a composite activity composed of out-of-box activities could be used to define the approval process or even better, custom activities could be developed to handle the approval process as well; however, in this case the WriteLine activity is used to simply demonstrate how the flow of an order can be changed dynamically during runtime (see Figure 10-40).

9781430243830_Fig10-40.jpg

Figure 10-40.  Defining the workflow for handling orders over $10

The Else branch of the If activity illustrated in Figure 10-40 will handle orders placed between $2 and $5 so that current sales reminders are sent to the customer along with the order (see Figure 10-41).

9781430243830_Fig10-41.jpg

Figure 10-41.  Defining the workflow for handling orders between $2 and $5 dollars

Now that the workflow checks for the appropriate price points, the last rule that needs to be implemented within the workflow is to check for orders where Widget2 was ordered so that the customer receives a one year warranty. Clicking the CustomWorkflow indicated in Figure 10-41, which is above the workflow, the workflow changes its view within the designer to the root of the workflow. Another If activity can be added beneath the existing If activity illustrated in Figure 10-42 so the last business rule can be created.

9781430243830_Fig10-42.jpg

Figure 10-42.  Adding another If activity to the root of the workflow

Since the last rule only needs to check for an order that includes Widget2, there is no need for this rule to be a part of the price checks dictated by the other two rules. Pressing the Save button to save the workflow, the new rules created immediately take effect (see Figure 10-43).

9781430243830_Fig10-43.jpg

Figure 10-43.  Rule changes taking effect during runtime

Figure 10-43 illustrates that the new rules are working correctly. The first message indicates that sales reminders should be sent to the customer. This is because the order price was $4.30, which is between $2 and $5 dollars. The next message indicates that an order needs approval because the order price was over $10; however the console also indicates that the same order that is over $10 was also an order for Widget2, therefore the customer should also receive a warranty.

Summary

Business logic built for software applications using workflows no longer has to rely on developers to integrate new logic as business processes change. By rehosting WF components within custom software, end users are given the power to make business logic changes as they see fit. The power of WF comes from the visualization it provides in building workflows. WF extends this power of workflow visualization and orchestration to end users, who may be non-technical but may require the same advantages as developers in building custom workflows.

This chapter focused on how the WF designer can be rehosted within a custom WPF application using code and also XAML, so changes could be made via XML rather than changing code. Some new features were added within the new WF designer in WF4.5 and this chapter discussed how these enhancements could also be added when rehosting the WF designer. The application demonstrated how to manage workflows that were built while rehosting the WF designer by saving the XAML files representing the workflows to the file system so other applications could execute the workflow. The highlight of the chapter was the creation of an ordering application that simulated orders being received. Workflows were built for defining business logic outside of the application, and then you added this logic while the application was running so the workflow could manage how the application processed orders.

The next chapter will show how workflows can be used to define business logic within WCF services. WCF services that are authored as workflows can also maintain state, thereby defining SOA architectures using workflows rather than code.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.14.131.212