November Happy Hour will be moved to Thursday December 5th.
November Happy Hour will be moved to Thursday December 5th.
This document describes the EPiServer CMS implementation of Windows Workflow Foundation and attempts to explain how you as a developer can take advantage of the wide variety of possibilities that workflows provide.
The configuration of the workflow system is contained in the web.config file and is configured by directly editing the file.
Sample from EPiServerSamples web.config file showing the workflowRuntime section.
<workflowRuntime>
<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.SharedConnectionWorkflowCommitWorkBatchService,
System.Workflow.Runtime,
Version=3.0.00000.0,
Culture=neutral,
PublicKeyToken=31bf3856ad364e35" />
<add type="System.Workflow.Runtime.Hosting.SqlWorkflowPersistenceService,
System.Workflow.Runtime,
Version=3.0.0.0,
Culture=neutral,
PublicKeyToken=31bf3856ad364e35"
UnloadOnIdle="true" />
</Services>
</workflowRuntime>
Sample from EPiServerSamples web.config file showing the workflowSettings section.
<workflowSettings
workflowDelayedLoading="false">
<workflowHost
type="EPiServer.WorkflowFoundation.AspNetWorkflowManager,
EPiServer.WorkflowFoundation" />
<definitions>
<!-- definition:
Workflow definitions that should be predefined, that is if no definition with specified type exists it will be created-->
<definition
name="Sequential Approval"
description="A sequential approval workflow for pages"
type="EPiServer.WorkflowFoundation.Workflows.SequentialApproval,
EPiServer.WorkflowFoundation"/>
<definition
name="Parallel Approval"
description="A paralell approval workflow for pages"
type="EPiServer.WorkflowFoundation.Workflows.ParallelApproval,
EPiServer.WorkflowFoundation"/>
<definition
name="Request for feedback"
description="Assigns request for feedback tasks to users/roles"
type="EPiServer.WorkflowFoundation.Workflows.RequestForFeedback,
EPiServer.WorkflowFoundation"/>
<definition
name="Ready for translation"
description="Assigns translation tasks to users/roles"
type="EPiServer.WorkflowFoundation.Workflows.ReadyForTranslation,
EPiServer.WorkflowFoundation"/>
</definitions>
<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"/>
</externalServices>
<references>
<!-- reference:
References for xoml based workflows, used at compiling of xoml based workflows-->
<!-- reference path="C:\Inetpub\wwwroot\mysite\bin\customdependency.dll" /-->
</references>
</workflowSettings>
Workflow development is typically done using Visual Studio with Windows Workflow Foundation as your tool.
In EPiServer.WorkflowFoundation there are a number of EPiServer related activities defined. To use them within Visual Studio, select the menu option Tools/Choose Toolbox Items. On the .NET Framework Components tab, browse to locate the EPiServer.WorkflowFoundation.dll and add it to your Visual Studio toolbar.
Event ActivitiesWhen a workflow instance enters an event activity (that is any activity derived from HandleExternalEventActivity) the workflow instance will become idle, persisted and finally unloaded from memory. The workflow host will keep track of which workflow instances are waiting for events. When the event occurs, the host will then load the workflow instance from persistence storage and invoke the event to the workflow instance.
These activities relate to events from DataFactory. The argument to page related event activities is EPiServer.WorkflowFoundation.Activities.WorkflowPageEventArgs which contains the PageReference to the page. It also contains a CacheKey which can be used to get the original PageEventArgs from DataFactory.
For a list of page related event activities, see the EPiServer.WorkflowFoundation.Activities namespace.
File System Related Event ActivitiesThese activities relate to events from the EPiServer file system. The argument to file system related event activities is WorkflowFileSystemEventArgs which for example contains the virtual path to the file or the directory.
For a complete list of file related event activities, see the EPiServer.WorkflowFoundation.Activities namespace.
Task Related ActivitiesThe following task related activities can be found in the EPiServer.WorkflowFoundation.Activities namespace.
Apart from the built in activities it is also possible to develop your own custom activities and use them within the EPiServer workflow system. Activities that are used to send data from the host (residing outside a running workflow instance) to running instances or from an instance to the outside are primarily developed using an interface decorated with the ExternalDataExchange attribute.
A suggested way to create your own custom activities that pass data to/from workflow instances is to begin with creating an interface with the attribute ExternalDataExchange where events (event argument should be inherited from ExternalDataEventArgs) are defined for each event activity (which means that data is being sent from a host to a workflow instance). A method needs to be defined for each method that will be an Invoke based activity (which means that data is sent from a workflow instance to a host). After creating your interface, the tool wca.exe can be used to generate activities from the interface definition (visit Windows Workflow Foundation Tools at MSDN.com).
You handle these custom activities on the host side by implementing a class that implements the custom interface specified above and register the class with the ExternalDataExchange service. This is done by deploying the assembly to the "~bin" directory of the site and then adding the class to the externalServices section in the web.config file.
To associate a task with a custom activity the property AssociatedActivity on the CreateTask activity can be used. In this case, the custom activity should have an ActivityPlugIn attribute, specifying a url to a user control (the usercontrol must be deployed to specified url before it can be loaded).
If you have custom event activities that you want to use outside tasks and you wish to be able to keep track over which instances that's currently waiting for the custom event you can use your custom event activity as template parameter to EPiServer.WorkflowFoundation.Activities.CompositeCustomBase<T>. Then the EPiServer.WorkflowFoundation.Services.TrackingService will keep track over which instances that is currently registered with the event meaning you can query for waiting instances like EventTrackingService.GetInstancesForEvent("<InterfaceName>.<EventName>").
It is possible to define properties on a workflow that can be used to set initial values on the workflow instance when it is created (or to get output data from a workflow instance when it is completed). The property should be defined on the workflow class and have publicly declared get/set accessors. The property type should be serializable and registered with Object Store.
The values passed to workflow instances as start arguments can be set through the API either by manually starting an instance through the method StartInstance on the IInstanceHandler interface or by an event delegate to event InstanceStarting on IInstanceHandler. Start parameters can also be set through a UI, which is described in section below.
The values passed in the StartParameters dictionary must match the properties on the workflow class both regarding name and type.
A workflow might need input from a user, for example, when something of the following occurs:
If you wish to enable users to interact with the workflow instance you can do so by associating a workflow and/or an event based activity with a user control. See the following topics below: Configure Start Parameters and Interact With Event Activities for more information on how this is done.
It is possible to associate a user control with a workflow class so that a user can assign values that will be passed to the workflow instance when it is created. These values can be assigned either at definition time (at the workflow configuration page in admin mode) or when the instance is about to be created from an action window. The user control is associated with the workflow class by using a WorkflowPlugIn attribute where the path to the user control is specified (before usage the user control has to be deployed to the specified path).
Example on associating a user control with a workflow.
[WorkflowPlugIn(Area = EPiServer.PlugIn.PlugInArea.None,
Url = "~/UI/edit/ApprovalStartParams.ascx")]
public sealed partial class SequentialApproval : StateMachineWorkflowActivity
The specified control should implement the interface EPiServer.WorkflowFoundation.UI.IWorkflowStartParameterHandler.
The interface has two methods LoadStartParameters and SaveStartParameters which are defined as:
void LoadStartParameters( Guid definitionId,
PageReference pageLink,
bool definitionMode,
IDictionary<string, object> parameters);
IDictionary<string, object> SaveStartParameters();
LoadStartParameters will be called when the control is loaded, initializing the UI with the previously saved values. The following parameters are used:
Parameter name | Parameter definition |
---|---|
definitionId | Specifies which definition the start parameters are set for. |
pageLink | Specifies which page the instance is associated with (if any). |
definitionMode | Specifies whether the control is loaded in configuration (admin mode) mode (true) or if it is loaded in action window (false). |
EPiServer.WorkflowFoundation.UI.IWorkflowStartParameterHandler.SaveStartParameters SaveStartParameters is called when the user calls either Save (in admin mode) or Start (action window). Before this function is called another call is made to Page.Validate System.Web.UI.Page.Validate meaning any validator on the user control will get a chance to validate user input. The items in the returned dictionary (types has to be serializable and registered with Object Store) will at start time be validated against workflow class meaning only entries that match both regarding name and type of a public get/set property on the workflow class will be passed to the workflow instance.
If a workflow instance is set to be created automatically from a page related event, information of the event will be sent to the workflow instance if the workflow class has a PageArgs property of the workflowPageArgs type as specified below:
public static DependencyProperty PageArgsProperty =
DependencyProperty.Register("PageArgs",
typeof(WorkflowEventArgs),
typeof(SequentialApproval));
public workflowPageEventArgs PageArgs
{
get { return (WorkflowPageEventArgs)this.GetValue(SequentialApproval.PageArgsProperty); }
set { this.SetValue(SequentialApproval.PageArgsProperty, value); }
}
If a workflow instance is set to be created automatically from an EPiServer file system related event, information of the event will be sent to the workflow instance if the workflow class has a FileSystemArgs property of the workflowFileArgs type as specified below:
private WorkflowFileSystemEventArgs _fileSystemArgs;
public WorkflowFileSystemEventArgs FileSystemArgs
{
get { return _fileSystemArgs; }
set { _fileSystemArgs = value;}
}
If a workflow instance is set to be created automatically when another workflow is completed, values from all public properties on the completed workflow instance will be passed to the created instance.
It is possible to interact with a custom event based activity by associating a UI through use of the attribute ActivityPlugIn as specified below:
[ActivityPlugIn(Area = EPiServer.PlugIn.PlugInArea.None,
Url = "~/UI/edit/ApprovalEventControl.ascx")]
[ToolboxItemAttribute(typeof(ActivityToolboxItem))]
public partial class ApprovalEvent : HandleExternalEventActivity
{
...
...
}
When using such an activity in a workflow you should first create a task in EPiServer using the CreateTask EPiServer.WorkflowFoundation.Activities.CreateTask activity. On the CreateTask activity you can set the AssociatedActivity property to the event activity. When the person (or group) who is assigned the task clicks on the task, the specified user control will be loaded and the user can then submit values to the custom event activity in the workflow instance (if a task has no associated activity specified then all event activities currently waiting on the workflow instance will be searched for an ActivityPlugIn attribute).
The user control should implement the interface EPiServer.WorkflowFoundation.UI.IWorkflowTaskControl, which has the methods ContextData, InvokeEvent, PageHasChanged and the property InvokeButtonText. ContextData is specified as below:
void ContextData(
Guid workflowInstanceId,
int taskId,
string eventQualifiedName);
ContextData is called when the control is loaded, it is used to set the context of the invoked event, containing the following information:
The qualified name of the event based activity that specified the user control (given in attribute above) is eventQualifiedName. Which event a workflow instance is currently waiting for is examined by looking in the EventQueue for the workflow instance. It is possible to point out which activity is related to which task on the property AssociatedActivity on CreateTaskActivity (normally this will not be needed, but if an instance is waiting for several events at the same time this can be used to distinguish which event the task is related to).
EPiServer.WorkflowFoundation.UI.IWorkflowTaskControl.InvokeEvent InvokeEvent is called when the user clicks the send button. Before the InvokeEvent is called, a call is made to Page.Validate() which means any specified validator on the user control will be called to ensure that user input can be validated. If the page in edit mode has been changed since ContextData is called method PageHasChanged will also be called before InvokeEvent is called. In the implementation of InvokeEvent the associated service registered with runtime should be fetched and then the corresponding event should be invoked on the service.
Example implementation of InvokeEvent.
public bool InvokeEvent(Guid workflowInstanceId, int taskId, string qualifiedName)
{
ApprovalService service = (ApprovalService)WorkflowSystem.WorkflowManager.GetService_
(typeof(ApprovalService));
service.InvokeApprovalEvent(new ApprovalEventArgs(Approved.Checked,
currentUser,
Message.Text,
workflowInstanceId));
return true;
}
The return value of the method indicates whether the task should be deleted or not.
InvokeButtonText states which text should be displayed on the Send button. If string.Empty is returned, the default value “Send” (language dependant) will be used.
All data that is sent as arguments to activities must be serializable. The cache service is a convenient way to share non-serialized data from outside into workflow instances. You can send custom data to the cache service as described below.
CacheService cacheService = (CacheService)WorkflowSystem.WorkflowManager.GetService_
(typeof(CacheService));
string cacheKey = cacheService.AddItemToCache(cacheItem);
Then the cache key can be sent to a workflow instance as part of a custom event argument. The cache service can be achieved on the workflow instance by overriding Initialize on the workflow class as described below.
CacheService _cacheService;
protected override void Initialize(IServiceProvider provider)
{
base.Initialize(provider);
_cacheService = (CacheService)provider.GetService(typeof(CacheService));
}
Before instances can be created, the workflow has to be defined with EPiServer CMS. This is done in admin mode under workflows. Part of the definition is a class name (fully qualified) and an assembly name. Before the definition is created the assembly should have been deployed to the sites bin directory. The UI will only support precompiled workflows although the API also offers a possibility to define XOML based workflows that will be compiled at registration time. It is possible to define if instances of the workflow should be created automatically and if so, which EPiServer CMS related event should trigger this behavior. It is also possible to set access rights on definitions.
The main entry point when interacting with the workflow system in EPiServer CMS is through the class EPiServer.WorkflowFoundation.WorkflowSystem which exposes the central components:
Instances of WorkflowDefinition and WorkflowDefinitionInstance are stored in EPiServer object store. This implies that objects that are stored in WorkflowDefinition.StartParameters or in WorkflowDefinitionInstance.UserData dictionaries must have schemas registered with object store before they can be used.
The registration with object store must be done before any saved definition or instance is loaded or saved. A convenient way to register custom types used with workflows is to register them in the constructor of a class that is registered as a external service, in that case you can be certain the registration is done before any attempt to load or save an instance or definition is performed. The following is an simple service that registers a custom type with object store:
using EPiServer.BaseLibrary;
using EPiServer.Implementation;
public class CustomService
{
public CustomService()
{
if (Context.Repository.SchemaForType(typeof(CustomType)) == null)
{
Type type = typeof(CustomType);
Serialization.TypeSchemaBuilder.RegisterSchemaAndType(type.ToString(), type);
}
}
}
The service should then be registered with runtime as a external service as described in configuration section.
Custom services can be registered with the runtime, this can preferably be achieved by registering the service on the configuration element externalServices in web.config. The custom service could for example be a service to be used with communication with custom event activities. Another usage of services is to register a service to handle events from the workflow system, for example, to listen to the WorkflowCompleted or WorkflowTerminated events of a particular workflow type and then do some cleanup. Yet another usage is to use the constructor of the service to register custom types used in workflows with object store. Since the services will be instantiated at runtime startup this is a convenient way to setup event listening (compared to writing an HttpModule).
The requirement for a custom service is that they have a public default constructor (the event listening can be setup in this constructor). Services that are used to communicate with custom event activities must implement an interface with the attribute ExternalDataExchange.
Startparameters that are set by UI (that is through usercontrol specified by WorkflowPluginAttribute) can be validated by ordinary validator controls on the usercontrol. A way to validate all startparamters (that is both those set by UI or by API passed, for example, as parameter to StartInstance method) is to add en eventlistner to IInstanceHandler.InstanceStarting (which can be done, for example, in a service as described in above section). In this handler you could then look through the start parameters and validate them. If the parameters are not valid you can on the event argument passed with InstanceStarting set the property Cancel to true and set a description in CancelReason property. Then the instance will not be started and the caller to StartInstance will get an exception of type WorkflowInstanceException with the value passed as CancelReason.
Last updated: Mar 25, 2013