Integrieren von Windows Workflow Foundation und Windows Communication Foundation

 

Jeremy Boyd
Intergen

Januar 2007

Gilt für:
   Windows Workflow Foundation
   Windows Communication Foundation
   Microsoft Visual Studio 2005

Zusammenfassung: Dieser Artikel bietet eine Übersicht darüber, wie Workflows, die mithilfe von Windows Workflow Foundation (WF) erstellt werden, in Diensten gehostet werden können, die mit Windows Communication Foundation (WCF) erstellt wurden. Der Artikel beschreibt auch, wie wir einige der umfassenden Funktionen nutzen können, die von WCF bereitgestellt werden, um Clientereignisrückrufe durch Verwendung eines Duplexkanals zu erleichtern. (15 gedruckte Seiten)

Inhalte

Einführung
Beispiel für die Spesenabrechnung
Checkliste für die Integration
Definieren von Dienstverträgen
Hosten der Workflowlaufzeit
Konfigurieren der Bereitstellung
Nutzen des Diensts
Konfigurieren von Duplexkanälen
Zusammenfassung
Weitere Informationen
Danksagung

Klicken Sie hier , um "Windows Workflow Foundation-Beispiel für die Integration von WF und WCF" herunterzuladen.

Einführung

Mit der Verfügbarkeit von Windows Workflow Foundation (WF) führt Microsoft Workflowfunktionen in die .NET-Entwicklerplattform ein. Diese Funktionen ermöglichen es Entwicklern, Workflows zu erstellen, die eine Vielzahl von Szenarien erfüllen, von einfachen sequenziellen Workflows bis hin zu komplexen machinenbasierten Workflows mit anspruchsvollen menschlichen Interaktionen.

Gleichzeitig gibt es einen Schritt zur Förderung von Geschäftsfunktionen, die über gekapselte Dienstendpunkte verfügbar gemacht werden können, die die Wiederverwendung und Zusammensetzung von Geschäftsfunktionen und Prozessen ermöglichen, die zu Service-Oriented Architekturen führen. Windows Communication Foundation (WCF) wurde zur Verfügung gestellt, um Entwicklern Funktionen zur einfachen Entwicklung verbundener Systeme zur Verfügung zu stellen, indem eine konsistente Entwickler-API, eine stabile Hostinglaufzeit und eine flexible konfigurationsgesteuerte Lösung zur Unterstützung der Bereitstellung bereitgestellt werden.

Am Ende dieses Dokuments finden Sie eine Liste mit zusätzlichen Ressourcen, die verwendet werden können, um mehr über WF und WCF zu erfahren.

Beispiel für die Spesenabrechnung

Das Codebeispiel für diesen Artikel basiert auf dem Workflowbeispiel "Expense Reporting", das einen Standardgeschäftsprozess zum Übermitteln und Genehmigen des Spesenanspruchs eines Mitarbeiters modelliert. Das ursprüngliche Beispiel wurde aktualisiert, um zu veranschaulichen, wie Sie WCF und das .NET 3.0 Framework nutzen können, um dieses Szenario effektiver zu hosten.

Als das erste Expense Reporting-Beispiel veröffentlicht wurde, wurde .NET Remoting verwendet, um die Kommunikation zwischen den Clientanwendungen und der Hostanwendung bereitzustellen, die die Workflow-Runtime-instance enthielt.

Wir haben die Implementierung von Expense Reporting umgestaltet, um die Kommunikation zwischen Clients und Dienst mithilfe von WCF durchzuführen. Die Lösung wurde auch logisch strukturiert, um die verschiedenen Anliegen innerhalb der Lösung zu trennen.

Bb266709.intgrwfwcf01(en-us,MSDN.10).gif

Abbildung 1. Die Struktur unserer umgestalteten Lösung

Es ist wichtig zu verstehen, wie Nachrichten im Kontext des Geschäftsprozesses verwendet werden, damit Sie diese in Ihren Entwurf integrieren können. Im Lebenszyklus der Spesenabrechnung gibt es mehrere Interaktionspunkte. Lassen Sie uns diese kurz betrachten:

  • Es gibt drei Parteien: einen Client, einen Manager und das Expense Reporting-Hostsystem .
  • Der Prozess beginnt damit, dass ein Kunde einen neuen Spesenanspruch einreicht.
  • Wir verwenden eine Regelrichtlinie , um zu bestimmen, ob die Spesenforderung automatisch genehmigt werden kann.
  • Wenn der Spesenanspruch nicht automatisch genehmigt wurde, benötigen wir einen Vorgesetzten , um den Bericht zu genehmigen. Der Vorgesetzte muss entweder überprüfen, ob ein neuer Bericht genehmigt werden kann, oder er muss darüber benachrichtigt werden.
  • Wenn der Vorgesetzte nicht innerhalb einer flexiblen Verzögerung genehmigt, lehnt der Prozess den Anspruch automatisch ab.
  • Nachdem eine Spesenforderung überprüft wurde, müssen der Kunde und der Manager über das Ergebnis aktualisiert werden.

