Accès aux modèles à partir de modèles de texte

À l’aide de modèles de texte, vous pouvez créer des fichiers de rapport, des fichiers de code source et d’autres fichiers texte basés sur des modèles de langage dédié. Pour plus d’informations sur les modèles de texte, consultez Génération de code et modèles de texte T4. Les modèles de texte fonctionnent en mode expérimental lors du débogage du langage dédié. Ils sont également utilisables sur un ordinateur sur lequel le langage dédié a été déployé.

Notes

Lorsque vous créez une solution de langage dédié, des exemples de fichiers *.tt de modèle de texte sont générés dans le projet de débogage. Ces modèles ne fonctionnent plus dès lors que le nom des classes de domaine a été modifié. Ils incluent néanmoins les directives de base dont vous avez besoin et donnent des exemples que vous pouvez mettre à jour en fonction de votre langage dédié.

Pour accéder à un modèle à partir d’un modèle de texte, procédez comme suit :

  • Définissez la propriété inherits de la directive de modèle sur Microsoft.VisualStudio.TextTemplating.VSHost.ModelingTextTransformation. Cela permet d’accéder au Store.

  • Spécifiez des processeurs de directives pour le langage dédié auquel vous souhaitez accéder. Cela permet de charger les assemblys de votre langage dédié et donc d’utiliser ses classes de domaine, ses propriétés et ses relations dans le code du modèle de texte. Le fichier de modèle spécifié est également chargé.

    Un fichier .tt similaire à l’exemple suivant est créé dans le projet Débogage lorsque vous générez une solution Visual Studio à partir du modèle de langage dédié Langage minimal.

<#@ 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 #>
<#
  }
#>

Notez les points suivants sur ce modèle :

  • Le modèle peut utiliser les classes de domaine, les propriétés et les relations que vous avez spécifiées dans la définition du langage dédié.

  • Le modèle de texte charge le fichier de modèle spécifié dans la propriété requires.

  • Une propriété située dans this contient l’élément racine. À partir de là, votre code peut accéder à d’autres éléments du modèle. La propriété porte généralement le même nom que la classe de domaine racine du langage dédié. Dans cet exemple, il s’agit de this.ExampleModel.

  • Bien que les fragments de code soient écrits en C#, vous pouvez générer du texte de n’importe quel type. Vous avez également la possibilité d’intégrer du code en Visual Basic en ajoutant la propriété language="VB" à la directive template.

  • Pour déboguer le modèle, ajoutez debug="true" à la directive template. Le modèle s’ouvre dans une autre instance de Visual Studio en cas d’exception. Si vous souhaitez entrer dans le débogueur à un point spécifique du code, insérez l’instruction System.Diagnostics.Debugger.Break();.

    Pour plus d’informations, consultez Débogage d’un modèle de texte T4.

À propos du processeur de directives du langage dédié

Le modèle peut utiliser les classes de domaine spécifiées dans la définition du langage dédié, grâce à une directive qui apparaît généralement au début du modèle. Celle de l’exemple précédent se présente comme suit.

<#@ MyLanguage processor="MyLanguageDirectiveProcessor" requires="fileName='Sample.myDsl1'" #>

Le nom de la directive (MyLanguage dans cet exemple) est dérivé du nom du langage dédié. Elle appelle un processeur de directives généré dans le cadre du langage dédié. Vous trouverez son code source dans Dsl\GeneratedCode\DirectiveProcessor.cs.

Le processeur de directives du langage dédié effectue deux tâches principales :

  • Il insère des directives assembly et import dans le modèle qui fait référence au langage dédié. Vous pouvez ainsi utiliser vos classes de domaine dans le code du modèle.

  • Il charge le fichier spécifié dans le paramètre requires et définit une propriété dans this qui fait référence à l’élément racine du modèle chargé.

Validation du modèle avant exécution du modèle de texte

Vous pouvez faire valider le modèle avant l’exécution du modèle de texte.

<#@ MyLanguage processor="MyLanguageDirectiveProcessor" requires="fileName='Sample.myDsl1';validation='open|load|save|menu'" #>

Notez que :

  1. Les paramètres filename et validation sont séparés par « ; ». Il ne doit pas y avoir d’autres séparateurs ni d’espaces.

  2. La liste des catégories de validation détermine les méthodes de validation qui seront exécutées. Les catégories doivent être séparées par « | ». Il ne doit pas y avoir d’autres séparateurs ni d’espaces.

    Si une erreur est détectée, elle est signalée dans la fenêtre erreurs. Le fichier de résultat contient par ailleurs un message d’erreur.

Accès à plusieurs modèles à partir d’un modèle de texte

Notes

Cette méthode vous permet de lire plusieurs modèles dans le même modèle de texte, mais ne prend pas en charge les références ModelBus. Pour lire les modèles interconnectés par des références ModelBus, consultez Utilisation de Visual Studio ModelBus dans un modèle de texte.

Si vous souhaitez accéder à plusieurs modèles à partir du même modèle de texte, vous devez appeler le processeur de directives généré une fois par modèle. Spécifiez le nom de fichier de chaque modèle dans le paramètre requires, et les noms que vous souhaitez utiliser pour la classe de domaine racine dans le paramètre provides. La valeur des paramètres provides doit être différente dans chacun des appels de directive. Prenons par exemple trois fichiers de modèle appelés Library.xyz, School.xyz et Work.xyz. Pour y accéder à partir du même modèle de texte, vous devez écrire trois appels de directive :

<#@ 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" #>

Notes

Cet exemple de code concerne un langage basé sur le modèle de solution Langage minimal.

Pour accéder aux modèles de votre modèle de texte, vous pouvez maintenant écrire du code similaire à l’exemple suivant.

<#
foreach (ExampleElement element in this.LibraryModel.Elements)
...
foreach (ExampleElement element in this.SchoolModel.Elements)
...
foreach (ExampleElement element in this.WorkModel.Elements)
...
#>

Chargement dynamique des modèles

Si vous souhaitez déterminer à l’exécution les modèles à charger, vous pouvez, au lieu d’utiliser la directive propre au langage dédié, charger dynamiquement un fichier de modèle dans le code de votre programme.

Toutefois, l’une des fonctions de la directive propre au langage dédié consiste à importer l’espace de noms du langage dédié, afin que le code de modèle puisse utiliser les classes de domaine définies dans ce langage dédié. Étant donné que vous n’utilisez pas cette directive, vous devez ajouter des directives <assembly> et <import> pour tous les modèles susceptibles d’être chargés. L’opération est facile si ceux-ci constituent tous des instances du même langage dédié.

La méthode la plus efficace pour charger le fichier consiste à utiliser Visual Studio ModelBus. Dans un scénario classique, le modèle de texte emploie une directive propre au langage dédié pour charger le premier modèle de la manière habituelle. Ce modèle contient alors des références ModelBus à un autre modèle. Vous pouvez vous servir de ModelBus pour ouvrir le modèle cité et accéder à un élément en particulier. Pour plus d’informations, consultez Utilisation de Visual Studio ModelBus dans un modèle de texte.

Un scénario moins habituel consiste à ouvrir un fichier modèle pour lequel vous ne disposez que d’un nom de fichier et qui ne figure pas nécessairement dans le projet Visual Studio actuel. Dans ce cas, vous pouvez ouvrir le fichier selon la technique décrite dans Guide pratique : Ouverture d’un modèle à partir d’un fichier dans le code du programme.

Génération de plusieurs fichiers à partir d’un modèle

Si vous souhaitez générer plusieurs fichiers (par exemple un fichier distinct pour chaque élément d’un modèle), il existe plusieurs approches possibles. Par défaut, un seul fichier est produit à partir de chaque fichier de modèle.

Fractionnement d’un fichier long

Selon cette méthode, un modèle est utilisé pour générer un seul fichier, séparé par un délimiteur. Le fichier est ensuite fractionné en plusieurs parties. Il existe deux modèles : l’un pour générer le fichier unique, l’autre pour le fractionner.

LoopTemplate.t4 génère le fichier unique long. Notez que son extension de fichier est « .t4 », car il ne doit pas être traité directement lorsque vous cliquez sur Transformer tous les modèles. Ce modèle prend un paramètre, qui spécifie la chaîne de délimiteur séparant les segments :

<#@ 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 appelle LoopTemplate.t4, puis fractionne en segments le fichier qui en résulte. Notez que ce modèle de texte n’a pas besoin de servir pour la modélisation, car il ne lit pas le modèle.

<#@ 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]);
  }
#>