New Community Website

Ordinarily, you'd be at the right spot, but we've recently launched a brand new community website... For the community, by the community.

Yay... Take Me to the Community!

The Community Blog is a personal opinion of community members and by no means the official standpoint of DNN Corp or DNN Platform. This is a place to express personal thoughts about DNNPlatform, the community and its ecosystem. Do you have useful information that you would like to share with the DNN Community in a featured article or blog? If so, please contact .

The use of the Community Blog is covered by our Community Blog Guidelines - please read before commenting or posting.

Implementing Workflow in a Custom DNN Module


DNN introduced the Workflow API in version 7.4 which allows custom modules to leverage the core workflow processes in their custom module.  You can watch the overview of the 7.4 features here.  Big thanks to Francesco Rivola and Xepient Solutions for their work in this feature.  I finished a code project and training tutorial for on this topic.  I created a module project from the ChrisHammond DAL2 C# template using the sample code in the template and layered on a complete workflow process.  This project is only available to Premium DNNHero subscribers at the moment.  If you’re planning on dealing with this topic, I recommend reading further and subscribing.

What is Workflow?

Workflow allows you to create a publishing flow that provides an approval process between when a user creates the content and when the content becomes live.  DNN has built a few different workflow processes for the HTML and File Manager modules.  Now that release 7.4 is out, third-party module developers can tap into the same workflow mechanism and definitions.  Content workflow in a DNN module, when done right, has the following attributes:

  • Review & Approve Content
  • Different workflow processes can be chosen or defined
  • Notifications to appropriate users
  • Versioning and Rollback capability

Setup work for Workflow

