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

  • Full Stack Hands-On Development with .NET

    In the fast-paced realm of modern software development, proficiency across a full stack of technologies is not just beneficial, it's essential. Microsoft has an entire stack of open source development components in its .NET platform (formerly known as .NET Core) that can be used to build an end-to-end set of applications.

  • .NET-Centric Uno Platform Debuts 'Single Project' for 9 Targets

    "We've reduced the complexity of project files and eliminated the need for explicit NuGet package references, separate project libraries, or 'shared' projects."

  • Creating Reactive Applications in .NET

    In modern applications, data is being retrieved in asynchronous, real-time streams, as traditional pull requests where the clients asks for data from the server are becoming a thing of the past.

  • AI for GitHub Collaboration? Maybe Not So Much

    No doubt GitHub Copilot has been a boon for developers, but AI might not be the best tool for collaboration, according to developers weighing in on a recent social media post from the GitHub team.

  • Visual Studio 2022 Getting VS Code 'Command Palette' Equivalent

    As any Visual Studio Code user knows, the editor's command palette is a powerful tool for getting things done quickly, without having to navigate through menus and dialogs. Now, we learn how an equivalent is coming for Microsoft's flagship Visual Studio IDE, invoked by the same familiar Ctrl+Shift+P keyboard shortcut.

Subscribe on YouTube