Mark Everard

Hello, I'm Mark – a PhD physicist turned technologist / architect.

Archive for the ‘Workflow’ Category

Developing a custom workflow in EPiServer : Part four

with 4 comments

This is the fourth post, in a series of five about developing a custom workflow in EPiServer. By now you’ve probably forgotten the first three.

An important part of our custom workflow is the decisions that are made by users in the Approvers and Publishers roles. These external choices will alter the current state of the workflow process (which if you remember is the reason we chose a StateMachineWorkflow way back in part two). In this case EPiServer is acting as the user interface for all workflow decisions.

EPiServer provides a framework to achieve this by tying together the concept of an EPiServer Task with the Workflow and an EventDrivenActivity within the workflow.

What is an EPiServer Task?

An EPiServer.Personalization.Task describes a task assignable to users in the system. Each task can be assigned to a single user, or a group of users, and can be assigned a due date. Notification of assigned tasks are emailed to each user and are also visible on the page tree tab (and also a Dashboard gadget in CMS 6).

Tasks can be created programatically, though they are mainly used internally by EPiServer to provide a means of notifying editors of any actions that they are required to take in the CMS. Tasks can also be created programatically from the Workflow designer surface by dragging and dropping an EPiServer.WorkflowFoundation.Activities.CreateTask activity which is included in the EPiServer workflow library. This Activity allows you to bind all of the Task properties using the designer interface. This allows your workflow to create Tasks, and so notify users of any necessary actions.

Using tasks to tie the workflow to the user interface
The EPiServer.Personalization.Task type has a property called AssociatedActivity which allows you to associate a workflow Activity with a given Task. EPiServer then provides a way Activities themselves can be associated with UI elements (such as a standard ASP.NET User control) by decorating your custom Activity with an ActivityPlugin attribute.

This is where the ability to define partial classes and have the c# compiler combine them into one comes in extremely handy. In part three we used the external wca.exe tool to generate four custom EventDriven activities based against the four events defined in our custom communication service (ITwoStageService). These were PageApproved, PageDeclined, PagePassedToApprover and PagePassedToPublisher.

Rather than adding these attributes to the auto-generated code, we can create some partial stubs with the same signature and add the ActivityPlugin attribute to those. These prevents you having to touch the auto-generated code. For example:

using EPiServer.WorkflowFoundation.UI;

namespace TwoStage.Workflow.Activities
{
     [ActivityPlugIn(Area = EPiServer.PlugIn.PlugInArea.None, Url = "~/Templates/Plugins/Workflows/ApproverDecisionControl.ascx")]
     public partial class PagePassedToApprover
     {
            //no implementation - just a partial stub
     }
}

This Attribute specifies a User Control and so forms the final link between a Task (created in the workflow using the CreateTask Activity), a custom Activity and a user interface element.

If the AssociatedActivity is not set for a Task, the corresponding workflow instance will be searched to find any waiting (event-based) activities when the task link is followed by the user. The User Control specified by the attribute will be loaded if any of the waiting activities has a valid ActivityPlugIn attribute set. If no valid attribute is found the default edit task control will be loaded. If this isn’t found then you’ll end up with strange errors of the form ‘Cannot redirect HTTP headers already sent”. What this basically means is that you haven’t specified a User Control and so EPiServer doesn’t know which UI to load.

Your custom User Control must adhere to the IWorkflowTaskControl interface which contains the following methods ContextData, InvokeEvent, PageHasChanged and the property InvokeButtonText.

  • ContextData – is called when the user control is first loaded and as its name suggest, it is used to pass the ‘context’ of the workflow to the control. This obviously depends on the exact functionality defined in your custom workflow and what data you wish to present to the user. An example is presented below, which loads the current task and binds the task subject, description and associated workflow pageLink to controls on the page.

public void ContextData(Guid workflowInstanceId, int taskId, string eventQualifiedName, PageReference pageLink)
{
      Task currentTask = new TaskDB().Load(taskId);
      ltrTaskSubject.Text = currentTask.Subject;
      ltrMessageFromCreator.Text = currentTask.Description;
      pageRoot.PageLink = pageLink;
 }

  • InvokeEvent – when your User Control is presented in the Action Window, EPiServer adds two buttons beneath your control. One is a cancel button and the other is a submit button which you can name by overriding the InvokeButtonText property. This button also fires an event which you must hook into using the InvokeEvent method. It is in this method that you can put code which can raise events in the workflow. In our case we want to raise one of our custom events that will be handled by an EventDrivenActivity in the workflow. This will kick the workflow into a new state and run any of the StateInitializationActivities that we bound using the designer surface.

