Hands-On Lab

Windows Server AppFabric Hosting:

Lab 4 - Custom Activities

Lab version:1.0.0

Last updated:12/23/2018

||

Contents

Overview

Exercise 1: Using ADO.NET from workflow services

Task 1 – Use the ExecuteSqlQuery and ExecuteSqlNonQuery Activities

Exercise 1 Verification

Exercise 2: Creating the Label Generator activity

Task 1 – Creating the Label Generator Project

Task 2 – Creating the Label Generator Activity

Task 3 – Creating the Delete Label Activity

Exercise 2 Verification

Exercise 3: Using an Activity Designer

Applying the LabelGeneratorDesigner

Summary

Overview

You will use custom activities whenever you need more functionality than that provided by the built-in activities or when you need to incorporate business logic into a workflow. In this lab,you will make use of custom activities in two ways. First, you will utilize activities from the WF ADO.NET Activity Pack to enable the InventoryService to update the inventory database using ADO.NET. Next, you will build a custom activity for the LabelingService that takes the user submitted image and produces a label for the bag of coffee beans. In addition to the custom activity, you will also apply a provided activity designer for the Label Generator that places the important properties of the activity front and center on activity’s design surface.

Objectives

Understand the use and implementation of custom activities

  • Use ADO.NET from workflows with the WF Activity Pack activities
  • Implement a custom activity
  • Apply an activity designer to a custom activity

Setup

You must perform the following steps to prepare your computer for this lab:

  1. Complete the Development Environment Setup lab.
  2. Read through the Fourth Coffee Solution Overview to understand the solution that will be implemented in these labs.
  3. To simplify the process of registering the numerous Fourth Coffee applications with IIS, we have provided a utility called LabStarter that you should run as the first step in any lab.
  4. To use it, run LabStarter.exe from the %InstallFolder%\Assets directory and click the button corresponding to the Lab exercise you wish to open. This will perform the requisite configuration and then open the desired solution in Visual Studio for you automatically.

Exercises

This Hands-On Lab comprises the following exercises:

  1. Creating the Label Generator activity
  2. Creating an activity designer for the Label Generator activity

Estimated time to complete this lab: 45minutes.

Starting Materials

Use the LabStarter as described in the Setup section to begin this lab.

Note:Inside each exercise folder, you will find anend folder containing a solution with the completed lab exercise.

Exercise 1: Using ADO.NET from workflow services

This exercise will guide you through the steps to use the activities from the WF ADO.NET Activity Pack, which is a set of activities from Microsoft, separate from those built-in to the .NET Framework.

Task 1 –Use the ExecuteSqlQuery and ExecuteSqlNonQuery Activities

  1. In order to complete this exercise, ensure that you have installed the activity pack. See the Setup section in the beginning of this document for instructions.
  2. Open the starter solution for this exercise as described in the Starting Materials section.
  3. Open the InventoryService.xamlx within the InventoryService project.
  4. From the Toolbox, Data section, drag and drop a ExecuteSqlQuery into the body of the first foreach as shown and delete the DB Lookup Place Holder activity:
  5. Set the connection string used by the activity by clicking the Configure… button on the activity’s design surface.
  6. In the dialog that appears, choose the connection string for the Inventory database (if it is not in the list, press the New Connection button to bring up the dialog to configure and add the connection string to the web.config. Click OK.
  7. Now configure the activity to call the UpdateStockOnHand. Select the Stored procedure radio button, and in the Name drop down, pick UpdateStockOnHand.
  8. Next, configure the parameters required by the stored procedure. Click the Parameters button.
  9. Configure the parameters as shown:
  10. Click OK, twice to return to the designer.
  11. Rename the ExecuteSqlQuery activity to “Reserve Item”.
  12. The ExecuteSqlQuery will execute the stored procedure and return the results within a Collection<IDataRecord>. The body of the ExecuteSqlQuery activity loops over each record using foreach semantics which allows you to process each returned record. In our case, we know we will always have one record returned that indicates if the reservation was successful or not. Add an Assign activity within the body as shown:
  13. Configure the Assign activity as below to capture the result of reservation attempt:

Field Name / Value
To / wasItemReserved
Value / record.GetBoolean(0)
  1. The final activity should appear as follows:
  2. Next you will implement the logic to “unreserve” an item when the order is cancelled. Within the Branch 2 activity, ForEach body shown below, drag and drop an ExecuteSqlNonQuery from the toolbox.
  3. Name the activity Unreserve Item.
  4. Configure the connection string to point to the Inventory database.
  5. Configure the command to use the UpdateStockOnHand stored procedure, and configure the parameters as shown below:
  6. The final activity should appear as shown:

Exercise 1 Verification

In order to verify that you have correctly performed all steps of exercise 1, proceed as follows:

Verification 1

In this verification, you will use the console based TestClient to verify your implementation of the InventoryService.

  1. Set the TestClient project as the startup project, press Ctrl+F5 to run the project without debugging.
  2. Type 3 and press enter to run the Inventory Service test. Your output should look similar to the following (though you will receive a different confirmation guid):
  3. Press 1 to Confirm and verify you get the Order confirmed success message.
  4. You may wish to repeat steps 1-3 and choose 2 to Cancel.

Exercise 2: Creating the Label Generator activity

In this exercise you will build the Label Generator activity that is used by the LabelingService to create the custom coffee bag labels.

Task 1 –Creating the Label Generator Project

  1. Open the starter solution for this exercise as described in the Starting Materials section.
  1. Add a new Activity Library project to the solution, and name it Activities.
  2. By default, this will create a project containing a declarative (XAML only) custom activity called Activity1.xaml. Because we want to write a code based activity, we do not need this file. Delete Activity1.xaml.

Task 2 – Creating the Label Generator Activity

  1. Add a new Code Activity to the project— call the file LabelGenerator.cs.
  2. Add a reference to thePresentationCore and WindowsBaseassemblies.
  3. Add a new Class File to the project and call it ImageFX.cs. This class will contain the graphic manipulation code that creates the label image. For simplicity, we include this type within the activity project, in many scenarios this would come from an external library.
  4. Replace the contents of ImageFX.cs with the following code. You can use the provided code snippet for this. In the editor click where you want to insert the code and type the shortcut ImageFx and hit tab, or right click, select Insert Snippets… In the drop down select My Code Snippets and then the item with the aforementioned shortcut in its name).

