none
Cancelling a specific workflow when list item is changed

    Question

  • I have created an email reminder system within a SharePoint list which sends out reminder emails when a project is due (email sent 5 days before, 3 days before and once when past due).  When a new item is created, the workflow pauses itself until 5 days before the End Date of the project and then sends the 1st email and then pauses itself until 3 days before the End Date and so on.

    My challenge is how to handle updates to the item when the workflow is already in progress (item changes do not affect the running workflow).  Essentially, I need to cancel the workflow when an item is changed and then kickoff the new workflow.  Pretty straightforward.

    I created an Event Receiver which fires on ItemUpdating to check to see if any workflows are running on the item and cancel them.  This works fine and cancels the running workflow when I update the item (woo!).  The new workflow automatically is kicking off as expected also.  The problem is that this approach cancels ALL running workflows on the list item.  I need to tweak my code to only target a specific workflow (Workflow A) and not cancel any other workflows which may be running on the item (Workflow B, C, etc...).  I also only want this Event Receiver to fire if the End Date on the item is changed (it should not fire if the End Date remains the same).

    How can I best adjust my code so that I am checking for only a specific workflow and so that it only fires when End Date is changed?  Here's what I have so far:

    namespace CancelRunningWorkflowEventReceiver
    {
      class CancelRunningWorkflowEventReceiver: SPItemEventReceiver
      {
        public override void ItemUpdating(SPItemEventProperties properties)
        {
          SPSecurity.RunWithElevatedPrivileges(delegate()
          {
            using (SPSite site = new SPSite(properties.SiteId))
            {
              using (SPWeb web = site.OpenWeb(properties.RelativeWebUrl))
              {
                SPWorkflowManager manager = site.WorkflowManager;
                SPListItem item = web.Lists[properties.ListTitle].GetItemById(properties.ListItemId);
                foreach (SPWorkflow workflow in manager.GetItemActiveWorkflows(item))
                {
                  foreach (SPWorkflowTask t in workflow.Tasks)
                  {
                    t["Status"] = "Canceled";
                    t.Update();
                  }
                  SPWorkflowManager.CancelWorkflow(workflow);
                }
              }
            }
          });
    
        }
      }
    }
    
    Wednesday, March 02, 2011 7:27 PM

