Partager via


Procédure pas à pas : créer un processeur de directive personnalisé

Les processeurs de directives fonctionnent en ajoutant du code à la classe de transformation générée. Si vous appelez une directive à partir d’un modèle de texte, le reste du code que vous écrivez dans votre modèle de texte peut dépendre des fonctionnalités qu’elle fournit.

Vous pouvez écrire vos propres processeurs de directive personnalisés. Vous avez ainsi la possibilité de personnaliser vos modèles de texte. Pour créer un processeur de directive personnalisé, vous devez définir une classe qui hérite de DirectiveProcessor ou de RequiresProvidesDirectiveProcessor.

Cette procédure pas à pas décrit notamment les tâches suivantes :

  • Créer un processeur de directives personnalisé

  • Inscrire le processeur de directives

  • Tester le processeur de directives

Créer un processeur de directive personnalisé

Dans cette procédure pas à pas, vous allez créer un processeur de directive personnalisé. Vous ajouterez une directive personnalisée qui lit un fichier XML, le stocke dans une variable XmlDocument et l'expose via une propriété. Dans la section « Test du processeur de directive », vous allez utiliser cette propriété dans un modèle de texte pour accéder au fichier XML.

L'appel à votre directive personnalisée se présente comme suit :

<#@ CoolDirective Processor="CustomDirectiveProcessor" FileName="<Your Path>DocFile.xml" #>

Le processeur de directive personnalisé ajoute la variable et la propriété à la classe de transformation générée. La directive que vous écrivez utilise les classes System.CodeDom pour créer le code que le moteur ajoute à la classe de transformation générée. Les classes System.CodeDom créent du code en Visual C# ou Visual Basic, selon le langage spécifié dans le paramètre language de la directive template. Le langage du processeur de directive et celui du modèle de texte qui accède à ce processeur ne doivent pas nécessairement correspondre.

Le code créé par la directive se présente comme suit :

private System.Xml.XmlDocument document0Value;

public virtual System.Xml.XmlDocument Document0
{
  get
  {
    if ((this.document0Value == null))
    {
      this.document0Value = XmlReaderHelper.ReadXml(<FileNameParameterValue>);
    }
    return this.document0Value;
  }
}