C#

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Windows.Media.Imaging;

using System.Windows.Media;

using System.Globalization;

using System.Windows;

using System.IO;

namespace FourthCoffee.Imaging

{

publicclassImageFx

{

publicstaticstring ApplyWaterMark(string sourceImagePath, string workingFolderPath, string watermarkText, string watermarkImageFileName)

{

BitmapImage image = newBitmapImage(newUri(sourceImagePath));

string watermarkImagePath = Path.Combine(Path.GetDirectoryName(sourceImagePath), watermarkImageFileName);

BitmapImage beans = newBitmapImage(newUri(watermarkImagePath));

RenderTargetBitmap targetImage =

newRenderTargetBitmap(288, 576, 96, 96, PixelFormats.Default);

DrawingVisual vis = newDrawingVisual();

DrawingContext context = vis.RenderOpen();

context.DrawImage(image, newRect(0, 0, 288, 576));

context.PushOpacity(0.9);

context.DrawImage(beans, newRect(0, 400, 288, 192));

context.DrawRectangle(Brushes.Brown, null, newRect(0, 20, 288, 50));

context.Pop();

context.DrawText(

newFormattedText(watermarkText,

CultureInfo.GetCultureInfo("en-us"),

FlowDirection.LeftToRight,

newTypeface("Segoe Script"),

28,

Brushes.Chocolate),

newPoint(60, 20));

context.Close();

targetImage.Render(vis);

PngBitmapEncoder encoder = newPngBitmapEncoder();

encoder.Frames.Add(BitmapFrame.Create(targetImage));

string fileName = Path.GetFileName(sourceImagePath);

string destinationPath = Path.Combine(workingFolderPath, fileName);

FileStream stream = newFileStream(

destinationPath,

FileMode.Create, FileAccess.ReadWrite);

encoder.Save(stream);

stream.Close();

return destinationPath;

}

}

}

Task 3 – Creating the Delete Label Activity

  1. Add a new Code Activity to the project— call the file DeleteLabel.cs.
  2. Replace the contents of DeleteLabel.cs with the following code. You can use the provided code snippet for this. In the editor click where you want to insert the code and type the shortcut DeleteLabel and hit tab, or right click, select Insert Snippets… In the drop down select My Code Snippets and then the item with the aforementioned shortcut in its name).

C#

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Activities;

namespace FourthCoffee

{

publicsealedclassDeleteLabel : CodeActivity

{

publicInArgumentstring> LabelImagePath { get; set; }

protectedoverridevoid Execute(CodeActivityContext context)

{

string pathToImage = context.GetValue(this.LabelImagePath);

System.IO.File.Delete(pathToImage);

}

}

}

  1. Open LabelGenerator.cs.
  2. Rename the namespace Activities to FourthCoffee.
  3. Add the following three namespaces:

C#

using System.IO;

using System.ComponentModel;

using FourthCoffee.Imaging;

  1. The LabelGenerator activity exposes three string arguments. The first input argument provides the path to working folder where the activity can write its output. The second input argument provides the path to the uploaded, user provided image. The third output argument returns the path to the file written by the activity when it completes. To add such arguments add the following code just beneath the class declaration:

C#

public InArgument<string> WorkingFolderPath { get; set; }

public InArgument<string> SourceImagePath { get; set; }

public OutArgument<string> OutputImagePath { get; set; }

  1. For simplicity we define a constant for the name of the image of coffee beans that is overlayed on every label. Define a constant as follows and place it just under the class declaration as well.

C#

conststring STD_OVERLAY_IMAGENAME = "CoffeeBeansSmall.png";

  1. Replace the contents of the Execute method with the following code which makes use of the ImageFX imaging library. Note the use of SourceImagePath.Get(context), WorkingFolderPath.Get(context) and OutputImagePath.Set(context)— this is how one gets and sets the runtime values for arguments.

