When developing SharePoint workflows, one often needs to send email notification for a task, and then keep sending regular reminders for this task at scheduled intervals. When developing workflows in SharePoint Designer, one can use DelayUntilActivity and DelayForActivity, but those were not designed for in Visual Studio workflows.
When using State Machine workflows, the implementation for regular reminders is quite different than that in Sequential workflow. One can use put in EventDrivenActivity with DelayActivity but it will only execute once so you can only have one-time reminder, instead of regular reminders. If you try to wrap DelayActivity in a WhileActivity, the EventDrivenActivity won’t compile because its first child must be IEventActivity (such as DelayActivity).
Unlike with the Sequential workflow, ConditionedActivityGroupActivity cannot be used in State machine EventDrivenActivity. Also, one cannot write a loop around DelayActivity as it is supposed to be the first child of EventDrivenActivity.
The approach that can be taken is to use WF ability to reenter the same StateActivity over and over and thus reset the workflow. It is suggested here http://forums.microsoft.com/msdn/ShowPost.aspx?PostID=3553632&SiteID=1 but the following discussion adopts it for the SharePoint reality of task correlation tokens.
One common way to use StateMachineActivity in SharePoint is to create a task in StateInitializationActivity, register for OnTaskChanged or OnItemChanged event using EventDrivenActivity and use EventDrivenActivity with DelayActivity to send reminders. When using State Machine and SharePoint tasks, you most likely expect to reenter this state over and over, creating more tasks of the same kind (one common scenario is multi-stage approval with ability to send back the approval to previous people for re-approval). If you expect to reenter this task, you must set the all-important correlation tokens for the task should be set to use the containing StateActivity as a parent. Following is the structure you might create:
<StateActivity Name=“MyCustomTaskState”>
<StateInitializationActivity>
<CreateTask CorrelationTokenParent=“MyCustomTaskState” />
<SendEmail Type=“Task Created” />
</StateInitializationActivity>
<EventDrivenActivity Type=“Task Changed”>
<OnTaskChanged CorrelationTokenParent=“MyCustomTaskState” />
<UpdateTask CorrelationTokenParent=“MyCustomTaskState” />
<IfElseActivity>
<IfElseBranch Condition=”Task Completed”>
<SetStateActivity Target=“OtherState” />
<IfElseBranch />
<IfElseActivity />
</EventDrivenActivity>
<EventDrivenActivity Type=“Item Changed”>
<OnItemChanged />
<EventDrivenActivity Type=“Reminder”>
<DelayActivity />
<SendEmail Type=“Reminder” />
<SetStateActivity Target=“MyCustomTaskState” />
<StateFinalizationActivity>
<CompleteTask CorrelationTokenParent=“MyCustomTaskState” />
<SendEmail Type=“Task Completed” />
</StateFinalizationActivity>
<StateActivity />
If you run this code, you will notice the following sequence of events:
1. <CreateTask CorrelationTokenParent=“MyCustomTaskState” />
2. <SendEmail Type=“Task Created” />
3. <OnTaskChanged CorrelationTokenParent=“MyCustomTaskState” /> - register
4. <OnItemChanged /> - register
5. <DelayActivity /> - sleep
The workflow will then go to sleep and wait for either OnTaskChanged event, OnItemChanged event or for DelayActivity to elapse.
If OnTaskChanged or OnItemChange event wakes up workflow before DelayActivity has ever elapsed, and the user action causes workflow to move to another state everything is going to run correctly. However, once DelayActivity elapses, it wakes up and the following sequence of events occurs:
1. <DelayActivity /> - wake
2. <SendEmail Type=“Reminder” />
3. <SetStateActivity Target=“MyCustomTaskState” />
4. <CompleteTask CorrelationTokenParent=“MyCustomTaskState” />
5. <SendEmail Type=“Task Completed” />
6. <OnTaskChanged CorrelationTokenParent=“MyCustomTaskState” /> - register
7. <CreateTask CorrelationTokenParent=“MyCustomTaskState” />
8. <SendEmail Type=“Task Created” />
9. <OnTaskChanged CorrelationTokenParent=“MyCustomTaskState” /> - register
10.<OnTaskChanged /> - register
11.<DelayActivity /> - sleep
As you can see, your task will be completed and immediately new task of the same type will be created and new notification emails will be sent. If you sent link to the original task in the emails, the URLs in the email are no longer valid.
To avoid this, you might be tempted to wrap the activities in StateInitializationActivity and StateFinalizationActivity to avoid completing and recreating the task. If you do, you will discover that after reentering the state, OnTaskChanged events will no longer register correctly with exception indicating that correlation token for the task has not been initialized. This is because the correlation tokens use the StateActivity as a parent, and when it is reentered, they are reset, and you have orphaned your original task. There is no way to preserve those correlation tokens from one entry of StateActivity to another. A different approach is needed.
The correct approach is to use ability of StateActivity to host another StateActivity and break up task creation, task completion and task events into three separate StateActivity entities, and set the correlation token to use the top-most parent StateActivity:
<StateActivity Name=“CreateTaskState”>
<SetStateActivity Target=“TaskEventsState” />
<StateActivity Name=“TaskEventsState”>
<IfElseBranch Condition=“Task Completed”>
<SetStateActivity Target=“CompleteTaskState” />
<StateActivity Name=“CompleteTaskState”>
3. <SetStateActivity Target=“TaskEventsState” />
4. <OnTaskChanged CorrelationTokenParent=“MyCustomTaskState” /> - register
5. <OnItemChanged /> - register
6. <DelayActivity /> - sleep
The workflow will then go to sleep and wait for either OnTaskChanged event, OnItemChanged event or for DelayActivity to elapse. Once DelayActivity elapses, following sequence of events occurs:
5. <OnTaskChanged /> - register
Whenever the task change occurs that indicates that workflow is ready to move to another state, the following sequence of events occurs:
1. <OnTaskChanged CorrelationTokenParent=“MyCustomTaskState” /> or
2. <UpdateTask CorrelationTokenParent=“MyCustomTaskState” />
3. <IfElseActivity>
4. <IfElseBranch Condition=“Task Completed”>
5. <SetStateActivity Target=“CompleteTaskState” />
6. <CompleteTask CorrelationTokenParent=“MyCustomTaskState” />
7. <SendEmail Type=“Task Completed” />
8. <IfElseActivity>
9. <IfElseBranch Condition=“Task Completed”>
10.<SetStateActivity Target=“OtherState” />
Whenever the item change occurs that indicates that workflow is ready to move to another state, the following sequence of events occurs:
1. <OnItemChanged />
2. <SetStateActivity Target=“TaskEventsState” />
The workflow designer outline of this is shown below:
Overall view of the parent state
Overall hierarchy of all activities
CreateTaskState with creation of the task and events
Properties window of CreateTask activity with correlation token pointing to parent state of all three activities
EventDrivenActivity for OnTaskChanged event
EventDrivenActivity for OnItemChanged event
EventDrivenActivity for DelayActivity and reminder email
CompleteTaskState and transition to another state
PingBack from http://blog.a-foton.ru/2008/07/using-nested-stateactivity-to-send-regular-reminders-in-visual-studio-sharepoint-state-machine-workflow/
Hello Ali,
do you know if it is possible to modify a Workflow from the InfoPath Code?
I want to add a DelayActivity dynamically to the Workflow from the VSTA Code.
I tried using the WorflowRuntime but i dont get the WorkflowInstance correctly.
Any idea if this will work out from "outside"?
Greetings and thanks
I've been trying to figure out how to dynamically set the correlation token when a new task of the same type was created, but evidently I was looking at it wrong. Thanks for this post, it helped me a ton.
This is a very nice post. Can you please let me know how to create multiple tasks in State Machine workflow. I could not find help anywhere.
Jeff
Can yu please let me know how you resolved it