Integrieren von Windows Workflow Foundation und Windows Communication Foundation

Veröffentlicht: 05. Mrz 2007
Von Jeremy Boyd

Dieser Artikel enthält einen Überblick darüber, wie Workflows, die mit Windows Workflow Foundation (WF) erstellt wurden, in Diensten gehostet werden können, die mit Windows Communication Foundation (WCF) erstellt wurden. In diesem Artikel wird außerdem beschrieben, wie einige der vielfältigen Möglichkeiten von WCF zum Vereinfachen von Clientereignisrückrufen durch Verwenden eines Duplexkanals genutzt werden können.

Klicken Sie hier, um das Windows Workflow Foundation-Beispiel zur Integration von WF und WCF herunterzuladen.

Auf dieser Seite

Einführung Einführung
Codebeispiel: Spesenabrechnung Codebeispiel: Spesenabrechnung
Checkliste zur Integration Checkliste zur Integration
Definieren von Dienstverträgen Definieren von Dienstverträgen
Hosten des Workflowlaufzeitmoduls Hosten des Workflowlaufzeitmoduls
Konfigurieren der Bereitstellung Konfigurieren der Bereitstellung
Verarbeiten des Dienstes Verarbeiten des Dienstes
Konfigurieren von Duplexkanälen Konfigurieren von Duplexkanälen
Schlussbemerkung Schlussbemerkung
Weitere Informationen Weitere Informationen
Danksagung Danksagung
Der Autor Der Autor

Einführung

Mit der Verfügbarkeit von Windows Workflow Foundation (WF) führt Microsoft Workflowmöglichkeiten für die .NET-Plattform ein. Diese ermöglichen es Entwicklern, Workflows zu erstellen, die eine Vielzahl von Szenarien abdecken: von einfachen sequenziellen Workflows zu komplexen computerbasierten Workflows, für die eine komplizierte Interaktion mit dem Benutzer erforderlich ist.

Gleichzeitig besteht der Trend zu einer besseren Geschäftsfunktionalität, die durch gekapselte Dienstendpunkte verfügbar gemacht werden sollen, was die Wiederverwendung und Komposition von Unternehmensfunktionen und -prozessen ermöglicht und dienstorientierte Architekturen nach sich zieht. Windows Communication Foundation (WCF) bietet Entwicklern die Möglichkeit, problemlos verbundene Systeme zu entwickeln, indem ein einheitliches Entwickler-API, eine robuste Hostinglaufzeit und eine flexible, konfigurierbare Lösung für die Bereitstellung in einem Paket zur Verfügung gestellt werden.

Am Ende dieses Dokuments sind zusätzliche Ressourcen aufgeführt, mit deren Hilfe Sie mehr über WF und WCF erfahren können.

Codebeispiel: Spesenabrechnung

Das Codebeispiel für diesen Artikel basiert auf dem Spesenabrechnungsbeispiel, das einen Standardgeschäftsprozess zum Einreichen und Genehmigen der Spesenabrechnung eines Mitarbeiters darstellt. Das ursprüngliche Beispiel wurde aktualisiert, um zu demonstrieren, wie Sie WCF und .NET 3.0 Framework einsetzen können, um dieses Szenario effektiver zu hosten.

Als das erste Spesenabrechnungsbeispiel veröffentlicht wurde, verwendete es .NET Remoting, um die Kommunikation zwischen den Clientanwendungen und der Hostanwendung zu ermöglichen, die die Workflowlaufzeitinstanz enthielt.

Wir haben die Implementierung des Spesenabrechnungsbeispiels neu ausgearbeitet, damit die Kommunikation zwischen Clients und Dienst mithilfe von WCF erfolgt. Die Lösung wurde außerdem logisch strukturiert, um die verschiedenen Probleme innerhalb der Lösung herauszufiltern.

Abbildung 1: Die Struktur der neu erarbeiteten Lösung
Abbildung 1: Die Struktur der neu erarbeiteten Lösung

Es ist wichtig zu verstehen, wie Nachrichten im Rahmen von Geschäftsprozessen verwendet werden, damit Sie diese in Ihr Design integrieren können. Im Lebenszyklus von Spesenabrechnungen gibt es mehrere Interaktionspunkte. Lassen Sie uns diese kurz ansprechen:

  • Es sind drei Parteien beteiligt: Ein Client, ein Manager, und das Host-System zur Spesenabrechnung.

  • Der Prozess beginnt damit, dass ein Client eine neue Spesenabrechnung einreicht.

  • Wir verwenden eine Richtlinie aus Regeln, um zu bestimmen, ob die Spesenabrechnung automatisch genehmigt werden kann.

  • Wenn die Spesenabrechnung nicht automatisch genehmigt wurde, ist ein Manager erforderlich, um die Abrechnung zu genehmigen. Der Manager müsste entweder nach einer neuen zu genehmigenden Abrechnung suchen, oder diesbezüglich benachrichtigt werden.

  • Wenn der Manager nicht innerhalb einer flexiblen Reaktionszeit eine Genehmigung erteilt, wird der Prozess die Abrechnung automatisch ablehnen.

  • Nachdem eine Spesenanforderung überprüft wurde, müssen Client und Manager über das Ergebnis informiert werden.

Mit WF können wir diesen Prozess mithilfe der Standardaktivitäten entwerfen, die im Framework enthalten sind. Wir können DelayActivity verwenden, um einen Ereignisauslöser zu verwalten, nachdem eine Zeitspanne abgelaufen ist, und wir können das Regelmodul und PolicyActivity verwenden, um einen flexiblen Regelsatz zu verwalten, der hinsichtlich eines Ergebnisses abgefragt wird.