Mithilfe von WF können wir diesen Prozess mithilfe der Standardaktivitäten modellieren, die mit dem Framework bereitgestellt werden. Wir können delayActivity verwenden, um ein Ereignis zu verwalten, das nach einem bestimmten Zeitraum ausgelöst wird, und wir können die Regel-Engine und PolicyActivity verwenden, um einen flexiblen Satz von Regeln zu verwalten, der für ein Ergebnis abgefragt wird.

Da es sich um einen menschenorientierten Prozess handelt, müssen wir mit den Endbenutzern interagieren und diese Interaktion wieder in den Workflow integrieren. WF bietet ein umfassendes Programmiermodell für die Kommunikation zwischen einem Host und einem Workflow, indem lokale Dienste, handleExternalEventActivity und CallExternalMethodActivity bereitgestellt werden.

Da dies ein wichtiges Konzept beim Erstellen interaktiver Workflows ist, lassen Sie uns schnell erläutern, wie dies in WF entworfen wurde.

Damit Interaktionen in WF modelliert werden können, müssen wir einen Vertrag entwerfen, der eine Reihe von Ereignissen und Methoden verfügbar macht. Dieser Vertrag wird sowohl vom Workflow als auch vom Hostprozess verstanden. Der Vertrag/die Schnittstelle, den wir erstellen, muss mit dem Attribut [ExternalDataExchange()] gekennzeichnet werden, mit dem er als für den Workflow-Datenaustausch konzipiert ist. In unserem Beispiel verwenden wir die IExpenseLocalService-Schnittstelle mit unserem Workflow.

Anschließend abonnieren wir eine Klasse (bekannt als lokaler Dienst), die diese Schnittstelle mit der Workflowlaufzeit implementiert. Die Workflowaktivitäten können sich für Ereignisse registrieren oder Methoden nutzen, die für den Schnittstellentyp definiert sind, und werden mit dem lokalen Dienst verbunden, den wir registriert haben. Dabei wird ein Muster namens Inversion der Steuerung verwendet, das die enge Kopplung zwischen dem Workflow und dem konkreten Typ des lokalen Diensts entfernt. In unserem Beispiel implementiert die ExpenseLocalService-Klasse unseren IExpenseLocalService-Vertrag .

Wenn der Workflow zum ersten Mal ausgeführt wird, kann er eine erste Tüte mit Daten bereitgestellt werden, mit denen er ausgeführt werden soll. Nachdem der Workflow einen Punkt erreicht hat, an dem eine externe Interaktion erforderlich ist, können wir ein Ereignis auslösen, das an eine HandleExternalEventActivity innerhalb des Workflows gebunden werden kann. Diese Aktivität akzeptiert einen Schnittstellentyp und ein Ereignis als Argumente, und der Workflow wird ausgelöst, wenn das Ereignis ausgelöst wird, sodass die Ausführung fortgesetzt werden kann.

Wenn der Workflow einen lokalen Dienst aufrufen muss, kann er dazu eine CallExternalMethodActivity verwenden und die Schnittstelle und den Methodennamen als Argumente angeben.

Mithilfe dieser Aktivitäten können wir bidirektionale Kommunikation innerhalb des Hostprozesses mit einem ausgeführten Workflow durchführen. und durch die Verwendung des Musters "Inversion der Steuerung" in WF sind Sie vor einer engen Kopplung zwischen Workflows und Ihren lokalen Diensten geschützt.

Breiter als nur der Hostprozess müssen wir jedoch Interaktionen zulassen, die von anderen Systemen oder sogar Menschen gesteuert werden. Wir können dieses Maß an Interaktion erreichen, indem wir unsere interaktiven Vorgänge über Dienste verteilen, die wiederum von anderen Diensten oder benutzergesteuerten Anwendungen aufgerufen werden können. WCF ist das Framework, auf dem wir diese Messagingfunktion flexibel erstellen können.

Die wichtigsten Vorteile in unserem Szenario der Integration in WCF sind:

  • Wir können unsere Dienstimplementierung vom Messaging-Sanitärcode entkoppeln.
  • Es gibt viel weniger Code und Komplexität, um unsere Systeme zu verbinden.
  • Wir verfügen über Flexibilität bei der Bereitstellung.
  • Wir sind in der Lage, direkte Rückrufe vom Host an die Clients zu verwenden, um schnellere und weniger aufwandsbezogene Aktualisierungen von Informationen bereitzustellen.

Checkliste für die Integration

Um eine Integration von WF und WCF abzuschließen, müssen wir eine Dienstschnittstelle verfügbar machen, die eine Reihe von Schnittstellenpunkten für Verbraucher bereitstellt, wo sie einen ausgeführten Workflow initiieren oder mit ihnen interagieren können. Der Dienst sollte um die Punkte herum modelliert werden, an denen der Geschäftsprozess mit externen Entitäten interagiert, z. B. personen, die am Prozess beteiligt sind.

Bb266709.intgrwfwcf02(en-us,MSDN.10).gif

Abbildung 2. Interaktionspunkte im Szenario "Expense Reporting"

