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

  • Visual Studio Takes Aim at Copilot Billing Shock

    Beyond Copilot usage visibility, the June update delivers several other enhancements centered on AI-assisted development, security and quality-of-life improvements. Here's a quick rundown of the remaining additions announced by Microsoft.

  • Claude AI Gets Yet Another Boost in VS Code 1.128

    The July 8, 2026, Visual Studio Code update expands agent workflows, chat attachments, browser-tab controls, OS-level shortcuts and enterprise telemetry management.

  • TypeScript 7 Arrives to Rock VS Code with Go-Powered Speed

    Microsoft says TypeScript 7, announced July 8, brings native Go performance to VS Code, Visual Studio and other editors.

  • Full-Stack with a Side of Copilot: Building and Deploying an App the AI-Accelerated Way

    In this Q&A, developer and VSLive! speaker Esteban Garcia explains how GitHub Copilot can accelerate the full software development lifecycle -- from architecture and code to tests, CI/CD, and Azure deployment -- and how to use it as a repeatable engineering workflow rather than just a faster autocomplete tool.

Subscribe on YouTube