Freigeben über


ASP.NET-Workflow

Webanwendungen, die lang andauernde Vorgänge unterstützen

Michael Kennedy

Codedownload verfügbar in der MSDN-Codegalerie
Code online durchsuchen

Themen in diesem Artikel:

  • Prozessunabhängige Workflows
  • Synchrone und asynchrone Aktivitäten
  • Workflows, Aktivitäten und Persistenz
  • Integration in ASP.NET
In diesem Artikel werden folgende Technologien verwendet:
Windows Workflow Foundation, ASP.NET

Inhalt

Nutzen von Workflows
Synchrone und Asynchrone Aktivitäten
Was genau ist mit Leerlauf gemeint?
Synchrone Aufgaben asynchron gestalten
Workflows und Aktivitäten
Persistenz
Umsetzung in die Realität
Integration in ASP.NET
Weitere Erwägungen
Zusammenfassung

Softwareentwickler werden oft gebeten, Webanwendungen zu erstellen, die lang andauernde Vorgänge unterstützen. Ein Beispiel ist der Bestellabschluss in Onlineshops, der mehrere Minuten in Anspruch nehmen kann. Obwohl dies nach manchen Maßstäben ein lang andauernder Vorgang ist, werden in diesem Artikel lang andauernde Vorgänge eines ganz anderen Ausmaßes untersucht: Vorgänge, die Tage, Wochen oder sogar Monate dauern können. Ein Beispiel für einen solchen Vorgang ist die Personaleinstellung, die Interaktionen zwischen mehreren Personen und den Austausch vieler realer Dokumente umfassen kann.

Betrachten Sie zunächst ein etwas einfacheres Problem von einem ASP.NET-Standpunkt aus: Sie müssen eine Lösung für den Bestellabschluss in einem Onlineshop entwerfen. Bei dieser Lösung müssen aufgrund der Dauer besondere Überlegungen angestellt werden. Sie könnten sich z. B. entscheiden, die Warenkorbdaten in einer ASP.NET-Sitzung zu speichern. Vielleicht verschieben Sie diesen Sitzungszustand sogar in einen prozessexternen Zustandsserver oder eine prozessexterne Datenbank, um Websiteaktualisierungen und Lastenausgleich zu ermöglichen. Sie werden feststellen, dass alle erforderlichen Tools, um dieses Problem ohne große Schwierigkeiten zu lösen, von ASP.NET bereitgestellt werden.

Wenn die Dauer des Vorgangs jedoch die typische ASP.NET-Sitzungsdauer (20 Minuten) überschreitet oder mehrere Akteure erfordert (wie im Beispiel der Personaleinstellung), bietet ASP.NET keine ausreichende Unterstützung. Sie erinnern sich vielleicht, dass die ASP.NET-Arbeitsprozesse automatisch in den Leerlauf übergehen und regelmäßig neu starten. Dies führt zu großen Problemen bei lang andauernden Vorgängen, da der innerhalb dieser Prozesse vorhandene Zustand verloren geht.

Stellen Sie sich für einen Moment vor, dass Sie diese sehr lang andauernden Vorgänge innerhalb eines einzelnen Prozesses hosten müssen. Offensichtlich ist der ASP.NET-Arbeitsprozess aus den gerade beschriebenen Gründen hierzu nicht geeignet. Vielleicht ist es möglich, einen Windows-Dienst zu erstellen, dessen einzige Aufgabe darin besteht, diese Vorgänge auszuführen. Wenn Sie diesen Dienst niemals neu starten, sind Sie einer Lösung näher, als wenn Sie ASP.NET direkt verwenden, denn einfach dadurch, dass ein Dienstprozess vorliegt, der nicht automatisch neu gestartet wird, wird theoretisch sichergestellt, dass der Zustand des lang andauernden Vorgangs nicht verloren geht.

Aber ist das Problem damit wirklich gelöst? Wahrscheinlich nicht. Was ist, wenn der Server Lastenausgleich erfordert? Das wird sehr schwierig, wenn Sie an einen einzelnen Prozess gebunden sind. Schlimmer noch: Was ist, wenn Sie den Server neu starten müssen oder der Prozess abstürzt? Dann gehen alle ausgeführten Vorgänge verloren.

Wenn Vorgänge Tage oder Wochen dauern, benötigen Sie eine Lösung, die unabhängig vom Lebenszyklus des Prozesses ist, der sie ausführt. Dies gilt generell, ist aber besonders für ASP.NET-Webanwendungen wichtig.

Nutzen von Workflows

Windows Workflow Foundation (WF) ist nicht unbedingt die Technologie, die zum Erstellen von Webanwendungen in Betracht gezogen wird. WF enthält jedoch mehrere wichtige Features, die eine Workflowlösung erwägenswert machen. WF bietet die Möglichkeit, Prozessunabhängigkeit für lang andauernde Vorgänge zu erreichen, indem Workflows im Leerlauf vollständig aus dem Prozessbereich entladen und automatisch in den aktiven Prozess geladen werden, wenn sie sich nicht mehr im Leerlauf befinden (siehe Abbildung 1). Mittels WF können Sie über den nicht deterministischen Lebenszyklus des ASP.NET-Arbeitsprozesses hinausgehen und lang andauernde Vorgänge innerhalb einer Webanwendung ermöglichen.

