Saturday, March 31, 2012

SPItemEventReceiver.ItemUpdated Method Doesn’t Fire When a Workflow Status Changes

The Backstory

I recently worked on a project where our users wanted to have a number of different approval workflows (about 15) on a single list. When they added a new list item, they would manually select an appropriate workflow depending on the type of list item they had added. Each list item would only have a single workflow running on it at any given time.

However, every time a new workflow is started on a given list, a new column is added to the list that shows the workflow status. After starting several of these workflows, not only do things get ugly by having too many columns, forcing the user to horizontally scroll, but SharePoint lists only allow 8 lookup columns on a list by default. This means that after too many of these workflows are started, they get the following error: This view cannot be displayed because the number of lookup and workflow status columns it contains exceeds the threshold (8) enforced by the administrator.

Of course, this setting can be changed (http://blogs.msdn.com/b/dinaayoub/archive/2010/04/22/sharepoint-2010-how-to-change-the-list-view-threshold.aspx), but changing this value beyond 8 results in a performance hit and not only that, you’re still left with an ugly looking list with too many columns, forcing users to scroll horizontally.

The ideal solution would be to create a single, custom SharePoint Designer workflow which accepts user input to decide where to route the approvals. Our users didn’t like that idea. They wanted separate, out-of-the-box workflows.

Since we would only be running a single workflow on any given list item, I came up with the idea to create a hyperlink column on the list called “Workflow Status”, and have it show the status of the most recently started workflow. This way, rather than having 15 different columns, each showing a different workflow’s status, we could have a single column. Clicking on the status link would show the workflow information and history.

Since we weren’t using SharePoint Designer workflows, we needed a different way to have the workflow update my custom column with the workflow status. I decided to create an event receiver which would be triggered when a workflow was started or its status changed. Below is the code that I wrote:

using System;

using System.Security.Permissions;

using System.Xml.Linq;

using Microsoft.SharePoint;

using Microsoft.SharePoint.Security;

using Microsoft.SharePoint.Utilities;

using Microsoft.SharePoint.Workflow;

 

namespace ListEventReciever.EventReceiver1

{

    public class EventReceiver1 : SPItemEventReceiver

    {

        public override void ItemUpdating(SPItemEventProperties properties)

        {

            updateWFStatus(properties, true);

            base.ItemUpdating(properties);

        }

 

        public override void ItemUpdated(SPItemEventProperties properties)

        {

            updateWFStatus(properties, false);

            base.ItemUpdated(properties);

        }

 

        private void updateWFStatus(SPItemEventProperties properties, bool ing)

        {

            try

            {

                SPListItem currentItem = properties.ListItem;

                string url = string.Empty;

                string wfState = string.Empty;

                SPWorkflowCollection wfc = currentItem.Workflows;

                SPWorkflow workflow = null;

 

                 //Although only one workflow should be running on this list item,

                 //we should check. If there is more than one, only grab the most

                 //recent one.

                foreach (SPWorkflow wf in wfc)

                {

                    if (workflow == null)

                        workflow = wf;

                    else

                    {

                        if (wf.Created > workflow.Created)

                            workflow = wf;

                    }

                }

                 //The workflow xml is going to let us tap into the status of the WF
                 //and the URL that will allow us to click on the status hyperlink.

                XDocument xdoc = XDocument.Parse(workflow.Xml);

                wfState = GetWorkflowStatus(xdoc);

                url = workflow.StatusUrl;

 

                this.EventFiringEnabled = false;

                if (ing)

                {

                    properties.AfterProperties["Workflow Status"] = properties.Web.Url + "/" + url + ", " + wfState;

                }

                else

                {

                    currentItem["Workflow Status"] = properties.Web.Url + "/" + url + ", " + wfState;

                    currentItem.Update();