Practical .NET

Simplify Workflow with Custom Activities

The number of built-in Activities that you can use to create a service that handles a long running service is small. Fortunately, it's easy to add additional Activities that wrap up business logic.

In my article on using Workflow to support long running operations in an service-oriented kind of way, I mentioned in passing that you have the option to create custom activities for your workflows. Here's how to do that.

In a workflow, you can call any method on any class by using the InvokeMethod Activity. It's a general-purpose tool;  however, that doesn't provide much support for the specific activity you're performing. That's especially obvious when you start passing parameters to the Activity. You don't get any IntelliSense support, just some error messages that will, at best, tell you if you have the right number of parameters of the right type. Creating your own custom Activities gives you an Activity dedicated to whatever task you want to perform with named, data-typed properties you can set reliably.

A custom Activity can be a simple wrapper around some existing method you'd otherwise call using the InvokeMethod activity, or your Activity can hold the actual code you want to execute.

To create a custom Activity, first add a class to your project and have it inherit from System.Activities.CodeActivity (under Add New Item, select the Activity template in the Workflow section):

 Imports System.Activities

Public NotInheritable Class SendEmails
Inherits CodeActivity

If you're only going to be using your custom Activity in one workflow, add it to the Workflow project. If you think your activity will be useful in multiple Workflows, create the class in a separate Class Library project.

Your Activity must override the Execute method of CodeActivity class (this is the method called by Windows Workflow when your Activity is processed). Workflow will pass that method a CodeActivityContext object, which gives you access to data in (and about) the Workflow:

Protected Overrides Sub  Execute(ByVal context As  CodeActivityContext)

End Sub

You next step is to define the properties a developer will use to pass values to your Activity. These properties must be declared using the generic InArgument<T>, specifying the data type you want the parameter to have. For any required parameter, you can decorate the property with the System.Activites.RequiredFieldAttribute to ensure that a developer using your Activity realizes that a value must be provided.

These two lines define two input values, one as a string and one as a Customer object (the CustomerParameter is required):

Property Body() As InArgument(Of String)
<RequiredArgument()>
Property CustomerToEmail() As InArgument(Of Customer)

Within your Execute method, you can retrieve the value of the parameter with the GetValue method of the CodeActivityContext object passed to the method, passing a reference to the Property you want to retrieve:
cust = context.GetValue(Me.Cust)

There's a corresponding SaveValue method you could use to return a value through one of these properties. To use that, declare the property as either an OutArgument or an InOutArgument. However, if you do need to return a value, it's a better practice to inherit from the CodeActivity<T> class, specifying the data type you'll return. The CodeActivity<T> class still includes an Execute method, but now it's a function that returns a value of the type you specified when you declared the class.

This example, for instance, creates an Activity that returns a Boolean value:

Public NotInheritable Class SendEmail
Inherits CodeActivity(Of Boolean)

Protected Overrides Function Execute(
context As System.Activities.CodeActivityContext) As Boolean
Return True

End Function

After you've finished coding your class, rebuild your application and you'll find that your Toolbox includes a new icon for the Activity you've just defined—you just have to drag it into your workflow and set its properties to have it execute in your workflow.

In addition to accessing the properties set on your Activity, it's also possible to access “Workflow-level” variables from within your Execute method. Like using an OutArgument, it's not a best practice (you're tying your CustomActivity to what are, effectively, global variables) but, in case you need it, here's the code to retrieve and set a Workflow variable:

Dim props As System.ComponentModel.PropertyDescriptorCollection
props = context.DataContext.GetProperties()
cust = CType(props("CurrentCustomer").
GetValue(context.DataContext), CustomerManagement.Customer)
cust.LastName = "Irvine"
props("CurrentCustomer").SetValue(context.DataContext, cust)

This is just the simplest custom Activity you can create. If, for instance, you have a long-running method, rather than pause the Workflow while you wait for the method to complete, inherit from ASyncCodeActivity (it has both a BeginExecute and EndExecute method); if you need something more sophisticated than a class with a single method, inherit from the NativeActivity class; if you want to coordinate the actions of other Activities, you can extend the Activity class itself. Really, whatever your need is there's an Activity object waiting for you to extend.

About the Author

Peter Vogel is a system architect and principal in PH&V Information Services. PH&V provides full-stack consulting from UX design through object modeling to database design. Peter tweets about his VSM columns with the hashtag #vogelarticles. His blog posts on user experience design can be found at http://blog.learningtree.com/tag/ui/.

comments powered by Disqus

Featured

  • Compare New GitHub Copilot Free Plan for Visual Studio/VS Code to Paid Plans

    The free plan restricts the number of completions, chat requests and access to AI models, being suitable for occasional users and small projects.

  • Diving Deep into .NET MAUI

    Ever since someone figured out that fiddling bits results in source code, developers have sought one codebase for all types of apps on all platforms, with Microsoft's latest attempt to further that effort being .NET MAUI.

  • Copilot AI Boosts Abound in New VS Code v1.96

    Microsoft improved on its new "Copilot Edit" functionality in the latest release of Visual Studio Code, v1.96, its open-source based code editor that has become the most popular in the world according to many surveys.

  • AdaBoost Regression Using C#

    Dr. James McCaffrey from Microsoft Research presents a complete end-to-end demonstration of the AdaBoost.R2 algorithm for regression problems (where the goal is to predict a single numeric value). The implementation follows the original source research paper closely, so you can use it as a guide for customization for specific scenarios.

  • Versioning and Documenting ASP.NET Core Services

    Building an API with ASP.NET Core is only half the job. If your API is going to live more than one release cycle, you're going to need to version it. If you have other people building clients for it, you're going to need to document it.

Subscribe on YouTube