public bool InvokeEvent(Guid workflowInstanceId, int taskId, string eventQualifiedName)
{
         //get current task information
	var task = Task.Load(taskId);

	//get workflow communication service
	TwoStageService service = WorkflowSystem.WorkflowManager.GetService<TwoStageService>();

       var entityName = GetApproverGroupOrUserName();
       var taskOwner = GetTaskOwner(entityName);

       //create the eventArgs from the current control context and raise the necessary event in the workflow communication service
       var passOnEventArgs = new EmailWorkflowEventArgs(workflowInstanceId, pageRoot.PageLink, taskOwner,
	                                           actioningUser, tbxMessageApprover.Text);
       service.RaiseEmailPassedToApprover(passOnEventArgs);

       //Deletes current task if true is returned
       return true;
}

  • PageHasChanged – this method will be called prior to InvokeEvent if the page viewed in the edit tab has changed since ContextData was invoked. Here you can handle what should happen in this scenario, for example whether you want the Workflow to act against the new page shown in the edit window.

Additional User Interface elements

EPiServer also allows you to associate a user control with the act of creating a workflow instance. This control should use the IWorkflowStartParameterHandler instance. This control will be seen in either Admin mode or the Action window. It allows you to specify start parameters for the workflow instance using the LoadStartParameters and SaveStartParameter methods. For example:

public void LoadStartParameters(Guid definitionId, PageReference pageLink, bool definitionMode, IDictionary<string, object> parameters)
{
      //if workflow already contains state then set the current pagelink
      if (parameters.ContainsKey("WorkflowState") && parameters["WorkflowState"].GetType() == typeof(EmailWorkflowEventArgs))
            pageRoot.PageLink = ((EmailWorkflowEventArgs)parameters["WorkflowState"]).PageLink;

      //if loaded from edit tab, set pagelink if not already set
      if (PageReference.IsNullOrEmpty(pageRoot.PageLink) && Page.Request["currentPage"] != null)
            pageRoot.PageLink = pageLink;

      //perform any other necessary interface actions here...
  }

public IDictionary<string, object> SaveStartParameters()
{
      var startParameters = new Dictionary<string, object>();

      //custom class to represent task ownership
      var newTaskOwner = new GroupTaskOwner(ddlUsers.SelectedValue);
      var actioningOwner = new ActioningUserTaskOwner();

      //set initial state from user interface data
      startParameters["WorkflowState"] = new EmailWorkflowEventArgs(Guid.Empty, pageRoot.PageLink,
                                                              newTaskOwner, actioningOwner, tbxDescription.Text);
      startParameters["WorkflowCreator"] = tbxOwner.Text;
      return startParameters;
}

That’s covered off the basics of hooking up external user interface events to an EPiServer workflow. Additional detailed information can be found in the EPiServer SDK. Hopefully the combination of these two posts will provide enough information for you to create some intricate EPiServer workflows. In the final part of this series we’ll cover off some finishing touches that you may want to add, such as only allowing one instance of a particular workflow and some elements of Access control. I promise it won’t take as long to get the final installment……. 😉

Written by mark

January 24th, 2011 at 9:30 am

Developing a custom workflow in EPiServer : Part three

without comments

This is the third post, in a series of five about developing a custom workflow in EPiServer.

Designing the Workflow

Now we’ve defined the communication pathway between the application (EPiServer) and the Workflow runtime, it’s time to use the designer surface to piece together pre-existing Windows workflow activities, the EPiServer specific activities and the bespoke EventDriven Activities that are raised by the host application.

This is done using a Visual Studio design surface which auto-magically generates code files descibing your workflow based on what you drag-and-drop onto the surface. Personally I hate developing in this manner, the one redeeming feature is that it does make for some easily created diagrams demonstrating whats going on!

workflow-design-surface

Here you can see the Two-stage workflow laid out on the designer surface. Each box represents a unique State within this StateMachine workflow, with the arrows indicating the possible transitions between states. Initally the workflow process is created in the InitialState (which is set by right-clicking and selecting the relevant option from the dropdown).

Each State contains a list of Activities, for example the PageApproverState contains four activities; three bespoke EventListener activites and a StateInitialization Activity. You’ll notice I’ve used an StateInitialization Activity as a common pattern across all of the States (barring the FinalizedState – which has no additional work to do). This will initialise / modify any data within the workflow to be persisted (which in our case is the data encapsulated by our TwoStageEventArgs).

StateInitializationActivity

StateInitialization Activities, available by dragging and dropping from the Visual Studio Toolbox 🙁 are performed by the workflow runtime as soon as the StateMachine workflow is moved into that particular state. They can contain any number of other Activities, so as to make up your exact Workflow requirements.

