Thursday, August 13, 2015

Simplest way to implement a state machine approach for SharePoint list items

Introduction

As you can read in WikipediaA finite-state machine (FSM) or finite-state automaton (plural: automata), or simply a state machine, is a mathematical model of computation used to design both computer programs and sequential logic circuits. It is conceived as an abstract machine that can be in one of a finite number of states. The machine is in only one state at a time; the state it is in at any given time is called the current state. It can change from one state to another when initiated by a triggering event or condition; this is called a transition. A particular FSM is defined by a list of its states, and the triggering condition for each transition.

SharePoint developers frequently face to state machine problems and the typical solution includes a workflow implementation. Actually, SharePoint includes default workflows for common scenarios, for instance Approval (route a document or item for approval or rejection), Collect Feedback (route a document or item for feedback), Collect Signatures (route a document, workbook, or form for digital signatures), Three-State (track an issue, project, or task through three states or phases) and Publishing Approval (automate content routing for review and approval) workflows.

But sometimes such workflows doesn’t fit exactly to your scenario or worse, the workflow usage is just an overkill.

Next, I’ll introduce you a clean state machine (also personal) approach to handling state transitions of SharePoint list items without workflows.

Event receivers as workflows alternative

As you should suppose the only way implement this is by using event receivers (a.k.a. event handlers) instead workflows. But how make a clean solution event handler based to implement a state machine. 

Allow me show you how the final code looks like for a customized publication process based on this approach:

[StateMachine("State", typeof(PublicationRequestStateMachineValidator))]
public sealed class PublicationRequestStateMachineItemEventReceiver : StateMachineItemEventReceiverBase
{
 [State("Approved")]
 private void OnApproved(SPItemEventProperties properties)
 {
  /*...*/
 }

 [State("Rejected")]
 private void OnRejected(SPItemEventProperties properties)
 {
  /*...*/ 
 }

 [State("ReadyToBePublished")]
 private void OnReadyToBePublished(SPItemEventProperties properties)
 {
  /*...*/
 }

 [State("ReadyToBeUnpublished")]
 private void OnReadyToBeUnpublished(SPItemEventProperties properties)
 {
  /*...*/ 
 }
}

Notice the introduction of a few new classes:
  • StateMachineAttribute: To indicate the column to be monitored and its validator class. The example above indicates that the column name is "State" and the validator class  is typeof(PublicationRequestStateMachineValidator). 
  • StateAttribute: To indicate the event method that will be called when the state change to the specified value. For instance, the usage of [State("Approved")] means that the method OnApproved will be called when the item change to  the "Approved" state.
  • StateMachineItemEventReceiverBase: Implements the base behavior of the state machine (invokes the validation to avoid not allowed transitions also call the event methods).
To make this work properly you must register you state machine item event receiver on the list that you want to monitor for state change. This must be done via RegisterEventReceiverIfRequired extension method for SPList. This method not only registers the event receiver the list, it also adds a custom column to the list to implement the change detection approach whether the event receiver inherits from StateMachineItemEventReceiverBase. 

This could be done with the follow code in a feature activation for instance.

publicationRequestList.RegisterEventReceiverIfRequired(SPEventReceiverType.ItemUpdating, typeof(PublicationRequestStateMachineItemEventReceiver).Assembly.FullName, typeof(PublicationRequestStateMachineItemEventReceiver).FullName);
publicationRequestList.RegisterEventReceiverIfRequired(SPEventReceiverType.ItemUpdated, typeof(PublicationRequestStateMachineItemEventReceiver).Assembly.FullName, typeof(PublicationRequestStateMachineItemEventReceiver).FullName);

How validate state transitions?

Support transition validation is also a cool feature of this library. In order to validate the transition, you can inherit from StateMachineValidator class and type your own. Our custom publication request example the state machine validator looks like this:


public sealed class PublicationRequestStateMachineValidator : StateMachineValidator<string>
{
        public PublicationRequestStateMachineValidator()
        {
            this.AddAllowedTransition("WaitingForApproval", "Approved");
            this.AddAllowedTransition("WaitingForApproval", "Rejected");
            this.AddAllowedTransition("Approved", "ReadyToBePublished");
            this.AddAllowedTransition("ReadyToBePublished", "Published");
            this.AddAllowedTransition("Published", "ReadyToBeUnpublished");
            this.AddAllowedTransition("ReadyToBeUnpublished", "Unpublished");
        }
}

Now if someone tries to change the state from WaitingForApproval to Published - for instance - the change will be reverted automatically.

This is also useful to enable or disable some actions. Here are couple of pictures that depicts the enable state of the ribbon button depends on an allowed transition validation implemented in javascript in combination with a REST service.

Example A: Enabled because transitions are allowed
Example B: Disabled because transitions are not allowed

Conclusions

This post is an introduction to the new StateMachine.SharePoint library. This library allows you simplify the implementation of a state machine based approach to monitor and validate the states of SharePoint list items.

For now, you can build this library from its sources and deploy directly into your SharePoint farm or wait for the "top secret" weapon and forthcoming project PackageManager.SharePoint  ;)

X-ray StoneAssemblies.MassAuth with NDepend

Introduction A long time ago, I wrote this post  Why should you start using NDepend?  which I consider as the best post I have ever...