Um dies zu erreichen, müssen wir:

  • Definieren sie Dienstverträge.
  • Implementieren Sie Dienstvorgänge, die neue Workflows erstellen (oder mit vorhandenen) Über Ereignisse interagieren.
  • Hosten Sie eine Workflow-Runtime-instance in einem Diensthost.

Zusätzlich zum einfachen Hosten unseres Workflows können wir auch WCF-Duplexkanäle verwenden, um Ereignisse aus dem Workflow wieder auf verbrauchte Clients auszulösen. Für die Spesenabrechnung ist dies von Vorteil, da die Lösung davon abhängt, dass die Clients den Dienst nach regelmäßigen Datenupdates abfragen. Stattdessen können sie direkt vom Dienst benachrichtigt werden.

Dazu müssen wir:

  • Definieren Sie einen Rückrufvertrag.
  • Verwenden Sie eine Bindung, die einen Duplexkanal unterstützt.

Definieren von Dienstverträgen

Windows Communication Foundation (WCF) erforderte, dass ein formaler Vertrag deklariert wurde, der die Funktionen eines Diensts und den Austausch von Daten abstrakt definiert. Dies wird im Code durch Deklarieren einer Schnittstelle definiert.

Beim Entwerfen eines Geschäftsdiensts verwenden Sie in der Regel ein Anforderungs-Antwort-Zusammenarbeitsmuster. Wenn Sie dieses Muster verwenden, müssen Sie drei Aspekte im angebotenen Vertrag berücksichtigen:

  • Die vorgänge, die veröffentlicht werden. Dies sind die Funktionen, die der Dienst für seine Consumer veröffentlicht. Dies sind die Methoden für die Schnittstelle.
  • Nachrichten, die die strukturierten Daten für jede Anforderung und Antwort kapseln. Dies sind die Argumente und Rückgabetypen für jede Methode. In der WCF-Terminologie handelt es sich in der Regel um Nachrichtenverträge oder in einfacheren Szenarien um Datenverträge.
  • Datendefinitionen der wichtigsten Geschäftsentitäten, die über den Dienst ausgetauscht werden können. Diese sind Teil der Nachrichten. In der WCF-Terminologie sind dies unsere Datenverträge.

Ein Dienstvertrag wird mithilfe eines attributbasierten Markups definiert, das die Verträge definiert, die Vorgänge verfügbar machen, und dann die spezifischen Vorgänge, die über das Netzwerk veröffentlicht werden.

Jeder Dienstvertrag wird explizit mit dem [ServiceContract] -Attribut gekennzeichnet. Dieses Attribut kann mit Parametern deklariert werden, die Folgendes umfassen:

  • Name. Steuert den Namen des Vertrags, der im WSDL-PortType-Element<> deklariert wurde.
  • Namespace. Steuert den Namen des Vertrags, der im WSDL-PortType-Element<> deklariert wurde.
  • Sessionmode. Gibt an, ob der Vertrag eine Bindung erfordert, die Sitzungen unterstützt.
  • Callbackcontract. Gibt den Vertrag an, der für Clientrückrufe verwendet werden soll.
  • Protectionlevel. Gibt an, ob der Vertrag eine Bindung erfordert, die die ProtectionLevel-Eigenschaft unterstützt, die zum Deklarieren von Anforderungen für Verschlüsselung und digitale Signaturen verwendet wird.

Deklarieren der Vorgänge

Der Dienst besteht dann aus einer Reihe von veröffentlichten Vorgängen. Vorgänge werden explizit für den Vertrag aktiviert, indem sie mit dem [OperationContract] -Attribut markiert werden. Wie der ServiceContract verfügt ein OperationContract über eine Reihe von Parametern, die steuern, wie es an einen Endpunkt gebunden werden kann. Dazu zählen unter anderem folgende Einstellungen:

  • Action (Aktion). Steuert 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. Gibt an, dass der Vorgang eine Anforderungsnachricht annimmt, aber keine Antwort erzeugt. Dies unterscheidet sich von der einfachen Rückgabe eines void-Rückgabetyps, der weiterhin eine Ergebnismeldung generiert.
  • Protectionlevel. Gibt die Anforderungen für die Verschlüsselung oder Signatur an, die für den Vorgang erforderlich sind.

Im Folgenden finden Sie ein Beispiel dafür, wie ein Dienstvertrag im Code aussieht.

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

        [OperationContract]
        GetExpenseReportResponse GetExpenseReport(GetExpenseReportRequest 
getExpenseReportRequest);
}

Deklarieren der Nachrichten und Datenentitäten

Sie sollten Ihre Nachrichten als Klassen modellieren, die die Nutzlast oder den Text für jede der Nachrichten definieren, die Sie senden. Dies ähnelt der Modellierung von Nachrichten mithilfe von Tools wie WS Contract First (WSCF) beim Erstellen von Webdiensten mit ASP.NET.