C#

//Modify image in working folder

string outImagePath = ImageFx.ApplyWaterMark(

SourceImagePath.Get(context),

WorkingFolderPath.Get(context),

"Fourth Coffee",

STD_OVERLAY_IMAGENAME);

//Update output path

OutputImagePath.Set(context, outImagePath);

  1. LabelGenerator.cs is now complete and should look as follows. If you need to, you can use the provided code snippet for this code. In the editor click where you want to insert the code and type the shortcut LabelGenerator and hit tab, or right click, select Insert Snippets… In the drop down select My Code Snippets and then the item with the aforementioned shortcut in its name).

C#

namespace FourthCoffee

{

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Activities;

using System.IO;

using System.ComponentModel;

using FourthCoffee.Imaging;

publicsealedclassLabelGenerator : CodeActivity

{

conststring STD_OVERLAY_IMAGENAME = "CoffeeBeansSmall.png";

publicInArgumentstring> WorkingFolderPath { get; set; }

publicInArgumentstring> SourceImagePath { get; set; }

publicOutArgumentstring> OutputImagePath { get; set; }

protectedoverridevoid Execute(CodeActivityContext context)

{

//Modify image in working folder

string outImagePath = ImageFx.ApplyWaterMark(

SourceImagePath.Get(context),

WorkingFolderPath.Get(context),

"Fourth Coffee",

STD_OVERLAY_IMAGENAME);

//Update output path

OutputImagePath.Set(context, outImagePath);

}

}

}

Exercise 2 Verification

In order to verify that you have correctly performed all steps of exercise2, proceed as follows:

Verification 1

In this verification, you will write a simple test harness that executes the LabelGenerator activity and displays the label that results.

  1. Add a new Workflow Console application to the solution called UnitTests.
  2. Delete the Workflow1.xaml created by the project template.
  3. Add a reference to the Activities project.
  4. Replace the code in Program.cs with the following. You can use the provided code snippet for this. In the editor click where you want to insert the code and type the shortcut UnitTestLabelGen and hit tab, or right click, select Insert Snippets… In the drop down select My Code Snippets and then the item with the aforementioned shortcut in its name).

C#

namespace UnitTests

{

using System;

using System.Linq;

using System.Activities;

using System.Activities.Statements;

using System.Collections.Generic;

classProgram

{

staticvoid Main(string[] args)

{

GenerateSampleLabel();

}

privatestaticvoid GenerateSampleLabel()

{

Dictionarystring, object> inputs = newDictionarystring, object>()

{

{ "WorkingFolderPath", @"C:\FourthCoffee\Output"},

{ "SourceImagePath", @"C:\FourthCoffee\Input\SampleInput.jpg"}

};

IDictionarystring, object> outputs = WorkflowInvoker.Invoke(new FourthCoffee.LabelGenerator(), inputs);

Console.WriteLine("Image Written to: " + outputs["OutputImagePath"]);

System.Diagnostics.Process proc = new System.Diagnostics.Process();

proc.EnableRaisingEvents = false;

proc.StartInfo.FileName = "explorer";

proc.StartInfo.Arguments = outputs["OutputImagePath"] asstring;

proc.Start();

proc.WaitForExit();

}

}

}

  1. Set the UnitTest project as the Startup Project.
  2. Run it without debugging.
  3. The default image viewer associated with the JPG file format should appear with the generated label.

Exercise 3: Using an Activity Designer

If you were to use the activity in a workflow now, it would appear with the default UI for custom activities. In this exercise you customize the appearance of the LabelGenerator activity so that it shows its properties on the design surface. You will accomplish this by applying a provided activity designer.

You will then add the LabelGenerator and DeleteLabel activitiesto the LabelingService to verify that the designer works as expected.

Applying the LabelGeneratorDesigner

  1. The solution folder includes a ready to go Activity Designer project, but you must first include it in the solution. Right click on your solution in Solution Explorer and select Add->Existing Project.
  2. Browse to your current solution’s folder, then open the ActivityDesigners folder and select the ActivityDesigners.csproj project and click Open.
  3. Build the solution.
  4. Add a file reference (do not use a project reference) to Activities.dll and Activity.Design.dll from the bin\debug output folder of the ActivityDesigners project.
  5. Build your solution.
  6. Open LabelingService.xamlx.
  7. From the toolbox, drag the LabelGenerator activity and connect it after the SendReplyToProcessLabel Request activity.
  8. Configure the properties on the designer surface with the following values, and verify that they also get set in the property grid.

Field Name / Value
Working Folder / workingFolderPath
Source Image / myOrder.OrderLineItems(0).CustomLabel
Output Image / generatedLabelPath
  1. The workflow service should look as shown:
  1. Finally, replace the DeleteLabel Placeholder at the bottom of this workflow with the DeleteLabel activity and configure the LabelImagePath accordingly

Field Name / Value
LabelImagePath / generatedLabelPath

Summary

In this lab you have both created and used custom activities and utilize an activity designer. You also saw how to unit test a custom activity.

1