Pour créer un processeur de directive personnalisé

  1. Dans Visual Studio, créez un projet de bibliothèque de classes C# ou Visual Basic nommé CustomDP.

    Notes

    Si vous voulez installer le processeur de directives sur plusieurs ordinateurs, utilisez plutôt un projet d’extension Visual Studio (VSIX) et ajoutez un fichier .pkgdef dans l’extension. Pour plus d’informations, consultez Déploiement d’un processeur de directives personnalisé.

  2. Ajoutez des références à ces assemblys :

    • Microsoft.VisualStudio.TextTemplating.*.0

    • Microsoft.VisualStudio.TextTemplating.Interfaces.*.0

  3. Remplacez le code de Class1 par le code suivant. Ce code définit une classe CustomDirectiveProcessor qui hérite de la classe DirectiveProcessor et implémente les méthodes nécessaires.

    using System;
    using System.CodeDom;
    using System.CodeDom.Compiler;
    using System.Collections.Generic;
    using System.Globalization;
    using System.IO;
    using System.Text;
    using System.Xml;
    using System.Xml.Serialization;
    using Microsoft.VisualStudio.TextTemplating;
    
    namespace CustomDP
    {
        public class CustomDirectiveProcessor : DirectiveProcessor
        {
            // This buffer stores the code that is added to the
            // generated transformation class after all the processing is done.
            // ---------------------------------------------------------------------
            private StringBuilder codeBuffer;
    
            // Using a Code Dom Provider creates code for the
            // generated transformation class in either Visual Basic or C#.
            // If you want your directive processor to support only one language, you
            // can hard code the code you add to the generated transformation class.
            // In that case, you do not need this field.
            // --------------------------------------------------------------------------
            private CodeDomProvider codeDomProvider;
    
            // This stores the full contents of the text template that is being processed.
            // --------------------------------------------------------------------------
            private String templateContents;
    
            // These are the errors that occur during processing. The engine passes
            // the errors to the host, and the host can decide how to display them,
            // for example the host can display the errors in the UI
            // or write them to a file.
            // ---------------------------------------------------------------------
            private CompilerErrorCollection errorsValue;
            public new CompilerErrorCollection Errors
            {
                get { return errorsValue; }
            }
    
            // Each time this directive processor is called, it creates a new property.
            // We count how many times we are called, and append "n" to each new
            // property name. The property names are therefore unique.
            // -----------------------------------------------------------------------------
            private int directiveCount = 0;
    
            public override void Initialize(ITextTemplatingEngineHost host)
            {
                // We do not need to do any initialization work.
            }
    
            public override void StartProcessingRun(CodeDomProvider languageProvider, String templateContents, CompilerErrorCollection errors)
            {
                // The engine has passed us the language of the text template
                // we will use that language to generate code later.
                // ----------------------------------------------------------
                this.codeDomProvider = languageProvider;
                this.templateContents = templateContents;
                this.errorsValue = errors;
    
                this.codeBuffer = new StringBuilder();
            }
    
            // Before calling the ProcessDirective method for a directive, the
            // engine calls this function to see whether the directive is supported.
            // Notice that one directive processor might support many directives.
            // ---------------------------------------------------------------------
            public override bool IsDirectiveSupported(string directiveName)
            {
                if (string.Compare(directiveName, "CoolDirective", StringComparison.OrdinalIgnoreCase) == 0)
                {
                    return true;
                }
                if (string.Compare(directiveName, "SuperCoolDirective", StringComparison.OrdinalIgnoreCase) == 0)
                {
                    return true;
                }
                return false;
            }
    
            public override void ProcessDirective(string directiveName, IDictionary<string, string> arguments)
            {
                if (string.Compare(directiveName, "CoolDirective", StringComparison.OrdinalIgnoreCase) == 0)
                {
                    string fileName;
    
                    if (!arguments.TryGetValue("FileName", out fileName))
                    {
                        throw new DirectiveProcessorException("Required argument 'FileName' not specified.");
                    }
    
                    if (string.IsNullOrEmpty(fileName))
                    {
                        throw new DirectiveProcessorException("Argument 'FileName' is null or empty.");
                    }
    
                    // Now we add code to the generated transformation class.
                    // This directive supports either Visual Basic or C#, so we must use the
                    // System.CodeDom to create the code.
                    // If a directive supports only one language, you can hard code the code.
                    // --------------------------------------------------------------------------
    
                    CodeMemberField documentField = new CodeMemberField();
    
                    documentField.Name = "document" + directiveCount + "Value";
                    documentField.Type = new CodeTypeReference(typeof(XmlDocument));
                    documentField.Attributes = MemberAttributes.Private;
    
                    CodeMemberProperty documentProperty = new CodeMemberProperty();
    
                    documentProperty.Name = "Document" + directiveCount;
                    documentProperty.Type = new CodeTypeReference(typeof(XmlDocument));
                    documentProperty.Attributes = MemberAttributes.Public;
                    documentProperty.HasSet = false;
                    documentProperty.HasGet = true;
    
                    CodeExpression fieldName = new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), documentField.Name);
                    CodeExpression booleanTest = new CodeBinaryOperatorExpression(fieldName, CodeBinaryOperatorType.IdentityEquality, new CodePrimitiveExpression(null));
                    CodeExpression rightSide = new CodeMethodInvokeExpression(new CodeTypeReferenceExpression("XmlReaderHelper"), "ReadXml", new CodePrimitiveExpression(fileName));
                    CodeStatement[] thenSteps = new CodeStatement[] { new CodeAssignStatement(fieldName, rightSide) };
    
                    CodeConditionStatement ifThen = new CodeConditionStatement(booleanTest, thenSteps);
                    documentProperty.GetStatements.Add(ifThen);
    
                    CodeStatement s = new CodeMethodReturnStatement(fieldName);
                    documentProperty.GetStatements.Add(s);
    
                    CodeGeneratorOptions options = new CodeGeneratorOptions();
                    options.BlankLinesBetweenMembers = true;
                    options.IndentString = "    ";
                    options.VerbatimOrder = true;
                    options.BracingStyle = "C";
    
                    using (StringWriter writer = new StringWriter(codeBuffer, CultureInfo.InvariantCulture))
                    {
                        codeDomProvider.GenerateCodeFromMember(documentField, writer, options);
                        codeDomProvider.GenerateCodeFromMember(documentProperty, writer, options);
                    }
                }
    
                // One directive processor can contain many directives.
                // If you want to support more directives, the code goes here...
                // -----------------------------------------------------------------
                if (string.Compare(directiveName, "supercooldirective", StringComparison.OrdinalIgnoreCase) == 0)
                {
                    // Code for SuperCoolDirective goes here...
                }
    
                // Track how many times the processor has been called.
                // -----------------------------------------------------------------
                directiveCount++;
    
            }
    
            public override void FinishProcessingRun()
            {
                this.codeDomProvider = null;
    
                // Important: do not do this:
                // The get methods below are called after this method
                // and the get methods can access this field.
                // -----------------------------------------------------------------
                // this.codeBuffer = null;
            }
    
            public override string GetPreInitializationCodeForProcessingRun()
            {
                // Use this method to add code to the start of the
                // Initialize() method of the generated transformation class.
                // We do not need any pre-initialization, so we will just return "".
                // -----------------------------------------------------------------
                // GetPreInitializationCodeForProcessingRun runs before the
                // Initialize() method of the base class.
                // -----------------------------------------------------------------
                return String.Empty;
            }
    
            public override string GetPostInitializationCodeForProcessingRun()
            {
                // Use this method to add code to the end of the
                // Initialize() method of the generated transformation class.
                // We do not need any post-initialization, so we will just return "".
                // ------------------------------------------------------------------
                // GetPostInitializationCodeForProcessingRun runs after the
                // Initialize() method of the base class.
                // -----------------------------------------------------------------
                return String.Empty;
            }
    
            public override string GetClassCodeForProcessingRun()
            {
                //Return the code to add to the generated transformation class.
                // -----------------------------------------------------------------
                return codeBuffer.ToString();
            }
    
            public override string[] GetReferencesForProcessingRun()
            {
                // This returns the references that we want to use when
                // compiling the generated transformation class.
                // -----------------------------------------------------------------
                // We need a reference to this assembly to be able to call
                // XmlReaderHelper.ReadXml from the generated transformation class.
                // -----------------------------------------------------------------
                return new string[]
                {
                    "System.Xml",
                    this.GetType().Assembly.Location
                };
            }
    
            public override string[] GetImportsForProcessingRun()
            {
                //This returns the imports or using statements that we want to
                //add to the generated transformation class.
                // -----------------------------------------------------------------
                //We need CustomDP to be able to call XmlReaderHelper.ReadXml
                //from the generated transformation class.
                // -----------------------------------------------------------------
                return new string[]
                {
                    "System.Xml",
                    "CustomDP"
                };
            }
        }
    
        // -------------------------------------------------------------------------
        // The code that we are adding to the generated transformation class
        // will call this method.
        // -------------------------------------------------------------------------
        public static class XmlReaderHelper
        {
            public static XmlDocument ReadXml(string fileName)
            {
                XmlDocument d = new XmlDocument();
    
                using (XmlReader reader = XmlReader.Create(fileName))
                {
                    try
                    {
                        d.Load(reader);
                    }
                    catch (System.Xml.XmlException e)
                    {
                        throw new DirectiveProcessorException("Unable to read the XML file.", e);
                    }
                }
                return d;
            }
        }
    }
    
  4. Pour Visual Basic uniquement, ouvrez le menu Projet et cliquez sur Propriétés de CustomDP. Sous l’onglet Application, dans Espace de noms racine, supprimez la valeur par défaut CustomDP.

  5. Dans le menu Fichier , cliquez sur Enregistrer tout.

  6. Dans le menu Générer, cliquez sur Générer la solution.