WCF verwendet standardmäßig eine Serialisierungs-Engine namens DataContractSerializer, um Daten zu serialisieren und zu deserialisieren (d. a. um sie in und aus XML zu konvertieren). Wir verwenden dataContractSerializer, indem wir einen Verweis auf den System.Runtime.Serialization-Namespace hinzufügen und dann unsere Klasse mit einem [DataContract]- Attribut und den Membern markieren, die mit [DataMember] veröffentlicht werden sollen.

[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 in Ihrer Geschäftsdomäne dar. Wie bei den Nachrichtenverträgen können wir dataContractSerializer verwenden und Explizite Member festlegen, die verteilt werden. Oder, wenn wir nur Modelldaten verwenden, können wir einen Ansatz von öffentlichen Feldern verwenden und die Klasse als serialisierbar markieren.

Im Beispiel haben wir den Datenvertragsansatz zum Markieren von Messaging verwendet. In realen Szenarien werden Sie häufig mit komplizierteren Schemas, der Verwendung von Attributen in Ihren Schemas und anforderungen zur Verwendung von SOAP-Headern zu tun haben. WCF bietet die Möglichkeit, eine Klasse zu definieren, die mit dem [ MessageContract] -Attribut gekennzeichnet ist, das den gesamten SOAP-Umschlag anstelle des Textkörpers beschreibt.

Weitere Informationen zu Datenverträgen und Nachrichtenverträgen finden Sie in den entsprechenden Artikeln der MSDN Library im Abschnitt Weitere Informationen am Ende dieses Artikels.

Hosten der Workflowlaufzeit

Dienste ermöglichen normalerweise gleichzeitiges Verhalten, bei dem eine neue instance des Diensttyps für den Lebenszyklus einer Sitzung erstellt und verwaltet wird. Um den Workflow in dieser Situation verwenden zu können, müssen wir eine instance der Workflowlaufzeit erstellen und für die Lebensdauer des Diensthosts instance beibehalten, anstatt pro Aufruf.

Der empfohlene Ansatz hierfür ist die Verwendung einer Erweiterungsklasse, die bei der Erstellung des Diensthosts aktiviert wird. Diese Erweiterung erstellt und verwaltet eine globale instance der Workflowlaufzeit, sodass jede der unabhängigen Dienstinstanzen darauf zugreifen kann.

Um eine Erweiterung für ServiceHost zu implementieren, erstellen Sie eine Klasse, die IExtension<ServiceHostBase> implementiert. In der Projektmappe finden Sie ein Beispiel dafür in der WfWcfExtension-Klasse , die sich unter dem WcfExtensions-Codeprojekt befindet.

Wir müssen zwei Methoden implementieren: Attach, das aufgerufen wird, wenn die Erweiterung an das übergeordnete Objekt angefügt wird, und Detach, das aufgerufen wird, wenn das übergeordnete Objekt entladen wird.

Die hier gezeigte Attach-Methode erstellt eine neue instance von WorkflowRuntime und instanziiert sie mit den erforderlichen Diensten. Dies wird in einem lokalen privaten Feld namens 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, umfasst unsere Initialisierung der Workflowlaufzeit auch das Hinzufügen von Dienstinstanzen zur Laufzeit, bevor sie gestartet wird. Beim Erstellen Ihrer Lösungen wird im Allgemeinen empfohlen, vor dem Starten der Runtime alle Dienste hinzuzufügen. Wenn die Kopplung jedoch ein Problem darstellt, ist es möglicherweise sinnvoller, einen Spätbindungsansatz zu verwenden.

In unserem Beispiel fügen wir im Rahmen der SetUpWorkflowEnvironment-Methode in der ExpenseService-Klasse eine instance von ExpenseLocalService in Den ExternalDataExchangeService hinzu, nachdem WorkflowRuntime bereits gestartet wurde.

Die hier gezeigte Detach-Methode beendet die Laufzeit durch Aufrufen von StopRuntime.

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

Wenn WorkflowRuntime als Teil des Diensthoststarts erstellt und initialisiert wird, können alle vorhandenen Workflows vor Dienstaufrufen fortgesetzt werden. Wenn der Diensthost beendet wird, wird die Workflowlaufzeit ordnungsgemäß heruntergefahren.

Hinweis Es wird empfohlen, Workflowpersistenz (z. B. SqlWorkflowPersistenceService) beim Hosten von Workflows und beim Modellieren von Workflows mit langer Ausführungsdauer zu verwenden, was die Norm ist. Dadurch erhalten Sie einen Mechanismus für die Zustandspersistenz, um anwendungs- oder prozessneustarts zu überstehen.

Erstellen von Dienstvorgängen

Um eine Klasse zu erstellen, die das Dienstverhalten enthält, müssen Sie eine oder mehrere Schnittstellen implementieren, die einen Dienstvertrag definieren.

public class ExpenseService :
        IExpenseService,
        IExpenseServiceClient,
        IExpenseServiceManager

Zur Integration in unseren Workflow enthalten unsere Dienstmethoden keine Geschäftslogik, sondern code zum Steuern oder Auslösen von Ereignissen in einem ausgeführten Workflow, der den Geschäftsprozess kapselt.

Bei Vorgängen, die sich mit dem Workflow befassen, starten wir entweder einen neuen Workflow oder interagieren mit einem bereits ausgeführten Workflow.

Zum Erstellen eines neuen Workflow-instance müssen wir eine WorkflowRuntime verwenden, um eine neue instance des gewünschten Workflowtyps zu instanziieren. Eine dieser Optionen wurde bereits in unserer ServiceHost-Erweiterungsklasse erstellt. Um einen Verweis auf diese instance zu erhalten, müssen wir die benutzerdefinierte Erweiterung mithilfe von OperationContext finden.

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

OperationContext ist eine Klasse, die uns Zugriff auf den Ausführungskontext der Dienstmethode ermöglicht. Wie Sie im vorherigen Code sehen können, wird ein Singleton mit dem Namen Current bereitgestellt, der uns den Kontext für die aktuelle Dienstmethode bereitstellt. Wir rufen die Host-Eigenschaft auf, um eine instance zurück zu dem ServiceHost abzurufen, in dem wir ausgeführt werden, und suchen dann unsere Erweiterung basierend auf ihrem Typ.

Nachdem wir über einen Verweis auf unsere Erweiterung instance verfügen, können wir workflowRuntime über unsere öffentliche Eigenschaft zurückgeben und damit eine neue instance des SequentialWorkflows erstellen.

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 vorherigen Code erstellen wir einen neuen Workflow instance basierend auf einem vordefinierten Typ. Obwohl dies durch direkte Typinstanziierung erreicht werden könnte, zeigt dies, dass wir tatsächlich die Flexibilität haben könnten, einen Workflow basierend auf einer dynamischen Regel zur Laufzeit zu erstellen, anstatt über eine stark typisierte Bindung.

Die letzte Zeile signalisiert den Start des Workflows, indem ein Ereignis ausgelöst wird, das von der ersten HandleExternalEventActivity im Workflow behandelt wird. Dies wird durch eine instance der ExpenseLocalService-Klasse erhöht. Im Beispiel wird ExpenseLocalService verwendet, um mit dem Workflow zu interagieren, indem neue Workflows gestartet oder Ereignisse für vorhandene Workflows ausgelöst werden. Wir verwenden diese Klasse als Mechanismus zum Kapseln des Geschäftsprozesses. Intern implementieren wir dies mithilfe von WF.

Bb266709.intgrwfwcf03(en-us,MSDN.10).gif

Abbildung 3. Unser Workflow beginnt mit einer HandleExternalEventActivity.

Die andere Art von Situation, mit der wir uns befassen werden, besteht darin, einen vorhandenen Workflow zurückzurufen und Ereignisse auszurufen. Wir müssen ein Ereignis für die Workflow-Engine auslösen, das dazu führt, dass unser vorhandener Workflow das Ereignis empfängt und die Verarbeitung fortsetzt.

Ein Beispiel dafür, wo dies im Ablauf der Spesenabrechnung auftritt, ist, wenn eine Managergenehmigung erforderlich ist. Der Workflow ruft die externe Methode für RequestManagerApproval auf, die eine Warnung an den Manager ausgibt, dass er eine neue Spesenabrechnung genehmigen oder ablehnen muss.

Der Workflow enthält eine ListenActivity , die blockiert, bis eines der möglichen Ereignisse aufgetreten ist. In diesem Fall erhalten wir ein Ereignis, das angibt, dass ein Vorgesetzter den Bericht überprüft hat, oder dass ein Timeout basierend auf einer DelayActivity erfolgt.

Bb266709.intgrwfwcf04(en-us,MSDN.10).gif

Abbildung 4. Der benutzerdefinierte Aktivitätsfluss "ManagerApproval"

Guid workflowInstanceId = 
submitReviewedExpenseReportRequest.Report.ExpenseReportId;

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

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

Wenn ein Vorgesetzter einen Bericht mithilfe der ManagerApplication überprüft, wird ein Dienstaufruf an den Host zurückgegeben, der die SubmitReviewedExpenseReport-Methode aufruft , die das ExpenseReportReviewed-Ereignis auslöst.

Wenn Sie ein Ereignis in eine HandleExternalEventActivity im Workflow auslösen, müssen Sie die GUID des Workflows instance kennen, mit dem wir uns befassen, damit das Ereignis weitergeleitet werden kann.

Jedes Ereignis wird mit EventArgs ausgelöst, das es uns ermöglicht, Daten über das Ereignismodell an den Workflow zurück zu übergeben. In diesem Fall können wir Details sowohl zum aktuellen Status des Berichts als auch zu den Daten übergeben, die uns Kontext zur Überprüfungsaktivität geben.

Im Workflow werden Ereignisse automatisch über die Eigenschaften einer HandleExternalEventActivity mit dem Workflow verknüpft.

Bb266709.intgrwfwcf05(en-us,MSDN.10).gif

Abbildung 5. Wir verkabeln die HandleExternalEventActivity mit der IExpenseLocalService-Schnittstelle.

Sie geben den Schnittstellentyp an, der mit dem Attribut [ExternalDataExchange] gekennzeichnet werden muss, und dann das Ereignis für diese Schnittstelle, für das HandleExternalEventActivity abonniert werden soll.

Die Ereignisargumente müssen von der ExternalDataEventArgs-Klasse abgeleitet werden. Dies bedeutet mindestens, dass jedes Ereignis Kontext enthält, z. B. die InstanceId des Workflows. Die Workflowlaufzeit verwaltet dann das Routing des Ereignisses an den richtigen Workflow instance, um es fortzusetzen. Wenn wir einen Persistenzdienst verwenden, verwaltet die Runtime auch die Hydratation und Rehydrierung jedes ausgeführten Zustands für den Workflow über die Dauer seiner Ausführung.

Hosten des Diensts

Zum Hosten eines WCF-Diensts müssen wir im ServiceHost-Container ausgeführt werden.

Um zu überprüfen, wie das Hosting mit WCF erreicht werden kann, lassen Sie uns zunächst die verfügbaren Alternativen verstehen:

  • Bei Windows-Standardprozessen kann eine instance von ServiceHost manuell erstellt und geöffnet werden.
  • Beim Hosten eines Webendpunkts (Webdienst) über Microsoft-Internetinformationsdienste (IIS) 6.0 verwenden wir einen benutzerdefinierten HttpHandler, der unter dem System.ServiceModel-Namespace bereitgestellt wird.
  • Beim Hosten unter IIS 7 können wir den Windows-Aktivierungsdienst (WAS) verwenden, um unsere Endpunkte zu hosten.

Normalerweise sollten Sie den Host mithilfe von Internetinformationsdienste auswählen, wenn Sie Webdienste erstellen. Wenn Sie einen einzelnen instance-Endpunkt erstellen, der als Daemon fungiert, entscheiden Sie sich normalerweise für den Host mithilfe eines Windows-Diensts.

In unserem Beispiel hosten wir den Standard-Dienst instance in einer Windows-Konsolenanwendung, die dem Hosten eines Windows-Diensts ähnelt.

Um einen Dienst bereitzustellen, müssen wir eine instance der ServiceHost-Klasse erstellen und deren Endpunkte für jeden Diensttyp öffnen, den wir veröffentlichen möchten. ServiceHost akzeptiert eine Reihe von Argumenten als Teil des Konstruktors. Das primäre Argument ist jedoch entweder ein Type-Argument oder ein instance einer Klasse, die einen ServiceContract implementiert.

  • Verwenden Sie einen Typ , wenn Sie die PerCall - oder PerSession-Instancing verwenden möchten.
  • Verwenden Sie eine einzelne instance, wenn Sie die Einzelfinanzierung verwenden.

Weitere Informationen zur Instancing und Parallelität innerhalb von WCF finden Sie unter Sitzungen, Instancing und Parallelität in der MSDN Library.

Nachdem ein Host eingerichtet wurde, analysiert er alle verfügbaren Konfigurationen (weitere Informationen dazu finden Sie im folgenden Abschnitt Konfigurieren der Bereitstellung) und führt diese mit jeder explizit hinzugefügten Konfiguration zusammen, um die verfügbaren Endpunkte zu ermitteln und diese Endpunkte für die Veröffentlichung zu öffnen. Wenn Aufrufe von Clients empfangen werden, werden die Anforderungen in neuen Hintergrundarbeitsthreads verarbeitet und an die entsprechenden Dienstvorgänge weitergeleitet, wie durch den SOAP-Vertragsnamen und die Aktion für die Nachricht angegeben.

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 vor dem Öffnen der Endpunkte für Verbindungen tun. Sie können dies tun, wie zuvor gezeigt, indem Sie mit dem Hostobjekt interagieren, bevor Sie aufrufen . Open(). Es wird empfohlen, einen using-Bereich zu verwenden, um den ServiceHost vor der Verwendung zu entfernen, und dass Sie Close() explizit am Ende dieses Bereichs aufrufen, um alle aktiven Verbindungen und Endpunkte sauber herunterzufahren.

Konfigurieren der Bereitstellung

WCF bietet einen Mechanismus zum Trennen von Bereitstellungsproblemen von der Implementierung, indem Endpunkte über die XML-Konfiguration konfiguriert werden können. Dadurch können Administratoren die Richtlinie eines Diensts ändern, ohne dass Code neu entwickelt werden muss.

Jeder Dienst wird auf einem oder mehreren Endpunkten veröffentlicht. Ein Endpunkt ist einfach ein adressierbarer Verbindungspunkt, mit dem Clients den Dienst nutzen können. In WCF wird jeder Endpunkt mit drei Attributen deklariert, die als ABCs von WCF populär gemacht wurden.

Hierbei handelt es sich um eine Adresse, eine Bindung und einen Vertrag.

Adresse: Der eindeutige adressierbare Speicherort dieses Endpunkts. In der Regel ist dies ein URI, der Ihnen die absolute Adresse angibt, an der der Dienst auf Anforderungen lauscht, z. B.: http://myhost/myservice oder net.tcp://myhost:400/myservice

Bindung: Die Richtlinie, die das Protokoll für die Kommunikation zwischen dem Dienst und seinen Consumern diktiert. Die Bindung gibt Aspekte an, z. B. den Typ des verwendeten Transports, die Art und Weise, wie Nachrichten codiert werden, und die Art und Weise, wie Daten serialisiert werden. WCF enthält eine Reihe von sofort einsatzbereiten Bindungen, die die meisten gängigsten Szenarien unterstützen.

Vertrag: Die Vorgänge und Daten, die veröffentlicht werden, wie wir über eine Schnittstelle im Code definiert haben.

Um unseren Dienst zu konfigurieren, müssen wir eine Konfiguration deklarieren, die unseren Dienst deklariert, und eine beliebige Anzahl von Endpunkten für den Dienst konfigurieren. 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 gezeigt.

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

In diesem Konfigurationsbeispiel deklarieren wir die Konfiguration für den Dienst vom Typ ExpenseServices.ExpenseService. Dadurch kann die Runtime die Konfiguration finden, wenn wir einen neuen ServiceHost basierend auf diesem Typ instanziieren. Weitere Informationen zu Bindungen finden Sie unter WCF-Bindungen in der MSDN Library.

Nutzen des Diensts

Die Nutzung von Diensten mithilfe von WCF erfolgt mithilfe der ChannelFactory-Klasse . ChannelFactory verwendet das Factorymuster, um Proxyinstanzen eines Dienstvertrags bereitzustellen, die eine Verbindung mit dem in der Konfiguration angegebenen Endpunkt herstellen. Wir können die Factory mit Laufzeitinformationen wie Sicherheitsanmeldeinformationen und Zertifikaten für die Nachrichtenverschlüsselung konfigurieren oder die Endpunktinformationen dynamisch bestimmen.

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

   return proxy;
}