Weil dies ein benutzerorientierter Prozess ist, müssen wir mit den Endbenutzern zusammenarbeiten und diese Interaktion wieder in den Workflow übernehmen. WF stellt ein umfassendes Programmiermodell für das Ermöglichen der Kommunikation zwischen einem Host und einem Workflow bereit, indem Lokaldienste verfügbar gemacht werden, nämlich HandleExternalEventActivity und CallExternalMethodActivity.

Da dies ein wichtiges Konzept für das Erstellen interaktiver Workflows ist, sollte kurz erklärt werden, wie es in WF entworfen wurde.

Damit Interaktionen in WF entworfen werden können, müssen wir einen Vertrag entwerfen, der eine Anzahl von Ereignissen und Methoden verfügbar macht. Dieser Vertrag wird sowohl vom Workflow als auch vom Hostprozess verstanden. Der Vertrag bzw. die Schnittstelle, den bzw. die wir erstellen, muss mit dem Attribut [ExternalDataExchange()] markiert werden, das ihn bzw. sie als für den Workflow-Datenaustausch entworfen identifiziert. In unserem Beispiel verwenden wir die Schnittstelle IExpenseLocalService für unseren Workflow.

Wir abonnieren dann eine Klasse (einen lokalen Dienst), die diese Schnittstelle für die Workflowlaufzeit umsetzt. Die Workflowaktivitäten können sich für Ereignisse anmelden oder Methoden nutzen, die durch den Schnittstellentyp definiert sind, und werden mit dem lokalen Dienst verbunden, den wir registriert haben. Dies erfolgt nach dem Schema der Steuerumkehrung, was bedeutet, dass die enge Bindung zwischen dem Workflow und dem konkreten Typ des lokalen Dienstes entfernt wird. In unserem Beispiel implementiert die Klasse ExpenseLocalService den Vertrag IExpenseLocalService.

Wenn der Workflow zuerst ausgeführt wird, kann er für die Arbeit einen anfänglichen Datensatz bereitstellen. Nachdem der Workflow einen Punkt erreicht, an dem externe Interaktion erforderlich ist, können wir ein Ereignis auslösen, das mit HandleExternalEventActivity innerhalb des Workflows verbunden sein kann. Diese Aktion verwendet einen Schnittstellentyp und ein Ereignis als Argumente, und der Workflow wird gestartet, wenn das Ereignis ausgelöst wird, was die weitere Ausführung ermöglicht.

Wenn der Workflow einen Rückruf bei einem lokalen Dienst durchführen muss, kann er dies mithilfe von CallExternalMethodActivity und durch Angeben der Schnittstelle und des Methodennamens als Argumente ausführen.

Über diese Aktionen können wir eine bidirektionale Kommunikation innerhalb des Hostprozesses mit einem ausführenden Workflow durchführen, und durch Verwenden der Steuerumkehrung innerhalb von WF sind Sie vor einer zu dichten Bindung zwischen Workflows und Ihren lokalen Diensten geschützt.

Über den Hostprozess hinaus müssen wir jedoch Interaktionen ermöglichen, die von anderen Systemen oder sogar Benutzern gesteuert werden. Wir können diesen Grad der Interaktion durch Verteilen unserer interaktiven Operationen auf Dienste erreichen, die wiederum von anderen Diensten oder von benutzergesteuerten Anwendungen aufgerufen werden können. WCF ist das Framework, in dem wir diese Messagingfunktion in flexibler Weise erstellen können.

Die wichtigsten Vorteile in unserem Szenario zum Integrieren mit WCF sind folgende:

  • Wir können unsere Dienstimplementierung vom Messaging-Grundlagencode lösen.

  • Zum Verbinden unserer Systeme ist weniger Code erforderlich und die Programmierung ist weniger komplex.

  • Die Bereitstellung ist flexibel.

  • Wir können direkte Rückrufe vom Host zu den Clients nutzen, was schnellere und weniger aufwendige Informationsupdates bedeutet.

Checkliste zur Integration

Um die Integration von WF und WCF abzuschließen, müssen wir eine Diensteschnittstelle verfügbar machen, die Schnittstellenpunkte für Benutzer bereitstellt, über die sie mit einem gerade ausgeführten Workflow interagieren bzw. diesen initiieren können. Der Dienst sollte um die Punkte herum entworfen werden, bei denen der Geschäftsprozess mit externen Entitäten interagiert; beispielsweise Benutzern, die in den Prozess verwickelt sind.

Abbildung 2: Interaktionspunkte im Spesenabrechnungsszenario
Abbildung 2: Interaktionspunkte im Spesenabrechnungsszenario

dies zu erreichen, müssen Sie Folgendes ausführen:

  • Dienstverträge definieren

  • Dienstoperationen implementieren, die neue Workflows durch Ereignisse erstellen, bzw. mit bestehenden interagieren

  • Eine Workflowlaufzeitinstanz innerhalb eines Diensthosts hosten

Neben dem einfachen Hosten des Workflows können wir auch die WCF-Duplexkanäle nutzen, um Ereignisse im Workflow für Benutzerclients auszulösen. Für die Spesenabrechnung ist dies von Vorteil, weil die Lösung sich für regelmäßige Datenaktualisierungen auf das Abrufen des Dienstes durch Clients verlässt. Alternativ können sie auch direkt vom Dienst benachrichtigt werden.

Um dies zu erreichen, müssen Sie Folgendes ausführen:

  • Einen Rückrufvertrag definieren

  • Eine Bindung verwenden, die einen Duplexkanal unterstützt.

Definieren von Dienstverträgen

Für Windows Communication Foundation (WCF) ist das Festlegen eines formellen Vertrags erforderlich, der abstrakt die Möglichkeiten des Dienstes und des Datenaustauschs definiert. Dies geschieht beim Programmieren durch Deklarieren einer Schnittstelle.

