Die Microsoft Windows Workflow Foundation - Eine Einführung für Entwickler

Veröffentlicht: 22. Nov 2005

Von Dino Esposito

Dieser Artikel beschreibt die Technologien und Features der Microsoft Windows Workflow Foundation, die für alle Entwickler interessant ist, die workflow-gesteuerte Anwendungen für die .NET-Plattform von Microsoft erstellen möchten (39 gedruckte Seiten).

Hinweis Dieser Artikel bezieht sich auf die Version Windows Workflow Foundation Beta 1. Beachten Sie, dass Änderungen in der Endversion sehr wahrscheinlich sind.

* * *

Auf dieser Seite

Die Workflow-Unterstützung der Windows-Plattform Die Workflow-Unterstützung der Windows-Plattform
Erstellen eines ersten Workflows Erstellen eines ersten Workflows
Empfangen und Verarbeiten von Daten Empfangen und Verarbeiten von Daten
Die Workflow-Runtime Die Workflow-Runtime
Workflows und Aktivitäten Workflows und Aktivitäten
Entwickeln einer benutzerdefinierten Aktivität Entwickeln einer benutzerdefinierten Aktivität
Planen eines realistischeren Workflows Planen eines realistischeren Workflows
Schlussbemerkung Schlussbemerkung
Der Autor Der Autor

Die Workflow-Unterstützung der Windows-Plattform

Bei der Microsoft Windows Workflow Foundation (WWF) handelt es sich um ein erweiterbares Framework zum Entwickeln von Workflow-Lösungen auf der Windows-Plattform. Als Bestandteil des in Entwicklung befindlichen Microsoft WinFX stellt die Windows Workflow Foundation sowohl eine API als auch Tools zum Entwickeln und Ausführen von workflow-gesteuerten Anwendungen zur Verfügung. Die Windows Workflow Foundation bietet ein einheitliches Modell zum Erstellen von anwendungsübergreifenden End-to-End-Lösungen, die individuelle und systemgesteuerte Workflow-Elemente enthalten.

Die Windows Workflow Foundation ist ein umfassendes und universelles Workflow-Framework, das Erweiterbarkeit auf jeder Ebene ermöglichen soll. Lösungen auf Basis der Windows Workflow Foundation bestehen aus miteinander verbundenen Komponenten, die in Microsoft .NET-Code entwickelt sind und in einer Hostanwendung ausgeführt werden. Wie beim visuellen Erstellen von Webseiten in einer besonders angepassten Umgebung, können Sie die Schritte Ihres spezifischen Workflows in einem visuellen Designer definieren und den Workflow-Komponenten Code hinzufügen, um so Regeln und den Geschäftsprozess zu definieren.

Die Windows Workflow Foundation bietet ein Workflow-Modul, eine .NET-verwaltete API, Laufzeitdienste und einen visuellen Designer und Debugger, integriert in Microsoft Visual Studio 2005. Sie können die Windows Workflow Foundation zum Erstellen und Ausführen von Workflows verwenden, die Client und Server umfassen und in allen Ausprägungen von .NET-Anwendungen ausgeführt werden können.

Dieser Artikel bietet eine erste Einführung in die Windows Workflow Foundation und erläutert die Funktionsweise mit einigen aufeinander aufbauenden Beispielen.

Ein Workflow ist das Modell eines von Menschen oder Maschinen durchzuführenden Prozesses, der aus einer Abfolge von Aktivitäten besteht. Eine Aktivität ist ein Schritt innerhalb eines Workflows, die zu verwendende Einheit zur Ausführung, Wiederverwendung und Komposition eines Workflows. Die Abfolge von Aktivitäten beschreibt Regeln, Aktionen, Zustände und deren Abhängigkeiten. Nach dem Entwurf der Aktivitäten wird ein Windows Workflow Foundation-Workflow als .NET-Assembly kompiliert und in der Workflow-Runtime sowie der Common Language Runtime (CLR) ausgeführt.

 

Erstellen eines ersten Workflows

Die Windows Workflow Foundation besteht in erster Linie aus einer .NET-gesteuerten Laufzeitumgebung, die besondere, in einem Visual Studio-Designer entworfene und implementierte Objekte verarbeitet. Microsoft .NET Framework 2.0 ist zur Unterstützung der Windows Workflow Foundation erforderlich. Ein eigenes Installer-Paket fügt Visual Studio 2005 den Windows Workflow Foundation-Designer und eine Projektvorlage hinzu. Nach der Installation wird der Standardprojektliste in Visual Studio 2005 ein vollständig neuer Knoten hinzugefügt, wie in Abbildung 1 dargestellt.

Workflow-Projektvorlagen in Visual Studio 2005
Abbildung 1: Workflow-Projektvorlagen in Visual Studio 2005

Sie können zwischen verschiedenen Optionen wählen, jede steht für einen speziellen Typ einer Workflow-Anwendung. In Tabelle 1 wird eine (unvollständige) Liste von Workflow-Projektvorlagen aufgelistet.

Tabelle 1: Workflow-Projekttypen in Visual Studio 2005

Typ

Beschreibung

Sequential Workflow Console Application

Erstellt ein Projekt zum Entwerfen von Workflows, das einen vordefinierten sequenziellen Workflow und eine Hostanwendung zum Testen enthält.

Sequential Workflow Library

Erstellt ein Projekt zum Entwerfen eines sequenziellen Workflows als Bibliothek.

Workflow Activity Library

Erstellt ein Projekt zum Entwerfen einer Bibliothek von Aktivitäten, die später als Baustein in Workflow-Anwendungen wieder verwendet werden kann.

State Machine Console Application

Erstellt ein Projekt zum Entwerfen von Statusmechanismus-Workflows, das eine Konsolen-Hostanwendung enthält.

State Machine Workflow Library

Erstellt ein Projekt zum Entwerfen eines Statusmechanismus-Workflows als Bibliothek.

Empty Workflow (Leerer Workflow)

Erstellt ein leeres Projekt, das Workflows und Aktivitäten enthalten kann.

Die Windows Workflow Foundation unterstützt zwei grundlegende Workflowtypen: sequenzielle Workflows und Statusmechanismus-Workflows.

Ein sequenzieller Workflow ist für Vorgänge geeignet, die durch eine Reihe von aufeinander folgenden und einzeln ausgeführten Schritten beschrieben werden können. Sequenzielle Workflows werden jedoch nicht ausschließlich sequenziell ausgeführt. Sie können externe Ereignisse verarbeiten oder parallele Aufgaben starten und in diesen Fällen von einer exakten sequenziellen Ausführung etwas abweichen.

Ein Statusmechanismus-Workflow besteht aus einer Reihe von Status, Übergängen und Aktionen. Ein Status wird als Startstatus bezeichnet und kann, nach einem aufgetretenen Ereignis, in einen anderen Status übergehen. Der Statusmechanismus-Workflow kann durch einen definierten Endstatus beendet werden.

Nehmen wir an, Sie haben ein neues Projekt des Typs sequenzielle Workflowkonsolen-Anwendung erstellt. In diesem Fall enthält der Projektmappen-Explorer in Visual Studio 2005 zwei Dateien: workflow1.cs und, zunächst in der Anzeige ausgeblendet, workflow1.designer.cs. Diese beiden Dateien repräsentieren den zu erstellenden Workflow. Ein Windows Workflow Foundation-Workflow besteht aus der Workflow-Modelldatei und einer Klassencodedatei. Die Datei workflow1.cs enthält den Klassencode, in den Sie Ihre eigene Geschäftslogik für den Workflow schreiben können. Die Klassendatei workflow1.designer.cs enthält die Beschreibung der Aktivitätenabfolge. Diese Datei wird automatisch von Visual Studio 2005 verwaltet, ähnlich den Formularen in einem Microsoft Windows Forms-Projekt. Beim Hinzufügen von Aktivitäten zum Workflow wird die Designerklasse von Visual Studio 2005 automatisch mit Microsoft C#-Code aktualisiert, der programmtechnisch die Aktivitätenabfolge erstellt. In Analogie zu den Windows Forms entspricht ein Workflow einem Formular sowie die Aktivitäten den Steuerelementen.

Für das Aktivitätslayout können Sie eine andere Art der Persistenz auswählen - das XML-Format für Workflow-Code. Entfernen Sie zum Testen dieser Vorgehensweise die Datei workflow1.cs aus dem Projekt, und fügen Sie ein neues Workflow-Element hinzu, wie in Abbildung 2 dargestellt.

