Developing Activities


Even though Windows Workflow Foundation provides a great deal of functionality and is extremely extensible, you may have situations where you want to develop custom workflow activities from scratch. For example, you there may be a need specific to your business that dictates the development of a new activity. This doesn’t mean that you won’t also need more generic activities that were not covered in the base API, but the online Windows Workflow Foundation community may have already solved a lot of these needs. For example, the Base Activity Library does not come with an activity that can execute commands against a SQL Server database. However, even before Windows Workflow Foundation was released, many community-contributed activities were available on the official website: wf.winfx.com.

With that said, there may be times when developing a new activity is the only answer. The following sections can help get you started in this area and make the most of your Windows Workflow Foundation platform.

To pique your interest before all the necessary foundational information is covered, here is a quick-and-dirty example of the simplest activity you could imagine. This HelloWorldActivity activity uses a property, NameOfPerson, which is set at design time to print a personalized message on the console.

  using System; using System.ComponentModel; using System.Workflow.ComponentModel; namespace HelloWorldActivity {     public sealed class HelloWorldActivity : Activity     {         public static DependencyProperty NameOfPersonProperty =             DependencyProperty.Register(                 "NameOfPerson",                 typeof(string),                 typeof(HelloWorldActivity));         [Description("Someone's name who wants a personalized message.")]         [Category("Display")]         [Browsable(true)]         public string NameOfPerson         {             get             {                 return (string)base.GetValue(                     HelloWorldActivity.NameOfPersonProperty);             }             set             {                 base.SetValue(HelloWorldActivity.NameOfPersonProperty, value);             }         }         public HelloWorldActivity()         {         }         protected override ActivityExecutionStatus Execute(             ActivityExecutionContext executionContext)         {             // present the personalized message if a name was provided             if (!String.IsNullOrEmpty(this.NameOfPerson))                 Console.WriteLine(String.Format("Hello {0}!", this.NameOfPerson));             else                 Console.WriteLine("I don't know who to say hello to :(");             // we're done; return a "Closed" status             return ActivityExecutionStatus.Closed;         }     } } 

The Activity Class

The Activity class is located in System.Workflow.ComponentModel and is the base class from which all other activities inherit. The Activity class itself inherits from DependencyObject, which is discussed later in this chapter (after a bit more context). This class provides all the basics you need to build your own activities.

To implement the logic necessary for a custom activity to function, you use three methods: Initialize, Execute, and Cancel. All three methods are called by the workflow runtime (not manually) and are passed context information related to the current execution environment. Both Execute and Cancel are passed an instance of the ActivityExecutionContext class, and the Initialize method is passed an IServiceProvider instance. You can use either of these parameter types to access valuable information about the runtime or to manipulate runtime behavior.

To implement a basic activity, you need only to inherit from the Activity class and override the Execute method. Of course, the interesting logic that the activity provides should be implemented in Execute. The Execute method returns a value from the ActivityExecutionStatus enumeration. In most normal circumstances in which the Execute method has done its business and is completely finished, it returns ActivityExecutionStatus.Closed. Other possible values are Canceling, Compensating, Executing, Faulting, and Initialized. The diagram shown in Figure 6-7 represents the states in which an activity can be and which states can transition to others.

image from book
Figure 6-7

The CompositeActivity Class

The CompositeActivity class inherits from Activity and provides a base from which activities that provide a parent–child model can inherit. This level of the inheritance tree introduces the Activities property, which is a collection of all child activities that have been added to this, the parent. The CompositeActivity class also has a property called EnabledActivities, which retrieves all activities that would be returned from the Activities property, not including disabled and special activities such as CancellationHandler and CompensationHandler.

Activity Properties

As you have seen, activities generally expose properties that affect their runtime behavior. You may be familiar with exposing properties on standard .NET types, but Windows Workflow Foundation activity properties are different enough to cause a slight learning curve. The following sections cover activity properties and their associated traits.

DependencyObject and DependencyProperty

These classes provide the foundation for the activity data storage mechanism. Think back to the workflow tenets introduced in Chapter 1. The requirement that workflows remain stateful is very important, and these two classes play an important role in that constraint. In addition, dependency objects and their associated properties allow activities to project their properties onto their children.

For example, the ConditionedActivityGroup activity uses dependency properties to apply its WhenCondition property to its children. Figure 6-8 shows a Code activity added as a child to a ConditionedActivityGroup activity. In addition to the normal properties for a Code activity, there is a WhenCondition. Even though this appears to be part of the Code activity, it is actually inherited from its parent.

image from book
Figure 6-8

DependencyObject is basically a hash table that uses DependencyProperty instances as its key to retrieve values for a particular DependencyObject instance. To use dependency properties in activities, you generally follow a common pattern. You add a public static instance of a DependencyProperty to manage that property for all instances of the activity. Then you add a standard .NET property with get and set accessors. The get accessor uses the base GetValue method defined in the DependencyObject class (which the Activity class inherits from) to retrieve the stored value of the property for that activity instance by passing the static DependencyProperty instance mentioned earlier. Similarly, the set accessor uses the DependencyObject.SetValue method.

The previous paragraph describes a situation in which you use dependency properties to represent variable values such as strings or some other object type. You can also use dependency properties to manage other entities such as events. The following code shows an example of using dependency properties with events. Remember, the AddHandler and RemoveHandler methods are defined in the DependencyObject class, which is the base class for Activity.

  public class MyActivity : Activity {    public static readonly DependencyProperty SomethingHappenedEvent;    public event EventHandler SomethingHappened    {       add       {          base.AddHandler(MyActivity.SomethingHappenedEvent, value);       }       remove       {          base.RemoveHandler(MyActivity.SomethingHappenedEvent, value);       }    } } 

Property Types

In Windows Workflow Foundation, there are two types of activity properties: meta and instance. You must set meta properties to static values. You can set instance properties at runtime or bind them to another instance variable that provides the values during runtime.

Meta properties are like those introduced in the previous section. They use DependencyProperty instances and the DependencyObject infrastructure to manage their values. Conversely, if you added a standard .NET property to an activity, this is considered an instance property because its value is derived from an instance member during runtime. You can use traits from both of these property types through property binding.

Activity Property Binding

Property binding enables you to combine dependency properties with instance members that can be modified at runtime. To do this, you use the ActivityBind class to map a path from a dependency property to a dynamic instance member such as a variable, event, or method.

For example, the Throw activity has a dependency property called Fault that specifies an exception instance to throw when the activity is executed. In this case, you can use the ActivityBind class to map an Exception instance to the Fault property at runtime. This enables you to modify the exception instance during runtime, prior to the execution of the Throw activity. Here is the code you would use to configure this binding:

  ActivityBind faultBinding = new ActivityBind(); faultBinding.Name = "MyWorkflow"; faultBinding.Path = "ex"; throwActivity.SetBinding(ThrowActivity.FaultProperty, faultBinding); 

As you can see, two properties provide the path to the exception instance: Name and Path. Name specifies the name of the class where the member variable, Path, can be found. Then the SetBinding method of the Throw activity is called. The dependency property, ThrowActivity.FaultProperty, and the ActivityBind instance are passed to this method to complete the binding operation.

Visual Studio can automatically perform property binding. To set this up, you use the property binding dialog box (see Figure 6-9), which you can access by clicking the ellipsis button in the Fault property text box in the Properties Explorer. Figure 6-9 shows the property binding dialog box for the Throw activity example. From here, you can select an existing instance member, in this case ex, to bind to the Fault property. You can also have a new instance member or dependency property automatically generated using this dialog box’s Bind to a new member tab.

image from book
Figure 6-9

Property Attributes

You can apply attributes to activity properties to enhance their design-time and runtime behavior. Table 6-1 lists these attributes. Many of these attributes are used not only by Windows Workflow Foundation, but by many other components outside the workflow world as well.

Table 6-1: Activity-Related Attributes
Open table as spreadsheet

Attribute

Description

BrowsableAttribute

Indicates whether or not the property will be displayed in the Properties Explorer of Visual Studio.

DefaultValueAttribute

Identifies a default value for the property.

DesignerSerialization VisibilityAttribute

Specifies the method of persistence to be used during design-time serialization of the property.

EditorAttribute

Specifies the class that should be used as the UI editor for the property. For example, the CallExternalMethod activity uses this attribute to specify that the TypeBrowser Editor class should be used to set the InterfaceType property.

ParenthesizeProperty NameAttribute

Indicates whether or not a property should be surrounded by parentheses. A property enclosed in parentheses appears at the top of an alphabetical list. This attribute is commonly applied to a component’s Name property.

PersistOnCloseAttribute

This attribute is specific to Windows Workflow Foundation and denotes that a workflow should be persisted when the activity has been executed successfully.

MergablePropertyAttribute

When this attribute passes a value of true, it indicates that the property can be combined with properties belonging to other objects in Visual Studio’s Properties Explorer.

RefreshPropertiesAttribute

Specifies which properties of the current object should be refreshed in the Properties Explorer when the current property is changed. This is useful when other properties’ values are dependent on another property.

TypeConverterAttribute

Specifies the TypeConverter class type that should be applied to the property.

Activity Components

You can use component attributes to apply custom behaviors to an activity class. You implement these custom behaviors by inheriting from one of the following classes:

  • System.Drawing.Design.ToolboxItem

  • System.ComponentModel.Designer

  • System.Workflow.ComponentModel.Compiler.ActivityValidator

  • System.Workflow.ComponentModel.Serialization.WorkflowMarkupSerializer

  • System.Workflow.ComponentModel.Compiler.ActivityCodeGenerator

The following sections discuss these classes.

ToolboxItem

You use the ToolboxItem class to specify how a designable component is represented in Visual Studio’s Toolbox window. You can specify properties such as the component’s display name and visual icon representation.

In addition, you use this class to define the component’s default state. For example, think about the IfElse activity. When you drag and drop the IfElse component from the Toolbox to the workflow designer, it automatically contains two IfElseBranch activities. Although it may seem as though this happens by magic, it definitely does not.

The following code snippet is taken from an IfElseToolboxItem class that was reverse-engineered using Reflector. As you can see, the CreateComponentsCore method creates an instance of the IfElseActivity class and then adds two instances of the IfElseBranchActivity class to it. The IfElseActivity instance is then returned in an IComponent array and added to the workflow by Visual Studio.

  protected override IComponent[] CreateComponentsCore(IDesignerHost designerHost) {       CompositeActivity activity1 = new IfElseActivity();       activity1.Activities.Add(new IfElseBranchActivity());       activity1.Activities.Add(new IfElseBranchActivity());       return new IComponent[] { activity1 }; } 

You apply a ToolboxItem class to an activity by decorating an activity class with the System.ComponentModel.ToolboxItemAttribute class and passing a Type reference to its constructor.

Designer

As you well know by now, activities have a visual component that is used during workflow design time. Traits such as text and image layout are defined in activity designer classes. Each activity designer ultimately inherits from the ActivityDesigner class that is defined in System.Workflow.ComponentModel.Design.

This class has important properties such as ImageRectangle and TextRectangle as well as important methods such as Initialize and OnLayoutSize. However, you can override these properties and methods to provide custom layout logic for new activities.

In addition, you can decorate custom activity designer classes with the ActivityDesignerTheme attribute to specify custom activity designer theme classes. These theme classes inherit from Activity DesignerTheme and define various visual traits of a custom activity, such as the foreground and background colors as well as border styles. Here is a short example of a custom activity designer theme class:

  public class MyActivityDesignerTheme : ActivityDesignerTheme {     public MyActivityDesignerTheme(WorkflowTheme theme) : base(theme)     {     }     public override void Initialize()     {         this.BorderColor = Color.Black;         this.BorderStyle = DashStyle.Solid;         this.ForeColor = Color.Black;         base.Initialize();     } } 

ActivityValidator

You use ActivityValidator classes to specify the rules that govern whether an activity’s given state is valid or not. Activity validation is the concept that provides the little red exclamation point icons on activities in the workflow designer. Validation can check for things as simple as whether or not a value is set or as complex as whether or not a given value is valid based on another property’s value.

Most of the out-of-the-box activities have their own custom validation logic defined in ActivityValidator classes. For example, by looking at the IfElseValidator class in Reflector, you can see that the Validate method ensures that the child count is not less than 1 and that each child in its EnabledActivities collection is an IfElseBranchActivity instance. You can prove this logic by placing a new IfElse activity in a workflow and inspecting its errors.

WorkflowMarkupSerializer

You can serialize workflow activities according to logic you specify. You define the custom serialization logic in classes that inherit from WorkflowMarkupSerializer. Then you apply the serialization class to an activity using the DesignerSerializer attribute.

The following three markup serializers are provided out of the box for use in specific situations:

  • WorkflowMarkupSerializer

  • ActivityMarkupSerializer

  • CompositeActivityMarkupSerializer

It is pretty easy to ascertain which entities these classes apply to based on their names.

ActivityCodeGenerator

Custom code generators can be written so that developers can participate in code generation during compile time. Generally, developers create new classes inheriting from ActivityCodeGenerator that pertain to a very specific need for code generation. Although you may rarely need to use code generation in your solutions, it is a very powerful tool when required.

A good example of how activity code generation is used in the Windows Workflow Foundation API is the WebServiceInput activity. When this activity is used to expose a workflow as an ASP.NET web service, code is generated when the workflow is compiled to create a new class inheriting from Workflow WebService. WorkflowWebService in turn inherits from System.Web.Services.WebService, which is the base class for all ASP.NET web services. Logic is then injected into the new class, which calls the workflow, and handles any input and output to and from the workflow.

The following code snippet represents a portion of the definition metadata of the WebServiceInput Activity class:

  [SRCategory("Standard")] [SRDescription("WebServiceReceiveActivityDescription")] [ToolboxBitmap(typeof(WebServiceInputActivity), "Resources.WebServiceIn.png")] [Designer(typeof(WebServiceReceiveDesigner), typeof(IDesigner))] [ActivityValidator(typeof(WebServiceReceiveValidator))] [ActivityCodeGenerator(typeof(WebServiceCodeGenerator))] [DefaultEvent("InputReceived")] public sealed class WebServiceInputActivity :     Activity, IEventActivity, IPropertyValueProvider,     IActivityEventListener<QueueEventArgs>, IDynamicPropertyTypeProvider { } 

The important line to look at here is the second-to-last attribute of the class, ActivityCodeGenerator. This attribute references a class called WebServiceCodeGenerator, which, not surprisingly, inherits from ActivityCodeGenerator. This tells the compiler to call the code generator’s GenerateCode method during compilation to create any necessary code and compile it as well. Code generation is a large topic in itself and not something specific to Windows Workflow Foundation; therefore, it is not discussed in detail here. However, having a basic understanding of what is going on in the GenerateCode method of WebServiceCodeGenerator may help you think of ways that you can use this infrastructure.

Basically, a new web service class definition is created using the System.CodeDom.CodeType Declaration class, and a method is created and added to the new class using the CodeMemberMethod class of the same namespace. To really get an idea of what is going here, use Lutz’s Reflector to see what the code is trying to accomplish.

To summarize, if you need to create dynamically generate code at workflow compile time, you can inherit from the ActivityCodeGenerator class and apply the ActivityCodeGenerator attribute to your activity class.

An Example: Developing the WriteFile Activity

This activity development case study involves an activity that writes some text to a file. This goal of this case study is to cover the gamut of what you need to know for basic activity development. The WriteFile activity will have the following features:

  • A custom Toolbox icon

  • A custom Toolbox item type that will allow custom display text in the Toolbox

  • A custom designer and a theme that includes an image

  • Custom validation

  • Its own execution logic

The first step in creating the new activity is to create a new class that inherits from System.Workflow.ComponentModel.Activity. The following code block shows the skeleton of the WriteFileActivity class, with numerous attributes decorating the new class:

  [ToolboxItem(typeof(MyActivityToolboxItem))] [ToolboxBitmap(typeof(WriteFileActivity), "Resources.write.ico")] [Designer(typeof(WriteFileActivityDesigner), typeof(IDesigner))] [ActivityValidator(typeof(WriteFileActivityValidator))] public class WriteFileActivity : Activity {    ... } 

In this code, the ToolboxItem attribute is used to specify which class should be used to implement custom Toolbox behavior in Visual Studio. The code specifies a class called MyActivityToolboxItem, which was developed for this example and whose implementation is shown later. The ToolboxBitmap attribute specifies that the Toolbox should show an icon called write.ico, which has been included in the current assembly as a resource.

The WriteFileActivity class is also decorated with a Designer attribute instance that is used to specify a class that implements IDesigner. The WriteFileActivityDesigner class defines the behavior used when displaying the activity within the workflow designer. The implementation of this class is also shown later in the case study.

Finally, the activity class is decorated with the ActivityValidator attribute. This attribute simply specifies the type of a custom activity validator class - in this case, WriteFileActivityValidator. Like all of the other custom code developed for this example, this class is covered a little later.

Part of the actual implementation of WriteFileActivity is shown in the following code. There are a couple of important things to notice here. First, the required behavior of this activity dictates that you should be able to specify the name of the output file as well as the text to be written to that file. Therefore, there are two public properties defined: FileName and FileText. Notice that the get and set accessors of these two properties reference their corresponding static DependencyProperty instances.

 public class WriteFileActivity : Activity {     public static DependencyProperty FileTextProperty =         DependencyProperty.Register("FileText", typeof(string),             typeof(WriteFileActivity));     [Description("The text to write to the file")]     [Category("File")]     [Browsable(true)]     [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]     public string FileText     {         get         {             return ((string)(base.GetValue(WriteFileActivity.FileTextProperty)));         }         set         {             base.SetValue(WriteFileActivity.FileTextProperty, value);         }     }     public static DependencyProperty FileNameProperty =         DependencyProperty.Register("FileName", typeof(string),             typeof(WriteFileActivity));     [Description("The file to write to")]     [Category("File")]     [Browsable(true)]     [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]     public string FileName     {         get         {             return ((string)(base.GetValue(WriteFileActivity.FileNameProperty)));         }         set         {             base.SetValue(WriteFileActivity.FileNameProperty, value);         }     } }

The next block of code is still in the WriteFileActivity class and is the meat of this entire exercise: the Execute method implementation. Here, the code simply creates a new instance of the StreamWriter class using the System.IO.File.CreateText method. Next, the text you specified in the FileText property is written to the stream; the stream is flushed and then closed. Finally, the Closed value from the ActivityExecutionStatus enumeration is returned to indicate a successful execution.

  protected override ActivityExecutionStatus Execute(     ActivityExecutionContext executionContext) {     StreamWriter writer = File.CreateText(this.FileName);     writer.Write(this.FileText);     writer.Flush();     writer.Close();     return ActivityExecutionStatus.Closed; } 

The next block of code shown is the implementation of the MyActivityToolboxItem class specified as the custom ToolboxItem class for the previous WriteFileActivity. Although this class doesn’t have any behavior that is specific to this example, it shows how you can implement simple behavior that applies a custom description for any activity decorated with the ToolboxItem attribute and specifying this class.

   [Serializable] public class MyActivityToolboxItem : ActivityToolboxItem {     public MyActivityToolboxItem()     {     }     public MyActivityToolboxItem(Type type)         : base(type)     {         if (type != null)         {             if (type.Name.EndsWith("Activity") && !type.Name.Equals("Activity"))             {                 base.DisplayName =                     type.Name.Substring(0, type.Name.Length - 8);             }         }     }     protected MyActivityToolboxItem(SerializationInfo info,         StreamingContext context)     {         this.Deserialize(info, context);     } } 

Basically, this class changes the default display name of an activity in the Toolbox from its full name, in this case WriteFileActivity, to a shorter and friendlier name. In this example, the display name is WriteFile. This behavior is actually implemented in the ActivityToolboxItem class in the Windows Workflow Foundation APIs, but only for activities that have been included out of the box. So this custom class enables you to take advantage of the same behavior.

The following code is the WriteFileActivityDesigner class skeleton. Remember, this class was applied to WriteFileActivity earlier using the Designer attribute. Notice that there is an Activity DesignerTheme attribute decorating WriteFileActivityDesigner, which specifies a type of Write FileActivityDesignerTheme. As discussed earlier in this chapter, there are two classes that define how an activity is presented in the workflow designer. The designer class defines how an image and text are laid out, and the designer theme class defines other visual traits such as text and background color as well as the border’s look and feel.

  [ActivityDesignerTheme(typeof(WriteFileActivityDesignerTheme))] public class WriteFileActivityDesigner : ActivityDesigner {    ... } 

The following code is the implementation of this WriteFileActivityDesigner class:

 public class WriteFileActivityDesigner : ActivityDesigner {     private const int TEXT_WIDTH = 75;     private const int PADDING = 4;     protected override Rectangle ImageRectangle     {         get         {             Rectangle rect = new Rectangle();             rect.X = this.Bounds.Left + PADDING;             rect.Y = this.Bounds.Top + PADDING;             rect.Size = Properties.Resources.Write.Size;             return rect;         }     }     protected override Rectangle TextRectangle     {         get         {             Rectangle imgRect = this.ImageRectangle;             Rectangle rect = new Rectangle(                 imgRect.Right + PADDING,                 imgRect.Top,                 TEXT_WIDTH,                 imgRect.Height);             return rect;         }     }     protected override void Initialize(Activity activity)     {         base.Initialize(activity);         Bitmap image = Properties.Resources.Write;         image.MakeTransparent();         this.Image = image;     }     protected override Size OnLayoutSize(ActivityDesignerLayoutEventArgs e)     {         base.OnLayoutSize(e);         Size imgSize = Properties.Resources.Write.Size;         return new Size(imgSize.Width + TEXT_WIDTH + (PADDING * 3),             imgSize.Height + (PADDING * 2));     } }

First, take a look at the two properties: ImageRectangle and TextRectangle. These two overridden properties define the logic that tells the designer where to place the activity image and text as well as the rectangle size of those two entities. There are two integer constants at the top of the class that are used during the visual layout process. TEXT_WIDTH simply says how wide the text’s rectangle area will be, and PADDING says how large the margins will be in several areas of the activity’s visual representation.

Next, the overridden Initialize method grabs the activity’s image file from the assembly’s resources store. The image is made transparent and then set to the Image property, which originally defined the base ActivityDesigner class.

Adding an image to an assembly’s resources is simple. First, right-click the project icon in the Solution Explorer and select the Properties option from the context menu. This displays a multitude of properties and settings related to the project. On the Resources tab, click the down arrow on the Add Resource toolbar button and select Add Existing File (see Figure 6-10). This displays a dialog box where you can point to an image on your filesystem. After you select the image, it appears in the area below the toolbar. You can now reference this image directly from code using the Properties.Resources class that is generated for you. This easy way of managing and accessing assembly resources was introduced in .NET 2.0 and Visual Studio 2005.

image from book
Figure 6-10

To get back to the example at hand, look at the overridden OnLayoutSize method in the WriteFile ActivityDesigner class. Here, the code that specifies how the activity’s size is calculated is implemented. Several variables are used to determine the activity’s size, including the size of the activity image, the desired padding, and the width of the text rectangle.

The following code shows the WriteFileActivityDesignerTheme class. This is the class that was applied to the WriteFileActivityDesigner class using the ActivityDesignerTheme attribute. To implement custom behavior here, the Initialize method was overridden, and numerous properties defined in the base ActivityDesignerTheme class are set. As you can see, the foreground color, background color, border style, and a few other visual attributes are specified in this custom class.

  public class WriteFileActivityDesignerTheme : ActivityDesignerTheme {     public WriteFileActivityDesignerTheme(WorkflowTheme theme)         : base(theme)     {     }     public override void Initialize()     {         this.ForeColor = Color.Black;         this.BorderColor = Color.Black;         this.BorderStyle = DashStyle.Solid;         this.BackgroundStyle = LinearGradientMode.Vertical;         this.BackColorStart = Color.White;         this.BackColorEnd = Color.LightGray;         base.Initialize();     } } 

This next class covers a very interesting aspect of activity development. The WriteFileActivity Validator class, shown in the following code, defines the logic necessary to make sure the WriteFile activity is configured correctly during design time and runtime. The custom validator class inherits from ActivityValidator and is applied to the WriteFileActivity class using the ActivityValidator attribute. WriteFileActivityValidator defines its logic by overriding the Validate method of the ActivityValidator base class. In this example, an instance of the activity in question is acquired by casting the obj variable to a variable of type WriteFileActivity. From here, the code is able to analyze the activity to make sure that the necessary properties are set and that they are set correctly.

  public class WriteFileActivityValidator : ActivityValidator {     public override ValidationErrorCollection Validate(         ValidationManager manager, object obj)     {         if (manager == null)             throw new ArgumentNullException("manager");         if (obj == null)             throw new ArgumentNullException("obj");         WriteFileActivity activity = obj as WriteFileActivity;         if (activity == null)             throw new                 InvalidOperationException("obj should be a WriteFileActivity");         if (activity.Parent != null)         {             ValidationErrorCollection errors = base.Validate(manager, obj);             if (String.IsNullOrEmpty(activity.FileName))                 errors.Add(ValidationError.GetNotSetValidationError("FileName"));             if (String.IsNullOrEmpty(activity.FileText))                 errors.Add(ValidationError.GetNotSetValidationError("FileText"));             bool dirError = false;             try             {                 string fileDir = Path.GetDirectoryName(activity.FileName);                 dirError = !Directory.Exists(fileDir);             }             catch { dirError = true; }             if (dirError)             {                 errors.Add(new ValidationError(                     "Directory for FileName is not valid or does not exist",                     3,                     false,                     "FileName"));             }             if (Path.GetExtension(activity.FileName) != ".txt")                 errors.Add(new ValidationError(                     "This activity only supports .txt files!",                     4,                     false,                     "FileName"));             return errors;         }         else         {             return new ValidationErrorCollection();         }     } } 

Notice the line that checks to see whether the activity.Parent property is null. Basically, the workflow compiler performs the validation logic even during the compilation of the activity class during design time. This means that if the activity does not pass validation given its default property values, it does not compile. Obviously, this isn’t optimal because you are more concerned about whether the activity is configured correctly when it is actually being used. This is where the Parent property null check comes in. The activity has a parent only when it is being used in the workflow designer or during runtime. Therefore, none of the code within the if block is executed during the activity class’ initial compilation. If there is no parent, an empty ValidationErrorCollection instance is returned, representing no errors.

Finally, take a look at the code that does the validation itself. This code checks for the following items:

  • The FileName and FileText properties cannot be null or an empty string.

  • A valid directory name must be specified in the FileName property, and it must already exist.

  • The extension of the file specified in the FileName property must be .txt.

If any of these conditions is not met, a new instance of the ValidationError class is added to the errors collection, which is then returned at the end of the method. The logic defined in an Activity Validator class is what dictates when the red exclamation-point icon is added to activities in the workflow designer as well as whether or not the workflow can compile.

For general errors, a new instance of the ValidationError class is added to the errors collection. The ValidationError class has properties such as ErrorText, IsWarning, and ErrorNumber. In addition, the ValidationError class has a static utility method called GetNotSetValidationError, which allows you to easily create a validation error that is displayed when a property is not set.

The activity is complete. To test it, you can add the activity to Visual Studio’s Toolbox by pointing to the compiled assembly on the filesystem. Doing this should result in something similar to Figure 6-11. Notice that the activity is using the custom naming defined in the MyActivityToolboxItem class - that is, the display name is WriteFile rather than WriteFileActivity. In addition, the activity’s display image is using the icon that was defined using the ToolboxBitmap attribute in the activity’s class definition.

image from book
Figure 6-11

Next, create a new workflow in a separate project, and add the new activity to the workflow by dragging and dropping it on the design surface. The size of the activity, the text, and the image adhere to the logic that was implemented in the WriteFileActivityDesigner class. In addition, the red validation icon appears on the activity, indicating that it is not correctly configured (see Figure 6-12).

image from book
Figure 6-12

After you properly set each property, you should be able to execute the workflow, and a file will appear in the location specified with the FileName property.



Professional Windows Workflow Foundation
Professional Windows Workflow Foundation
ISBN: 0470053860
EAN: 2147483647
Year: 2004
Pages: 118
Authors: Todd Kitta

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net