Answers

  • Try to use workflow.ParentAssociation.Name for that

    .Net Follower (http://dotnetfollower.com)
    • Marked as answer by Porter Wang Monday, March 14, 2011 2:29 AM
    Friday, March 04, 2011 7:15 PM

All replies

  • Hello!

    To detect changes you can use something like that

    properties.ListItem["End Date"] !=  properties.AfterProperties["End Date"]

    To remove workflows, probably, you can use

    while (item.Workflows.Count > 0)
                {
                    SPWorkflow wf = item.Workflows[0];
                    SPWorkflowManager.CancelWorkflow(wf);
                    spSite.WorkflowManager.RemoveWorkflowFromListItem(wf);
                }

    According this approach you don't have to receive and use the instance of SPWorkflowManager

    Thanks!


    .Net Follower (http://dotnetfollower.com)
    Wednesday, March 02, 2011 9:56 PM
  • Here's what I have now, based on your suggestion, but the workflow is still getting cancelled even if I do not make any updates to the End Date column.  Perhaps my placement/syntax is off?

    namespace CancelRunningWorkflowEventReceiver
    {
     class CancelRunningWorkflowEventReceiver: SPItemEventReceiver
     {
      public override void ItemUpdating(SPItemEventProperties properties)
      {
       if (properties.ListItem["End Date"] != properties.AfterProperties["End Date"])
       SPSecurity.RunWithElevatedPrivileges(delegate()
       {
        using (SPSite site = new SPSite(properties.SiteId))
        {
         using (SPWeb web = site.OpenWeb(properties.RelativeWebUrl))
         {
          SPWorkflowManager manager = site.WorkflowManager;
          SPListItem item = web.Lists[properties.ListTitle].GetItemById(properties.ListItemId);
          {
           foreach (SPWorkflow workflow in manager.GetItemActiveWorkflows(item))
           {
            foreach (SPWorkflowTask t in workflow.Tasks)
            {
             t["Status"] = "Canceled";
             t.Update();
            }
            SPWorkflowManager.CancelWorkflow(workflow);
           }
          }
         }
        }
       });
    
      }
     }
    }
    
    Thursday, March 03, 2011 7:13 PM
  • Probably, we have a problem with object comparison. Try to do something like that

    DateTime currentEndDate = properties.ListItem["End Date"] != null ? (DateTime)properties.ListItem["End Date"] : DateTime.MinValue;
            DateTime newEndDate   = properties.AfterProperties["End Date"] != null ? (DateTime)properties.AfterProperties["End Date"] : DateTime.MinValue;
            if (currentEndDate != newEndDate)
            {
              // do something
            }
    
    Thanks!


    .Net Follower (http://dotnetfollower.com)
    Thursday, March 03, 2011 7:34 PM
  • .Net Follower - thank you for your continued assistance.  I tweaked the code to include your suggestion, but the workflow is still not cancelling.  If I take out the if statement, it works again but for any change on the item.  Perhaps I am positioning the if statement incorrectly or, as you suggested, there is an issue with the comparison?

    namespace CancelRunningWorkflowEventReceiver
    {
      class CancelRunningWorkflowEventReceiver: SPItemEventReceiver
      {
        public override void ItemUpdating(SPItemEventProperties properties)
        {
          SPSecurity.RunWithElevatedPrivileges(delegate()
          {
            using (SPSite site = new SPSite(properties.SiteId))
            {
              using (SPWeb web = site.OpenWeb(properties.RelativeWebUrl))
              {
                SPWorkflowManager manager = site.WorkflowManager;
                SPListItem item = web.Lists[properties.ListTitle].GetItemById(properties.ListItemId);
    
                DateTime origEndDate = properties.ListItem["EndDate"] != null ? (DateTime)properties.ListItem["EndDate"] : DateTime.MinValue;
                DateTime newEndDate = properties.AfterProperties["EndDate"] != null ? (DateTime)properties.AfterProperties["EndDate"] : DateTime.MinValue;
                if (origEndDate != newEndDate)
                {
                  foreach (SPWorkflow workflow in manager.GetItemActiveWorkflows(item))
                  {
                    foreach (SPWorkflowTask t in workflow.Tasks)
                    {
                      t["Status"] = "Canceled";
                      t.Update();
                    }
                    SPWorkflowManager.CancelWorkflow(workflow);
                  }
                }
              }
            }
          });
    
        }
      }
    }
    
    
    Friday, March 04, 2011 6:26 PM
  • Figured it out with the help of http://labs.ecraft.com/Blogs/SharePoint-Event-Handler-Gotchas

    The problem is how the format which comes out of ListItems is the format which the SP site uses (as expected) while the AfterProperties stores the date in ISO8601 format.  I had to change:

     DateTime origEndDate = properties.ListItem["EndDate"] != null ? (DateTime)properties.ListItem["EndDate"] : DateTime.MinValue;
     DateTime newEndDate = properties.AfterProperties["EndDate"] != null ? (DateTime)properties.AfterProperties["EndDate"] : DateTime.MinValue;
       if (origEndDate != newEndDate)
    
    

    to

    string origEndDate = Microsoft.SharePoint.Utilities.SPUtility.CreateISO8601DateTimeFromSystemDateTime(DateTime.Parse(properties.ListItem["EndDate"].ToString()));
    string newEndDate = properties.AfterProperties["EndDate"].ToString();
      if (origEndDate != newEndDate)
    

     

    So this settles the EndDate portion of my question, but the main question still remains.  If I have three different workflows running on the list (Approval, Email Reminder & Disposition), how can I only cancel the Email Reminder workflow with this event receiver?

    Friday, March 04, 2011 7:04 PM
  • Hello!

    Probably, the field "EndDate" always changes during Update. For example, it's usually set to the current date or something like this. Can you check this assumption?

    Did you try to debug this code?

    Thanks!


    .Net Follower (http://dotnetfollower.com)
    Friday, March 04, 2011 7:04 PM
  • Try to use workflow.ParentAssociation.Name for that

    .Net Follower (http://dotnetfollower.com)
    • Marked as answer by Porter Wang Monday, March 14, 2011 2:29 AM
    Friday, March 04, 2011 7:15 PM