Custom Code Activity für SharePoint 2013 Workflow

Der Workflow Manager bringt schon einige Activities mit. Jedoch ist es manchmal wünschenswert eigene Activities mit C# Code zu erstellen und nicht alles nur im Designer “zusammen zu klicken” Im großen Überblick sind die notwendigen Steps einfach. 1) Die Activity ist eine von “CodeActivity” abgeleitete Klassen. Die Methode “Execute” wird vom Workflow aufgerufen. 2) das Assembly mit der Klasse muss auf der Maschine mit dem Workflowmanager deployed werden. So schnell der Teil 1 erledigt ist, so mühsam ist Teil 2, weil hierfür kein Template bereitgestellt wird. Zunächst legen wir ein neues Visual Studio Projekt an. Als Vorlage wird “Activity Library” ausgewählt. Dieses Template ist im Bereich “Workflow” zu finden. Die automatisch angelegte “Activity1.xaml” kann gelöscht werden. Danach wird mit “Add-Item” eine neue Code-Activity in das Projekt eingefügt. Im Beispiel nenne ich diese “SimpleActivity.cs” Mit dieser Vorlage wird die Klasse “SimlpeActivity” angelegt und die Methode Execute überschrieben. 1 public sealed class SimpleActivity : CodeActivity 2 { 3 4 public InArgument<string> Text { get; set; } 5 public OutArgument<string> Result { get; set; } 6 7 protected override void Execute(CodeActivityContext context) 8 { 9 10 string text = context.GetValue(this.Text); 11 12 Result.Set(context, text.ToUpper()); 13 } 14 } In den Zeile 4 und 5 werden ein Input und ein Output Parameter für diese Activity angelegt. Innerhalb der Excecute Methode kann über Context.GetValue ein Inputparameter abgerufen werden. Mit Result.Set kann der Output-Parameter gesetzt werden. In diesem simplen Demo wird der Inputwert in Großbuchstaben in den Output geschrieben. In der Realität wird wohl die Execute-Methode etwas komplexer werden. Teil 2 – Das Deployment Das Assembly muss mit einem “strong name” versehen sein, daher muss das Assembly signiert werden. Dafür wird in den Properties des Projektes, die Checkbox “Sign the assembly” gesetzt und somit ein neues Keyfile im Projekt angelegt. Als nächstes muss eine Datei mit dem Namen “allowedtypes.xml” erstellt werden. In dieser Datei werden die Typen beschrieben, welche vom Workflowmanager geladen werden. Der Dateiinhalt sieht wie folgt aus: 1 <AllowedTypes> 2 <Assembly Name="SimpleActivityDemo"> 3 <Namespace Name="SimpleActivityDemo"> 4 <Type>SimpleActivity</Type> 5 </Namespace> 6 </Assembly> 7 </AllowedTypes> Nachdem das Projekt kompiliert wurde müssen die erstellte DLL und die Datei “AllowedTypes.xml” in ein Verzeichnis des Workflowmanagers kopiert werden. Das Zielverzeichnis ist: “C:\Program Files\Workflow Manager\1.0\Workflow\Artifacts” Möchte man die Activity nun in einem Visual Studio Workflow verwenden, muss die Activity in die Toolbox aufgenommen werden. Im Context-Menü der Toolbox kann mit dem Menü Punkt “Choose Items…” die DLL ausgewählt werden und damit die enthaltene Activity in die Toolbox aufgenommen werden. Danach kann die Custom-Activity wie jede andere in Workflow Projekte aufgenommen werden. Die beiden Parameter “Text” und “Result” sind wie gewohnt über das Eigenschaftenfenster im Visual Studio zu setzen. „Sie möchten mehr zur  Workflow Programmierung mit SharePoint erfahren? Werfen Sie doch mal einen Blick auf unser dazu passendes SharePoint Training.“

SharePoint 2013 Workflow–Lookup Activities

