Saturday, June 28, 2008

Workflow Sequenziali con activity long running

. Saturday, June 28, 2008
0 commenti

E' possibile creare workflow sequenziali costituiti da custom activity che mettono il flusso di lavoro in attesa ( long running activities):
LongRunning1
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.

Read More »»

Tuesday, June 24, 2008

Sticky Notes

. Tuesday, June 24, 2008
0 commenti

StickyNotes2 StickyNotes1

Spesso vado alla ricerca di programmi che fanno le cose più disparate, e poi scopro che qualcosa che fa al caso mio è già presente nel sistema operativo. E' il caso di Sticky Notes presente su Windows Vista ( Home Premium, Business, Enterprise, Ultimate) che permette di creare brevi note vocali o scritte a mano.
Per richiamare l'applicazione digitare nella casella di ricerca Sticky Notes.
Per maggiori dettagli la guida.

Read More »»

Saturday, June 21, 2008

Gli store della persistenza

. Saturday, June 21, 2008
0 commenti

Senza persistenza il ciclo di vita di un workflow è limitato, per questo motivo, grazie ad un'architettura basata su vari servizi che possiamo agganciare al runtime, ci viene fornito un servizio che si occupa di salvare un workflow su un database SQL rimuovendolo dalla memoria per poi gestire il suo caricamento in un secondo momento.

La protagonista  di questa serie di operazioni è la classe SqlWorkflowPersistenceService.
In questo post non vedremo quali sono i passi iniziali per poter usare il servizio ( per chi invece fosse interessato rimando alla documentazione su msdn) ma bensì quali sono le situazioni in cui lo stato di un workflow viene fatto persistere.
E' bene fare però una precisazione, le situazioni che andrò ad elencare non sempre producono come risultato la serializzazione del flusso sul database, ma bensì sono da intendere come punti in cui avviene un qualche aggiornamento.
Questa affermazione forse sarà un pò più chiara con il procedere della lettura.
Iniziamo con l'indicare il primo store in cui lo stato del workflow viene persistito cioè quando un workflow diventa inattivo ( idle).
Per provare quanto detto servono due cose: un workflow sequenziale ( Figura 1) e SQL Server Profiler ( per chi usa SQL Express 2005 consiglio il seguente tool):
Persistence1
Nel caso specifico ho inserito un DelayActivity per rendere il workflow inattivo.
Ciò però non basta in quanto è necessario indicare alla classe SqlWorkflowPersistenceService che appena il flusso raggiunge lo stato di attesa, il workflow deve essere serializzato.
Il codice che aggiunge il servizio di persistenza al runtime e lo istruisce a quanto detto sopra è il seguente:

   1: WorkflowRuntime runtime = new WorkflowRuntime();
   2: ManualWorkflowSchedulerService scheduler = new ManualWorkflowSchedulerService();
   3: runtime.AddService(scheduler);
   4: NameValueCollection col = new NameValueCollection();
   5: col.Add("connectionString", "connessione al db");
   6: col.Add("UnloadOnIdle", "true");
   7: WorkflowPersistenceService persistence = new SqlWorkflowPersistenceService(col);
   8: runtime.AddService(persistence);
   9: runtime.StartRuntime();
  10: WorkflowInstance instance = runtime.CreateWorkflow(typeof(SeqWF.Workflow1));
  11: instance.Start();
  12: scheduler.RunWorkflow(instance.InstanceId);

