Eine Einführung für Entwickler in Windows Workflow Foundation (WF) in .NET 4
Matt Milner, Pluralsight
November 2009
Aktualisiert auf Release: April 2010
Übersicht
Wie Softwareentwickler wissen, kann das Schreiben von Anwendungen eine Herausforderung sein, und wir sind ständig auf der Suche nach Tools und Frameworks, um den Prozess zu vereinfachen und uns dabei zu helfen, uns auf die geschäftlichen Herausforderungen zu konzentrieren, die wir zu lösen versuchen. Wir sind vom Schreiben von Code in Computersprachen wie Assembler zu höheren Sprachen wie C# und Visual Basic gewechselt, die unsere Entwicklung erleichtern, Probleme auf niedrigerer Ebene wie die Speicherverwaltung beseitigen und unsere Produktivität als Entwickler steigern. Für Microsoft-Entwickler ermöglicht der Wechsel zu .NET die Common Language Runtime (CLR), Arbeitsspeicher zuzuweisen, nicht benötigte Objekte zu bereinigen und Konstrukte auf niedriger Ebene wie Zeiger zu verarbeiten.
Ein Großteil der Komplexität einer Anwendung lebt in der Logik und Verarbeitung, die hinter den Kulissen erfolgt. Probleme wie die asynchrone oder parallele Ausführung und im Allgemeinen die Koordination der Aufgaben, um auf Benutzeranforderungen oder Dienstanforderungen zu reagieren, können Anwendungsentwickler schnell wieder zurück in die low-Level-Codierung von Handles, Rückrufen, Synchronisierung usw. führen. Als Entwickler benötigen wir die gleiche Leistungsfähigkeit und Flexibilität eines deklarativen Programmiermodells für die Internen einer Anwendung wie für die Benutzeroberfläche in Windows Presentation Foundation (WPF). Windows Workflow Foundation (WF) stellt das deklarative Framework zum Erstellen von Anwendungs- und Dienstlogik bereit und bietet Entwicklern eine höhere Sprache für die Verarbeitung asynchroner, paralleler Aufgaben und anderer komplexer Verarbeitungen.
Durch eine Laufzeit zum Verwalten von Arbeitsspeicher und Objekten können wir uns mehr auf die wichtigen Geschäftsaspekte des Schreibens von Code konzentrieren. Ebenso bietet eine Runtime, die die Komplexität der Koordination asynchroner Arbeit verwalten kann, eine Reihe von Features, die die Produktivität der Entwickler verbessern. WF ist eine Reihe von Tools zum Deklarieren Ihres Workflows (Ihrer Geschäftslogik), Aktivitäten zum Definieren der Logik und des Ablaufs sowie eine Laufzeit für die Ausführung der resultierenden Anwendungsdefinition. Kurz gesagt, bei WF geht es um die Verwendung einer höheren Sprache zum Schreiben von Anwendungen, mit dem Ziel, Entwickler produktiver zu machen, Anwendungen einfacher zu verwalten und Änderungen schneller zu implementieren. Die WF-Runtime führt nicht nur Ihre Workflows für Sie aus, sie bietet auch Dienste und Features, die beim Schreiben von Anwendungslogik wichtig sind, z. B. Persistenz des Zustands, Lesezeichen und Wiederaufnahme der Geschäftslogik, die alle zu Thread- und Prozessflexibilität führen, die das Hoch- und Hochskalieren von Geschäftsprozessen ermöglicht.
Weitere konzeptionelle Informationen dazu, wie Sie mithilfe von WF zum Erstellen Ihrer Anwendungen produktiver machen können, finden Sie unter "The Workflow Way" von David Chappell im Abschnitt Zusätzliche Ressourcen.
Neuerungen in WF4
In Version 4 des Microsoft® .NET Framework führt Windows Workflow Foundation erhebliche Änderungen gegenüber den vorherigen Versionen der Technologie ein, die als Teil von .NET 3.0 und 3.5 ausgeliefert wurden. Tatsächlich hat das Team den Kern des Programmiermodells, der Laufzeit und der Tools erneut aufgegriffen und jedes modelliert, um die Leistung und Produktivität zu steigern und das wichtige Feedback zu adressieren, das von Kundeninteraktionen mit den vorherigen Versionen erhalten wurde. Die erheblichen Änderungen waren erforderlich, um Entwicklern die bestmögliche Erfahrung bei der Einführung von WF zu bieten und wf weiterhin eine starke grundlegende Komponente zu sein, auf die Sie in Ihren Anwendungen aufbauen können. Ich werde die allgemeinen Änderungen hier vorstellen, und im gesamten Papier wird jedes Thema ausführlicher behandelt.
Bevor ich fortfahren, ist es wichtig zu verstehen, dass abwärtskompatibilität auch ein wichtiges Ziel in dieser Version war. Die neuen Frameworkkomponenten befinden sich hauptsächlich in den System.Activities.*-Assemblys, während sich die abwärtskompatiblen Frameworkkomponenten in den System.Workflow.*-Assemblys befinden. Die System.Workflow.*-Assemblys sind Teil der .NET Framework 4 und bieten vollständige Abwärtskompatibilität, sodass Sie Ihre Anwendung ohne Änderungen am Workflowcode zu .NET 4 migrieren können. In diesem Artikel werde ich den Namen WF4 verwenden, um auf die neuen Komponenten in den System.Activities.*-Assemblys und WF3 zu verweisen, um auf die Komponenten in den System.Workflow.*-Assemblys zu verweisen.
Designer
Einer der sichtbarsten Verbesserungsbereiche ist der Workflow-Designer. Benutzerfreundlichkeit und Leistung waren wichtige Ziele für das Team für das Release von VS 2010. Der Designer unterstützt jetzt die Möglichkeit, mit viel größeren Workflows zu arbeiten, ohne die Leistung zu beeinträchtigen, und Designer basieren alle auf Windows Presentation Foundation (WPF) und nutzen die umfassende Benutzeroberfläche, die sie mit dem deklarativen Benutzeroberflächenframework erstellen können. Aktivitätsentwickler verwenden XAML, um zu definieren, wie ihre Aktivitäten aussehen und mit Benutzern in einer visuellen Entwurfsumgebung interagieren. Darüber hinaus ist das erneute Hosten des Workflow-Designers in Ihren eigenen Anwendungen wesentlich einfacher, damit Nichtentwickler Ihre Workflows anzeigen und mit ihnen interagieren können.
Datenfluss
In WF3 war der Datenfluss in einem Workflow undurchsichtig. WF4 bietet ein klares, präzises Modell für den Datenfluss und die Bereichsdefinition bei der Verwendung von Argumenten und Variablen. Diese Konzepte, die allen Entwicklern vertraut sind, vereinfachen sowohl die Definition der Datenspeicherung als auch den Datenfluss in und aus Workflows und Aktivitäten. Das Datenflussmodell macht auch die erwarteten Eingaben und Ausgaben einer bestimmten Aktivität deutlicher und verbessert die Leistung der Laufzeit, da Daten einfacher verwaltet werden können.
Flussdiagramm
Eine neue Ablaufsteuerungsaktivität namens Flussdiagramm wurde hinzugefügt, damit Entwickler das Flussdiagrammmodell verwenden können, um einen Workflow zu definieren. Das Flussdiagramm ähnelt eher den Konzepten und Denkprozessen, die viele Analysten und Entwickler beim Erstellen von Lösungen oder beim Entwerfen von Geschäftsprozessen durchlaufen. Daher war es sinnvoll, eine Aktivität bereitzustellen, um die bereits durchgeführten konzeptionellen Überlegungen und Planungen zu modellieren. Das Flussdiagramm ermöglicht Konzepte wie die Rückkehr zu vorherigen Schritten und das Aufteilen von Logik basierend auf einer einzelnen Bedingung oder einer Switch/Case-Logik.
Programmiermodell
Das WF-Programmiermodell wurde überarbeitet, um es einfacher und robuster zu machen. Aktivität ist der Kernbasistyp im Programmiermodell und stellt sowohl Workflows als auch Aktivitäten dar. Darüber hinaus müssen Sie keinen WorkflowRuntime mehr erstellen, um einen Workflow aufzurufen, Sie können einfach eine instance erstellen und ausführen, was Komponententests und Anwendungsszenarien vereinfacht, in denen Sie nicht die Probleme beim Einrichten einer bestimmten Umgebung durchgehen möchten. Schließlich wird das Workflowprogrammierungsmodell zu einer vollständig deklarativen Zusammensetzung von Aktivitäten ohne Code-Nebensing, was die Workflowerstellung vereinfacht.
Windows Communication Foundation (WCF)-Integration
Die Vorteile von WF gelten sicherlich sowohl für die Erstellung von Diensten als auch für die Nutzung oder Koordinierung von Dienstinteraktionen. Die Integration zwischen WCF und WF wurde mit großem Aufwand verbessert. Neue Messagingaktivitäten, Nachrichtenkorrelation und verbesserte Hostingunterstützung sowie die vollständig deklarative Dienstdefinition sind die wichtigsten Verbesserungsbereiche.
Erste Schritte mit Workflow
Die beste Möglichkeit, WF zu verstehen, besteht darin, es zu verwenden und die Konzepte anzuwenden. Ich werde mehrere Kernkonzepte rund um die Grundlagen des Workflows behandeln und dann ein paar einfache Workflows erstellen, um zu veranschaulichen, wie diese Konzepte miteinander in Beziehung stehen.
Workflowstruktur
Aktivitäten sind die Bausteine von WF, und alle Aktivitäten werden letztendlich von Activity abgeleitet. Terminologiehinweis: Aktivitäten sind eine Arbeitseinheit in WF. Aktivitäten können zu größeren Aktivitäten zusammengefasst werden. Wenn eine Aktivität als Einstiegspunkt auf oberster Ebene verwendet wird, wird sie als "Workflow" bezeichnet, genau wie Main einfach eine weitere Funktion ist, die einen Einstiegspunkt der obersten Ebene in CLR-Programme darstellt. Abbildung 1 zeigt beispielsweise einen einfachen Workflow, der in Code erstellt wird.
Sequenz s = neue Sequenz
{
Aktivitäten = {
new WriteLine {Text = "Hello"},
neue Sequenz {
Aktivitäten =
{
new WriteLine {Text = "Workflow"},
new WriteLine {Text = "World"}
}
}
}
};
Abbildung 1: Ein einfacher Workflow
Beachten Sie in Abbildung 1, dass die Sequenzaktivität als Stammaktivität verwendet wird, um den Ablaufstil der Stammsteuerung für den Workflow zu definieren. Jede Aktivität kann als Stamm- oder Workflow verwendet und ausgeführt werden, sogar eine einfache WriteLine. Durch Festlegen der Activities-Eigenschaft für die Sequenz mit einer Auflistung anderer Aktivitäten habe ich die Workflowstruktur definiert. Darüber hinaus können die untergeordneten Aktivitäten untergeordnete Aktivitäten aufweisen, wodurch eine Struktur von Aktivitäten erstellt wird, die die gesamte Workflowdefinition bilden.
Workflowvorlagen und die Workflow-Designer
WF4 enthält viele Aktivitäten, und Visual Studio 2010 enthält eine Vorlage zum Definieren von Aktivitäten. Die zwei häufigsten Aktivitäten, die für den Stammsteuerungsfluss verwendet werden, sind Sequenz und Flussdiagramm. Während jede Aktivität als Workflow ausgeführt werden kann, stellen diese beiden die gängigsten Entwurfsmuster für die Definition von Geschäftslogik bereit. Abbildung 2 zeigt ein Beispiel für ein sequenzielles Workflowmodell, das die Verarbeitung einer bestellung definiert, die empfangen, gespeichert und dann an andere Dienste gesendet wird.
Abbildung 2: Entwurf des sequenziellen Workflows
Der Workflowtyp Flussdiagramm wird in WF4 eingeführt, um allgemeine Anforderungen von vorhandenen Benutzern zu erfüllen, z. B. die Möglichkeit, zu vorherigen Schritten in einem Workflow zurückzukehren, und weil er dem konzeptionellen Entwurf ähnelt, der von Analysten und Entwicklern durchgeführt wurde, die an der Definition von Geschäftslogik arbeiten. Betrachten Sie beispielsweise ein Szenario mit Benutzereingaben für Ihre Anwendung. Als Reaktion auf die vom Benutzer bereitgestellten Daten sollte Ihr Programm entweder den Prozess fortsetzen oder zu einem vorherigen Schritt zurückkehren, um erneut zur Eingabe aufzufordern. Bei einem sequenziellen Workflow würde dies ähnlich wie in Abbildung 3 dargestellt sein, in dem eine DoWhile-Aktivität verwendet wird, um die Verarbeitung fortzusetzen, bis eine Bedingung erfüllt ist.
Abbildung 3: Sequenziell für entscheidungsbranch
Der Entwurf in Abbildung 3 funktioniert, aber als Entwickler oder Analyst, der den Workflow betrachtet, stellt das Modell nicht die ursprüngliche Logik oder die ursprünglichen Anforderungen dar, die beschrieben wurden. Der Flowchart-Workflow in Abbildung 4 liefert ein ähnliches technisches Ergebnis wie das in Abbildung 3 verwendete sequenzielle Modell, aber der Entwurf des Workflows entspricht den ursprünglich definierten Überlegungen und Anforderungen besser.
Abbildung 4: Flussdiagramm-Workflow
Datenfluss in Workflows
Der erste Gedanke, den die meisten Menschen haben, wenn sie über den Workflow nachdenken, ist der Geschäftsprozess oder der Ablauf der Anwendung. Ebenso kritisch wie der Flow sind jedoch die Daten, die den Prozess steuern, und die Informationen, die während der Ausführung des Workflows gesammelt und gespeichert werden. In WF4 war die Speicherung und Verwaltung von Daten ein wichtiger Aspekt des Entwurfs.
Es gibt drei Standard Konzepte, die in Bezug auf Daten zu verstehen sind: Variablen, Argumente und Ausdrücke. Die einfachen Definitionen für jede sind, dass Variablen zum Speichern von Daten, Argumente zum Übergeben von Daten und Ausdrücke zum Bearbeiten von Daten sind.
Variablen – Speichern von Daten
Variablen in Workflows ähneln den Variablen, die Sie in imperativen Sprachen gewohnt sind: Sie beschreiben einen benannten Speicherort für zu speichernde Daten und folgen bestimmten Bereichsregeln. Um eine Variable zu erstellen, bestimmen Sie zunächst, in welchem Bereich die Variable verfügbar sein muss. Genau wie Sie möglicherweise Variablen im Code haben, die auf Klassen- oder Methodenebene verfügbar sind, können Ihre Workflowvariablen in verschiedenen Bereichen definiert werden. Betrachten Sie den Workflow in Abbildung 5. In diesem Beispiel können Sie eine Variable auf der Stammebene des Workflows oder im Durch die Sequenzaktivität Feeddaten sammeln definierten Bereich definieren.
Abbildung 5: Auf Aktivitäten bezogene Variablen
Argumente – Übergeben von Daten
Argumente werden für Aktivitäten definiert und definieren den Datenfluss in und aus der Aktivität. Sie können sich Argumente für Aktivitäten ähnlich wie die Verwendung von Argumenten für Methoden im imperativen Code vorstellen. Argumente können In, Out oder In/Out sein und einen Namen und Typ aufweisen. Beim Erstellen eines Workflows können Sie Argumente für die Stammaktivität definieren, sodass Daten beim Aufrufen an Ihren Workflow übergeben werden können. Sie definieren Argumente für den Workflow genauso wie Variablen mithilfe des Argumentfensters.
Wenn Sie Ihrem Workflow Aktivitäten hinzufügen, müssen Sie die Argumente für die Aktivitäten konfigurieren. Dies geschieht in erster Linie durch Verweisen auf Bereichsvariablen oder die Verwendung von Ausdrücken, auf die ich als Nächstes eingehen werde. Tatsächlich enthält die Argument-Basisklasse eine Expression-Eigenschaft, bei der es sich um eine Aktivität handelt, die einen Wert des Argumenttyps zurückgibt, sodass alle diese Optionen verknüpft sind und auf Aktivität basieren.
Wenn Sie den Workflow-Designer zum Bearbeiten von Argumenten verwenden, können Sie Ausdrücke, die Literalwerte darstellen, in das Eigenschaftenraster eingeben oder Variablennamen verwenden, um auf eine Bereichsvariable zu verweisen, wie in Abbildung 6 dargestellt, wobei emailResult und emailAddress Variablen sind, die im Workflow definiert sind.
Abbildung 6: Konfigurieren von Argumenten für Aktivitäten
Ausdrücke – Auf Daten wirken
Ausdrücke sind Aktivitäten, die Sie in Ihrem Workflow verwenden können, um mit Daten zu arbeiten. Ausdrücke können an Stellen verwendet werden, an denen Sie Aktivität verwenden und an einem Rückgabewert interessiert sind. Dies bedeutet, dass Sie Ausdrücke zum Festlegen von Argumenten oder zum Definieren von Bedingungen für Aktivitäten wie die Aktivitäten While oder If verwenden können. Denken Sie daran, dass die meisten Dinge in WF4 von Aktivität abgeleitet sind und sich Ausdrücke nicht unterscheiden, sie sind eine Ableitung von Activity<TResult> , was bedeutet, dass sie einen Wert eines bestimmten Typs zurückgeben. Darüber hinaus enthält WF4 mehrere gängige Ausdrücke zum Verweisen auf Variablen und Argumente sowie Visual Basic-Ausdrücke. Aufgrund dieser Ebene spezialisierter Ausdrücke können die Ausdrücke, die Sie zum Definieren von Argumenten verwenden, Code, Literalwerte und Variablenverweise enthalten. Tabelle 1 enthält eine kleine Stichprobe der Ausdruckstypen, die Sie beim Definieren von Argumenten mithilfe des Workflow-Designers verwenden können. Beim Erstellen von Ausdrücken im Code gibt es mehrere weitere Optionen, einschließlich der Verwendung von Lambdaausdrücken.
Ausdruck | Ausdruckstyp |
---|---|
"hello world" |
Literalzeichenfolgenwert |
10 |
Literalwert int32 |
System.String.Concat("hello", " ", "world") |
Imperative Methodenaufrufe |
"hello " " & world" |
Visual Basic-Ausdruck |
argInputString |
Argumentverweis (Argumentname) |
varResult |
Variablenverweis (Variablenname) |
"hello: " & argInputString |
Literale und Argumente/Variablen gemischt |
Tabelle 1: Beispielausdrücke
Erstellen Ihres ersten Workflows
Nachdem ich nun die wichtigsten Konzepte rund um Aktivität und Datenfluss behandelt habe, kann ich mit diesen Konzepten einen Workflow erstellen. Ich werde mit einem einfachen Hello World-Workflow beginnen, um mich auf die Konzepte und nicht auf das wahre Wertversprechen von WF zu konzentrieren. Erstellen Sie zunächst ein neues Komponententestprojekt in Visual Studio 2010. Um den Kern von WF zu verwenden, fügen Sie einen Verweis auf die System.Activities-Assembly hinzu, und fügen Sie using-Anweisungen für System.Activities, System.Activities.Statements und System.IO in der Testklassendatei hinzu. Fügen Sie dann eine Testmethode wie in Abbildung 7 gezeigt hinzu, um einen einfachen Workflow zu erstellen und auszuführen.
[TestMethod]
public void TestHelloWorldStatic()
{
StringWriter writer = new StringWriter();
Console.SetOut(writer);
Sequence wf = new Sequence
{
Aktivitäten = {
new WriteLine {Text = "Hello"},
new WriteLine {Text = "World"}
}
};
WorkflowInvoker.Invoke(wf);
Assert.IsTrue(String.Compare(
"Hello\r\nWorld\r\n",
Schriftsteller. GetStringBuilder(). ToString()) == 0,
"Falsche Zeichenfolge geschrieben");
}
Abbildung 7: Erstellen von "hello world" im Code
Die Text-Eigenschaft für die WriteLine-Aktivität ist eine InArgument-Zeichenfolge<>, und in diesem Beispiel habe ich einen Literalwert an diese Eigenschaft übergeben.
Der nächste Schritt besteht darin, diesen Workflow so zu aktualisieren, dass Variablen verwendet werden und diese Variablen an die Aktivitätsargumente übergeben werden. Abbildung 8 zeigt den neuen Test, der für die Verwendung der Variablen aktualisiert wurde.
[TestMethod]
public void TestHelloWorldVariables()
{
StringWriter writer = new StringWriter();
Console.SetOut(writer);
Sequence wf = new Sequence
{
Variablen = {
new Variable<string>{Default = "Hello", Name = "greeting"},
new Variable<string> { Default = "Bill", Name = "name" } },
Aktivitäten = {
new WriteLine { Text = new VisualBasicValue<string>("greeting"),
new WriteLine { Text = new VisualBasicValue string<>(
"name + \"Gates\"")}
}
};
WorkflowInvoker.Invoke(wf);
}
Abbildung 8: Codeworkflow mit Variablen
In diesem Fall werden die Variablen als Variable-Zeichenfolge<> vom Typ definiert und erhalten einen Standardwert. Die Variablen werden innerhalb der Sequenzaktivität deklariert und dann vom Text-Argument der beiden Aktivitäten referenziert. Alternativ könnte ich Ausdrücke verwenden, um die Variablen zu initialisieren, wie in Abbildung 9 gezeigt, wobei die VisualBasicValue<TResult-Klasse> verwendet wird, indem eine Zeichenfolge übergeben wird, die den Ausdruck darstellt. Im ersten Fall verweist der Ausdruck auf den Namen der Variablen, und im zweiten Fall wird die Variable mit einem Literalwert verkettet. Die Syntax, die in Textausdrücken verwendet wird, ist Visual Basic, auch wenn Code in C# geschrieben wird.
Aktivitäten = {
new WriteLine { Text = new VisualBasicValue<string>("greeting"),
TextWriter = Writer },
new WriteLine { Text = new VisualBasicValue<string>("name + \"Gates\""),
TextWriter = Writer }
}
Abbildung 9: Verwenden von Ausdrücken zum Definieren von Argumenten
Während Codebeispiele wichtige Punkte veranschaulichen und sich für Entwickler im Allgemeinen wohl fühlen, verwenden die meisten Benutzer den Designer, um Workflows zu erstellen. Im Designer ziehen Sie Aktivitäten auf die Entwurfsoberfläche und verwenden ein aktualisiertes, aber vertrautes Eigenschaftenraster, um Argumente festzulegen. Darüber hinaus enthält der Workflow-Designer am unteren Rand erweiterbare Regionen, um Argumente für den Workflow und Variablen zu bearbeiten. Um einen ähnlichen Workflow im Designer zu erstellen, fügen Sie der Projektmappe ein neues Projekt hinzu, wählen Sie die Vorlage Aktivitätsbibliothek aus, und fügen Sie dann eine neue Aktivität hinzu.
Wenn der Workflow-Designer zum ersten Mal angezeigt wird, enthält er keine Aktivitäten. Der erste Schritt beim Definieren der Aktivität besteht darin, die Stammaktivität auszuwählen. Fügen Sie in diesem Beispiel dem Designer eine Flussdiagrammaktivität hinzu, indem Sie sie aus der Kategorie Flussdiagramm in der Toolbox ziehen. Ziehen Sie im Flussdiagramm-Designer zwei WriteLine-Aktivitäten aus der Toolbox, und fügen Sie sie untereinander dem Flussdiagramm hinzu. Nun müssen Sie die Aktivitäten miteinander verbinden, damit das Flussdiagramm den pfad kennt, dem sie folgen soll. Dazu zeigen Sie zuerst über den grünen "Start"-Kreis oben im Flussdiagramm, um die Ziehpunkte anzuzeigen, klicken und ziehen Sie dann auf die erste WriteLine, und legen Sie sie auf dem Ziehpunkt ab, der oben in der Aktivität angezeigt wird. Gehen Sie genauso vor, um die erste WriteLine mit der zweiten WriteLine zu verbinden. Die Entwurfsoberfläche sollte wie Abbildung 10 aussehen.
Abbildung 10: Layout des Hello-Flussdiagramms
Sobald die Struktur eingerichtet ist, müssen Sie einige Variablen konfigurieren. Wenn Sie auf die Entwurfsoberfläche klicken und dann auf die Schaltfläche Variablen am unteren Rand des Designers klicken, können Sie die Auflistung der Variablen für das Flussdiagramm bearbeiten. Fügen Sie zwei Variablen namens "Greeting" und "name" hinzu, um die Standardwerte auf "Hello" bzw. "Bill" festzulegen. Achten Sie beim Festlegen der Werte darauf, die Anführungszeichen einzuschließen, da dies ein Ausdruck ist, sodass Literalzeichenfolgen in Anführungszeichen gesetzt werden müssen. Abbildung 11 zeigt das Variablenfenster, das mit den beiden Variablen und ihren Standardwerten konfiguriert ist.
Abbildung 11: Im Workflow-Designer definierte Variablen
Um diese Variablen in den WriteLine-Aktivitäten zu verwenden, geben Sie "Greeting" (ohne Anführungszeichen) im Eigenschaftenraster für das Text-Argument der ersten Aktivität und "name" (wieder ohne anführungszeichen) für das Text-Argument für die zweite WriteLine ein. Wenn zur Laufzeit die Text-Argumente ausgewertet werden, wird der Wert in der Variablen aufgelöst und von der WriteLine-Aktivität verwendet.
Fügen Sie im Testprojekt einen Verweis auf das Projekt hinzu, das Ihren Workflow enthält. Anschließend können Sie eine Testmethode wie in Abbildung 12 hinzufügen, um diesen Workflow aufzurufen und die Ausgabe zu testen. In Abbildung 12 sehen Sie, wie der Workflow erstellt wird, indem Sie eine instance der erstellten Klasse instanziieren. In diesem Fall wurde der Workflow im Designer definiert und in eine von Activity abgeleitete Klasse kompiliert, die dann aufgerufen werden kann.
[TestMethod]
public void TestHelloFlowChart()
{
StringWriter tWriter = new StringWriter();
Console.SetOut(tWriter);
Workflows.HelloFlow wf = new Workflows.HelloFlow();
WorkflowInvoker.Invoke(wf);
Ausgelassene Asserts
}
Abbildung 12: Testen des Flussdiagramms
Bisher habe ich Variablen im Workflow verwendet und sie verwendet, um Werte für Argumente für die WriteLine-Aktivitäten anzugeben. Für den Workflow können auch Argumente definiert werden, mit denen Sie Daten an den Workflow übergeben können, wenn Sie ihn aufrufen und nach Abschluss des Workflows eine Ausgabe empfangen können. Um das Flussdiagramm aus dem vorherigen Beispiel zu aktualisieren, entfernen Sie die Variable "name" (wählen Sie sie aus, und drücken Sie die ENTF-TASTE), und erstellen Sie stattdessen ein "name"-Argument vom Typ Zeichenfolge. Sie tun dies auf die gleiche Weise, mit der Ausnahme, dass Sie die Schaltfläche Argumente verwenden, um den Argument-Editor anzuzeigen. Beachten Sie, dass die Argumente auch eine Richtung haben können und Sie keinen Standardwert angeben müssen, da der Wert zur Laufzeit an die Aktivität übergeben wird. Da Sie für das Argument den gleichen Namen wie für die Variable verwenden, bleiben die Textargumente der WriteLine-Aktivitäten gültig. Jetzt zur Laufzeit werten diese Argumente den Wert des Arguments "name" im Workflow aus, lösen diesen auf und verwenden diesen Wert. Fügen Sie ein zusätzliches Argument vom Typ string mit der Richtung Out und dem Namen "fullGreeting" hinzu. dies wird an den aufrufenden Code zurückgegeben.
Abbildung 13: Definieren von Argumenten für den Workflow
Fügen Sie im Flussdiagramm eine Aktivität Zuweisen hinzu, und verbinden Sie sie mit der letzten WriteLine-Aktivität. Geben Sie für das To-Argument "fullGreeting" (keine Anführungszeichen) ein, und geben Sie für das Argument Value "Begrüßungsname & " (ohne Anführungszeichen) ein. Dadurch wird die Verkettung der Greeting-Variablen mit dem Argument name dem ausgabeargument fullGreeting zugewiesen.
Aktualisieren Sie nun im Komponententest den Code, um das Argument beim Aufrufen des Workflows anzugeben. Argumente werden als Wörterbuchzeichenfolge<an den Workflow übergeben, wobei> der Schlüssel der Name des Arguments ist. Dazu können Sie einfach den Aufruf ändern, um den Workflow aufzurufen, wie in Abbildung 14 dargestellt. Beachten Sie, dass die Ausgabeargumente auch in einer Dictionary-Zeichenfolge<enthalten sind, einer Objektauflistung> , die auf den Argumentnamen festgelegt ist.
IDictionary-Zeichenfolge<, Objektergebnisse> = WorkflowInvoker.Invoke(wf,
neue Wörterbuchzeichenfolge,Objekt<> {
{"name", "Bill" } }
);
string outValue = results["fullGreeting"]. ToString();
Abbildung 14: Übergeben von Argumenten an einen Workflow
Nachdem Sie nun die Grundlagen der Zusammenstellung von Aktivitäten, Variablen und Argumenten kennengelernt haben, werde ich Sie durch eine Tour durch die aktivitäten führen, die im Framework enthalten sind, um interessantere Workflows zu ermöglichen, die sich auf Geschäftslogik anstelle von Konzepten auf niedriger Ebene konzentrieren.
Tour durch die Workflowaktivitätspalette
Bei jeder Programmiersprache erwarten Sie Kernkonstrukte zum Definieren Ihrer Anwendungslogik. Wenn Sie ein Entwicklungsframework auf höherer Ebene wie WF verwenden, ändert sich diese Erwartung nicht. Genau wie Sie Anweisungen in .NET-Sprachen wie If/Else, Switch und While haben, benötigen Sie für die Verwaltung des Ablaufsteuerungs die gleichen Funktionen, wenn Sie Ihre Logik in einem deklarativen Workflow definieren. Diese Funktionen werden in Form der Basisaktivitätsbibliothek bereitgestellt, die im Rahmen des Frameworks enthalten ist. In diesem Abschnitt werde ich Ihnen einen kurzen Überblick über die Aktivitäten geben, die mit dem Framework ausgeliefert werden, um Ihnen eine Vorstellung von den funktionen zu geben, die standardmäßig bereitgestellt werden.
Aktivitätsgrundtypen und Sammlungsaktivitäten
Wenn Sie zu einem deklarativen Programmiermodell wechseln, ist es leicht, sich zu fragen, wie häufige Objektbearbeitungsaufgaben ausgeführt werden, die beim Schreiben von Code zweiter Natur sind. Für Aufgaben, bei denen Sie mit Objekten arbeiten und Eigenschaften festlegen, Befehle aufrufen oder eine Sammlung von Elementen verwalten müssen, gibt es eine Reihe von Aktivitäten, die speziell für diese Aufgaben entwickelt wurden.
Aktivität | BESCHREIBUNG |
---|---|
Zuweisen |
Weist einem Standort einen Wert zu– aktiviert Einstellungsvariablen. |
Verzögern |
Verzögert den Ausführungspfad für einen angegebenen Zeitraum. |
InvokeMethod |
Ruft eine Methode für ein .NET-Objekt oder eine statische Methode für einen .NET-Typ auf, optional mit dem Rückgabetyp T. |
WriteLine |
Schreibt angegebenen Text in einen Textwriter – standardmäßig Konsole.Out |
AddToCollection<T> |
Fügt einer typisierten Auflistung ein Element hinzu. |
RemoveFromCollection<T> |
Entfernt ein Element aus einer typisierten Auflistung. |
ClearCollection<T> |
Entfernt alle Elemente aus einer Auflistung. |
ExistsInCollection<T> |
Gibt einen booleschen Wert zurück, der angibt, ob das angegebene Element in der Auflistung vorhanden ist. |
Ablaufsteuerungsaktivitäten
Beim Definieren von Geschäftslogik oder Geschäftsprozessen ist die Kontrolle über den Ausführungsfluss von entscheidender Bedeutung. Zu den Ablaufsteuerungsaktivitäten gehören Grundlagen wie Sequenz, die einen gemeinsamen Container bereitstellt, wenn Sie Schritte in der Reihenfolge ausführen müssen, und allgemeine Verzweigungslogik wie die Aktivitäten If und Switch. Die Ablaufsteuerungsaktivitäten umfassen auch Schleifenlogik basierend auf Daten (ForEach) und Bedingungen(While). Am wichtigsten für die Vereinfachung der komplexen Programmierung sind die parallelen Aktivitäten, die es allen ermöglichen, mehrere asynchrone Aktivitäten gleichzeitig zu erledigen.
Aktivität | BESCHREIBUNG |
---|---|
Sequenz |
Zum Ausführen von Aktivitäten in Reihen |
While/DoWhile |
Führt eine untergeordnete Aktivität aus, während eine Bedingung (Ausdruck) true ist. |
ForEach<T> |
Durchläuft eine aufzählbare Auflistung und führt die untergeordnete Aktivität einmal für jedes Element in der Auflistung aus, wobei darauf gewartet wird, dass das untergeordnete Element abgeschlossen ist, bevor mit der nächsten Iteration begonnen wird. Bietet typisierten Zugriff auf das einzelne Element, das die Iteration in Form eines benannten Arguments antreibt. |
If |
Führt je nach Ergebnis der Bedingung (Ausdruck) eine von zwei untergeordneten Aktivitäten aus. |
Umschalten<von T> |
Wertet einen Ausdruck aus und plant die untergeordnete Aktivität mit einem übereinstimmenden Schlüssel. |
Parallel |
Plant alle untergeordneten Aktivitäten gleichzeitig, stellt aber auch eine Abschlussbedingung bereit, damit die Aktivität alle ausstehenden untergeordneten Aktivitäten abbrechen kann, wenn bestimmte Bedingungen erfüllt sind. |
ParallelForEach<T> |
Durchläuft eine aufzählbare Auflistung und führt die untergeordnete Aktivität einmal für jedes Element in der Auflistung aus, wobei alle Instanzen gleichzeitig geplant werden. Wie forEach<T> ermöglicht diese Aktivität den Zugriff auf das aktuelle Datenelement in Form eines benannten Arguments. |
Auswählen |
Plant alle untergeordneten PickBranch-Aktivitäten und bricht alle bis auf die erste ab, um den Trigger abzuschließen. Die PickBranch-Aktivität verfügt sowohl über einen Trigger als auch über eine Aktion. jede ist eine Aktivität. Wenn eine Triggeraktivität abgeschlossen ist, bricht pick alle anderen untergeordneten Aktivitäten ab. |
Die beiden folgenden Beispiele zeigen mehrere dieser Aktivitäten, die verwendet werden, um zu veranschaulichen, wie diese Aktivitäten zusammen erstellt werden. Das erste Beispiel, Abbildung 15, enthält die Verwendung von ParallelForEach<T> , um eine Liste von URLs zu verwenden und den RSS-Feed asynchron an der angegebenen Adresse abzurufen. Nachdem der Feed zurückgegeben wurde, wird forEach<T> verwendet, um die Feedelemente zu durchlaufen und zu verarbeiten.
Beachten Sie, dass der Code eine VisualBasicSettings-instance mit Verweisen auf die System.ServiceModel.Syndication-Typen deklariert und definiert. Dieses Einstellungsobjekt wird dann verwendet, wenn VisualBasicValue<T-Instanzen> deklariert werden, die auf Variablentypen aus diesem Namespace verweisen, um die Typauflösung für diese Ausdrücke zu aktivieren. Beachten Sie auch, dass der Text der ParallelForEach-Aktivität eine ActivityAction verwendet, die im Abschnitt zum Erstellen benutzerdefinierter Aktivitäten erwähnt wird. Diese Aktionen verwenden DelegateInArgument und DelegateOutArgument auf die gleiche Weise wie Aktivitäten InArgument und OutArgument.
Abbildung 15: Ablaufsteuerungsaktivitäten
Das zweite Beispiel, Abbildung 16, ist ein häufiges Muster des Wartens auf eine Antwort mit einem Timeout. Beispiel: Warten auf die Genehmigung einer Anforderung durch einen Vorgesetzten und Senden einer Erinnerung, wenn die Antwort nicht in der zugewiesenen Zeit eingetroffen ist. Eine DoWhile-Aktivität verursacht die Wiederholung des Wartens auf eine Antwort, während die Pick-Aktivität verwendet wird, um sowohl eine ManagerResponse-Aktivität als auch eine Verzögerungsaktivität gleichzeitig mit Triggern auszuführen. Wenn die Verzögerung zuerst abgeschlossen ist, wird die Erinnerung gesendet, und wenn die ManagerResponse-Aktivität zuerst abgeschlossen wird, wird das Flag so festgelegt, dass es aus der DoWhile-Schleife ausbricht.
Abbildung 16: Auswählen und DoWhile-Aktivitäten
Das Beispiel in Abbildung 16 zeigt auch, wie Lesezeichen in Workflows verwendet werden können. Eine Textmarke wird von einer Aktivität erstellt, um eine Stelle im Workflow zu markieren, sodass die Verarbeitung von diesem Punkt zu einem späteren Zeitpunkt fortgesetzt werden kann. Die Verzögerungsaktivität verfügt über ein Lesezeichen, das nach einer bestimmten Zeitspanne fortgesetzt wird. Die ManagerResponse-Aktivität ist eine benutzerdefinierte Aktivität, die auf die Eingabe wartet und den Workflow fortsetzen wird, sobald die Daten eintreffen. Die kurz erläuterten Messagingaktivitäten sind die Standard Aktivitäten für die Ausführung von Lesezeichen. Wenn ein Workflow nicht aktiv Arbeit verarbeitet und nur darauf wartet, dass Lesezeichen fortgesetzt werden, gilt er als im Leerlauf und kann in einem dauerhaften Speicher beibehalten werden. Lesezeichen werden im Abschnitt zum Erstellen benutzerdefinierter Aktivitäten ausführlicher erläutert.
Migration
Für Entwickler, die WF3 verwenden, kann die Interop-Aktivität eine wichtige Rolle bei der Wiederverwendung vorhandener Ressourcen spielen. Die Aktivität, die in der Assembly System.Workflow.Runtime gefunden wurde, umschließt Ihren vorhandenen Aktivitätstyp und zeigt die Eigenschaften der Aktivität als Argumente im WF4-Modell an. Da es sich bei den Eigenschaften um Argumente handelt, können Sie Ausdrücke verwenden, um die Werte zu definieren. Abbildung 17 zeigt die Konfiguration der Interop-Aktivität zum Aufrufen einer WF3-Aktivität. Die Eingabeargumente werden mit Verweisen auf Bereichsvariablen innerhalb der Workflowdefinition definiert. Aktivitäten, die in WF3 erstellt wurden, hatten Eigenschaften anstelle von Argumenten. Daher erhält jede Eigenschaft ein entsprechendes Eingabe- und Ausgabeargument, sodass Sie die Daten, die Sie in die Aktivität senden, und die Daten unterscheiden können, die Sie nach ausführung der Aktivität abrufen möchten. In einem neuen WF4-Projekt finden Sie diese Aktivität nicht in der Toolbox, da das Zielframework auf .NET Framework 4 Clientprofil festgelegt ist. Ändern Sie das Zielframework in den Projekteigenschaften in .NET Framework 4, und die Aktivität wird in der Toolbox angezeigt.
Abbildung 17: Konfiguration der Interopaktivität
Flussdiagramm
Beim Entwerfen von Flussdiagrammworkflows gibt es mehrere Konstrukte, die zum Verwalten des Ausführungsflusses innerhalb des Flussdiagramms verwendet werden können. Diese Konstrukte selbst bieten einfache Schritte, einfache Entscheidungspunkte, die auf einer einzelnen Bedingung basieren, oder eine Switch-Anweisung. Die eigentliche Leistung des Flussdiagramms ist die Möglichkeit, diese Knotentypen mit dem gewünschten Flow zu verbinden.
Konstrukt/Aktivität | BESCHREIBUNG |
---|---|
Flussdiagramm |
Der Container für eine Reihe von Flussschritten, wobei jeder Flussschritt eine beliebige Aktivität oder eines der folgenden Konstrukte sein kann, aber um ausgeführt zu werden, muss er innerhalb des Flussdiagramms verbunden sein. |
FlowDecision |
Stellt Verzweigungslogik basierend auf einer Bedingung bereit. |
FlowSwitch<T> |
Ermöglicht mehrere Branches basierend auf dem Wert eines Ausdrucks. |
FlowStep |
Stellt einen Schritt im Flussdiagramm mit der Möglichkeit dar, mit anderen Schritten verbunden zu werden. Dieser Typ wird in der Toolbox nicht angezeigt, da er implizit vom Designer hinzugefügt wird. Diese Aktivität umschließt andere Aktivitäten im Workflow und stellt die Navigationssemantik für die nächsten Schritte im Workflow bereit. |
Es ist wichtig zu beachten, dass es zwar bestimmte Aktivitäten für das Flussdiagrammmodell gibt, aber alle anderen Aktivitäten innerhalb des Workflows verwendet werden können. Ebenso kann eine Flussdiagrammaktivität einer anderen Aktivität hinzugefügt werden, um die Ausführungs- und Entwurfssemantik eines Flussdiagramms innerhalb dieses Workflows bereitzustellen. Dies bedeutet, dass Sie eine Sequenz mit mehreren Aktivitäten und einem Flussdiagramm direkt in der Mitte haben können.
Messagingaktivitäten
Einer der Hauptschwerpunkte von WF4 ist eine engere Integration zwischen WF und WCF. In Bezug auf Workflows bedeutet dies Aktivitäten zum Modellieren von Messagingvorgängen wie das Senden und Empfangen von Nachrichten. Im Framework für Messaging sind tatsächlich mehrere verschiedene Aktivitäten enthalten, die jeweils etwas unterschiedliche Funktionen und Zwecke aufweisen.
Aktivität | BESCHREIBUNG |
---|---|
Senden/Empfangen |
One-Way-Messagingaktivitäten zum Senden oder Empfangen einer Nachricht. Dieselben Aktivitäten werden in Interaktionen im Anforderungs-/Antwortstil zusammengesetzt. |
ReceiveAndSendReply |
Modelliert einen Dienstvorgang, der eine Nachricht empfängt und eine Antwort zurücksendet. |
SendAndReceiveReply |
Ruft einen Dienstvorgang auf und empfängt die Antwort. |
InitializeCorrelation |
Erlauben Sie, Korrelationswerte explizit als Teil der Workflowlogik zu initialisieren, anstatt die Werte aus einer Nachricht zu extrahieren. |
CorrelationScope |
Definiert einen Ausführungsbereich, in dem auf ein Korrelationshandle zum Empfangen und Senden von Aktivitäten zugegriffen werden kann, wodurch die Konfiguration eines Handles vereinfacht wird, das von mehreren Messagingaktivitäten freigegeben wird. |
TransactedReceiveScope |
Ermöglicht die Einbeziehung von Workflowlogik in dieselbe Transaktion, die in einen WCF-Vorgang mithilfe der Receive-Aktivität fließt. |
Um einen Dienstvorgang in einem Workflow aufzurufen, führen Sie die vertrauten Schritte zum Hinzufügen eines Dienstverweiss zu Ihrem Workflowprojekt aus. Das Projektsystem in Visual Studio nutzt dann die Metadaten aus dem Dienst und erstellt eine benutzerdefinierte Aktivität für jeden Dienstvorgang, der im Vertrag gefunden wird. Sie können sich dies als Workflowäquivalent eines WCF-Proxys vorstellen, der in einem C#- oder Visual Basic-Projekt erstellt wird. Beispiel: Verwenden eines vorhandenen Diensts, der nach Hotelreservierungen sucht, mit dem in Abbildung 18 dargestellten Dienstvertrag; Wenn Sie einen Dienstverweis hinzufügen, wird eine benutzerdefinierte Aktivität in Abbildung 19 angezeigt.
[ServiceContract]
öffentliche Schnittstelle IHotelService
{
[OperationContract]
List<HotelSearchResult> SearchHotels(
HotelSearchRequest requestDetails);
}
Abbildung 18: Dienstvertrag
Abbildung 19: Benutzerdefinierte WCF-Aktivität
Weitere Informationen zum Erstellen von Workflows, die als WCF-Dienste verfügbar gemacht werden, finden Sie im Abschnitt Workflowdienste weiter unten in diesem Dokument.
Transaktionen und Fehlerbehandlung
Das Schreiben zuverlässiger Systeme kann schwierig sein und erfordert, dass Sie Fehler gut behandeln und verwalten, um einen konsistenten Zustand in Ihrer Anwendung zu erhalten. Für einen Workflow werden Bereiche zum Verwalten von Ausnahmebehandlungen und Transaktionen mithilfe von Aktivitäten mit Eigenschaften des Typs ActivityAction oder Activity modelliert, damit Entwickler Fehlerverarbeitungslogik modellieren können. Über diese gängigen Konsistenzmuster hinaus umfasst WF4 auch Aktivitäten, mit denen Sie eine lang andauernde verteilte Koordination durch Kompensation und Bestätigung modellieren können.
Aktivität | BESCHREIBUNG |
---|---|
CancellationScope |
Wird verwendet, damit der Workflowentwickler reagieren kann, wenn ein Arbeitskörper abgebrochen wird. |
TransactionScope |
Ermöglicht semantik ähnlich der Verwendung eines Transaktionsbereichs im Code, indem alle untergeordneten Aktivitäten im Bereich unter einer Transaktion ausgeführt werden. |
TryCatch/Catch<T> |
Wird verwendet, um die Ausnahmebehandlung zu modellieren und typisierte Ausnahmen abzufangen. |
Throw |
Kann verwendet werden, um eine Ausnahme von der Aktivität auszulösen. |
Erneut auslösen |
Wird verwendet, um eine Ausnahme erneut zu erstellen, in der Regel eine Ausnahme, die mithilfe der TryCatch-Aktivität abgefangen wurde. |
CompensableActivity |
Definiert die Logik zum Ausführen untergeordneter Aktivitäten, für die ihre Aktionen nach dem Erfolg möglicherweise kompensiert werden müssen. Stellt einen Platzhalter für die Kompensationslogik, die Bestätigungslogik und die Abbruchbehandlung bereit. |
Kompensieren |
Ruft die Kompensationsbehandlungslogik für eine kompensierbare Aktivität auf. |
Confirm |
Ruft die Bestätigungslogik für eine kompensierbare Aktivität auf. |
Die TryCatch-Aktivität bietet eine vertraute Möglichkeit, eine Reihe von Aufgaben zum Abfangen von möglicherweise auftretenden Ausnahmen festzulegen, und die Catch<T-Aktivität> stellt den Container für die Ausnahmebehandlungslogik bereit. Abbildung 20 zeigt ein Beispiel für die Verwendung dieser Aktivität, wobei jeder typisierte Ausnahmeblock durch eine Catch-T-Aktivität<> definiert wird, die den Zugriff auf die Ausnahme über das benannte Argument ermöglicht, das links neben jedem Catch zu sehen ist. Wenn dies erforderlich ist, kann die Rethrow-Aktivität verwendet werden, um die abgefangene Ausnahme erneut zu erstellen, oder die Throw-Aktivität, um eine neue Ausnahme zu auslösen.
Abbildung 20: TryCatch-Aktivität
Transaktionen sorgen für Konsistenz in kurzlebigen Arbeiten, und die TransactionScope-Aktivität bietet dieselbe Art von Bereich, die Sie in .NET-Code mit der TransactionScope-Klasse abrufen können.
Wenn Sie die Konsistenz beibehalten müssen, aber keine atomare Transaktion über die Ressourcen hinweg verwenden können, können Sie die Kompensation verwenden. Die Vergütung ermöglicht es Ihnen, eine Reihe von Arbeiten zu definieren, die nach Abschluss über eine Reihe von Aktivitäten verfügen können, um die vorgenommenen Änderungen zu kompensieren. Betrachten Sie als einfaches Beispiel die Kompensable-Aktivität in Abbildung 21, an die eine E-Mail gesendet wird. Wenn nach Abschluss der Aktivität eine Ausnahme auftritt, kann die Kompensationslogik aufgerufen werden, um das System wieder in einen konsistenten Zustand zu versetzen. in diesem Fall eine Nachverfolgungs-E-Mail, um die frühere Nachricht zurückzuziehen.
Abbildung 21: Kompensierbare Aktivität
Erstellen und Ausführen von Workflows
Wie bei jeder Programmiersprache gibt es zwei grundlegende Dinge, die Sie mit Workflows tun: Sie definieren sie und führen sie aus. In beiden Fällen bietet WF mehrere Optionen, um Ihnen Flexibilität und Kontrolle zu bieten.
Optionen für das Entwerfen von Workflows
Beim Entwerfen oder Definieren von Workflows gibt es zwei Standard Optionen: Code oder XAML. XAML bietet die wirklich deklarative Benutzeroberfläche und ermöglicht es, die gesamte Definition Ihres Workflows im XML-Markup zu definieren, wobei auf Aktivitäten und Typen verwiesen wird, die mit .NET erstellt wurden. Die meisten Entwickler verwenden wahrscheinlich den Workflow-Designer, um Workflows zu erstellen, was zu einer deklarativen XAML-Workflowdefinition führt. Da XAML jedoch nur XML ist, kann jedes Tool verwendet werden, um es zu erstellen, was einer der Gründe ist, warum es ein so leistungsfähiges Modell zum Erstellen von Anwendungen ist. Beispielsweise wurde der in Abbildung 22 gezeigte XAML-Code in einem einfachen Text-Editor erstellt und kann entweder kompiliert oder direkt verwendet werden, um eine instance des zu definierenden Workflows auszuführen.
<p:Activity x:Class="Workflows.HelloSeq" xmlns="https://schemas.microsoft.com/netfx/2009/xaml/activities/design" xmlns:p="https://schemas.microsoft.com/netfx/2009/xaml/activities" xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml">
<x:Members>
<x:Property Name="greeting" Type="p:InArgument(x:String)" />
<x:Property Name="name" Type="p:InArgument(x:String)" />
</x:Members>
<p:Sequence>
<p:WriteLine>[greeting & " from workflow"]</p:WriteLine>
<p:WriteLine>[Name]</p:WriteLine>
</p:Sequence>
</p:Activity>
Abbildung 22: In XAML definierter Workflow
Der gleiche XAML-Code wird vom Designer generiert und kann mit benutzerdefinierten Tools generiert werden, was die Verwaltung erheblich vereinfacht. Darüber hinaus ist es auch möglich, wie zuvor gezeigt, Workflows mithilfe von Code zu erstellen. Dies umfasst die Erstellung der Aktivitätshierarchie durch die Verwendung der verschiedenen Aktivitäten im Framework und Ihrer benutzerdefinierten Aktivitäten. Sie haben bereits mehrere Beispiele für Workflows gesehen, die vollständig im Code definiert sind. Im Gegensatz zu WF3 ist es jetzt möglich, einen Workflow im Code zu erstellen und ihn einfach in XAML zu serialisieren. Bietet mehr Flexibilität bei der Modellierung und Verwaltung von Workflowdefinitionen.
Wie ich zeigen werde, benötigen Sie zum Ausführen eines Workflows lediglich eine Aktivität, die eine instance integrierten Code oder eine aus XAML erstellte sein kann. Das Endergebnis der einzelnen Modellierungstechniken ist entweder eine von Activity abgeleitete Klasse oder eine XML-Darstellung, die deserialisiert oder in eine Aktivität kompiliert werden kann.
Optionen zum Ausführen von Workflows
Um einen Workflow auszuführen, benötigen Sie eine Aktivität, die den Workflow definiert. Es gibt zwei typische Möglichkeiten, eine Aktivität abzurufen, die ausgeführt werden kann: Erstellen sie im Code oder Lesen in einer XAML-Datei und Deserialisieren des Inhalts in einer Aktivität. Die erste Option ist einfach, und ich habe bereits einige Beispiele gezeigt. Zum Laden einer XAML-Datei sollten Sie die ActivityXamlServices-Klasse verwenden, die eine statische Load-Methode bereitstellt. Übergeben Sie einfach ein Stream- oder XamlReader-Objekt, und Sie erhalten die im XAML-Code dargestellte Aktivität zurück.
Sobald Sie über eine Aktivität verfügen, ist die einfachste Möglichkeit, sie auszuführen, indem Sie die WorkflowInvoker-Klasse verwenden, wie ich dies in den Komponententests zuvor getan habe. Die Invoke-Methode dieser Klasse verfügt über einen Parameter vom Typ Activity und gibt eine IDictionary-Zeichenfolge<( Object>) zurück. Wenn Sie Argumente an den Workflow übergeben müssen, definieren Sie sie zunächst für den Workflow und übergeben dann die Werte zusammen mit der Aktivität an die Invoke-Methode als Wörterbuch von Name/Wert-Paaren. Ebenso werden alle out- oder In/Out-Argumente, die für den Workflow definiert sind, als Ergebnis der Ausführung der -Methode zurückgegeben. Abbildung 23 enthält ein Beispiel für das Laden eines Workflows aus einer XAML-Datei, das Übergeben von Argumenten an den Workflow und das Abrufen der resultierenden Ausgabeargumente.
AktivitätsberechnungWF;
using (Stream mathXaml = File.OpenRead("Math.xaml"))
{
mathWF = ActivityXamlServices.Load(mathXaml);
}
var outputs = WorkflowInvoker.Invoke(mathWF,
neue Wörterbuchzeichenfolge<, Objekt> {
{ "operand1", 5 },
{ "operand2", 10 },
{ "operation", "add" } });
Assert.AreEqual<int>(15, (int)outputs["result"], "Falsches Ergebnis zurückgegeben");
Abbildung 23: Aufrufen eines "Loose XAML"-Workflows mit ein- und ausgehenden Argumenten
Beachten Sie in diesem Beispiel, dass die Aktivität aus einer XAML-Datei geladen wird und weiterhin Argumente akzeptieren und zurückgeben kann. Der Workflow wurde zur Vereinfachung mithilfe des Designers in Visual Studio entwickelt, konnte aber in einem benutzerdefinierten Designer entwickelt und überall gespeichert werden. Der XAML-Code kann aus einer Datenbank, sharePoint-Bibliothek oder einem anderen Speicher geladen werden, bevor er zur Ausführung an die Runtime übergeben wird.
Die Verwendung von WorkflowInvoker bietet den einfachsten Mechanismus zum Ausführen von kurzlebigen Workflows. Dadurch verhält sich der Workflow im Wesentlichen wie ein Methodenaufruf in Ihrer Anwendung, sodass Sie alle Vorteile von WF einfacher nutzen können, ohne viel Arbeit zum Hosten von WF selbst erledigen zu müssen. Dies ist besonders nützlich, wenn Komponenten ihre Aktivitäten und Workflows getestet werden, da dadurch die Testeinrichtung reduziert wird, die zum Trainieren einer getesteten Komponente erforderlich ist.
Eine weitere gängige Hostingklasse ist die WorkflowApplication, die ein sicheres Handle für einen Workflow bereitstellt, der in der Runtime ausgeführt wird, und ermöglicht es Ihnen, Workflows mit langer Ausführungszeit einfacher zu verwalten. Mit workflowApplication können Sie argumente weiterhin auf die gleiche Weise an den Workflow übergeben wie mit workflowInvoker, aber Sie verwenden die Run-Methode, um die Workflowausführung zu starten. An diesem Punkt beginnt der Workflow mit der Ausführung in einem anderen Thread, und das Steuerelement kehrt an den aufrufenden Code zurück.
Da der Workflow jetzt asynchron ausgeführt wird, möchten Sie in Ihrem Hostingcode wahrscheinlich wissen, wann der Workflow abgeschlossen ist oder ob er eine Ausnahme auslöst usw. Für diese Arten von Benachrichtigungen verfügt die WorkflowApplication-Klasse über eine Reihe von Eigenschaften vom Typ Action<T> , die wie Ereignisse verwendet werden können, um Code hinzuzufügen, um auf bestimmte Bedingungen der Workflowausführung zu reagieren, einschließlich: abgebrochen, nicht behandelte Ausnahme, abgeschlossen, im Leerlauf und Entladen. Wenn Sie einen Workflow mithilfe von WorkflowApplication ausführen, können Sie Code ähnlich wie in Abbildung 24 mithilfe von Aktionen verwenden, um die Ereignisse zu behandeln.
WorkflowApplication wf = new WorkflowApplication(new Flowchart1());
Wf. Completed = delegate(WorkflowApplicationCompletedEventArgs e)
{
Console.WriteLine("Workflow {0} complete", e.InstanceId);
};
Wf. Aborted = delegate(WorkflowApplicationAbortedEventArgs e)
{
Console.WriteLine(e.Reason);
};
Wf. OnUnhandledException =
delegate(WorkflowApplicationUnhandledExceptionEventArgs e)
{
Console.WriteLine(e.UnhandledException.ToString());
return UnhandledExceptionAction.Terminate;
};
Wf. Run();
Abbildung 24: WorkflowAnwendungsaktionen
In diesem Beispiel besteht das Ziel des Hostcodes, einer einfachen Konsolenanwendung, darin, den Benutzer über die Konsole zu benachrichtigen, wenn der Workflow abgeschlossen ist oder ein Fehler auftritt. In einem realen System wird der Host an diesen Ereignissen interessiert sein und wahrscheinlich anders darauf reagieren, um Administratoren Informationen über Fehler bereitzustellen oder die Instanzen besser zu verwalten.
Workflowerweiterungen
Eines der Kernfeatures von WF seit WF3 besteht darin, dass es einfach genug ist, um in jeder .NET-Anwendungsdomäne gehostet zu werden. Da die Runtime in verschiedenen Domänen ausgeführt werden kann und möglicherweise eine angepasste Ausführungssemantik benötigt, müssen verschiedene Aspekte des Laufzeitverhaltens von der Laufzeit externalisiert werden. Hier kommen Workflowerweiterungen ins Spiel. Workflowerweiterungen ermöglichen Es Ihnen als Entwickler, den Hostcode zu schreiben, wenn Sie dies möchten, das Verhalten zur Laufzeit hinzuzufügen, indem Sie es mit benutzerdefiniertem Code erweitern.
Zwei Erweiterungstypen, die der WF-Runtime bekannt sind, sind die Persistenz- und Nachverfolgungserweiterungen. Die Persistenzerweiterung bietet die Kernfunktionen zum Speichern des Zustands eines Workflows in einem permanenten Speicher und zum Abrufen dieses Zustands, wenn er benötigt wird. Der Persistenzerweiterungsversand mit dem Framework umfasst Unterstützung für Microsoft SQL Server. Erweiterungen können jedoch zur Unterstützung anderer Datenbanksysteme oder dauerhafter Speicher geschrieben werden.
Persistenz
Um die Persistenzerweiterung zu verwenden, müssen Sie zunächst eine Datenbank einrichten, die den Zustand enthält. Dies kann mithilfe der SQL-Skripts unter %windir%\Microsoft.NET\Framework\v4.0.30319\sql\<language> erfolgen (z. B. c:\windows\microsoft.net\framework\v4.0.30319\sql\en\). Nach dem Erstellen einer Datenbank führen Sie die beiden Skripts aus, um sowohl die Struktur (SqlWorkflowInstanceStoreSchema.sql) als auch die gespeicherten Prozeduren (SqlWorkflowInstanceStoreLogic.sql) innerhalb der Datenbank zu erstellen.
Nachdem die Datenbank eingerichtet wurde, verwenden Sie sqlWorkflowInstanceStore zusammen mit der WorkflowApplication-Klasse. Sie erstellen zuerst den Speicher und konfigurieren ihn und geben ihn dann an die WorkflowAnwendung an, wie in Abbildung 25 dargestellt.
WorkflowApplication-Anwendung = neue WorkflowAnwendung(Aktivität);
InstanceStore instanceStore = new SqlWorkflowInstanceStore(
@"Data Source=.\\SQLEXPRESS;Integrated Security=True");
InstanceView-Ansicht = instanceStore.Execute(
instanceStore.CreateInstanceHandle(), new CreateWorkflowOwnerCommand(),
TimeSpan.FromSeconds(30));
instanceStore.DefaultInstanceOwner = view. Instanceowner;
Anwendung. InstanceStore = instanceStore;
Abbildung 25: Hinzufügen des SQL-Persistenzanbieters
Es gibt zwei Möglichkeiten, wie der Workflow beibehalten werden kann. Die erste erfolgt durch direkte Verwendung der Persist-Aktivität in einem Workflow. Wenn diese Aktivität ausgeführt wird, wird der Workflowzustand in der Datenbank beibehalten, und der aktuelle Status des Workflows wird gespeichert. Dadurch hat der Workflowautor die Kontrolle darüber, wann es wichtig ist, den aktuellen Zustand des Workflows zu speichern. Die zweite Option ermöglicht es der Hostinganwendung, den Workflowzustand beizubehalten, wenn verschiedene Ereignisse im Workflow instance auftreten, höchstwahrscheinlich, wenn sich der Workflow im Leerlauf befindet. Wenn Sie sich für die PersistableIdle-Aktion für workflowApplication registrieren, kann Ihr Hostcode dann auf das Ereignis reagieren, um anzugeben, ob die instance beibehalten, entladen oder nicht geladen werden soll. Abbildung 26 zeigt eine Hostanwendung, die registriert wird, um benachrichtigt zu werden, wenn sich der Workflow im Leerlauf befindet und dazu führt, dass er beibehalten wird.
Wf. PersistableIdle = (waie) => PersistableIdleAction.Persist;
Abbildung 26: Entladen des Workflows im Leerlauf
Sobald ein Workflow im Leerlauf ist und beibehalten wurde, kann die WF-Runtime ihn aus dem Arbeitsspeicher entladen, wodurch Ressourcen für andere Workflows freigegeben werden, die verarbeitet werden müssen. Sie können ihren Workflow instance entladen lassen, indem Sie die entsprechende Enumeration aus der PersistableIdle-Aktion zurückgeben. Sobald ein Workflow entladen wurde, möchte der Host ihn irgendwann wieder aus dem Persistenzspeicher laden, um ihn fortzusetzen. Dazu muss der workflow-instance mit einem instance-Speicher und dem instance-Bezeichner geladen werden. Dies führt wiederum dazu, dass der instance Speicher aufgefordert wird, den Zustand zu laden. Nachdem der Zustand geladen wurde, kann die Run-Methode für die WorkflowApplication verwendet werden, oder ein Lesezeichen kann wie in Abbildung 27 dargestellt fortgesetzt werden. Weitere Informationen zu Lesezeichen finden Sie im Abschnitt benutzerdefinierte Aktivitäten.
WorkflowApplication-Anwendung = neue WorkflowAnwendung(Aktivität);
Anwendung. InstanceStore = instanceStore;
Anwendung. Load(id);
Anwendung. ResumeBookmark(readLineBookmark, input);
Abbildung 27: Fortsetzen eines persistenten Workflows
Eine der Änderungen in WF4 besteht darin, wie der Zustand von Workflows beibehalten wird und stark auf den neuen Datenverwaltungstechniken von Argumenten und Variablen basiert. Anstatt die gesamte Aktivitätsstruktur zu serialisieren und den Zustand für die Lebensdauer des Workflows beizubehalten, werden nur Bereichsvariablen und Argumentwerte zusammen mit einigen Laufzeitdaten wie Lesezeicheninformationen beibehalten. Beachten Sie in Abbildung 27, dass beim Erneutladen des persistenten instance zusätzlich zur Verwendung des instance-Speichers auch die Aktivität übergeben wird, die den Workflow definiert. Im Wesentlichen wird der Zustand aus der Datenbank auf die angegebene Aktivität angewendet. Diese Technik ermöglicht eine viel größere Flexibilität bei der Versionsverwaltung und führt zu einer besseren Leistung, da der Zustand einen geringeren Speicherbedarf aufweist.
Nachverfolgung
Persistenz ermöglicht es dem Host, prozesse mit langer Ausführungsdauer, einen Lastenausgleich zwischen Instanzen auf Hosts und andere fehlertolerante Verhaltensweisen zu unterstützen. Sobald der Workflow instance abgeschlossen ist, wird der Zustand in der Datenbank jedoch häufig gelöscht, da er nicht mehr nützlich ist. In einer Produktionsumgebung ist es wichtig, Informationen darüber zu erhalten, was ein Workflow derzeit tut und was er getan hat, sowohl für die Verwaltung von Workflows als auch für den Einblick in den Geschäftsprozess. Die Möglichkeit zu verfolgen, was in Ihrer Anwendung geschieht, ist eines der überzeugenden Features der Verwendung der WF-Runtime.
Die Nachverfolgung besteht aus zwei Hauptkomponenten: Nachverfolgungsteilnehmern und Verfolgungsprofilen. Ein Nachverfolgungsprofil definiert, welche Ereignisse und Daten von der Laufzeit nachverfolgt werden sollen. Profile können drei primäre Arten von Abfragen enthalten:
- ActivityStateQuery – wird verwendet, um Aktivitätszustände (z. B. geschlossen) und Variablen oder Argumente zum Extrahieren von Daten zu identifizieren.
- WorkflowInstanceQuery – wird verwendet, um Workflowereignisse zu identifizieren.
- CustomTrackingQuery – wird verwendet, um explizite Aufrufe zum Nachverfolgen von Daten zu identifizieren, normalerweise innerhalb benutzerdefinierter Aktivitäten.
Abbildung 28 zeigt beispielsweise, dass ein TrackingProfile erstellt wird, das eine ActivityStateQuery und eine WorkflowInstanceQuery enthält. Beachten Sie, dass die Abfragen sowohl angeben, wann Informationen gesammelt werden sollen, als auch, welche Daten erfasst werden sollen. Für ActivityStateQuery habe ich eine Liste von Variablen eingefügt, deren Wert extrahiert und den nachverfolgten Daten hinzugefügt werden sollte.
TrackingProfile-Profil = neues TrackingProfile
{
Name = "SimpleProfile",
Abfragen = {
new WorkflowInstanceQuery {
States = { "*" }
},
new ActivityStateQuery {
ActivityName = "WriteLine",
States={ "*" },
Variablen = {"Text" }
}
}
};
Abbildung 28: Erstellen eines Nachverfolgungsprofils
Ein Nachverfolgungsteilnehmer ist eine Erweiterung, die zur Laufzeit hinzugefügt werden kann und für die Verarbeitung von Nachverfolgungsdatensätzen verantwortlich ist, während sie ausgegeben werden. Die TrackingParticipant-Basisklasse definiert eine Eigenschaft, um ein TrackingProfile und eine Track-Methode bereitzustellen, die die Nachverfolgung verarbeitet. Abbildung 29 zeigt einen eingeschränkten benutzerdefinierten Nachverfolgungsteilnehmer, der Daten in die Konsole schreibt. Um den Nachverfolgungsteilnehmer verwenden zu können, muss er mit einem Nachverfolgungsprofil initialisiert und dann der Auflistung erweiterungen im Workflow instance hinzugefügt werden.
public class ConsoleTrackingParticipant : TrackingParticipant
{
protected override void Track(TrackingRecord record, TimeSpan timeout)
{
ActivityStateRecord aRecord = record as ActivityStateRecord;
if (aRecord != NULL)
{
Console.WriteLine("{0} eingegebener Status {1}",
aRecord.Activity.Name, aRecord.State);
foreach (var-Element in aRecord.Arguments)
{
Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine("Variable:{0} has value: {1}",
Artikel. Schlüssel, Element. Wert);
Console.ResetColor();
}
}
}
}
Abbildung 29: Konsolennachverfolgungsteilnehmer
WF wird mit einem EtwTrackingParticipant (ETW = Enterprise Trace for Windows) ausgeliefert, den Sie zur Laufzeit hinzufügen können, um eine hochleistungsfähige Nachverfolgung von Daten zu ermöglichen. ETW ist ein Ablaufverfolgungssystem, das eine native Komponente in Windows ist und von vielen Komponenten und Diensten im Betriebssystem verwendet wird, einschließlich Treibern und anderen Code auf Kernelebene. Die in ETW geschriebenen Daten können mithilfe von benutzerdefiniertem Code oder mit Tools wie dem bevorstehenden Windows Server AppFabric verwendet werden. Für Benutzer, die von WF3 migrieren, ist dies eine Änderung, da der Versand von SQL-Nachverfolgungsteilnehmern als Teil des Frameworks nicht erfolgt. Windows Server AppFabric wird jedoch mit ETW-Consumern ausgeliefert, die die ETW-Daten sammeln und in einer SQL-Datenbank speichern. Ebenso können Sie einen ETW-Consumer erstellen und die Daten in jedem format speichern, das Sie bevorzugen, oder Ihren eigenen Nachverfolgungsteilnehmer schreiben, je nachdem, was für Ihre Umgebung sinnvoller ist.
Erstellen benutzerdefinierter Aktivitäten
Obwohl die Basisaktivitätsbibliothek eine umfangreiche Palette von Aktivitäten für die Interaktion mit Diensten, Objekten und Sammlungen enthält, bietet sie keine Aktivitäten für die Interaktion mit Subsystemen wie Datenbanken, E-Mail-Servern oder Ihren benutzerdefinierten Domänenobjekten und Systemen. Ein Teil der ersten Schritte mit WF4 besteht darin, herauszufinden, welche Kernaktivitäten Sie beim Erstellen von Workflows benötigen oder möchten. Das Tolle ist, dass Sie beim Erstellen von Kernarbeitseinheiten diese zu groben Aktivitäten kombinieren können, die Entwickler in ihren Workflows verwenden können.
Aktivitätsklassenhierarchie
Während eine Aktivität eine Aktivität ist, ist es nicht wahr, dass alle Aktivitäten die gleichen Anforderungen oder Anforderungen haben. Aus diesem Grund gibt es anstelle aller Aktivitäten, die direkt von Activity abgeleitet werden, eine Hierarchie von Aktivitätsbasisklassen, die in Abbildung 30 dargestellt ist, aus der Sie beim Erstellen einer benutzerdefinierten Aktivität auswählen können.
Abbildung 30: Aktivitätsklassenhierarchie
Bei den meisten benutzerdefinierten Aktivitäten leiten Sie entweder von AsyncCodeActivity, CodeActivity oder NativeActivity (oder einer der generischen Varianten) ab oder modellieren Ihre Aktivität deklarativ. Auf hoher Ebene können die vier Basisklassen wie folgt beschrieben werden:
- Aktivität: Wird verwendet, um Aktivitäten zu modellieren, indem andere Aktivitäten erstellt werden, die in der Regel mit XAML definiert werden.
- CodeActivity – eine vereinfachte Basisklasse, wenn Sie Code schreiben müssen, um die Arbeit zu erledigen.
- AsyncCodeActivity: Wird verwendet, wenn Ihre Aktivität einige Aufgaben asynchron ausführt.
- NativeActivity: Wenn Ihre Aktivität Zugriff auf die internen Laufzeitdaten benötigt, z. B. um andere Aktivitäten zu planen oder Lesezeichen zu erstellen.
In den folgenden Abschnitten erstelle ich mehrere Aktivitäten, um zu erfahren, wie die einzelnen Basisklassen verwendet werden. Im Allgemeinen sollten Sie bei der Verwendung der Zu verwendenden Basisklasse mit der Activity-Basisklasse beginnen und prüfen, ob Sie Ihre Aktivität mit deklarativer Logik erstellen können, wie im nächsten Abschnitt gezeigt. Als Nächstes stellt die CodeActivity ein vereinfachtes Modell bereit, wenn Sie feststellen, dass Sie etwas .NET-Code schreiben müssen, um Ihre Aufgabe auszuführen, und die AsyncCodeActivity, wenn Ihre Aktivität asynchron ausgeführt werden soll. Wenn Sie schließlich Ablaufsteuerungsaktivitäten wie die im Framework (z. B. While, Switch, If) schreiben, müssen Sie von der NativeActivity-Basisklasse ableiten, um die untergeordneten Aktivitäten zu verwalten.
Erstellen von Aktivitäten mithilfe des Aktivitäts-Designers
Wenn Sie ein neues Aktivitätsbibliotheksprojekt erstellen oder einem WF-Projekt ein neues Element hinzufügen und die Vorlage Aktivität auswählen, erhalten Sie eine XAML-Datei mit einem leeren Activity-Element. Im Designer präsentiert sich dies als Entwurfsoberfläche, auf der Sie den Körper der Aktivität erstellen können. Um mit einer Aktivität zu beginnen, die mehr als einen Schritt umfasst, ist es in der Regel hilfreich, eine Sequenzaktivität als Textkörper einzuziehen und diese dann mit Ihrer eigentlichen Aktivitätslogik aufzufüllen, da der Text eine einzelne untergeordnete Aktivität darstellt.
Sie können sich die Aktivität ähnlich wie eine Methode für eine Komponente mit Argumenten vorstellen. Für die Aktivität selbst können Sie Argumente zusammen mit ihrer Richtung definieren, um die Schnittstelle der Aktivität zu definieren. Variablen, die Sie innerhalb der Aktivität verwenden möchten, müssen in den Aktivitäten definiert werden, aus denen der Text besteht, z. B. in der zuvor erwähnten Stammsequenz. Beispielsweise können Sie eine NotifyManager-Aktivität erstellen, die zwei einfachere Aktivitäten umfasst: GetManager und SendMail.
Erstellen Sie zunächst ein neues ActivityLibrary-Projekt in Visual Studio 2010, und benennen Sie die Datei Activity1.xaml in NotifyManager.xaml um. Ziehen Sie als Nächstes eine Sequence-Aktivität aus der Toolbox, und fügen Sie sie dem Designer hinzu. Sobald die Sequenz eingerichtet ist, können Sie sie mit untergeordneten Aktivitäten auffüllen, wie in Abbildung 31 dargestellt. (Beachten Sie, dass es sich bei den in diesem Beispiel verwendeten Aktivitäten um benutzerdefinierte Aktivitäten in einem Projekt handelt, auf das verwiesen wird und nicht im Framework verfügbar ist.)
Abbildung 31: Aktivität des Vorgesetzten benachrichtigen
Für diese Aktivität müssen Argumente für den Mitarbeiternamen und den Nachrichtentext verwendet werden. Die GetManager-Aktivität sucht den Vorgesetzten des Mitarbeiters und stellt die E-Mail als OutArgument-Zeichenfolge<> bereit. Schließlich sendet die SendMail-Aktivität eine Nachricht an den Manager. Der nächste Schritt besteht darin, die Argumente für die Aktivität zu definieren, indem Sie das Fenster Argumente am unteren Rand des Designers erweitern und die Informationen für die beiden erforderlichen Eingabeargumente eingeben.
Um diese Elemente zusammenzustellen, müssen Sie in der Lage sein, die eingabeargumente, die in der NotifyManager-Aktivität angegeben sind, an die einzelnen untergeordneten Aktivitäten zu übergeben. Für die GetManager-Aktivität benötigen Sie den Mitarbeiternamen, der ein in-Argument für die Aktivität ist. Sie können den Argumentnamen im Eigenschaftendialogfeld für das Argument employeeName für die GetManager-Aktivität verwenden, wie in Abbildung 31 dargestellt. Die SendMail-Aktivität benötigt die E-Mail-Adresse des Managers und die Nachricht. Für die Nachricht können Sie den Namen des Arguments eingeben, das die Nachricht enthält. Für die E-Mail-Adresse benötigen Sie jedoch eine Möglichkeit, das Out-Argument aus der GetManager-Aktivität an das in-Argument für die SendMail-Aktivität zu übergeben. Dazu benötigen Sie eine Variable.
Nachdem Sie die Sequenzaktivität hervorgehoben haben, können Sie das Variablenfenster verwenden, um eine Variable mit dem Namen "mgrEmail" zu definieren. Nun können Sie diesen Variablennamen sowohl für das Argument ManagerEmail out für die GetManager-Aktivität als auch für das Argument To in für die SendMail-Aktivität eingeben. Wenn die GetManager-Aktivität ausgeführt wird, werden die Ausgabedaten in dieser Variablen gespeichert, und wenn die SendMail-Aktivität ausgeführt wird, liest sie Daten aus dieser Variablen als in-Argument.
Der soeben beschriebene Ansatz ist ein rein deklaratives Modell zum Definieren des Aktivitätstexts. Unter bestimmten Umständen sollten Sie den Textkörper der Aktivität im Code angeben. Ihre Aktivität kann beispielsweise eine Sammlung von Eigenschaften enthalten, die wiederum eine Reihe von untergeordneten Aktivitäten antreiben. Ein Satz benannter Werte, die die Erstellung einer Gruppe von Zuweisen von Aktivitäten steuern, wäre ein Fall, in dem die Verwendung von Code bevorzugt wäre. In diesen Fällen können Sie eine Klasse schreiben, die von der Aktivität abgeleitet ist, und Code in die Implementation-Eigenschaft schreiben, um eine Aktivität (und alle untergeordneten Elemente) zu erstellen, um die Funktionalität Ihrer Aktivität darzustellen. Das Endergebnis ist in beiden Fällen identisch: Ihre Logik wird durch das Komponieren anderer Aktivitäten definiert, nur der Mechanismus, durch den der Text definiert wird, ist anders. Abbildung 32 zeigt die gleiche NotifyManager-Aktivität, die im Code definiert ist.
öffentliche Klasse NotifyManager : Aktivität
{
public InArgument<string> EmployeeName { get; set; }
public InArgument<Zeichenfolge> Message { get; set; }
Protected override Func<Activity> Implementation
{
get
{
return () =>
{
Variable<Zeichenfolge> mgrEmail =
new Variable<string> { Name = "mgrEmail" };
Sequenz s = neue Sequenz
{
Variables = { mgrEmail },
Aktivitäten = {
new GetManager {
EmployeeName =
neue VisualBasicValue-Zeichenfolge<>("EmployeeName"),
Result = mgrEmail,
},
new SendMail {
ToAddress = mgrEmail,
MailBody = new VisualBasicValue<string>("Message"),
Von = "test@contoso.com",
Betreff = "Automatisierte E-Mail"
}
}
};
gibt s zurück;
};
}
set { base. Implementierung = Wert; }
}
}
Abbildung 32: Aktivitätskomposition mithilfe von Code
Beachten Sie, dass die Basisklasse Activity und die Implementation-Eigenschaft eine Func-Aktivität<> ist, um eine einzelne Aktivität zurückzugeben. Dieser Code ähnelt dem, der zuvor beim Erstellen von Workflows gezeigt wurde, und das sollte nicht überraschen, da das Ziel in beiden Fällen darin besteht, eine Aktivität zu erstellen. Darüber hinaus können im Codeansatz Argumente mit Variablen festgelegt werden, oder Sie können Ausdrücke verwenden, um ein Argument mit einem anderen zu verbinden, wie für die Argumente EmployeeName und Message gezeigt wird, wie sie für die beiden untergeordneten Aktivitäten verwendet werden.
Schreiben benutzerdefinierter Aktivitätsklassen
Wenn Sie feststellen, dass Ihre Aktivitätslogik nicht durch das Komponieren anderer Aktivitäten erreicht werden kann, können Sie eine codebasierte Aktivität schreiben. Beim Schreiben von Aktivitäten in Code leiten Sie von der entsprechenden Klasse ab, definieren Argumente und überschreiben dann die Execute-Methode. Die Execute-Methode wird von der Runtime aufgerufen, wenn die Aktivität ihre Arbeit erledigen muss.
Um die im vorherigen Beispiel verwendete SendMail-Aktivität zu erstellen, muss zuerst der Basistyp ausgewählt werden. Es ist zwar möglich, die Funktionalität der SendMail-Aktivität mithilfe der Activity-Basisklasse und des Komponierens von TryCatch<T> - und InvokeMethod-Aktivitäten zu erstellen, aber für die meisten Entwickler ist es natürlicher, zwischen den Basisklassen CodeActivity und NativeActivity zu wählen und Code für die Ausführungslogik zu schreiben. Da diese Aktivität keine Lesezeichen erstellen oder andere Aktivitäten planen muss, kann ich von der CodeActivity-Basisklasse abgeleitet werden. Da diese Aktivität außerdem ein einzelnes Ausgabeargument zurückgibt, sollte es von CodeActivity<TResult> abgeleitet werden, das ein OutArgument-TResult>< bereitstellt. Der erste Schritt besteht darin, mehrere Eingabeargumente für die E-Mail-Eigenschaften zu definieren. Anschließend überschreiben Sie die Execute-Methode, um die Funktionalität der Aktivität zu implementieren. Abbildung 33 zeigt die abgeschlossene Aktivitätsklasse.
öffentliche Klasse SendMail : CodeActivity<SmtpStatusCode>
{
public InArgument<string> To { get; set; }
public InArgument<string> From { get; set; }
public InArgument<string> Subject { get; set; }
public InArgument<string> Body { get; set; }
protected override SmtpStatusCode Execute(
CodeActivityContext-Kontext)
{
Versuch
{
SmtpClient-Client = new SmtpClient();
Kunde. Senden(
From.Get(context), ToAddress.Get(context),
Subject.Get(context), MailBody.Get(context));
}
catch (SmtpException smtpEx)
{
gibt smtpEx.StatusCode zurück;
}
gibt SmtpStatusCode.Ok zurück;
}
}
Abbildung 33: Benutzerdefinierte SendMail-Aktivität
Bei der Verwendung von Argumenten sind mehrere Punkte zu beachten. Ich habe mehrere .NET-Standardeigenschaften mit den Typen InArgument<T> deklariert. So definiert eine codebasierte Aktivität ihre Eingabe- und Ausgabeargumente. Im Code kann ich jedoch nicht einfach auf diese Eigenschaften verweisen, um den Wert des Arguments abzurufen. Ich muss das Argument als eine Art Handle verwenden, um den Wert mithilfe des angegebenen ActivityContext abzurufen. in diesem Fall ein CodeActivityContext. Sie verwenden die Get-Methode der Argumentklasse, um den Wert abzurufen, und die Set-Methode, um einem Argument einen Wert zuzuweisen.
Sobald die Aktivität die Execute-Methode abgeschlossen hat, erkennt die Laufzeit dies und geht davon aus, dass die Aktivität abgeschlossen ist und schließt sie. Bei asynchronen oder lang andauernden Arbeiten gibt es Möglichkeiten, dieses Verhalten zu ändern, die in einem bevorstehenden Abschnitt erläutert werden, aber in diesem Fall ist die Arbeit nach dem Senden der E-Mail abgeschlossen.
Nun untersuchen wir eine Aktivität mithilfe der NativeActivity-Basisklasse, um untergeordnete Aktivitäten zu verwalten. Ich werde eine Iteratoraktivität erstellen, die eine bestimmte Anzahl von untergeordneten Aktivitäten ausführt. Zunächst benötige ich ein Argument, um die Anzahl der auszuführenden Iterationen, eine Eigenschaft für die Auszuführende Aktivität und eine Variable für die aktuelle Anzahl von Iterationen zu enthalten. Die Execute-Methode ruft eine BeginIteration-Methode auf, um die anfängliche Iteration zu starten, und nachfolgende Iterationen werden auf die gleiche Weise aufgerufen. Beachten Sie in Abbildung 34, dass Variablen mit der Get- und Set-Methode auf die gleiche Weise wie Argumente bearbeitet werden.
Öffentliche Klasse Iterator : NativeActivity
{
public Activity Body { get; set; }
public InArgument<int> RequestedIterations { get; set; }
public Variable<int> CurrentIteration { get; set; }
public Iterator()
{
CurrentIteration = new Variable<int> { Default = 0 };
}
protected override void Execute(NativeActivityContext context)
{
BeginIteration(kontext);
}
private void BeginIteration(NativeActivityContext-Kontext)
{
if (RequestedIterations.Get(context) > CurrentIteration.Get(context))
{
Kontext. ScheduleActivity(Body,
new CompletionCallback(ChildComplete),
new FaultCallback(ChildFaulted));
}
}
}
Abbildung 34: Native Iteratoraktivität
Wenn Sie sich die BeginIteration-Methode genau ansehen, werden Sie den Aufruf der ScheduleActivity-Methode bemerken. So kann eine Aktivität mit der Laufzeit interagieren, um eine andere Aktivität für die Ausführung zu planen, und dies liegt daran, dass Sie diese Funktionalität benötigen, die Sie von NativeActivity ableiten. Beachten Sie außerdem, dass beim Aufrufen der ScheduleActivity-Methode für ActivityExecutionContext zwei Rückrufmethoden bereitgestellt werden. Da Sie nicht wissen, welche Aktivität als Textkörper verwendet wird oder wie lange die Ausführung dauert, und DA WF eine stark asynchrone Programmierumgebung ist, werden Rückrufe verwendet, um Ihre Aktivität zu benachrichtigen, wenn eine untergeordnete Aktivität abgeschlossen ist, sodass Sie Code schreiben können, um zu reagieren.
Der zweite Rückruf ist ein FaultCallback, der aufgerufen wird, wenn die untergeordnete Aktivität eine Ausnahme auslöst. In diesem Rückruf erhalten Sie die Ausnahme und haben die Möglichkeit, über ActivityFaultContext der Laufzeit anzugeben, dass der Fehler behandelt wurde, wodurch er nicht mehr sprudelt und die Aktivität aus ihr herausrückt. Abbildung 35 zeigt die Rückrufmethoden für die Iteratoraktivität, bei der beide die nächste Iteration planen und der FaultCallback den Fehler verarbeitet, damit die Ausführung mit der nächsten Iteration fortgesetzt werden kann.
private void ChildComplete(NativeActivityContext-Kontext,
ActivityInstance instance)
{
CurrentIteration.Set(context, CurrentIteration.Get(context) + 1);
BeginIteration(kontext);
}
private void ChildFaulted(NativeActivityFaultContext-Kontext, Ausnahme z. B.,
ActivityInstance instance)
{
CurrentIteration.Set(context, CurrentIteration.Get(context) + 1);
Kontext. HandleFault();
}
Abbildung 35: Aktivitätsrückrufe
Wenn Ihre Aktivität Arbeiten ausführen muss, die möglicherweise lang dauern können, kann diese Arbeit idealerweise an einen anderen Thread übergeben werden, damit der Workflowthread zum Fortsetzen der Verarbeitung anderer Aktivitäten oder zur Verarbeitung anderer Workflows verwendet werden kann. Das einfache Starten von Threads und das Zurückgeben des Steuerelements an die Runtime kann jedoch Zu Problemen führen, da die Runtime die von Ihnen erstellten Threads nicht überwacht. Wenn die Runtime festgestellt hat, dass sich der Workflow im Leerlauf befand, kann der Workflow ihre Rückrufmethoden entladen, entsorgen, oder die Laufzeit könnte davon ausgehen, dass Ihre Aktivität abgeschlossen ist und zur nächsten Aktivität im Workflow wechselt. Um die asynchrone Programmierung in Ihrer Aktivität zu unterstützen, leiten Sie von AsyncCodeActivity oder AsyncCodeActivity<T> ab.
Abbildung 36 zeigt ein Beispiel für eine asynchrone Aktivität, die einen RSS-Feed lädt. Diese Signatur der execute-Methode unterscheidet sich für die asynchronen Aktivitäten dadurch, dass sie ein IAsyncResult zurückgibt. Sie schreiben den Code in die execute-Methode, um den asynchronen Vorgang zu beginnen. Überschreiben Sie die EndExecute-Methode, um den Rückruf zu behandeln, wenn Ihr asynchroner Vorgang abgeschlossen ist und das Ergebnis der Ausführung zurückgibt.
public sealed class GetFeed : AsyncCodeActivity<SyndicationFeed>
{
public InArgument<Uri> FeedUrl { get; set; }
protected override IAsyncResult BeginExecute(
AsyncCodeActivityContext-Kontext, AsyncCallback-Rückruf,
Objektzustand)
{
var req = (HttpWebRequest)HttpWebRequest.Create(
FeedUrl.Get(context));
Req. Method = "GET";
Kontext. UserState = req;
req zurückgeben. BeginGetResponse(new AsyncCallback(callback), state);
}
protected override SyndicationFeed EndExecute(
AsyncCodeActivityContext-Kontext, IAsyncResult-Ergebnis)
{
HttpWebRequest req = Context. UserState als HttpWebRequest;
WebResponse wr = req. EndGetResponse(result);
SyndicationFeed localFeed = SyndicationFeed.Load(
XmlReader.Create(wr. GetResponseStream()));
localFeed zurückgeben;
}
}
Abbildung 36: Erstellen asynchroner Aktivitäten
Zusätzliche Aktivitätskonzepte
Nachdem Sie nun die Grundlagen des Erstellens von Aktivitäten mithilfe der Basisklassen, Argumente und Variablen und der Verwaltung der Ausführung kennengelernt haben; Ich werde kurz auf einige erweiterte Features eingehen, die Sie bei der Aktivitätsentwicklung verwenden können.
Lesezeichen ermöglichen es einem Aktivitätsautor, einen Wiederaufnahmepunkt bei der Ausführung eines Workflows zu erstellen. Nachdem ein Lesezeichen erstellt wurde, kann es fortgesetzt werden, um die Workflowverarbeitung dort fortzusetzen, wo sie aufgehört hat. Lesezeichen werden als Teil des Zustands gespeichert, sodass es im Gegensatz zu den asynchronen Aktivitäten nach dem Erstellen eines Lesezeichens kein Problem darstellt, dass der Workflow instance beibehalten und entladen werden soll. Wenn der Host den Workflow fortsetzen möchte, lädt er den Workflow instance und ruft die ResumeBookmark-Methode auf, um den instance dort fortzusetzen, wo er aufgehört hat. Beim Fortsetzen von Lesezeichen können auch Daten an die Aktivität übergeben werden. Abbildung 37 zeigt eine ReadLine-Aktivität, die ein Lesezeichen zum Empfangen von Eingaben erstellt und eine Rückrufmethode registriert, die beim Eintreffen der Daten aufgerufen wird. Die Laufzeit weiß, wann eine Aktivität über herausragende Lesezeichen verfügt, und schließt die Aktivität erst, wenn das Lesezeichen fortgesetzt wurde. Die ResumeBookmark-Methode kann für die WorkflowApplication-Klasse verwendet werden, um Daten an das benannte Lesezeichen zu senden und das BookmarkCallback zu signalisieren.
öffentliche Klasse ReadLine : NativeActivity-Zeichenfolge<>
{
public OutArgument<string> InputText { get; set; }
protected override void Execute(NativeActivityContext context)
{
Kontext. CreateBookmark("ReadLine",
new BookmarkCallback(BookmarkResumed));
}
private void BookmarkResumed(NativeActivityContext-Kontext,
Lesezeichen bk, Objektzustand)
{
Result.Set(context, state);
}
}
Abbildung 37: Erstellen eines Lesezeichens
Ein weiteres leistungsstarkes Feature für Aktivitätsautoren ist das Konzept einer ActivityAction. ActivityAction ist das Workflowäquivalent der Action-Klasse im imperativen Code: Beschreibt einen gemeinsamen Delegaten; Für einige ist die Idee einer Vorlage jedoch leichter zu verstehen. Betrachten Sie die ForEach<T-Aktivität> , mit der Sie einen Satz von Daten durchlaufen und eine untergeordnete Aktivität für jedes Datenelement planen können. Sie benötigen eine Möglichkeit, dem Consumer Ihrer Aktivität das Definieren des Textkörpers und die Verwendung des Datenelements vom Typ T zu ermöglichen. Im Wesentlichen benötigen Sie eine Aktivität, die ein Element vom Typ T akzeptieren und darauf reagieren kann. ActivityAction<T> wird verwendet, um dieses Szenario zu aktivieren, und stellt einen Verweis auf das Argument und die Definition einer Aktivität bereit, um das Element zu verarbeiten. Sie können ActivityAction in Ihren benutzerdefinierten Aktivitäten verwenden, damit Consumer Ihrer Aktivität an entsprechenden Stellen ihre eigenen Schritte hinzufügen können. Auf diese Weise können Sie Workflow- oder Aktivitätsvorlagen einer Art erstellen, in denen ein Consumer die relevanten Teile ausfüllen kann, um die Ausführung für ihre Verwendung anzupassen. Sie können auch activityFunc<TResult> und die zugehörigen Alternativen verwenden, wenn der Delegat, den Ihre Aktivität aufrufen muss, einen Wert zurückgibt.
Schließlich können Aktivitäten überprüft werden, um sicherzustellen, dass sie ordnungsgemäß konfiguriert sind, indem einzelne Einstellungen überprüft, zulässige untergeordnete Aktivitäten eingeschränkt werden usw. Für die allgemeine Notwendigkeit, ein bestimmtes Argument zu erfordern, können Sie der Eigenschaftsdeklaration in der Aktivität ein RequiredArgument-Attribut hinzufügen. Erstellen Sie im Konstruktor Ihrer Aktivität eine Einschränkung, und fügen Sie sie der Auflistung der Einschränkungen hinzu, die in der Activity-Klasse aufgetaucht sind. Eine Einschränkung ist eine Aktivität, die geschrieben wird, um die Zielaktivität zu überprüfen und die Gültigkeit sicherzustellen. Abbildung 38 zeigt den Konstruktor für die Iteratoraktivität, die überprüft, ob die RequestedIterations-Eigenschaft festgelegt ist. Schließlich können Sie durch Überschreiben der CacheMetadata-Methode die AddValidationError-Methode für den ActivityMetdata-Parameter aufrufen, um Validierungsfehler für Ihre Aktivität hinzuzufügen.
var act = new DelegateInArgument<Iterator> { Name = "constraintArg" };
var vctx = new DelegateInArgument<ValidationContext>();
Einschränkungs-Iteratorkons<> = neuer Einschränkungs-Iterator<>
{
Body = new ActivityAction<Iterator, ValidationContext>
{
Argument1 = act,
Argument2 = vctx,
Handler = new AssertValidation
{
Message = "Iteration muss bereitgestellt werden",
PropertyName = "RequestedIterations",
Assertion = new InArgument<bool>(
(e) => act. Get(e). RequestedIterations != null)
}
}
};
Basis. Constraints.Add(cons);
Abbildung 38: Einschränkungen
Aktivitätsdesigner
Einer der Vorteile von WF ist, dass Sie Ihre Anwendungslogik deklarativ programmieren können, aber die meisten Benutzer möchten XAML nicht von Hand schreiben, weshalb die Designererfahrung in WF so wichtig ist. Beim Erstellen benutzerdefinierter Aktivitäten möchten Sie wahrscheinlich auch einen Designer erstellen, um die Anzeige- und visuelle Interaktionserfahrung für Die Verbraucher Ihrer Aktivität bereitzustellen. Der Designer für WF basiert auf Windows Presentation Foundation was bedeutet, dass Sie über alle Möglichkeiten des Stylings, der Trigger, der Datenbindung und aller anderen großartigen Tools zum Erstellen einer umfangreichen Benutzeroberfläche für Ihren Designer verfügen. Darüber hinaus bietet WF einige Benutzersteuerelemente, die Sie in Ihrem Designer verwenden können, um die Aufgabe zu vereinfachen, eine einzelne untergeordnete Aktivität oder eine Sammlung von Aktivitäten anzuzeigen. Die vier primären Steuerelemente sind:
- ActivityDesigner – WPF-Stammsteuerelement, das in Aktivitätsdesignern verwendet wird
- WorkflowItemPresenter – wird verwendet, um eine einzelne Aktivität anzuzeigen
- WorkflowItemsPresenter – wird verwendet, um eine Auflistung untergeordneter Aktivitäten anzuzeigen
- ExpressionTextBox – wird verwendet, um die direkte Bearbeitung von Ausdrücken wie Argumenten zu ermöglichen
WF4 enthält eine ActivityDesignerLibrary-Projektvorlage und eine ActivityDesigner-Elementvorlage, die zum anfänglichen Erstellen der Artefakte verwendet werden können. Nachdem Sie den Designer initialisiert haben, können Sie mit den oben genannten Elementen das Erscheinungsbild Ihrer Aktivität anpassen, indem Sie festlegen, wie Daten und visuelle Darstellungen untergeordneter Aktivitäten angezeigt werden. Innerhalb des Designers haben Sie Zugriff auf ein ModelItem, das eine Abstraktion über die tatsächliche Aktivität darstellt, und alle Eigenschaften der Aktivität.
Das WorkflowItemPresenter-Steuerelement kann verwendet werden, um eine einzelne Aktivität anzuzeigen, die Teil Ihrer Aktivität ist. Um beispielsweise die Möglichkeit zu bieten, eine Aktivität auf die zuvor gezeigte Iterator-Aktivität zu ziehen und die in der Iteractor-Aktivität enthaltene Aktivität anzuzeigen, können Sie den in Abbildung 39 gezeigten XAML-Code verwenden, um den WorkflowItemPresenter an modelItem.Body zu binden. Abbildung 40 zeigt den Designer, der auf einer Workflowentwurfsoberfläche verwendet wird.
<sap:ActivityDesigner x:Class="CustomActivities.Presentation.IteratorDesigner"
xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sap="clr-namespace:System.Activities.Presentation;
assembly=System.Activities.Presentation"
xmlns:sapv="clr-namespace:System.Activities.Presentation.View;
assembly=System.Activities.Presentation">
<Raster>
<Border BorderBrush="MidnightBlue" BorderThickness="3" CornerRadius="10">
<sap:WorkflowItemPresenter MinHeight="50"
Item="{Binding Path=ModelItem.Body, Mode=TwoWay}"
HintText="Text hier hinzufügen" />
</Grenze>
</Raster>
</sap:ActivityDesigner>
Abbildung 39: WorkflowItemPresenter im Aktivitäts-Designer
Abbildung 40: Iterator-Designer
WorkflowItemsPresenter bietet ähnliche Funktionen zum Anzeigen mehrerer Elemente, z. B. der untergeordneten Elemente in einer Sequenz. Sie können eine Vorlage und Ausrichtung für die Anzeige der Elemente sowie eine Spacer-Vorlage definieren, um visuelle Elemente zwischen Aktivitäten wie Connectors, Pfeilen oder interessanten Elementen hinzuzufügen. Abbildung 41 zeigt einen einfachen Designer für eine benutzerdefinierte Sequenzaktivität, die die untergeordneten Aktivitäten mit einer horizontalen Linie zwischen beiden anzeigt.
<sap:ActivityDesigner x:Class="CustomActivities.Presentation.CustomSequenceDesigner"
xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sap="clr-namespace:System.Activities.Presentation;
assembly=System.Activities.Presentation"
xmlns:sapv="clr-namespace:System.Activities.Presentation.View;
assembly=System.Activities.Presentation">
<Raster>
<StackPanel>
<Border BorderBrush="Goldenrod" BorderThickness="3">
<sap:WorkflowItemsPresenter HintText="Aktivitäten hier ablegen"
Items="{Binding Path=ModelItem.ChildActivities}">
<sap:WorkflowItemsPresenter.SpacerTemplate>
<DataTemplate>
<Rechteck Height="3" Width="40"
Fill="MidnightBlue" Margin="5" />
</Datatemplate>
</sap:WorkflowItemsPresenter.SpacerTemplate>
<sap:WorkflowItemsPresenter.ItemsPanel>
<Itemspaneltemplate>
<StackPanel Orientation="Vertical"/>
</Itemspaneltemplate>
</sap:WorkflowItemsPresenter.ItemsPanel>
</sap:WorkflowItemsPresenter>
</Grenze>
</Stackpanel>
</Raster>
</sap:ActivityDesigner>
Abbildung 41: Benutzerdefinierter Sequenz-Designer mithilfe von WorkflowItemsPresenter
Abbildung 42: Sequenz-Designer
Schließlich ermöglicht das ExpressionTextBox-Steuerelement die direkte Bearbeitung von Aktivitätsargumenten im Designer zusätzlich zum Eigenschaftenraster. In Visual Studio 2010 umfasst dies intellisense-Unterstützung für die eingegebenen Ausdrücke. Abbildung 43 zeigt einen Designer für die zuvor erstellte GetManager-Aktivität, die die Bearbeitung der Argumente EmployeeName und ManagerEmail innerhalb des Designers ermöglicht. Der tatsächliche Designer ist in Abbildung 44 dargestellt.
<sap:ActivityDesigner x:Class="CustomActivities.Presentation.GetManagerDesigner"
xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sap="clr-namespace:System.Activities.Presentation;
assembly=System.Activities.Presentation"
xmlns:sapv="clr-namespace:System.Activities.Presentation.View;
assembly=System.Activities.Presentation"
xmlns:sapc="clr-namespace:System.Activities.Presentation.Converters;
assembly=System.Activities.Presentation">
<sap:ActivityDesigner.Resources>
<sapc:ArgumentToExpressionConverter
x:Key="ArgumentToExpressionConverter"
x:Uid="swdv:ArgumentToExpressionConverter_1" />
</sap:ActivityDesigner.Resources>
<Raster>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<Rowdefinition/>
<Rowdefinition/>
<Rowdefinition/>
</Grid.RowDefinitions>
<TextBlock VerticalAlignment="Center"
HorizontalAlignment="Center">Employee:</TextBlock>
<sapv:ExpressionTextBox Grid.Column="1"
Expression="{Binding Path=ModelItem.EmployeeName, Mode=TwoWay,
Converter={StaticResource ArgumentToExpressionConverter},
ConverterParameter=In}" OwnerActivity="{Binding Path=ModelItem}"
MinLines="1" MaxLines="1" MinWidth="50"
HintText="< Mitarbeitername>" />
<TextBlock Grid.Row="1" VerticalAlignment="Center"
HorizontalAlignment="Center">Mgr Email:</TextBlock>
<sapv:ExpressionTextBox Grid.Column="2" Grid.Row="1"
UseLocationExpression="True"
Expression="{Binding Path=ModelItem.ManagerEmail, Mode=TwoWay,
Converter={StaticResource ArgumentToExpressionConverter},
ConverterParameter=Out}" OwnerActivity="{Binding Path=ModelItem}"
MinLines="1" MaxLines="1" MinWidth="50" />
</Raster>
</sap:ActivityDesigner>
Abbildung 43: Verwenden von ExpressionTextBox zum Bearbeiten von Argumenten im Designer
Abbildung 44: GetManager-Aktivitäts-Designer
Die hier vorgestellten Beispieldesigner waren einfach, um die spezifischen Features hervorzuheben, aber die volle Leistungsfähigkeit von WPF steht Ihnen zur Verfügung, um Ihre Aktivitäten einfacher zu konfigurieren und natürlicher für Ihre Verbraucher zu verwenden. Nachdem Sie einen Designer erstellt haben, gibt es zwei Möglichkeiten, ihn der Aktivität zuzuordnen: Anwenden eines Attributs auf die Aktivitätsklasse oder Implementieren der IRegisterMetadata-Schnittstelle. Damit die Aktivitätsdefinition die Auswahl des Designers steuern kann, wenden Sie einfach das DesignerAttribute auf die Aktivitätsklasse an, und geben Sie den Typ für den Designer an. dies funktioniert genau wie in WF3. Für eine lose gekoppelte Implementierung können Sie die IRegisterMetadata-Schnittstelle implementieren und die Attribute definieren, die Sie auf die Aktivitätsklasse und die Eigenschaften anwenden möchten. Abbildung 45 zeigt eine Beispielimplementierung zum Anwenden der Designer für zwei benutzerdefinierte Aktivitäten. Visual Studio ermittelt Ihre Implementierung und ruft sie automatisch auf, während ein benutzerdefinierter Designerhost, der als Nächstes erläutert wird, die Methode explizit aufruft.
Metadaten der öffentlichen Klasse : IRegisterMetadata
{
public void Register()
{
AttributeTableBuilder builder = new AttributeTableBuilder();
Bauherr. AddCustomAttributes(typeof(SendMail), new Attribute[] {
new DesignerAttribute(typeof(SendMailDesigner)});
Bauherr. AddCustomAttributes(typeof(GetManager), new Attribute[]{
new DesignerAttribute(typeof(GetManagerDesigner))});
MetadataStore.AddAttributeTable(builder. CreateTable());
}
}
Abbildung 45: Registrieren von Designern für Aktivitäten
Erneutes Hosten des Workflow-Designers
In der Vergangenheit wollten Entwickler ihren Benutzern häufig die Möglichkeit bieten, Workflows zu erstellen oder zu bearbeiten. In früheren Versionen von Windows Workflow Foundation war dies möglich, aber keine triviale Aufgabe. Mit dem Wechsel zu WPF kommt ein neuer Designer und das erneute Hosten war ein erstklassiger Anwendungsfall für das Entwicklungsteam. Sie können den Designer in einer benutzerdefinierten WPF-Anwendung wie in Abbildung 46 mit wenigen XAML-Zeilen und wenigen Codezeilen hosten.
Abbildung 46: Neu gehosteter Workflow-Designer
Der XAML-Code für das Fenster, Abbildung 47, verwendet ein Raster, um drei Spalten zu layouten, und legt dann ein Rahmensteuerelement in jede Zelle ein. In der ersten Zelle wird die Toolbox deklarativ erstellt, kann aber auch im Code erstellt werden. Für die Designeransicht selbst und das Eigenschaftenraster gibt es zwei leere Rahmensteuerelemente in jeder der verbleibenden Zellen. Diese Steuerelemente werden im Code hinzugefügt.
<Fenster x:Class="WorkflowEditor.MainWindow"
xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:sapt="clr-namespace:System.Activities.Presentation.Toolbox;
assembly=System.Activities.Presentation"
Title="Workflow-Editor" Height="500" Width="700" >
<Window.Resources>
<sys:String x:Key="AssemblyName">System.Activities, Version=4.0.0.0,
Culture=neutral, PublicKeyToken=31bf3856ad364e35</sys:String>
<sys:String x:Key="MyAssemblyName">CustomActivities</sys:String>
</Window.Resources>
<Grid x:Name="DesignerGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*" />
<ColumnDefinition Width="7*" />
<ColumnDefinition Width="3*" />
</Grid.ColumnDefinitions>
<Randbereich>
<sapt:ToolboxControl>
<sapt:ToolboxControl.Categories>
<sapt:ToolboxCategory CategoryName="Basic">
<sapt:ToolboxItemWrapper
AssemblyName="{StaticResource AssemblyName}" >
<sapt:ToolboxItemWrapper.ToolName>
System.Activities.Statements.Sequence
</sapt:ToolboxItemWrapper.ToolName>
</sapt:ToolboxItemWrapper>
<sapt:ToolboxItemWrapper
AssemblyName="{StaticResource MyAssemblyName}">
<sapt:ToolboxItemWrapper.ToolName>
CustomActivities.SendMail
</sapt:ToolboxItemWrapper.ToolName>
</sapt:ToolboxItemWrapper>
</sapt:ToolboxCategory>
</sapt:ToolboxControl.Categories>
</sapt:ToolboxControl>
</Grenze>
<Border Name="DesignerBorder" Grid.Column="1"
BorderBrush="Salmon" BorderThickness="3" />
<Rahmen x:Name="PropertyGridExpander" Grid.Column="2" />
</Raster>
</Fenster>
Abbildung 47: Designer erneutes Hosten von XAML
Der Code zum Initialisieren des Designers und des Eigenschaftenrasters ist in Abbildung 48 dargestellt. Der erste Schritt in der OnInitialized-Methode besteht darin, die Designer für alle Aktivitäten zu registrieren, die im Workflow-Designer verwendet werden. Die DesignerMetadata-Klasse registriert die zugeordneten Designer für die Aktivitäten, die mit dem Framework ausgeliefert werden, und die Metadata-Klasse registriert die Designer für meine benutzerdefinierten Aktivitäten. Erstellen Sie als Nächstes die WorkflowDesigner-Klasse, und verwenden Sie die Eigenschaften View und PropertyInspectorView, um auf die UIElement-Objekte zuzugreifen, die zum Rendern erforderlich sind. Der Designer wird mit einer leeren Sequenz initialisiert, Aber Sie können auch Menüs hinzufügen und den Workflow-XAML aus einer Vielzahl von Quellen laden, einschließlich des Dateisystems oder einer Datenbank.
public MainWindow()
{
InitializeComponent();
}
protected override void OnInitialized(EventArgs e) {
Basis. OnInitialized(e);
RegisterDesigners();
PlaceDesignerAndPropGrid();
}
private void RegisterDesigners() {
new DesignerMetadata(). Register();
new CustomActivities.Presentation.Metadata(). Register();
}
private void PlaceDesignerAndPropGrid() {
WorkflowDesigner-Designer = new WorkflowDesigner();
Designer. Load(new System.Activities.Statements.Sequence());
DesignerBorder.Child = Designer. Ansehen;
PropertyGridExpander.Child = Designer. PropertyInspectorView;
}
Abbildung 48: Designer erneutes Hosten von Code
Mit diesem wenig Code und Markup können Sie einen Workflow bearbeiten, Aktivitäten per Drag and Drop aus der Toolbox ziehen, Variablen im Workflow konfigurieren und Argumente für die Aktivitäten bearbeiten. Sie können den Text des XAML-Codes auch abrufen, indem Sie Flush im Designer aufrufen und dann auf die Text-Eigenschaft des WorkflowDesigner verweisen. Es gibt viel mehr, was Sie tun können, und der Einstieg ist viel einfacher als zuvor.
Workflowdienste
Wie bereits erwähnt, ist einer der Hauptschwerpunkte in dieser Version von Windows Workflow Foundation eine engere Integration in Windows Communication Foundation. Das Beispiel in der exemplarischen Vorgehensweise der Aktivität hat gezeigt, wie ein Dienst aus einem Workflow aufgerufen wird, und in diesem Abschnitt wird erläutert, wie ein Workflow als WCF-Dienst verfügbar gemacht wird.
Erstellen Sie zunächst ein neues Projekt, und wählen Sie das PROJEKT WCF-Workflowdienst aus. Sobald die Vorlage abgeschlossen ist, finden Sie ein Projekt mit einer web.config-Datei und einer Datei mit einer XAMLX-Erweiterung. Die XAMLX-Datei ist ein deklarativer Dienst oder Workflowdienst und bietet wie andere WCF-Vorlagen eine funktionierende, wenn auch einfache Dienstimplementierung, die eine Anforderung empfängt und eine Antwort zurückgibt. In diesem Beispiel werde ich den Vertrag ändern, um einen Vorgang zu erstellen, um eine Spesenabrechnung zu erhalten und einen Indikator zurückzugeben, dass der Bericht empfangen wurde. Fügen Sie zunächst eine ExpenseReport-Klasse zum Projekt hinzu, wie sie in Abbildung 49 dargestellt ist.
[DataContract]
öffentliche Klasse ExpenseReport
{
[DataMember]
public DateTime FirstDateOfTravel { get; set; }
[DataMember]
public double TotalAmount { get; set; }
[DataMember]
öffentliche Zeichenfolge EmployeeName { get; set; }
}
Abbildung 49: Vertragstyp spesenabrechnung
Ändern Sie im Workflow-Designer den Typ der Variablen "data" in den Variablen in den soeben definierten ExpenseReport-Typ (möglicherweise müssen Sie das Projekt erstellen, damit der Typ im Dialogfeld für die Typauswahl angezeigt wird). Aktualisieren Sie die Empfangsaktivität, indem Sie auf die Schaltfläche Inhalt klicken und den Typ im Dialogfeld in den Typ ExpenseReport ändern, der übereinstimmen soll. Aktualisieren Sie die Aktivitätseigenschaften, um einen neuen OperationName und ServiceContractName zu definieren, wie in Abbildung 50 dargestellt.
Abbildung 50: Konfigurieren des Empfangsspeicherorts
Jetzt kann für die SendReply-Aktivität der Wert auf True festgelegt werden, um den Empfangsindikator zurückzugeben. Offensichtlich könnten Sie in einem komplizierteren Beispiel die volle Leistungsfähigkeit des Workflows nutzen, um Informationen in der Datenbank zu speichern, die Überprüfung des Berichts durchzuführen usw. vor dem Ermitteln der Antwort. Das Navigieren zum Dienst mit der WCFTestClient-Anwendung zeigt, wie die Aktivitätskonfiguration den verfügbar gemachten Dienstvertrag definiert, wie in Abbildung 51 dargestellt.
Abbildung 51: Vom Workflowdienst generierter Vertrag
Nachdem Sie einen Workflowdienst erstellt haben, müssen Sie ihn hosten, genau wie bei jedem WCF-Dienst. Im vorherigen Beispiel wurde die einfachste Option für das Hosting verwendet: IIS. Um einen Workflowdienst in Ihrem eigenen Prozess zu hosten, sollten Sie die WorkflowServiceHost-Klasse verwenden, die sich in der System.ServiceModel.Activities-Assembly befindet. Beachten Sie, dass in der System.WorkflowServices-Assembly eine weitere Klasse mit demselben Namen vorhanden ist, die zum Hosten von Workflowdiensten verwendet wird, die mithilfe der WF3-Aktivitäten erstellt wurden. Im einfachsten Fall des Selbsthostings können Sie Code wie in Abbildung 52 verwenden, um Ihren Dienst zu hosten und Endpunkte hinzuzufügen.
WorkflowServiceHost-Host = neu
WorkflowServiceHost(XamlServices.Load("ExpenseReportService.xamlx"),
new Uri("https://localhost:9897/Services/Expense"));
Host. AddDefaultEndpoints();
Host. Description.Behaviors.Add(
new ServiceMetadataBehavior { HttpGetEnabled = true });
Host. Open();
Console.WriteLine("Host is open");
Console.ReadLine();
Abbildung 52: Selbsthosting des Workflowdiensts
Wenn Sie die verwalteten Hostingoptionen in Windows nutzen möchten, können Sie Ihren Dienst in IIS hosten, indem Sie die XAMLX-Datei in das Verzeichnis kopieren und alle erforderlichen Konfigurationsinformationen für Verhalten, Bindungen, Endpunkte usw. angeben. in der datei web.config. Wenn Sie ein neues Projekt in Visual Studio mithilfe einer der Projektvorlagen des deklarativen Workflowdiensts erstellen, wird ein Projekt mit einer web.config-Datei und der in einem XAMLX definierte Workflowdienst bereitgestellt.
Wenn Sie die WorkflowServiceHost-Klasse zum Hosten Ihres Workflowdiensts verwenden, müssen Sie weiterhin in der Lage sein, die Workflowinstanzen zu konfigurieren und zu steuern. Zum Steuern des Diensts gibt es einen neuen Standardendpunkt namens WorkflowControlEndpoint. Die Begleitklasse WorkflowControlClient stellt einen vordefinierten Clientproxy bereit. Sie können einen WorkFlowControlEndpoint für Ihren Dienst verfügbar machen und workflowControlClient verwenden, um neue Instanzen von Workflows zu erstellen und auszuführen oder ausgeführte Workflows zu steuern. Sie verwenden dasselbe Modell, um Dienste auf dem lokalen Computer zu verwalten, oder Sie können es zum Verwalten von Diensten auf einem Remotecomputer verwenden. Abbildung 53 zeigt ein aktualisiertes Beispiel für das Verfügbarmachen des Workflowsteuerungsendpunkts in Ihrem Dienst.
WorkflowServiceHost-Host = neu
WorkflowServiceHost("ExpenseReportService.xamlx",
new Uri("https://localhost:9897/Services/Expense"));
Host. AddDefaultEndpoints();
WorkflowControlEndpoint wce = new WorkflowControlEndpoint(
new NetNamedPipeBinding(),
new EndpointAddress("net.pipe://localhost/Expense/WCE"));
Host. AddServiceEndpoint(wce);
Host. Open();
Abbildung 53: Workflowsteuerungsendpunkt
Da Sie workflowInstance nicht direkt erstellen, gibt es zwei Optionen zum Hinzufügen von Erweiterungen zur Runtime. Erstens können Sie dem WorkflowServiceHost einige Erweiterungen hinzufügen, die mit Ihren Workflowinstanzen ordnungsgemäß initialisiert werden. Die zweite ist die Verwendung der Konfiguration. Das Nachverfolgungssystem kann beispielsweise vollständig in der Anwendungskonfigurationsdatei definiert werden. Sie definieren die Überwachungsteilnehmer und die Nachverfolgungsprofile in der Konfigurationsdatei und wenden mithilfe eines Verhaltens einen bestimmten Teilnehmer auf einen Dienst an, wie in Abbildung 54 dargestellt.
<system.serviceModel>
<tracking>
<participants>
<add name="EtwTrackingParticipant"
type="System.Activities.Tracking.EtwTrackingParticipant, System.Activities, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
profileName="HealthMonitoring_Tracking_Profile"/>
</Teilnehmer>
<profiles>
<trackingProfile name="HealthMonitoring_Tracking_Profile">
<workflow activityDefinitionId="*">
<workflowInstanceQuery>
<states>
<state name="Started"/>
<state name="Completed"/>
</Staaten>
</workflowInstanceQuery>
</Workflow>
</Trackingprofile>
</Profile>
</Tracking>
. . .
<behaviors>
<serviceBehaviors>
<behavior name="SampleTrackingSample.SampleWFBehavior">
<etwTracking profileName=" HealthMonitoring_Tracking_Profile" />
</Verhalten>
</serviceBehaviors>
</Verhaltensweisen>
. . .
Abbildung 54: Konfigurieren der Nachverfolgung für Dienste
Es gibt viele neue Verbesserungen beim Hosten und Konfigurieren von WCF-Diensten, die Sie in Ihren Workflows nutzen können, z. B. "SVC-lose" Dienste oder Dienste, die keine Dateierweiterung, Standardbindungen und Standardendpunkte benötigen, um nur einige zu nennen. Weitere Informationen zu diesen und anderen Features in WCF4 finden Sie im Begleitartikel zu WCF im Abschnitt Zusätzliche Ressourcen.
Zusätzlich zu den Hostingverbesserungen nutzt Windows Workflow Foundation auch andere Features in WCF. einige direkt und andere indirekt. Beispielsweise ist nachrichtenkorrelation jetzt ein wichtiges Feature bei der Erstellung von Diensten, mit dem Sie Nachrichten mit einem bestimmten Workflow instance basierend auf Daten in der Nachricht, z. B. einer Bestellnummer oder mitarbeiter-ID, verknüpfen können. Korrelation kann für die verschiedenen Messagingaktivitäten sowohl für die Anforderung als auch für die Antwort konfiguriert werden und unterstützt die Korrelation mit mehreren Werten in der Nachricht. Weitere Informationen zu diesen neuen Features finden Sie im Begleitpapier zu WCF4.
Zusammenfassung
Die neue Windows Workflow Foundation-Technologie, die im Lieferumfang von .NET Framework 4 enthalten ist, stellt eine erhebliche Verbesserung der Leistung und Der Produktivität von Entwicklern dar und verwirklicht ein Ziel der deklarativen Anwendungsentwicklung für Geschäftslogik. Das Framework bietet tools für kurze Einheiten komplizierter Arbeit, die vereinfacht werden können, und zum Erstellen komplexer Dienste mit langer Ausführungszeit mit korrelierten Nachrichten, persistentem Zustand und umfassender Anwendungssichtbarkeit durch Nachverfolgung.
Zum Autor
Matt ist Mitglied des technischen Personals bei Pluralsight, wo er sich auf Technologien für verbundene Systeme (WCF, WF, BizTalk, AppFabric und die Azure Services Platform) konzentriert. Matt ist auch ein unabhängiger Berater, der sich auf den Entwurf und die Entwicklung von Microsoft .NET-Anwendungen spezialisiert hat. Als Autor hat Matt an mehreren Zeitschriften und Magazinen mitgewirkt, darunter das MSDN Magazine, wo er derzeit den Workflowinhalt für die Foundations-Spalte erstellt. Matt teilt regelmäßig seine Liebe zur Technologie, indem er auf lokalen, regionalen und internationalen Konferenzen wie Tech Ed spricht. Microsoft hat Matt als MVP für seine Community-Beiträge zur Technologie verbundener Systeme anerkannt. Kontaktieren Sie Matt über seinen Blog: http://www.pluralsight.com/community/blogs/matt/default.aspx.