Accueil
Rechercher:
sur developpez.com sur les forums
Forums | Tutoriels | F.A.Q's | Participez | Hébergement | Contacts
Club Emploi Blogs   TV   Dév. Web PHP XML Python Autres 2D-3D-Jeux Sécurité Windows Linux PC Mac
Accueil Conception Java DotNET Visual Basic  C  C++ Delphi Eclipse MS-Office SQL & SGBD Oracle  4D  Business Intelligence
FORUMS .NET FAQs .NET TUTORIELS .NET SOURCES .NET LIVRES .NET OUTILS .NET BLOG .NET DOTNET TV

Gestion d'un formulaire OpenXML avec Windows Workflow Foundation

Date de publication : 04/09/2008 , Date de mise à jour : 04/09/2008

Par Florian Casabianca (mon site)
 

Pas de note (Aucun commentaire)

Cet article a pour but de montrer une utilisation conjointe du format Open XML et de Windows Workflow Foundation à travers la création d'un Workflow de traitement de formulaires Microsoft Word.

Introduction
I. Création du formulaire
I-A. Word 2007
I-B. Mise en place du custom XML
II. Le contrat de service
III. Création du Workflow
III-A. ReceiveActivity
III-B. Développer ses propres activités
III-B-1. ExtractCustomXmlActivity
III-B-2. ValidationXDocumentActivity
III-C. SaveCustomXmlActivity
III-D. Utilisation des activités personnalisées
III-D-1. Extraction du Custom XML
III-D-2. Validation du Custom XML
III-D-3. Sauvegarde du Custom XML
III-D-4. Sauvegarde du Custom XML
IV. L'hôte
V. Le client
Conclusion
Sources
Liens
Remerciements
Contact


Introduction

Cet article a pour but de montrer une utilisation conjointe du format Open XML et de Windows Workflow Foundation à travers la création d'un Workflow de traitement de formulaires Microsoft Word.

Nous allons aborder dans une première partie comment créer un document Word au format Open XML contenant directement des données métier. Cela passera par l'utilisation de Custom XML lié à des Content Controls (ou Structured Document Tags).

Nous passerons ensuite au développement d'un Workflow permettant de traiter ce document Open XML afin d'en extraire les données métiers saisies par un utilisateur. Nous utiliserons pour cela le SDK Open XML permettant de manipuler plus facilement ce type de document. L'envoi du document au Workflow se fera via un service WCF. Nous découvrirons ainsi quelques nouveautés apportées à Windows Workflow 3.5 permettant son interaction avec WCF. Une grande partie sera aussi consacrée au développement d'activités personnalisées et réutilisables que nous intégrerons au Workflow.


I. Création du formulaire

Open XML intègre la notion de Custom XML part. Il s'agit de la possibilité d'insérer au sein d'un document Open XML des données métiers. Celles-ci seront stockées dans un fichier XML (une part) ne contenant aucune balise Open XML, seulement ses propres balises métier. Les informations contenues dans ce fichier pourront être liées à des Content Controls disposés au sein du document afin d'être visibles et manipulables par un utilisateur. Cette technique permet une séparation totale entre les données et leur présentation dans un document Open XML.


I-A. Word 2007

La première chose à faire est d'activer l'onglet Développeur si se n'est déjà fait.

Cliquez sur le bouton Microsoft OfficeImage du bouton, puis sur Options Word. La fenêtre suivante apparait:

D:\dotNET\developpez.com\Articles\CustomXML\activer_onglet.png

Cochez la case Afficher l'onglet développeur dans le ruban, puis cliquez sur OK. Le nouvel onglet devrait apparaître dans le ruban:

D:\dotNET\developpez.com\Articles\CustomXML\ongletdev.png

Il ne reste plus qu'à insérer les différents contrôles au sein du document. N'oubliez pas d'éditer les propriétés de ces contrôles et de leur assigner notamment un titre (visible par l'utilisateur dans Word) et un nom de balise (qui va nous permettre de différencier les différents contrôles de contenu lors de la phase de data binding).

D:\dotNET\developpez.com\Articles\CustomXML\ccNom.png

Notre document final ressemblera à ceci:

P:\dotNET\developpez.com\Articles\CustomXML\fiche1.png

Comme vous le voyez il s'agit d'un formulaire simple de saisie d'informations concernant un animal quelconque.


I-B. Mise en place du custom XML