La parte che in questo momento ci interessa è il valore true passato al parametro unloadOnIdle.
Monitorando con il profiler possiamo verificare che il servizio richiama la stored procedure di inserimento InsertInstanceState nella tabella InstanceState usata da Workflow Foundation per la persistenza dello stato di un workflow:
Persistence2
Il secondo store avviene quando un workflow è completato o terminato. Nel seguente caso il workflow sequenziale è quello di Figura 2:
Persistence3
In questo caso possiamo anche passare al parametro unloadOnIdle il valore false, oppure scegliere il costruttore della classe in cui passare semplicemente la sola stringa di connessione. Anche in questo caso tramite il profiler constatiamo che, al completamento del workflow, viene richiamata la solita stored procedure anche se con qualcosa di diverso.
Persistence4Come si può notare al parametro state non viene passato nessun blob e, cosa ancora più importante, al parametro status viene assegnato il valore 1.
Alla fine dell'esecuzione della stored procedure la tabella InstanceState non contiene nessun nuovo record. Il motivo è da ricercare nella stessa stored procedure, in quanto se lo status del workflow vale 1 ( workflow completato) o 3 ( workflow terminato, si può ottenere usando l'activity Terminate) viene rimossa l'eventuale copia persistita tramite la seguente delete:

   1: DELETE FROM [dbo].[InstanceState]
   2:        WHERE uidInstanceID=@uidInstanceID 
   3:        AND ((ownerID = @ownerID AND ownedUntil>=@now) 
   4:        OR (ownerID IS NULL AND @ownerID IS NULL ))

Con ciò forse risulterà più chiara la precisazione fatta ad inizio post, il comportamento di default del servizio di persistenza non sempre produce un record nella tabella del workflow serializzato, ma comunque un qualche aggiornamento.
Terzo store, al completamento degli activity TransactionScope e CompensatableTransactionScope.
Per garantire l'esito positivo o negativo di transazioni ACID entrano in scena le due activity sopra citate. Senza scendere troppo nello specifico possiamo dire che tramite il TransactionScopeActivity garantiamo un'unica transazione per tutte le activity racchiuse al suo interno. Così facendo otteniamo se sono completate correttamente un commit, altrimenti un'operazione di rollback.
Il CompensatableTransactionScopeActivity garantisce il meccanismo della compensazione cioè è possibile annullare gli effetti prodotti da un'operazione eseguita in precedenza; detto in altre parole è possibile scrivere il codice che eseguirà il rollback delle operazione che in precedenza sono state sottoposte a commit.
Il questo caso il workflow sequenziale è rappresentato dalla Figura 3:
Persistence5
Con il solito profiler verifichiamo il tutto.
Persistence6 Dopo il completamento dell'activity TransactionScope abbiamo il lancio della stored procedure, ma per quello che si è detto al secondo store, al completamento del workflow la copia serializzata viene cancellata.




Quarto store al completamento di un custom activity decorato con l'attributo PersistOnClose:

   1: [PersistOnClose]
   2: public partial class ActivityPersist: Activity
   3: {
   4:   public ActivityPersist()
   5:   {
   6:     InitializeComponent();
   7:   }
   8:  
   9:   protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
  10:   {
  11:     return ActivityExecutionStatus.Closed;
  12:   }
  13: }

Il workflow sequenziale è rappresentato dalla Figura 5:

Persistence7 Persistence8
Il quinto store quando si invoca sulla classe WorkflowInstance il metodo Unload o TryUnload:

   1: instance.Unload();

Read More »»

Wednesday, June 18, 2008

L'esperienza con Workflow Foundation

. Wednesday, June 18, 2008
0 commenti

Da qualche mese sto lavorando con una tecnologia che trovo estremamente interessante: Windows Workflow Foundation e devo dire che mi sta affascinando non poco.
Come qualsiasi post che si rispetti è fondamentale fare una piccola introduzione sui concetti di base.
Per workflow si intende un insieme di step in cui si definiscono una serie di regole per descrivere un modello di processo.
WF1Per quanto riguarda Workflow Foundation posso dire che si tratta di un Framework introdotto con la versione 3.0 che ci permette di generare workflow di tipo sequential o state machine, grazie anche all'uso di un designer integrato in VS.
Nel caso di Visual Studio 2008 tutto il necessario per lo sviluppo è già integrato, mentre per Visual Studio 2005 bisogna installare le extension per WF oltre al Framework 3.0.

