Custom Activities C# Help

So far, you have used activities that are defined within the System. Workflow. Activities namespace. In this section, you learn how to create custom activities and extend these activities to provide a good user experience at both design time and runtime.

To begin, you create a WriteLineActivity that can be used to output a line of text to the console. Although this is a trivial example, it will be expanded to show the full gamut of options available for custom activities using this example. When creating custom activities, you can simply construct a class within a workflow project; however, it is preferable to construct your custom activities inside a separate assembly, because the Visual Studio design time environment (and specifically workflow projects) will load activities from your assemblies and can lock the assembly that you are trying to update. For this reason, you.should create a simple class library project to construct your custom activities within . you. A simple activity such as the writeLineActivity will be derived directly from the Activity base class. The following code shows a constructed activity class and defines a Message property that is displayed when the Execute method is called:

sa

Within the Execute method, you can write the message to the console and then return a status of Closed to notify the runtime that the activity has completed.

You can also define attributes on the Message property so that a description and category are defined for that property. This isused in the property grid within VisualStudio, as shown in Figure 43-8.

The code for the activities created in this section is in the 03 CustomActivities solution. U you compile that solution, you can then add the custom activities to the toolbox within VisualStudio by
choosing the Choose Items menu item from the context menu on the toolbox and navigating to the folder where the assembly containing the activities resides. All activities within the assembly will be added to the toolbox. .

Figure 43-8

Figure 43-8

As it stands, the activity is perfectly usable; however, there are several areas that should be addressed to make this more user-friendly. As you saw with the CodeActivity earlier in the chapter, it has some mandatory properties that, when not defined, produce an error glyph on the design surface. To get the  same behavior from your activity, you need to construct a class that derives from Activity Validator and associate this class with your activity.

Activity Validation

When an activity is placed onto the design surface, the Workflow Designer looks for an attribute on that activity that defines a class that performs validation on that activity. To validate your activity, you need to check if the Message property has been set.

A custom validator is passed the activity instance, and from this you can then determine which mandatory properties (if any) have not been defined and add an error to the ValidationErrorCollection used by the Designer. This collection is then read by the Workflow Designer, and any errors found in the collection will cause a glyph to be added to the activity and optionally link each error to the property that needs attention.

Capture

The validate method is called by the Designer when any part of the activity is updated and also when the activity is dropped onto the design surface. The Designer calls the validate method and passes
through the activity as the untyped obj parameter.

In this method, first validate the arguments passed in, and then call the base class Validate method to obtain a ValidationErrorCollection. Although this is not strictly necessary here, if you are deriving from an activity that has a number of properties that also need to be validated, calling the base class method will ensure that these are also checked.

Coerce the passed obj parameter into a writeLineActivity instance, and check if the activity has a parent. This test is necessary because the validate function is called during compilation of the activity (if the activity is within a workflow project or activity library), and, at this point, no parent activity has been defined. Without this check, you cannot actually build the assembly that contains the activity and the validator. This extra step is not needed if the project type is class library.

The last step is to check that the Message property has been set to a value other than an empty string. This uses a static method of the validationError class, which constructs an error that specifies that the property has not been defined. .

To add validation support to your WriteLineActivity, the last step is to add the Activity Validation attribute to the activity, as shown in the following snippet:

Capture

If you compile the application and then drop a WriteLineActivity onto the workflow, you should see a validation error, as shown in Figure 43-9 clicking this error will take you to that property within the
property grid.

Figure 43-9

Figure 43-9

If you enter some text for the Message property, the validation error will be removed, and you can then compile and run the application:

Now that you have completed the activity validation, the next thing to do is to change the rendering behavior of the activity to add a fill color to that activity. To do this, you need to define both an Activity Designer class and an ActivityDesignerTheme class, as described in the next section.

Themes and Designers

The onscreen rendering of an activity is performed using an Activity Designer class, and this can also use an ActivityDesignerTheme.

The theme class is used to make simpIe changes to the rendering behavior of the activity within the Workflow Designer:

sa

A theme is derived from ActivityDesignerTheme, which has a constructor that is passed a WorkflowTheme argument. Within the constructor, set the start and end colors for the activity, and then define a linear gradient brush, which is used when painting the background.

The Designer class is used to override the rendering behavior of the activity. In this case, no override is necessary, so the following code will suffice:

Capture

Note that the theme has been associated with the Designer by using the ActivityDesignerTheme attribute.
The last step is to adorn the activity with the Designer attribute:

Capture

With this in place the activity is rendered as shown in Figure 43-10.

Figure 43-10

Figure 43-10

With the addition of the Designer and the theme, the activity now looks much more professional. A number of other properties are available on the theme – such as the pen used to render the border, the color of the border, and the border style.

By overriding the OnPaint method of the ActivityDesigner class, you can have complete control over the rendering of the activity. Be sure to exercise restraint here, because you could get carried away and
create an activity that doesn’t resemble any of the other activities in the toolbox.

