Hour 20 Creating Basic Custom Activities

What You’ll Learn in This Hour:

Image    Custom activity overview

Image    Basic custom activity overview

Image    Creating a basic custom activity

Image    Creating a compound activity and activity binding

Image    Overview of custom basic activity programming model

Image    Adding a designer

Image    Adding a ToolboxBitMap and making it available across projects

Custom Activity Conceptual Overview

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:

Image    To improve on an out-of-the-box activity for usability reasons

Image    To create domain specific activities

Image    To create custom control flow patterns.

In the next sections, you’ll look at each.

Improve on Out-of-the-Box Activities

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.

Create Domain-Specific 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.

Custom Control Flow Patterns

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.

Custom Activity Technical Overview

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.

Image

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.

Basic Custom Activity Overview

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.

Creating the Solution and 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.

Creating the Solution and Projects

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.)

Creating the Customer Custom Activity

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.

Setting the Base Activity

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.

FIGURE 20.1 Default custom activity designer and base class derivation.

Default custom activity designer and base class derivation.

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).

FIGURE 20.2 Base activity selection form.

Base activity selection form.

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.

Overriding the Execute Method

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;
        }
    }



Image

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.

Adding the Custom Activity to a Workflow

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.

FIGURE 20.3 Toolbox with Customer activity included.

Toolbox with Customer activity included.

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).

FIGURE 20.4 Workflow with Customer activity results.

Workflow with Customer activity results.

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.

Updating the Customer Activity to Receive Input

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.

Adding a DependencyProperty to the Activity

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.

Updating the Execute Method

Follow the next steps to update the Execute method to use the DependencyProperty you added to the activity.

1.   Replace the body of the Execute method with the following:

            Console.WriteLine("The customer is: " + CustomerNumber);
            return ActivityExecutionStatus.Closed;


2.   Build the CustomBasicActivities project.

Running the Workflow with the CustomerNumber Property

Follow the next steps to run the workflow and populate the CustomerNumber property.

1.   Click the Customer activity on the workflow, and you should see the property window shown in Figure 20.5.

FIGURE 20.5 Customer activity property window.

Customer activity property window.

2.   Input the value MyCustomer in the CustomerNumber property and run the workflow. You should see the results shown in Figure 20.6.

FIGURE 20.6 Custom activity printing value from property windows.

Custom activity printing value from property windows.

Updating the Customer Activity to Retrieve Information from a Database

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.

Running the SQL Script to Create the SQL Components

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.

FIGURE 20.7 Customer table.

Customer table.

Adding Additional Dependency Properties

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.

TABLE 20.1 Dependency Properties to Add

Image

Retrieving Customer from Database

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.

Running the Workflow with Data Coming from the Database

1.   Open the workflows in design-mode and input and input 0002 in the Customer activity CustomerNumber property.

2.   Run the workflow. Customer 0002 should be retrieved from the database, and its values output as shown in Figure 20.8.

FIGURE 20.8 Get Custom activity printing customer data from database.

Get Custom activity printing customer data from database.

Adding Existing Custom CreditCheck Activity

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.

Import the CreditCheck Activity

Follow the next steps to add the existing CreditCheck activity to the project.

1.   Right-click the CustomBasicActivities project, select Add, Existing Item.

2.   Browse to C:SamsWf24hrsHoursHour20BasicCustomActivityCheckCreditActivity and select the CheckCredit.cs and CheckCredit.designer.cs files. Then click OK.

Updating the Host to Pass in the OrderAmount

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.

Binding the Customer Properties to the CheckCredit Activity

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.

FIGURE 20.9 Binding the CheckCredit dependency property to the Customer credit limit dependency property of the same name.

Binding the CheckCredit dependency property to the Customer credit limit dependency property of the same name.

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.

Adding an IfElse to Evaluate Credit Hold Status

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.");


6.   Your completed workflow should look like Figure 20.10.

FIGURE 20.10 Completed custom activity tester workflow.

Completed custom activity tester workflow.

Running the Workflow with the CheckCredit Activity

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.

FIGURE 20.11 Workflow run for customer set to never go on hold.

Workflow run for customer set to never go on hold.

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.

Adding Event Handlers to Activities

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.

Creating the Event Handler

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.

Modifying the Execute Method

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.

Adding Event Handler and Running the Workflow

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.

Creating Compound Activities

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.

Creating and Modeling the Compound Activity

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:

FIGURE 20.12 Modeled CompoundCreditCheck activity.

Modeled CompoundCreditCheck activity.

Configure the IfElse Activity

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.

Configuring the CheckCredit Activity in the CompoundCreditCheck Activity

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.

Configuring the Customer Activity in the CompoundCreditCheck Activity

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.

Image

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.

Creating a New Sequential Workflow

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.

Modeling the Workflow with the CompoundCreditCheck Activity

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.

FIGURE 20.13 CompoundCreditCheck activity placed on workflow and its properties.

CompoundCreditCheck activity placed on workflow and its properties.

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.

FIGURE 20.14 Workflow results with CompoundCreditCheck activity.

Workflow results with CompoundCreditCheck activity.

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 Programming Model

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.”

FIGURE 20.15 Custom activity programming model.

Custom activity programming model.

Image

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.

Designer Components

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.

Creating an Activity Designer Theme

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.

Attributing the Customer Activity

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


Creating a Customer ActivityDesigner Class

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
    {
    }


Implementing the Activity Designer Theme

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.

FIGURE 20.16 Customer activity with theme applied.

Customer activity with theme applied.

Image

If you like, run the workflow again, although it is not necessary because the changes made were design-time only.

ToolboxBitMap Class

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.

Adding Graphic as Image and Associating Class

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.

Image

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).

FIGURE 20.17 Customer activity with custom embedded customer graphic.

Customer activity with custom embedded customer graphic.

Image

If your graphic is named differently from your activity, you will have to use the ToolboxBitmap overload that accepts the assembly and the resource. See MSDN or other sources for details.

Adding the Activity to the Toolbox Across Projects

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.

Creating the VSContent 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.

1.   Copy the CustomActivities.vscontent file shown next from the C:SamsWf24hrsHoursHour20BasicCustomActivity directory to the C:SamsWf24hrsHoursHour20BasicCustomActivityBasicCustomActivitySolutionCustomBasicActivitiesinDebug directory.

Creating the VSI 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).

FIGURE 20.18 VSI Wizard form.

VSI Wizard form.

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):

FIGURE 20.19 Customer activity on workflow and toolbox with custom icon.

Customer activity on workflow and toolbox with custom icon.

Summary

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.

Workshop

Quiz

1.

What method must you override in an activity for it to do anything?

2.

What classes allow you to customize the activity look-and-feel?

3.

When do you use the features in the ActivityDesigner class?

4.

When do you use the features in the ActivityDesignerTheme class?

5.

How are activity properties made available to the workflow?

6.

How must the Execute method be completed?

Answers

1.

Execute.

2.

ActivityDesigner and ActivityDesignerAttribute.

3.

Changing the size or painting the activity manually are sample usages. It is generally used in more advanced cases.

4.

When you want to change the look of the activity.

5.

The dependency properties created in the activity are accessed via the activity name + the property name (for example, this.getCustomer2.CustomerSales).

6.

Return ActivityExecutionStatus.Closed.

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

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