Wenn Sie eine Dienstleistung für Unternehmen entwerfen, werden Sie üblicherweise ein Zusammenarbeitsmuster des Typs „Anforderung/Antwort“ verwenden. Wenn Sie dieses Muster verwenden, müssen Sie drei Aspekte des angebotenen Vertrags berücksichtigen:

  • Die zu veröffentlichenden Operationen. Dabei handelt es sich um die Möglichkeiten, die der Dienst für seine Benutzer veröffentlicht. Das sind konkret die Methoden der Schnittstelle.

  • Nachrichten, die für jede Anforderung und Antwort die strukturierten Daten einkapseln. Dabei handelt es sich um die Argumente und Rückgabetypen für jede Methode. In der WCF-Fachsprache handelt es sich dabei üblicherweise um Nachrichtenverträge oder, in einfacheren Szenarien, Datenverträge.

  • Datendefinitionen zu den zentralen Geschäftsentitäten, die durch den Dienst ausgetauscht werden können. Diese bilden einen Teil der Nachrichten. In der WCF-Fachsprache ausgedrückt sind dies unsere Datenverträge.

Ein Dienstvertrag wird mithilfe eines attributbasierten Markups definiert, das zunächst die Verträge definiert, die Operationen verfügbar machen, und anschließend die spezifischen Operationen, die über das Netzwerk veröffentlicht werden.

Jeder Dienstvertrag ist explizit mit dem Attribut [ServiceContract] markiert. Dieses Attribut kann mit Parametern wie den folgenden deklariert werden:

  • Name. Steuert den Namen des Vertrags, der im WSDL-Element <portType> deklariert wird.

  • Namespace. Steuert den Namen des Vertrags, der im WSDL-Element <portType> deklariert wird.

  • SessionMode. Gibt an, ob für den Vertrag eine Bindung erforderlich ist, die Sitzungen unterstützt.

  • CallbackContract. Gibt den Vertrag an, der für Clientrückrufe zu verwenden ist.

  • ProtectionLevel. Gibt an, ob für den Vertrag eine Bindung erforderlich ist, die die Eigenschaft ProtectionLevel unterstützt. Diese Eigenschaft wird verwendet, um Anforderungen für die Verschlüsselung und digitale Signaturen zu deklarieren.

Deklarieren der Operationen

Der Dienst setzt sich aus einer Anzahl veröffentlichter Operationen zusammen. Operationen werden ausdrücklich in den Vertrag übernommen, indem Sie mit dem Attribut [OperationContract] markiert werden. Wie ServiceContract, verfügt ein OperationContract über eine Anzahl von Parametern, die die Bindung zu einem Endpunkt steuern. Diese umfassen:

  • Action. Deklariert den Namen, um diesen Vorgang eindeutig zu identifizieren. Wenn Nachrichten von einem Endpunkt empfangen werden, verwendet der Verteiler das Steuerelement und die Aktion, um die aufzurufende Methode zu bestimmen.

  • IsOneWay. Zeigt an, dass der Vorgang eine Anforderungsnachricht annehmen, aber keine Antwort ausgeben wird. Dies unterscheidet sich vom einfachen Zurückgeben eines ungültigen Typs, der immerhin eine (ungültige) Ergebnisnachricht generiert.

  • ProtectionLevel. Gibt die Anforderungen an die für den Vorgang erforderliche Verschlüsselung oder Signatur an.

Hier folgt ein Beispiel dafür, wie ein Dienstvertrag als Code aussehen kann.

[ServiceContract]
public interface IExpenseService
{
[OperationContract]
GetExpenseReportsResponse GetExpenseReports();

[OperationContract]
GetExpenseReportResponse GetExpenseReport(GetExpenseReportRequest
getExpenseReportRequest);
}

Deklarieren der Nachrichten und Datenentitäten

Es empfiehlt sich, Ihre Nachrichten als Klassen zu entwerfen, die den Inhalt oder den Text für alle Nachrichten definieren, die Sie senden werden. Dies ähnelt dem Entwerfen von Nachrichten mit Tools wie WS Contract First (WSCF) beim Erstellen von Webdiensten mit ASP.NET.

WCF verwendet standardmäßig ein Serialisierungsmodul, das DataContractSerializer genannt wird. Es serialisiert und deserialisiert Daten (d. h. sie werden in oder von XML umgewandelt). Wir nutzen den DataContractSerializer durch Hinzufügen eines Verweises zum Namespace System.Runtime.Serialization, und markieren dann unsere Klasse mit einem Attribut des Typs [DataContract] und den Mitgliedern, die mit [DataMember] zu veröffentlichen sind.

[DataContract]
public class GetExpenseReportsResponse
{
private List<ExpenseReport> reports;

[DataMember]
public List<ExpenseReport> Reports
{
get { return reports; }
set { reports = value; }
}
}

Die Datenentitäten, die in den Nachrichten verwendet werden, stellen die Entitäten innerhalb Ihrer Geschäftsdomäne dar. Wie die Nachrichtenverträge können wir auch DataContractSerializer und Attribute verwenden, um ausdrücklich Mitglieder zu integrieren, die verteilt werden. Wenn wir nur Daten entwerfen werden, können wir optional eine Methode mit öffentlichen Feldern verwenden und die Klasse als serialisierbar markieren.

Im Beispiel haben wir die Vorgehensweise mit Datenvertrag für das Messaging verwendet. In der Praxis werden Sie oft mit komplizierteren Schemas, dem Verwenden von Attributen in Ihren Schemas sowie mit Anforderungen bezüglich der Verwendung von SOAP-Headern konfrontiert. WCF trägt diesen besonderen Fällen Rechnung, indem Sie die Möglichkeit haben, eine Klasse zu definieren, die mit dem Attribut [MessageContract] markiert ist, und die den gesamten SOAP-Umschlag beschreibt (und nicht nur den eigentlichen Text).

Weitere Informationen zu Daten- und Nachrichtenverträgen finden Sie in den einzelnen Artikeln der MSDN-Bibliothek, die im Abschnitt Weitere Informationen am Ende dieses Artikels angegeben sind.