Quittons Word pour nous intéresser à la mise en place de la partie Custom XML (Custom XML part) au sein du document OpenXML.

Voici le XML métier que nous allons insérer dans le custom XML:
Fichier XML métier
<?xml version="1.0" encoding="utf-8" ?>
<ferme xmlns="http://developpez.com/ns/animauxdelaferme" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <animal id="">
    <espece></espece>
    <datenaissance></datenaissance>
    <description></description>
    <photo></photo>
    <nom></nom>
  </animal>
</ferme>
Et le schéma associé:
Schéma XML
<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" attributeFormDefault="unqualified" 
elementFormDefault="qualified" targetNamespace="http://developpez.com/ns/animauxdelaferme" 
xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="ferme">
    <xs:complexType>
      <xs:sequence>
        <xs:element name="animal">
          <xs:complexType>
            <xs:sequence>
              <xs:element name="espece" type="xs:string" />
              <xs:element name="datenaissance" type="xs:dateTime" />
              <xs:element name="description" type="xs:string" />
              <xs:element name="photo" type="xs:base64Binary" />
              <xs:element name="nom" type="xs:string" />
            </xs:sequence>
            <xs:attribute name="id" type="xs:integer" use="required" />
          </xs:complexType>
        </xs:element>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
</xs:schema>
Nous voulons donc mettre en place un data binding (bi-directionnel) entre les Content Controls du document Word et notre XML métier. Pour cela nous allons utiliser un outil nommé Word 2007 Content Control Toolkit disponible sur le site Codeplex.

Son utilisation est assez simple. Après avoir ouvert le document Word (via le toolkit) vous vous retrouvez avec une interface ressemblant à celle-ci-dessous:

P:\dotNET\developpez.com\Articles\CustomXML\toolkit1.png