Génération du projet

Créez le projet. Dans le menu Générer, cliquez sur Générer la solution.

Inscrire le processeur de directives

Pour pouvoir appeler une directive à partir d’un modèle de texte dans Visual Studio, vous devez ajouter une clé de Registre pour le processeur de directives.

Notes

Si vous voulez installer le processeur de directives sur plusieurs ordinateurs, définissez plutôt une extension Visual Studio (VSIX) comprenant un fichier .pkgdef avec votre assembly. Pour plus d’informations, consultez Déploiement d’un processeur de directives personnalisé.

Des clés sont disponibles pour les processeurs de directive dans le Registre à l'emplacement suivant :

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\*.0\TextTemplating\DirectiveProcessors

Pour les systèmes 64 bits, l'emplacement de Registre est :

HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\VisualStudio\*.0\TextTemplating\DirectiveProcessors

Dans cette section, vous ajoutez au Registre une clé pour votre processeur de directive personnalisé, dans le même emplacement.

Attention

Une modification incorrecte du Registre peut sérieusement endommager votre système. Avant d'apporter des modifications au Registre, sauvegardez toutes les données utiles qui se trouvent sur l'ordinateur.

Pour ajouter une clé de Registre pour le processeur de directive

  1. Exécutez la commande regedit à partir du menu Démarrer ou de la ligne de commande.

  2. Accédez à l’emplacement HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\*.0\TextTemplating\DirectiveProcessors et cliquez sur le nœud.

    Sur les systèmes 64 bits, utilisez HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\VisualStudio\*.0\TextTemplating\DirectiveProcessors

  3. Ajoutez une nouvelle clé nommée CustomDirectiveProcessor.

    Remarque

    Il s'agit du nom que vous utiliserez dans le champ Processeur de vos directives personnalisées. Ce nom ne doit pas nécessairement correspondre au nom de la directive, au nom de la classe du processeur de directive ou à l'espace de noms du processeur de directive.

  4. Ajoutez une nouvelle valeur de chaîne nommée Class ayant la valeur CustomDP.CustomDirectiveProcessor pour le nom de la nouvelle chaîne.

  5. Ajoutez une nouvelle valeur de chaîne nommée CodeBase ayant une valeur égale au chemin d’accès du fichier CustomDP.dll que vous avez créé précédemment dans cette procédure pas à pas.

    Par exemple, le chemin peut ressembler à C:\UserFiles\CustomDP\bin\Debug\CustomDP.dll.

    Votre clé de Registre doit avoir les valeurs suivantes :

    Nom Type Données
    (Par défaut) REG_SZ (valeur non définie)
    Classe REG_SZ CustomDP.CustomDirectiveProcessor
    CodeBase REG_SZ <Chemin de votre solution>CustomDP\bin\Debug\CustomDP.dll

    Si vous avez placé l'assembly dans le GAC, les valeurs doivent se présenter comme suit :

    Nom Type Données
    (Par défaut) REG_SZ (valeur non définie)
    Classe REG_SZ CustomDP.CustomDirectiveProcessor
    Assembly REG_SZ CustomDP.dll
  6. Démarrez Visual Studio.