Hosten des Workflowlaufzeitmoduls

Dienste lassen in der Regel ein paralleles Verhalten zu, bei dem für die Dauer einer Sitzung eine neue Instanz des Diensttyps erstellt und verwaltet wird. Um in dieser Situation einen Workflow zu verwenden, muss eine Instanz des Workflowlaufzeitmoduls erstellt und, anstatt pro Aufruf, für die Lebensdauer der Diensthostinstanz verwaltet werden.

Die empfohlene Methode dafür stellt eine Erweiterungsklasse dar, die bei der Erstellung eines Diensthosts aktiviert wird. Durch diese Erweiterung wird eine globale Instanz des Workflowlaufzeitmoduls verwaltet, sodass alle unabhängigen Dienstinstanzen auf sie zugreifen können.

Zum Implementieren einer ServiceHost-Erweiterung erstellen Sie eine Klasse, die IExtension<ServiceHostBase> implementiert. In der Lösung finden Sie ein Beispiel dafür in der Klasse WfWcfExtension, die sich im Codeprojekt WcfExtensions befindet.

Es müssen zwei Methoden implementiert werden: Attach wird aufgerufen, wenn die Erweiterung an ihr übergeordnetes Objekt angehängt wird, und Detach wird aufgerufen, wenn das übergeordnete Objekt entladen wird.

Die Attach-Methode, die hier dargestellt wird, erstellt eine neue Instanz von WorkflowRuntime und instanziert das Objekt mit den erforderlichen Diensten. Dies wird in einem lokalen privaten Feld mit dem Namen workflowRuntime gespeichert.

void IExtension<ServiceHostBase>.Attach(ServiceHostBase owner)
{
   workflowRuntime = new WorkflowRuntime(workflowServicesConfig);
   ExternalDataExchangeService exSvc = new ExternalDataExchangeService();
   workflowRuntime.AddService(exSvc);
   workflowRuntime.StartRuntime();
}

Wie Sie sehen können, beinhaltet diese Initialisierung des Workflowlaufzeitmoduls auch das Hinzufügen von Dienstinstanzen zum Laufzeitmodul, bevor es gestartet wird. Beim Erstellen Ihrer Lösungen empfiehlt es sich in der Regel, jegliche Dienste vor dem Start des Laufzeitmoduls hinzuzufügen. Wenn das Koppeln jedoch Probleme bereiten könnte, ist es möglicherweise vernünftiger, eine späte Bindung anzuwenden.

Im vorliegenden Beispiel wird – als Teil der Methode SetUpWorkflowEnvironment in der Klasse ExpenseService – eine Instanz von ExpenseLocalService im ExternalDataExchangeService hinzugefügt, nachdem WorkflowRuntime bereits gestartet wurde.

Die hier dargestellte Detach-Methode fährt das Laufzeitmodul durch Aufrufen von StopRuntime herunter.

void IExtension<ServiceHostBase>.Detach(ServiceHostBase owner)
{
   workflowRuntime.StopRuntime();
}

Während WorkflowRuntime erstellt und als Teil des Diensthoststarts initialisiert wird, können alle vorhandenen Workflows fortgesetzt werden, bevor Dienstaufrufe erfolgen. Wenn der Diensthost beendet ist, wird das Workflowlaufzeitmodul ordnungsgemäß heruntergefahren.

Hinweis   Es wird empfohlen, beim Hosten von Workflows und Modellieren lange andauernder Workflows die Workflowpersistenz zu verwenden (z. B. SqlWorkflowPersistenceService). Diese Vorgehensweise ist die Norm. Dadurch erhalten Sie einen Mechanismus für die Zustandspersistenz, um alle Anwendungs- und Prozessneustarts zu überstehen.

Erstellen von Dienstoperationen

Zum Erstellen einer Klasse, die ein Dienstverhalten umfasst, müssen Sie eine oder mehrere Schnittstellen implementieren, durch die ein Dienstvertrag definiert wird.

public class ExpenseService :
        IExpenseService,
        IExpenseServiceClient,
        IExpenseServiceManager

Damit sie sich in unseren Workflow integrieren, werden diese Dienstmethoden keine Geschäftslogik enthalten. Sie enthalten stattdessen jedoch Code, um Ereignisse zu kontrollieren oder sie in einem ausführenden Arbeitslauf auszulösen, der den Geschäftsprozess beinhalten wird.

Für Operationen, die den Workflow betreffen, wird entweder ein neuer Workflow gestartet, oder es findet eine Interaktion mit einem bereits laufenden Workflow statt.

Für das Erstellen einer neuen Workflowinstanz ist die Verwendung von WorkflowRuntime erforderlich, um eine neue Instanz des gewünschten Workflowtyps zu instanzieren. Eine solche Instanz wurde bereits in der ServiceHost-Erweiterungsklasse erstellt. Um einen Verweis auf diese Instanz zu erhalten, muss die benutzerdefinierte Erweiterung mithilfe von OperationContext gefunden werden.

WfWcfExtension extension = 
OperationContext.Current.Host.Extensions.Find<WfWcfExtension>();
workflowRuntime = extension.WorkflowRuntime;

OperationContext ist eine Klasse, die einen Zugriff auf den Ausführungskontext der Dienstmethode bereitstellt. Wie Sie im vorherigen Code sehen können, stellt sie ein Singleton namens Current bereit, das uns den Kontext für die aktuelle Dienstmethode zur Verfügung stellt. Die Host-Eigenschaft wird aufgerufen, um eine Instanz zurück zum aktuell ausgeführten ServiceHost abzurufen und dann die Erweiterung aufgrund ihres Typs zu ermitteln.

Nach dem Erhalt eines Verweises zur Erweiterungsinstanz kann WorkflowRuntime durch die öffentliche Eigenschaft zurückgegeben und damit eine neue Instanz von SequentialWorkflow erstellt werden.

