What You’ll Learn in This Hour:
Custom activity overview
Basic custom activity overview
Creating a basic custom activity
Creating a compound activity and activity binding
Overview of custom basic activity programming model
Adding a designer
Adding a ToolboxBitMap
and making it available across projects
You can create your own custom activities in WF. In fact, creating custom activities is as core to WF as any other capability. Therefore, five hours are devoted to them. This section describes the value of custom activities. The next section describes them from a more technical perspective. Then you begin building them.
Following are three reasons to create custom activities:
To improve on an out-of-the-box activity for usability reasons
To create domain specific activities
To create custom control flow patterns.
In the next sections, you’ll look at each.
In Hour 17, “Learning Advanced Hosting,” a third-party–created synchronous InvokeWorkflow
activity was discussed because many scenarios call for calling workflows and waiting for the called workflow to return a response. Others have modified the Delay
activity to make it wait until a milestone, rather than waiting for a period of time. Some may even choose to create an entire new set of out-of-the-box (OOB) activities. Improving the OOB activities is a viable scenario for custom activity development, but it is not anticipated to be the primary motivation behind creating custom activities.
The OOB activities provide general functionality. They are host agnostic and know nothing about any vertical domains or any individual enterprise. Many who use WF will find that its value grows proportionally to the amount of domain activities added. If, for example, you want to use WF to model the credit process, you could use the OOB activities for control flow and then augment them with standard code to perform the actual credit process. Alternatively, you could create Customer
, CheckCredit
, SendNotification
, and other custom activities that express the credit process. In certain situations, when the domain activities are rich enough, they can be used in conjunction with standard control flow activities to permit code-free workflow construction by developers and even business people. For those that use SharePoint workflow, your get to see how different—and in many ways more immediately useful—WF becomes when a number of activities are added with knowledge of the SharePoint domain.
A large portion of WF’s utility and capability to attract a wide range of authors is dictated by its modeling simplicity and capabilities. The Replicator
activity, for instance, makes it much easier to model n number of elements determined at runtime on a workflow. The EventHandlingScope
activity likewise makes it easier to respond to events while executing a sequential workflow. The StateMachineWorkflow
allows for an entirely different workflow style. Although the OOB activities offer a solid start, they do not satisfy all possible control flow patterns. They don’t even come close. There are numerous other possible control flow patterns. Some can be found at www.workflowpatterns.com, a site that describes 40 or so control flow patterns. Others may be custom to the process and workflow authors of a specific process. In many cases, the maximum benefit will be achieved through a combination of custom domain and control flow activities. The first will bring the domain to WF, and the latter will streamline development and potentially allow less-technical people to create processes.
Activities are .NET types and have properties and methods, as does a typical class. Activities have their own programming model and life cycle. Their programming model is primarily supported through a number of optional classes that can be associated with an activity via attributes. These optional classes provide custom validation, design, and toolbox support. The activity programming model is very component-centric, wherein an activity is surrounded by a number of support classes that further tailor the activity.
The activity’s life cycle is governed by a finite state machine that includes initialized, executing, closed, and other states. One of the main benefits supplied by workflows is long-running transaction state management. The workflow and many of its embedded activities must be able to execute, go idle, and continue execution when an external event arrives. Developers will create queues managed by the workflow runtime to allow activities to go idle, receive information, and to continue execution when the queue is updated via external input or a timer. The activity life cycle is primarily supported through a set of virtual methods contained in the base activity class (covered throughout the next hours).
Finally, activities such as IfElse
and While
must contain a designer that conveys their capabilities. These activities must also be able to control the execution of their child activities. The capability to create custom activity layouts and control their child activity execution enables the creation of custom control flow patterns. As mentioned before, WF workflows are themselves activities. They, too, carry out their duties by scheduling their embedded child activities, and their look-and-feel is supplied via designers.
Activities that always execute in a single unit in one burst will be referred to as basic activities throughout the next five hours. Basic activities are covered in this hour. Single activities that can execute across multiple bursts are referred to as multiburst activities. Multiburst activities are covered in Hour 21, “Creating Queued Activities,” and Hour 22, “Creating Typed Queued and EventDriven
-Enabled Activities.” Those that control subordinate child execution are referred to as control flow activities. Control flow activities are covered in Hour 23, “Creating Control Flow Activities Session 1,” and Hour 24, “Creating Control Flow Activities Session 2.”
A fourth type of activity features a control flow activity prepopulated with child activities to save modeling time. This type of activity is referred to as a compound activity and is covered in this hour.
Control flow activities are generally referred to as composite activities. I like control flow better and will use it.
All basic activities are derived (directly or indirectly) from System.Workflow.ComponentModel.Activity
, and control flow activities from System.Workflow.ComponentModel.CompositeActivity
. Unfortunately, most of the OOB activities are sealed. The CallExternalMethod
, HandleExternalEvent
, and two state machine and two sequential activities are not sealed. When developing custom activities, you will either extend one of the base classes or higher-level nonsealed activities, depending on your needs. A large portion of SharePoint workflow is implemented through a number of CallExternalMethod
- and HandleExternalEvent
-derived activities that manipulate SharePoint tasks and other elements.
This hour walks you through the creation of a custom activity that will retrieve customer data from a SQL table. The first part of the hour involves coding the functionality to retrieve the custom data by overriding the execute method. Overriding the execute method is the only mandatory step when creating a custom basic activity. The second part of this hour demonstrates creating a type of activities referred to as compound activities. You also learn about activity binding and property promotion while creating a compound activity.
The third part of the hour overviews the basic activity programming model that revolves around a series of classes that are associated to the activity via attributes. You will implement the activity designer and activity toolbox icon attributes in this hour. The first controls the look and feel of the activity, and the second controls its toolbox icon and the small icon that appears on the activity itself. After that, you will learn to make the activity available across all projects.
Follow the next steps to create the solution and two projects. The first will hold the custom activity and the second will hold the workflow you place the custom activity on.
The solution will contain two projects. The first will hold the custom activity and the second the workflow you place the custom activity on.
1. Create a new blank solution named BasicCustomActivitySolution
and place it in the C:SamsWf24hrsHoursHour20BasicCustomActivity
directory (see Hour 2, “A Spin Around Windows Workflow Foundation”).
2. Add a Workflow Activity Library (not a Sequential Workflow Library) project named CustomBasicActivities
to the solution.
3. Rename Activity1.cs
to Customer.cs
in the Solution Explorer.
4. Add a Sequential Workflow Console application project named CustomActivityTester
to the solution.
5. Add code to pause the host. (See See Hour 3, “Learning Basic Hosting,” if you need help.)
In this section you create a custom activity and then extend it to receive input. The input is hardcoded originally and then taken from a database.
Double-click the Customer
activity (the .cs
file) in the CustomBasicActivities
project. You will see that the activity contains a sequence designer capable of holding child activities (Figure 20.1). The activity currently derives from Sequence
activity, as shown in the property window and Figure 20.1.
The first thing you will do is change the activity to derive from the System.Workflow.ComponentModel.Activity
base class because you are creating a basic activity.
Follow the next steps to change the class the activity derives from.
1. Click the Customer
activity, click in its Properties panel, and click the ellipsis in the Base Class property. Here you can see all the base classes you can derive from when creating a custom activity.
2. Select the System.Workflow.ComponentModel
and choose the Activity
type in the middle of the screen (Figure 20.2).
3. Click OK, and the Customer
activity base class should now be set to System.Workflow.ComponentModel.Activity
.
Notice that the activity designer now looks like the standard basic activity designer. It is a single unit and cannot contain child activities. This is the default designer associated with activities directly derived from the Activity
base class.
For a custom activity to exhibit behavior, you must override its Execute
method. This is the one mandatory step in custom activity creation.
Follow the next steps to override the Execute
method.
1. Get to the activity code-beside file by right-clicking the activity and selecting View Code.
2. Notice in the class signature that the custom activity is derived from the base Activity
class.
3. Add a couple of blank lines below the constructor and type protected override
, press the spacebar, and select the Execute
method to create the execute method shown next:
protected override ActivityExecutionStatus
Execute(ActivityExecutionContext executionContext)
{
return base.Execute(executionContext);
}
4. Replace the code in the method body with the following:
Console.WriteLine
("I'm printing from within a custom activity's execute
method.");
return ActivityExecutionStatus.Closed;
5. Your Customer
class should now look like Listing 20.1.
LISTING 20.1 Customer Custom Activity
public partial class Customer : System.Workflow.ComponentModel.Activity
{
public Customer( )
{
InitializeComponent( );
}
protected override ActivityExecutionStatus
Execute(ActivityExecutionContext executionContext)
{
Console.WriteLine
("I'm printing from within a custom activity's execute method.");
return ActivityExecutionStatus.Closed;
}
}
The specifics of the ActivityExecutionContext
class and the ActivityExecutionStatus
enumerationwill be discussed in Hours 21 and 22. When working with basic activities, it is enough to know that the ActivityExecutionStatus
must be returned in closed state so that the activity will complete.
6. Build the CustomBasicActivities
project.
You have now overridden the Execute
method, instructed it to print a line to the console, and then to return control to the runtime in a closed state. This tells the runtime the activity is finished executing.
You will now add the activity to a workflow and run the workflow. The activity is included in a new section of the toolbox named CustomBasicActivities Components (Figure 20.3). The new toolbox section is available to all projects in the current solution. In the upcoming “Adding the Activity to the Toolbox Across Projects” section of this hour, you will learn to make it available to all workflow projects and to change the icon that appears on the toolbox.
Follow the next steps to add the Customer
activity to the workflow.
1. Set the CustomActivityTester
project as the startup project.
2. Drag and drop the Customer
activity onto the workflow.
3. Run the workflow, and you should see that the Customer
activity printed the content you specified in its execute method to the console (Figure 20.4).
That’s it. You have created a custom activity, placed it on the workflow, and executed the workflow with the custom activity on it. You will create a more useful custom activity as the hour progresses.
You update the Customer
activity to receive input in this section. First you add a DependencyProperty
and then you modify the activity to receive input and print the contents of this DependencyProperty
to the console.
We need to be able to accept a value and pass it to our Customer
activity, which can, in turn, retrieve the correct customer. You will add a new property to the Customer
activity that holds the customer number. The property is added and associated to the Customer
activity and not to the workflow. The activity will then print this value to the console.
Follow the next steps to create a DependencyProperty
.
1. Get to the activity code-beside file by right-clicking Customer.cs
and selecting View Code.
2. Move your cursor directly below the constructor, insert a couple of additional blank lines, move your cursor between the lines, right-click, and select Insert Snippet. Double-click Other, double-click Workflow, and double-click DependencyProperty—Property to insert a new dependency property.
3. You should see the stub dependency property, and the text MyProperty should be highlighted.
4. Enter CustomerNumber
(replacing MyProperty), and press Enter to leave the wizard.
Follow the next steps to update the Execute
method to use the DependencyProperty
you added to the activity.
Follow the next steps to run the workflow and populate the CustomerNumber
property.
The customer number will be manually entered into the Customer
activity. The Customer
activity’s Execute
method will now retrieve the customer specified in the property from a SQL database. Finally, the results from the table will be placed in the Customer
activity’s corresponding dependency properties.
Follow the next steps to run a SQL script that will create a CustomActivity database. The database will have one table and a stored procedure. The script needs to be run. The directions that follow show how to run the script in Microsoft SQL Server Management Studio.
1. Open Microsoft SQL Server Management Studio. Select your SQL Server and log in to the dialog.
2. Click File, Open, File, and then open the SQL script C:SamsWf24hrsHoursHour20BasicCustomActivitySQLCreateCustomActivityDatabase.sql
.
3. Click Execute or press F5 to run the query.
4. Verify that the CustomActivity
database was created and that it holds a Customer
table and a GetCustomer
stored procedure.
5. There are three records in the Customer
table. Customer 0003 is set to always go on hold, customer 0002 evaluates whether to go on hold, and customer 0001 never goes on hold. See Figure 20.7.
Now you will add CustomerName
, CustomerCreditLimit
, and other dependency properties to the Customer
activity. You’ll manually input the customer number into the CustomerNumber
property, and the other values will be retrieved from a SQL database. Although the customer number would be received from the host system in a real-world scenario, the manual property input is sufficient for demonstrating this activity.
1. Get to the activity code-beside file by right-clicking the Customer
activity and selecting View Code.
2. Move your cursor below the CustomerNumber
dependency property, right-click, and select Insert Snippet. Double-click Other, double-click Workflow, and double-click DependencyProperty—Property to insert a new dependency property.
3. You should see the stub dependency property, and the text MyProperty should be highlighted.
4. Enter CustomerName
(replacing MyProperty), and press Enter to leave the wizard.
5. Create additional dependency properties specified in Table 20.1.
Follow the next steps to update the Execute
method to retrieve the customer from the database and store its attributes to the activity dependency properties. You will add quite a bit of code to the Execute
method that retrieves a customer from the database and stores the table values to variables. Because it is not WF code, it is not explained.
1. Get to the activity code-beside file by right-clicking the activity and selecting View Code.
2. Add the following using
directives:
using System.Data;
using System.Data.SqlClient;
3. Add the following member variable declaration below the constructor:
private static string ConnectionString = "Initial
Catalog=CustomActivity;" +
"Data Source=localhost; Integrated Security=SSPI;";
4. Replace the contents of the Execute
method with Listing 20.2.
LISTING 20.2 Customer Execute Method Retrieves Customer From Database
SqlConnection dbConn = new SqlConnection(ConnectionString);
SqlCommand getCustomer = new SqlCommand("GetCustomer", dbConn);
getCustomer.CommandType = System.Data.CommandType.StoredProcedure;
getCustomer.Parameters.AddWithValue("@CustomerNumber",
CustomerNumber);
dbConn.Open( );
using (SqlDataReader custReader =
getCustomer.ExecuteReader(CommandBehavior.CloseConnection))
{
if (custReader.Read( ))
{
CustomerName = custReader["CustomerName"].ToString( ).Trim( );
CustomerCreditLimit =
double.Parse(custReader["CustomerCreditLimit"].ToString( ));
CustomerType = custReader["CustomerType"].ToString( ).Trim( );
CustomerYtdSales =
double.Parse(custReader["CustomerYtdSales"].ToString( ));
CustomerHoldRules =
custReader["CustomerHoldRules"].ToString( ).Trim( );
}
}
Console.WriteLine
("The customer number is: " + CustomerNumber);
Console.WriteLine
("The customer name is: " + CustomerName);
Console.WriteLine
("The customer credit limit is: " + CustomerCreditLimit);
Console.WriteLine
("The customer type is: " + CustomerType);
Console.WriteLine
("The customer YTD sales is: " + CustomerYtdSales);
Console.WriteLine
("The customer hold rules is: " + CustomerHoldRules);
return ActivityExecutionStatus.Closed;
5. Build the CustomBasicActivities
project.
The custom CheckCredit
activity uses the values retrieved by the Customer
activity and an order amount passed from the host to determine whether the customer passes the credit check. To save time, the CheckCredit
activity is already created. You have to import it into the project and update the host and workflow to use the new activity.
Follow the next steps to add the existing CreditCheck
activity to the project.
Follow the next steps to update the host to pass the OrderAmount
to the workflow.
1. Open Program.cs
and add a parameter above the line that instantiates the WorkflowInstance using the code in the next snippet:
// Add the parameters via a dictionary object
Dictionary<string, object> parameters = new
Dictionary<string, object>( );
parameters.Add("OrderAmount", 1200);
2. Add the parameters to be passed to the workflow by replacing the current lines that instantiate the WorkflowInstance as shown:
WorkflowInstance instance =
WorkflowRuntime.CreateWorkflow(
typeof(CustomActivityTester.Workflow1), parameters);
3. Open Workflow1.cs
in code-beside view. Then add a DependencyProperty named OrderAmount
and set its type to double (see Hour 4, “Learning Host-Workflow Data Exchange,” if you need help adding a DependencyProperty). This property will retrieve the OrderAmount
passed in from the host.
The CheckCredit
activity is already constructed and its properties have already been added (Figure 20.9). The CheckCredit
activity makes credit decisions based on Customer
activity and workflow data. ActivityBinding
will be used to update the CheckCredit
activity’s properties. Instead of binding most of the CheckCredit
activity’s properties to the workflow, they will be bound to the corresponding properties in the Customer
activity. The CheckCredit
activity also has an OrderAmount
property that is bound to the workflow OrderAmount
property that is received from the host. The CheckCredit
activity’s OnHold
property is updated to Yes if the customer is placed on hold and to No if not. A string is used to allow more information and results other than Yes and No to be stored.
Follow the next steps to bind the Customer
activity properties to the CheckCredit
activity’s properties.
1. Build the CustomBasicActivities
project to add the CreditCheck
activity to the toolbox.
2. Add a CheckCredit
activity to the workflow below the Customer
activity.
3. Click the CheckCredit
activity. Then click the CustomerCreditLimit
property, click the ellipsis, click the + next to customer1, and select CustomerCreditLimit
(Figure 20.9). Then click OK.
4. Click the CustomerHoldRules
property, click the ellipsis, click the + next to customer1, and select CustomerHoldRules. Then click OK.
5. Now bind the CustomerName
, CustomerNumber
, CustomerType
, and CustomerYTDSales
properties to their corresponding Customer properties.
6. Bind the OrderAmount
property to the workflow OrderAmount
property. Click the OrderAmount
property, click the ellipsis, and select OrderAmount
. Then click OK.
Follow the next steps to add and configure and IfElse
activity to evaluate the Status
returned from the CheckCredit
activity.
1. Place an IfElse
activity below the CheckCredit
activity. Name the IfElse
activity CheckCredit
. Name the left branch CreditApproved
and name the right branch CreditRejected
.
2. Click the drop-down in the Condition
property of the left branch of the IfElse
activity, select Declarative Rule Condition, click the + that appears next to the Condition property, and enter CreditHoldRule
in the Condition Name property. Click the ellipsis in the ConditionName property, click the Edit button (to update the CreditHoldRule) and enter the following in the dialog box.
this.checkCredit1.OnHold == "Yes"
3. Select OK twice to return to the workflow.
4. Add a Code
activity to the left branch. Double-click it and add the following code to its handler:
Console.WriteLine("Customer is on hold.");
5. Add a Code
activity to the right branch. Double-click it and add the following code to its handler:
Console.WriteLine("Customer is not on hold.");
The workflow will now determine hold status. Customer 0001 will never place the customer on hold and 0003 will always place the customer on hold. Customer 0002 will add the order amount to the YTD sales, and if the sum is less than the credit limit, the customer will not be placed on hold. Otherwise, the customer will be placed on hold.
Follow the next steps to run the workflow a couple of times for different customers and evaluate the results.
1. Input 0001
into the Customer
activity CustomerNumber
property on the workflow.
2. Run the workflow; the customer should not go on hold, as shown in Figure 20.11.
3. Input 0002
into the Customer activity CustomerNumber
property.
4. Run the workflow; the customer should go on hold because the order amount (1,200) plus the YTD sales (4000) is more than the credit limit (5,000).
5. Try changing the order amount passed in from the host to less than 1,000 and running the workflow again if you like.
Event handlers can be placed on activities by adding a DependencyProperty
of type EventHandler
. The EventHandler
then appears as a property on the activity when it is placed on the workflow and functions just like the handlers on the Code
, CallExternalMethod
, and other WF activities. In this section you create an EventHandler
typed DependencyProperty and then modify the Execute
method to invoke the EventHandler
.
You will add a preprocessing EventHandler
to the Customer
activity. We will employ the same terminology used by WF activities that have handlers and name it Invoking, because it executes before the activity executes.
1. Open the Customer
activity in code view, move your cursor directly below the Customer constructor, insert a couple of additional blank lines, move your cursor between the lines, right-click, and select Insert Snippet. Double-click Other, double-click Workflow, and double-click DependencyProperty—EventHandler (select Event Handler, not Property this time) to insert a new EventHandler dependency property.
2. You should see the stub dependency property, and the text Invoke should be highlighted.
3. Enter Invoking
(replacing Invoke
) and press Enter to leave the wizard. The type is set to EventHandler
.
The body of the dependency property is an event with AddHandler
and RemoveHandler
methods instead of get and set methods. You have now created an event handler that code can be associated with and can execute when you specify.
Follow the next steps to modify the Execute
method needed to invoke the handler. The handler will be called at the beginning of the Execute
method so that it is invoked before the Custom
activity does its work (retrieves the customer). Conversely, if the handler was placed at the end of the Execute
method it would support post processing.
1. Add the following code to the top of the Execute
method in the Customer
activity class to invoke the event.
// Perform preprocessing.
base.RaiseEvent(Customer.InvokingEvent, this, EventArgs.Empty);
2. Build the CustomBasicActivities
project.
You can substitute your activity and dependency property name to use on another activity.
Follow the next steps to configure the Invoking
property on the Customer
activity. Then add logic to the handler that is created and run the workflow.
1. Open Workflow in design-mode and click the Customer
activity.
2. Click its Invoking
property, enter PreProcessing
, and press Enter to create the handler.
3. Enter the following code in the handler:
Console.WriteLine("I am preprocessing from an event handler.");
4. Press F5 to run the workflow, and the text from the PreProcessing
handler should be displayed along with the other text displayed by the workflow and activity.
Compound
activities are standard WF control flow activities prepopulated with other activities. Retrieving the customer, checking the credit, and then performing actions based on approval or rejection is likely to be a common pattern. Therefore, you will create a CompoundCreditCheck
activity that satisfies this scenario in one activity in this section.
Promoted properties allow the properties of child activities to be propagated to their parent. It is common to need to promote properties to the parent when working with compound activities because the child activity properties are locked and not accessible. Promoted properties are demonstrated when configuring the CompoundCreditCheck
activity shortly. Property promotion and activity binding can be done only with dependency properties. Neither can be done with standard .NET properties.
Follow the next steps to create the CompoundCreditCheck
activity and populate it with child activities.
1. Right-click the CustomBasicActivities
project and select Add, Activity.
2. Name the activity CompoundCreditCheck
and click OK.
3. Add a Customer
activity to the CompoundCreditCheck
activity.
4. Add a CheckCredit
activity below the Customer
activity.
5. Add an IfElse
activity below the CheckCredit
activity. Name the IfElse
activity CheckCredit
, the left branch CreditApproved
, and the right branch CreditRejected
.
6. Place a Code
activity in each branch of the IfElse
activity.
7. The modeled CompoundCreditCheck
activity should look like Figure 20.12:
Follow the next steps to configure the IfElse
activity placed on the CompoundCreditCheck
activity.
1. Click the drop-down in the Condition
property of the left branch of the IfElse
activity, select Declarative Rule Condition, click the + that appears next to the Condition
property, and enter CreditHoldRule
in the ConditionName
property. Click the ellipsis in the ConditionName
property, click the Edit button (to update the CreditHoldRule
), and enter the following in the dialog box.
this.checkCredit1.OnHold == "Yes"
2. Click OK twice.
3. Double-click the Code
activity in the left branch and add the following code to its handler:
Console.WriteLine("Customer is on hold.");
4. Double-click the Code
activity in the right branch. Double-click it and add the following code to its handler:
Console.WriteLine("Customer is not on hold.");
5. Build the CustomBasicActivities
project.
Binding is the major component to configuring the CheckCredit
activity. First, the CheckCredit
activity properties need to be bound to their corresponding Customer
activity properties. When that is done, the OrderAmount
property needs to be promoted so that it can later be bound to the workflow OrderAmount
property.
Follow the next steps to configure the CheckCredit
activity on the CompoundCreditCheck
activity.
1. Bind the CustomerCreditLimit
, CustomerHoldRules
, CustomerName
, CustomerNumber
, CustomerType
, and CustomerYTDSales
properties on the checkCredit1
activity in the CompoundCreditCheck
activity to their corresponding customer1
activity properties.
2. Right-click the checkCredit1
activity and select Promote Bindable Properties. Doing so makes the OrderAmount
and OnHold
properties become properties of the CompoundCreditCheck
activity. These two properties are the remaining CheckCredit
properties that were not bound to the customer activity. You will not see the promoted properties on the CompoundCreditCheck
activity until placing it on the workflow.
Follow the next steps to configure the Customer
activity in the CompoundCreditCheck
activity.
1. Right-click the Customer
activity and select Promote Bindable Properties. This promotes all customer properties, even though only the CustomerNumber
property is needed.
There is no hard penalty for binding additional properties. If, for clarity or other reasons, binding all properties does not work for you, you can programmatically promote the properties. The details of doing so are not covered in this book. If you are interested, as a starting point, search for ActivityBind
in CompoundCreditCheck.Designer.cs
to get an idea about how it works.
2. Build the CustomBasicActivities
project.
Follow the next steps to create a new sequential workflow and modify the host to point to the new workflow.
1. Right-click the CustomActivityTester
project. Then select Add, Sequential Workflow.
2. Name the workflow CompoundActivityWorkflow
and click OK.
3. Open Program.cs
and modify it to use CompoundActivityWorkflow
instead of Workflow1 by replacing the current line of code that begins with WorkflowInstance
instance with the following:
WorkflowInstance instance =
workflowRuntime.CreateWorkflow(
typeof(CustomActivityTester.CompoundActivityWorkflow),
parameters);
4. Open the CompoundActivityWorkflow
in code view. Then create a dependency property named OrderAmount
and set its type to double
.
Follow the next steps to add the CompoundCreditCheck
activity and run the workflow you created in the last section.
1. Open the CompoundActivityWorkflow
in design mode and add the CompoundCreditCheck
activity to the workflow. What took six activities last section now takes one activity. Moreover, most of the properties including the condition are already set.
2. Click the CompoundCreditCheck
activity and you will see that it contains a number of additional properties (Figure 20.13). The CheckCredit
promoted properties are prefaced with check_Credit_1
and the ones from the Customer
activity are also prefaced.
3. Click the CompoundCreditCheck
activity. Then click its creditCheck1_OrderAmount
property and bind it to the workflow’s OrderAmount
property.
4. Enter 0002
in the CompoundCreditCheck
activity’s customer1_CustomerNumber1
property and run the workflow. You should see the results shown in Figure 20.14.
Try entering different customer numbers if you like. The rules should be the same as when you manually build out the workflow using the individual activities.
Activity validation, serialization, design, toolbox support, and code generation are enabled through optional classes that can be associated with the activity via attributes (as shown in Figure 20.15). This hour demonstrates activity design and toolbox support. Activity validation is covered in Hour 24, “Creating Control Flow Activities Session 2.”
Activity validation is for design-time enforcement only. It should be used to validate if a Condition
property is updated or that the first child of an EventDriven
activity is a blocking activity. It should not be used to ensure the customer number is valid.
Activity designers control the look-and-feel of activities. The base activity class is associated with the ActivityDesigner
type by default. This designer produces the standard basic activity designer shape that is now used by the Customer
and CheckCredit
activities. This section focuses on ActivityDesignerThemes
. The activity designer foreground, background, fonts, and icons can be changed via ActivityDesignerThemes
. The general look-and-feel of an activity designer can be specified via ActivityDesignerThemes
.
When more precise control is necessary, a number of methods in the ActivityDesigner
type, such as OnPaint
and OnLayoutSize
, allow you to construct the designer look-and-feel from the ground up. Using the methods in the ActivityDesigner
is not covered in this book. See MSDN or other resources if you need more control than that provided by the ActivityDesignerTheme
.
Designers are even more important when applied to control flow activities. The designer should convey the activity intent, and it is the designers on a workflow model that make the workflow transparent. For instance, the general function of a workflow with an IfElse
and a While
activity can be inferred by looking at the designers rendered on the workflow that are associated with these activities.
To implement an ActivityDesigner
, you must place an ActivityDesigner
attribute on the activity class. The attribute must reference a custom class that derives from ActivityDesigner
. You then place an ActivityDesignerTheme
attribute on the ActivityDesigner
class.
Follow the next steps to decorate the Customer
activity with a CustomerDesigner
attribute.
1. Add the following attribute above the Customer
activity class declaration.
[Designer(typeof(CustomerDesigner), typeof(IDesigner))]
2. Your Customer
class should now look like this:
[Designer(typeof(CustomerDesigner), typeof(IDesigner))]
public partial class Customer : System.Workflow.ComponentModel.Activity
// Additional code
You will not use any of the methods in this class, but the ActivityDesignerTheme
is referenced through the designer associated with the activity and not the activity itself. Therefore, follow the next steps to create a blank CustomerDesigner
class that the designer theme class can be referenced (attributed) from.
1. Right-click the CustomBasicActivities
project. Select Add, Class, and name it CustomerDesigner
.
2. Add the following using
directive:
using System.Workflow.ComponentModel.Design;
3. Specify that the class implements ActivityDesigner
by replacing the class declaration with the following code:
class CustomerDesigner : ActivityDesigner
4. Add the ActivityDesignerTheme
attribute by placing the following code above the class declaration:
[ActivityDesignerThemeAttribute(typeof(CustomerDesignerTheme))]
5. The CustomerDesigner
class should look like this:
[ActivityDesignerThemeAttribute(typeof(CustomerDesignerTheme))]
class CustomerDesigner : ActivityDesigner
{
}
Follow the next steps to create the ActivityDesignerTheme
class that will modify the activity designer’s appearance.
1. Right-click the CustomBasicActivities
project. Select Add, Class, and name it CustomerDesignerTheme
.
2. Add the following using directives:
using System.Workflow.ComponentModel.Design;
using System.Drawing;
using System.Drawing.Drawing2D;
3. Replace the class declaration with the following code that implements the ActivityDesignerTheme
.
public class CustomerDesignerTheme : ActivityDesignerTheme
4. Add the following constructor to the CustomerDesignerTheme
class in which you will place the design logic in the next step:
public CustomerDesignerTheme(WorkflowTheme theme)
: base(theme)
{
}
5. Now alter the activity’s appearance by changing its border style, shading, and color by adding the code below the constructor you just added:
this.BorderColor = Color.Red;
this.BorderStyle = DashStyle.Dash;
this.BackColorStart = Color.Aqua;
this.BackColorEnd = Color.Cyan;
this.BackgroundStyle = LinearGradientMode.Horizontal;
6. Build the CustomBasicActivities
project.
7. Open the CompoundActivityWorkflow in design mode, and you should see that the Customer
activity is now blue. See Figure 20.16, although it does not show the color.
The ToolboxBitmap
type allows a graphic to be associated with an activity. The graphic appears on the toolbox and is embedded in the activity when the activity is on a workflow. The graphic must be 16 × 16 pixels. These characteristics comply with .NET’s toolbox graphic rules. There is an existing graphic that meets this criteria that you will use in this exercise. See other resources for instructions on creating a compliant ToolboxBitMap icon. This section covers adding an existing graphic to the toolbox.
Follow the next steps to add the existing .jpg
file to a new project directory you will create named Resources, and set the file’s Build Action
property to Embedded Resource
.
The icon shows only on the workflow in this section. It will show on the toolbox as well after the next section, when the item is made available across all projects on the toolbox.
1. Right-click the CustomBasicActivities
project and select Add, New Folder.
2. Name the folder Resources
.
3. Right-click the Resources folder and select Add, Existing Item.
4. Browse to the C:SamsWf24hrsHoursHour20BasicCustomActivity
directory and select the Customer.jpg
file.
5. Click the Customer.jpg
file.
6. Set its Build Action
property to Embedded Resource.
7. Open the Customer
activity class in code view. Then add the following code above the class declaration to associate the activity with the customer toolbox icon.
[ToolboxBitmap(typeof(Customer), "Resources.Customer.jpg")]
8. Build the CustomBasicActivities
project.
9. Open the Workflow1 in design mode. The activity is unchanged on the toolbox.
10. Add the activity to Workflow1; the activity now has a person with a notepad embedded rather than the standard embedded custom activity graphic (Figure 20.17).
By default, the activity is available on the toolbox to projects in the current solution. In this section, you learn to make the activity accessible from the toolbox across workflow projects outside of the current solution as well. Installing an item on the Visual Studio toolbox that can be seen across workflow projects is not specific to WF. It requires creating a .vscontent
file and a .vsi
file.
The .vscontent
file contains the name of the category the toolbox menu item is added to and the name of the DLL in the FileName
element. The DisplayName
and Description
elements are set to Custom Activities. The rest of the information is boilerplate.
Follow the next step to update the .vscontent
file.
This file and the DLL must be added to a Zip file. The Zip file is renamed to the .vsi
extension. This file is then double-clicked to add the item to the Visual Studio toolbox.
Follow the next steps to create the .vsi
file.
1. Go to the C:SamsWf24hrsHoursHour20BasicCustomActivityBasicCustomActivitySolutionCustomBasicActivitiesinDebug
directory.
2. Create a new Folder (File, New Folder) named CustomActivities
. The name of this folder will be the category name the toolbox menu item resides in.
3. Copy the CustomBasicActivities.dll
file into the CustomActivities directory.
4. Right-click the CustomActivities folder and select Send To, Compressed Zip folder.
5. Drag-and-drop the .vscontent
file into the Zip file. The Zip file should now have the CustomActivities
directory with the .dll
and the .vscontent
file.
6. Exit all copies of Visual Studio.
7. Exit from the Zip file and rename it to CustomActivities.vsi
.
8. Double-click the CustomActivities.vsi
file, and you should see the following form (Figure 20.18).
9. Select Finished and answer Yes to the dialog that warns of a lack of a certificate.
10. If you receive a dialog that Tools.InstallCommunityControls is invalid, click OK and continue.
11. Close the dialog.
12. Open another solution from another hour that has a workflow. Open the workflow. Open the toolbox and the Customer
activity will reside on the toolbox, even though this project is not in the CustomBasicActivities solution.
13. Add the Customer
activity to the workflow, and you should see that both the toolbox and the activity have the custom icon you just associated with the Customer
activity (Figure 20.19):
This hour scratched the surface of what is arguably WF’s most powerful feature: custom activities. The Customer
and CheckCredit
activities represent custom domain activities that can be combined with other domain activities, such as GetOrder
to allow common business functions to be molded much more rapidly where they benefit from WF’s design-time and runtime capabilities. Then compound activities that allow common activity patterns to be added to the workflow in one step were covered. Finally, changing the activity’s appearance and its ToolboxBitmap
were covered. Event driven and control flow activities are covered in the next four hours.
13.58.115.61