Le panneau de gauche liste les différents Content Controls du document. La colonne Tag permet de différencier les Content Controls entres eux (d'où l'importance d'avoir rempli cette information dans les propriétés des Content Controls lors de leur insertion dans Word). Le panneau de droite permet de gérer les Custom XML du document. Notre document Word n'en contenant pas, il suffit de cliquer sur le lien "Click here to create a new one" pour en créer un.

Puis, dans l'onglet Edit View, saisissez le XML métier que vous souhaitez insérer dans le document:

P:\dotNET\developpez.com\Articles\CustomXML\toolkit2.png

Retournez maintenant dans l'onglet Bind View. Un treeview représentant votre document XML y apparait. Pour associer un noud du document XML à un Content Control il suffit simplement d'effectuer un drag n' drop du noud sur le Content Control en question (du panneau de gauche). Et c'est tout ! A noter que pour sélectionner un noud il faut faire un double clic dessus. Vous devriez voir la colonne XPath se remplir avec les informations du binding. Enfin, n'oubliez pas d'enregistrer les modifications avant de quitter.

P:\dotNET\developpez.com\Articles\CustomXML\toolkit3.png

Si l'on examine le fichier OpenXML après cette opération, on s'aperçoit que celui-ci contient un nouveau répertoire (customXML) à sa racine:

D:\dotNET\developpez.com\Articles\CustomXML\customxml.png
Le fichier Item1.xml contient exactement (ni plus, ni moins) le code XML que nous avons inséré via le Content Control Toolkit. Un deuxième fichier itemProps1.xml a aussi été créé. Celui-ci contient des informations concernant le custom XML associé. Dans le cas de notre exemple, voici son contenu:
Contenu du fichier de propriété associé au Custom XML
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<ds:datastoreItem ds:itemID="{8DBF1AED-BD48-42B0-99A6-890F79C2D5FF}" 
xmlns:ds="http://schemas.openxmlformats.org/officeDocument/2006/customXml">
  <ds:schemaRefs>
    <ds:schemaRef ds:uri="http://developpez.com/ns/animauxdelaferme"/>
  </ds:schemaRefs>
</ds:datastoreItem>
Deux éléments sont importants ici:

  • L'identifiant (itemID) qui permet d'identifier de façon unique une partie au sein du document OpenXML. Word utilise un GUID mais vous pouvez utiliser n'importe quel type d'identifiant.
  • Une référence sur le schéma utilisé dans le Custom XML.
Les noms des fichiers (item1.xml et itemProps1.xml) sont totalement arbitraires. Rien ne vous empêche de les changer (il ne faut donc pas se baser dessus pour récupérer les informations qu'ils contiennent). Ce qui importe ce sont les relations qui lient les parties d'un document OpenXML entre elles, pas leur nom.

Si l'utilisateur remplit le document de cette manière...

D:\dotNET\developpez.com\Articles\CustomXML\fiche2.png

... le contenu du Custom XML sera le suivant:
Contenu du custom XML
<?xml version="1.0" encoding="utf-8"?><ferme xmlns="http://developpez.com/ns/animauxdelaferme" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <animal id="000339">
    <espece>JERSEY</espece>
    <datenaissance>2006-08-11</datenaissance>
    <description>Mathilde a la réputation de séduire tous les membres de la famille par sa personnalité attachante, sa docilité et sa présence. 
	Une vache de petite taille, fort charmante avec son pelage fauve.</description>
    <photo>.DAAYEBQYFBA.</photo>
    <nom>Mathilde</nom>
  </animal>
</ferme>
Les informations contenues dans le formulaire sont donc clairement dissociées du document Word et se trouvent au sein d'un fichier XML "métier" totalement personnalisé. L'utilisateur va maintenant pouvoir envoyer ce formulaire à un Workflow qui va extraire le Custom XML part du document OpenXML pour en récupérer les informations (et les traiter selon sa logique métier).

Note: peut-être vous demandez vous ce qu'une vache vient faire ici ? Rassurez-vous, l'auteur ne le sait pas lui-même...


II. Le contrat de service

Le Workflow sera exposé en tant que service WCF afin que l'utilisateur puisse lui transmettre le document à traiter. Comme tout service WCF nous devons donc commencer par définir le contrat de service utilisé.

Dans la solution Visual Studio créez un nouveau projet de type Librairie de classe et nommez-le FileUploadServiceLibrary. Ajoutez les références vers System.Runtime.Serialization et System.ServiceModel. Créez une nouvelle interface IUploadService et définissez-y une fonction UploadFile prenant en paramètre un tableau d'octets et renvoyant un objet de type ResultatTraitement:
Contrat de service
using System.IO;
using System.ServiceModel;
using System.Runtime.Serialization;
using System;

namespace FileUploadServiceLibrary
{
    [ServiceContract]
    public interface IUploadService
    {
        [OperationContract]
        ResultatTraitement UploadFile(byte[] file);
    }

    
    [DataContract]
    public class ResultatTraitement
    {
        [DataMember]
        public Guid NumDossier { get; set; }

        [DataMember]
        public bool Accepte { get; set; }

        [DataMember]
        public String Commentaire { get; set; }
    }
}
N'oubliez pas les habituels attributs ServiceContract, OperationContract, DataContract et DataMember.

La classe ResultatTraitement permettra au client de connaître le résultat du traitement par le Workflow du fichier uploadé.


III. Création du Workflow

Plusieurs templates sont disponibles pour le type de projet Workflow. Ici nous utiliserons le template Sequential Workflow Library permettant de définir un Workflow séquentiel, c'est-à-dire contenant des activités s'exécutant dans un ordre défini. Etant donné qu'il s'agit d'une librairie il nous faudra développer (dans une prochaine partie) un hôte permettant de l'héberger. Appelons ce projet WorkflowFormulaireLibrary.

creationworkflowlibrary.png

Le template contient normalement déjà un Workflow appelé Workflow1. Renommez-le en WorkflowTraitementFormulaire. Ajoutez une référence vers le contrat de service que nous avons créé précédemment:

addreference.png

En mode design le Workflow ressemble à ceci:

workflow1.png

III-A. ReceiveActivity

La version 3.5 du Framework apporte deux nouvelles activités à Workflow Foundation: ReceiveActivity et SendActivity. Ces deux nouvelles activités permettent la prise en charge de services WCF. 

newactivities35.png

SendActivity est utilisée pour appeler un service WCF depuis un Workflow et ReceiveActivity permet d'implémenter une opération définie dans un contrat de service (et ainsi exposer le Workflow en tant que service WCF). ReceiveActivity peut implémenter une opération d'un contrat de service existant (c'est ce que nous allons faire) ou définir le contrat et l'opération directement dans le Workflow.

Ajoutons donc une activité de type ReceiveActivity au sein du Workflow via un simple drag n' drop et nommons-la WorkflowTraitementFormulaire. Dans la fenêtre de propriétés mettez à True la propriété CanCreateInstance.

configReceiveActivity.png

Cette propriété spécifie qu'un appel d'un client à cette opération de service déclenchera la création d'un nouveau Workflow (et son exécution).

Notre Workflow doit maintenant ressembler à ceci:

workflow2.png
Vous aurez noté la petite bulle rouge indiquant une erreur au niveau de l'activité ReceiveActivity. Un clic dessus vous apportera plus de détails:

erreurserviceopreceiveactivity.png
L'erreur indique qu'il n'existe pas d'opération de service (WCF) associée à cette activité. Qu'à cela ne tienne, ajoutons-en une. Dans la fenêtre de propriétés de l'activité ReceiveActivity localisez la propriété ServiceOperationInfo et cliquez sur les trois petits points à coté:

erreurserviceopreceiveactivity2.png

Une nouvelle fenêtre s'ouvre vous permettant d'ajouter ou d'importer un contrat existant. Nous choisissons ici l'option d'import:

importcontrat1.png
Une deuxième fenêtre apparaît demandant le contrat de service à importer. Dans le noud Referenced Assemblies vous devriez trouver le contrat WCF que nous avons créé précédemment (dans le cas contraire recompilez la solution). Sélectionnez-le et validez.

importcontrat2.png

La première fenêtre réapparaît. Les différentes opérations du contrat sont listées (dans notre cas nous n'en avons qu'une). Sélectionnez l'opération à associer à l'activité et validez.

importcontrat22.png

La propriété ServiceOperationInfo devrait maintenant être correctement renseignée et la bulle d'erreur devrait avoir disparue.

importcontrat4.png

Notre Workflow est maintenant capable de recevoir un appel d'un client à la méthode UploadFile. Il nous faut ensuite pouvoir récupérer le fichier envoyé (c'est-à-dire le tableau d'octets) et retourner un objet de type ResultatTraitement au client. Pour cela nous allons déclarer deux propriétés dans le fichier de code associé au Workflow:
Propriétés du Workflow
//Fichier sous forme de tableau de bytes envoyé par le client
private byte[] _fileBytes;
public byte[] FileBytes
{
    get
    {
        return _fileBytes;
    }
    set
    {
        _fileBytes = value;
    }
}

//Objet décrivant le résultat du traitement
private ResultatTraitement _resultatWorkflow;
public ResultatTraitement ResultatWorkflow
{
    get
    {
        return _resultatWorkflow;
    }
    set
    {
        _resultatWorkflow = value;
    }
}
L'astuce consiste maintenant à lier (binding) ces propriétés à la méthode UploadFile. Pour cela retournez en mode design et rendez-vous dans la fenêtre de propriétés de l'activité ReceiveActivity. Une catégorie Parameters a fait son apparition lorsque vous avez associé l'activité à la méthode UploadFile:

configReceiveActivity2.png

Nous allons lier ces paramètres aux propriétés créées précédemment. Faites un double clic sur le petit cylindre jaune pour afficher la fenêtre suivante:

configReceiveActivity3.png
La liste des propriétés disponibles pour une liaison apparaît. Associez la valeur de retour à ResultatUpload et file à FileBytes.

La propriété FileBytes du Workflow sera dorénavant initialisée avec le paramètre de la fonction UploadFile et la valeur de retour de cette fonction sera la valeur de la propriété ResultatTraitement. Reste maintenant à effectuer des traitements sur le fichier reçu et à initialiser la propriété ResultatTraitement avec un objet de type ResultatTraitement en fonction de ces traitements.

L'activité ReceiveActivity est dite composite, c'est à dire qu'elle peut contenir d'autres activités. Nous allons donc y ajouter une activité de type CodeActivity. Il s'agit d'une activité standard de Workflow Foundation permettant d'exécuter du code. Dans la suite nous ajouterons d'autres activités, toujours à l'intérieur de l'activité ReceiveActivity. Cela veut dire que l'activité ReceiveActivity ne sera terminée (et donc renverra une réponse au client) que lorsque toutes les activités qu'elle contient le seront également. Cela signifie aussi que le client restera bloqué sur l'appel de la fonction UploadFile tant que l'activité ReceiveActivity ne sera pas terminée. Il s'agit d'un comportement qui n'est pas forcément souhaitable dans certaines situations (en particulier lorsque l'activité dure longtemps). Une alternative possible est d'utiliser la propriété IsOneWay de WCF sur la méthode UploadFile pour qu'elle ne renvoie pas de réponse. Le client devra alors effectuer un deuxième appel sur une autre fonction de service afin d'obtenir le résultat du traitement. Vous pouvez aussi utiliser l'activité SendActivity pour que le Workflow appell un service coté client pour lui envoyer le résultat.

Une fois l'activité CodeActivity ajoutée et renommée en TraitementUploadFile, le Workflow devrait ressembler à ceci:

workflow22.png
Effectuez un double clic sur l'activité CodeActivity pour y insérer le code suivant:
TraitementUploadFile
private void TraitementUploadFile_ExecuteCode(object sender, EventArgs e)
{
    Console.Out.WriteLine("Réception d'un fichier");

    try
    {
        //on crée le répertoire
        if (!Directory.Exists(this.FolderPathToSaveDocs))
                Directory.CreateDirectory(this.FolderPathToSaveDocs);

        Console.WriteLine("Enregistrement du fichier sous: {0}", this.FormulairePath);
        //on enregistre le fichier sur le disque
        File.WriteAllBytes(FormulairePath, this.FileBytes);

        Console.WriteLine();
        Console.WriteLine("Fichier {0} enregistré", this.FormulairePath);
    }
    catch (IOException ex)
    {
        Console.WriteLine(
            String.Format("An exception was thrown while opening or writing to file {0}", this.FormulairePath));
        Console.WriteLine("Exception is: ");
        Console.WriteLine(ex.ToString());

        throw ex;
    }
}
Ce code crée tout d'abord un répertoire qui contiendra les différents fichiers créés par le Workflow. Puis il y enregistre le formulaire envoyé par le client. Le formulaire (envoyé sous forme de tableau d'octets) est accessible via la propriété FileBytes (il s'agit de la propriété que nous avons liée au paramètre file de la méthode UploadFile).

Les propriétés FolderPathToSaveDocs et FormulairePath utilisées sont aussi définies dans le code associé au Workflow:
Propriétés du Workflow
//chemin du répertoire  enregistrer les différents fichiers
private string _folderPathToSaveDocs;
public string FolderPathToSaveDocs
{
    get
    {
        if (_folderPathToSaveDocs == null)
            _folderPathToSaveDocs = String.Format("formulaire_{0}", DateTime.Now.ToString("dd-MM-yyyy__HH_mm_ss"));
        return _folderPathToSaveDocs;
    }
}

//chemin  a été enregistré le formulaire envoyé par le client
private string _formulairePath;
public string FormulairePath
{
    get
    {
        if (_formulairePath == null)
            _formulairePath = Path.Combine(FolderPathToSaveDocs, "formulaire.docx");
        return _formulairePath;
    }
}
Nous allons aussi définir deux autres propriétés qui nous serviront pour plus tard: SchemaPath qui contient l'emplacement du schéma XML associé au Custom XML et SchemaRef qui défini la référence de ce schéma XSD:
//fichier xsd permettant de valider le custom XML
private string _schemaPath = Path.Combine(System.Environment.CurrentDirectory, "Animal.xsd");
public string SchemaPath
{
    get
    {
        return _schemaPath;
    }
}

//référence du schéma du customXML
private string _schemaRef = @"http://developpez.com/ns/animauxdelaferme";
public string SchemaRef
{
    get
    {
        return _schemaRef;
    }
}

III-B. Développer ses propres activités

Nous venons de voir qu'il était possible d'ajouter du code à un Workflow via l'activité Code. Il existe une deuxième technique qui consiste à développer ses propres activités encapsulant une logique métier (dans le même esprit que le développement de contrôles utilisateur en Winform par exemple). Bien que cela demandera du travail supplémentaire, créer ses propres activités offrira de nombreux avantages : composants indépendants (plus facilement testables) et réutilisables dans d'autres Workflows, disponibilité de ces composants dans la boite à outils de Visual Studio (utilisation via un simple drag n' drop). Ce dernier point est important car il va potentiellement offrir à une personne non développeur (mais qui connait le métier) de construire de A à Z un Workflow simplement en manipulant des activités via la surface de design (à la manière de mashups).

Pour notre exemple nous allons développer trois activités:

  • ExtractCustomXmlActivity dont le but sera d'extraire le Custom XML d'un document OpenXML correspondant à un schéma donné.
  • Validation XDocument Activity qui devra valider le contenu d'un XDocument par rapport à un schéma XSD.
  • SaveCustomXmlActivity qui sauvegardera le contenu du Custom XML sur le disque dur.

III-B-1. ExtractCustomXmlActivity

Dans la solution Visual Studio rajoutez un nouveau projet de type Workflow Activity Library et nommez le CustomActivities.

customactivities.png
Cette librairie contiendra les trois activités que nous allons développer. Comme vous le voyez, il s'agit d'une librairie indépendante du projet contenant notre Workflow et donc réutilisable dans d'autres Workflows.

ExtractCustomXmlActivity devra ouvrir un document OpenXML (qui se trouve sur le disque dur) et en extraire le custom XML correspondant à un schéma donné. Remarquez que nous ne parlons pas d'extraire le custom XML spécifique au formulaire de notre exemple mais d'un Custom XML correspondant à un schéma donné. Cela permettra de réutiliser cette activité pour extraire le Custom XML de n'importe quel autre document OpenXML, même s'il ne s'agit pas d'un formulaire correspondant à notre exemple.

Bien que l'extraction de Custom XML puisse être faite de différentes manières, nous allons utiliser ici le SDK OpenXML en version 1.0. Il s'agit d'une API utilisant la librairie System.IO.Packaging mais offrant un niveau d'abstraction supplémentaire et donc une donc une manipulation plus facile de documents OpenXML. A titre de comparaison vous pouvez jeter un coup d'oil sur l'article suivant: lire et modifier un fichier Word OpenXML utilisant System.IO.Packaging pour manipuler les documents OpenXML.

Il nous faut rajouter une référence vers le composant DocumentFormat.OpenXml:

documentformat.openxml.png

Visual Studio a normalement créé automatiquement une nouvelle activité appelée Activity1. Supprimez la et ajoutez-en une nouvelle (clic droit, Add, Activity) ou alors renommez la en ExtractCustomXmlActivity.

Si vous analysez le code de cette activité vous verrez qu'elle dérive de la classe de base SequenceActivity. Cela signifie que notre activité est de type composite et a la possibilité d'héberger d'autres activités en son sein. Dans notre exemple nous ne voulons développer que des activités simples et n'avons donc pas besoin de cette possibilité de composition. Nous allons donc plutôt faire dériver l'activité de la classe Activity:
public partial class ExtractCustomXmlActivity : Activity
Cette activité doit donc prendre en entrée un chemin vers un fichier OpenXML et une référence de schéma (tous deux de type String) et retourner le Custom XML correspondant (de type XDocument). Pour cela nous allons utiliser des dependency properties. L'avantage des dependency properties est qu'elles vont permettre de faire des liaisons (binding) entre activités. Il sera par exemple possible de lier une propriété d'entrée d'une activité à une propriété de sortie d'une autre activité.

Dans le cas de l'activité ExtractCustomXmlActivity nous avons besoin de trois dependency properties:
Dependency propertie pour le chemin du fichier OpenXML
public static DependencyProperty FilePathProperty = DependencyProperty.Register("FilePath", typeof(string), typeof(ExtractCustomXmlActivity));

[DescriptionAttribute("Chemin du fichier OpenXML dont il faut extraire le custom Xml")]
[CategoryAttribute("Input")]
[BrowsableAttribute(true)]
[DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Visible)]
public string FilePath
{
    get
    {
        return ((string)(base.GetValue(ExtractCustomXmlActivity.FilePathProperty)));
    }
    set
    {
        base.SetValue(ExtractCustomXmlActivity.FilePathProperty, value);
    }
}
Dependency propertie pour le Custom XML
public static DependencyProperty CustomXmlProperty = DependencyProperty.Register("CustomXml", typeof(XDocument), typeof(ExtractCustomXmlActivity));

[DescriptionAttribute("XDocument résultant de l'extraction du custom Xml")]
[CategoryAttribute("Output")]
[BrowsableAttribute(true)]
[DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Visible)]
public XDocument CustomXml
{
    get
    {
        return ((XDocument)(base.GetValue(ExtractCustomXmlActivity.CustomXmlProperty)));
    }
    set
    {
        base.SetValue(ExtractCustomXmlActivity.CustomXmlProperty, value);
    }
}
Dependency propertie pour le schéma XML
public static DependencyProperty SchemaProperty = DependencyProperty.Register("Schema", typeof(string), typeof(ExtractCustomXmlActivity));

[DescriptionAttribute("Schema associé au custom Xml à extraire")]
[CategoryAttribute("Input")]
[BrowsableAttribute(true)]
[DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Visible)]
public string Schema
{
    get
    {
        return ((string)(base.GetValue(ExtractCustomXmlActivity.SchemaProperty)));
    }
    set
    {
        base.SetValue(ExtractCustomXmlActivity.SchemaProperty, value);
    }
}
L'écriture d'une dependency propertie peut sembler quelque peu rébarbative mais Visual Studio propose un snipet pour vous aider.

