Créer une activité personnelle pour les workflows Sharepoint Designer

Stéphane Eyskens

Le but de cet article est de montrer comment créer une activité personnelle utilisable dans l'assistant de workflow de Sharepoint Designer. Ceci montre d'une part que Sharepoint Designer ne se restreint pas aux activités standard et d'autre part ouvre une porte vers des perspectives non négligeables.

- Titre : Créer une activité personnelle pour les workflows Sharepoint Designer

- Auteur : Stéphane Eyskens

- Parution : 2 septembre 2007

- Licence : Ce document est issu de http://www.developpez.com et reste la propriété exclusive de son auteur. La copie, modification et/ou distribution par quelque moyen que ce soit est soumise à l'obtention préalable de l'autorisation de l'auteur.

Sommaire

- I. Introduction

- I-A. A quoi sert une activité personnelle?

- II. Création de notre service web

- II-A. Note sur la sécurité

- III. Création de notre activité personnelle

- III-A. Type de projet

- III-B. Référencement du service web

- III-C. Code de notre activité

- III-C-1. Création d'une propriété de type DependencyProperty

- III-C-2. Implémentation de la méthode ActivityExecutionStatus

- III-D. Déploiement de l'activité dans la GAC

- III-E. Mise à jour du web.config de Sharepoint

- III-F. Renseignement de notre activité dans le WSS.ACTIONS ou dans un fichier .ACTIONS propre

- III-F-1. via le fichier WSS.ACTIONS

- III-F-2. via un fichier spécifique à notre action

- IV. Création de notre workflow dans Sharepoint Designer

- V. Plus loin avec les activités personnelles

- V-A. Description des propriétés __Context, __ListId et __ListItem

- V-B. Code permettant de transformer le champ cible choisi par l'utilisateur

- V-C. Le code complet du fichier .ACTIONS

- V-D. Comment tester notre activité opérant une transformation de chaîne?

- VI. Téléchargement

I - Introduction

Dans cet article nous allons dans un premier temps créer une simple activité personnelle qui interroge un service web qui lui retournera une réponse. Nous utiliserons ensuite cette activité dans un workflow que l'on créera avec Sharepoint Designer pour mettre à jour la colonne Title de la liste d'annonces de notre site lorsqu'une annonce est ajoutée. Dans un deuxième temps, nous aborderons des concepts plus complexes en travaillant directement sur des éléments de listes via les propriétés __Context, __ListId et __ListItem

I-A - A quoi sert une activité personnelle?

Une activité personnelle sert à faire tout ce que les activités standard ne permettent pas de faire. On peut par exemple imaginer devoir interroger Active Directory ou un service web ou décider de créer une entrée dans le gestionnaire d'évènements etc... au cours d'un workflow. Toutes ces actions ne sont pas réalisables avec les activités standard. Il faut donc passer par une activité personnelle. Dès qu'une limitation quelconque est rencontrée, il est possible soit de créer entièrement un workflow avec Visual Studio mais le coût de développement est élevé, soit de créer une activité personnelle le cas échéant, ce qui réduit le coût de développement et est est plus efficace en terme de réutilisation. De surcroît, cette activité ne se crée qu'une fois et peut être réutilisée par tous les workflows.

II - Création de notre service web

Le service web que nous créons pour cet article n'est qu'un prétexte servant à montrer l'interaction possible entre un workflow créé avec Sharepoint Designer et les services web. Il ne fait donc rien de très enthousiasmant. Je ne vais pas rentrer dans le détail concernant la création et l'utilisation d'un service web car ce n'est pas le but de cet article. D'autres ressources traitant de ce sujet sont disponibles sur ce site. Je vais donc simplement exposer le code afin de vous montrer ce qui sera retourné par le service.

using System;
using System.Web;
using System.Web.Services;
using System.Web.Services.Protocols;