fig01.gif

Abbildung 1 Workflows bewahren Vorgänge über Prozessinstanzen hinweg auf

Für diese Funktion werden zwei Hauptfeatures von WF kombiniert. Erstens wird ein asynchrones Aktivitätssignal an die Workflowlaufzeit gesendet, um sie zu informieren, dass sich der Workflow im Leerlauf befindet, da er auf ein externes Ereignis wartet. Zweitens entlädt ein Persistenzdienst Workflows im Leerlauf aus dem Prozess, speichert sie in einem dauerhaften Speicher wie z. B. einer Datenbank und lädt die Workflows, wenn sie wieder ausgeführt werden sollen.

Diese Prozessunabhängigkeit bietet weitere Vorteile. Sie ermöglicht auf einfache Weise Lastenausgleich und Dauerhaftigkeit – Fehlertoleranz bei Prozess- oder Serverfehlern.

Synchrone und asynchrone Aktivitäten

Aktivitäten sind die atomarischen Elemente von WF. Alle Workflows werden mithilfe von Aktivitäten in einem Muster erstellt, das dem zusammengesetzten Entwurfsmuster ähnelt. Workflows selbst sind einfach spezialisierte Aktivitäten. Diese Aktivitäten können in synchrone und asynchrone Aktivitäten unterteilt werden. Eine synchrone Aktivität führt alle ihre Anweisungen von Anfang bis Ende aus.

Ein Beispiel für eine synchrone Aktivität ist beispielsweise die Berechnung der Mehrwertsteuer für einen Auftrag in einem Onlineshop. Wie könnte eine solche Aktivität implementiert werden? Wie bei den meisten WF-Aktivitäten erfolgt der größte Teil der Arbeit in der außer Kraft gesetzten Execute-Methode. Die Schritte dieser Methode könnten wie folgt aussehen:

  1. Empfangen der Auftragsdaten von einer früheren Aktivität. Dies erfolgt meist durch Datenbindung. Ein Beispiel dafür folgt später.
  2. Nachschlagen des Kunden, der mit dem Auftrag verbunden ist, in einer Datenbank.
  3. Nachschlagen des Steuersatzes in einer Datenbank basierend auf dem Standort des Kunden.
  4. Durchführen einfacher Berechnungen anhand des Steuersatzes und der bestellten Artikel.
  5. Speichern des Mehrwertsteuergesamtbetrags in einer Eigenschaft, die darauffolgende Aktivitäten binden können, um den Bestellabschluss fertigzustellen.
  6. Informieren der Workflowlaufzeit, dass diese Aktivität abgeschlossen ist, indem von den Execute-Methoden das Zustandskennzeichen „Completed“ zurückgegeben wird.

Beachten Sie, dass an keinem Punkt Wartezeiten eintreten. Es findet immer ein Arbeitsprozess statt. Die Execute-Methode führt einfach die Schritte aus und ist nach kurzer Zeit fertig. Dies ist das Wesentliche an einer synchronen Aktivität: Die gesamte Arbeit wird in der Execute-Methode ausgeführt.

Asynchrone Aktivitäten sind dagegen anders. Im Unterschied zu ihren synchronen Gegenstücken werden asynchrone Aktivitäten für eine Weile ausgeführt und warten dann auf ein externes Ereignis. Während der Wartezeit befinden sich die Aktivitäten im Leerlauf. Wenn das Ereignis stattfindet, wird die Aktivität fortgesetzt und abgeschlossen.

Ein Beispiel für eine asynchrone Aktivität ist ein Schritt im Personaleinstellungsprozess, wenn eine Bewerbung für eine Position von einem Manager begutachtet werden muss. Was würde geschehen, wenn dieser Manager im Urlaub ist und bis nächste Woche keine Möglichkeit hat, sich die Bewerbung anzusehen? Es ist völlig unangemessen, in der Mitte der Execute-Methode anzuhalten und auf die Antwort zu warten. Wenn Software auf eine Person wartet, muss sie möglicherweise sehr lange warten. Dies muss in Ihrem Entwurf berücksichtigt werden.

Was genau ist mit Leerlauf gemeint?

Bedeutet „Leerlauf“, dass die Aktivität untätig ist? Treten Sie einen Schritt von WF zurück, um allgemeiner über diese Frage nachzudenken.

Nehmen Sie z. B. die folgende Klasse, die einen Webdienst verwendet, um ein Kennwort zu ändern:

public class PasswordOperation : Operation {
  Status ChangePassword(Guid userId, string pw) {
    // Create a web service proxy:
    UserService svc = new UserService();

    // This can take up to 20 sec for 
    // the web server to respond:
    bool result = svc.ChangePassword( userId, pw );

    Logger.AccountAction( "User {0} changed pw ({1}).",
      userId, result);
    return Status.Completed;
  }
}

Ist die ChangePassword-Methode dabei jemals untätig? Wenn ja, an welcher Stelle?