Guid workflowInstanceId = 
submitExpenseReportRequest.Report.ExpenseReportId;

Assembly asm = Assembly.Load("ExpenseWorkflows");
Type workflowType = asm.GetType("ExpenseWorkflows.SequentialWorkflow");

WorkflowInstance workflowInstance =
   workflowRuntime.CreateWorkflow(workflowType, null, workflowInstanceId);
workflowInstance.Start();

expenseLocalService.RaiseExpenseReportSubmittedEvent(
   workflowInstanceId, submitExpenseReportRequest.Report);

Im obigen Code wird eine neue Workflowinstanz erstellt, die auf einem vordefinierten Typ basiert. Dies kann durch eine direkte Typinstanzierung erreicht werden. Dieses Beispiel zeigt jedoch, dass wir tatsächlich die Flexibilität haben könnten, einen Workflow zu erstellen, der zur Laufzeit auf einer dynamischen Regel basiert, anstatt dafür eine stark typisierte Bindung zu verwenden.

Die letzte Zeile signalisiert den Start des Workflows durch Auslösen eines Ereignisses, das von der ersten HandleExternalEventActivity im Arbeitslauf verarbeitet wird. Dies wird durch eine Instanz der ExpenseLocalService-Klasse ausgelöst. Im Beispiel wird ExpenseLocalService für die Interaktion mit dem Workflow verwendet, indem neue Workflows gestartet oder Ereignisse in vorhandenen Workflows ausgelöst werden. Diese Klasse wird als ein Mechanismus zum Kapseln des Geschäftsprozesses eingesetzt. Intern wird dies mithilfe von WF implementiert.

Abbildung 3. Der Workflow startet mit einer HandleExternalEventActivity.
Abbildung 3. Der Workflow startet mit einer HandleExternalEventActivity.

Im anderen Szenario, das weiter unten behandelt wird, findet ein Rückruf in einen bereits vorhandenen Workflow und das Auslösen von Ereignissen statt. Es muss ein Ereignis im Workflowmodul ausgelöst werden, das dazu führt, dass der vorhandene Workflow das Ereignis empfängt und dessen Verarbeitung fortsetzt.

Dies ereignet sich innerhalb des Spesenabrechnungsworkflows zum Beispiel dann, wenn die Genehmigung eines Managers erforderlich wird. Der Workflow ruft dann die externe Methode zu RequestManagerApproval auf, wodurch eine Nachricht an den Manager gesendet wird, dass eine neue Spesenabrechnung genehmigt bzw. abgelehnt werden muss.

Der Workflow enthält eine ListenActivity, die so lange blockiert, bis eines der möglichen Ereignisse stattgefunden hat. Im vorliegenden Fall zeigt das Ereignis an, dass ein Manager den Bericht überprüft hat, oder es kommt zu einer Zeitüberschreitung aufgrund von DelayActivity.

Abbildung 4. Benutzerdefinierter Aktivitätsworkflow „ManagerApproval“
Abbildung 4. Benutzerdefinierter Aktivitätsworkflow „ManagerApproval“

Guid workflowInstanceId = 
submitReviewedExpenseReportRequest.Report.ExpenseReportId;

ExpenseReportReviewedEventArgs e =
   new ExpenseReportReviewedEventArgs(workflowInstanceId, report, review);

if (ExpenseReportReviewed != null)
{
   ExpenseReportReviewed(null, e); 
}

Wenn ein Manager einen Bericht mithilfe von ManagerApplication überprüft, erfolgt ein Dienstaufruf zurück zum Host, der die SubmitReviewedExpenseReport-Methode aufruft, die das ExpenseReportReviewed-Ereignis auslöst.

Beim Auslösen eines Ereignisses für eine HandleExternalEventActivity im Workflow, müssen Sie die GUID der entsprechenden Workflowinstanz kennen, damit das Ereignis weitergeleitet werden kann.

Jedes Ereignis wird mit EventArgs ausgelöst. Dadurch können die Daten durch das Ereignismodell zurück in den Workflow übergeben werden. Im vorliegenden Beispiel können wir Details des aktuellen Abrechnungsstatus und der Daten durchschreiten, die uns einen Kontext für die Nachprüfungsaktivität zur Verfügung stellt.

In Workflow sind die Ereignisse durch die Eigenschaften auf einer HandleExternalEventActivity automatisch bis zum Workflow verknüpft.

Abbildung 5. Integrieren der HandleExternalEventActivity in die IExpenseLocalService-Schnittstelle
Abbildung 5. Integrieren der HandleExternalEventActivity in die IExpenseLocalService-Schnittstelle

Sie geben den Schnittstellentyp an, der mit dem Attribut [ExternalDataExchange] markiert werden muss, und anschließend das Ereignis dieser Schnittstelle, das von HandleExternalEventActivity abonniert wird.

Die Ereignisargumente müssen von der ExternalDataEventArgs-Klasse abgeleitet werden. Das heißt, dass jedes Ereignis Kontext enthalten wird, z. B. die InstanceId des Workflows. Das Workflowlaufzeitmodul verwaltet dann das Weiterleiten des Ereignisses zur richtigen Workflowinstanz für die Weiterverarbeitung. Wenn ein Persistenzdienst verwendet wird, verwaltet das Laufzeitmodul auch das Aufnehmen und Wiederaufnehmen aller Ausführungsstati für den Workflow, solange er ausgeführt wird.

Hosten des Dienstes

Um einen WCF-Dienst zu hosten zu können, muss dessen Ausführung innerhalb des ServiceHost-Containers stattfinden.