Wie Sie sehen, erstellen wir zunächst eine instance der Factory, die ein generisches Argument für den Dienstvertrag verwendet, um eine präzisere Factory zu erstellen, die nur Instanzen des gewünschten Vertrags zurückgibt. Wir geben auch ein Argument an, das die Konfiguration bestimmt, die für den Endpunkt verwendet wird. In diesem Fall verwenden wir eine Endpunktkonfiguration namens ExpenseServiceManager, die sich auf die Konfiguration in unserer Anwendungskonfigurationsdatei bezieht.

<system.serviceModel>
   <client>
         <endpoint name="ExpenseServiceManager"
            address="https://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 ist. Im Allgemeinen besteht der einzige Zeitpunkt, zu dem sich die Konfiguration unterscheidet, darin, dass sich die Adresse aufgrund der Netzwerkkonfiguration zwischen Client und Server unterscheidet oder das benutzerdefinierte Verhalten implementiert wird.

Wenn Sie das Windows SDK installiert haben, finden Sie ein bereitgestelltes Tool (svcutil), das die Erstellung einer Proxyklasse und Endpunktkonfiguration automatisiert, die Sie in Ihre Lösung integrieren können. Der Zieldienst muss eine Beschreibung seiner Metadaten über WSDL oder WS-MetadataExchange veröffentlichen, um dieses Tool nutzen zu können.