Der Thread dieser Methode wird blockiert, während er auf eine HTTP-Antwort von UserService wartet. In konzeptioneller Hinsicht lautet die Antwort also: Ja, der Thread ist untätig, während er auf die Dienstantwort wartet. Aber kann der Thread nicht vielleicht andere Arbeit leisten, während auf den Dienst gewartet wird? Nein, da er derzeit verwendet wird. Aus einer WF-Perspektive ist dieser „Workflow“ also niemals untätig.

Weshalb ist er niemals untätig? Stellen Sie sich vor, Ihnen liegt eine größere Planerklasse vor, die effizient Vorgänge wie ChangePassword ausführen soll. Mit „effizient“ meine ich das parallele Ausführen mehrerer Vorgänge mithilfe der Mindestzahl von Threads, die für vollständige Parallelität erforderlich ist, und so weiter. Der Schlüssel zu dieser Effizienz besteht in der Kenntnis, wann der Vorgang ausgeführt wird und wann er sich im Leerlauf befindet. Wenn sich ein Vorgang im Leerlauf befindet, kann der Planer nämlich den Thread, der den Vorgang ausgeführt hat, verwenden, um andere Arbeit zu erledigen, bis der Vorgang wieder bereit für die Ausführung ist.

Leider ist die ChangePassword-Methode für den Planer völlig undurchsichtig. Obwohl es einen Zeitraum gibt, in dem der Thread effektiv untätig ist, ist diese Methode aus der externen Sicht des Planers eine einzige Einheit, die weitere Arbeit blockiert. Der Planer hat keine Möglichkeit, diese Arbeitseinheit zu zerteilen und den Thread während des Leerlaufzeitraums anderweitig zu verwenden.

Synchrone Aufgaben asynchron gestalten

Sie können dem Vorgang die benötigte Planungstransparenz hinzufügen, indem Sie ihn in zwei Teile teilen: einen, der ausgeführt wird, bis sich der Vorgang möglicherweise im Leerlauf befindet, und einen, der den Code nach dem Leerlaufzustand ausführt.

Im oben erwähnten hypothetischen Beispiel könnten Sie die asynchronen Funktionen verwenden, die vom Webdienstproxy bereitgestellt werden. Denken Sie daran, dass dies eine Vereinfachung darstellt und WF, wie Sie gleich sehen werden, in Wirklichkeit ein wenig anders arbeitet.

In Abbildung 2 habe ich eine verbesserte Version der Kennwortänderungsmethode namens „ChangePasswordImproved“ erstellt. Erstellen Sie den Webdienstproxy so wie zuvor. Dann registriert die Methode eine Rückrufmethode, um benachrichtigt zu werden, wenn der Server geantwortet hat. Als Nächstes führen Sie den Dienstaufruf asynchron aus und teilen dem Planer durch Zurückgeben von Status.Executing mit, dass sich der Vorgang im Leerlauf befindet, aber nicht abgeschlossen ist. Dieser Schritt ist wichtig, denn er macht es möglich, dass andere Arbeit vom Planer durchgeführt wird, während sich Ihr Code im Leerlauf befindet. Wenn schließlich das Completed-Ereignis stattfindet, rufen Sie den Planer auf, um zu signalisieren, dass der Vorgang beendet ist und der Prozess fortgesetzt werden kann.

Abbildung 2 Einfacher Dienstaufruf zur Kennwortänderung

public class PasswordOperation : Operation {
  Status ChangePasswordImproved(Guid userId, string pw) {
    // Create a web service proxy:
    UserService svc = new UserService();

    svc.ChangePasswordComplete += svc_ChangeComplete;
    svc.ChangePasswordAsync( userId, pw );
    return Status.Executing;
  }

  void svc_ChangeComplete(object sender, PasswordArgs e) {
    Logger.AccountAction( "User {0} changed pw ({1}).",
      e.UserID, e.Result );

    Scheduler.SignalCompleted( this );
  }
}

Workflows und Aktivitäten

Nun wenden Sie das Konzept eines Vorgangs im Leerlauf auf das Erstellen von Aktivitäten in WF an. Dies hat große Ähnlichkeit mit den weiter oben aufgeführten Details, aber jetzt müssen Sie innerhalb des WF-Modells arbeiten.

WF besitzt viele vordefinierte Aktivitäten. Wenn Sie jedoch erst einmal reale Systeme mittels WF erstellen, werden Sie bald Ihre eigenen benutzerdefinierten wiederverwendbaren Aktivitäten erstellen wollen. Dies ist ganz einfach. Sie definieren eine Klasse, die von der universellen Activity-Klasse abgeleitet ist. Es folgt ein einfaches Beispiel:

class MyActivity : Activity {
  override ActivityExecutionStatus 
    Execute(ActivityExecutionContext ctx) {

    // Do work here.
    return ActivityExecutionStatus.Closed;
  }
}

Damit Ihre Aktivität nutzbringend arbeiten kann, müssen Sie die Execute-Methode außer Kraft setzen. Wenn Sie eine kurzlebige synchrone Aktivität erstellen, implementieren Sie einfach den Vorgang der Aktivität innerhalb dieser Methode und geben den Status „Closed“ zurück.