We’ll be using them to perform the following common tasks:

1) Email the workflow creator to inform them of the change of workflow state.
2) Create an EPiServer Task (which are designed for informing users or groups about workflow tasks), using the information provided in the passed TwoStageEventArgs.
3) Moving the workflow into a sequential state when there is no input expected from an external user (for example in the case of moving from PageApprovedState to FinalisedState)

The code for each of these three types of Activity is already implemented for us (either within the Windows Workflow library or the EPiServer Workflow library), so all we have to do is drag them from the Visual Studio toolbox onto our workflow surface and bind the relevant data to them. Sending emails is handled by the SendEmailActivity available within Windows Workflow Foundation library, as is the SetStateActivity – which moves the StateMachine into a defined State.

The CreateTaskActivity (defined in EPiServer.WorkflowFoundation.dll – make sure this assembly in added to the Visual Studio toolbox) creates a Task associated with a User or Group and also sends an Email notification to them. EPiServer Tasks nicely bind the workflow to the EPiServer user interface, so the User/Group will see them and be able to interact as soon as they log in to EPiServer.

Additionally there are some activities that must occur in particular workflow states, such as initially associating the workflow with an EPiServer page so that the EPiServer UI is aware that the current workflow instance is asscociated with a page and can populate the Workflow tab in Edit mode with the workflow instance details. We’ll look at these in the next post

EventDrivenActivity

EventDriven activities are the most important part of a StateMachine workflow as they are the activities which are execute when a pre-defined user event is raised from the host application. Microsoft have helpfully provided a tool for creating the bespoke EventDriven Activites based on the events and methods that are exposed from any class marked with the [ExternalDataExchange()] attribute.

The (wca.exe) tool lives in:

“Program Files””Microsoft SDKs”Windowsv6.0Abin

and when run it needs to be passed arguments informing it of the path of the assembly to scan and also the namespace and location in which to create the bespoke EventDriven activities. In our solution it will find the ITwoStageService and create EventDriven activities based against the four events defined in that service, PageApproved, PageDeclined, PagePassedToApprover and PagePassedToPublisher.

Each of these bespoke event-activities needs to persist the current state of the workflow, and also move the StateMachine to a subsequent State. The first of these responsibilities is handled by a custom piece of code with the workflow code file, which must be bound to the Invoked handler on each custom Event activity.


private void OnExternalUiEventHandled(object sender, ExternalDataEventArgs e)
{
   var currentArgs = e as TwoStageEventArgs;
   if (currentArgs == null)
      throw new ArgumentException("Workflow event args were not of type TwoStageEventArgs");
   WorkflowStateArgs = currentArgs;
}

This code stores the passed TwoStageEventArgs locally in the workflow so that when the workflow runtime persists the current state, the TwoStageEventArgs are serialised and stored too.

After Structure – comes Databinding….

After you’ve set up your structure you will need to bind specific data to your Activities within each State. This is done using the Visual Studio Properties window, where you will need to hook up and bind a code property to the Activity property. In most cases these properties will be encompassed in your TwoStageEventArgs (which effectively represent the current Workflow State), however sometimes there are specific pieces of data needed, such as the SMTPsettings or simple strings. In these cases, add additional code properties (with getters) within you workflow class to get the data and then bind your Activity property to these properties.

Next post , we’ll cover EPiServer Tasks and also how to present Workflow User Interfaces in Edit and Admin mode.

Written by mark

November 10th, 2010 at 10:19 am

Developing a custom workflow in EPiServer : Part two

without comments

This is the second post, in a series of five about developing a custom workflow in EPiServer.

Windows Workflow Foundation in EPiServer

Windows workflow is a full runtime that can be hosted by any application running on the Microsoft ASP.NET stack. It defines all of the services that are necessary (persistence, threading and transactions) to manage the steps, rules and decisions that make up any arbitrary workflow task.

EPiServer first implemented Windows Workflow Foundation in CMS 5. The EPiServer application acts as the host for the workflow runtime, telling the workflow runtime the type of workflows to create, and communicating any events to the runtime. The runtime itself manages the workflow lifecycle, including persistence and will notify the host of any important workflow events such as completion or termination.

You can see the configuration for the workflow foundation within the EPiServer web.config file, where the runtime itself is configured and the persistence mechanism is setup, which is either a SQL Server or Oracle database (state is serialised into one of two workflow related tables called InstanceState and CompletedScope).

There are two main types of workflows that can be developed using Windows workflow foundation

  1. StateMachineWorkflows
  2. SequentialWorkflows