Konfigurieren von Duplexkanälen

Bisher haben wir davon ausgegangen, dass unser Kommunikationsfluss ein Anforderungsantwortmuster für die Zusammenarbeit verwendet, wobei Nachrichten von einem Consumer gesendet und von einem Dienst beantwortet werden. WCF unterstützt eine Reihe alternativer Nachrichtenflüsse, z. B. unidirektionale Kommunikation (Fire and Forget) oder bidirektionale Duplexkommunikation. Wenn es sich um einen Nachrichtenfluss handelt, in dem beide Parteien eine Konversation initiieren können, müssen wir einen Duplex- oder Bidirektionalen Kanal verwenden. Duplexkanäle können sehr effektiv für stärker verbundene Systeme sein, in denen Daten aus beiden Richtungen gesendet werden können. Ein Beispiel dafür, wo dies für Sie nützlich sein kann, ist die Bereitstellung von Rückrufen von Ereignisvorgängen.

Implementieren von Clientrückrufen

Clientrückrufe werden in WCF über ein Konzept namens CallbackContracts implementiert. Für einen Vertrag, den wir veröffentlichen, können wir einen zweiten Vertrag nominieren, um Vorgänge zu definieren, die von den Clients veröffentlicht werden und die durch Code, der auf dem Dienst ausgeführt wird, aufgerufen werden können.

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

