Návod: Vytvoření vlastního procesoru direktiv
Procesory direktiv fungují přidáním kódu do vygenerované transformační třídy. Pokud voláte direktivu z textové šablony, zbytek kódu, který píšete v textové šabloně, může záviset na funkcích, které direktiva poskytuje.
Můžete si vytvořit své vlastní procesory direktiv. To vám pak umožní textové šablony přizpůsobit. Chcete-li vytvořit vlastní procesor direktiv, vytvoříte třídu, která dědí z nebo DirectiveProcessor RequiresProvidesDirectiveProcessor.
Mezi úlohy popsané v tomto návodu patří:
Vytvoření vlastního procesoru direktiv
Registrace procesoru direktiv
Testování procesoru direktiv
Vytvoření vlastního procesoru direktiv
V tomto návodu vytvoříte vlastní procesor direktiv. Přidáte vlastní direktivu, která čte soubor XML, uloží ho XmlDocument do proměnné a zpřístupní ji prostřednictvím vlastnosti. V části „Testování procesoru direktiv“ získáte pomocí této vlastnosti v textové šabloně přístup k tomuto souboru XML.
Volání vlastní direktivy vypadá následovně:
<#@ CoolDirective Processor="CustomDirectiveProcessor" FileName="<Your Path>DocFile.xml" #>
Vlastní procesor direktiv přidá proměnnou a vlastnost do vygenerované třídy transformace. Direktiva, kterou napíšete, používá System.CodeDom třídy k vytvoření kódu, který modul přidá do vygenerované transformační třídy. Třídy System.CodeDom vytvářejí kód v jazyce Visual C# nebo Visual Basic v závislosti na jazyce určeném language
v parametru direktivy template
. Jazyk procesoru direktiv a jazyk textové šablony, ke které tento procesor direktiv přistupuje, se nemusejí shodovat.
Kód, který direktiva vytvoří, vypadá takto:
private System.Xml.XmlDocument document0Value;
public virtual System.Xml.XmlDocument Document0
{
get
{
if ((this.document0Value == null))
{
this.document0Value = XmlReaderHelper.ReadXml(<FileNameParameterValue>);
}
return this.document0Value;
}
}
Vytvoření vlastního procesoru direktiv
V sadě Visual Studio vytvořte projekt knihovny tříd jazyka C# nebo Visual Basic s názvem CustomDP.
Poznámka:
Pokud chcete procesor direktiv nainstalovat na více než jeden počítač, je lepší použít projekt rozšíření sady Visual Studio (VSIX) a zahrnout do přípony soubor .pkgdef. Další informace naleznete v tématu Nasazení vlastního procesoru direktiv.
Přidejte odkazy na tato sestavení:
Microsoft.VisualStudio.TextTemplating.*.0
Microsoft.VisualStudio.TextTemplating.Interfaces.*.0
Nahraďte kód ve třídě 1 následujícím kódem. Tento kód definuje CustomDirectiveProcessor třídy, která dědí z DirectiveProcessor třídy a implementuje nezbytné metody.
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; } } }
Pouze v jazyce Visual Basic otevřete nabídku Projekt a klepněte na příkaz Vlastní vlastnostiDP. Na kartě Aplikace v kořenovém oboru názvů odstraňte výchozí hodnotu .
CustomDP
V nabídce File (Soubor) klikněte na Save All (Uložit vše).
V nabídce Sestavení klikněte na Sestavit řešení.
Sestavení projektu
Sestavte projekt. V nabídce Sestavení klikněte na Sestavit řešení.
Registrace procesoru direktiv
Než budete moct volat direktivu z textové šablony v sadě Visual Studio, musíte pro procesor direktiv přidat klíč registru.
Poznámka:
Pokud chcete procesor direktiv nainstalovat na více než jeden počítač, je lepší definovat rozšíření sady Visual Studio (VSIX), které obsahuje soubor .pkgdef spolu s sestavením. Další informace naleznete v tématu Nasazení vlastního procesoru direktiv.
Klíče procesoru direktiv se v registru nacházejí na následujícím místě:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\*.0\TextTemplating\DirectiveProcessors
64bitové systémy používají toto místo v registru:
HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\VisualStudio\*.0\TextTemplating\DirectiveProcessors
V tomto oddílu přidáte na stejné místo v registru klíč pro vlastní procesor direktiv.
Upozornění
Nesprávná úprava registru může vážně poškodit systém. Před prováděním změn registru zazálohujte všechna cenná data v počítači.
Přidání klíče registru pro procesor direktiv
regedit
Spusťte příkaz pomocí nabídka Start nebo příkazového řádku.Přejděte do umístění HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\*.0\TextTemplating\DirectiveProcessors a klikněte na uzel.
V 64bitových systémech použijte HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\VisualStudio\*.0\TextTemplating\DirectiveProcessors
Přidejte nový klíč s názvem CustomDirectiveProcessor.
Poznámka:
Jedná se o název, který budete používat v poli Processor vlastních direktiv. Tento název se nemusí shodovat s názvem direktivy, názvem třídy procesoru direktiv ani s oborem názvů procesoru direktiv.
Přidejte novou řetězcovou hodnotu s názvem Class, která má pro název nového řetězce hodnotu CustomDP.CustomDirectiveProcessor.
Přidejte novou řetězcovou hodnotu s názvem CodeBase, jejíž hodnota se shoduje s cestou k souboru CustomDP.dll, který jste vytvořili dříve v tomto návodu.
Například cesta může vypadat nějak
C:\UserFiles\CustomDP\bin\Debug\CustomDP.dll
takto.Klíč registru by měl mít následující hodnoty:
Name Type Data (Výchozí) REG_SZ (hodnota nenastavena) Třída REG_SZ CustomDP.CustomDirectiveProcessor CodeBase REG_SZ <Cesta k vašemu řešení>CustomDP\bin\Debug\CustomDP.dll Pokud jste sestavení vložili do mezipaměti GAC, měly by tyto hodnoty vypadat takto:
Name Type Data (Výchozí) REG_SZ (hodnota nenastavena) Třída REG_SZ CustomDP.CustomDirectiveProcessor Sestavení REG_SZ CustomDP.dll Restartujte Visual Studio.
Testování procesoru direktiv
Při testování procesoru direktiv potřebujete napsat textovou šablonu, která ho zavolá.
V tomto příkladu volá textová šablona direktivu a předá název souboru XML, který obsahuje dokumentaci pro soubor třídy. Textová šablona používá XmlDocument vlastnost, kterou direktiva vytvoří, k navigaci xml a tisku komentářů dokumentace.
Vytvoření souboru XML pro testování procesoru direktiv
Pomocí libovolného textového editoru (například Poznámkový blok) vytvořte soubor DocFile.xml.
Poznámka:
Tento soubor můžete vytvořit v libovolném umístění (například C:\Test\DocFile.xml).
Do souboru XML přidejte následující kód:
<?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>
Soubor uložte a zavřete.
Vytvoření textové šablony pro testování procesoru direktiv
V sadě Visual Studio vytvořte projekt knihovny tříd jazyka C# nebo Visual Basic s názvem TemplateTest.
Přidejte nový soubor textové šablony s názvem TestDP.tt.
Ujistěte se, že vlastnost Vlastní nástroj TestDP.tt je nastavena na
TextTemplatingFileGenerator
hodnotu .Změňte obsah souboru TestDP.tt na následující text.
Poznámka:
Nahraďte řetězec
<YOUR PATH>
cestou k souboru DocFile.xml .Jazyk textové šablony se nemusí shodovat s jazykem procesoru direktiv.
<#@ 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"); // ... #>
Poznámka:
V tomto příkladu je
CustomDirectiveProcessor
hodnota parametruProcessor
. Hodnota parametruProcessor
se musí shodovat s názvem klíče registru procesoru.V nabídce Soubor zvolte Uložit vše.
Testování procesoru direktiv
V Průzkumník řešení klepněte pravým tlačítkem myši na TestDP.tt a potom klepněte na tlačítko Spustit vlastní nástroj.
U uživatelů jazyka Visual Basic se ve výchozím nastavení nemusí testDP.txt zobrazovat v Průzkumník řešení. Pokud chcete zobrazit všechny soubory přiřazené k projektu, otevřete nabídku Projekt a klikněte na Zobrazit všechny soubory.
V Průzkumník řešení rozbalte uzel TestDP.txt a poklikáním na TestDP.txt ho otevřete v editoru.
Zobrazí se vygenerovaný textový výstup. Výstup by měl vypadat takto:
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
Přidání KÓDU HTML do generovaného textu
Po otestování vlastního procesoru direktiv můžete k vygenerovanému textu přidat kód HTML.
Přidání kódu HTML k vygenerovanému textu
Nahraďte kód v TestDP.tt následujícím kódem. Kód HTML je zvýrazněn. Nezapomeňte nahradit řetězec
YOUR PATH
cestou k souboru DocFile.xml .Poznámka:
Další otevřené <# a zavřít značky #> odděluje kód příkazu od značek 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>
V nabídce Soubor klepněte na tlačítko Uložit TestDP.txt.
Chcete-li zobrazit výstup v prohlížeči, v Průzkumník řešení klikněte pravým tlačítkem na TestDP.htm a klepněte na příkaz Zobrazit v prohlížeči.
Výstup by měl být stejný jako původní text s tím rozdílem, že má použitý formát HTML. Název každé položky se zobrazí tučně.