Um zu verstehen, wie das Hosten mit WCF erreicht werden kann, empfiehlt es sich, zuerst die verfügbaren Alternativen zu verstehen:

  • Für Windows-Standardprozesse kann eine Instanz von ServiceHost manuell erstellt und geöffnet werden.

  • Beim Hosten eines Webendpunkts (Webdienstes) durch Microsoft Internetinformationsdienste (IIS) 6.0 wird ein benutzerdefinierter HttpHandler verwendet, der unter dem System.ServiceModel-Namespace bereitgestellt wird.

  • Beim Hosten unter IIS 7 kann Windows Activation Service (WAS) zum Hosten der Endpunkte verwendet werden.

In der Regel empfiehlt sich das Hosten mithilfe der Internetinformationsdienste, wenn Sie Webdienste erstellen. Wenn Sie einen einzigen Instanzendpunkt erstellen, der als ein Daemon agiert, empfiehlt sich das Hosten mithilfe eines Windows-Dienstes.

In diesem Beispiel wird die Hauptdienstinstanz innerhalb einer Windows-Konsolenanwendung gehostet. Dies ähnelt dem Hosten eines Windows-Dienstes.

Um einen Dienst bereitzustellen, muss eine Instanz die ServiceHost-Klasse erstellen und seine Endpunkte für jeden zu veröffentlichenden Diensttyp öffnen. ServiceHost nimmt eine Anzahl von Argumenten in seinem Konstruktors an. Das primäre Argument ist jedoch entweder ein Type-Argument oder eine Instanz einer Klasse, die einen ServiceContract implementiert.

  • Verwenden Sie Type, wenn Sie Instanzen mit PerCall oder PerSession erstellen möchten.

  • Verwenden Sie eine einzige Instanz, wenn Sie Single Instancing verwenden.

Weitere Informationen zu Instanzen und Parallelität in WCF erhalten Sie unter Sessions, Instancing, and Concurrency (in englischer Sprache) in der MSDN-Bibliothek.

Nachdem ein Host eingerichtet ist, wird er eine beliebige verfügbare Konfiguration analysieren (mehr dazu im Abschnitt Konfigurieren der Bereitstellung weiter unten) und diese Informationen mit einer beliebigen explizit hinzugefügten Konfiguration verknüpfen, um die verfügbaren Endpunkte zu bestimmen und sie zum Veröffentlichen zu öffnen. Sobald sie Aufrufe von Clients empfangen, werden die Anforderungen in neuen Arbeitsthreads im Hintergrund verarbeitet und, angeleitet durch den SOAP-Vertragsnamen und die Aktion für die Nachricht, zu den entsprechenden Dienstoperationen weitergeleitet.

using (ServiceHost serviceHost = new ServiceHost(new ExpenseService()))
{
   WfWcfExtension wfWcfExtension =
      new WfWcfExtension("WorkflowRuntimeConfig");
   serviceHost.Extensions.Add(wfWcfExtension);
   serviceHost.Open();

   // block the process at this point, for example Console.ReadLine();

   serviceHost.Close();
}

Wenn Sie einen ServiceHost konfigurieren, müssen Sie dies noch vor dem Öffnen der Endpunkte für Verbindungen durchführen. Wie bereits gezeigt, können Sie dies durch die Interaktion mit dem Hostobjekt vor dem Aufruf von Open vornehmen. Es wird empfohlen, dass Sie einen eingeschränkten Gültigkeitsbereich verwenden, damit der ServiceHost vor der Verwendung von Open entsorgt wird, und dass Sie am Ende dieses Gültigkeitsbereichs explizit Close aufrufen, damit alle aktiven Verbindungen und Endpunkte ordnungsgemäß geschlossen werden.

Konfigurieren der Bereitstellung

WCF stellt einen Mechanismus bereit, um Bereitstellungsprobleme von der Implementierung zu trennen, indem WCF es ermöglicht, Endpunkte per XML zu konfigurieren. Dies bietet Administratoren die Möglichkeit, die Richtlinie eines Dienstes zu ändern, ohne dass Code neu entwickelt werden muss.

Jeder Dienst wird an einem oder mehreren Endpunkten veröffentlicht. Ein Endpunkt stellt einfach einen adressierbaren Verbindungspunkt dar, an dem Clients den Dienst verarbeiten können. In WCF wird jeder Endpunkt mit drei Attributen angegeben, die als das ABC von WCF bekannt gemacht wurden.

Die ABC-Attribute sind Address (Adresse), Binding (Bindung) und Contract (Vertrag).

Adresss: Der eindeutige adressierbare Ort dieses Endpunkts. In der Regel handelt es sich hierbei um einen URI, der Ihnen die absolute Adresse angibt, über die der Dienst Anforderungen abfragt, z. B.: http://myhost/myservice oder net.tcp://myhost:400/myservice

Binding: Die Richtlinie, die das Protokoll für die Kommunikation zwischen dem Dienst und seinen Benutzern diktiert. Die Bindung gibt verschiedene Aspekte an, wie z. B. den Transporttyp, den Nachrichtencodierungstyp und die Datenserialisierung. WCF enthält bereits einige vorgefertigte Bindungen, die die häufigsten Szenarien unterstützen.

Contract: Die Operationen und Daten werden veröffentlicht gemäß unserer Definition durch eine in Code beschriebene Schnittstelle.

Um den Dienst zu konfigurieren, muss eine Konfiguration deklariert werden, die den Dienst deklariert, und es muss eine beliebige Anzahl von Endpunkten für den Dienst konfiguriert werden. Da ein Dienst möglicherweise eine beliebige Anzahl von Verträgen implementiert, wirkt sich dies auch auf die Anzahl der Endpunkte aus, die Sie veröffentlichen müssen.

Eine Beispielkonfiguration wird hier dargestellt.

<services>
   <service name="ExpenseServices.ExpenseService">
      <endpoint
         address="http://localhost:8081/ExpenseService/Manager"
         binding="wsHttpBinding"
         contract="ExpenseContracts.IExpenseServiceManager" />