Es gibt einige grundlegende Aspekte, die in realen Aktivitäten wahrscheinlich berücksichtigt werden müssen. Wie kommuniziert Ihre Aktivität mit anderen Aktivitäten innerhalb des Workflows und der größeren Anwendung, die den Workflow hostet? Wie greift sie auf Dienste wie Datenbanksysteme, Benutzeroberflächeninteraktion usw. zu? Beim Erstellen synchroner Aktivitäten sind diese Aspekte relativ einfach.

Das Erstellen asynchroner Aktivitäten hingegen kann eine komplexere Aufgabe sein. Glücklicherweise wird das verwendete Muster bei den meisten asynchronen Aktivitäten wiederholt. Sie können dieses Muster problemlos in einer Basisklasse zusammenfassen, wie gleich vorgeführt wird.

Dies sind die grundlegenden Schritte, die für das Erstellen der meisten asynchronen Aktivitäten erforderlich sind:

  1. Erstellen einer Klasse, die von Activity abgeleitet ist.
  2. Außerkraftsetzen der Execute-Methode.
  3. Erstellen einer Workflowwarteschlange, die für das Empfangen von Benachrichtigungen darüber verwendet werden kann, dass das erwartete asynchrone Ereignis abgeschlossen wurde.
  4. Abonnieren des QueueItemAvailable-Ereignisses der Warteschlange.
  5. Initiieren des Starts des lang andauernden Vorgangs (z. B. Senden einer E-Mail, in der ein Manager gebeten wird, sich eine Bewerbung für eine freie Stelle anzusehen).
  6. Warten auf ein externes Ereignis. Dadurch wird signalisiert, dass sich die Aktivität im Leerlauf befindet. Sie zeigen dies der Workflowlaufzeit an, indem Sie ExecutionActivityStatus.Executing zurückgeben.
  7. Wenn das Ereignis stattfindet, entfernt die Methode, die das QueueItemAvailable-Ereignis behandelt, das Element aus der Warteschlange, konvertiert es in den erwarteten Datentyp und verarbeitet die Ergebnisse.
  8. In der Regel ist die Aktivität damit beendet. Die Workflowlaufzeit wird dann durch Zurückgeben von ActivityExecutionContext.CloseActivity informiert.

Persistenz

Am Anfang dieses Artikels wurde erwähnt, dass die zwei grundlegenden Dinge, die für das Erreichen von Prozessunabhängigkeit mittels Workflows erforderlich sind, asynchrone Aktivitäten und ein Persistenzdienst sind. Die asynchronen Aktivitäten wurden gerade besprochen. Befassen wir uns nun mit der Technologie hinter der Persistenz: den Workflowdiensten.

Workflowdienste sind ein entscheidender Erweiterbarkeitspunkt für WF. Die WF-Laufzeit ist eine Klasse, die Sie in Ihrer Anwendung instanziieren, um alle ausgeführten Workflows zu hosten. Diese Klasse hat zwei entgegengesetzte Entwurfsziele, die durch das Konzept der Workflowdienste gleichzeitig erreicht werden. Das erste Ziel ist, dass diese Workflowlaufzeit ein einfaches Objekt ist, das an vielen Stellen verwendet werden kann. Das zweite Ziel ist, dass diese Laufzeit den Workflows während ihrer Ausführung leistungsfähige Funktionen bietet. Sie kann z. B. die Möglichkeit bieten, Workflows im Leerlauf automatisch beizubehalten, den Workflowfortschritt nachzuverfolgen und andere benutzerdefinierte Funktionen zu unterstützen.

Die Workflowlaufzeit bleibt standardmäßig einfach, weil nur einige dieser Funktionen vordefiniert sind. Komplexere Dienste wie Persistenz und Nachverfolgung werden durch das Dienstmodell optional installiert. Ein Dienst ist per Definition eine globale Funktion, die Sie für Ihre Workflows bereitstellen möchten. Sie installieren diese Dienste in der Laufzeit, indem Sie einfach die AddService-Methode der WorkflowRuntime-Klasse aufrufen:

void AddService(object service)

Da AddService einen System.Object-Verweis verwendet, können Sie hinzufügen, was immer Ihr Workflow benötigt.

Hier wird mit zwei Diensten gearbeitet: Zuerst wird WorkflowQueuingService für den Zugriff auf die Workflowwarteschlangen verwendet, die für das Erstellen asynchroner Aktivitäten erforderlich sind. Dieser Dienst wird standardmäßig installiert und kann nicht angepasst werden. Der andere Dienst ist SqlWorkflowPersistenceService. Dieser Dienst bietet selbstverständlich die Persistenzfunktionen und wird nicht standardmäßig installiert. Glücklicherweise ist er in WF enthalten. Sie müssen ihn nur der Laufzeit hinzufügen.