[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class Service : System.Web.Services.WebService
{
public Service () {

//Uncomment the following line if using designed components
//InitializeComponent();
}

[WebMethod]
public string GetAnswer() {
return "coucou";
}

}

Comme vous pouvez le constater, ce service n'expose qu'une web méthod qui retourne la chaîne de caractères coucou témoignant de la bonne exécution de cette dernière. C'est donc cette chaîne de caractères qui servira à mettre à jour la colonne Title de notre liste d'annonces.

II-A - Note sur la sécurité

Dans cet exemple, mon service est accessible de manière anonyme, il sera donc interrogeable directement par un workflow s'exécutant au sein de Sharepoint. Il est plus que probable qu'en situation réelle, vous ayez à utiliser la sécurité intégrée et à transmettre les informations de l' utilisateur courant lors de l'interrogation du service et à éventuellement faire une impersonation. Ces concepts ne sont pas abordés dans cet article

III - Création de notre activité personnelle

III-A - Type de projet

Vous pouvez utiliser le type de projet standard dédié à la création d'une activité personnelle. Choisissez le modèle illustré par l'image ci-dessous

Note: vous devez au moins disposer de la version 3.0 du framework dotnet.

III-B - Référencement du service web

Lorsque vous avez créé le projet, vous pouvez directement référencer le service en ajoutant une référence web et en renseignant l'url de votre service comme illustré ci-dessous.

III-C - Code de notre activité

III-C-1 - Création d'une propriété de type DependencyProperty

Les propriétés vont nous permettre d'associer les valeurs en entrée et en sortie de notre workflow aux propriétés exposées dans l'assistant workflow de Sharepoint Designer. Pour créer une de ces propriétés dans le code, il est aisé d'utiliser une snippet. Pour cela, dans votre fichier source, cliquez sur le bouton droit, ensuite Insert Snippet et ensuite cliquez sur Workflow.

Ceci crée une propriété que vous pourrez renommer selon vos besoins.

Dans notre exemple, nous avons créé la propriété suivante

public static DependencyProperty WebServiceReturnValueProperty =
DependencyProperty.Register("WebServiceReturnValue", typeof(string), typeof(GetWsInfo));
[Description("Return value of the demo web service")]
[Category("User")]
[Browsable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public string WebServiceReturnValue
{
get
{
return (string)GetValue(WebServiceReturnValueProperty);
}
set
{
SetValue(WebServiceReturnValueProperty, value);
}
}

- Register permet d'enregistrer la propriété pour qu'elle soit connue par le workflow

- le premier typeof prend en paramètre le type de notre propriété (string, int, workflowcontext etc...)

- le deuxième typeof doit prendre en paramètre le nom de la classe principale

III-C-2 - Implémentation de la méthode ActivityExecutionStatus

Pour que votre activité s'exécute, vous devez réécrire la méthode ActivityExecutionStatus de la classe de base.

try
{
//momix est le nom de ma référence pointant sur le service web
momix.Service DemoService = new GetWebServiceAnswer.momix.Service();
//appel de la méthode du service et affectation de la valeur à la propriété
WebServiceReturnValue = DemoService.GetAnswer();
DemoService.Dispose();
return ActivityExecutionStatus.Closed;
}
catch (Exception)//idéalement logger le problème via une LogActivity ou autre.
{
return ActivityExecutionStatus.Faulting;
}

III-D - Déploiement de l'activité dans la GAC

Notre activité étant une DLL, vous pouvez suivre les instructions stipulées dans ce tuto section Signature, GAC et construction d'un fichier bat car la marche à suivre est exactement la même.

III-E - Mise à jour du web.config de Sharepoint

Habituellement, lorsque l'on développe un contrôle personnel ou un webpart pour Sharepoint, il faut le référencer en tant que SafeControl dans le web.config. Cette fois, c'est une autre section qu'il faut modifier.

Editez le web.config correspondant à votre application Sharepoint, localisez la section <System.Workflow.ComponentModel.WorkflowCompiler> et ajoutez-y ceci

<authorizedType Assembly="GetWebServiceAnswer, Version=1.0.0.0, Culture=neutral, PublicKeyToken=8986eea5ac14faeb"
Namespace="GetWebServiceAnswer" TypeName="*" Authorized="True" />

en remplaçant bien sûr la signature par celle de votre assembly.

III-F - Renseignement de notre activité dans le WSS.ACTIONS ou dans un fichier .ACTIONS propre

Afin que Sharepoint Designer liste notre activité dans la liste d'actions de l'assistant workflow, nous devons lui renseigner celle-ci dans un fichier de configuration. Il est possible d'utiliser deux techniques différentes. On peut enregistrer l'action dans le fichier wss.ACTIONS ou dans un fichier séparé. La deuxième option semble préférable dans le mesure où l'on ne risque pas d'induire une déstabilisation quelconque en ne modifiant pas le fichier standard.

III-F-1 - via le fichier WSS.ACTIONS

Le fichier WSS.ACTIONS se trouve dans le répertoire 12\TEMPLATE\1033\Workflow de votre installation Sharepoint. Pour ajouter une action il faut modifier la section Actions et ajouter une action spécifique comme ceci

<Action Name="Get web service Info"
ClassName="GetWebServiceAnswer.GetWsInfo"
Assembly="GetWebServiceAnswer, Version=1.0.0.0, Culture=neutral, PublicKeyToken=8986eea5ac14faeb"
AppliesTo="all">
<RuleDesigner Sentence="Get information from web service (Output to %1)">
<FieldBind Field="WebServiceReturnValue" Text="WebServiceReturnValue" DesignerType="ParameterNames" Id="1"/>
</RuleDesigner>
<Parameters>
<Parameter Name="WebServiceReturnValue" Type="System.String, mscorlib" Direction="Out" />
</Parameters>
</Action>

Dans notre exemple, nous déclarons une règle dans laquelle nous lions le champ de sortie au paramètre de sortie WebServiceReturnValue. C'est celui-là même qui stockera la réponse du service web. Le mot clé ParameterNames spécifie au designer que ce champ doit être considéré comme une variable.

III-F-2 - via un fichier spécifique à notre action

La méthode est la même que pour le WSS.ACTIONS mais l'on spécifie notre XML dans un fichier à part.

<?xml version="1.0" encoding="utf-8" ?>
<WorkflowInfo>
<Actions Sequential="then" Parallel="and">
<Action Name="Get web service Info"
ClassName="GetWebServiceAnswer.GetWsInfo"
Assembly="GetWebServiceAnswer, Version=1.0.0.0, Culture=neutral, PublicKeyToken=8986eea5ac14faeb"
AppliesTo="all">
<RuleDesigner Sentence="Get information from web service (Output to %1)">
<FieldBind Field="WebServiceReturnValue" Text="WebServiceReturnValue" DesignerType="ParameterNames" Id="1"/>
</RuleDesigner>
<Parameters>
<Parameter Name="WebServiceReturnValue" Type="System.String, mscorlib" Direction="Out" />
</Parameters>
</Action>
</Actions>
</WorkflowInfo>

La seule différence par rapport à WSS.ACTIONS est que l'on déclare la section Actions puisqu'elle n'existe pas déjà comme c'est le cas dans WSS.ACTIONS

Que vous ayez choisi l'une ou l'autre méthode, vous devrez redémarrer IIS et rafraîchir Sharepoint Designer pour pouvoir visualiser l'activité dans la liste d'actions.

IV - Création de notre workflow dans Sharepoint Designer

La première étape consiste à créer le workflow, pour cela, ouvrez le site cible dans Sharepoint Designer et cliquez sur, File => New => Workflow

Donnez un nom à votre workflow et faites en sorte qu'il s'exécute lorsqu'une nouvelle annonce est créée.

Choisissez notre activité personnelle dans la liste d'actions.

Cliquez sur l'activité Build Dynamic String pour récupérer la valeur actuelle de la colonne Title et la concaténer avec la variable retournée par notre action "Get web service info" et envoyez le tout dans une variable nommée New Title par exemple.

Ensuite, choisissez l'action Update List Item qui va nous permettre de mettre à jour la colonne Title avec la valeur stockée dans New Title

Cliquez ensuite sur Finish pour terminer le workflow. Vous pouvez ensuite créer une annonce dans votre liste d'annonce et le titre devrait être modifié et contenir le titre original plus la réponse du service web.

V - Plus loin avec les activités personnelles

Dans cette section, nous allons explorer plus avant grâce à un exemple simple les possibilités d'interaction entre une activité personnelle et l'assistant de création de workflow de Sharepoint Designer. Par ailleurs, nous allons également voir comment récupérer/transformer les données de la liste sur laquelle est exécuté notre workflow.

L'exemple que nous allons développer est une activité personnelle qui permettra à l'utilisateur de créer un workflow dans Sharepoint Designer qui lui permettra de choisir une colonne qui devra être transformée soit en minuscules soit en majuscules. Cet exemple simpliste permet de se familiariser avec certaines propriétés importantes.

V-A - Description des propriétés __Context, __ListId et __ListItem

- __Context Cette propriété est une instance de WorkflowContext et permet de récupérer le contexte dans lequel le workflow évolue. On peut via cette propriété récupérer le SPSite et le SPWeb

- __ListId Cette propriété représente le GUID de la liste à laquelle est attaché le workflow

- __ListItem Cette propriété représente l'ID de l'élément de la liste auquel est attaché le workflow

Vous l'aurez compris, c'est grâce à ces trois propriétés que l'on va pouvoir travailler sur l'élément de liste sur lequel est basé le workflow. Voici comment elles sont déclarées dans le code.

public static DependencyProperty __ContextProperty =
System.Workflow.ComponentModel.DependencyProperty.Register("__Context", typeof(WorkflowContext), typeof(StringActivity));

[Description("Workflow Context")]
[Category("String Transformation")]
[Browsable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
[ValidationOption(ValidationOption.Required)]
public WorkflowContext __Context
{
get
{
return ((WorkflowContext)(base.GetValue(StringActivity.__ContextProperty)));
}
set
{
base.SetValue(StringActivity.__ContextProperty, value);
}
}

public static DependencyProperty __ListItemProperty =
System.Workflow.ComponentModel.DependencyProperty.Register("__ListItem", typeof(int), typeof(StringActivity));

[Description("ListItem")]
[Category("String Transformation")]
[Browsable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
[ValidationOption(ValidationOption.Required)]
public int __ListItem
{
get
{
return ((int)(base.GetValue(StringActivity.__ListItemProperty)));
}
set
{
base.SetValue(StringActivity.__ListItemProperty, value);
}
}

public static DependencyProperty __ListIdProperty =
System.Workflow.ComponentModel.DependencyProperty.Register("__ListId", typeof(string), typeof(StringActivity));

[Description("Id of the list")]//ici on peut spécifier une description
[Category("String Transformation")]//ici on peut spécifier une catégorie
[Browsable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
[ValidationOption(ValidationOption.Required)]
public string __ListId
{
get
{
return ((string)(base.GetValue(StringActivity.__ListIdProperty)));
}
set
{
base.SetValue(StringActivity.__ListIdProperty, value);
}
}

Ces propriétés ressemblent à n'importe quelles autres propriétés dotnet. Elles sont simplement préfixée d'attributs qui seront séralisés en XML et interprétés par le designer. Voici comment elles sont déclarées dans le fichier .ACTIONS

<Parameter Name="__Context" Type="Microsoft.SharePoint.WorkflowActions.WorkflowContext, Microsoft.SharePoint.WorkflowActions" />
<Parameter Name="__ListId" Type="System.String, mscorlib" Direction="In" />
<Parameter Name="__ListItem" Type="System.Int32, mscorlib" Direction="In" />

Dans notre exemple, nous avons besoin de connaître le nom de la colonne de liste qui devra subir la transformation en majuscules ou en minuscules. Voici la propriété correspondant à ce nom. Cette propriété sera affichée sous cette forme par l'assistant graphique de Sharepoint Designer

et voici le code Csharp correspondant

public static DependencyProperty FieldProperty =
System.Workflow.ComponentModel.DependencyProperty.Register("Field", typeof(string), typeof(StringActivity));

[Description("Column to be transformed")]
[Category("String Transformation")]
[Browsable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
[ValidationOption(ValidationOption.Required)]
public string Field
{
get
{
return ((string)(base.GetValue(StringActivity.FieldProperty)));
}
set
{
base.SetValue(StringActivity.FieldProperty, value);
}
}

Voici à présent sa déclaration dans le fichier .ACTIONS

<FieldBind Field="Field" Text="Field" Id="1" DesignerType="writablefieldNames"/>

writablefieldNames permet de ne liste que les champs qui ne sont pas en lecture seule de la liste sur laquelle s'exécute le workflow. Pour la liste complète, vous pouvez utiliser fieldNames

Voici enfin la dernière propriété, elle correspond au type d'opération que l'on veut appliquer, c'est à dire à une transformation en minuscules ou en majuscules. Elle est représentée sous forme de liste dans Sharepoint Designer comme suit:

et voici son code Csharp

public static DependencyProperty FunctionProperty =
System.Workflow.ComponentModel.DependencyProperty.Register("Function", typeof(string), typeof(StringActivity));

[Description("Function to apply")]
[Category("String Transformation")]
[Browsable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
[ValidationOption(ValidationOption.Required)]
public string Function
{
get
{
return ((string)(base.GetValue(StringActivity.FunctionProperty)));
}
set
{
base.SetValue(StringActivity.FunctionProperty, value);
}
}

Voici à présent sa déclaration dans le fichier .ACTIONS

<FieldBind Field="Function" Text="Function to apply" Id="2" DesignerType="Dropdown">
<Option Name="Uppercase" Value="Uppercase"/>
<Option Name="Lowercase" Value="Lowercase"/>
</FieldBind>

Dropdown permet de spécifier que la propriété doit être rendue sous forme de zone de liste. Les différentes options sont ensuite listées une à une.

V-B - Code permettant de transformer le champ cible choisi par l'utilisateur

Lorsque le créateur du workflow aura spécifié le champ à transformer ainsi que l'opération à y appliquer, le code suivant fera en sorte d'exécuter les instructions.

protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
{
//__Context permet de récupérer le SPWeb dans lequel le workflow s'exécute
using (SPWeb ContextWeb = __Context.Web)
{
//Grâce à ce SPWeb et à __ListId contenant le GUID de la liste, on peut pointer
//sur la liste
SPList TargetList = ContextWeb.Lists[new Guid(__ListId)];
//__ListItem contenant le numéro de l'élément qui vient d'être créé/modifié, on peut pointer dessus
SPListItem TargetItem = TargetList.GetItemById(__ListItem);
//ici on teste si la colonne est bien de type Texte car on y applique des fonctions String
if (TargetList.Fields[Field].Type == SPFieldType.Text)
{
//Pour éviter de faire un ToString() de null, on teste d'abord la colonne cible.
string CurrentValue = (TargetItem[Field] != null) ? TargetItem[Field].ToString() : "";
//Function contient soit Uppercase soit Lowercase puisque ce sont les options que l'on a défini
//au niveau de la propriété dans le fichier .ACTIONS
switch(Function)
{
case "Uppercase":
//On convertit en majuscules
TargetItem[Field] = CurrentValue.ToUpper();
break;
//On convertit en minuscules
case "Lowercase":
TargetItem[Field] = CurrentValue.ToLower();
break;
}

}
TargetItem.Update();//On met à jour l'élément de liste
}//Using disposera automatiquement du SPWeb.

return ActivityExecutionStatus.Closed;//On notifie le système que le workflow est terminé.
}

Tous les commentaires utiles sont dans le code. Toutefois, on pourrait y ajouter une gestion d'erreur. Il faut savoir qu'en cas de non gestion d'erreur explicite, si le workflow rencontre un problème, il sera en statut "Error Occured" et des informations relatives à l'erreur pourront normalement être trouvées soit dans le gestionnaire d'évènements soit dans les fichiers logs de Sharepoint que l'on trouve dans le répertoire <intallation Sharepoint>/12/Logs/

V-C - Le code complet du fichier .ACTIONS

Il est préférable de créer un fichier .ACTIONS indépendant, dans lequel vous pouvez insérer ce code

<?xml version="1.0" encoding="utf-8" ?>
<WorkflowInfo>
<Actions Sequential="then" Parallel="and">
<Action Name="Transform String"
ClassName="StringTransformations.StringActivity"
Assembly="StringTransformations, Version=1.0.0.0, Culture=neutral, PublicKeyToken=eda78c3b825c364e"
AppliesTo="list"
Category="String Operations"
UsesCurrentItem="true">
<RuleDesigner Sentence="Transform %1 to be in %2">
<FieldBind Field="Field" Text="Field" Id="1" DesignerType="writablefieldNames"/>
<FieldBind Field="Function" Text="Function to apply" Id="2" DesignerType="Dropdown">
<Option Name="Uppercase" Value="Uppercase"/>
<Option Name="Lowercase" Value="Lowercase"/>
</FieldBind>
</RuleDesigner>
<Parameters>
<Parameter Name="__Context" Type="Microsoft.SharePoint.WorkflowActions.WorkflowContext, Microsoft.SharePoint.WorkflowActions" />
<Parameter Name="__ListId" Type="System.String, mscorlib" Direction="In" />
<Parameter Name="__ListItem" Type="System.Int32, mscorlib" Direction="In" />
<Parameter Name="Field" Type="System.String, mscorlib" Direction="In" />
<Parameter Name="Function" Type="System.String, mscorlib" Direction="In" />
</Parameters>
</Action>
</Actions>
</WorkflowInfo>

V-D - Comment tester notre activité opérant une transformation de chaîne?

Pour tester le workflow, rien de plus simple (inspirez-vous de la section Création de notre workflow dans Sharepoint Designer. Voici donc en résumé les différentes étapes à accomplir

- Ouvrez Sharepoint Designer sur le site cible et cliquez sur New => Workflow

- Faites démarrer le workflow sur l'ajout d'un élément

- Ajoutez un step, ajoutez une action en choisissant notre activité et choisissez le champ de la liste à convertir dans la zone "field"

- Sélectionnez ensuite la transformation à accomplir dans la zone "function"

- Cliquez sur Finish

- Allez dans votre site Sharepoint, ajoutez un élément dans votre liste et voyez si la valeur de la colonne cible a bien été transformée

VI - Téléchargement

Vous pouvez télécharger l'exemple de projet ici. C'est l'exemple qui modifie la colonne choisie en majuscules ou minuscules.