<endpoint
         address="http://localhost:8081/ExpenseService/Client"
         binding="wsDualHttpBinding"
         contract="ExpenseContracts.IExpenseServiceClient" />
   </service>
</services>

In diesem Konfigurationsbeispiel wird eine Konfiguration für den Diensttyp ExpenseServices.ExpenseService deklariert. Dies ermöglicht dem Laufzeitmodul, die Konfiguration zu finden, wenn ein neuer ServiceHost instanziert wird, der auf diesem Typ basiert. Weitere Informationen zu Bindungen erhalten Sie unter WCF Binding (in englischer Sprache) in der MSDN-Bibliothek.

Verarbeiten des Dienstes

Das Verarbeiten von Diensten mithilfe von WCF erfolgt durch die ChannelFactory-Klasse. ChannelFactory verwendet das Factorymuster, um Proxyinstanzen eines Dienstvertrags bereitzustellen, die eine Verbindung zu dem in der Konfiguration angegebenen Endpunkt herstellen. Die Factory kann mit Laufzeitinformationen konfiguriert werden (wie z. B. Sicherheitsanmeldeinformationen und Zertifikaten für die Nachrichtverschlüsselung), oder zur dynamischen Ermittlung der Endpunktinformationen.

private IExpenseServiceManager CreateChannelExpenseServiceManager()
{
   ChannelFactory<IExpenseServiceManager> factory = new
ChannelFactory<IExpenseServiceManager>("ExpenseServiceManager");
   IExpenseServiceManager proxy = factory.CreateChannel();

   return proxy; 
}

Wie Sie sehen können, wird anfänglich eine Instanz der Factory erstellt, die ein generisches Argument für den Dienstvertrag verwendet, sodass eine präzisere Factory erstellt werden kann, die nur Instanzen vom gewünschten Vertrag zurückgeben wird. Es wird außerdem ein Argument angegeben, das die für den Endpunkt verwendete Konfiguration festlegt. In diesem Fall wird eine Endpunktkonfiguration mit dem Namen ExpenseServiceManager verwendet, die sich auf die Konfiguration in der Anwendungskonfigurationsdatei bezieht.

<system.serviceModel>
   <client>
         <endpoint name="ExpenseServiceManager"
            address="http://localhost:8081/ExpenseService/Manager"
            binding="wsHttpBinding"
            contract="ExpenseContracts.IExpenseServiceManager" />
   </client>
</system.serviceModel>

Sie können sehen, dass die Endpunktdefinition genau mit der Definition übereinstimmt, die in der Konfiguration des Hosts deklariert wurde. Im Allgemeinen werden Sie einer unterschiedlichen Konfiguration nur dann begegnen, wenn entweder die Adresse zwischen dem Client und dem Server aufgrund der Netzwerkkonfiguration abweicht, oder wenn das benutzerdefinierte Verhalten implementiert wird.

Wenn Sie das Windows-SDK installiert haben, finden Sie ein Tool (svcutil), das bereitgestellt wurde, um das Erstellen einer Proxyklasse und einer Endpunktkonfiguration, die Sie in Ihre Lösung integrieren können, zu automatisieren. Der Zieldienst muss eine Beschreibung seiner Metadaten durch WSDL oder WS-MetadataExchange veröffentlichen, um dieses Tool zu verwenden.

Konfigurieren von Duplexkanälen

Bis jetzt wurde davon ausgegangen, dass der Datenverkehr ein Zusammenarbeitsmuster aus Anforderung und Antwort verwenden wird, bei dem die Nachrichten von einem Benutzer gesendet und von einem Dienst beantwortet werden. WCF unterstützt eine Reihe alternativer Meldungsflüsse, z. B. unidirektionale (Fire-and-Forget) oder Zweiwege-/Duplexkommunikation. Wenn es sich um einen Meldungsfluss handelt, bei dem jede Partei eine Konversation initiieren kann, muss ein Duplex- oder Zweiwegekanal verwendet werden. Duplexkanäle können bei stärker verbundenen Systemen, in denen Daten aus jeder Richtung gesendet werden können, sehr effektiv sein. Ein Beispiel für einen Fall, in dem dies nützlich sein könnte, ist die Bereitstellung von Rückrufen von der Ereignisverwaltung.

Implementieren von Clientrückrufen

Clientrückrufe werden mit einem CallbackContracts genannten Konzept in WCF implementiert. Für einen Vertrag, der veröffentlicht wird, kann ein zweiter Vertrag nominiert werden. Mit diesem werden Operationen definiert, die die Clients veröffentlichen, die von Code zurückgerufen werden können, der in dem Dienst ausgeführt wird.

Um einen CallbackContract zu deklarieren, geben Sie den Schnittstellentyp als Teil des Dienstvertrags an, von dem zurückgerufen wird.

[ServiceContract(CallbackContract = 
typeof(IExpenseServiceClientCallback))]

Außerdem müssen Sie eine Bindung verwenden, die Duplexkanäle unterstützt, z. B. netTcpBinding oder wsDualHttpBinding. Duplexing über TCP wird durch eine Zweiwegeverbindung erreicht, die über den Nachrichtenaustausch eingerichtet und die ganze Zeit beibehalten wird. Über HTTP wird dies durch einen Rückruf an einen Clientlistener erreicht. Da der Client sich u. U. seines Rückkehrpfads nicht bewusst ist bzw. dies stark durch die Konfiguration definiert sein soll, kann eine benutzerdefinierte Bindungskonfiguration verwendet werden, um eine alternative clientBaseAddress zu deklarieren.

<endpoint binding="wsDualHttpBinding" 
bindingConfiguration="AlternativeClientCallback"/>
<bindings>
   <wsDualHttpBinding>
      <binding name="AlternativeClientCallback" 
clientBaseAddress="http://localhost:8082/ExpenseService/ClientCallback"/>
   </wsDualHttpBinding>
</bindings>

Implementieren des Rückrufs in dem Client

