Dienstag, April 29, 2008

Workflow Foundation Erkenntnisse (Hosting TODOs)

Ich habe die letzten Wochen Beratung unter anderem zum Thema Windows Workflow Foundation gemacht. Dabei sollte das Workflow Designer-Control für die Anpassung der WFs genutzt werden. Im Großen und Ganzen ein super Tool und sehr interessant und mächtig, allerdings hatte die Demo, die wir benutzt haben einige ärgerliche Probleme. Eine Version mit ausführlicher Anleitung kann beim Artikel „Windows Workflow Foundation: Everything About Re-Hosting the Workflow Designer“ heruntergeladen werden.

1.       Bei der Verwendung von Umlauten, bekommt die Anwendung arge Probleme, hier müssen einige Methoden in der Loader-Klasse angepasst werden.

In der Methode PerformFlush sollte das Encoding für den Writer gesetzt werden, hier der Auszug:

   244   if (rootActivity != null )

  245  {

  246      using ( XmlWriter xmlWriter = XmlWriter . Create(

  247          this . xoml

  248          , new XmlWriterSettings () { Encoding = Encoding . UTF8, OmitXmlDeclaration = false }))

  249      {

  250          WorkflowMarkupSerializer xomlSerializer = new WorkflowMarkupSerializer ();

  251          xomlSerializer . Serialize(xmlWriter, rootActivity);

   252      }

  253  }

Außerdem habe ich noch die Methode PerformLoad angepasst, hier der Auszug:

  193   Activity rootActivity = null ;

  194   using ( StreamReader sr = new StreamReader ( this . xoml, Encoding . UTF8, true ))

  195  {

  196      using ( XmlReader reader = XmlReader . Create( this . xoml))

  197      {

  198          WorkflowMarkupSerializer xomlSerializer = new WorkflowMarkupSerializer ();

  199          try

  200          {

  201              rootActivity = xomlSerializer . Deserialize(serializationManager, reader) as Activity ;

  202          }

  203          catch ( Exception ex)

  204          {

  205              Trace . WriteLine(ex);

  206          }

  207      }

  208      if (rootActivity != null && designerHost != null )

  209      {

  210          AddObjectGraphToDesignerHost(designerHost, rootActivity);

  211          Type companionType = rootActivity . GetValue( WorkflowMarkupSerializer . XClassProperty) as Type ;

  212          if (companionType != null )

  213              SetBaseComponentClassName(companionType . FullName);

   214      }

  215  }

Bei der letzen Änderung bin ich mir nicht sicher, aber dennoch der Hinweis. Die Methode GetRootActivity in der Helper-Klasse könnte ebenfalls zu Problemen führen, auch hier eine kleine Anpassung:

   52   internal static Activity GetRootActivity( string fileName, IServiceProvider serviceProvider)

   53  {

   54      Activity rootActivity = null ;

   55      using ( StreamReader sr = new StreamReader (fileName, Encoding . UTF8, true ))

   56      {

   57          using ( XmlReader reader = XmlReader . Create(sr

   58              , new XmlReaderSettings () { XmlResolver = new System . Xml . XmlUrlResolver () }))

   59          {

   60              WorkflowMarkupSerializer xomlSerializer = new WorkflowMarkupSerializer ();

   61              DesignerSerializationManager ser = new DesignerSerializationManager (serviceProvider);

   62              ser . CreateSession();

   63              rootActivity = xomlSerializer . Deserialize(ser, reader) as Activity ;

    64          }

   65      }

   66      return rootActivity;

   67  }

2.       Die Nutzung einer Konfigurationsdatei für die möglichen Controls finde ich mehr als ungünstig, hier sollte man die AddToolboxEntries-Methode der ToolboxService-Klasse anpassen, in dem alle Assemblies (aus dem aktuellen und dessen Unterverzeichnis) durchlaufen werden und geprüft wird, ob Klassen mit der Basis-Klasse „Activity“ vorhanden sind. Außerdem nicht vergessen die Standard Activities zu laden. Das Durchlaufen der Assemblies im Ordner ist in den Code-Zeilen nicht vorhanden.

   546   Assembly assembly = typeof (System . Workflow . Activities . ActiveDirectoryRole ) . Assembly;

  547  LoopAssemblyTypes(lb, assembly);

  548  assembly = typeof ( TerminateActivity ) . Assembly;

  549  LoopAssemblyTypes(lb, assembly);

  550   //VS2008 required

  551  assembly = typeof ( SendActivity ) . Assembly;

   552  LoopAssemblyTypes(lb, assembly);