Bei einem Namen wie SqlWorkflowPersistenceService können Sie wetten, dass eine Datenbank erforderlich ist. Sie können für diesen Zweck eine leere Datenbank erstellen oder einer vorhandenen Datenbank einige Tabellen hinzufügen. Ich persönlich bevorzuge, eine dedizierte Datenbank zu verwenden, statt Workflowpersistenzdaten mit meinen anderen Daten zu vermischen. Erstellen Sie also in SQL Server eine leere Datenbank namens „WF_Persist“. Dann erstellen Sie das erforderliche Datenbankschema und die gespeicherten Prozeduren durch Ausführen einiger Skripts. Diese werden als Teil von Microsoft .NET Framework installiert und befinden sich standardmäßig im folgenden Ordner:

C:\Windows\Microsoft.NET\Framework\v3.0\Windows Workflow Foundation\SQL\EN\

Sie sollten zuerst das SqlPersistenceService_Schema.sql-Skript und dann das SqlPersistenceService_Logic.sql-Skript ausführen. Nun können Sie diese Datenbank für die Persistenz verwenden, indem Sie dem Persistenzdienst die Verbindungszeichenfolge übergeben:

SqlWorkflowPersistenceService sqlSvc = 
    new SqlWorkflowPersistenceService(
  @"server=.;database=WF_Persist;trusted_connection=true",
  true, TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(10));

wfRuntime.AddService(sqlSvc);

Mehr als dieser einfache AddService-Methodenaufruf ist nicht erforderlich, um Workflows im Leerlauf zu entladen und in der Datenbank zu speichern sowie sie dann wiederherzustellen, wenn sie wieder benötigt werden. Die WF-Laufzeit sorgt für alles andere.

Umsetzung in die Realität

Nun, da Sie die technischen Grundlagen gelegt haben, können Sie mit ihrer Hilfe eine ASP.NET-Website erstellen, die lang andauernde Vorgänge unterstützt. Die drei Hauptelemente, die Sie in diesem Beispiel sehen werden, sind das Erstellen asynchroner Aktivitäten, das Integrieren der Workflowlaufzeit in die Webanwendung und das Kommunizieren mit dem Workflow von den Webseiten aus.

Hier wird mit einem hypothetischen .NET-Beratungsunternehmen namens „Trey Research“ gearbeitet. Dieses Unternehmen möchte den Einstellungsprozess für Berater automatisieren. Sie erstellen also eine ASP.NET-Website, um diesen Einstellungsprozess zu unterstützen. Es soll alles möglichst einfach gehalten werden, aber bei diesem Prozess gibt es mehrere Schritte:

  1. Ein Bewerber besucht die Trey Research-Website und bringt sein Interesse für eine Position zum Ausdruck.
  2. Der Manager wird per E-Mail darüber informiert, dass es einen neuen Bewerber gibt.
  3. Dieser Manager sieht sich die Bewerbung an und genehmigt den Bewerber für eine bestimmte Position.
  4. Der Bewerber erhält eine E-Mail mit Informationen zur vorgeschlagenen Position.
  5. Der Bewerber besucht die Website und akzeptiert die Position oder lehnt sie ab.

Obwohl dieser Prozess recht einfach ist, gibt es mehrere Schritte, bei denen die Anwendung darauf wartet, dass eine Person bestimmte Informationen eingibt. Diese Leerlaufpunkte nehmen möglicherweise viel Zeit in Anspruch. Daher sind sie ideal für das Vorführen eines lang andauernden Prozesses geeignet.

Die Webanwendung ist im Quellcode für diesen Artikel enthalten. Um jedoch den Gesamteffekt zu sehen, müssen Sie die Persistenzdatenbank erstellen und das Beispiel für ihre Verwendung konfigurieren. Ich habe einen Schalter erstellt, um zu steuern, ob der Persistenzdienst ein- oder ausgeschaltet ist und habe ihn standardmäßig deaktiviert. Um ihn einzuschalten, setzen Sie usePersistDB im Abschnitt „AppSettings“ von web.config auf „true“.

fig03.gif

Abbildung 3 Der Einstellungsprozess als Workflow

Zunächst soll der Workflow völlig unabhängig von ASP.NET entworfen werden. Zum Erstellen des Workflows werden vier benutzerdefinierte Aktivitäten erstellt. Die erste ist eine einfache synchrone Aktivität zum Senden von E-Mails. Die anderen drei repräsentieren die erwähnten Schritte 1, 3 und 5 und sind asynchron. Diese Aktivitäten sind für den Erfolg des lang andauernden Vorgangs entscheidend. Ich nenne sie GatherEmployeeInfoActivity, AssignJobActivity und ConfirmJobActivity. Kombinieren Sie dann diese Aktivitäten im einfachen Workflow in Abbildung 3.

Die Aktivität zum Senden von E-Mails ist sehr einfach. Deshalb wird sie hier nicht im Detail erläutert. Es handelt sich um eine synchrone Aktivität wie die oben erwähnte MyActivity-Klasse. Detaillierte Informationen finden Sie im Codedownload.

