Archiv für 7. Oktober 2008

Aktivierung von XOML Workflow Services

Dienstag, 7. Oktober 2008

Vor der Einführung der Workflow Services mit .NET 3.5 war das Leben eines .NET WF Entwicklers etwas einfacher. Man erzeugt einfach irgendwann eine (oder entgegen der weit verbreiteten Annahme auch mehrere) WorkflowRuntime, welche dann verschiedene Overloads der CreateWorkflow() Methode zur Verfügung stellten und so die Workflow Instanzen erzeugen können.

XOML Workflow Aktivierung mit der WorkflowRuntime

Je nachdem, ob die Workflow Definitionen in einem Assembly oder in XOML vorhanden sind, wählt man den entsprechenden Overload aus.

Für Workflow Definitionen, welche sich in einem Assembly befinden, stehen drei Methoden Overloads zur Verfügung, welche einen Typ als Parameter erwarten:

public WorkflowInstance CreateWorkflow(Type workflowType)

public WorkflowInstance CreateWorkflow(Type workflowType,
                                       Dictionary<string, Object> namedArgumentValues)

public WorkflowInstance CreateWorkflow(Type workflowType,
                                       Dictionary<string, Object> namedArgumentValues,
                                       Guid instanceId)

Wenn die Workflow Definitionen in XOML definiert sind, wählt man einen der anderen drei Overloads aus, welche einen XmlReader als Parameter erwarten. Der zweite XmlReader Parameter nimmt den Inhalt eines .rules files entgegen:

public WorkflowInstance CreateWorkflow(XmlReader workflowDefinitionReader)

public WorkflowInstance CreateWorkflow(XmlReader workflowDefinitionReader,
                                                 XmlReader rulesReader,
                                                 Dictionary<string, Object> namedArgumentValues)

public WorkflowInstance CreateWorkflow(XmlReader workflowDefinitionReader,
                                                 XmlReader rulesReader,
                                                 Dictionary<string, Object> namedArgumentValues,
                                                 Guid instanceId)

XOML basierte Workflows haben einige Vorteile, aber auch konzeptbedingt ein paar Einschränkungen:

  • Vorteil: Ein Workflow kann einfacher geändert werden. Dazu muss lediglich die XOML Datei oder wenn die Definition in einer Datenbank gehalten wird, der entsprechende Eintrag angepasst werden.
  • Vorteil: Die Regeln eines Workflows werden abenfalls in einer Datei (.rules) oder einem Record gehalten und können unabhängig vom Workflow aktualisiert werden. In Code basierten Workflows erfordert dies ein erneutes Kompilieren.
  • Vorteil: Die Workflow Definitionen müssen nicht kompiliert werden. Dies erleichtert das Management und die Verwaltung der Workflow Definitionen.
  • Nachteil: In den Workflow Definitionen können keine Properties definiert und keine Event Handler genutzt werden. Dies ist nur mit Code basierten Workflows möglich.

Nicht nur die XOML basierten Workflows enthalten meistens auch eigene Aktivitäten, welche in einem anderen Assembly bereitgestellt sind. Während der Entwicklung der Workflows wird lediglich ein Verweis auf diese Assemblies gesetzt, danach können die dort definierten Aktivitäten im Designer genutzt werden.
Wenn dieses Assembly nicht schon in der AppDomain der Workflow Runtime geladen ist, muss dies der WorkflowRuntime mitgeteilt werden. Dies erfolgt über einen TypeProvider, welcher der WorkflowRuntime als Service hinzugefügt wird:

WorkflowRuntime workflowRuntime = new WorkflowRuntime();

// Create a type provider for types referenced in the XOML
// which are not referenced by this project
TypeProvider typeProvider = new TypeProvider(workflowRuntime);

// Load referenced assemblies into the type provider
Assembly assembly = Assembly.Load("assemblyname");
typeProvider.AddAssembly(assembly);

// OR
// typeProvider.AddAssemblyReference(@"path to the assembly")

workflowRuntime.AddService(typeProvider);

Wenn dann eine Workflow Instanz mit einem der CreateWorkflow() Overloads erzeugt wird, wird die XOML basierte Workflow Definition validiert. Sollte dabei ein Problem auftreten, wird eine WorkflowValidationFailedException geworfen. Dies ist in der Dokumentation nur unter den Bemerkungen zu finden, die Exception wird nicht in der Liste der anderen Ausnahmen aufgeführt.
Die WorkflowValidationException besitzt ein Property Errors (Collection von ValidationError), welches genau Auskunft über die Probleme bei der Validierung der Workflow Definition gibt:

XmlReader workflowReader = XmlReader.Create("Workflow1.xoml");
WorkflowInstance workflowInstance = null;
try
{
    workflowInstance = workflowRuntime.CreateWorkflow(workflowReader);
    workflowInstance.Start();
}
catch (WorkflowValidationFailedException ex)
{
    foreach (ValidationError error in ex.Errors)
    {
        if (string.IsNullOrEmpty(error.PropertyName))
            Console.WriteLine("Validation error: {0}-{1}", error.ErrorNumber, error.ErrorText);
        else
            Console.WriteLine("Validation error for property \"{2}\": {0}-{1}", error.ErrorNumber, error.ErrorText, error.PropertyName);
    }
}

XOML Workflow Aktivierung mit Workflow Services

Wie funktioniert dies nun, wenn man Workflow Services einsetzt, und ebenfalls XOML basierte Workflows aktivieren möchte? Hier stellt sich auf den ersten Blick eine Art “Huhn-Ei Problem“. Der TypeProvider soll der WorkflowRuntime als Service hinzugefügt werden, diese existiert aber erst nach dem Erstellen des WorkflowServiceHosts. Man hat auch nicht direkten Zugriff auf die WorkflowRuntime, so dass nicht ohne weiteres ein Service hinzugefügt werden könnte.
Der Weg führt hier über die Verwendung einer eigenen WorkflowServiceHostFactory, welche einen eigenen WorkflowServiceHost erzeugt. Der Konstruktor dieses WorkflowServiceHosts ist sieben Mal überladen (plus einmal protected), dabei gibt es einen Konstruktor, welcher nebst dem Stream mit der Workflow Definition einen TypeProvider aufnimmt:

public WorkflowServiceHost(Stream workflowDefinition,
                           Stream ruleDefinition,
                           ITypeProvider typeProvider,
                           params Uri[] baseAddress)

Dieser TypeProvider kann nun wie folgt dem Konstruktor übergeben werden:

TypeProvider typeProvider = new TypeProvider(null);

// Load referenced assemblies into the type provider
Assembly assembly = Assembly.Load("assemblyname");
typeProvider.AddAssembly(assembly);

// OR
// typeProvider.AddAssemblyReference(@"path to the assembly")

Stream stream = new MemoryStream(ASCIIEncoding.Default.GetBytes("<MyXOML...>"));;
Uri uri = new Uri("myuri");

WorkflowServiceHost wfsh = new WorkflowServiceHost(stream, null, typeProvider, uri);

Der Konstruktor wirft nun bei Validierungsproblemen entgegen der Dokumentation ebenfalls eine WorkflowValidationFailedException, was so nicht in der Dokumentation steht, so dass man die Fehler entsprechend behandeln kann.