Il ne reste plus qu'à implémenter la logique de l'activité. Cela s'effectue en implémentant la méthode Execute qui est une méthode virtuelle de la classe de base.
Méthode Execute de l'activity ExtractCustomXmlActivity
protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
{
    Console.WriteLine("Extraction du custom XML");

    XNamespace ds = @"http://schemas.openxmlformats.org/officeDocument/2006/customXml";
    XNamespace nsmetier = Schema;            

    using (WordprocessingDocument wordDoc = WordprocessingDocument.Open(FilePath, false))
    {
        MainDocumentPart mainPart = wordDoc.MainDocumentPart;
        foreach (var customXmlPart in mainPart.CustomXmlParts)
        {
            //on ouvre le CustomXmlPropertiesPart associé au customXmlPart qu'on traite
            XDocument customXmlPropertiesPart = XDocument.Load(XmlReader.Create(customXmlPart.CustomXmlPropertiesPart.GetStream()));
            //namespace du schéma du Custom XML
            string uri = customXmlPropertiesPart.Descendants(ds + "schemaRef").First().Attribute(ds + "uri").Value;
            //si le namespace correspond à celui que l'on recherche
            if (uri.Equals(Schema, StringComparison.InvariantCultureIgnoreCase))
            {
                CustomXml = XDocument.Load(XmlReader.Create(customXmlPart.GetStream()));
                break;
            }
        }
        
    }
    return base.Execute(executionContext);
}
C'est ici que nous utilisons le SDK OpenXML. Nous instancions un objet de type WordprocessingDocument qui représente un document Word OpenXML. La propriété MainDocumentPart renvoie la partie principale de ce document (notez la simplicité par rapport à l'utilisation directe de System.IO.Packaging). Nous parcourons la liste des Custom XML et pour chacun d'eux nous analysons la partie contenant les propriétés associées à la recherche de l'espace de nom passé en paramètre de l'activité. Une fois trouvée, nous chargeons le Custom XML dans un XDocument et l'affectons à la propriété CustomXML de l'activité.