The first thing you need to know about using the Workflow API is that it works inherently with Content Items.  My suggestion is to use the Content Items API as a storage mechanism for your versioned content (more on this later).  To do this, you need to create a content item type for your custom versioned content.  You also need to register your 5 workflow action classes as per Francesco’s instructions in the introduction video.  To do this, I suggest implementing IUpgradeable in your module in order to perform these one-time actions.
string IUpgradeable.UpgradeModule(string Version)
	WorkflowActionManager _workflowActionManager = new WorkflowActionManager();
	var message = String.Format("DotNetNuclear Workflow Upgradeable actions for version {0}.)", Version);
	switch (Version)
		case "00.00.01":
			// Register content type id necessary for participating in the workflow api
			int contentTypeId = Integration.Content.Instance.AddContentType(Common.Constants.CONTENTTYPENAME);
			message += "Added content type for workflow. " + Environment.NewLine;
			// Register 5 workflow actions: StartWorkflow, CompleteWorkflow, DiscardWorkflow, DiscardState, CompleteState
			_workflowActionManager.RegisterWorkflowAction(new WorkflowAction
				ContentTypeId = contentTypeId,
				ActionType = WorkflowActionTypes.StartWorkflow.ToString(),
				ActionSource = typeof(WorkflowStartAction).AssemblyQualifiedName
			_workflowActionManager.RegisterWorkflowAction(new WorkflowAction
				ContentTypeId = contentTypeId,
				ActionType = WorkflowActionTypes.CompleteWorkflow.ToString(),
				ActionSource = typeof(WorkflowCompleteAction).AssemblyQualifiedName
			_workflowActionManager.RegisterWorkflowAction(new WorkflowAction
				ContentTypeId = contentTypeId,
				ActionType = WorkflowActionTypes.DiscardWorkflow.ToString(),
				ActionSource = typeof(WorkflowDiscardAction).AssemblyQualifiedName
			_workflowActionManager.RegisterWorkflowAction(new WorkflowAction
				ContentTypeId = contentTypeId,
				ActionType = WorkflowActionTypes.DiscardState.ToString(),
				ActionSource = typeof(StateDiscardAction).AssemblyQualifiedName
			_workflowActionManager.RegisterWorkflowAction(new WorkflowAction
				ContentTypeId = contentTypeId,
				ActionType = WorkflowActionTypes.CompleteState.ToString(),
				ActionSource = typeof(StateCompleteAction).AssemblyQualifiedName
			message += "Added workflow actions. " + Environment.NewLine;
	return message;

The other thing we need to setup is a module setting for allowing the administrator to select the workflow they want to implement. These workflows are defined in the DNN core. Here is a screenshot of the module setting I added.

To get the list of workflows defined in the current portal, I use the following code:

WorkflowManager _workflowManager = new DotNetNuke.Entities.Content.Workflow. WorkflowManager();
var workflows = _workflowManager.GetWorkflows(PortalId).Select(w => new { WorkflowID = w.WorkflowID, WorkflowName = w.WorkflowName });

Versioning and Workflow

The basis for how I do versioning is as follows: I create my main “Item” which is my module’s domain object. This would be equivalent to an “Article” or “HTML” entity in other publishing modules. I use the DAL2 data layer to persist my object which is the same code created by the template. At the same time, I use the Content Items API in DNN to create my versioned items. The content field of the versioned item is a json-serialized version of my Item entity class. I also use the DAL2 Item ID (primary key) as the Content Key of the Content Item to relate back to the main Item. This approach has the advantage of not adding overhead to the “reads” of our main Item repository – we only need to read from the DAL2 controller to get the main items. Version items (content items) are only needed in the editing process.

Create New “Item”

When you save an item, this is the flow that should happen. You need to create the DAL2 item and then create the versioned content item with the Item ID as its Content Key. Then you will send that versioned content item into the workflow process.

WorkflowEngine _workflowEngine = new WorkflowEngine();
_workflowEngine.StartWorkflow(workflowId, contentItem.ContentItemId, PortalSettings.Current.UserId);

The workflow ID in the StartWorkflow parameters comes from the module setting the user has selected as the desired workflow. The content Item ID comes from your versioned content item.

Update an “Item”

Updating an item is not much different than the create process. You don’t need to do anything to the main item, just create the versioned item and send it to the workflow process

Workflow State Actions

So how do we control what happens when we start the workflow? Remember the IUpgradeable method? Those action state classes that we registered will get called by the framework when users interact with the workflow items from the Notifications area of the User Messaging page. Here is a sample flow:

Create new item and start workflow using Content Approval workflow.

  1. My WorkflowStartAction’s DoActionOnStateChanging() is fired (no action)
  2. My WorkflowStartAction’s DoActionOnStateChanged() is fired (no action)
  3. My WorkflowStartAction’s GetActionMessage() is fired (produce message content)
  4. A Notification is sent to the appropriate user (if Content Approval, then it is the content creator to Submit for review). The subject and body of this message is defined in the GetActionMessage() return.
  5. Creator user gets Notification message with action links for Submit and Discard. User clicks the Submit link.
  6. My StateCompleteAction’s DoActionOnStateChanging() is fired (no action)
  7. My StateCompleteAction’s DoActionOnStateChanged () is fired (no action)
  8. My StateCompleteAction’s GetActionMessage() is fired (produce message content)
  9. A Notification is sent to the appropriate users with the action links to Approve or Discard the content. The review clicks Approve link.
  10. My WorkflowCompleteAction’s DoActionOnStateChanging() is fired (no action)
  11. My WorkflowCompleteAction’s DoActionOnStateChanged () is fired. Here I have code that will take the content item from the workflow, deserialize it, update the main content item, and set the main content item to active.

Version History

The Workflow API doesn't help us with showing users the version history. We need to handle this. I created a simple module view that lays out all content items related to the main item and provides some viewing and workflow actions.  In this screenshot you can see I added a Version History button to the main module view.  When clicked it takes you to the History module view.

The version History view shows the versions associated with the main item.  The bold indicates the versioned item that matches the main item (current).  You can delete, view, and move the item to the next state, where applicable.

Next Steps

Get the code and watch the full tutorial on  If you are a subscribing member, you get access to the Premium Forums, many other advanced module development tutorials and code projects, and over a hundred hours of other administration videos and how to’s.


Joe Brinkman
Great post. Glad to see the community looking at the new API. We'll be adding a Workflow Manager as part of DNN 7.5 which will make this an even more powerful option for module developers.
Joe Brinkman Tuesday, March 24, 2015 11:37 PM (link)
Good post Scott.
People, you can see an example of how to interact with Page Versioning in the following post:

I think it is complementary to this post and will help you to understand better Versioning steps and needed componentsin order to develop your own versionable module
agrueso Monday, June 29, 2015 11:30 AM (link)
Roman Yagodin
Nice article, but due to lack of public documentation on Workflow API it could be much nicer with source code of WorkflowAction's.
Roman Yagodin Friday, February 19, 2016 4:03 AM (link)

Comment Form

Only registered users may post comments.


Aderson Oliveira (22)
Alec Whittington (11)
Alessandra Davies (3)
Alex Shirley (10)
Andrew Hoefling (3)
Andrew Nurse (30)
Andy Tryba (1)
Anthony Glenwright (5)
Antonio Chagoury (28)
Ash Prasad (37)
Ben Schmidt (1)
Benjamin Hermann (25)
Benoit Sarton (9)
Beth Firebaugh (12)
Bill Walker (36)
Bob Kruger (5)
Bogdan Litescu (1)
Brian Dukes (2)
Brice Snow (1)
Bruce Chapman (20)
Bryan Andrews (1)
cathal connolly (55)
Charles Nurse (163)
Chris Hammond (213)
Chris Paterra (55)
Clint Patterson (108)
Cuong Dang (21)
Daniel Bartholomew (2)
Daniel Mettler (181)
Daniel Valadas (48)
Dave Buckner (2)
David Poindexter (12)
David Rodriguez (3)
Dennis Shiao (1)
Doug Howell (11)
Erik van Ballegoij (30)
Ernst Peter Tamminga (80)
Francisco Perez Andres (17)
Geoff Barlow (12)
George Alatrash (12)
Gifford Watkins (3)
Gilles Le Pigocher (3)
Ian Robinson (7)
Israel Martinez (17)
Jan Blomquist (2)
Jan Jonas (3)
Jaspreet Bhatia (1)
Jenni Merrifield (6)
Joe Brinkman (274)
John Mitchell (1)
Jon Henning (14)
Jonathan Sheely (4)
Jordan Coopersmith (1)
Joseph Craig (2)
Kan Ma (1)
Keivan Beigi (3)
Kelly Ford (4)
Ken Grierson (10)
Kevin Schreiner (6)
Leigh Pointer (31)
Lorraine Young (60)
Malik Khan (1)
Matt Rutledge (2)
Matthias Schlomann (16)
Mauricio Márquez (5)
Michael Doxsey (7)
Michael Tobisch (3)
Michael Washington (202)
Miguel Gatmaytan (3)
Mike Horton (19)
Mitchel Sellers (40)
Nathan Rover (3)
Navin V Nagiah (14)
Néstor Sánchez (31)
Nik Kalyani (14)
Oliver Hine (1)
Patricio F. Salinas (1)
Patrick Ryan (1)
Peter Donker (54)
Philip Beadle (135)
Philipp Becker (4)
Richard Dumas (22)
Robert J Collins (5)
Roger Selwyn (8)
Ruben Lopez (1)
Ryan Martinez (1)
Sacha Trauwaen (1)
Salar Golestanian (4)
Sanjay Mehrotra (9)
Scott McCulloch (1)
Scott Schlesier (11)
Scott Wilkinson (3)
Scott Willhite (97)
Sebastian Leupold (80)
Shaun Walker (237)
Shawn Mehaffie (17)
Stefan Cullmann (12)
Stefan Kamphuis (12)
Steve Fabian (31)
Steven Fisher (1)
Tony Henrich (3)
Torsten Weggen (3)
Tycho de Waard (4)
Vicenç Masanas (27)
Vincent Nguyen (3)
Vitaly Kozadayev (6)
Will Morgenweck (40)
Will Strohl (180)
William Severance (5)
What is Liquid Content?
Find Out
What is Liquid Content?
Find Out
What is Liquid Content?
Find Out