One other useful override on the ActivityDesigner class is the verbs property. This allows you to add menu items on the context menu for the activity. It is used by the Designer of the ParallelActivity to insert the Add Branch menu item into the activities context menu and also the Workflow menu. You can also alter the list of properties exposed for an activity by overriding the PreFilterproperties method of the Designer – this is how the method parameters for the CallExternalMethoC!Activity are surfaced into the property grid. H you need to do this type of extension to your Designer, you should run Lutz Roeder’s Reflector  and load the workflow assemblies into it to see how Microsoft has defined some of these extended properties.

This activity is nearly done, but now you need to define the icon used when rendering the activity and also the toolbox item to associate with the activity.

ActivltyToolboxltem and Icons

To complete your custom activity, you need to add an icon. You can optionally create a class deriving from ActivityToolboxltem that is used when displaying the activity in the toolbox within VIsualStudio.

To define an icon for an activity, create a 16 x 16 pixel image.and include it into your project. When it has been included, set the build action for the image to be Embedded Resource. This will include the image in the manifest resources for the assembly. You can add a folder to your project called Resources, as shown in Figure 43-11.

Figure 43-11

Figure 43-11

Once you have added the image file and set its build action to Embedded Resource, you can then attribute the activity as shown in the following snippet:

Capture

The Toolbox Bit map attribute has a number of constructors defined, and the one being used here takes a type defined in the activity assembly and the name of the resource. When you add a resource to a folder, its name is constructed from the namespace of the assembly and the name of the folder that the image resides within – so the fully qualified name for the resource here is Custom Activitiy Resources .WriteLine. png. The constructor used with the Toolbox Bit map attribute appends the namespace that the type parameter resides within to the string passed as the second argument, so this will resolve to the appropriate resource when loaded by VisualStudio.

The last class you need to create is derived from Activity Toolboxltem. This class is used when the activity is loaded into the VisualStudio toolbox, A typical use of this class is ‘to change the displayed name of the activity on the toolbox – all of the built-in activities have their names changed to remove the word” Activity” from the type. In your class, you can do the same by setting the Display Name property to “WriteLine.”

Capture

The class is derived from ActivityToolboxItern and overrides the constructor to change the display name; it also provides a serialization constructor that is used by the toolbox when the item is loaded into the toolbox. Without this constructor, you will receive an error when you attempt to add the activity to the toolbox, Note that the class is also marked as (Serializable].

The toolbox item is added to the activity by using the Toolboxltern attribute as shown:

Capture

With all of these changes in place, you can compile the assembly and then create a new workflow project. To add the activity to the toolbox, open a workflow and then display the context menu for the toolbox and click Choose Items.

You can then browse for the assembly containing your activity, and once you have added it to the toolbox, will look something like Figure 43-12. The icon is somewhat less than perfect, but it’~ close enough.

Figure 43-12

Figure 43-12

You revisit the ActivityToolboxltern in the next section on custom composite activities, because there. are some extra facilities available with that class that are necessary only when adding composite activities to the design surface.

Custom Composite Activities

There are two main types of activity. Activities that derive from Activity can be thought of as callable functions from the workflow. Activities that derive from CompositeActivity (such as ParallelActivity, If ElseActivity, and the ListenActivity) are containers for other activities. Their design-time behavior is considerably different from simple activities in that they present an area on the Designer where child activities can be dropped.

In this section, you create an activity that you can call the DaysOfWeekActivity. This activity can be used to execute different parts of a workflow based on the current date. You might, for instance, need to execute a different path in the workflow for orders that arrive over the weekend than for those that arrive during the week. In this example, you learn about a number of advanced workflow topics, and by the end of this section, you should have a good understanding of how to extend the system with your own composite activities. The code for this example is also available in the 03 CustomActivities solution.

To begin, you create a custom activity that has a property that will default to the current date/time. You will allow that property to be set to another value that could come from another activity in the workflow or a parameter that is passed to the workflow when it execute. This composite activity will contain a number of branches – these will be user defined. Each of these branches will contain an enumerated constant that defines which day{§) that branch will execute, The following example defines the activity and two branches:

Capture

For this example: you need an enumeration that defines the days of the week – this will include the [Flags] attribute (so you can’t use the built-in DayOfWeek enum defined within the System namespace, because this doesn’t include the [Flags] attribute).

Capture

Also included is a custom editor for this type, which will allow you to choose enum values based on check boxes. This code is available in the download.

With the enumerated type defined, you can take an initial stab at the activity itself. Custom composite activities are typically derived from the CompositeActivity class, because this defines among other things an Activities property, which is a collection of all subordinate activities.

Capture

The Date property provides the regular getter and setter, and we’ve also added a number of standard attributes so that it displays correctly within the property browser. The code, though, looks somewhat different from a normal .NET property, because the getter and setter are not using a standard field to store their values, but instead are using what’s called a Dependency Property.