A SequentialWorkflow is predictable and can only execute in the given order which is defined within the workflow code itself. In this sense you can describe the workflow itself as being in control of its execution pathway. A StateMachineWorkflow relies on external events to drive it to completion and the external decisions define which state the workflow transitions to, meaning that it is the external events that are in control.

Our requirements can be satisifed by a StateMachineWorkflow as the external decisions that are made by Approvers or Publishers define the workflow completion order.

You can select a new StateMachineWorkflow directly from the Visual Studio ‘Add item’ command, you’ll see that it simply inherits from StateMachineWorkflowActivity:

public sealed partial class TwoStagePageWorkflow : StateMachineWorkflowActivity
{
   //implementation
}

Communication

The most important element to define is the code that specifies how your host application communicates with the workflow runtime.

Workflow foundation diagram

The workflow can either handle external events or invoke external methods. This is defined by the contents of a Service interface that is marked with the [ExternalDataExchange()] attribute.

using System;
using System.Workflow.Activities;

namespace TwoStage.Workflow.Communication
{
	[ExternalDataExchange()]
	public interface ITwoStageService
	{
		event EventHandler<TwoStageEventArgs> PageApproved;
                event EventHandler<TwoStageEventArgs> PageDeclined;
                event EventHandler<TwoStageEventArgs> PagePassedToApprover;
                event EventHandler<TwoStageEventArgs> PagePassedToPublisher;
	}
}

For our two-stage workflow, we need to handle the following external events, which correspond to decisions that a Creator, Approver or Publisher will make within the EPiServer Ui, which will then be raised by a concrete implementation of the above ITwoStageService interface. These events map to our StateMachine moving between the following four states.

  1. WithApprover
  2. WithPublisher
  3. PageDeclined
  4. PageApproved

Also you must define the event arguments that are passed when any of the external events are raised and then handled.


using System;
using System.Workflow.Activities;
using EPiServer.Core;
using TwoStage.Workflow.Domain.TaskOwner;

namespace TwoStage.Workflow.Communication
{
[Serializable]
public class TwoStageEventArgs : ExternalDataEventArgs
{
   public TwoStageEventArgs(Guid instanceId, PageReference pageLink, TaskOwner assignedTo, TaskOwner wasAssignedTo, string message) : base(instanceId)
   {
      PageLink = pageLink;
      IsAssignedTo = assignedTo;
      WasAssignedTo = wasAssignedTo;
      Message = message.Replace(Environment.NewLine, "<br />");
   }

   public PageReference PageLink { get; internal set; }

   public TaskOwner IsAssignedTo { get; internal set; }
   public TaskOwner WasAssignedTo { get; internal set; }

   public string Message { get; internal set; }
   }
}

Our custom EventArgs will hold the following properties, a PageReference so we know which EPiServer page the workflow task has been created against. A custom type allowing us to know who the current actioning user or role is, and also the same type to define who previously was responsible. It also defines a string based message.

Any custom EventArgs must inbherit from ExternalDataEventArgs and must be marked as serializable (as must all types defined within the EventArgs class).

If you look at the default EPiServer web.config file / EPiServer.config, you will see that there are three communication services defined for the out-of-the-box workflows. Any new service should also be registered here.

<externalServices>
   <!-- externalService:      Custom services that is to be registered with workflow runtime-->
   <externalService type= "EPiServer.WorkflowFoundation.Workflows.ApprovalService,EPiServer.WorkflowFoundation" />
   <externalService type= "EPiServer.WorkflowFoundation.Workflows.ReadyForTranslationService,EPiServer.WorkflowFoundation" />
   <externalService type= "EPiServer.WorkflowFoundation.Workflows.RequestForFeedbackService,EPiServer.WorkflowFoundation" />
</externalServices>

Now we’ve defined the communication pathway between the workflow runtime and the EPiServer host application, we can design the workflow itself and create the activities that the workflow must perform.

See you for part 3 😉

Written by mark

September 30th, 2010 at 10:00 pm

Developing a custom workflow in EPiServer : Part one

without comments

This in a first of a series of posts about how to build a custom EPiServer workflow. There is a fair amount of information about EPiServer workflows (most of which are nicely summarised by Frederik Vig in his amazingly useful EPiServer Developer Resources). Hopeful these posts will be able to add to that and also provide further context. Comments are most welcome!

Over the course of five posts I’ll cover the following:

Overview

The out-of-the box workflows in EPiServer cover off some common editor based scenarios such as approval and feedback. However the real world always throws up more exacting requirements that can’t be satisfied by the pre-defined workflows (or any combination of them – remember that you can chain them together).

