Tuesday, December 17, 2013

How to cancel a workflow from custom workflow activity

When things go wrong, quite often we don't want to let SharePoint workflow engine to handle the exception. Instead of the "Error Occurred" result, it's better to cancel the workflow instance so site owners don't need to get involved to cancel it manually.

But how? "return ActivityExecutionStatus.Canceling;" doesn't work, because workflow engine knows that something is wrong, and it still end the workflow instance with "Error Occurred" status.

Actually, we cannot cancel a workflow instance from custom workflow activity, and we should not do that.

This is easy to understand. Application should not "Log Off" or "Restart" Windows OS directly. If it's necessary, the application should "tell" the users to do that. The relationship between "Workflow Activity" and "Workflow Instance" is just like the one between "Application" and "Windows".

Once this is clear, it's easy to implement the feature properly.

We should add a column such as "ActivityStatus" to the target list. After executing a workflow activity, the workflow can check this field. If the value is "Cancelled", then the workflow can cancel the workflow instance. This normally can be done through SharePoint Designer.

        //this is done through RESTful API
        private void UpdateResult(WorkflowContext __Context, string strActivityStatus, string strComments)
        {
            try
            {
                SharePointAdministrationDataContext ctx = new SharePointAdministrationDataContext(new Uri(string.Format(@"{0}/_vti_bin/listdata.svc/", __Context.CurrentWebUrl)));
                ctx.MergeOption = MergeOption.OverwriteChanges;
                ctx.Credentials = CredentialCache.DefaultCredentials;
                ModulesItem oItem = (ModulesItem)ctx.Modules.Where(i => i.Id == __Context.ItemId).FirstOrDefault();
                oItem.Comments = strComments;
                oItem.ActivityStatus = strActivityStatus;

                ctx.UpdateObject(oItem);
                ctx.SaveChanges();

            }
            catch (Exception ex)
            {
                WriteInfoToHistoryLog(__Context.Web, __Context.WorkflowInstanceId, @"ex.Message=" + ex.Message);
                WriteInfoToHistoryLog(__Context.Web, __Context.WorkflowInstanceId, @"ex.StackTrace=" + ex.StackTrace);
            }
        }

        public static void WriteInfoToHistoryLog(SPWeb web, Guid workflow, string description)
        {
            TimeSpan ts = new TimeSpan();
            SPSecurity.RunWithElevatedPrivileges(delegate()
            {
                SPWorkflow.CreateHistoryEvent(web, workflow, 0, web.CurrentUser, ts, "information", description, string.Empty);
            });
        }

Now we can catch and process the exception in workflow activity properly.

            catch (Exception ex)
            {
                WriteInfoToHistoryLog(__Context.Web, __Context.WorkflowInstanceId, @"ex.Message=" + ex.Message);
                WriteInfoToHistoryLog(__Context.Web, __Context.WorkflowInstanceId, @"ex.StackTrace=" + ex.StackTrace);
                UpdateResult(__Context, _ActivityStatus_Canceled, @"ex.Message=" + ex.Message);
                return ActivityExecutionStatus.Canceling;
            }

No comments:

Post a Comment