Somit müssen nur noch die drei asynchronen Aktivitäten erstellt werden. Sie können sich viel Arbeit ersparen, wenn Sie den acht Schritte umfassenden Prozess für das Erstellen einer asynchronen Aktivität in eine allgemeine Basisklasse kapseln. Dazu definieren Sie eine Klasse namens „AsyncActivity“ (siehe Abbildung 4). Beachten Sie, dass in dieser Auflistung mehrere interne Hilfsmethoden oder Fehlerbehandlungen nicht enthalten sind, die aber im tatsächlichen Code vorhanden sind. Diese Details wurden hier aus Platzgründen weggelassen.

Abbildung 4 AsyncActivity

public abstract class AsyncActivity : Activity {
  private string queueName;

  protected AsyncActivity(string queueName) {
    this.queueName = queueName;
  }

  protected WorkflowQueue GetQueue(
      ActivityExecutionContext ctx) {
    var svc = ctx.GetService<WorkflowQueuingService>();
    if (!svc.Exists(queueName))
      return svc.CreateWorkflowQueue(queueName, false);

    return svc.GetWorkflowQueue(queueName);
  }

  protected void SubscribeToItemAvailable(
      ActivityExecutionContext ctx) {
    GetQueue(ctx).QueueItemAvailable += queueItemAvailable;
  }

  private void queueItemAvailable(
      object sender, QueueEventArgs e) {
    ActivityExecutionContext ctx = 
      (ActivityExecutionContext)sender;
    try { OnQueueItemAvailable(ctx); } 
    finally { ctx.CloseActivity(); }
  }

  protected abstract void OnQueueItemAvailable(
    ActivityExecutionContext ctx);
}

Sie sehen, dass in dieser Basisklasse mehrere der mühsamen und sich wiederholenden Schritte des Erstellens einer asynchronen Aktivität untergebracht wurden. Gehen wir diese Klasse einmal von oben bis unten durch. Beginnend mit dem Konstruktor wird eine Zeichenfolge für den Warteschlangennamen übergeben. Workflowwarteschlangen sind die Eingabepunkte für die Hostanwendung (die Webseiten), um Daten an Aktivitäten zu übergeben, während weiterhin eine lockere Kopplung besteht. Auf diese Warteschlangen wird mit Name und Workflowinstanz verwiesen. Deshalb benötigt jede asynchrone Aktivität ihren eigenen speziellen Warteschlangennamen.

Als Nächstes wird die GetQueue-Methode definiert. Wie Sie sehen, ist es nicht schwierig, auf Workflowwarteschlangen zuzugreifen und sie zu erstellen, allerdings ist es etwas mühsam. Ich habe diese Methode als Hilfsmethode für die Verwendung innerhalb dieser Klasse und der abgeleiteten Klassen erstellt.

Dann wird eine Methode namens „SubscribeToItemAvailable“ definiert. Diese Methode kapselt die Details für das Abonnieren des Ereignisses, das ausgelöst wird, wenn ein Element in der Workflowwarteschlange eintrifft. Dies repräsentiert fast immer den Abschluss einer langen Wartezeit, während der sich der Workflow im Leerlauf befand. Die Verwendung sieht also in etwa wie folgt aus:

  1. Beginnen des lang andauernden Vorgangs und Aufrufen von SubscribeToItemAvailable.
  2. Informieren der Workflowlaufzeit darüber, dass sich die Aktivität im Leerlauf befindet.
  3. Die Workflowinstanz wird durch den Persistenzdienst zur Datenbank serialisiert.
  4. Bei Abschluss des Vorgangs wird ein Element zur Workflowwarteschlange gesendet.
  5. Dies sorgt dafür, dass die Workflowinstanz aus der Datenbank wiederhergestellt wird.
  6. Die abstrakte Vorlagenmethode „OnQueueItemAvailable“ wird von der AsyncActivity-Basisklasse ausgeführt.
  7. Die Aktivität wird beendet.

Um diese AsyncActivity-Klasse in Aktion zu sehen, implementieren Sie nun die AssignJobActivity-Klasse. Die anderen zwei asynchronen Aktivitäten sind ganz ähnlich und im Codedownload enthalten.

In Abbildung 5 können Sie sehen, wie AssignJobActivity die Vorlage verwendet, die von der AsyncActivity-Basisklasse geboten wird. Setzen Sie Execute außer Kraft, um einleitende Arbeiten für den Beginn der lang andauernden Aktivität auszuführen, obwohl in diesem Fall im Grunde keine erforderlich sind. Dann abonnieren Sie das Ereignis für den Fall, dass mehr Daten verfügbar sind.

Abbildung 5 AssignJobActivity

public partial class AssignJobActivity : AsyncActivity {
  public const string QUEUE NAME = "AssignJobQueue";

  public AssignJobActivity()
    : base(QUEUE_NAME) 
  {
    InitializeComponent();
  }

  protected override ActivityExecutionStatus Execute(
      ActivityExecutionContext ctx) {
    // Runs before idle period:
    SubscribeToItemAvailable(ctx);
    return ActivityExecutionStatus.Executing;
  }

  protected override void OnQueueItemAvailable(
      ActivityExecutionContext ctx) {
    // Runs after idle period:
    Job job = (Job)GetQueue(ctx).Dequeue();

    // Assign job to employee, save in DB.
    Employee employee = Database.FindEmployee(this.WorkflowInstanceId);
    employee.Job = job.JobTitle;
    employee.Salary = job.Salary;
  }
}

