Loading...
Area: Episerver Community API
Applies to versions: 1 and higher

Moderating actions and resources

Recommendations [hide]

Once 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. This topic explains how to moderate actions and resources within the Episerver Community API.

Representing moderation state

In the Episerver 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 in time.
  • 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. For more information, see References.

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.

Managing 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/2016
 2  A  Pending   Resource B  3/2/2016
 3  A  In Review   Resource A  3/3/2016
 4  A  Published   Resource A  3/4/2016
 5  A  In Review   Resource B  3/5/2016
 6  A  Pending   Resource C  3/6/2016

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 record its new 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, therefore, its current workflow state. The table also provides a history. For example, when retrieving WorkflowItems for Resource A, you can follow its history from "Pending" > "In review" > "Published."

Managing moderation state

In the Episerver Community API, WorkflowItems are managed through a service that implement the IWorkflowItemService interface. The workflow item service provides the ability to persist and retrieve workflows that you define. If your application uses asynchronous programming to improve the overall responsiveness of your application, this service also exposes Async versions of these APIs. The sections below explain, both, the synchronous and asynchronous APIs of the workflow item service.

For more information on the Episerver Community Async API see Async API.

This service provides the ability to persist, retrieve, and remove WorkflowItems that you define.

Accessing an IWorkflowItemService

If the Moderation feature is installed to an Episerver CMS site via the site integration package, 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 to a non-Episerver 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();

Adding 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 new state of the target under moderation.
  • An instance of the TransitionSessionToken class, which indicates that exclusive access to transition the target has been successfully obtained. (For information on how to obtain a TransitionSessionToken, see Transition Sessions.)

The method returns a new instance of WorkflowItem, which has been 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 the previous add workflow item example, the request to add a workflow item is invoked synchronously. An example of adding a workflow item asynchronously using the asynchronous overloads of the BeginTransitionSession and Add methods is also described in the Transition Sessions section.

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 not valid 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 new state of the target under moderation.

The method returns a new 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.

Retrieving 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.

In the previous example, the request to retrieve a workflow item is invoked 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 WorkflowItems, 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 wish 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 all 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 WorkflowItems, 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 that 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.

Removing 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 all 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 of more workflow items is invoked synchronously. Asynchronous methods are available for all the Remove overloads described above. An example of asynchronously removing all 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;
  }

Extending 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 Episerver Community API features, you can extend a WorkflowItem with data of your design by creating a Composite. (For an introduction to Composites, see Composites.)

Adding 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 new state of the target under moderation.
  • An instance of TExtension, which describes custom data with which the WorkflowItem will be composed.
  • An instance of the TransitionSessionToken class, which indicates that exclusive access to transition the target was successfully obtained. (For information on how to obtain a TransitionSessionToken, see Transition Sessions.)

The method returns a new instance of Composite<WorkflowItem,TExtension>, which has been 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);
      }
  }

In the above example, the request to add a workflow tem is invoked 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 new state of the target under moderation.
  • An instance of TExtension, which describes custom data with which the WorkflowItem is composed.

The method returns a new 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 all overloads of the Add method to allow for adding workflow items asynchronously.

Retrieving a composite WorkflowItem

You can retrieve a specific instance of a Composite WorkflowItem, which was previously added through the platform, via 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.

In the previous example, the request to retrieve a workflow item is invoked 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 further refine the result set by values represented within your extension data. (For more information on this type of filter, see Composite Criteria and Filtering Composites.)

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);

In the previous example, 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<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:

  • Prevents you from permanently committing unapproved versions of resources, which might otherwise lead to complicated data management scenarios.
  • Provides a traceable and potentially replay-able 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.
Do you find this information helpful? Please log in to provide feedback.

Last updated: Nov 01, 2016

Recommendations [hide]