Hier die aufgerufene Methode (Außerdem wird der Assembly-Name als Titel verwendet):

   555   private void LoopAssemblyTypes( ListBox lb, Assembly assembly)

  556  {

  557      Type [] assemblyTypes = assembly . GetTypes();

  558      bool groupSet = false ;

  559      foreach ( Type assemblyType in assemblyTypes)

  560      {

  561          if (assemblyType . IsAbstract || ! assemblyType . IsClass || ! assemblyType . IsSubclassOf( typeof ( Activity )))

  562          {

  563              Debug . WriteLine( string . Format( "Class '{0}' not a valid Activity!" , assemblyType . FullName), "ToolboxService.LoopAssemblyTypes" );

  564          }

  565          else

  566          {

  567              Trace . WriteLine( string . Format( "Loading '{0}' into toolbox!" , assemblyType . FullName), "ToolboxService.LoopAssemblyTypes" );

  568              SelfHostToolboxItem item = new SelfHostToolboxItem (assemblyType, assemblyType . FullName);

  569              tbItems . Add(item);

  570              if ( ! groupSet)

  571              {

  572                  lb . Items . Add(assembly . FullName . Split( new char [] { ',' }, 2 )[ 0 ]);

  573                  groupSet = true ;

  574              }

  575              lb . Items . Add(item);

   576          }

  577      }

  578  }

3.       Übergeben der Typen an den TypeProvider dazu die Methode Initialize des Loaders erweitern. (Wird der TypeProvider nicht erweitert mit den Assemblies und dieser auch überall übergeben, so kann das der Grund sein, warum definierte Workflows nicht geladen werden. (Es gibt auch keine Fehlermeldung)

  102   foreach ( SelfHostToolboxItem toolboxItem in toolbox . Items)

   103  {

  104      typeProvider . AddAssembly(toolboxItem . Compon entClass . Assembly);

  105  }

  106  host . AddService( typeof ( ITypeProvider ), typeProvider, true );

4.       Außerdem habe ich einen ToolTip in die Anwendung integriert, der das DescriptionAttribute auswertet und ausgibt. Dazu muss kräftig in die ToolboxService-Klasse eingegriffen werden.

Die erste Änderung in der OnListBoxMouseMove

  692   int currentIndex = listBox . IndexFromPoint( new Point (e . X, e . Y));

  693   if (currentIndex >= 0 )

  694  {

  695      SelfHostToolboxItem item = listBox . Items[currentIndex] as SelfHostToolboxItem ;

  696      if (item == null || string . IsNullOrEmpty(item . Tooltip))

  697      {

  698          this . tooltip = null ;

  699  

  700          currentHighlightItemIndex = Int32 . MinValue;

  701      }

  702      else

  703      {

  704          {

  705              // ToolTip setzen

  706              if (currentHighlightItemIndex != currentIndex)

  707              {

  708                  this . tooltip = item . Tooltip;

  709                  currentHighlightItemIndex = currentIndex;

  710                  this . tp . Show(tooltip, this , new Point (e . X, e . Y));

   711              }

  712          }

  713      }

  714  }

Auf Klassenebene sind die 3 Fields definiert

  717   ToolTip tp = new ToolTip ();

  718   string tooltip;

   719   int currentHighlightItemIndex;

Außer den beschriebenen Anpassungen kann man sich durchaus auch noch kräftig in der OnDrawItem-Methode austoben, hier kann man die Anzeige den eigenen Wünschen anpassen.

Man muss/sollte in seinen Workflows-Assemblies unbedingt das XmlnsDefinitionAttribute verwenden und einen für sich eindeutigen Namespace definieren. Mittels des Attributes wird die Auflösung der Namepsaces im Xml (Xoml-Workflow) und der CLR gesteuert. Kommt es zu Problemen beim Laden der Workflows im Designer, obwohl der TypeProvider überall mitgegeben wird, so kann man sich mit einem Trick weiterhelfen. Das XmlnsDefinitionAttribute wird auf den CLR-Namespace gesetzt, zum Beispiel xmlns:cv="clr-namespace:DE.CapeVision.Workflow.MainActivities;assembly=CapeVisionMainActivityLib". Viel mehr Details dazu unter XAML Namespaces and Namespace Mapping.

Sonntag, April 20, 2008

Wenig geblogged

Ich habe in den letzten 3-4 Wochen noch weniger geblogged als die anderen Wochen zuvor. Ich konnte mich einfach nicht richtig durchringen etwas zu machen. Zu dem habe ich neben der Arbeit dringend ein Buch lesen müssen und so verschwand die Zeit. Zu dem war ich extrem down, wegen meiner Ex, obwohl sich von dem Zustand gar nichts verändert hatte. Einzig das Hotelleben war mal wieder da, so dass da viel Zeit war. L Ich bin so deprimiert, zum einen weil keine Freundin da ist und zum anderen weil ich meine Ex-Freundin immer noch vermisse. Ich mache mir immer noch Vorwürfe, aber ich habe keine Schimmer, wie ich etwas ändern könnte. Ich bin so demotiviert. Nun werde ich mich richtig auf meine Ziele für dieses Jahr stürzen, die aber noch nicht abgestimmt sind. Wenn ich die so, wie ich sie entworfen habe unterschreibe, dann werde ich wohl nur noch arbeiten. Das hat dann auch den Charme, das keine Zeit mehr ist an etwas anderes zu denken.

Im Rahmen der Zielauswertung 2007 musste ich zwangsläufig über das vergangene Jahr nachdenken. Privat war das Jahr zweigeteilt, beruflich dagegen das ganze Jahr ok. Es ist spannender in einem kleinen Unternehmen, was man positiv und negativ auslegen kann, aber ich würde es eher positiv auslegen. Mir macht es Spaß die Abwechslung in den Projekt zu haben, mal schauen, was dieses Jahr spannendes noch auftaucht.

Mittwoch, April 16, 2008

Schleifen in Workflow Foundation

Letzte Woche bin ich auf einen Effekt bei der Workflow Foundation gestoßen, der mir so nicht bewusst war und auch nur bedingt sinnvoll aus meiner Sicht.

Der Effekt tritt auf, wenn man eine Schleife benutzt und Werte innerhalb der Schleife abfragen möchte. Konkret trat das Problem auf, sobald ein IF-Branch verwendet wurde. Versucht man auf Ativitäten innerhalb der Schleife zu zugreifen, so sind die Werte/Variablen nicht gesetzt. Nach etwas Recherche in dem WF Buch von AWP, ist die Ursache klar, der ExecutionContext wird ständig neu Instanziiert. Im ExecutionContext sind alle Informationen enthalten, welchen Status hat die Ativity und Ergebnisse. Wird eine Activity geschlossen, was der normale Zustand nach der Fertigstellung ist, so ist die Activity im Status Closed. Aus dem Status Closed kann man eine Activity nicht wieder zurück in den „Executing“-Status setzen. Aus diesem Grund wird für jeden Schleifendurchlauf ein neuer Context erzeugt. In dem Buch sind entsprechende Code-Beispiele enthalten, wie man selber Schleifen erzeugen kann. Um nun aber an die Daten heranzukommen, die im aktuellen Schleifendurchlauf vorhanden sind, wird bei Schleifen eine Property zur Verfügung gestellt mittels der eine Abfrage des Inhalts möglich ist, für while-Acitivty ist es die Dynamic-Activity-Property. Eine Abfrage könnte dann etwa so aussehen:

    7  (( MyDataActivity )

    8      (( WhileActivity ) this . GetActivityByName ( "MeineWhileSchleife" ))

     9          . DynamicActivity . GetActivityByName ( "MyDataActivity" )) . MyDataProperty

Sieht kompliziert aus, aber leider bekommt man von der GetActivityByName-Methode nur den Activity-Typ, so dass die Casts notwendig sind. Leider habe ich kein Beispiel parat, aber zumindest ein bisschen Code für eine eigene Schleife werde ich nach liefern.

Evtl. morgen dann noch ein Posts zu Trouble mit den Custom-Activity und das laden im Designer (Custom Designer).

Update
Hier ist noch ein kleiner Nachtrag, leider nicht der Code der While-Activity. Ich hatte leider vergessen die entsprechende Anweisung zu erstellen. Dafür kann ich als alternative aber einige Artikel bzw. Auszüge aus dem Buch Essential Windows Workflow Foundation bieten, auf die gestoßen bin. Die Informationen sind unter http://codeidol.com/other/essential-windows-workflow-foundation/ einzusehen.Die While-Activity ist als Beispiel im Artikel Activity Execution Context beschrieben.

Sonntag, April 06, 2008

DAS Tool für Software Configuration Management (SCM)

Wir stellen uns mal wieder, oder besser immer noch, die Frage was das beste SCM-Tool ist. Momentan nutzen wir SVN und TRAC als die SCM-Tools, momentan suchen wir nach weiteren Alternativen. Wir haben noch TFS und OTRS (Wikipedia) aufgetan, die ohne allzu hohe Kosten bei uns eingesetzt werden könnten. Bei TFS haben wir ein wenig Erfahrungen und lehnen es aus diversen Gründen ab.

OTRS ist interessant, es handelt sich dabei um ein Open Source Projekt. Die Installation unter Windows ist sehr einfach und geht recht schnell. Man sollte beachten, dass kein IIS installiert sein sollte. Bei der Installation werden Perl, MySql und Apache mit installiert. Die Installation ist wirklich sehr einfach und kann eigentlich jeder durchführen. Das große Problem war die Konfiguration. Ich bin da einfach nicht durchgestiegen. Vielleicht muss man das auch im ausgeruhten Zustand betrachten. Aber laut der Website gibt das Tool viel her. Zu der Anwendung gibt es auch eine Reihe von Erweiterungen, die man sich anschauen sollte.

Momentan noch mein Fazit, SVN und TRAC ist das BESTE. Übrigens bin ich am Freitag beim schnüffeln im Internet auf eine nette Präsentation zu TRAC und SVN gestoßen, http://www.prestonlee.com/tmp/Subversion%20for%20SCM.pdf.