Es gibt hier einen impliziten Vertrag, dass die Hostanwendung, die Webseite, ein neues Stellenobjekt (Job) an die Warteschlange der Aktivität sendet, wenn sie diese Informationen vom Manager erhalten hat. Dadurch wird der Aktivität signalisiert, dass sie fortfahren kann. Der Mitarbeiter wird nun in der Datenbank aktualisiert. Die nächste Aktivität im Workflow sendet dem voraussichtlichen Mitarbeiter eine E-Mail, die ihn darüber informiert, dass ihm diese Stelle angeboten wird.

Integration in ASP.NET

So funktioniert es innerhalb des Workflows. Aber wie wird der Workflow gestartet? Wie erhält die Webseite das Stellenangebot vom Manager? Wie wird die Stelle an die Aktivität übergeben?

Aber eines nach dem anderen: Sehen Sie sich zunächst an, wie der Workflow gestartet wird. Auf der Startseite für die Website gibt es einen Link „Apply Now“ (Jetzt anwenden). Wenn der Bewerber auf diesen Link klickt, werden parallel sowohl der Workflow als auch die Navigation durch die Benutzeroberfläche gestartet:

protected void LinkButtonJoin_Click(
    object sender, EventArgs e) {
  WorkflowInstance wfInst = 
    Global.WorkflowRuntime.CreateWorkflow(typeof(MainWorkflow));

  wfInst.Start();
  Response.Redirect(
    "GatherEmployeeData.aspx?id=" + wfInst.InstanceId);
}

Rufen Sie einfach CreateWorkflow aus der Workflowlaufzeit auf, und starten Sie die Workflowinstanz. Anschließend verfolgen Sie die Workflowinstanz, indem Sie die Instanz-ID als Abfrageparameter an alle folgenden Webseiten übergeben.

Wie werden Daten von der Webseite an den Workflow zurückgesendet? Sehen Sie sich die zugewiesene Stellenseite an (Abbildung 6), auf der ein Manager eine Stelle für einen Bewerber auswählt.

Abbildung 6 Zuweisen einer Stelle

public class AssignJobPage : System.Web.UI.Page {
  /* Some details omitted */
  void ButtonSubmit_Click(object sender, EventArgs e) {
    Guid id = QueryStringData.GetWorkflowId();
    WorkflowInstance wfInst = Global.WorkflowRuntime.GetWorkflow(id);

    Job job = new Job();
    job.JobTitle = DropDownListJob.SelectedValue;
    job.Salary = Convert.ToDouble(TextBoxSalary.Text);

    wfInst.EnqueueItem(AssignJobActivity.QUEUE_NAME, job, null, null);

    buttonSubmit.Enabled = false;
    LabelMessage.Text = "Email sent to new recruit.";
  }
}

Die Webseite zum Zuweisen einer Stelle ist im Wesentlichen nur ein einfaches Eingabeformular. Sie besitzt eine Dropdownliste verfügbarer Stellen und ein Textfeld für das vorgeschlagene Gehalt. Sie zeigt auch den aktuellen Bewerber an, obwohl dieser Code nicht in der Auflistung enthalten ist. Wenn der Manager dem Bewerber eine Position und ein Gehalt zuweist, klickt er auf die Schaltfläche „Submit“ (Senden) und führt den Code in Abbildung 6 aus.

Diese Seite verwendet die Workflowinstanz-ID als Abfragezeichenfolgenparameter, um die zugeordnete Workflowinstanz nachzuschlagen. Dann wird ein Stellenobjekt (Job) erstellt und mit den Werten des Formulars initialisiert. Abschließend senden Sie diese Informationen an die Aktivität zurück, indem Sie die Stelle in die Warteschlange dieser Aktivität einreihen. Dies ist der entscheidende Schritt, durch den der im Leerlauf befindliche Workflow erneut geladen und die weitere Ausführung ermöglicht wird. AssignJobActivity ordnet diese Stelle dem zuvor abgerufenen Mitarbeiter zu und speichert beides in einer Datenbank.

Diese letzten zwei Codeauflistungen zeigen, wie wichtig Workflowwarteschlangen für den Erfolg der asynchronen Aktivitäten und der Workflowkommunikation mit dem externen Host sind. Es muss auch beachtet werden, dass die Verwendung des Workflows hier keine Auswirkungen auf den Seitenfluss hat. Zwar könnten Sie auch WF verwenden, um den Seitenfluss zu steuern, aber das ist nicht der Schwerpunkt dieses Artikels.

In Abbildung 6 haben Sie gesehen, dass auf die Workflowlaufzeit über die globale Anwendungsklasse zugegriffen wurde, und zwar folgendermaßen:

WorkflowInstance wfInst = 
  Global.WorkflowRuntime.GetWorkflow(id);