[ServiceContract(CallbackContract = 
typeof(IExpenseServiceClientCallback))]

Sie müssen auch eine Bindung verwenden, die Duplexkanäle unterstützt, z. B. netTcpBinding oder wsDualHttpBinding. Die Duplexierung über TCP wird durch eine bidirektionale Verbindung erreicht, die während des gesamten Nachrichtenaustauschs hergestellt und beibehalten wird. Über HTTP wird dies durch einen Rückruf an einen Clientlistener erreicht. Da der Client möglicherweise nicht über seinen Rückgabepfad informiert ist oder Sie diesen über die Konfiguration stark definieren möchten, können wir eine benutzerdefinierte Bindungskonfiguration verwenden, um eine alternative clientBaseAddress zu deklarieren.

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

Implementieren des Rückrufs auf dem Client

Die Implementierung eines Rückrufvertrags entspricht genau der Implementierung eines Dienstvertrags. Wir müssen eine Implementierung der von uns definierten Schnittstelle bereitstellen.

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

Damit der Host eine instance der CallbackHandler-Klasse zurückrufen kann, müssen wir unseren Clientkanal so einrichten, dass er über die Duplexnatur der Verbindung weiß.

Erstens verwenden wir, wie bereits beschrieben, eine Bindung, die Duplexkanäle unterstützt. Zweitens verwenden wir beim Initialisieren der Verbindung mit dem Dienstendpunkt eine unterklassierte Version von ChannelFactory namens DuplexChannelFactory , die für uns eine Duplexverbindung mit dem Dienst erstellt.

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 Verwendung einer DuplexChannelFactory besteht darin, dass wir eine instance unserer CallbackHandler-Klasse initialisieren und diese an den Konstruktor übergeben, damit die Factory einen Kontext initialisiert, der für Rückrufe verwendet werden soll.

