Création d'activités de workflow pour SharePoint Designer

En tant que développeur SharePoint, quand vous êtes amené à étendre une solution par un développement spécifique, vous devez veiller à respecter une des principales directives du produit SharePoint : favoriser l'extensibilité et donner la main à l'utilisateur final d'adapter les fonctionnalités à ses besoins (qui changent fréquemment).

 

Par exemple, si votre client vous demande de créer plusieurs versions d'un workflow qui présentent une logique similaire mais avec de faibles variations, ou bien qui manipulent des objets SharePoint différents, il est judicieux dans ce cas de lui proposer de créer des activites de workflow répondant à son besoin et de le laisser configuer ses workflows à sa guise en utilisant Microsoft SharePoint Designer.

 

Dans cet article, nous allons voir comment créer une activité de workflow SharePoint en utilisant Visual Studio 2012.

 

Création de la solution

 

Pour commencer nous allons créer un nouveau projet SharePoint vide (nommé SharePointWorkflowActivity)

 

 

Ensuite définir l'url du site SharePoint de test, et choisir de déployer la solution en tant que "farm solution".

 

 

Une fois la solution visual studio créée, nous allons y ajouter les références suivantes :

  • Microsoft.SharePoint.WorkflowActions : Contient les activités de Workflow incluses par défaut dans SharePoint, ainsi que les classes de base nécessaires à la création de workflows SharePoint.

  • System.Workflow.ComponentModel : Contient les classes associées au modèle objet des Windows Workflow Foundation.

  • System.Workflow.Activities : Contient les classes associées aux activités de Windows Workflow Foundation.

Voici un aperçu de l'arborescence de la la solution Visual Studio :

 

 

Création de l'activité

La création de l'activité du workflow consiste en cinq étapes :

  • Ajout d'une classe qui représente l'activité et qui hérite de la classe System.Workflow.ComponentModel.Activity.

  • Ajout des Dependency properties à la classe de l'activité, pour pouvoir échanger des données entre le workflow et l'activité.

  • Redéfinition de la méthode Execute  pour y inclure la logique métier de l'activité.

  • Ajout d'un fichier d'extension .action nécessaire pour décrire l'activité pour qu'elle puisse être utilisée à partir de SharePoint Designer.

  • Déploiement de de l'activité.

Ajout de la classe de l'activité

Dans cet exemple nous nous proposons de créer une activité pour l'envoi d'un sms, cette activité reçoit en paramètres d'entrée le numéro d'envoi et le corps du message SMS. L'envoi du message se fait à travers l'appel d'une API propriétaire d'envoi de SMS.

Ajoutons la classe SendSMSActivity au projet :

 

using System.Workflow.ComponentModel;

namespace SharePointWorkflowActivity
{
    public class SendSMSActivity : Activity
    {

    }
}

Creation des propriétés de dépendances

Pour pouvoir récupérer les paramètres d'entrée de l'activité et retourner les paramètres de sortie au workflow, nous devons associer une Dependency Property à chaque Propriété d'entrée ou de sortie de l'activité.

Les Dependecy Properties sont un mécanisme utilisé dans .Net pour transmettre des données entre objet, notamment dans le contexte de databinding, permettant entre autres d'associer des métadonnées aux propriétés, de gérer les valeurs par défaut, et d'assurer la validation. Les dependency properties sont associées à des calsses héritant de la classe DependencyObject qui contient nottament les méthodes GetValue et SetValue permettant la lecture et l'écriture des Dependency Properties.

Une Dependency Property est déclarée en tant que membre publique statique de la classe, avec en paramètres le nom de la propriété, son type, et le type de la classe qui la contient :

 

public static DependencyProperty NameProperty
    = DependencyProperty.Register("Name", typeof(PrpertyTypeName), typeof(ClassName));

 

Puisque la classe de base Activity hérite de la classe DependencyObject, nous pouvons imlplémenter les propriétés PoneNumber et MessageText en les associant à des Dependency Properties :

 

public class SendSMSActivity : Activity
{
    public static DependencyProperty PhoneNumberProperty
        = DependencyProperty.Register("PhoneNumber", typeof(string), typeof(SendSMSActivity));

    [Description("Recipient Pone Number")]
    [Browsable(true)]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
    public string PhoneNumber {
        get
        {
            return ((string)(base.GetValue(SendSMSActivity.PhoneNumberProperty)));
        }
        set
        {
            base.SetValue(SendSMSActivity.PhoneNumberProperty, value);
        }
    }

    public static DependencyProperty MessageTextProperty
        = DependencyProperty.Register("MessageText", typeof(string), typeof(SendSMSActivity));
        
    public string MessageText {
        get
        {
            return ((string)(base.GetValue(SendSMSActivity.MessageTextProperty)));
        }
        set
        {
            base.SetValue(SendSMSActivity.MessageTextProperty, value);
        }
    }
}

Redéfinition de la méthode Execute

La méthode Execute est responsable de l'exécution de la logique métier spécifique à notre activité. Dans notre exemple, il suffit d'invoquer l'API d'envoi de SMS en lui passant les propriétés PhoneNumber et MessageText.

 

protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
{
    try
    {
        SPSecurity.RunWithElevatedPrivileges(delegate()
        {
            SMSSender.SendTextMessage(this.PhoneNumber, this.MessageText);
        });

    }
    catch (Exception e)
    {
        Logger.Log(e, executionContext, base.WorkflowInstanceId);
        throw e;
    }
    return base.Execute(executionContext);
}

Création du fichier .action