La méthode Execute doit retourner une valeur de l'énumération ActivityExecutionStatus. L'instruction base.Execute(executionContext); retourne la valeur Closed qui spécifie que l'activité est terminée.


III-B-2. ValidationXDocumentActivity

Dans notre librairie CustomActivities ajoutez une nouvelle activité et nommez-la ValidationXDocumentActivity (pensez aussi à modifier la classe de base comme pour l'activité précédente). Cette activité devra valider un document XML par rapport à un schéma XSD. Un booléen déterminera si la validation a réussit ou non.

Déclarons trois dependency properties:

SchemaPathProperty de type string représentera le chemin du fichier XSD.
Dependency property pour le chemin du fichier xsd
public static DependencyProperty SchemaPathProperty = DependencyProperty.Register("SchemaPath", typeof(string), typeof(ValidationXDocumentActivity));

[DescriptionAttribute("Chemin du fichier xsd permettant la validation du custom XML")]
[CategoryAttribute("Input")]
[BrowsableAttribute(true)]
[DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Visible)]
public string SchemaPath
{
    get
    {
        return ((string)(base.GetValue(ValidationXDocumentActivity.SchemaPathProperty)));
    }
    set
    {
        base.SetValue(ValidationXDocumentActivity.SchemaPathProperty, value);
    }
}
XDocumentToValidateProperty sera le XDocument à valider.
Dependency property pour le XDocument à valider
public static DependencyProperty XDocumentToValidateProperty = 
		  	DependencyProperty.Register("XDocumentToValidate", typeof(XDocument), typeof(ValidationXDocumentActivity));