Tester le processeur de directives

Pour tester le processeur de directive, vous devez écrire un modèle de texte qui l'appelle.

Dans cet exemple, le modèle de texte appelle la directive et passe le nom d'un fichier XML qui contient la documentation d'un fichier de classe. Le modèle de texte utilise la propriété XmlDocument que la directive crée pour naviguer dans le code XML et imprimer les commentaires de documentation.

Pour créer un fichier XML à utiliser pour le test du processeur de directive

  1. Créez un fichier nommé DocFile.xml dans l’éditeur de texte de votre choix (par exemple, le Bloc-notes).

    Remarque

    Vous pouvez créer ce fichier n’importe où (par exemple, C:\Test\DocFile.xml).

  2. Ajoutez le code suivant au fichier XML :

    <?xml version="1.0"?>
    <doc>
        <assembly>
            <name>xmlsample</name>
        </assembly>
        <members>
            <member name="T:SomeClass">
                <summary>Class level summary documentation goes here.</summary>
                <remarks>Longer comments can be associated with a type or member through the remarks tag</remarks>
            </member>
            <member name="F:SomeClass.m_Name">
                <summary>Store for the name property</summary>
            </member>
            <member name="M:SomeClass.#ctor">
                <summary>The class constructor.</summary>
            </member>
            <member name="M:SomeClass.SomeMethod(System.String)">
                <summary>Description for SomeMethod.</summary>
                <param name="s">Parameter description for s goes here</param>
                <seealso cref="T:System.String">You can use the cref attribute on any tag to reference a type or member and the compiler will check that the reference exists.</seealso>
            </member>
            <member name="M:SomeClass.SomeOtherMethod">
                <summary>Some other method.</summary>
                <returns>Return results are described through the returns tag.</returns>
                <seealso cref="M:SomeClass.SomeMethod(System.String)">Notice the use of the cref attribute to reference a specific method</seealso>
            </member>
            <member name="M:SomeClass.Main(System.String[])">
                <summary>The entry point for the application.</summary>
                <param name="args">A list of command line arguments</param>
            </member>
            <member name="P:SomeClass.Name">
                <summary>Name property</summary>
                <value>A value tag is used to describe the property value</value>
            </member>
        </members>
    </doc>
    
  3. Enregistrez et fermez le fichier.

