E' possibile creare workflow sequenziali costituiti da custom activity che mettono il flusso di lavoro in attesa ( long running activities):
In questo modo possiamo consentire all'utente di lavorare su una determinata maschera collegata ad un certo flusso per il tempo a lui necessario e solo in seguito effettuare l'avanzamento al successivo activity. Durante il periodo di attesa, lo stato del workflow viene persistito sul database e può essere reidratato, al momento dell'avanzamento.
Alla base di questo ragionamento c'è la gestione delle code del flusso di lavoro: WorkflowQueue, con cui possiamo inviare messaggi dall'host all'activity in un workflow.
La custom activity incaricata alla creazione dell'oggetto WorkflowQueue può essere così fatta:
1: public partial class PersistActivity: Activity
2: {
3: public PersistActivity()
4: {
5: InitializeComponent();
6: }
7:
8: protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
9: {
10: WorkflowQueuingService queueService = executionContext.GetService<WorkflowQueuingService>();
11: WorkflowQueue wfQueue = queueService.CreateWorkflowQueue(this.Name, true);
12: wfQueue.QueueItemAvailable += new EventHandler<QueueEventArgs>(nextActivity);
13: return ActivityExecutionStatus.Executing;
14: }
15:
16: void nextActivity(object sender, QueueEventArgs e)
17: {
18: ActivityExecutionContext context = (ActivityExecutionContext)sender;
19: WorkflowQueuingService queueService = context.GetService<WorkflowQueuingService>();
20: queueService.DeleteWorkflowQueue(e.QueueName);
21: context.CloseActivity();
22: }
23: }
La nostra classe eredita da Activity, cioè la classe base di tutte le activity, ed estendiamo il metodo Execute, in cui definire la logica da eseguire. Il metodo ritorna lo stato di esecuzione dell'activity; nel caso specifico indico che lo stato è Executing.
Inoltre andiamo a creare l'oggetto WorkflowQueue richiamando il metodo WorkflowQueuingService.CreateWorkflowQueue al quale passiamo il nome dell'activity.
La spiegazione della sottoscrizione dell'evento QueueItemAvailable la rimandiamo subito dopo aver visto il codice presente sull'host:
Questa prima parte si occupa di creare il runtime al quale vengono aggiunti i vari servizi, il workflow:
1: WorkflowRuntime workflowRuntime = new WorkflowRuntime();
2: ManualWorkflowSchedulerService scheduler = new ManualWorkflowSchedulerService();
3: workflowRuntime.AddService(scheduler);
4: string connection = @"connessione";
5: System.Collections.Specialized.NameValueCollection col =
new System.Collections.Specialized.NameValueCollection();
6: col.Add("connectionString", connection);
7: col.Add("unloadOnIdle", "true");
8: WorkflowPersistenceService persitence = new SqlWorkflowPersistenceService(col);
9: workflowRuntime.AddService(persitence);
10: workflowRuntime.StartRuntime();
11: WorkflowInstance instance = workflowRuntime.CreateWorkflow(typeof(WFSequential.Workflow1));
12: instance.Start();
13: scheduler.RunWorkflow(instance.InstanceId);
Al momento della reidratazione dello stato del workflow:
1: WorkflowRuntime workflowRuntime = new WorkflowRuntime();
2: ManualWorkflowSchedulerService scheduler = new ManualWorkflowSchedulerService();
3: workflowRuntime.AddService(scheduler);
4: string connection = @"connessione";
5: System.Collections.Specialized.NameValueCollection col =
6: new System.Collections.Specialized.NameValueCollection();
7: col.Add("connectionString", connection);
8: col.Add("unloadOnIdle", "true");
9: WorkflowPersistenceService persitence = new SqlWorkflowPersistenceService(col);
10: workflowRuntime.AddService(persitence);
11: workflowRuntime.StartRuntime();
12: //Passare il guid che rapparesenta l'instanza del workflow
13: WorkflowInstance instance = workflowRuntime.GetWorkflow(_instanceId);
14: System.Collections.ObjectModel.ReadOnlyCollection<WorkflowQueueInfo> list =
15: instance.GetWorkflowQueueData();
16: //Nel caso specifico la coda sarà costituita da un solo messaggio
17: instance.EnqueueItem(list[0].QueueName, null, null, null);
18: scheduler.RunWorkflow(_instanceId);
Da sottolineare l'uso di EnqueueItem sull'oggetto WorkflowInstance per inserire un messaggio nella coda. A questo punto è facile intuire lo scopo dell'evento QueueItemAvailable, il quale scatterà alla notifica che l'oggetto è stato inserito nella coda. Nel metodo nextActivity ci andremo a preoccupare di ripulire la coda ed a modificare lo stato dell'activity passando da Executing a Closed richiamando il metodo CloseActivity su ActivityExecutionContext che rappresenta l'ambiente di esecuzione di un activity.
Si tratta ovviamente di un semplice esempio, da prendere come mezzo di riflessione per i propri sviluppi.
Da precisare che il medesimo risultato si può ottenere tranquillamente usando un più appropriato State Machine Workflow.
Concludo segnalandovi un articolo di un mio amico, nonchè ex-collega in cui mostra nel dettaglio l'integrazione di custom long running activity in un'applicazione Asp.Net.
Per l'articolo cliccare qui.