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.

3 Kommentare:

Unknown hat gesagt…

Nur um zu unterstreichen, dass die Änderung in "GetRootActivity" sehr wichtig ist: Genau an dieser Stelle ist vor dem Anwenden dieser "Patches" bei mir der Workflow-Designer unmittelbar nach der Eingabe von Umlauten in irgendeine Property abgeschmiert ;-)

Jan Zieschang hat gesagt…

Danke für die Info svenni. Ich hatte zum Glück alle Stellen gesucht und angepasst.
Es ist echt schade, dass die WF-Designer diese UTF-8 Fehler im Original hat, das finden der problematischen Stellen ist leider sehr nervig und langwierig.
Viel Spass beim Designer nutzen.

Unknown hat gesagt…
Dieser Kommentar wurde vom Autor entfernt.