Pour créer un modèle de texte de façon à tester le processeur de directive

  1. Dans Visual Studio, créez un projet de bibliothèque de classes C# ou Visual Basic nommé TemplateTest.

  2. Ajoutez un nouveau fichier modèle de texte nommé TestDP.tt.

  3. Vérifiez que la propriété Outil personnalisé de TestDP.tt est définie sur TextTemplatingFileGenerator.

  4. Remplacez le contenu de TestDP.tt par le texte suivant.

    Notes

    Remplacez la chaîne <YOUR PATH> par le chemin du fichier DocFile.xml.

    Le langage du modèle de texte ne doit pas nécessairement correspondre à celui du processeur de directive.

    <#@ assembly name="System.Xml" #>
    <#@ template debug="true" #>
    <#@ output extension=".txt" #>
    
    <#  // This will call the custom directive processor. #>
    <#@ CoolDirective Processor="CustomDirectiveProcessor" FileName="<YOUR PATH>\DocFile.xml" #>
    
    <#  // Uncomment this line if you want to see the generated transformation class. #>
    <#  // System.Diagnostics.Debugger.Break(); #>
    
    <#  // This will use the results of the directive processor. #>
    <#  // The directive processor has read the XML and stored it in Document0. #>
    <#
        XmlNode node = Document0.DocumentElement.SelectSingleNode("members");
    
        foreach (XmlNode member in node.ChildNodes)
        {
            XmlNode name = member.Attributes.GetNamedItem("name");
            WriteLine("{0,7}:  {1}", "Name", name.Value);
    
            foreach (XmlNode comment in member.ChildNodes)
            {
                WriteLine("{0,7}:  {1}", comment.Name, comment.InnerText);
            }
            WriteLine("");
        }
    #>
    
    <# // You can call the directive processor again and pass it a different file. #>
    <# // @ CoolDirective Processor="CustomDirectiveProcessor" FileName="<YOUR PATH>\<Your Second File>" #>
    
    <#  // To use the results of the second directive call, use Document1. #>
    <#
        // XmlNode node2 = Document1.DocumentElement.SelectSingleNode("members");
    
        // ...
    #>
    

    Notes

    Dans cet exemple, la valeur du paramètre Processor est CustomDirectiveProcessor. La valeur du paramètre Processor doit correspondre au nom de la clé de Registre du processeur.

  5. Dans le menu Fichier, choisissez Tout enregistrer.

