Monthly Archives: January 2007

WF saját activity designer

A Workflow Foundation lehetővé teszi, hogy saját activity osztályunkhoz saját designer osztályt rendeljünk, ami meghatározza, hogyan fog viselkedni a komponensünk a Visual Studio környezetében. Ehhez mindössze egy osztályt kell készítenünk és egy attribútummal hozzárendelnünk az activity-hez:

[Designer( typeof( MyActivityDesigner ) )]
public partial class MyActivity : Activity
{…}

Hasznos lehet például, ha megváltoztatjuk azt a szöveget, ami az activity-n megjelenik a designerben. A nevén kívül több hasznos információt ki tudunk írni arra a kis helyre. Ha tényleg csak ennyi a feladatunk és nem akarjuk teljesen átvenni az activity megrajzolásának bonyodalmait, akkor egyszerűen felüldefiniálhatjuk a Text tulajdonságot:

public sealed class MyActivityDesigner : ActivityDesigner
{
  //…
  public override string Text
  {
    get
    {
      MyActivity activity = (MyActivity) this.Activity;
      return String.Format( "{0}: {1} -> {2}", base.Text, Path.GetFileName( activity.SourceFolder ), Path.GetFileName( activity.TargetFolder ) );
    }
    protected set
    {
      base.Text = value;
    }
  }
}

Előfordulhat persze, hogy az így összeállított szöveg nem fér bele a dobozba. Hosszú Reflectorozás után jó megoldásnak látszik a MinimumSize tulajdonság felüldefiniálása:

public override System.Drawing.Size MinimumSize
{
  get
  {
    return new System.Drawing.Size( 250, base.MinimumSize.Height );
  }
}

Ennél persze kifinomultabb megoldások is elképzelhetőek. Akinek van ötlete, írjon!

Saját események WF activity-ben

Aki sokat kódolt már Workflow Foundation platformon vagy járt nálunk tanfolyamon, biztosan megjegyezte, hogy WF DependencyProperty készítéséhez használható a "wdp" nevű snippet. Hasonlóan, ha a "wde" snippetet használjuk, gyorsan beszúrhatunk egy eseményt definiáló kódot a saját activity osztályunkba.

Alapértelmezés szerint a beszúrt kód EventHandler típusú eseményt definiál. Mi van akkor, ha mi további adatokat szeretnénk átadni esemény paraméterként? Természetesen létre kell hoznunk egy saját esemény paraméter osztályt. A bökkenő csak annyi, hogy a snippet nem kínál fel ezzel kapcsolatban semmilyen módosítási lehetőséget, pedig csak annyi a trükk, hogy gondolkodni kell, no meg generikus EventHandlert használni, például:

public static DependencyProperty OnWorkingEvent = DependencyProperty.Register( "OnWorking", typeof( EventHandler<WorkEventArgs> ), typeof( MyActivity ) );

// Sok attribútum jön ide
public event EventHandler<WorkEventArgs> OnWorking
{ … }

Ha ezt utólag próbáljuk átvezetni az activity kódján, a workflow designer várhatóan panaszkodni fog, hogy a korábban feldobott activity paraméterezése érvénytelen. Ha nem akarjuk elölről kezdeni a teljes workflow tervezését, akkor kívételes alkalom lévén ne féljünk beleszerkeszteni a workflow.designer.cs fájlba. Valahogy így:

this.myActivity1.OnWorking += new System.EventHandler<WorkEventArgs>( this.myActivity1_OnWorking );

Addig kell ügyeskedni, amíg a workflow designer abbahagyja a nyavalygást és hajlandó megjeleníteni a workflow definíciót grafikusan. Sok sikert 🙂

WF és ASP.NET: threading

A Workflow Foundation runtime alapértelmezés szerint aszinkron módon futtatja az egyes workflow példányokat, azaz minden egyes új példány létrehozásakor kér egy szabad szálat a CLR ThreadPooltól és azon indítja el a workflow-t alkotó activity-k futtatását.

A november végi MSDN konferencián röviden volt szó arról, hogy ez az aszinkron viselkedés néha gondot jelent, például webes környezetben. Az ASP.NET ugyanis maga gondoskodik az egyes kérésekhez érkező szálak kezeléséről és az újabb szálak indítása csak rontana a helyzeten. Arról nem is beszélve, hogy az aszinron végrehajtást nehéz egy oldal rendereléséhez igazítani.

A megoldás az ütemezésért felelős ManualWorkflowSchedulerService hozzáadása a runtime-hoz:

WorkflowRuntime runtime = new WorkflowRuntime();
ManualWorkflowSchedulerService scheduler = new ManualWorkflowSchedulerService();
runtime.AddService( scheduler );

Na ennyivel még nem vagyunk készen. Mivel a ManualWorkflowSchedulerService nem a ThreadPooltól kér szálat, ezért nekünk kell adnunk egyet, amin szinkron módon tudja futtatni a workflow-t, például valahogy így:

WorkflowInstance instance = runtime.CreateWorkflow( typeof( MyWorkflow ) );
instance.Start();
scheduler.RunWorkflow( instance.InstanceId );

A RunWorkflow metódusról ezt írja az MSDN:

This is a synchronous call that uses the current thread to run the workflow. It does not return until the workflow idles, suspends, completes, terminates, or aborts.

Magyarul a RunWorkflow() belerúg egyet a folyamatba és akkor tér vissza, amikor az megáll (befejeződik, várakozik, hibázik stb.). Ez sok esetben nem is rossz, de képzeljük el a következő szituációt: timeout-tal szeretnénk várakozni egy eseményre. Szépen behajítjuk a folyamatba a HandleExternalEvent és a Delay activity-ket, gyönyörűen párhuzamosítva egymással egy Listen activity-n belül. Meghívjuk a RunWorkflow()-t, ami futtatja a folyamatot egészen az eseményre várakozásig, ahogy az a nagy könyvben meg van írva. Ámde a várva várt esemény nem jön. Ekkor kellene kattannia a timeout-ot jelző Delay activity-nek.

Csakhogy nincs hol kattanjon. Nincs min kattanjon. Nincs mikor kattanjon. A workflow példányunk ugyanis egész egyszerűen nem fut. Executing állapotban van, de valójában nem fut. Egy szinkron módon futtatott workflow példány csak akkor fut, ha adunk szálat alá.

Ugyanez a probléma akkor is, ha eseménnyel üzenünk a workflow példánynak. Az esemény szépen beáll a "workflow számára továbbítandó események sorába", de majd csak akkor fog lefutni a workflow definícióban hozzá tartozó eseménykezelő, ha meghívjuk a RunWorkflow() metódust. Nem elég, hogy macerás a kommunikáció, még a folyamat továbbléptetéséről is nekünk kell gondoskodni!

Képzeljük el mindezt egy webalkalmazásban! Ha csak nem hívogatjuk passzióból a RunWorkflow() metódust időnként, nem fognak továbbfutni a workflow példányaink. Márpedig egy webalkalmazásban elég körülményes bármit is megcsinálni "időnként".

WF és ASP.NET: hosting

A novemberi konferenciánkon elhangzott, hogy a Windows Workflow Foundationt bármilyen CLR appdomainben lehet hosztolni. Mivel valójában egy .NET-es osztálykönyvtárról van szó, ez valójában nem is meglepő. Említettem azt is, hogy a hosztolás lényegében a WorkflowRuntime osztály megpéldányosítását jelenti.

Csakhogy ez nem mindig ilyen egyszerű.

Ebből az objektumból appdomainenként maximum egyetlen példány lehet és ez elég is, másként fogalmazva, a Workflow Foundationt egy alkalmazásban elég "egyszer hosztolni". Ha webalkalmazásban gondolkodunk, akkor ez Application szintű objektum referenciát jelent. A WF egyik utolsó bétájában ennek segítésére még volt egy segédosztály, a WorkflowWebRequestContext. Dino Esposito 2006. áprilisi MSDN Magazine cikkében remekbe szabott példát láthatunk arról, hogy ez az osztály implicit módon képes kezelni a runtime felparaméterezését, elindítását és az aktuális (egyetlen) példány visszaadását.

Ez így szép is lenne, de nincs így. Hosszú Reflectorozás után arra jutottam, hogy ez az osztály még internal változatban sincs benne a WF RTM verzióban. Marad tehát a körülményes global.asax-os, Application objektumos körbekódolásos megközelítés.

Na de miért? Miért? MIÉRT??? Nem tudom, hogy miért, csak arra tudok gondolni, hogy így teljesen ugyanúgy működik a WF runtime hosztolása webalkalmazásban, mint bármely más típusú alkalmazásban, nem kell hozzá tudni semmi extrát, nincsenek trükkös metódus hívások és minden átlátható. Persze az jó kérdés, hogy egy "körbekódolásos megközelítés" miért számít átláthatóbbnak egyetlen framework metódus hívásnál…