Dies bringt mich zum letzten Punkt, dem Integrieren von Windows Workflow in die Webanwendung: Alle Workflows werden innerhalb der Workflowlaufzeit ausgeführt. Obwohl in Ihrer Anwendungsdomäne beliebig viele Workflowlaufzeiten vorliegen können, ist es meist sinnvoll, eine einzige Workflowlaufzeit zu verwenden. Deshalb und weil das WF-Laufzeitobjekt threadsicher ist, wurde sie zu einer öffentlichen statischen Eigenschaft der globalen Anwendungsklasse gemacht. Außerdem wird die Workflowlaufzeit im Anwendungsstartereignis gestartet und im Anwendungsbeendigungsereignis beendet. Abbildung 7 ist eine gekürzte Version der globalen Anwendungsklasse.

Abbildung 7 Starten der Workflowlaufzeit

public class Global : HttpApplication {
  public static WorkflowRuntime WorkflowRuntime { get; set; }

  protected void Application_Start(object sender, EventArgs e) {
    WorkflowRuntime = new WorkflowRuntime();
    InstallPersistenceService();
    WorkflowRuntime.StartRuntime();
    // ...
  }

  protected void Application_End(object sender, EventArgs e) {
    WorkflowRuntime.StopRuntime();
    WorkflowRuntime.Dispose();
  }

  void InstallPersistenceService() {
    // Code from listing 4.
  }
}

Im Anwendungsstartereignis erstellen Sie die Laufzeit, installieren den Persistenzdienst und starten die Laufzeit. Im Anwendungsbeendigungsereignis beenden Sie die Laufzeit. Dies ist ein wichtiger Schritt. Wenn ausgeführte Workflows vorhanden sind, erfolgt eine Blockierung, bis diese Workflows entladen sind. Nach dem Beenden der Laufzeit rufen Sie Dispose auf. Der Aufruf an StopRuntime und dann an Dispose mag redundant scheinen, ist es aber nicht. Sie müssen beide Methoden in dieser Reihenfolge aufrufen.

Weitere Erwägungen

Im Folgenden werden Ihnen im Frage-Antwort-Stil weitere Erwägungen vorgestellt, die bisher nicht direkt erwähnt wurden. Weshalb wurde nicht ManualWorkflowSchedulerService verwendet? Wenn über das Integrieren von WF in ASP.NET gesprochen wird, wird oft betont, dass der Standardplaner für Workflows (der den Threadpool verwendet) durch einen Dienst namens „ManualWorkflowSchedulerService“ ersetzt werden sollte. Der Grund ist, dass er für die vorliegenden lang andauernden Aktivitäten nicht erforderlich oder nicht wirklich geeignet ist. Der manuelle Planer ist gut geeignet, wenn ein einzelner Workflow innerhalb einer bestimmten Anforderung bis zum Abschluss ausgeführt werden soll. Er ist weniger sinnvoll, wenn der Workflow über die Prozessgültigkeitsdauer, wenn nicht gar über Anforderungen hinweg ausgeführt wird.

Gibt es eine Möglichkeit, den aktuellen Fortschritt einer bestimmten Workflowinstanz nachzuverfolgen? Ja, es gibt in WF einen umfassenden Nachverfolgungsdienst, der auf ähnliche Weise wie der SQL-Persistenzdienst verwendet wird. Weitere Informationen finden Sie im Foundations-Artikel vom März 2007: Überwachungsdienste in Windows Workflow Foundation von Matt Milner.

Zusammenfassung

Die in diesem Artikel diskutierten Verfahren können in einigen wenigen Schritten zusammengefasst werden. Zu Beginn wurde beschrieben, warum die ASP.NET-Arbeitsprozesse und das Prozessmodell im Allgemeinen für sehr lang andauernde Vorgänge nicht geeignet sind. Um dieser Einschränkung zu entgehen, wurden zwei Features von WF genutzt, die kombiniert wurden, um Prozessunabhängigkeit zu erreichen: asynchrone Aktivitäten und Workflowpersistenz.

Da das Erstellen asynchroner Aktivitäten etwas schwierig sein kann, wurden die Details in die AsyncActivity-Basisklasse gekapselt, die in diesem Artikel eingeführt wurde. Dann wurde der lang andauernde Vorgang als sequenzieller Workflow ausgedrückt, der aus asynchronen Aktivitäten besteht, sodass dies in eine Webanwendung integriert werden kann und damit Prozessunabhängigkeit entsteht.

Abschließend wurde vorgeführt, dass die Integration des Workflows in ASP.NET aus zwei Grundelementen besteht: dem Kommunizieren mit den Aktivitäten über eine Workflowwarteschlange und dem Hosten der Laufzeit in der globalen Anwendungsklasse.

Da Sie nun gesehen haben, wie WF in ASP.NET integriert wird, um lang andauernde Vorgänge zu unterstützen, besitzen Sie ein weiteres leistungsfähiges Tool für das Erstellen von Lösungen, die auf .NET Framework basieren.

Michael Kennedy arbeitet als Ausbilder für DevelopMentor, wo er sich auf die zentralen .NET-Technologien sowie auf agile und testgesteuerte Entwicklungsmethodiken spezialisiert hat. Sie erreichen Michael Kennedy über seine Website und seinen Blog unter michaelckennedy.net.