Pour tester le processeur de directive

  1. Dans l’Explorateur de solutions, cliquez avec le bouton droit sur TestDP.tt, puis cliquez sur Exécuter un outil personnalisé.

    Pour les utilisateurs de Visual Basic, TestDP.txt peut ne pas s’afficher dans l’Explorateur de solutions par défaut. Pour afficher tous les fichiers attribués au projet, ouvrez le menu Projet et cliquez sur Afficher tous les fichiers.

  2. Dans l’Explorateur de solutions, développez le nœud TestDP.txt, puis double-cliquez sur TestDP.txt pour l’ouvrir dans l’éditeur.

    La sortie de texte générée s'affiche. La sortie doit se présenter comme suit :

       Name:  T:SomeClass
    summary:  Class level summary documentation goes here.
    remarks:  Longer comments can be associated with a type or member through the remarks tag
    
       Name:  F:SomeClass.m_Name
    summary:  Store for the name property
    
       Name:  M:SomeClass.#ctor
    summary:  The class constructor.
    
       Name:  M:SomeClass.SomeMethod(System.String)
    summary:  Description for SomeMethod.
      param:  Parameter description for s goes here
    seealso:  You can use the cref attribute on any tag to reference a type or member and the compiler will check that the reference exists.
    
       Name:  M:SomeClass.SomeOtherMethod
    summary:  Some other method.
    returns:  Return results are described through the returns tag.
    seealso:  Notice the use of the cref attribute to reference a specific method
    
       Name:  M:SomeClass.Main(System.String[])
    summary:  The entry point for the application.
      param:  A list of command line arguments
    
       Name:  P:SomeClass.Name
    summary:  Name property
      value:  A value tag is used to describe the property value
    

Ajouter du code HTML au texte généré

Après avoir testé votre processeur de directive personnalisé, vous pouvez ajouter du code HTML au texte généré.

Pour ajouter du code HTML au texte généré

  1. Remplacez le code de TestDP.txt par le code suivant. Le code HTML est mis en surbrillance. Veillez à remplacer la chaîne YOUR PATH par le chemin du fichier DocFile.xml.

    Notes

    Des étiquettes ouvrantes <# et fermantes #> supplémentaires séparent le code des instructions et les étiquettes HTML.

    <#@ assembly name="System.Xml" #>
    <#@ template debug="true" #>
    <#@ output extension=".htm" #>
    
    <#  // This will call the custom directive processor #>
    <#@ CoolDirective Processor="CustomDirectiveProcessor" FileName="<YOUR PATH>\DocFile.xml" #>
    
    <#  // Uncomment this line if you want to see the generated transformation class #>
    <#  // System.Diagnostics.Debugger.Break(); #>
    
    <html><body>
    
    <#  // This will use the results of the directive processor #>.
    <#  // The directive processor has read the XML and stored it in Document0#>.
    <#
        XmlNode node = Document0.DocumentElement.SelectSingleNode("members");
    
        foreach (XmlNode member in node.ChildNodes)
        {
    #>
    <h3>
    <#
            XmlNode name = member.Attributes.GetNamedItem("name");
            WriteLine("{0,7}:  {1}", "Name", name.Value);
    #>
    </h3>
    <#
            foreach (XmlNode comment in member.ChildNodes)
            {
                WriteLine("{0,7}:  {1}", comment.Name, comment.InnerText);
    #>
    <br/>
    <#
            }
        }
    #>
    </body></html>
    
  2. Dans le menu Fichier, cliquez sur Enregistrer TestDP.txt.

  3. Pour voir la sortie dans un navigateur, dans l’Explorateur de solutions, cliquez avec le bouton droit sur TestDP.htm, puis cliquez sur Voir dans le navigateur.

    Votre sortie doit être identique au texte d’origine, avec le format HTML appliqué. Chaque nom d’élément s’affiche en gras.