Si l’on s’en tient à la documentation Microsoft sur la création d’une page d’association de Workflow, chaque page devrait faire tout le boulot. Extrait du SDK (rubrique Workflow Association and Initiation Forms) :
The workflow developer must program what happens when the administrator submits changes to the form. In general, the custom workflow association form must perform the following actions:
- Examine the value of the GuidAssoc parameter to determine whether the user is adding a new workflow association or editing an existing workflow association.
- If the user is adding a new workflow association, call the AddWorkflowAssociation method to create a new workflow association.
- If the user is editing an existing workflow association, call the UpdateWorkflowAssociation method to update that workflow association.
- Create the task list for the workflow, if it does not already exist.
- Use the data collected from the user to set properties of the SPWorkflowAssociation object, as appropriate.
- Create the workflow history list, if necessary.
Tout ça est très lourd et doit être reproduit pour chaque page d’association que vous devez créer. Il existe toutefois une solution, probablement non supportée puisque non documentée.
Avec un petit coup de Reflector, j’ai trouvé que toutes les pages d’association de workflow héritent d’une même classe : Microsoft.SharePoint.ApplicationPages.WrkAssocPage dans l’assembly Microsoft.SharePoint.ApplicationPages.dll. Cette dernière se trouve dans le dossier %COMMONPROGRAMFILES%\Microsoft Shared\web server extensions\12\CONFIG\BIN.
Les classes qui héritent de cette page sont partiellement obfusquées, donc j’ai du tâtonner un peu avant de trouver comment l’utiliser.
Les avantages que cette page offre sont multiples :
- récupération automatique des paramètres du workflow (depuis la page AddWrkfl.aspx)
- gestion des paramètres du postback : chaque postback conserve les valeurs initiales du postback qui vous a conduit sur la page. Sans cela, il aurait fallu déclarer des champs de type
HIDDEN
pour conserver ses paramètres. - et le meilleur pour la fin, toute la création/modification des associations de workflow, incluant la création des listes de tâches / historique de workflow, etc. est gérée par la classe de base.
Voici un petit tutorial qui explique comment utiliser cette classe.
Exemple d’une page d’association qui utilise cette classe de base
Créer le projet
Pour commencer, nous allons créer un nouveau projet de type Workflow séquentiel (pour l’exemple, ça marcherait également avec un workflow de type State-Machine) :
On choisi ensuite le site sur lequel on va débugger :
Vu que l’objectif est d’illustrer la page d’association, choisissons de ne pas associer automatiquement le workflow :
Le workflow est créé !
Créer les références
Si comme moi, vous n’avez que WSS et non MOSS sur votre environnement, il faudra supprimer la référence vers Microsoft.Office.Workflow.Tasks.dll
Il faudra également supprimer une ligne du fichier Workflow1.cs pour la même raison :
using Microsoft.Office.Workflow.Utility;
Enfin, toujours pour la même raison (Microsoft n’a pas pensé à ceux qui n’utilisent que WSS 3.0), supprimer du fichier feature.xml les deux lignes :
ReceiverAssembly="Microsoft.Office.Workflow.Feature, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"
ReceiverClass="Microsoft.Office.Workflow.Feature.WorkflowFeatureReceiver"
Terminez par rajoute la référence vers l’assembly Microsoft.SharePoint.ApplicationPages.dll qui se trouve pour rappel ici : ”%COMMONPROGRAMFILES%\Microsoft Shared\web server extensions\12\CONFIG\BIN
Créer la page d’association
Commençons par ajouter à la racine du projet une classe Workflow1AssocPage.cs
, nous reviendrons dessus tout à l’heure.
Ajouter à votre projet la structure de dossier 12\Template\Layouts (j’utilise l’extention WSPBuilder pour construire le package de déploiement).
Dans ce dossier, créer un fichier de type text que vous appellerez Workflow1Assoc.aspx. Je passe par un fichier texte car le template de projet Workflow n’autorise pas l’ajout de page aspx :
Concevez votre page comme vous le souhaitez, en héritant de la classe précédemment créée. Je préfère pour des raisons d’homogénéité conserver le look&feel SharePoint, donc je vous conseille de partir d’une page existante, ou alors de copier coller le code suivant :
<%@ Assembly Name="SharePointWorkflow1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=923dfcd42d705cc0" %>
<%@ Page AutoEventWireup="true" Inherits="SharePointWorkflow1.Workflow1AssocPage, SharePointWorkflow1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=923dfcd42d705cc0" Language="C#" MasterPageFile="~/_layouts/application.master" Title="Choix des validateurs" %>
<%@ Register TagPrefix="SharePoint" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register TagPrefix="wssuc" TagName="InputFormSection" Src="/_controltemplates/InputFormSection.ascx" %>
<%@ Register TagPrefix="wssuc" TagName="InputFormControl" Src="/_controltemplates/InputFormControl.ascx" %>
<%@ Register TagPrefix="wssuc" TagName="ButtonSection" Src="/_controltemplates/ButtonSection.ascx" %>
<asp:Content ID="Content1" ContentPlaceHolderID="PlaceHolderPageTitleInTitleArea" runat="server">
<SharePoint:EncodedLiteral ID="EncodedLiteral1" runat="server" Text="Saisie du message" EncodeMethod='HtmlEncode' />
</asp:Content>
<asp:Content ID="Content2" runat="server" ContentPlaceHolderID="PlaceHolderMain">
<table border="0" cellspacing="0" cellpadding="0">
<wssuc:InputFormSection runat="server" Title="Saisie du message" id="ifsUsers">
<template_description>Saisissez votre message</template_description>
<template_inputformcontrols>
<tr valign="top">
<td width=10> </td>
<td colspan=4>
<asp:TextBox runat="server" ID="txtMsg" />
</td>
</tr>
</template_inputformcontrols>
</wssuc:InputFormSection>
<wssuc:ButtonSection runat="server">
<template_buttons>
<asp:Button UseSubmitBehavior="false" runat="server" Text="Ok" id="btnSubmit" onclick="btnSubmit_OnClick" />
</template_buttons>
</wssuc:ButtonSection>
</table>
<SharePoint:FormDigest ID="FormDigest1" runat="server" />
</asp:Content>
Pensez à changer votre PublicKeyToken ! (Reflector est votre ami).
Passons maintenant à la partie code.
Coder la page d’association
Ouvrez le fichier Workflow1AssocPage.cs et modifiez le comme suit :
using System;
using System.Collections.Generic;
using System.Text;
namespace SharePointWorkflow1
{
public class Workflow1AssocPage : Microsoft.SharePoint.ApplicationPages.WrkAssocPage
{
protected override void OnLoad(EventArgs ea)
{
base.OnLoad(ea);
base.AssociationOnLoad(ea);
base.PreservePostbackParameters();
}
}
}
Tout le secret se situe dans la surcharge de la méthode OnLoad.
base.OnLoad(ea);
Pour ne pas court-circuiter le fonctionnement de la classe de base.
base.AssociationOnLoad(ea);
Cette méthode va analyser les paramètres de l’association qui sont envoyées par la page précédente (AddWrkfl.aspx) et dans l’url.
base.PreservePostbackParameters();
Cette méthode va régénérer les champs de type hidden, pour qu’au fil des PostBack les paramètres soient conservés.
Ajoutons maintenant la directement using et les identifiants des contrôles de la page :
using System.Web.UI.WebControls;
public class Workflow1AssocPage : Microsoft.SharePoint.ApplicationPages.WrkAssocPage
{
protected TextBox txtMsg;
protected Button btnSubmit;
et enfin l’implémentation du bouton Submit :
protected void btnSubmit_OnClick(object sender, EventArgs e)
{
base.m_formData = System.Web.HttpUtility.HtmlEncode(txtMsg.Text);
base.PerformAssociation();
string url = base.Web.Lists[new Guid(base.Request.QueryString["List"])].DefaultViewUrl;
base.Response.Redirect(url);
}
Comment faire plus simple ?
La valeur du champ base.m_formData sera transmise à chaque worfkflow, dans le champ AssociationData de la propriété WorkflowProperties de l’activité OnWorkflowActivated. C’est donc dans ce champ que vous devrez passez toutes les infos utiles au fonctionnement de votre workflow (typique le choix des utilisateurs qui doivent approuver un élément).
La méthode base.PerformAssociation est la méthode magique qui fait tout :
- si la liste de tâche n’existe pas, elle est créée
- si la liste d’historique n’existe pas, elle est créée
- si c’est une nouvelle association elle est créée
- si c’est une association qui existe déjà elle est modifiée.
Modifions maintenant le workflow lui même. Pour l’exemple, je ne ferai que loguer ce qui est passé par la page d’association :
Ajout d’une activité LogToHistoryActivity :
Puis bind de la propriété HistoryOutcome de cette activité sur la valeur de la propriété AssociationData :
Il faut ensuite modifier le fichier Workflow.xml pour indiquer quelle est la page d’association du workflow :
<Workflow
Name="SharePointWorkflow1"
Description="My SharePoint Workflow"
Id="02c4f98f-8168-4eb0-bccb-2583a182e65a"
CodeBesideClass="SharePointWorkflow1.Workflow1"
CodeBesideAssembly="SharePointWorkflow1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=923dfcd42d705cc0"
AssociationUrl="_layouts/Workflow1Assoc.aspx">
Déployez tout ceci. avez une installation WSPBuilder :
- Compiler
- Déployer depuis le menu “Deploy” sur le clic droit de la solution
- Déployer le contenu du dossier 12 via le menu “WSPBuilder –> Copy to 12 hive”
- Recycler le pool d’application “WSPBuilder –> Recycle app pool”
Vérifions que tout fonctionne :
Sur votre site SharePoint, vérifier que la feature correspondante est bien activée (feature de collection de site) :
Sur une liste (ici une bibliothèque de documents), ouvrez les paramètres et choisissez “flux de travail”. Vous devriez voir votre workflow :
Validez. Vous arrivez comme prévu sur votre page :
Saisissez un texte puis validez.
Démarrer ensuite votre flux de travail (en créant un élément, en le démarrant manuellement, tout dépend des choix faits sur la page AddWrkfl.aspx
).
Le message saisi apparaît bien !
Conclusion
Une étape qui pouvait être assez pénible se trouve maintenant grandement facilitée, car toute la tuyauterie d’association de workflow est gérée pour vous. Cette page n’est pourtant pas documentée… ce qui est dommage. Heureusement que votre serviteur est là 🙂