In SharePoint 2013 Workflows werden einige Activities angeboten, die die ListenId benötigen. Im jeweiligen Property Feld kann eine Liste ausgewählt werden. Der tatsächliche Eintrag im Prperty wird zu folgendem Sourcecode: System.Guid.Parse("{$ListId:Lists/urlaubstage;}") Der Platzhalter $ListId…. wird durch die tatsächliche ListenID ersetzt. So weit so gut. Nur leider funktioniert dies nur, wenn der Workflow als Sandboxed Solution ausgeliefert wird. In einer Farm Solution wird dieser Platzhalter nicht ersetzt. Wie zu erwarten ist, kann aus dem Text “{$ListId…..” kein Guid geparsed werden. Die entsprechendene Fehlermeldung sieht dann so aus: RequestorId: 768cedc5-12d4-2419-33f9-ddc55553fe3c. Details: System.FormatException: Expected hex 0x in '{0}'. at System.Guid.GuidResult.SetFailure(ParseFailureKind failure, String failureMessageID, Object failureMessageFormatArgument) at System.Guid.TryParseGuidWithHexPrefix(String guidString, GuidResult& result) at System.Guid.TryParseGuid(String g, GuidStyles flags, GuidResult& result) at System.Guid.Parse(String input) at System.Activities.CodeActivity`1.InternalExecute(ActivityInstance instance, ActivityExecutor executor, BookmarkManager bookmarkManager) at … Dies ist ein bekannter Bug und wird hoffentlich demnächst behoben. Da “Demnächst” wohl nicht ausreichend schnell ist, hier ein Workaround: Jeder Workflow wird mittels Elements.xml –Datei ausgeliefert. In dieser können wir weitere Parameter ablegen. Diese Parameter können im Workflow mittels “GetConfigurationValue” ausgelesen werden. 1) Anpassung Elements.xml In die Elements-Datei ist eine weitere Zeile einzufügen um ein Property anzulegen. 1: <Module Name="WFEins" Url="wfsvc/18bfe43f6db7420fa3b0b5999963262b"> 2: <File Url="Workflow.xaml" Type="GhostableInLibrary" Path="WFEins\Workflow.xaml" DoGUIDFixUp="TRUE"> 3: <Property Name="ContentType" Value="WorkflowServiceDefinition" /> 4: <Property Name="isReusable" Value="true" /> 5: <Property Name="RequiresInitiationForm" Value="False" /> 6: <Property Name="RequiresAssociationForm" Value="False" /> 7: <Property Name="WSPublishState" Value="3" /> 8: <Property Name="WSDisplayName" Value="WFEins" /> 9: <Property Name="WSDescription" Value="My 'WFEins' Workflow" /> 10: <!-- If you change the name or Url of your custom initiation or association form, 11: remember to update the corresponding property value (InitiationUrl or AssociationUrl) to match the new web relative url. 12: --> 13: <Property Name="RestrictToType" Value="List" /> 14: <Property Name="RestrictToScope" Value="{$ListId:Lists/antrag;}" /> 15: </File> 16: <File Url="WorkflowStartAssociation" Path="WFEins\WorkflowStartAssociation" Type="GhostableInLibrary"> 17: <Property Name="WSDisplayName" Value="WFEins - Workflow Start" /> 18: <Property Name="ContentType" Value="WorkflowServiceSubscription" /> 19: <Property Name="WSPublishState" Value="3" /> 20: <Property Name="WSEventType" Value="WorkflowStart" /> 21: <Property Name="WSEnabled" Value="true" /> 22: <Property Name="WSGUID" Value="a0f42f40-b453-4120-b205-981009dbc9d8" /> 23: <Property Name="WSEventSourceGUID" Value="{$ListId:Lists/antrag;}" /> 24: <Property Name="Microsoft.SharePoint.ActivationProperties.ListId" Value="{$ListId:Lists/antrag;}" /> 25: <Property Name="HistoryListId" Value="{$ListId:Lists/WorkflowHistoryList;}" /> 26: <Property Name="TaskListId" Value="{$ListId:Lists/WorkflowTaskList;}" /> 27: <Property Name="UrlaubsListeId" Value="{$ListId:Lists/urlaubstage;}" /> 28: </File> 29: </Module> .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } In diesem Beispiel ist es die Zeile 27. An dieser Stelle funktioniert das Ersetzen der ListId. Somit steht in der Konfiguration ein Property mit dem Namen “UrlaubsListeId” zur Verfügung. 2) Auslesen des Properties im Workflow im Bereich “Runtime” steht die Activity “GetConfigurationValue” zur Verfügung:   Diese Activity kann die Propertywerte der Elements-Datei auslesen und in eine Variable speichern. Die Konfiguration der Activity sieht so aus: Name ist der in der Elementsdatei angegebene Name der Eigenschaft. Und Result ist eine String-Variable in die der  Wert geschrieben werden soll. (in meinen Beispiel “urlaubsTageListeId”). Ab nun kann im Workflow der Ausdruck  Guid.Parse(urlaubsTageListeId) verwendet werden um den Guid der Liste zu erhalten.