Acceder a modelos desde plantillas de texto

Mediante plantillas de texto, puede crear archivos de informe, archivos de código fuente y otros archivos de texto basados en modelos de lenguaje específicos de dominio. Para información básica sobre las plantillas de texto, consulte Generación de código y plantillas de texto T4. Las plantillas de texto funcionarán en el modo experimental al depurar el DSL y también funcionarán en un equipo en el que haya implementado el DSL.

Nota:

Al crear una solución DSL, se generan archivos *.tt de plantilla de texto de ejemplo en el proyecto de depuración. Al cambiar los nombres de las clases de dominio, estas plantillas ya no funcionarán. Sin embargo, incluyen las directivas básicas que necesita y proporcionan ejemplos que puede actualizar para que coincidan con su DSL.

Para acceder a un modelo desde una plantilla de texto:

  • Establezca la propiedad inherit de la directiva de plantilla en Microsoft.VisualStudio.TextTemplating.VSHost.ModelingTextTransformation. Este valor proporciona acceso a la Tienda.

  • Especifique los procesadores de directivas del DSL al que quiera acceder. Esta acción carga los ensamblados de su DSL para que pueda usar sus clases de dominio, propiedades y relaciones en el código de la plantilla de texto. También carga el archivo de modelo que especifique.

    Al crear una nueva solución de Visual Studio, se crea un archivo .tt similar al siguiente en el proyecto de depuración a partir de la plantilla de Lenguaje mínimo DSL.

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

Observe los siguientes aspectos sobre esta plantilla:

  • La plantilla puede usar las clases de dominio, las propiedades y las relaciones que definió en la definición de DSL.

  • La plantilla carga el archivo de modelo que especifique en la propiedad requires.

  • Una propiedad de this contiene el elemento raíz. Desde allí, el código puede desplazarse a otros elementos del modelo. El nombre de la propiedad suele ser el mismo que la clase de dominio raíz del DSL. En este ejemplo, es this.ExampleModel.

  • Aunque el lenguaje en el que se escriben los fragmentos de código es C#, puede generar texto de cualquier tipo. También puede escribir el código en Visual Basic agregando la propiedad language="VB" a la directiva template.

  • Para depurar la plantilla, agregue debug="true" a la directiva template. Si se produce una excepción, la plantilla se abrirá en otra instancia de Visual Studio. Si quiere irrumpir en el depurador en un punto específico del código, inserte la instrucción System.Diagnostics.Debugger.Break();.

    Para más información, consulte Depuración de una plantilla de texto T4.

Acerca del procesador de directivas DSL

La plantilla puede usar las clases de dominio que definió en la definición de DSL. Esto se consigue mediante una directiva que suele aparecer cerca del inicio de la plantilla. En el ejemplo anterior, es la siguiente.

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

El nombre de la directiva (MyLanguage, en este ejemplo) se deriva del nombre de su DSL. Invoca un procesador de directivas que se genera como parte de su DSL. Puede encontrar su código fuente en Dsl\GeneratedCode\DirectiveProcessor.cs.

El procesador de directivas DSL realiza dos tareas principales:

  • Inserta eficazmente directivas de ensamblado e importación en la plantilla que hace referencia a su DSL. Esto le permite usar las clases de dominio en el código de plantilla.

  • Carga el archivo especificado en el parámetro requires y establece una propiedad en this que hace referencia al elemento raíz del modelo cargado.

Validación del modelo antes de ejecutar la plantilla

Puede hacer que el modelo se valide antes de ejecutar la plantilla.

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

Tenga en lo siguiente:

  1. Los parámetros filename y validation se separan con ";" y no debe haber otros separadores ni espacios.

  2. La lista de categorías de validación determina qué métodos de validación se ejecutarán. Se deben separar varias categorías con "|" y no debe haber otros separadores ni espacios.

    Si se encuentra un error, se notificará en la ventana de errores y el archivo de resultados contendrá un mensaje de error.

Acceso a varios modelos desde una plantilla de texto

Nota

Este método permite leer varios modelos de la misma plantilla, pero no admite referencias de ModelBus. Para leer modelos que están vinculados por referencias de ModelBus, consulte Uso de Visual Studio ModelBus en una plantilla de texto.

Si quiere acceder a más de un modelo desde la misma plantilla de texto, debe llamar al procesador de directivas generado una vez para cada modelo. Debe especificar el nombre de archivo de cada modelo en el parámetro requires. Debe especificar los nombres que quiere usar para la clase de dominio raíz en el parámetro provides. Debe especificar valores diferentes para los parámetros provides de cada una de las llamadas de directiva. Por ejemplo, supongamos que tiene tres archivos de modelo llamados Library.xyz, School.xyz y Work.xyz. Para acceder a ellos desde la misma plantilla de texto, debe escribir tres llamadas de directiva similares a las siguientes.

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

Nota

Este código de ejemplo es para un lenguaje basado en la plantilla de solución Lenguaje mínimo.

Para acceder a los modelos de la plantilla de texto, ahora puede escribir código similar al del ejemplo siguiente.

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

Carga dinámica de modelos

Si quiere determinar en tiempo de ejecución qué modelos cargar, puede cargar un archivo de modelo dinámicamente en el código del programa, en lugar de usar la directiva específica de DSL.

Sin embargo, una de las funciones de la directiva específica de DSL es importar el espacio de nombres DSL, de modo que el código de plantilla pueda usar las clases de dominio definidas en ese DSL. Dado que no usa la directiva, debe agregar directivas de <ensamblado> e <importación> a todos los modelos que pueda cargar. Esto es fácil si los diferentes modelos que puede cargar son todos instancias del mismo DSL.

Para cargar el archivo, el método más eficaz es mediante Visual Studio ModelBus. En un escenario típico, la plantilla de texto usará una directiva específica de DSL para cargar el primer modelo de la manera habitual. Ese modelo contendrá referencias de ModelBus a otro modelo. Puede usar ModelBus para abrir el modelo al que se hace referencia y acceder a un elemento determinado. Para más información, consulte Uso de Visual Studio ModelBus en una plantilla de texto.

En un escenario menos habitual, es posible que quiera abrir un archivo de modelo para el que solo tenga un nombre de archivo y que podría no estar en el proyecto actual de Visual Studio. En este caso, puede abrir el archivo mediante la técnica descrita en Apertura de un modelo desde un archivo en el código del programa.

Generación de varios archivos a partir de una plantilla

Si quiere generar varios archivos; por ejemplo, generar un archivo distinto para cada elemento de un modelo, hay varios enfoques posibles. De forma predeterminada, solo se genera un archivo a partir de cada archivo de plantilla.

División de un archivo largo

En este método, se usa una plantilla para generar un único archivo, separados por un delimitador. A continuación, se divide el archivo en sus partes. Hay dos plantillas, una para generar el único archivo y la otra para dividirlo.

LoopTemplate.t4 genera el archivo único largo. Observe que su extensión de archivo es ".t4", ya que no debe procesarse directamente al hacer clic en Transformar todas las plantillas. Esta plantilla toma un parámetro, que especifica la cadena delimitadora que separa los segmentos:

<#@ 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 invoca LoopTemplate.t4 y, luego, divide el archivo resultante en sus segmentos. Tenga en cuenta que esta plantilla no tiene que ser una plantilla de modelado, ya que no lee el modelo.

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