HomeDev GuideAPI Reference
Dev GuideAPI ReferenceUser GuideGitHubNuGetDev CommunitySubmit a ticketLog In
GitHubNuGetDev CommunitySubmit a ticket

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. The Target is a key that associates a series of WorkflowItems 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

  1. Resource A enters moderation. The application adds a WorkflowItem with a "Pending" state (the workflow's initial state).
  2. Resource B enters moderation. The application adds a WorkflowItem with a "Pending" state.
  3. Resource A transitions to an "In review" state. The application adds a WorkflowItem with an "In Review" state.
  4. Resource A transitions to a "Published" state. The application adds a WorkflowItem with a "Published" state.
  5. Resource B transitions to an "In review" state. The application adds a WorkflowItem with an "In Review" state.
  6. Resource C enters moderation. A WorkflowItem is added with a "Pending" state. The application adds a WorkflowItem 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, 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 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 a TransitionSessionToken, 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 specified workflow ID is not found.
  • An InvalidWorkflowStateException occurs if the workflow item being added has a state that does not exist in the associated workflow.
  • A TransitionSessionDeniedException occurs if the specified TransitionSessionToken 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 obtained TransitionSessionTokens, 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 to WorkflowItems with a Target 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 to WorkflowItems with a state matching that value.
  • Workflow – Assigning a value (WorkflowId) to this property refines a result set to WorkflowItems with a Workflow 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 only WorkflowItems 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 the WorkflowItemId class, which identifies the particular WorkflowItem to be removed. The result is the deletion of the WorkflowItem 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 the WorkflowId class, which identifies the Workflow associated with the WorkflowItems to be removed. The result is the deletion of WorkflowItems associated with the identified Workflow.

    IWorkflowItemService workflowItemService;
    Workflow workflow;
    
    // ...
    workflowItemService.Remove(workflow.Id);
    
  • Remove(Reference) – This method accepts an instance of the Reference class, which identifies an entity under moderation (Target). The result is the deletion of WorkflowItems 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 targeted WorkflowItems leaves an entity with a valid moderation history. For example, the removal of a WorkflowItem 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 the WorkflowItem are composed.
  • An instance of the TransitionSessionToken class indicates that exclusive access to transition the target was obtained. For information on how to obtain a TransitionSessionToken, 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 composite WorkflowItem. 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 the WorkflowItem 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 obtained TransitionSessionTokens, 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.