In these situations you’ll have to build your own custom workflow piece utilising Windows Workflow Foundation and EPiServer’s own library of workflow elements and UI integration.

Requirements

First up we must start with requirements gathering. This is often a difficult process with workflows as you have to map and consolidate real business processes into a sensible technical solution.

Workflow requirements are always best distilled by a Visio flow diagram. Fact! So here’s ours (click to enlarge).

2 Stage Workflow Diagram

Looking at the required process, you can see that it’s a 2-stage approval process, where a page is created, sent for approval, and then finally sent through a further approval step, where upon the page is approved one final time before being published.

There are three user roles who interact with this process:

  • Creators, who initially create the content and kick off the workflow.
  • Approvers, who can decide to approve or decline the content, or pass on the approval decision.
  • Publishers, who can approve or decline the content, and publish the content.

This is only slightly different than the out-of-the-box Sequential Approval workflow, but the following differences necessitate a custom solution:

  1. There are two stages of approval
  2. Approvers can opt to ‘pass-the-buck’ and pass the approval stage to a fellow Approver.
  3. The workflow creator is informed at every decision point.

There are some additional requirements that can’t be shown on the diagram:

  1. Pages can only be published using the workflow
  2. Only one instance of the workflow can be in progress for a page at any one time.

Over the course of the next four posts I’ll piece together a workflow that meets the above requirements, covering the (very) basics of Windows Workflow Foundation and how you can use them and the integration methods that the EPiServer API exposes to allow any custom workflow to be implemented against EPiServer managed content.

Written by mark

September 29th, 2010 at 1:30 pm

Debugging Workflows and HttpContext

without comments

I stumbled across one of those ‘frustrating’ bugs last week whilst setting up some simple chained out-of-the-box EPiServer workflows.

The EPiServer UI reported that the workflow instance has successfully started, but no tasks or notifications were ever received and an immediate search for any running workflow instances reported that there were none in progress.

I wasn’t really sure where to start looking. I fired up SQL Profiler and could see that there were some workflow queries being passed, but no data was stored at the end of the request so something, somewhere along the line, was causing the transaction to be rolled back.

Debugging Workflows

The workflow module in EPiServer is built upon the .NET 3.0 workflow functionality and allows custom workflows to be developed which handle typical EPiServer events.

Workflows are managed under their own runtime, which itself is hosted by a .NET application – in this case EPiServer. The runtime is configured in the web.config file, along with the persistence mechanism (SQL Server).

<workflowRuntime EnablePerformanceCounters="false">
    <Services>
        <add type="System.Workflow.Runtime.Hosting.DefaultWorkflowSchedulerService, System.Workflow.Runtime, Version=3.0.00000.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" maxSimultaneousWorkflows="5" />
        <add type="System.Workflow.Runtime.Hosting.SqlWorkflowPersistenceService, System.Workflow.Runtime, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" UnloadOnIdle="true" />
    </Services>
</workflowRuntime>

Debugging can be enable by adding in and adjusting the following keys (which are commented out towards the bottom of the EPiServer web.config file).

<!--OPTIONAL Workflow Diagnostics - used for logging useful information for debugging purposes-->
  <system.diagnostics>
    <switches>
      <add name="System.Workflow.Runtime" value="Off" />
      <add name="System.Workflow.Runtime.Hosting" value="Off" />
      <add name="System.Workflow.Runtime.Tracking" value="Off" />
      <add name="System.Workflow.Activities" value="Off" />
      <add name="System.Workflow.Activities.Rules" value="Off" />
      <add name="System.Workflow LogToTraceListeners" value="1" />
      <add name="System.Workflow LogToFile" value="0" />
    </switches>
  </system.diagnostics>

There are two choices of where to log any workflows messages, to file or to a TraceListener (aka the output window in Visual Studio). Full details of the configuration settings can be on found on MSDN.

HttpContext?

Setting up the workflow logging – quickly highlighted my issue. One of the methods in the workflow was throwing an exception which the EPiServer UI wasn’t reporting. The exact stack trace pointed to the application’s custom ProfileProvider which was explicitly trying to store items in the HttpContext.Current.Cache.

Remember that workflows are executed using their own runtime, which means they operate on a thread outside of the current web request. So trying to resolve the HttpContext will always return null. Problem identified!

In summary:

  1. Debugging workflows can be enabled in web.config.
  2. Workflows operate under their own runtime.
  3. Never assume an HttpContext – be careful when rolling your own providers. Certain EPiServer services (scheduled jobs, workflows operate on a background thread).
  4. The EPiServer UI does not always tell you the full story….
  5. Happy workflowing!

Written by mark

June 1st, 2010 at 3:48 pm