[DescriptionAttribute("XDocument contenant le custom Xml à valider")]
[CategoryAttribute("Input")]
[BrowsableAttribute(true)]
[DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Visible)]
public XDocument XDocumentToValidate
{
    get
    {
        return ((XDocument)(base.GetValue(ValidationXDocumentActivity.XDocumentToValidateProperty)));
    }
    set
    {
        base.SetValue(ValidationXDocumentActivity.XDocumentToValidateProperty, value);
    }
}

Enfin, IsCustomXmlValideProperty, de type booléen, sera le résultat de la validation.
Dependency property pour le résultat de la validation
public static DependencyProperty IsCustomXmlValideProperty = 
		  	DependencyProperty.Register("IsCustomXmlValide", typeof(bool), typeof(ValidationXDocumentActivity));

[DescriptionAttribute("Résultat de la validation")]
[CategoryAttribute("Output")]
[BrowsableAttribute(true)]
[DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Visible)]
public bool IsCustomXmlValide
{
    get
    {
        return ((bool)(base.GetValue(ValidationXDocumentActivity.IsCustomXmlValideProperty)));
    }
    set
    {
        base.SetValue(ValidationXDocumentActivity.IsCustomXmlValideProperty, value);
    }
}

Implémentons maintenant la logique de l'activité via la méthode Execute:
Méthode Execute de ValidationXDocumentActivity
/// <summary>
/// Valide le custom Xml du document
/// </summary>
/// <param name="executionContext"></param>
/// <returns></returns>
protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
{
    Console.WriteLine("Validation du custom XML");

    if (XDocumentToValidate != null)
    {
        try
        {
            //on charge le fichier xsd
            XmlSchemaSet xmlSchemaSet = LoadSchema(SchemaPath);

            XDocumentToValidate.Validate(xmlSchemaSet, null);
            IsCustomXmlValide = true;
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }    
    return base.Execute(executionContext);
}
Nous chargeons le fichier XSD via la méthode LoadSchema (voir plus bas). Celle-ci retourne un objet de type XmlSchemaSet qui est un cache pouvant contenir plusieurs schémas XSD. Nous utilisons ensuite la méthode d'extension Validate (de l'espace de nom System.Xml.Schema) sur le XDocument contenant le Custom XML. Si la validation échoue une exception est lancée. Si la validation réussit nous mettons la valeur de la propriété IsCustomXmlValide à True.