E' necessario un'applicazione di host:
Windows Console Applications
Servizio Windows
Windows Forms

Applicazione WPF
Web Applications Asp.Net

per creare un'istanza del Workflow runtime.
Per chi vuole approfondire i concetti di base, ho raccolto qualche articolo in lingua italiana:
Introduzione a Workflow Foundation (Parte 1)
Introduzione a Workflow Foundation Parte II
Introduzione a Windows Workflow Foundation
Per concludere, a breve, cercherò di postare qualche prova/esempio su questa tecnologia.

Read More »»

Sunday, June 15, 2008

Taskbar Shuffle

. Sunday, June 15, 2008
0 commenti

ts2.5-smallTaskbar Shuffle è un'utile utility completamente free, per ambiente Windows ( Vista, Xp, 2000) che permette di spostare le schede dei programmi aperte nella taskbar semplicemente effettuando drag & drop.
Nello stesso identico modo è possibile ordinare le icone nella systray.

Read More »»

Monday, June 9, 2008

Themes per GridView

. Monday, June 9, 2008
0 commenti

Sul blog di Kevin Brammer una raccolta di cinque temi per il controllo GridView:

ThemeGridView1 ThemeGridView2
Oltre che scaricarli si possono testare cliccando qui.

Read More »»

Saturday, June 7, 2008

Rilascio Silverlight 2 Beta 2

. Saturday, June 7, 2008
0 commenti

Le voci sulla Beta 2 di Silverlight 2 erano nell'aria da qualche giorno, oggi il rilascio.
Per il download di Silverlight Tools Beta 2 per VS 2008, di Expression Blend 2.5 e Deep Zoom Composer basta andare qui.
Per tutto il resto c'è Scott Guthrie.

Read More »»

Thursday, June 5, 2008

Testare web application con IETester

. Thursday, June 5, 2008
0 commenti

Tramite IETester è possibile in modo estremamente semplice testare la corretta visualizzazione della propria applicazione web contemporaneamente in Internet Explorer 8 beta, IE 7, IE 6, IE 5,5 su Windows Xp o Vista.
IETester

Il tool è freeware e non necessita di nessuna registrazione per il download.
Fonte: Ajaxian

Read More »»

Monday, June 2, 2008

RadioButtonList e JavaScript

. Monday, June 2, 2008
0 commenti

Questo post nasce da alcune prove eseguite con un mio collega per recuperare l'elemento "checkato" di un controllo runat server di tipo radioButtonList tramite javascript allo scatenarsi dell'evento onClick.
Supponiamo di avere il seguente controllo radioButtonList così popolato:

   1: <asp:RadioButtonList ID="rbList" runat="server">
   2:   <asp:ListItem>Elemento1</asp:ListItem>
   3:   <asp:ListItem>Elemento2</asp:ListItem>
   4:   <asp:ListItem>Elemento3</asp:ListItem>
   5:   <asp:ListItem>Elemento4</asp:ListItem>
   6:   <asp:ListItem>Elemento5</asp:ListItem>
   7: </asp:RadioButtonList>

Il markup che viene generato in fase di rendering della pagina è qualcosa che possiamo schematizzare nel seguente modo:

schema_RadioButtonList 
A questo punto diventa abbastanza semplice andare a ricercare l'elemento che abbiamo selezionato, ciclando sulle righe della tabella, e per ogni cella recuperare la proprietà checked del primo elemento figlio che contiene nella fattispecie l'elemento input di tipo radio:

   1: <script type="text/javascript">
   1:  
   2:   function FindElement( e)
   3:   {
   4:     for( var i=0; i<e.rows.length; i++)
   5:     {
   6:       var isChecked = e.rows[i].cells[0].children[0].checked;
   7:       if( isChecked)
   8:       {
   9:         alert( "Trovato: " + e.rows[i].cells[0].children[0].value);
  10:         break;
  11:       }
  12:     }
  13:   }
</script>


Read More »»