Einen Rückrufvertrag zu implementieren ist dasselbe wie die Implementierung eines Dienstvertrags. Es muss eine Implementierung der Schnittstelle bereitgestellt werden, die definiert wurde.

class CallbackHandler : IExpenseServiceClientCallback
{
public void ExpenseReportReviewed(
 ExpenseReportReviewedRequest expenseReportReviewedRequest)
{
// We implement client logic to respond to the callback here.
}
}

Damit der Host eine Instanz der CallbackHandler-Klasse hat, in die er zurückrufen kann, muss der Clientkanal so eingerichtet werden, dass dieser über die Duplexnatur der Verbindung informiert ist.

Wie bereits beschrieben, wird zunächst eine Bindung verwendet, die Duplexkanäle unterstützt. Zweitens, wenn die Verbindung zum Dienstendpunkt initialisiert wird, wird eine Unterklassenversion von ChannelFactory mit dem Namen DuplexChannelFactory verwendet. Diese stellt eine Duplexverbindung zu dem Dienst her.

private IExpenseServiceClient CreateChannelExpenseServiceClient()
{
   InstanceContext context = new InstanceContext(new CallbackHandler());

   DuplexChannelFactory<IExpenseServiceClient> factory =
new DuplexChannelFactory<IExpenseServiceClient>(context, 
"ExpenseServiceClient");
   IExpenseServiceClient proxy = factory.CreateChannel();

   return proxy; 
}

Der Hauptunterschied bei der Verwendung einer DuplexChannelFactory ist, dass eine Instanz der Klasse CallbackHandler initialisiert und an den Konstruktor der Factory übergeben wird, um einen Kontext zu initialisieren, der für Rückrufe verwendet wird.

Implementieren des Rückrufs vom Host

Von der Perspektive des Hosts aus kann durch den Rückrufkanal, der im Vertrag IExpenseServiceClient definiert ist, ein Verweis für den Rückruf an den Client abgerufen werden.

[ServiceContract(CallbackContract = 
typeof(IExpenseServiceClientCallback))]
public interface IExpenseServiceClient : IExpenseService

Mit dem Attribut CallbackContract wird die Schnittstelle deklariert, die den Vertrag für die Rückrufe definiert, die vom Host durchgeführt werden.

Um den Rückruf durchzuführen, wird ein Verweis auf den Rückrufvertrag abgerufen, indem OperationContext.Current.GetCallbackChannel wie hier gezeigt aufgerufen wird.

IExpenseServiceClientCallback callback =
                   OperationContext.Current.GetCallbackChannel
<IExpenseServiceClientCallback>();
callback.ExpenseReportReviewed(new 
ExpenseReportReviewedRequest(e.Report));

Nachdem ein Verweis für den Rückrufkanal vorhanden ist, kann dieser normal aufgerufen werden.

Schlussbemerkung

Windows Workflow Foundation stellt ein allgemeines Framework für das Definieren von Workflows sowie ein robustes Laufzeitmodul bereit, mit dem Sie die ausgeführten Workflows bereitstellen und damit interagieren können.

Windows Communication Foundation stellt ein allgemeines Framwork zum Erstellen verbundener Systeme bereit. Es versorgt Entwickler mit einem konsistenten Satz an APIs sowie einem umfassenden Satz an Möglichkeiten, mit denen Sie definieren können, wie die Kommunikation stattfindet.

Diese beiden Rahmenwerke können Sie zusammen verwenden, um eine flexible und umfassende Anwendungsplattform zum Erstellen und Bereitstellen verteilter Geschäftsprozesse innerhalb Ihrer Umgebung zu etablieren. Mit WF können Sie Ihre Geschäftslogik und -prozesse modellieren und kapseln, während WCF Ihnen eine Messaging-Infrastruktur bereitstellt, anhand derer Sie Ihre Systeme verteilen können.

Hier einige Richtlinien, die Sie beim Entwerfen Ihrer Dienste berücksichtigen sollten:

  • Durch die Verwendung eines Persistenzdienstes sollten Sie für lang andauernde Workflows sorgen.

  • Eine Dienstoperation kann durch das Auslösen von Ereignissen mit einem ausgeführten Workflow interagieren. Ihre Workflows sollten so entworfen werden, dass Ereignisse ausgelöst werden, wenn Aufmerksamkeit gefordert wird. Außerdem sollte auf Ereignisse reagiert werden, wenn extern mit dem Workflow interagiert wird (z. B. durch einen externen Dienst oder einen Benutzer).

  • Workflows werden asynchron zum Dienstaufruf ausgeführt; deshalb müssen Sie das Design entsprechend gestalten, wenn Daten vom Dienst zurückgegeben werden sollen. Außerdem muss bedacht werden, in welchem Zustand die Daten sich zu diesem Zeitpunkt befinden. Wenn Sie einen synchronen Ansatz verwenden möchten, können Sie mit der Klasse ManualWorkflowSchedulerService die manuelle Ausführung von Workflows planen.

Danksagung

Vielen Dank an die folgenden Mitarbeiter und Rezensenten für ihre Unterstützung:

  • Christian Weyer, thinktecture

  • Paul Andrew, Microsoft Corporation

  • Khalid Aggag, Microsoft Corporation

  • Patrice Manac'h, Microsoft Corporation

Der Autor

Jeremy Boyd ist leitender technischer Berater für Intergen, ein Lösungsanbieter mit Sitz in Neuseeland, und „Microsoft Gold Certified Partner“ sowie MSDN Regional Director der Neuseeland-Community. Im Laufe der vergangenen 12 Monate hat Jeremy aktiv mit Kunden zusammengearbeitet, um beim Implementieren von Lösungen zu helfen, die auf WF und WCF basieren. Außerdem hat er anderen Entwicklern mithilfe seines Weblogs geholfen, die Vorteile dieser Technologien kennen zu lernen.


Anzeigen: