Zugreifen auf Modelle aus Textvorlagen
Mithilfe von Textvorlagen können Sie Berichtsdateien, Quellcodedateien und andere Textdateien erstellen, die auf domänenspezifischen Sprachmodellen basieren. Grundlegende Informationen zu Textvorlagen finden Sie unter Codegenerierung und T4-Textvorlagen. Die Textvorlagen funktionieren im experimentellen Modus, wenn Sie Ihre DSL debuggen, und sie funktionieren auch auf einem Computer, auf dem Sie die DSL bereitgestellt haben.
Hinweis
Wenn Sie eine DSL-Lösung erstellen, werden *.tt-Beispieltextvorlagen im Debuggingprojekt generiert. Wenn Sie die Namen der Domänenklassen ändern, funktionieren diese Vorlagen nicht mehr. Dennoch enthalten sie die grundlegenden Richtlinien, die Sie benötigen, und liefern Beispiele, die Sie an Ihre DSL anpassen können.
So greifen Sie über eine Textvorlage auf ein Modell zu:
Legen Sie die Erbeigenschaft der Vorlagenanweisung auf Microsoft.VisualStudio.TextTemplating.VSHost.ModelingTextTransformation fest. Dadurch erhalten Sie Zugriff auf den Speicher.
Geben Sie Anweisungsprozessoren für die DSL an, auf die Sie zugreifen möchten. Dadurch werden die Assemblys für Ihre DSL geladen, sodass Sie ihre Domänenklassen, Eigenschaften und Beziehungen im Code Ihrer Textvorlage verwenden können. Außerdem wird die von Ihnen angegebene Modelldatei geladen.
Eine
.tt
-Datei ähnlich dem folgenden Beispiel wird im Debuggingprojekt erstellt, wenn Sie eine neue Visual Studio-Projektmappe aus der DSL-Minimalsprachenvorlage erstellen.
<#@ template inherits="Microsoft.VisualStudio.TextTemplating.VSHost.ModelingTextTransformation" #>
<#@ output extension=".txt" #>
<#@ MyLanguage processor="MyLanguageDirectiveProcessor" requires="fileName='Sample.myDsl1'" #>
This text will be output directly.
This is the name of the model: <#= this.ModelRoot.Name #>
Here is a list of elements in the model:
<#
// When you change the DSL Definition, some of the code below may not work.
foreach (ExampleElement element in this.ExampleModel.Elements)
{#>
<#= element.Name #>
<#
}
#>
Beachten Sie die folgenden Punkte zu dieser Vorlage:
Die Vorlage kann die Domänenklassen, Eigenschaften und Beziehungen verwenden, die Sie in der DSL-Definition definiert haben.
Die Vorlage lädt die Modelldatei, die Sie in der Eigenschaft
requires
angeben.Eine Eigenschaft in
this
enthält das Stammelement. Von dort aus kann Ihr Code zu anderen Elementen des Modells navigieren. Der Name der Eigenschaft ist in der Regel identisch mit der Stammdomänenklasse Ihrer DSL. In diesem Beispiel lautet erthis.ExampleModel
.Obwohl die Codefragmente in der Sprache C# geschrieben sind, können Sie jede Art von Text generieren. Alternativ können Sie den Code in Visual Basic schreiben, indem Sie der
template
-Anweisung die Eigenschaftlanguage="VB"
hinzufügen.Um die Vorlage zu debuggen, fügen Sie
debug="true"
zurtemplate
-Anweisung hinzu. Die Vorlage wird in einer anderen Instanz von Visual Studio geöffnet, wenn eine Ausnahme auftritt. Wenn Sie an einer bestimmten Stelle des Codes in den Debugger wechseln möchten, fügen Sie die AnweisungSystem.Diagnostics.Debugger.Break();
ein.Weitere Informationen finden Sie unter Debuggen einer T4-Textvorlage.
Informationen zum DSL-Anweisungsprozessor
Die Vorlage kann die Domänenklassen verwenden, die Sie in Ihrer DSL-Definition definiert haben. Dies wird durch eine Anweisung bewirkt, die normalerweise am Anfang der Vorlage steht. Im vorherigen Beispiel ist dies die folgende.
<#@ MyLanguage processor="MyLanguageDirectiveProcessor" requires="fileName='Sample.myDsl1'" #>
Der Name der Anweisung (in diesem Beispiel MyLanguage
) wird vom Namen Ihrer DSL abgeleitet. Sie ruft einen Anweisungsprozessor auf, der als Teil Ihrer DSL generiert wird. Den Quellcode finden Sie unter Dsl\GeneratedCode\DirectiveProcessor.cs.
Der DSL-Anweisungsprozessor führt zwei Hauptaufgaben aus:
Er fügt Assembly- und Importanweisungen effektiv in die Vorlage ein, die auf Ihre DSL verweist. Auf diese Weise können Sie Ihre Domänenklassen im Vorlagencode verwenden.
Sie lädt die Datei, die Sie im Parameter
requires
angeben, und legt eine Eigenschaft inthis
fest, die auf das Stammelement des geladenen Modells verweist.
Überprüfen des Modells vor dem Ausführen der Vorlage
Sie können das Modell überprüfen lassen, bevor die Vorlage ausgeführt wird.
<#@ MyLanguage processor="MyLanguageDirectiveProcessor" requires="fileName='Sample.myDsl1';validation='open|load|save|menu'" #>
Beachten Sie Folgendes:
Die Parameter
filename
undvalidation
werden durch „;“ getrennt, und es dürfen keine anderen Trennzeichen oder Leerzeichen vorhanden sein.Die Liste der Überprüfungskategorien bestimmt, welche Überprüfungsmethoden ausgeführt werden sollen. Mehrere Kategorien sollten durch „|“ getrennt werden, und es dürfen keine anderen Trennzeichen oder Leerzeichen vorhanden sein.
Wenn ein Fehler gefunden wird, wird er im Fehlerfenster gemeldet, und die Ergebnisdatei enthält eine Fehlermeldung.
Zugreifen auf mehrere Modelle aus einer Textvorlage
Hinweis
Mit dieser Methode können Sie mehrere Modelle in derselben Vorlage lesen, aber sie unterstützt keine ModelBus-Referenzen. Informationen zum Lesen von Modellen, die mit ModelBus-Verweisen verknüpft sind, finden Sie unter Verwenden von Visual Studio ModelBus in einer Textvorlage.
Wenn Sie von derselben Textvorlage aus auf mehrere Modelle zugreifen möchten, müssen Sie den generierten Anweisungsprozessor für jedes Modell ein Mal aufrufen. Sie müssen den Dateinamen jedes Modells im Parameter requires
angeben. Sie müssen die Namen, die Sie für die Stammdomänenklasse verwenden möchten, im Parameter provides
angeben. Sie müssen in jedem der Anweisungsaufrufe unterschiedliche Werte für die provides
-Parameter angeben. Angenommen, Sie haben drei Modelldateien mit den Namen Library.xyz, School.xyz und Work.xyz. Um von derselben Textvorlage aus auf sie zuzugreifen, müssen Sie drei Anweisungsaufrufe schreiben, die den folgenden ähneln.
<#@ ExampleModel processor="<YourLanguageName>DirectiveProcessor" requires="fileName='Library.xyz'" provides="ExampleModel=LibraryModel" #>
<#@ ExampleModel processor="<YourLanguageName>DirectiveProcessor" requires="fileName='School.xyz'" provides="ExampleModel=SchoolModel" #>
<#@ ExampleModel processor="<YourLanguageName>DirectiveProcessor" requires="fileName='Work.xyz'" provides="ExampleModel=WorkModel" #>
Hinweis
Dieser Beispielcode ist für eine Sprache, die auf der Minimalsprachen-Lösungsvorlage basiert.
Um auf die Modelle in Ihrer Textvorlage zuzugreifen, können Sie nun Code schreiben, der dem Code im folgenden Beispiel ähnelt.
<#
foreach (ExampleElement element in this.LibraryModel.Elements)
...
foreach (ExampleElement element in this.SchoolModel.Elements)
...
foreach (ExampleElement element in this.WorkModel.Elements)
...
#>
Dynamisches Laden von Modellen
Wenn Sie zur Laufzeit bestimmen möchten, welche Modelle geladen werden sollen, können Sie eine Modelldatei dynamisch in Ihren Programmcode laden, anstatt die DSL-spezifische Anweisung zu verwenden.
Eine der Funktionen der DSL-spezifischen Anweisung besteht jedoch darin, den DSL-Namespace zu importieren, sodass der Vorlagencode die in dieser DSL definierten Domänenklassen verwenden kann. Da Sie die Anweisung nicht verwenden, müssen Sie die Anweisungen <assembly> und <import> für alle Modelle hinzufügen, die Sie möglicherweise laden. Das ist einfach, wenn die verschiedenen Modelle, die Sie laden können, alle Instanzen derselben DSL sind.
Zum Laden der Datei verwendet Visual Studio ModelBus die effektivste Methode. In einem typischen Szenario wird Ihre Textvorlage eine DSL-spezifische Anweisung verwenden, um das erste Modell auf die übliche Weise zu laden. Dieses Modell würde ModelBus-Verweise auf ein anderes Modell enthalten. Sie können eine ModelBus-Instanz verwenden, um das Modell zu öffnen, auf das verwiesen wird, und auf ein bestimmtes Element zuzugreifen. Weitere Informationen finden Sie unter Verwenden von Visual Studio ModelBus in einer Textvorlage.
In einem weniger üblichen Szenario sollten Sie eine Modelldatei öffnen, für die Sie nur einen Dateinamen haben und die möglicherweise nicht im aktuellen Visual Studio-Projekt enthalten ist. In diesem Fall können Sie die Datei mithilfe der in Öffnen eines Modells aus einer Datei im Programmcode beschriebenen Verfahren öffnen.
Generieren mehrerer Dateien aus einer Vorlage
Wenn Sie mehrere Dateien generieren möchten (z. B. um für jedes Element in einem Modell eine eigene Datei zu generieren) gibt es mehrere mögliche Ansätze. Standardmäßig wird nur eine Datei aus jeder Vorlagendatei erstellt.
Teilen einer langen Datei
In dieser Methode verwenden Sie eine Vorlage, um eine einzelne Datei zu generieren, die durch ein Trennzeichen getrennt ist. Dann teilen Sie die Datei in ihre Teile auf. Es gibt zwei Vorlagen, eine für die Erstellung einer einzigen Datei und eine für die Aufteilung.
LoopTemplate.t4 generiert die lange einzelne Datei. Beachten Sie, dass die Dateierweiterung „.t4“ lautet, da sie nicht direkt verarbeitet werden sollte, wenn Sie auf Alle Vorlagen transformieren klicken. Diese Vorlage verwendet einen Parameter, der die Trennzeichen-Zeichenfolge angibt, die die Segmente trennt:
<#@ template inherits="Microsoft.VisualStudio.TextTemplating.VSHost.ModelingTextTransformation" #>
<#@ parameter name="delimiter" type="System.String" #>
<#@ output extension=".txt" #>
<#@ MyDSL processor="MyDSLDirectiveProcessor" requires="fileName='SampleModel.mydsl1';validation='open|load|save|menu'" #>
<#
// Create a file segment for each element:
foreach (ExampleElement element in this.ExampleModel.Elements)
{
// First item is the delimiter:
#>
<#= string.Format(delimiter, element.Id) #>
Element: <#= element.Name #>
<#
// Here you generate more content derived from the element.
}
#>
LoopSplitter.tt
ruft LoopTemplate.t4
auf und teilt dann die entstehende Datei in ihre Segmente auf. Beachten Sie, dass diese Vorlage keine Modellierungsvorlage sein muss, da sie das Modell nicht liest.
<#@ template hostspecific="true" language="C#" #>
<#@ output extension=".txt" #>
<#@ import namespace="Microsoft.VisualStudio.TextTemplating" #>
<#@ import namespace="System.Runtime.Remoting.Messaging" #>
<#@ import namespace="System.IO" #>
<#
// Get the local path:
string itemTemplatePath = this.Host.ResolvePath("LoopTemplate.t4");
string dir = Path.GetDirectoryName(itemTemplatePath);
// Get the template for generating each file:
string loopTemplate = File.ReadAllText(itemTemplatePath);
Engine engine = new Engine();
// Pass parameter to new template:
string delimiterGuid = Guid.NewGuid().ToString();
string delimiter = "::::" + delimiterGuid + ":::";
CallContext.LogicalSetData("delimiter", delimiter + "{0}:::");
string joinedFiles = engine.ProcessTemplate(loopTemplate, this.Host);
string [] separateFiles = joinedFiles.Split(new string [] {delimiter}, StringSplitOptions.None);
foreach (string nameAndFile in separateFiles)
{
if (string.IsNullOrWhiteSpace(nameAndFile)) continue;
string[] parts = nameAndFile.Split(new string[]{":::"}, 2, StringSplitOptions.None);
if (parts.Length < 2) continue;
#>
Generate: [<#= dir #>] [<#= parts[0] #>]
<#
// Generate a file from this item:
File.WriteAllText(Path.Combine(dir, parts[0] + ".txt"), parts[1]);
}
#>