Voici le code de la méthode LoadSchema:
Fonction LoadSchema
static XmlSchemaSet LoadSchema(string schemaPath)
{
    XmlSchemaSet xmlSchemaSet = new XmlSchemaSet(new NameTable());
    using (XmlReader reader = XmlReader.Create(schemaPath))
    {
        //chargement du schéma
        XmlSchema animauxSchema = XmlSchema.Read(reader, null);
        reader.Close();
        //ajout du schéma au XmlSchemaSet
        xmlSchemaSet.Add(animauxSchema);
    }
    return xmlSchemaSet;
}

III-C. SaveCustomXmlActivity

Créons maintenant la troisième activité (vous savez comment faire maintenant). Celle-ci devra enregistrer sur le disque dur (à un emplacement déterminé par une dependency property) le Custom XML (dependency property également). De plus l'image sérialisée dans le Custom XML sera extraite et sauvegardée dans un fichier à part. Nous pourrions imaginer des scénarios plus élaborés comme l'enregistrement dans une base de données où l'envoi d'informations à un Web service.

La première dependency property, de type XDocument représente le Custom XML:
Property dependency pour XDocument à sauver
public static DependencyProperty CustomXmlToSaveProperty = DependencyProperty.Register("CustomXmlToSave", typeof(XDocument), typeof(SaveCustomXmlActivity));

[DescriptionAttribute("XDocument contenant le CustomXml à sauver")]
[CategoryAttribute("Input")]
[BrowsableAttribute(true)]
[DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Visible)]
public XDocument CustomXmlToSave
{
    get
    {
        return ((XDocument)(base.GetValue(SaveCustomXmlActivity.CustomXmlToSaveProperty)));
    }
    set
    {
        base.SetValue(SaveCustomXmlActivity.CustomXmlToSaveProperty, value);
    }
}

La deuxième correspond au chemin où sauvegarder le Custom XML ainsi que l'image désérialisée:
Property dependency pour le chemin où sauver les images
public static DependencyProperty SavePathProperty = DependencyProperty.Register("SavePath", typeof(string), typeof(SaveCustomXmlActivity));

[