Moderate actions and resources
Explains how to moderate actions and resources within the Optimizely Community API.
When a Workflow is defined, you can use it to moderate your application's requests, actions, and resources. Moderating these entities amounts to maintaining a record of the state of each entity within the Workflow.
Represent moderation state
In the Optimizely Community API, a record of an entity's state within a Workflow is represented by the WorkflowItem class.
An instance of WorkflowItem captures several important data points about the state of a Workflow entity.
- The
Workflowproperty identifies the workflow with which the entity is associated. - The
Stateproperty identifies the entity's state within the Workflow at this point. - The
Targetuniquely identifies the entity under moderation. This is a custom identifier defined within your application. TheTargetis a key that associates a series ofWorkflowItemsto a particular entity under moderation. See References in Discover the platform.
A series of WorkflowItems with the same Target value represents the moderation history for the entity identified by that reference. The most recent WorkflowItem in the series represents the current state of the entity.
Manage entities within a workflow
The table below illustrates the moderation history for three example resources within a Workflow.
| ID | Workflow | State | Target | Date |
|---|---|---|---|---|
| 1 | A | Pending | Resource A | 3/1/2022 |
| 2 | A | Pending | Resource B | 3/2/2022 |
| 3 | A | In Review | Resource A | 3/3/2022 |
| 4 | A | Published | Resource A | 3/4/2022 |
| 5 | A | In Review | Resource B | 3/5/2022 |
| 6 | A | Pending | Resource C | 3/6/2022 |
Each WorkflowItem represents a transition within a workflow (in this example, workflow A). Each time an entity is moderated within a Workflow, a WorkflowItem is committed to recording its state. Each WorkflowItem represents an individual record in the entity's moderation history.
Explanation of lines
- Resource A enters moderation. The application adds a
WorkflowItemwith a "Pending" state (the workflow's initial state). - Resource B enters moderation. The application adds a
WorkflowItemwith a "Pending" state. - Resource A transitions to an "In review" state. The application adds a
WorkflowItemwith an "In Review" state. - Resource A transitions to a "Published" state. The application adds a
WorkflowItemwith a "Published" state. - Resource B transitions to an "In review" state. The application adds a
WorkflowItemwith an "In Review" state. - Resource C enters moderation. A
WorkflowItemis added with a "Pending" state. The application adds aWorkflowItemwith a "Pending" state.
So, the green background indicates each entity's most recent transition and current workflow state. The table also provides a history. For example, when retrieving WorkflowItem for Resource A, you can follow its history from "Pending"> "In review"> "Published."
Manage moderation state
In the Optimizely Community API, WorkflowItems are managed through a service that implements the IWorkflowItemService interface. The workflow item service lets you persist and retrieve workflows you define. If your application uses asynchronous programming to improve its overall responsiveness, this service also exposes Async versions of these APIs. The sections below explain the synchronous and asynchronous APIs of the workflow item service. See Async API in Discover the platform.
- Accessing an
IWorkflowItemService - Adding a
WorkflowItem - Retrieving a
WorkflowItem - Removing a
WorkflowItem
This service provides the ability to persist, retrieve, and remove WorkflowItems that you define.
Access an IWorkflowItemService
Suppose the Moderation feature is installed on an Optimizely CMS site through the site integration package. In that case, you can get an instance of this service from the inversion of control (IoC) container.
Example:
var workflowItemService = EPiServer.ServiceLocation.ServiceLocator.Current.GetInstance<IWorkflowItemService>();If the feature is installed on a non-Optimizely CMS site, you can get an instance of a service from the default factory class provided in the package.
Example:
var factory = new EPiServer.Social.Moderation.Factories.DefaultWorkflowItemServiceFactory();
var workflowItemService = factory.Create();Add a WorkflowItem
The addition of a WorkflowItem records the transition of an entity under moderation to a new state. To add a WorkflowItem, use the Add(WorkflowItem,TransitionSessionToken) method of IWorkflowItemService.
This method accepts:
- An instance of the
WorkflowItemclass, which describes the state of the target under moderation. - An instance of the
TransitionSessionTokenclass indicates that exclusive access to transition the target is obtained. For information on how to obtain aTransitionSessionToken, see Transition Sessions in Moderation workflows.
The method returns an instance of WorkflowItem, which is populated with any additional, system-generated data (for example, a unique ID).
The example below illustrates the addition of a WorkflowItem to enter a resource into moderation. A WorkflowItem is added for the resource with a Workflow's initial state.
IWorkflowService workflowService;
IWorkflowItemService workflowItemService;
Workflow workflow;
// ...
Reference targetReference = Reference.Create("resource://identifier/for/my/content");
TransitionSessionToken sessionToken = null;
try {
sessionToken = workflowService.BeginTransitionSession(workflow.Id, targetReference);
var workflowItem = new WorkflowItem(workflow.Id, workflow.InitialState, targetReference);
workflowItemService.Add(workflowItem, sessionToken);
} catch (TransitionSessionDeniedException) {
// The workflow has indicated that the intended action
// is not possible given the target's current state. Handle
// the exception to inform the user, etc.
} finally {
if (sessionToken != null) {
workflowService.EndTransitionSession(sessionToken);
}
}For a more comprehensive example of how transition sessions are leveraged, see Transition Sessions in Moderation workflows.
The request to add a workflow item is invoked synchronously in the previous example of adding a workflow item. An example of adding a workflow item asynchronously using the asynchronous overloads of the BeginTransitionSession and Add methods is also described in Transition Sessions in Moderation workflows.
The following exceptions may occur in the course of using this method:
- A
WorkflowDoesNotExistExceptionoccurs if the specifiedworkflowID is not found. - An
InvalidWorkflowStateExceptionoccurs if theworkflowitem being added has a state that does not exist in the associatedworkflow. - A
TransitionSessionDeniedExceptionoccurs if the specifiedTransitionSessionTokenis invalid, or a different client has already obtained exclusive access to the target of moderation.
NoteWhile overloads of the
Addmethod exist,Add(WorkflowItem,TransitionSessionToken)is the recommended approach for implementing the safe transition of a target into a new state. Using this method consistently when implementing moderation strategies helps ensure the integrity of your workflow.
To add a WorkflowItem, without regard for any active transition sessions, use the Add(WorkflowItem) method of IWorkflowItemService.
This method accepts an instance of the WorkflowItem class, which describes the state of the target under moderation.
The method returns an instance of WorkflowItem, which was populated with any additional, system-generated data (for example, a unique ID).
NoteInvoking this method commits a
WorkflowItemregardless of any active transition sessions. It bypasses the exclusivity granted to those clients, which have successfully obtainedTransitionSessionTokens, to add the item.
Retrieve a WorkflowItem
To retrieve a specific instance of WorkflowItem, which was previously added through the platform, use the Get(WorkflowItemId) method. This method accepts an instance of the WorkflowItemId class, which identifies the WorkflowItem to be retrieved. It returns the instance of the WorkflowItem class corresponding to that identifier.
IWorkflowItemService workflowItemService;
// ...
// Construct a WorkflowItemId corresponding to the desired WorkflowItem
WorkflowItemId id = WorkflowItemId.Create("...");
var item = workflowItemService.Get(id);If the requested WorkflowItem cannot be found, a WorkflowItemDoesNotExistException occurs.
The previous example invokes the request to retrieve a workflow item synchronously. An example of retrieving a workflow item asynchronously using the asynchronous overload with C#'s async and await keywords is described below.
private async Task<WorkflowItem> GetWorkflowItemAsync(IWorkflowItemService workflowItemService) {
// Construct a WorkflowItemId corresponding to the desired WorkflowItem
WorkflowItemId id = WorkflowItemId.Create("...");
var getWorkflowItemTask = workflowItemService.GetAsync(id);
//Do other application specific work in parallel while the task executes.
//....
//Wait until the task runs to completion.
var item = await getWorkflowItemTask;
return item;
}To retrieve a collection of WorkflowItem, which have previously been added through the platform, use the Get(Criteria<WorkflowItemFilter>) method. This method accepts an instance of Criteria<WorkflowItemFilter>, which contains the specifications necessary to retrieve the desired WorkflowItems.
The Filter property of the Criteria<WorkflowItemFilter> class accepts an instance of the WorkflowItemFilter class. This class exposes properties representing the specifications that let you refine the result set of WorkflowItems you want to retrieve. WorkflowItemFilter properties include:
Target– Assigning a value (Reference) to this property refines a result set toWorkflowItems with aTargetmatching that value. The result set represents the complete moderation history for the entity identified by the specified reference.State– Assigning a value (WorkflowState) to this property refines a result set toWorkflowItems with a state matching that value.Workflow– Assigning a value (WorkflowId) to this property refines a result set toWorkflowItems with aWorkflowmatching that value. The result set represents the complete moderation history for entities under moderation within the identified Workflow.ExcludeHistoricalItems– Assigning a value (Boolean) to this property indicates whether or not the result set should target onlyWorkflowItems representing the current state of their associated entity.
The specifications of the WorkflowItemFilter may be applied in conjunction with one another. Each specification, which is assigned a value in the filter, further refines the result set (for example, a logical AND). The example below demonstrates the retrieval of a result page of WorkflowItem, identifying entities currently in a "Pending" state.
IWorkflowItemService workflowItemService;
// ...
var criteria = new Criteria<WorkflowItemFilter>() {
Filter = new WorkflowItemFilter {
ExcludeHistoricalItems = true,
State = new WorkflowState("Pending")
}
};
var pageOfWorkflowItems = workflowItemService.Get(criteria);In the next example, a result page of WorkflowItems is retrieved represents the complete moderation history for the identified entity.
IWorkflowItemService workflowItemService;
// ...
// Construct a Reference identifying an entity under moderation
var target = Reference.Create("resource://identifier/for/my/content");
var criteria = new Criteria<WorkflowItemFilter>() {
Filter = new WorkflowItemFilter {
Target = target
}
};
var pageOfWorkflowItems = workflowItemService.Get(criteria);In the previous examples, the request to retrieve workflow items is invoked synchronously. An example of retrieving workflow items asynchronously using the asynchronous overload with C#'s async and await keywords is described below.
private async Task<ResultPage<WorkflowItem>> GetWorkflowItemsAsync(IWorkflowItemService workflowItemService) {
// ...
var criteria = new Criteria<WorkflowItemFilter>() {
Filter = new WorkflowItemFilter {
ExcludeHistoricalItems = true,
State = new WorkflowState("Pending")
}
};
var getWorkflowItemsTask = workflowItemService.GetAsync(criteria);
//Do other application specific work in parallel while the task executes.
//....
//Wait until the task runs to completion.
var pageOfWorkflowItems = await getWorkflowItemsTask;
return pageOfWorkflowItems;
}For details regarding the use of criteria, including information on paging and sorting, see Criteria
in Discover the platform.
Remove a WorkflowItem
To remove a specific instance of Workflow, which was previously added through the platform, use one of three methods:
-
Remove(WorkflowItemId)– This method accepts an instance of theWorkflowItemIdclass, which identifies the particularWorkflowItemto be removed. The result is the deletion of theWorkflowItemcorresponding to that ID.IWorkflowItemService workflowItemService; // ... // Construct a WorkflowItemId corresponding to the desired WorkflowItem WorkflowItemId id = WorkflowItemId.Create("...") workflowItemService.Remove(id); -
Remove(WorkflowId)– This method accepts an instance of theWorkflowIdclass, which identifies theWorkflowassociated with theWorkflowItemsto be removed. The result is the deletion ofWorkflowItemsassociated with the identified Workflow.IWorkflowItemService workflowItemService; Workflow workflow; // ... workflowItemService.Remove(workflow.Id); -
Remove(Reference)– This method accepts an instance of theReferenceclass, which identifies an entity under moderation (Target). The result is the deletion ofWorkflowItemscorresponding to the identified entity (that is, the entire moderation history for that entity).IWorkflowItemService workflowItemService; // ... // Construct a Reference identifying an entity under moderation var target = Reference.Create("resource://identifier/for/my/content"); workflowItemService.Remove(target);
NoteThe
Removemethods do not ensure that the removal of the targetedWorkflowItemsleaves an entity with a valid moderation history. For example, the removal of aWorkflowItemmay leave an entity with gaps in its moderation history. This gives a developer the freedom to implement features, which lets an administrator undo the transition of an entity under moderation. However, be cautious in applications where the integrity of an entity's moderation history is important.
In the previous examples, the request to remove one or more workflow items is invoked synchronously. Asynchronous methods are available for the Remove overloads described above. An example of asynchronously removing WorkflowItems associated with the identified Workflow using the asynchronous overload with C#'s async and await keywords is described below.
private async Task RemoveWorkflowItemsAsync(IWorkflowItemService workflowItemService) {
// Construct or retrieve an ID for an existing workflow.
Workflow workflow;
var removeWorkflowItemsTask = workflowItemService.RemoveAsync(workflow);
//Do other application specific work in parallel while the task executes.
//....
//Wait until the task runs to completion.
await removeWorkflowItemsTask;
}Extend WorkflowItems with composites
A developer can compose WorkflowItems with additional data to create rich and powerful moderation experiences. You may need to associate additional information with a WorkflowItem to support your application's use cases. The additional information might represent a request, an action, or a resource entering moderation. For example, it might represent a request for membership in an exclusive group or the content of a comment pending review before it is committed to the system.
Like other Optimizely Community API features, you can extend a WorkflowItem with data from your design by creating a composite. See Composites in Discover the platform.
Add a composite WorkflowItem
You can save a Composite WorkflowItem by using the Add<TExtension>(WorkflowItem,TExtension,TransitionSessionToken) method of IWorkflowItemService.
This method accepts:
- An instance of the
WorkflowItemclass, which describes the state of the target under moderation. - An instance of
TExtension, which describes custom data with which theWorkflowItemare composed. - An instance of the
TransitionSessionTokenclass indicates that exclusive access to transition the target was obtained. For information on how to obtain aTransitionSessionToken, see Transition Sessions in Moderation Workflows,
The method returns an instance of Composite\<WorkflowItem,TExtension>, which is populated with any additional, system-generated data (for example, a unique ID).
Consider the following class, which represents a sample of extension data.
public class MyWorkflowItemExtension {
// ...
}In the example below, a WorkflowItem is added with an instance of this extension class to form a composite WorkflowItem.
IWorkflowService workflowService;
IWorkflowItemService workflowItemService;
Workflow workflow;
// ...
Reference targetReference = Reference.Create("resource://identifier/for/my/content");
TransitionSessionToken sessionToken = null;
try {
sessionToken = workflowService.BeginTransitionSession(workflow.Id, targetReference);
var workflowItem = new WorkflowItem(workflow.Id, workflow.InitialState, targetReference);
var extension = new MyWorkflowItemExtension {
// ...
};
workflowItemService.Add(workflowItem, extension, sessionToken);
} catch (TransitionSessionDeniedException) {
// The workflow has indicated that the intended action
// is not possible given the target's current state. Handle
// the exception to inform the user, etc.
} finally {
if (sessionToken != null) {
workflowService.EndTransitionSession(sessionToken);
}
}The above example invokes the request to add a workflow item synchronously. An example of adding a workflow item asynchronously using the asynchronous overload with C#'s async and await keywords is described below.
private async Task<Composite<WorkflowItem, MyWorkflowItemExtension>> AddWorkflowItemAsync(IWorkflowItemService workflowItemService) {
Workflow workflow;
// ...
Reference targetReference = Reference.Create("resource://identifier/for/my/content");
TransitionSessionToken sessionToken = null;
try {
sessionToken = workflowService.BeginTransitionSession(workflow.Id, targetReference);
var workflowItem = new WorkflowItem(workflow.Id, workflow.InitialState, targetReference);
var extension = new MyWorkflowItemExtension {
// ...
};
var addWorkflowItemTask = workflowItemService.AddAsync(workflowItem, extension, sessionToken);
//Do other application specific work in parallel while the task executes.
//....
//Wait until the task runs to completion.
var item = await addWorkflowItemTask;
return item;
} catch (TransitionSessionDeniedException) {
// The workflow has indicated that the intended action
// is not possible given the target's current state. Handle
// the exception to inform the user, etc.
} finally {
if (sessionToken != null) {
workflowService.EndTransitionSession(sessionToken);
}
}
}
NoteWhile overloads of the Add method exist,
Add<TExtension>(WorkflowItem,TExtension,TransitionSessionToken)is the recommended approach for adding a compositeWorkflowItem. Using this method consistently when implementing moderation strategies helps to ensure the integrity of your workflow.
To add a composite WorkflowItem, without regard for any active transition sessions, use the Add<TExtension>(WorkflowItem,TExtension) method of IWorkflowItemService.
This method accepts:
- An instance of the
WorkflowItemclass, which describes the state of the target under moderation. - An instance of
TExtension, which describes custom data with which theWorkflowItemis composed.
The method returns an instance of Composite\<WorkflowItem,TExtension>, which was populated with any additional, system-generated data (for example, a unique ID).
NoteInvoking this method commits a
WorkflowItemregardless of any active transition sessions. It bypasses the exclusivity granted to those clients, which have successfully obtainedTransitionSessionTokens, to add the item.
Async versions exist for overloads of the Add method to allow for adding workflow items asynchronously.
Retrieve a composite WorkflowItem
You can retrieve a specific instance of a Composite WorkflowItem, which was previously added through the platform, with the Get<TExtension>(WorkflowItemId) method. This method accepts an instance of the WorkflowItemId class, which identifies the particular WorkflowItem to be retrieved. It returns an instance of the Composite\<WorkflowItem,TExtension> class corresponding to that identifier.
IWorkflowItemService workflowItemService;
//...
// Construct a WorkflowItemId corresponding to the desired WorkflowItem
var workflowItemId = WorkflowItemId.Create("...");
var compositeItem = workflowItemService.Get<MyWorkflowItemExtension>(workflowItemId);If a Composite WorkflowItem with the specified ID and extension type cannot be found, a WorkflowItemDoesNotExistException occurs.
The previous example invokes the request to retrieve a workflow item synchronously. An example of retrieving a workflow item asynchronously using the asynchronous overload with C#'s async and await keywords is described below.
private async Task<Composite<WorkflowItem, MyWorkflowItemExtension>> GetWorkflowItemAsync(IWorkflowItemService workflowItemService) {
//...
// Construct a WorkflowItemId corresponding to the desired WorkflowItem
var workflowItemId = WorkflowItemId.Create("...");
var getWorkflowItemTask = workflowItemService.GetAsync<MyWorkflowItemExtension>(workflowItemId);
//Do other application specific work in parallel while the task executes.
//....
//Wait until the task runs to completion.
var compositeItem = await getWorkflowItemTask;
return compositeItem;
}To retrieve a collection of Composite WorkflowItems, which have previously been added through the platform, use the Get(CompositeCriteria\<WorkflowItemFilter,TExtension>) method. This method accepts an instance of CompositeCriteria\<WorkflowItemFilter,TExtension>, which contains specifications necessary to retrieve the desired WorkflowItems.
The Filter property of the CompositeCriteria\<WorkflowItemFilter,TExtension> class accepts an instance of the WorkflowItemFilter class. This class contains specifications that let you refine the result set of WorkflowItems you want to retrieve.
The ExtensionFilter property of the CompositeCriteria\<WorkflowItemFilter,TExtension> class accepts a FilterExpression that lets you specify a Boolean expression to refine the result set further by values represented within your extension data. For information on this type of filter, see Composite Criteria and Filtering Composites in Discover the platform.
Consider the following class, which represents a sample of extension data that could capture the content of a pending comment:
public class PendingComment {
public string Contributor {
get;
set;
}
public string Body {
get;
set;
}
}In the example below, a page of WorkflowItems composed with PendingComment is retrieved for a particular contributor:
IWorkflowItemService workflowItemService;
// ...
var filterExpression = FilterExpressionBuilder<PendingComment> .Field(pc => pc.Contributor).EqualTo("user://identifier/for/my/user");
var criteria = new CompositeCriteria<WorkflowItemFilter,
PendingComment> {
ExtensionFilter = filterExpression
};
var pageOfCompositeItems = workflowItemService.Get(criteria);The previous example invokes the request to retrieve workflow items synchronously. An example of retrieving workflow items asynchronously using the asynchronous overload with C#'s async and await keywords is described below.
private async Task<ResultPage<Composite<WorkflowItem, PendingComment>>> GetWorkflowItemsAsync(IWorkflowItemService workflowItemService) {
// ...
var filterExpression = FilterExpressionBuilder<PendingComment> .Field(pc => pc.Contributor).EqualTo("user://identifier/for/my/user");
var criteria = new CompositeCriteria<WorkflowItemFilter,
PendingComment> {
ExtensionFilter = filterExpression
};
var getItemsTask = workflowItemService.GetAsync(criteria);
//Do other application specific work in parallel while the task executes.
//....
//Wait until the task runs to completion.
var pageOfCompositeItems = await getItemsTask;
return pageOfCompositeItems;
}Best practices for extending WorkflowItems
Think of the thing you are moderating as an action rather than a resource. For example, you moderate:
- The act of publishing a comment rather than the comment itself.
- A request to join a group rather than a user.
The outcome of an action is only relevant after it is approved through moderation. So, maintain a record of that action in your moderation system and only commit its outcome upon approval.
The Command pattern, or similar behavioral software design patterns, can provide a helpful template for encapsulating an action in this manner.
Supplement the action with the data necessary to commit its outcome to the appropriate repository. If that data is extensive, consider designing the data as a delta against a previous version.
Designing your moderation systems in such a manner:
- It prevents you from permanently committing unapproved versions of resources, which might otherwise lead to complicated data management scenarios.
- Provides a traceable and potentially replayable history of actions on entities under moderation.
- Promotes a maintainable implementation, where the interpretation and execution of a request are decoupled from the state management of your resources.
Updated about 2 months ago