The Activity class (and therefore this class, because it’s ultimately derived from Activity) is derived from the DependencyObject class, and this defines a dictionary of values keyed on ‘a Dependency Property. This indirection of getting/setting property values is used by WF to support binding; that is, linking a property of one activity to a property of another. As an example, it is common to pass parameters around in code, sometimes by value, sometimes by reference. WF uses binding to link property values together – so in this example, you might have a DateTime property defined on the workflow, and this activity might need to be bound to that value at runtime. You see an example of binding later in the chapter.

If you build this activity, it won’t do much; indeed it will not even allow child activities to be dropped into it, because you haven’t defined a Designer class for the activity.

Adding a Designer

As you saw with the WriteLineActivity earlier in the chapter, each activity can have an associated Designer class, which is used to change the design-time behavior of that activity. You saw a blank Designer in the writeLineActivity, but for the composite activity you need to override a couple of methods to add so~e special case processing:

sa

You should also override the OnCreateNewBranch method that is called when the user chooses the Add Branch menu item. The Designer is associated with the activity by using the [Designer] attribute as shown here:

Capture

The design-time behavior is nearly complete; however, you also need to add a class that is derived from ActivityToolboxItern to this activity, because that defines what happens/when an instance of that activity is dragged from the toolbox. The default behavior is simply to construct a new activity; however. in the example you also want to create two default branches. The following code shows the toolbox item class in its entirety:

sa

As shown in the code, the display name of the activity was changed, a serialization constructor was implemented, and the CreateComponentsCore method was overridden.

This method is called at the end of the drag-and-drop operation, and it is where you construct an instance of the DaysOfWeeKActivity. In the code, you are also constructing two child sequence activities, because this gives the user of the activity a better design-time experience. Several of the builtin activities do this, too – when you drop an If ElseActivity onto the design surface, its toolbox item class adds two branches. A similar thing happens when you add a ParallelActivity to your workflow.

The serialization constructor and the [Serializable] attribute are necessary for all classes derived from ActivityToolbox Item.

The last thing to do is associate this toolbox item class with the activity:

Capture

With that in place, the UI of your activity is almost complete, as you can see in Figure 43-13.

Figure 43-13

Figure 43-13

Now, you need to define a property on each of the sequence activities shown in Figure 43-13, so that the user can define which day(s) the branch will execute. There are two ways to do this in Windows Workflow: you can create a subclass of Sequence Activity and define it there, or you can use another feature of dependency properties called Attached Properties.

You will use the latter method, because this means that you don’t have to subclass but instead can effectively extend the sequence activity without needing the source code of that activity.

Attached Properties

When registering dependency properties, you can call the Register Attached method to create an attached property. An attached property is one that is defined on one class but is displayed on another. So here, you define a property on the DaysOfWeekActivity, but that property is actually displayed in the UI as attached to a sequential activity.

The code in the following snippet shows a property called Weekday of type Weekday Enum, which will be added to the sequence activities that reside within your composite activity:

Capture

The final line allows you to specify extra information about a property. In this instance, it is specifying that it is a Metadata property.

Metadata properties differ from normal properties in that they are effectively read only at Runtime. You can think of a Metadata property as similar to a constant declaration within C#.You cannot alter constants while the program is executing, and you cannot change Metadata properties while a workflow is executing.

In this example, you wish to define the days that the activity will execute, so you could in the Designer set this field to “Saturday, Sunday”. In the code emitted for the workflow, you would see a declaration as follows (I have reformatted the code to fit the confines of the page):

Capture

In addition to defining the dependency property, you will need methods to get and set this value on an arbitrary activity. These are typically defined as static methods on the composite activity and are shown in the following code:

Capture

You need to make two other changes in order for this extra property to show up attached to a SequenceActivity. The first is to create an extender provider, which tells VisualStudio to include the extra property in the sequence activity. The second is to register this provider, which is done by overt\ding the Initialize method of the Activity Designer and adding the following code to it:

Capture

The calls of GetService in the preceding code allow the custom Designer to query for services proffered by the host (in this case VisualStudio).You query VisualStudio for the IExtenderListService, which provides a way  enumerate all available extender providers, and if no instance of the . Weekday Extender Provider service is found, then query for the IExtenderProviderService and add a new provider.

The code for the extender provider is shown here:

Capture

Capture

An extender provider is attributed with the properties that it provides, and for each of these properties it must provide a public Get<Property> and Set<Property> method. The names of these methods must match the name of the property with the appropriate Get or Set prefix.

With the preceding changes made to the Designer and the addition of the extender provider, when you click a sequence activity within the Designer, you will see the properties in Figure 43-14 within Visual Studio.

Figure 43-14

Figure 43-14

Extender providers are used for other features in .NET.One common one is to add tool tips to controls in a Windows Forms project – this registers an extender and adds a Tool tip property to each control on the form.

Posted on October 31, 2015 in Windows Workflow Foundation

Share the Story

Back to Top