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
Workflow
property identifies the workflow with which the entity is associated. - The
State
property identifies the entity's state within the Workflow at this point. - The
Target
uniquely identifies the entity under moderation. This is a custom identifier defined within your application. TheTarget
is a key that associates a series ofWorkflowItems
to 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
WorkflowItem
with a "Pending" state (the workflow's initial state). - Resource B enters moderation. The application adds a
WorkflowItem
with a "Pending" state. - Resource A transitions to an "In review" state. The application adds a
WorkflowItem
with an "In Review" state. - Resource A transitions to a "Published" state. The application adds a
WorkflowItem
with a "Published" state. - Resource B transitions to an "In review" state. The application adds a
WorkflowItem
with an "In Review" state. - Resource C enters moderation. A
WorkflowItem
is added with a "Pending" state. The application adds aWorkflowItem
with 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, WorkflowItem
s 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
WorkflowItem
class, which describes the state of the target under moderation. - An instance of the
TransitionSessionToken
class 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
WorkflowDoesNotExistException
occurs if the specifiedworkflow
ID is not found. - An
InvalidWorkflowStateException
occurs if theworkflow
item being added has a state that does not exist in the associatedworkflow
. - A
TransitionSessionDeniedException
occurs if the specifiedTransitionSessionToken
is invalid, or a different client has already obtained exclusive access to the target of moderation.
Note
While overloads of the
Add
method 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).
Note
Invoking this method commits a
WorkflowItem
regardless 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 toWorkflowItem
s with aTarget
matching 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 toWorkflowItem
s with a state matching that value.Workflow
– Assigning a value (WorkflowId
) to this property refines a result set toWorkflowItem
s with aWorkflow
matching 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 onlyWorkflowItem
s 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 theWorkflowItemId
class, which identifies the particularWorkflowItem
to be removed. The result is the deletion of theWorkflowItem
corresponding 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 theWorkflowId
class, which identifies theWorkflow
associated with theWorkflowItems
to be removed. The result is the deletion ofWorkflowItems
associated with the identified Workflow.IWorkflowItemService workflowItemService; Workflow workflow; // ... workflowItemService.Remove(workflow.Id);
-
Remove(Reference)
– This method accepts an instance of theReference
class, which identifies an entity under moderation (Target
). The result is the deletion ofWorkflowItems
corresponding 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);
Note
The
Remove
methods do not ensure that the removal of the targetedWorkflowItems
leaves an entity with a valid moderation history. For example, the removal of aWorkflowItem
may 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
WorkflowItem
class, which describes the state of the target under moderation. - An instance of
TExtension
, which describes custom data with which theWorkflowItem
are composed. - An instance of the
TransitionSessionToken
class 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);
}
}
}
Note
While 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
WorkflowItem
class, which describes the state of the target under moderation. - An instance of
TExtension
, which describes custom data with which theWorkflowItem
is composed.
The method returns an instance of Composite\<WorkflowItem,TExtension>
, which was populated with any additional, system-generated data (for example, a unique ID).
Note
Invoking this method commits a
WorkflowItem
regardless 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 7 months ago