Le fichier .action est nécessaire pour que SharePoint Designer puisse découvrir la nouvelle activité de Workflow créée, ce fichier doit être déployé sous {SharePointRoot}\TEMPLATE\1033\Workflow.

Ajoutons le dossier mappé {SharePointRoot}\TEMPLATE\1033\Workflow à notre projet :

 

 

Ajouton un fichier XML nommé SendSMSActivity.action sous le dossier mappé Workflow, avec le contenu suivant :

 

<WorkflowInfo>
  <Actions Sequential="then" Parallel="and">
    <Action Name="Send an SMS" ClassName="SharePointWorkflowActivity.SendSMSActivity"
            Assembly="SharePointWorkflowActivity, Version=1.0.0.0, Culture=neutral, PublicKeyToken=f2a341061174ebf7"
            AppliesTo="all" Category="Custom Actions">
      <RuleDesigner Sentence="Send an SMS with  %1  to %2">
        <FieldBind Field="MessageText" Text="this text" DesignerType="TextArea" Id="1"/>
        <FieldBind Field="PhoneNumber" Text="this phone number" DesignerType="TextArea" Id="2" />       
      </RuleDesigner>
      <Parameters>
        <Parameter Name="PhoneNumber" Type="System.String, mscorlib" Direction="In" />
        <Parameter Name="MessageText" Type="System.String, mscorlib" Direction="In" />
      </Parameters>
    </Action>
  </Actions>
</WorkflowInfo>

La balise Action contient la propriété Name qui sera utilisée comme label de l'activité dans l'éditeur de Workflow de SharePoint Designer. Cette balise contient aussi la propriété Assembly qui doit référencer l'assembly signé et déployé dans le gac de notre activité.

La balise RuleDesigner contient la propriété Sentence qui sera utilisée pour décrire une activitée déposée dans la surface de conception du Workflow et dont les propriétés sont configurées. La propriété Field de la balise FieldBind doit correspondre au nom d'un paramètre présent dans la liste des paramètres de la balise Parameters.

La balise Parameter décrit un paramètre de Workflow avec les propriétés Nom, type CLR, et direction (In, Out, Optional), les noms de ces paramètres doivent correspondre aux Dependency Properties créées dans le code de notre activité.

Déploiement de l'activité

Ajoutons une Feature à notre projet pour gérér le déploiement de l'assembly de notre activity "Send SMS" :

 

 

Noter le scope de déploiement "Web application" de la fonctionnalité (Feature).

Ajoutons un EventReciever à cette Feature pour gérer les évènements FeatureActivated et FeatureDeactivating :

 

public override void FeatureActivated(SPFeatureReceiverProperties properties)
{
    SPSecurity.RunWithElevatedPrivileges(delegate()
    {
        SPWebService spWebService = SPWebService.ContentService;
        SPWebConfigModification spWebConfigModification = new SPWebConfigModification();
        spWebConfigModification.Path = "configuration/System.Workflow.ComponentModel.WorkflowCompiler/authorizedTypes";
        spWebConfigModification.Name = "authorizedType[@Assembly='SharePointWorkflowActivity, Version=1.0.0.0, Culture=neutral, PublicKeyToken=f2a341061174ebf7'][@Namespace='SharePointWorkflowActivity'][@TypeName='*'][@Authorized='True']";
        spWebConfigModification.Sequence = 0;
        spWebConfigModification.Owner = "IPTechSendSMSActivity";
        spWebConfigModification.Type = SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode;
        spWebConfigModification.Value = "<authorizedType Assembly='SharePointWorkflowActivity, Version=1.0.0.0, Culture=neutral, PublicKeyToken=f2a341061174ebf7' Namespace='SharePointWorkflowActivity' TypeName='*' Authorized='True' />";
        spWebService.WebConfigModifications.Add(spWebConfigModification);
        spWebService.Update();
        spWebService.ApplyWebConfigModifications();
    });
}

public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
{
    SPSecurity.RunWithElevatedPrivileges(delegate()
    {
        SPWebApplication webApplication = properties.Feature.Parent as SPWebApplication;

        var spWebConfigModifications = webApplication.WebConfigModifications;
        var items = spWebConfigModifications.Where
                                            (m => m.Owner == "IPTechSendSMSActivity")
                                            .ToList();
        foreach (var item in items)
        {
            spWebConfigModifications.Remove(item);
        }
    });
}

Pour que l'assembly contenant notre activité puisse être utilisé par des workflows SharePoint, il doit être déclaré comme AuthorizedType dans le fichier web.config se trouvant à la racine du serveur web IIS de l'application web SharePoint.

Le code présent dans la méthode FeatureActivated édite ce fichier web.config en utilisant la classe SPWebConfigModification, puis ajoute une ligne référençant notre assembly sous la section "configuration/System.Workflow.ComponentModel.WorkflowCompiler/authorizedTypes".

La propriété Owner de cette modification porte l'identificateur "IPTechSendSMSActivity", qui nous permettera de supprimer cette ligne du fichier de web.config lors de l'invocation de la méthode FeatureDeactivating.

 

Test de l'activité

Lançons SharePoint Designer 2010 et créons un nouveau workflow "Send SMS test".

En cliquant sur le menu Action de la barre d'outil on retrouve bien une nouvelle catégorie nommée "Custom Actions" sous laquelle est listée notre activité "Send an SMS" (comme configuré dans le fichier .action).

 

 

En ajoutant l'activité (nommée action sous SharePoint Designer) à la première étape du workflow on obtient le message suivant :

 

 

Voilà notre activité est fonctionnelle et peut être utilisée dans n'importe quel workflow SharePoint par les développeurs sous visual Studio ou par les utilisateurs finaux avec SharePoint Designer.