Implementieren des Rückrufs vom Host

Aus Sicht des Hosts können wir über den Rückrufkanal, der in unserem IExpenseServiceClient-Vertrag definiert ist, einen Verweis zum Rückruf an unseren Client abrufen.

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

Das CallbackContract-Attribut deklariert die Schnittstelle, die den Vertrag für die vom Host vorgenommenen Rückrufe definiert.

Um den Rückruf durchzuführen, erhalten wir einen Verweis auf den Rückrufvertrag, indem wir OperationContext.Current.GetCallbackChannel aufrufen, wie hier gezeigt.

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

Nachdem wir einen Verweis auf unseren Rückrufkanal haben, können wir ihn normal aufrufen.

Zusammenfassung

Windows Workflow Foundation bietet ein allgemeines Framework zum Definieren von Workflows und eine robuste Runtime-Engine, mit der Sie die ausgeführten Workflows hosten und interagieren können.

Windows Communication Foundation bietet ein allgemeines Framework zum Erstellen verbundener Systeme und bietet Entwicklern eine konsistente API und umfassende Funktionen, um zu definieren, wie die Kommunikation erfolgt.

Sie können diese beiden Frameworks zusammen verwenden, um eine flexible und umfassende Anwendungsplattform zum Erstellen und Bereitstellen verteilter Geschäftsprozesse in Ihrer Umgebung bereitzustellen. MIT WF können Sie Ihre Geschäftslogik und Ihren Geschäftsprozess modellieren und kapseln, während WCF Ihnen Messaginginfrastruktur bietet, die Ihnen die Möglichkeit bietet, Ihre Systeme zu verteilen.

Hier sind einige Richtlinien, die Sie beim Entwerfen Ihrer Dienste beachten sollten:

  • Sie sollten für workflows mit langer Ausführungszeit einen Persistenzdienst verwenden.
  • Ein Dienstvorgang kann über einen ausgeführten Workflow interagieren, indem Ereignisse ausgelöst werden. Ihre Workflows sollten so konzipiert sein, dass Sie Ereignisse auslösen, wenn sie Aufmerksamkeit erfordern müssen, und auf Ereignisse reagieren, wenn sie extern (z. B. von einem externen Dienst oder einem Mitarbeiter) aus interagieren.
  • Workflows werden asynchron mit dem Dienstaufruf ausgeführt. Entwerfen Sie also angemessen, wenn Sie über die Rückgabe von Daten aus dem Dienst nachdenken und den Zustand, in dem sich die Daten zu diesem Zeitpunkt befinden könnten. Wenn Sie einen synchronen Ansatz verwenden möchten, können Sie die ManualWorkflowSchedulerService-Klasse verwenden, um die manuelle Planung der Workflowausführung zu ermöglichen.

Weitere Informationen

  1. Sitzungen, Instanziierung und Parallelität
  2. WCF-Bindungen
  3. Verwenden von Datenverträgen
  4. Verwendung von Nachrichtenverträgen
  5. .NET Framework 3.0 Communitywebsite
  6. Windows Workflow Foundation-Foren

Danksagung

Wir möchten folgenden beteiligten Personen und Editoren für ihre Bemühungen herzlich danken:

  • Christian Weyer, thinktecture
  • Paul Andrew, Microsoft Corporation
  • Khalid Aggag, Microsoft Corporation
  • Patrice Manac'h, Microsoft Corporation

 

Informationen zum Autor

Jeremy Boyd ist senior technical consultant für Intergen, einen neuseeländischen Lösungsanbieter und Microsoft Gold Certified Partner sowie MSDN Regional Director in der neuseeländischen Community. In den letzten 12 Monaten hat Jeremy aktiv mit Kunden zusammengearbeitet, um Lösungen zu implementieren, die auf WF und WCF basieren, und anderen Entwicklern dabei zu helfen, die Vorteile dieser Technologien über sein Weblog kennenzulernen.