Hinzufügen eines sequenziellen Workflow-Elements mit Codetrennung
Abbildung 2: Hinzufügen eines sequenziellen Workflow-Elements mit Codetrennung

Anschließend enthält Ihr Projekt zwei Dateien: workflow1.xoml und workflow1.xoml.cs. Die erste enthält den XML-Workflow-Code als Darstellung des Workflow-Modells, bei der zweiten handelt es sich um eine Klassencodedatei, die den Quellcode und die Ereignishandler für den Workflow enthält. Wenn Sie auf die XOML-Datei doppelklicken, können Sie den visuellen Workflow-Designer in Aktion erleben (siehe Abbildung 3).

Die Auswahl von Markup oder Code für die Serialisierung des Workflow-Modells hat zur Laufzeit keine Auswirkungen - nach der Kompilierung des Workflows in eine Assembly sind beide im Ergebnis identisch.

Workflow-Anwendungen bestehen aus einer Mischung von ausführenden Aktivitäten (bspw. Senden oder Empfangen von Daten) und verbundenen Aktivitäten, bspw. IfElse oder While, die die Ausführung einer Reihe von untergeordneten Aktivitäten steuern. Ein Workflow kann komplexe End-to-End-Szenarios implementieren, z.B. Überprüfen von Dokumenten, Genehmigen von Aufträgen, Verwalten von IT-Benutzern, Informationsaustausch zwischen Partnern, jede Art von Assistenten oder Geschäftsanwendungen.

In Abbildung 3 wird ein beispielhafter (und sehr einfacher) Workflow dargestellt, der lediglich eine Aktivität enthält: den code1-Block.

Der Visual Studio 2005-Workflowdesigner
Abbildung 3: Der Visual Studio 2005-Workflowdesigner

Der Code-Block entspricht einer Instanz der Klasse Code und repräsentiert eine Aktivität innerhalb des Workflows, dessen Verhalten durch benutzerdefinierten Code beschrieben wird. Der Back-End-Code wird von Visual Studio 2005 durch einfaches Doppelklicken auf das ausgewählte Element im Designer erstellt. Dies entspricht dem von ASP.NET-Anwendungen oder anderen Visual Studio 2005-Projekten bekannten Programmierstil.

Wenn Sie auf die Aktivität doppelklicken, wird die Codedatei geöffnet, die bereits einen Stub für den Codehandler enthält.

private void code1_ExecuteCode(object sender, EventArgs e)
{
   // Some code here
}

Jede von Ihnen in den Codehandler eingegebene Anweisung wird ausgeführt, wenn die Workflow-Runtime beim Abarbeiten des Workflows den jeweiligen Aktivitätsblock bearbeitet. Lassen Sie uns einfach eine Willkommensmitteilung ausgeben.

private void code1_ExecuteCode(object sender, EventArgs e)
{
    Code c = (Code) sender;
    Console.WriteLine("Hello, from '{0}'.\nI'm an instance of the {1} class.", 
       c.ID, c.ToString());
}

Neben dem visuellen Layout besteht der Workflow aus dem folgenden Code, der in der Datei workflow1.xoml.cs gespeichert ist.

using System;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Collections;
using System.Drawing;
using System.Workflow.ComponentModel.Compiler;
using System.Workflow.ComponentModel.Serialization;
using System.Workflow.ComponentModel;
using System.Workflow.ComponentModel.Design;
using System.Workflow.Runtime;
using System.Workflow.Activities;
using System.Workflow.Activities.Rules;

namespace HelloWorldWorkflow
{
    public partial class Workflow1 : SequentialWorkflow
    {
        private void code1_ExecuteCode(object sender, EventArgs e)
        {
           Code c = (Code) sender;
           Console.WriteLine("Hello, from '{0}'.\nI'm an instance of the {1} class.", 
                              c.ID, c.ToString());
        }
    }
}

Das Attribut partial bezieht sich auf partielle Klassen, einem neuen Konzept im .NET Framework 2.0. Eine partielle Klasse ist eine Klasse, deren Definition über verschiedene Quelldateien verteilt vorliegen kann. Jede Quelldatei scheint dabei eine gewöhnliche vollständige Klassendefinition zu enthalten, mit Ausnahme der partiellen Erscheinung und der nicht vollständigen Bearbeitung der von der Klasse benötigten Logik. Der Kompiler verbindet die Definitionen einer partiellen Klasse zu einer vollständigen Klassendefinition, die anschließend kompiliert werden kann. Partielle Klassen haben nichts mit Objektorientierung zu tun, es handelt sich um eine an eine Assembly gebundene Möglichkeit auf Codeebene, das Verhalten einer Klasse innerhalb des Projekts zu erweitern. Im .NET Framework 2.0 sind partielle Klassen dazu gedacht, Visual Studio 2005 vom Einfügen automatisch generierten Codes in Quellcode-Dateien abzuhalten. Jeder in der Originalklasse fehlende Bindungscode wird durch die Runtime über partielle Klassen hinzugefügt.

Ein Workflow kann ausschließlich in der Workflow-Runtime der Windows Workflow Foundation ausgeführt werden, die Workflow-Runtime erfordert dazu eine externe Host-Anwendung, die einige Regeln beachtet. Zu Testzwecken fügt Visual Studio 2005 die Datei program.cs dem Projekt hinzu. Wie im Folgenden beschrieben, handelt es sich hier um eine einfache Konsolenanwendung.

class Program
{
    static AutoResetEvent waitHandle = new AutoResetEvent(false);

    static void Main(string[] args)
    {
        WorkflowRuntime workflowRuntime = new WorkflowRuntime();
        workflowRuntime.StartRuntime();

        workflowRuntime.WorkflowCompleted += OnWorkflowCompleted;

        Type type = typeof(HelloWorldWorkflow.Workflow1);
        workflowRuntime.StartWorkflow(type);

        waitHandle.WaitOne();
        workflowRuntime.StopRuntime();

        // A bit of feedback to the user    
         Console.WriteLine("");
         Console.WriteLine("");
         Console.WriteLine("==========================");
         Console.WriteLine("Press any key to exit.");
         Console.WriteLine("==========================");
         Console.ReadLine();
    }

    static void OnWorkflowCompleted(object sender, WorkflowCompletedEventArgs e)
    {
       waitHandle.Set();
    }
}

Zur Vereinfachung verwendet Visual Studio 2005 den Namen der Workflow-Klasse statisch innerhalb der Konsolenanwendung, im vorausgehenden Quellcode wird dies durch fett gedruckte Zeilen hervorgehoben. Damit die Konsolenanwendung nicht unmittelbar nach der vollständigen Ausführung beendet wird, können Sie etwa einen Console.ReadLine-Aufruf an das Ende der Main-Methode stellen. An dieser Stelle sollten Sie den Workflow erstellen und testen: Drücken Sie jetzt F5. Wenn alles ordnungsgemäß funktioniert, sollte die Bildschirmausgabe der Abbildung 4 entsprechen.

Von einer Konsolen-Hostanwendung ausgeführter Beispiel-Workflow
Abbildung 4: Von einer Konsolen-Hostanwendung ausgeführter Beispiel-Workflow

Auch das Debuggen eines Workflows ist einfach. Sie müssen dazu nur einen Haltepunkt setzen. Den Haltepunkt können Sie an eine beliebige Stelle in der Klassencodedatei des Workflows setzen (wie von Ihrem C#-Code gewohnt) oder - und dies ist wirklich interessant - direkt in der Designeransicht. Wählen Sie die Aktivität aus, bei der der Debugger starten soll, und drücken Sie F9 zum Setzen des Haltepunkts, wie in Abbildung 5 dargestellt.

Ein in der Designeransicht des Workflows gesetzter Haltepunkt
Abbildung 5: Ein in der Designeransicht des Workflows gesetzter Haltepunkt

Sobald im Code die Stelle mit dem gesetzten Haltepunkt erreicht wird, verzweigt Visual Studio 2005 zum Workflow-Debugger (siehe Abbildung 6). Von hier aus können Sie im Code die Aktivitäten im visuellen Designer schrittweise verfolgen, indem Sie (wie erwartet) F11 drücken.

Die Workflow-Anwendung in einer Debugsitzung
Abbildung 6: Die Workflow-Anwendung in einer Debugsitzung

 

Empfangen und Verarbeiten von Daten

Wir werden nun den Workflow bearbeiten, damit er beim Instanziieren Daten empfängt und verarbeitet. Es gibt zwei grundsätzliche Ansätze zum Empfangen von Daten beim Instanziieren eines Workflows: Parameter und Ereignisse. Wenn Sie sich für Parameter entscheiden, müssen Sie im visuellen Designer manuell eine Liste von Parameternamen und -typen definieren. Entscheiden Sie sich für Ereignisse, müssen Sie eine benutzerdefinierte Aktivität erstellen und hinzufügen, die an einem bestimmten Punkt im Workflow-Modell ähnlich einer externen Quelle Daten übermittelt. Den ereignisgesteuerten Ansatz werden wir in diesem Artikel später darstellen, zunächst werden wir jedoch Parameter verwenden.

Wie in Abbildung 7 dargestellt, wird im Bereich Properties (Eigenschaften) des Workflow1 eine Parameters-Auflistung angezeigt, die Sie zur Entwurfszeit mit Name/Wert-Paaren füllen.

Hinzufügen von Parametern zu einem Workflow
Abbildung 7: Hinzufügen von Parametern zu einem Workflow

Abbildung 8 zeigt den Parametereditor in Aktion. Erstellen Sie für jeden gewünschten Parameter einen neuen Eintrag, und geben Sie jeweils Namen, Typ und Richtung an.

Hinzufügen der Zeichenfolgen-Parameter "FirstName" und "LastName"
Abbildung 8: Hinzufügen der Zeichenfolgen-Parameter "FirstName" und "LastName"

Den Typ eines Parameters können Sie manuell eingeben oder im angepassten Objektbrowser auswählen. Bearbeiten Sie nach dem Schließen des Dialogfelds Workflow Parameters Editor (Editor für Workflow-Parameter) die Quellcodedatei, damit die neu definierten Parameter einbezogen werden. Üblicherweise fügen Sie zwei öffentliche Eigenschaften zur Bereitstellung des Inhalts der Parameters-Auflistung hinzu, wie im folgenden Quellcode dargestellt wird.

public partial class Workflow1 : SequentialWorkflow
{
   public string UserFirstName
   {
      get { return (string) Parameters["FirstName"].Value; }
      set { Parameters["FirstName"].Value = value; }
   }

   public string UserLastName
   {
      get { return (string) Parameters["LastName"].Value; }
      set { Parameters["LastName"].Value = value; }
   }   

   :
}

Die Verwendung von öffentlichen Eigenschaften ist eine grundsätzlich bewährte Programmierpraxis, die den Quellcode einfach und übersichtlich hält. Es ist jedoch keineswegs eine Notwendigkeit zum Verarbeiten von Parameterdaten. Ein direktes Aufrufen der Parameters-Auflistung des Workflows funktioniert ebenfalls. Wenn Sie Parameter durch öffentliche Eigenschaften wrappen, können Sie beliebige Namen für die Eigenschaften verwenden. Beachten Sie jedoch, dass Parameternamen in C# zwischen Groß- und Kleinschreibung unterscheiden.

Wie erfolgt nun tatsächlich eine Dateneingabe durch diese Parameter? Verantwortlich für diese Aufgabe ist die Hostanwendung. Es ist wichtig zu beachten, dass der Host alle Parameter bei der Initialisierung setzt, wenn der Workflow zur Ausführung in einen Runtime-Container geladen wird. Um diesen Punkt etwas ausführlicher zu betrachten, schreiben wir eine beispielhafte Hostanwendung auf Basis von Windows Forms. Die Beispielanwendung wird eine Reihe von Textfeldern enthalten, in die Benutzer Vor- und Nachnamen eingeben (siehe Abbildung 9) und an die Workflow-Codehandler übergeben können. Zum Verarbeiten der Parameter ändern wir den Codehandler in der folgenden Weise.

private void code1_ExecuteCode(object sender, EventArgs e)
{
    MessageBox.Show("Welcome, " + UserFirstName + " " + UserLastName);
}

Der wesentliche Punkt der beispielhaften Windows Forms-Hostanwendung besteht im Handler für das Klicken auf die Schaltfläche Start Workflow (Workflow starten).

Ein Workflow als Windows Forms-Hostanwendung
Abbildung 9: Ein Workflow als Windows Forms-Hostanwendung

Hier der vollständige Quellcode der Codebehind-Klasse des Formulars.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Reflection;
using System.Workflow.ComponentModel;
using System.Workflow.Runtime;
using System.Workflow.Runtime.Hosting;


namespace WinFormHost
{
    public partial class Form1 : Form
    {
        private WorkflowRuntime _wr = null;
        private string _workflowAssembly = "";
        private string _workflowTypeName = "";

        public Form1()
        {
            InitializeComponent();
            _workflowAssembly = "WorkflowWithParams";
            _workflowTypeName = "WorkflowWithParams.Workflow1";
            _wr = new WorkflowRuntime();
            _wr.StartRuntime();
        }


        private void btnStartWorkflow_Click(object sender, EventArgs e)
        {
            string assemblyName = _workflowAssembly;
            string typeName = _workflowTypeName;

            // Attempt to get type by fully-qualified name
            Assembly assembly = Assembly.Load(assemblyName);
            Type workflowType = assembly.GetType(typeName);

            Dictionary<string, object> parameters = new Dictionary<string, object>();
            parameters.Add("FirstName", txtFirstName.Text);
            parameters.Add("LastName", txtLastName.Text);

            // Start the workflow
            Guid instanceID = Guid.NewGuid();
            _wr.StartWorkflow(workflowType, instanceID, parameters);
        }
    }
}

Zum Füllen der Parameters-Auflistung müssen Sie ein generisches Dictionary-Objekt verwenden, das sich aus Zeichenfolgen und Objekten zusammensetzt. Der Name des Elements besteht aus einer Zeichenfolge, der enthaltene Wert wird als Objekt definiert. Sie fügen dem Dictionary-Objekt so viele Elemente hinzu, wie statische Parameter im Workflowmodell vorliegen - in diesem Fall FirstName und LastName. Die beiden Parameter übernehmen den in die Textfelder eingegebenen Inhalt.

Schließlich wird der Workflow durch Erstellen einer Instanz des definierten Modells ausgeführt. Die StartWorkflow-Methode des Runtime-Objekts hat eine Reihe von Überladungen. Die im Code verwendete Version übernimmt den Workflow-Typ, die Auflistung der Eingabeparameter und eine systemgenerierte GUID (Global Unique Identifier).

Für jeden Prozess benötigen Sie lediglich eine Instanz der Workflow-Runtime, die Verwendung mehrerer Instanzen für eine AppDomain ist nicht zulässig. Der beste Weg besteht in dem Erstellen der erforderlichen Instanz direkt im Konstruktor des Formulars. Ein Runtimeobjekt kann eine Reihe von Workflow-Instanzen überwachen. Die Runtime unterscheidet die Instanzen anhand ihrer GUID und empfängt private Daten für jede einzelne Instanz.

Der parametrisierte Workflow in Aktion, verwaltet von einer Windows Forms-Hostanwendung
Abbildung 10: Der parametrisierte Workflow in Aktion, verwaltet von einer Windows Forms-Hostanwendung

Zu reinen Lehrzwecken werfen wir in dieser Entwicklungsphase einen kurzen Blick auf den Designer- und Workflow Markup-Code. Im Folgenden handelt es sich um die Quellcodedatei workflow1.designer.cs.

public sealed partial class Workflow1 : SequentialWorkflow
{
   private void InitializeComponent()
   {
      ParameterDeclaration FirstName = new ParameterDeclaration();
      ParameterDeclaration LastName = new ParameterDeclaration();
      this.code1 = new System.Workflow.Activities.Code();
      // 
      // code1
      // 
      this.code1.ID = "code1";
      this.code1.ExecuteCode += new System.EventHandler(this.code1_ExecuteCode);
      // 
      // Workflow1
      // 
      this.Activities.Add(this.code1);
      this.DynamicUpdateCondition = null;
      this.ID = "Workflow1";
      FirstName.Direction = System.Workflow.ComponentModel.ParameterDirection.In;
      FirstName.Name = "FirstName";
      FirstName.Type = typeof(string);
      FirstName.Value = null;
      LastName.Direction = System.Workflow.ComponentModel.ParameterDirection.In;
      LastName.Name = "LastName";
      LastName.Type = typeof(string);
      LastName.Value = null;
      this.Parameters.Add(FirstName);
      this.Parameters.Add(LastName);
   }

   private Code code1;
}

Es folgt hier der entsprechende Workflow Markup-Inhalt.

<?Mapping XmlNamespace="ComponentModel" ClrNamespace="System.Workflow.ComponentModel" 
          Assembly="System.Workflow.ComponentModel" ?>
<?Mapping XmlNamespace="Compiler" ClrNamespace="System.Workflow.ComponentModel.Compiler" 
          Assembly="System.Workflow.ComponentModel" ?>
<?Mapping XmlNamespace="Activities" ClrNamespace="System.Workflow.Activities" 
          Assembly="System.Workflow.Activities" ?>
<?Mapping XmlNamespace="RuleConditions" ClrNamespace="System.Workflow.Activities.Rules" 
          Assembly="System.Workflow.Activities.Rules" ?>
<SequentialWorkflow x:Class="WorkflowWithParams.Workflow1" 
                    x:CompileWith="Workflow1.xoml.cs" 
                    ID="Workflow1" 
                    xmlns:x="Definition" xmlns="Activities">
    <SequentialWorkflow.Parameters>
        <wcm:ParameterDeclaration Name="FirstName" Type="System.String" Direction="In" 
                                  xmlns:wcm="ComponentModel" />
        <wcm:ParameterDeclaration Name="LastName" Type="System.String" Direction="In" 
                                  xmlns:wcm="ComponentModel" />
    </SequentialWorkflow.Parameters>
    <Code ExecuteCode="code1_ExecuteCode" ID="code1" />
</SequentialWorkflow>

Beachten Sie, dass alle statisch definierten Parameter in der Parameters-Auflistung explizit initialisiert und übergeben werden müssen, wenn eine Instanz des Workflows zur Ausführung erstellt wird.

 

Die Workflow-Runtime

Der Host interagiert mit der Windows Workflow Foundation über die Klasse WorkflowRuntime. Lassen Sie sich von der offensichtlichen Einfachheit des oben dargestellten Beispielhosts nicht bezüglich dieses wichtigen Punkts täuschen. Ein Host ist verantwortlich für eine Reihe weiterer wichtiger Aspekte: die Erzeugung eines oder mehrerer Prozesse, einer oder mehrerer AppDomains, das Marshalling von Aufrufen zwischen AppDomains und die Einrichtung von Isolationsmechanismen. Ein Host muss möglicherweise aus Skalierungsgründen eine Reihe von Prozessen erzeugen, um mehrere CPUs eines Systems zu nutzen, oder in einer Serverfarm eine Reihe von Workflow-Instanzen ausführen.

Es gibt noch weitere vom Host zu erledigende Aufgaben. Er kann zum Beispiel die Richtlinien verwalten, die anzuwenden sind, wenn ein Workflow lange pausieren muss, auf besondere Ereignisse warten und diese Ereignisse an einen Benutzer oder Administrator melden, Timeouts und Wiederholungen für jeden Workflow festlegen, Leistungsindikatoren darstellen und zu Debug- und Diagnosezwecken Protokolleinträge schreiben.

Hosts führen die meisten dieser Aufgaben üblicherweise durch vordefinierte, benutzerdefinierte Dienste aus, die zusammen mit dem Container beim Starten registriert werden. Der Beispielhost erfüllt keine dieser Aufgaben, er dient lediglich zum Starten von Workflow-Instanzen. In vielen üblichen Situationen ist dies eine akzeptable Vorgehensweise.

 

Workflows und Aktivitäten

Kommen wir zurück zur Visual Studio 2005-Toolbox und betrachten wir diese, wenn ein Workflow-Projekt ausgewählt ist. In der Toolbox (in Abbildung 11 dargestellt) werden alle Aktivitäten aufgelistet, die Sie zum Entwerfen der einzelnen Schritte und deren Beziehungen untereinander verwenden können, um so das Workflow-Modell zu entwickeln.

Bausteine eines Windows Workflow Foundation-Workflows
Abbildung 11: Bausteine eines Windows Workflow Foundation-Workflows

Tabelle 2 enthält kurze Beschreibungen zu jeder Aktivität sowie einige Szenarios, in der ihr Einsatz sinnvoll erscheint.

Tabelle 2: Bausteine der Windows Workflow Foundation

Aktivität

Beschreibung

Code

Ermöglicht das Hinzufügen von Microsoft Visual Basic .NET- oder C#-Code zum Workflow, um benutzerdefinierte Aktionen auszuführen. Der Code sollte den Workflow jedoch nicht durch Abhängigkeiten von externen Ressourcen blockieren, zum Beispiel einem Webdienst.

Compensate

Ermöglicht beim Auftreten eines Fehlers den Aufruf von Code zum Umkehren oder Ausgleichen von bereits durch den Workflow durchgeführten Operationen. Üblicherweise werden Sie dem Benutzer, der zuvor über eine erfolgreiche Operation unterrichtet wurde, eine E-Mail senden, wenn die Operation nun nach einem Fehler abgebrochen wurde.

ConditionedActivityGroup (CAG)

Ermöglicht dem Workflow, eine Reihe von bedingten untergeordneten Aktivitäten auszuführen, basierend auf spezifischen Kriterien für jede einzelne Aktivität, bis eine Komplettierungsbedingung für die CAG im Ganzen zutrifft. Untergeordnete Aktivitäten sind voneinander unabhängig und können parallel ausgeführt werden.

Delay

Ermöglicht die zeitliche Steuerung des Workflows durch das Einbringen von Verzögerungen. Für die Verzögerung können Sie ein Timeout angeben, damit der Workflow vor der weiteren Ausführung pausiert.

EventDriven

Stellt eine Sequenz von Aktivitäten dar, deren Ausführung durch ein Ereignis ausgelöst wird. Die erste untergeordnete Aktivität muss in der Lage sein, auf externe Ereignisse zu warten. Mögliche erste untergeordnete Aktivitäten sind EventSink und Delay. Delay wird in diesem Fall als Timeout verwendet.

EventSink

Ermöglicht dem Workflow das Empfangen von Daten eines mit der Workflow-Runtime registrierten Datenaustauschdienstes, wenn der Dienst das angegebene Ereignis auslöst.

ExceptionHandler

Ermöglicht das Verarbeiten eines von Ihnen definierten Ausnahmetyps. Die ExceptionHandler-Aktivität ist ein Wrapper für andere Aktivitäten, die tatsächlich alle erforderlichen Arbeiten nach dem Auftreten der definierten Ausnahme durchführen. Optional können Sie eine lokale Variable angeben, die die Ausnahme speichert und in Codebehind zur Verfügung stellt.

IfElse

Ermöglicht dem Workflow das bedingte Ausführen eines von mehreren alternativen Zweigen. Für jeden Zweig legen Sie eine Bedingung fest. Anschließend wird der erste Zweig, für den die Bedingung TRUE gilt, ausgeführt. Für den letzten Zweig müssen Sie keine Bedingung angeben, da er automatisch als "else"-Zweig behandelt wird.

InvokeMethod

Ermöglicht dem Workflow, eine Methode für eine Schnittstelle aufzurufen, um Nachrichten vom Workflow zu einem Datenaustauschdienst zu senden, der zusammen mit der Workflow-Runtime registriert wurde.

InvokeWebService

Ermöglicht dem Workflow das Aufrufen einer Webdienstmethode. Sie legen die zu verwendende Proxyklasse (unter Verwendung von WSDL) und den Namen der aufzurufenden Methode fest. Dabei werden synchrone und asynchrone Aufrufe unterstützt.

InvokeWorkflow (Workflowaufruf)

Ermöglicht dem Workflow das Aufrufen oder Starten eines anderen Workflows, dabei ist die Verschachtelungstiefe beliebig. So kann ein aufgerufener Workflow einen dritten Workflow aufrufen, dieser einen vierten, usw. Rekursive Aufrufe werden nicht unterstützt. Das unterstützte Aufrufmodell lautet Fire-and-Forget.

Listen

Ermöglicht dem Workflow, auf ein Ereignis von (möglicherweise) mehreren Ereignissen zu warten oder das Warten nach einem definierten Timeout-Intervall abzubrechen und in Abhängigkeit von den Ergebnissen zu anderen Aktivitäten zu verzweigen. Jedem Ausführungszweig können Sie eine oder mehrere ereignisgesteuerte Aktivitäten hinzufügen. Lediglich der erste Zweig, für den eine Bedingung zutrifft, wird ausgeführt. Alle anderen Zweige werden nicht ausgeführt.

Parallel

Ermöglicht dem Workflow, zwei oder mehrere Operationen völlig unabhängig voneinander auszuführen. Die Aktivität wartet vor der weiteren Ausführung auf das Beenden dieser Operationen.

Policy

Ermöglicht das Darstellen und Ausführen eines Regelsatzes. Diese Aktivität befindet sich nicht in der Toolbox. Zur Verwendung der Funktionalität müssen Sie eine benutzerdefinierte Aktivität erstellen und eine Ableitung verwenden.

Replicator

Ermöglicht dem Workflow, eine beliebige Anzahl von Instanzen einer bestehenden Aktivität zu erstellen und diese sequenziell oder parallel auszuführen.

SelectData)

Ermöglicht dem Workflow das Abfragen externer Daten durch eine Methode, die für ein externes Datenquellenobjekt definiert ist. Wenn die SelectData-Aktivität ausgelöst wird, wird die verknüpfte Methode innerhalb des Host-Threads ausgeführt. Der Rückgabewert dieser Methode wird an den Workflow übergeben.

Sequence (Sequenz)

Ermöglicht die Koordination der seriellen Ausführung einer Reihe von untergeordneten Aktivitäten. Die Sequenz wird abgeschlossen, wenn die letzte untergeordnete Aktivität abgeschlossen wird.

SetState (Status festlegen)

Ermöglicht einem Statusmechanismus-Workflow das Festlegen eines Statuswechsels.

State (Status)

Stellt einen Status innerhalb eines Statusmechanismus-Workflows dar.

StateInitialization (Statusinitialisierung)

Wird als Container für untergeordnete Aktivitäten innerhalb einer Statusaktivität verwendet, die nach dem Statuswechsel ausgeführt werden.

Suspend (Anhalten)

Unterbricht die Ausführung des Workflows, um so einen Eingriff beim Auftreten einer Fehlerbedingung zu ermöglichen. Beim Unterbrechen einer Workflow-Instanz wird ein Fehler protokolliert. Sie können einen Nachrichtentext festlegen und damit den Administrator bei der Fehlerdiagnose unterstützen. Alle mit der aktuellen Instanz verbundenen Statusinformationen werden gespeichert und nach der eventuellen Wiederaufnahme der Ausführung durch den Administrator wiederhergestellt.

Terminate (Beenden)

Ermöglicht im Falle einer nicht normalen Situation das unmittelbare Beenden der Workflow-Ausführung. Beim Aufrufen innerhalb einer parallel ausgeführten Aktivität werden alle Ausführungszweige unmittelbar beendet, unabhängig von deren aktuellem Status. Beim Beenden eines Workflows wird ein Fehler protokolliert, eine Nachricht unterstützt den Administrator bei der Diagnose.

Throw (Auslösen)

Ermöglicht das Auslösen einer Ausnahme des festgelegten Typs. Ein Verwenden dieser Aktivität entspricht einem Codehandler, der die Ausnahme in Benutzercode auslöst. Die Aktivität entspricht einem deklarativen Weg zum Auslösen einer .NET-Ausnahme.

TransactionalContext

Unter Transaktionskontext ist ein Block zur Gruppierung von Aktivitäten zu verstehen. In erster Linie für die Ausführung von Transaktionen, Kompensation und Ausnahmebehandlung verwendet, kann diese Aktivität auch synchronisiert sein. Durch Synchronisieren eines Transaktionskontextes können Sie gewährleisten, dass jeder Zugriff auf gemeinsam genutzte Daten ordnungsgemäß serialisiert wird.

UpdateData

Ermöglicht dem Workflow das Aktualisieren eines Datenspeichers durch eine Methode, die für ein externes Datenquellenobjekt definiert ist. Wird die UpdateData-Aktivität ausgelöst, wird die verknüpfte Methode innerhalb des Host-Threads ausgeführt.

WaitForData

Ermöglicht dem Workflow das Empfangen von Informationen von einem externen Datenquellenobjekt. Die Aktivität wird ausgelöst, wenn der Status der gebundenen Datenquelle durch eingehende Daten geändert wird. Eingehende Daten werden durch einen gebundenen Datenquellendienst empfangen.

WaitForQuery

Ermöglicht einer externen Anwendung eine Datenabfrage an den Workflow. Diese Aktivität wartet auf das Empfangen einer Abfrage durch den Host. Abfragen von externen Anwendungen werden an den Workflow durch eine Methode des gebundenen Datenquellendienstes übermittelt.

WebServiceReceive

Ermöglicht einem Workflow, der als Webdienst offen gelegt ist, den Empfang einer Webdienstanforderung.

WebServiceResponse

Ermöglicht einem Workflow, der als Webdienst offen gelegt ist, die Antwort auf eine Webdienstanforderung.

While

Ermöglicht dem Workflow das Ausführen einer oder mehrerer Aktivitäten beim Zutreffen einer Bedingung. Vor jeder Iteration wird die Bedingung ausgewertet. Falls die Bedingung zutrifft, werden alle untergeordneten Aktivitäten ausgeführt, andernfalls wird die Aktivität abgeschlossen. Sie können wahlweise eine deklarative Bedingung oder eine Codebedingung angeben.

Aktivitäten entsprechen dem deklarativen Ansatz zur Workflow-Programmierung mit der Windows Workflow Foundation. Mittels Aktivitäten definieren Sie Ihr Workflow-Modell zur Entwurfszeit und weisen den Eigenschaften der einzelnen Aktivitäten Werte zu. Das abschließende Ergebnis wird als XML-Code in eine Workflow Markup-Datei mit XOML-Datei-Erweiterung gespeichert, wenn Sie sich für ein Workflow-Element mit Codetrennung entschieden haben. Andernfalls wird das von Ihnen definierte Modell in einer vom Designer erstellten C#- oder Visual Basic .NET-Klassendatei gespeichert, und zwar in Form einer Sequenz von Aufrufen an das Workflow-Objektmodell. Der erste Ansatz ähnelt ASP.NET-Seiten, der zweite entspricht der Funktionsweise von Windows Forms-Anwendungen.

Visual Studio 2005 verbirgt die meisten Unterschiede zwischen den beiden Ansätzen. Den Workflow entwerfen Sie in jedem Fall visuell, anschließend speichert Visual Studio 2005 Ihre Arbeit in einem von zwei verschiedenen Formaten. Wenn Sie eine reine Codelösung (keine XOML-Datei und Codetrennung) bevorzugen, haben Sie die Möglichkeit, den vom Designer erstellten Code zu optimieren und flexibler zu gestalten. So können Sie etwa vordefinierte Parameterwerte aus einer Konfigurationsdatei oder Datenbank auslesen. Wenn Sie Workflow Markup und Codetrennung bevorzugen, erreichen Sie eine elegante Trennung von Workflow-Code und dessen Modell.

Kann das Workflow-Modell programmtechnisch bearbeitet werden? Zur Entwurfszeit können Sie mit einem Workflow programmtechnisch alles tun, was in Visual Studio möglich ist. Da zur Laufzeit dynamische Aktualisierungen der Auflistung von Aktivitäten ebenfalls möglich sind, lässt sich eine laufende Instanz ebenfalls bearbeiten. Dynamische Änderungen können durch zur Entwurfszeit noch nicht bekannte angepasste Geschäftsvorgänge notwendig werden, oder auch durch Anforderungen an eine Geschäftslogik, die den Geschäftsprozess ändert und anschließend abschließt. In jedem Fall sollten die Anpassungen begrenzt bleiben: Besser optimieren statt neu entwerfen.

Dynamische Aktualisierungen zielen auf die einzelne Instanz eines Workflows im Kontext einer Anwendung. Zukünftige Instanzen desselben Workflows werden durch die Änderungen nicht betroffen. Dynamische Aktualisierungen einer Workflow-Instanz können innerhalb dieser Instanz durchgeführt werden, jedoch auch von außen durch Ihren Code in der Anwendung.

Das Windows Workflow Foundation-Framework unterstützt Webdienst-Interoperabilität. Dazu gehört die Option zur Offenlegung eines Workflows als Webdienst für ASP.NET-Clients oder auch für andere Workflows. Die Windows Workflow Foundation unterstützt die Veröffentlichung eines Workflows als ASP.NET-Webdienst auf einem Webserver oder einer Serverfarm unter Microsoft IIS 6.0 und ASP.NET.

Die Aktivitäts-Auflistung des Windows Workflow Foundation-Frameworks enthält die Aktivitäten WebServiceReceive und WebServiceResponse, die ein Verwenden des Workflows als Endpunkte eines Webdiensts ermöglichen.

Um als Webdienst zur Verfügung gestellt zu werden, muss der Workflow eine WebServiceReceive-Aktivität zum Empfangen der eingehenden Clientaufrufe enthalten. Über einen Befehl im Kontextmenü können Sie den Workflow als Webdienst veröffentlichen (siehe Abbildung 12).

Veröffentlichen eines Workflows als Webdienst
Abbildung 12: Veröffentlichen eines Workflows als Webdienst

 

Entwickeln einer benutzerdefinierten Aktivität

Der entscheidende Punkt zur Erweiterbarkeit der Windows Workflow Foundation besteht im Entwickeln von benutzerdefinierten Aktivitäten, da Sie auf diese Weise die Liste der Bausteine zum Entwerfen von Workflow-Modellen erweitern können.

Wenden wir uns der internen Architektur einer Aktivität zu, indem wir eine benutzerdefinierte Aktivität zum Senden von E-Mails entwickeln. Die Windows Workflow Foundation enthält eine fertige Visual Studio 2005-Vorlage für benutzerdefinierte Aktivitäten. Sie hat die Bezeichnung "Workflow Activity Library". Die Vorlage erstellt eine C#-Datei, die Sie beliebig umbenennen können, etwa in SendMailActivity. Eine Aktivität besteht aus einer einfachen Klasse, die von einer übergeordneten Klasse erbt. Ihre Aktivität können Sie von einer beliebigen vorhandenen Aktivität ableiten: einer vordefinierten Aktivität oder auch einer von Ihnen erstellten oder von einem Drittanbieter erworbenen Aktivität. Die übergeordnete Klasse fügt der neuen Komponente ein vordefiniertes Verhalten hinzu. Zum vollständig neuen Erstellen einer Aktivität leiten Sie diese von Activity ab. Das folgende Codebeispiel stellt das Gerüst der neuen Klasse dar.

public partial class SendMailActivity : System.Workflow.ComponentModel.Activity
{
   public SendMailActivity()
   {
      InitializeComponent();
   }

   protected override Status Execute(ActivityExecutionContext context)
   {
       : 
   }
}

Wie Sie sicher ahnen, ist die Execute-Methode das Herz der Komponente: der Ort, an dem die wesentlichen Aufgaben der Komponente ausgeführt werden.

Nach Abschluss der Entwicklung wird in der Toolbox eine Aktivität angezeigt, die bereit für Drag & Drop-Operationen in neuen Workflow-Anwendungen ist. Eine Liste von Eigenschaften ist zwar keine Voraussetzung, eine Aktivität ohne Eigenschaften jedoch nicht sehr nützlich. Wählen Sie zum Hinzufügen von Eigenschaften die zu entwickelnde Aktivität im Designer aus und klicken Sie auf den Eintrag Activity Properties (Aktivitätseigenschaften) im Bereich Properties (Eigenschaften) (siehe Abbildung 13).

Hinzufügen von Eigenschaften zu einer benutzerdefinierten Aktivität
Abbildung 13: Hinzufügen von Eigenschaften zu einer benutzerdefinierten Aktivität

Das Hinzufügen von Eigenschaften zu einer Aktivität unterscheidet sich nicht wesentlich vom Hinzufügen von Parametern zu einem Workflow. Sie müssen lediglich einen Namen und die Attribute für jede gewünschte Eigenschaft definieren. Abbildung 14 zeigt das Hinzufügen einer Eigenschaft To (An) zur SendMail-Aktivität.

Die zur SendMail-Aktivität hinzugefügte To-Eigenschaft
Abbildung 14: Die zur SendMail-Aktivität hinzugefügte To-Eigenschaft

Abschließend fügen wir weitere Eigenschaften hinzu: From (Von), Subject (Betreff), Body (Text) und Host. Damit können Benutzer die zu sendenden E-Mail-Nachrichten vollständig konfigurieren. Beim Hinzufügen von Eigenschaften bearbeitet der Assistent die C#-Codebehind-Datei, die die Logik der Aktivität enthält.

Der letzte Schritt besteht aus dem Erweitern der Execute-Methode, damit diese beim Ausführen der Aktivität E-Mails senden kann.

protected override Status Execute(ActivityExecutionContext context)
{
    MailAddress toAddress = new MailAddress(To);
    MailAddress fromAddress = new MailAddress(From);

    MailAddressCollection addresses = new MailAddressCollection();
    addresses.Add(toAddress);

    MailMessage msg = new MailMessage(fromAddress, toAddress);
    msg.Subject = Subject;
    msg.Body = Body;

    SmtpClient mail = new SmtpClient(Host);
    mail.Send(msg);
    return Status.Closed;
}

Wenn Sie das Aktivitätsprojekt innerhalb einer Workflow-Lösung entwickeln, findet das Workflow-Dokument automatisch die in der Toolbox neu angezeigte Aktivität (wie in Abbildung 15 dargestellt). Anderenfalls müssen Sie die Aktivität durch Klicken mit der rechten Maustaste auf die Toolbox hinzufügen.

Die SendMail-Aktivität in der Toolbox
Abbildung 15: Die SendMail-Aktivität in der Toolbox

Abbildung 16 zeigt, dass die SendMail-Aktivität tatsächlich funktioniert.

Die SendMail-Aktivität in Aktion
Abbildung 16: Die SendMail-Aktivität in Aktion

 

Planen eines realistischeren Workflows

Wir werden jetzt einige der in Tabelle 2 aufgelisteten Aktivitäten kombinieren, um eine etwas realistischere Aufgabe zu lösen. Wir stellen uns eine Geschäftsanwendung vor, in der eine Bestellung mehrere Status erreichen muss, bevor sie abgeschlossen werden kann. In einem typischen Szenario werden Regeln angewendet, die alle möglichen Ereignisse für eine Bestellung festlegen, jeweils in Abhängigkeit vom aktuellen Status. Eine offene Bestellung kann beispielsweise verarbeitet oder aktualisiert werden, jedoch nicht verworfen oder ausgeliefert.

Beim Eintreten eines Ereignisses ändert ein Statusmechanismus-Workflow den Status einer Bestellung. Tritt etwa das BeingProcessed-Ereignis für eine offene Bestellung ein, ändert der Statusmechanismus-Workflow den Status der Bestellung entsprechend. Abbildung 17 stellt das Diagramm für einen Statusmechanismus-Workflow einer Bestellung dar.

Ein Beispielschema für einen Statusmechanismus, der Bestellungen steuert
Abbildung 17: Ein Beispielschema für einen Statusmechanismus, der Bestellungen steuert

Beginnen wir mit dem Erstellen eines Statusmechanismus-Workflows. Wir verwenden die State-Aktivität zum Modellieren möglicher Status einer Bestellung. Anschließend werden die für jeden Status möglichen Ereignisse durch Verwenden von EventDriven-Aktivitäten festgelegt. Ein benutzerdefinierter Dienst empfängt externe Ereignisse und ändert den Status der Bestellung. Zum Durchführen der Statusänderung wird eine SetState-Aktivität verwendet. Nach der Modellierung des Workflows wird er in einer Windows Forms-Hostanwendung auf seine Funktionalität überprüft.

Ein Workflow kommuniziert über einen speziell für diesen Zweck eingerichteten Dienst mit seiner Außenwelt. Der Dienst löst Ereignisse aus, die mit ereignisgesteuerten Aktivitäten innerhalb des Workflows verknüpft sind. Dementsprechend stellt der Dienst öffentliche Methoden für den Workflow bereit, um Daten abzurufen oder an den Host zu senden. Methoden und Ereignisse werden in einer Schnittstelle definiert. Diese Schnittstelle ist bekannt als Datenaustauschdienst. Sie benötigen diesen Dienst bei jeder Interaktion des Workflows mit externen Komponenten, in ein- und ausgehender Richtung.

Ein Datenaustauschdienst ist eine normale .NET-Klassenbibliothek, die zumindest eine Schnittstellendefinition und eine Klasse zur Implementierung dieser Schnittstelle enthält. Die Schnittstelle ist speziell an die von Ihnen vorgesehenen Aufgaben angepasst. Im Beispielfall, der Darstellung der Lebensdauer einer Bestellung als Statusmechanismus, besteht die Schnittstelle aus fünf Ereignissen.

[DataExchangeService]
public interface IOrderService
{
    event EventHandler<OrderEventArgs> OrderCreated;
    event EventHandler<OrderEventArgs> OrderShipped;
    event EventHandler<OrderEventArgs> OrderUpdated;
    event EventHandler<OrderEventArgs> OrderProcessed;
    event EventHandler<OrderEventArgs> OrderCanceled;
}

Das [DataExchangeService]-Attribut markiert IOrderService als Datenaustauschdienst-Schnittstelle, damit die Workflow-Runtime diese zum Datenaustausch mit der Workflow-Instanz verwendet. In diesem Fall sendet der Host Daten an die Workflow-Instanz, indem er Ereignisse für eine Reihe von EventDriven-Aktivitäten auslöst. Sofern erforderlich, können Methoden in der IOrderService-Schnittstelle aus der Workflow-Instanz heraus durch die InvokeMethod-Aktivität aufgerufen werden.

Die Ereignisdeklaration in der Schnittstelle ist generisch und damit ein neues aufregendes Feature von .NET Framework 2.0. Die EventHandler-Klasse ist ein Delegat, der den Funktionsprototyp zum Verarbeiten des Ereignisses darstellt. In .NET Framework 1.x wurde EventHandler wie folgt definiert.

void EventHandler(object sender, EventArgs e)

Damit das Ereignis eine benutzerdefinierte Datenstruktur (wie OrderEventArgs) übergeben kann, müssen Sie einen neuen Delegaten erstellen und als Ersatz für EventHandler verwenden. Im Folgenden ein Beispiel.

delegate void OrderEventHandler(object sender, OrderEventArgs e)

Dieses Muster kann auch in .NET Framework 2.0 verwendet werden. Durch die Einführung generischer Klassen in .NET Framework 2.0 können Sie dasselbe Ergebnis ohne ausdrückliche Definition (und Instantiierung) einer neuen Delegatenklasse erreichen. Sie verwenden die generische Version des EventHandler<T>-Delegaten, in der der Ereignisdatentyp ein Parameter ist.

Ereignisse übergeben Clientdaten vom Typ OrderEventArgs, eine benutzerdefinierte Klasse, die von der Windows Workflow Foundation-Klasse WorkflowMessageEventArgs abgeleitet ist, die in derselben Assembly auf folgende Weise definiert ist.

[Serializable]
public class OrderEventArgs : WorkflowMessageEventArgs
{
    private string _orderId;

    public OrderEventArgs(Guid instanceId, string orderId) : base(instanceId)
    {
        _orderId = orderId;
    }

    public string OrderId
    {
       get { return _orderId; }
       set { _orderId = value; }
    }
}

Im nächsten Schritt definieren Sie eine Klasse, die die Schnittstelle implementiert. Die Klasse wird über so viele öffentliche Methoden verfügen, wie auslösbare Ereignisse in der Schnittstelle vorhanden sind.

public class OrderService : IOrderService
{
    public OrderService()
    {
    }

    public void RaiseOrderCreatedEvent(string orderId, Guid instanceId)
    {
        if (OrderCreated != null)
            OrderCreated(null, new OrderEventArgs(instanceId, orderId));
    }

    public void RaiseOrderShippedEvent(string orderId, Guid instanceId)
    {
        if (OrderShipped != null)
            OrderShipped(null, new OrderEventArgs(instanceId, orderId));
    }

    public void RaiseOrderUpdatedEvent(string orderId, Guid instanceId)
    {
        if (OrderUpdated != null)
            OrderUpdated(null, new OrderEventArgs(instanceId, orderId));
    }

    public void RaiseOrderProcessedEvent(string orderId, Guid instanceId)
    {
        if (OrderProcessed != null)
            OrderProcessed(null, new OrderEventArgs(instanceId, orderId));
    }
        
    public void RaiseOrderCanceledEvent(string orderId, Guid instanceId)
    {
        if (OrderCanceled != null)
            OrderCanceled(null, new OrderEventArgs(instanceId, orderId));
    }

    public event EventHandler<OrderEventArgs> OrderCreated;
    public event EventHandler<OrderEventArgs> OrderShipped;
    public event EventHandler<OrderEventArgs> OrderUpdated;
    public event EventHandler<OrderEventArgs> OrderProcessed;
    public event EventHandler<OrderEventArgs> OrderCanceled;
}

Sie können jetzt die Assembly mit dem Bestellungsdienst kompilieren und zum Statusmechanismus-Workflowprojekt zurückkehren. Im nächsten Schritt werden Sie dem Workflow-Projekt eine Referenz auf die neu erstellte Assembly hinzufügen. Anschließend fügen Sie vier State-Aktivitäten mit folgenden Bezeichnungen hinzu: WaitingForOrderState, OrderOpenState, OrderProcessedState, OrderCompletedState.

In Tabelle 3 wird das Statusdiagramm des Workflows dargestellt. Jeder Status verfügt über mehrere Ereignisse, die eine Statusänderung veranlassen können.

Tabelle 3: Ein Beispielstatusmechanismus für Bestellungen

Status

Unterstützte Ereignisse

Übergang zu

WaitingForOrderState

OrderCreated

OrderOpenState

OrderOpenState

OrderUpdated

OrderOpenState


OrderProcessed

OrderProcessedState

OrderProcessedState

OrderUpdated

OrderOpenState


OrderCanceled

Terminate-Aktivität


OrderShipped

OrderCompletedState

OrderCompletedState



Zum Implementieren des Diagramms fügen Sie jeder State-Aktivität die der Anzahl der unterstützten Ereignisse in der Tabelle entsprechenden EventDriven-Blöcke hinzu. Die State-Aktivität mit der Bezeichnung WaitingForOrderState enthält beispielsweise eine einzelne EventDriven-Aktivität mit der möglichen Bezeichnung OrderCreatedEvent (der Name ist beliebig). Wie in Abbildung 18 dargestellt wird, sind in die EventDriven-Aktivität eine EventSink- und eine SetState-Aktivität eingebettet, um das externe Ereignis zu empfangen und den Status zu ändern.

Ein Blick in die EventDriven-Aktivität von OrderCreatedEvent
Abbildung 18: Ein Blick in die EventDriven-Aktivität von OrderCreatedEvent

Im Bereich Properties (Eigenschaften) der EventSink-Aktivität wählen Sie den gewünschten Datenaustauschdienst - in diesem Fall die Schnittstelle IOrderService - und den zu verknüpfenden Ereignisnamen aus. Wenn Sie im Bereich Properties (Eigenschaften) der EventSink-Aktivität auf den Eintrag InterfaceType (Schnittstellentyp) klicken, stellt Visual Studio 2005 die Liste der im Projekt verfügbaren Datenaustauschdienste dar. Nach dem Auswählen des Dienstes stellt die EventName-Eigenschaft die Liste der durch den Dienst offen gelegten Ereignisse dar. Sie wählen anschließend das gewünschte Ereignis aus. Wählen Sie für die OrderCreatedEvent-Aktivität das Ereignis OrderCreated aus.

Die SetState-Aktivität wechselt den Status zu dem neuen in der Eigenschaft TargetState angegebenen Status. Die SetState-Aktivität in Abbildung 18 wurde auf OrderOpenState festgelegt.

Wiederholen Sie die angegebenen Schritte für alle in Tabelle 3 aufgelisteten Status und Ereignisse. Im Ergebnis sollte Ihr Workflow der Abbildung 19 entsprechen.

Die Endversion des Bestellungsstatus-Mechanismus
Abbildung 19: Die Endversion des Bestellungsstatus-Mechanismus

Der letzte Schritt beinhaltet das Erstellen einer Windows Forms-Anwendung zum Testen des Workflows. Die Benutzeroberfläche enthält eine Listenansicht zum Verfolgen aller offenen Bestellungen sowie ein Textfeld und eine Schaltfläche zum Erstellen neuer Bestellungen. Weitere Schaltflächen werden zum Aktualisieren, Verarbeiten und Abschließen von Bestellungen verwendet.

Der Statusmechanismus-Workflow wird im Form_Load-Ereignis initialisiert. Die Initialisierung eines Statusmechanismus-Workflows ist etwas komplexer als die eines sequenziellen Workflows, besonders dann, wenn Sie alle Statusänderungen protokollieren möchten. Das folgende Codebeispiel erläutert die Initialisierung der Workflow-Runtime.

private void StartWorkflowRuntime()
{
   // Create a new Workflow Runtime for this application
   _runtime = new WorkflowRuntime();

   // Register event handlers for the WorkflowRuntime object
   _runtime.WorkflowTerminated += new 
          EventHandler<WorkflowTerminatedEventArgs>(WorkflowRuntime_WorkflowTerminated);
   _runtime.WorkflowCompleted += new 
          EventHandler<WorkflowCompletedEventArgs>(WorkflowRuntime_WorkflowCompleted);

    // Create a new instance of the StateMachineTrackingService class  
    _stateMachineTrackingService = new StateMachineTrackingService(_runtime);

    // Start the workflow runtime 
    _runtime.StartRuntime();

    // Add a new instance of the OrderService to the runtime
    _orderService = new OrderService();
    _runtime.AddService(_orderService);
}

StateMachineTrackingService wird auf der obersten Ebene der Runtime ausgeführt und erweitert diese um die Möglichkeit, die Statusänderungen im Workflow zu verfolgen. Eine Instanz des Datenaustauschdienstes wird der Runtime ebenfalls hinzugefügt.

Wenn der Benutzer eine neue Bestellung durch einen Mausklick erstellt, wird der folgende Code ausgeführt.

private Guid StartOrderWorkflow(string orderID)
{
   // Create a new GUID for the WorkflowInstanceId
   Guid instanceID = Guid.NewGuid();

   // Load the OrderWorkflows assembly
   Assembly asm = Assembly.Load("OrderWorkflows");

   // Get a type reference to the OrderWorkflows.Workflow1 class
   Type workflowType = asm.GetType("OrderWorkflows.Workflow1");

   // Start a new instance of the state machine with state tracking support
   StateMachineInstance stateMachine = 
          _stateMachineTrackingService.RegisterInstance(workflowType, instanceID);
   stateMachine.StateChanged += new 
          EventHandler<ActivityEventArgs>(StateMachine_StateChanged);
   stateMachine.StartWorkflow();
   _stateMachineInstances.Add(instanceID.ToString(), stateMachine);

   // Return the workflow GUID 
   return instanceID;
}

Der Code instanziiert zunächst die Workflow-Instanz und registriert einen Ereignishandler für die Statusänderungen. Beachten Sie, dass die Verwendung von .NET Reflection zum Abrufen von Typinformationen nicht zwingend erforderlich ist, jedoch wesentlich mehr Flexibilität bietet. Der gute alte typeof-Operator kann ebenfalls eingesetzt werden, um den Typ der Workflow-Instanz an die Workflow-Runtime zu kommunizieren.

Abbildung 20 zeigt die Beispielanwendung in Aktion. Die Schaltflächen werden je nach Status der ausgewählten Workflow-Instanz aktiviert.

Der in einer Windows Forms-Anwendung gehostete Statusmechanismus-Workflow
Abbildung 20: Der in einer Windows Forms-Anwendung gehostete Statusmechanismus-Workflow

Klickt der Benutzer auf eine der vorhandenen Schaltflächen, wird das zugehörige Ereignis in der Kommunikationsschnittstelle ausgelöst und vom EventSink des Workflows empfangen. Ein Klicken auf die Order Processed-Schaltfläche einer Workflow-Instanz mit Status "Open" wird etwa wie folgt verarbeitet.

private void btnOrderEvent_Click(object sender, EventArgs e)
{
   // Get the name of the clicked button 
   string buttonName = ((Button)sender).Name;

   // Get the GUID of the selected order
   Guid instanceID = GetSelectedWorkflowInstanceID();

   // Get the ID of the selected order
   string orderID = GetSelectedOrderID();

   // Disable buttons before proceeding
   DisableButtons();

   // Determines what to do based on the name of the clicked button
   switch(buttonName)
   {
      // Raise an OrderShipped event using the Order Local Service
      case "btnOrderShipped":
         _orderService.RaiseOrderShippedEvent(orderID, instanceID);
     break;

      // Raise an OrderUpdated event using the Order Local Service
      case "btnOrderUpdated":
         _orderService.RaiseOrderUpdatedEvent(orderID, instanceID);
         break;

      // Raise an OrderCanceled event using the Order Local Service
      case "btnOrderCanceled":
         _orderService.RaiseOrderCanceledEvent(orderID, instanceID);
         break;

      // Raise an OrderProcessed event using the Order Local Service
      case "btnOrderProcessed":
         _orderService.RaiseOrderProcessedEvent(orderID, instanceID);
         break;
     }
}

Das im Workflow ausgelöste Ereignis wird in Abbildung 21 von der EventDriven-Aktivität empfangen.

Der "EventDriven"-Block des Statusmechanismus zum Verarbeiten des "OrderProcessed"-Ereignisses
Abbildung 21: Der "EventDriven"-Block des Statusmechanismus zum Verarbeiten des "OrderProcessed"-Ereignisses

Die EventSink-Aktivität empfängt und verarbeitet das Ereignis, indem der Status auf den in der SetState-Aktivität festgelegten Status geändert wird. Der Statuswechsel im Workflow wird vom zusätzlichen Statusüberwachungsdienst festgestellt und durch das StateChanged-Ereignis an den Host gemeldet, wie in den zuvor beschriebenen Codebeispielen dargestellt.

Den vollständigen Quellcode aller in diesem Artikel behandelten Beispiele (und einiges mehr zum Thema Workflow) können Sie unter der Adresse https://msdn.microsoft.com/workflow finden.

 

Schlussbemerkung

Entworfen als Workflow-Framework für alle neuen und vorhandenen Microsoft-Produkte, bietet die Windows Workflow Foundation die Leistung von WinFX und die einfache Bedienbarkeit von Visual Studio 2005 für alle Entwickler, die workflow-gesteuerte Anwendungen für die .NET-Plattform erstellen möchten.

Der größte Vorteil der Windows Workflow Foundation liegt in einem einheitlichen Workflow-Modell und einer Reihe von Tools, die viele proprietäre Bibliotheken ersetzen. In dieser Hinsicht ist die Windows Workflow Foundation auch für die Hersteller heutiger Workflow-Produkte von großer Bedeutung, da die Windows Workflow Foundation sie vom Warten Ihres Low-Level-Codes befreien und den Fokus auf höherwertige Aufgaben verschieben kann.

Die Windows Workflow Foundation stellt eine Workflow-Technologie bereit, die auf eine Vielzahl von Anwendungsbereichen und Anforderungen zielt. Die Windows Workflow Foundation ist ein umfassendes Framework, das in Hinsicht auf Erweiterungsmöglichkeiten auf jeder Ebene entwickelt wurde. Die besten Beispiele für diesen Grad an Erweiterbarkeit sind benutzerdefinierte Aktivitäten und austauschbare Runtime-Dienste. Mit benutzerdefinierten Aktivitäten können Sie die Liste der Bausteine zum Entwerfen von Workflows erweitern. Da sich Runtime-Dienste beispielsweise zur Persistenz oder Protokollierung zur Anpassung an die Anwendungsumgebung ändern lassen, kann man Microsoft SQL Server oder Datenbanken anderer Hersteller beibehalten.

Die Visual Studio 2005-Erweiterungen für die Windows Workflow Foundation ermöglichen eine visuelle Modellierung der Workflows, jedoch auch den direkten Zugriff auf den Quellcode.

Der visuelle Designer kann auch in anderen Designumgebungen gehostet werden. So können andere Hersteller die visuellen Modellierungsmöglichkeiten in ihre eigenen Umgebungen einbetten und eine der Anwendung entsprechende Benutzerfreundlichkeit bieten.

Dieser Artikel hat lediglich an der Oberfläche aller Windows Workflow Foundation-Technologien und -Features gekratzt, einen Überblick über die Arbeitsweise und Interna gegeben und etwas beispielhaften Quellcode bereitgestellt.

 

Der Autor

Dino Esposito ist Mentor von Solid Quality Learning (in englischer Sprache) und Autor von "Programming Microsoft ASP.NET 2.0" (Microsoft Press, 2005). Dino arbeitet in Italien und ist ein weltweit gefragter Referent bei Branchenveranstaltungen. Sie können Dino über cutting@microsoft.com (in englischer Sprache) erreichen oder dem Weblog unter weblogs.asp.net/despos (in englischer Sprache) beitreten.