Condividi tramite


Accedere ai modelli di testo da modelli di testo

Usando i modelli di testo, è possibile creare file di report, file di codice sorgente e altri file di testo basati su modelli linguistici specifici del dominio. Per informazioni di base sui modelli di testo, vedere Generazione di codice e Modelli di testo T4. I modelli di testo funzioneranno in modalità sperimentale durante il debug del linguaggio DSL e funzioneranno anche in un computer in cui è stato distribuito il linguaggio DSL.

Nota

Quando si crea una soluzione DSL, i file *.tt del modello di testo di esempio vengono generati nel progetto di debug. Quando si modificano i nomi delle classi di dominio, questi modelli non funzioneranno più. Tuttavia, includono le direttive di base necessarie e forniscono esempi che è possibile aggiornare in modo che corrispondano al linguaggio DSL.

Per accedere a un modello da un modello di testo:

  • Impostare la proprietà inherit della direttiva modello su Microsoft.VisualStudio.TextTemplating.VSHost.ModelingTextTransformation. In questo modo viene fornito l'accesso allo Store.

  • Specificare i processori di direttiva per il linguaggio DSL a cui si vuole accedere. Vengono caricati gli assembly per il linguaggio DSL in modo che sia possibile usare le relative classi di dominio, proprietà e relazioni nel codice del modello di testo. Carica anche il file del modello specificato.

    Un .tt file simile all'esempio seguente viene creato nel progetto debug quando si crea una nuova soluzione di Visual Studio dal modello DSL Minimal Language.

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

Si notino i punti seguenti relativi a questo modello:

  • Il modello può usare le classi, le proprietà e le relazioni di dominio definite nella definizione DSL.

  • Il modello carica il file del modello specificato nella requires proprietà .

  • Una proprietà in this contiene l'elemento radice. Da qui, il codice può passare ad altri elementi del modello. Il nome della proprietà è in genere uguale alla classe di dominio radice del linguaggio DSL. In questo esempio si tratta di this.ExampleModel.

  • Anche se il linguaggio in cui vengono scritti i frammenti di codice è C#, è possibile generare testo di qualsiasi tipo. In alternativa, è possibile scrivere il codice in Visual Basic aggiungendo la proprietà language="VB" alla template direttiva .

  • Per eseguire il debug del modello, aggiungere debug="true" alla template direttiva . Se si verifica un'eccezione, il modello verrà aperto in un'altra istanza di Visual Studio. Se si desidera suddividere il debugger in un punto specifico nel codice, inserire l'istruzione System.Diagnostics.Debugger.Break();

    Per altre informazioni, vedere Debug di un modello di testo T4.

Informazioni sul processore di direttiva DSL

Il modello può usare le classi di dominio definite nella definizione DSL. Ciò viene portato da una direttiva che in genere appare vicino all'inizio del modello. Nell'esempio precedente, è il seguente.

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

Il nome della direttiva ( , MyLanguagein questo esempio) è derivato dal nome del linguaggio DSL. Richiama un processore di direttiva generato come parte del linguaggio DSL. È possibile trovare il codice sorgente in Dsl\GeneratedCode\DirectiveProcessor.cs.

Il processore di direttiva DSL esegue due attività principali:

  • Inserisce in modo efficace le direttive assembly e import nel modello che fa riferimento al linguaggio DSL. In questo modo è possibile usare le classi di dominio nel codice del modello.

  • Carica il file specificato nel requires parametro e imposta una proprietà in this che fa riferimento all'elemento radice del modello caricato.

Convalida del modello prima di eseguire il modello

È possibile che il modello venga convalidato prima dell'esecuzione del modello.

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

Si noti che:

  1. I filename parametri e validation sono separati con ";" e non devono essere presenti altri separatori o spazi.

  2. L'elenco delle categorie di convalida determina quali metodi di convalida verranno eseguiti. Più categorie devono essere separate con "|" e non devono essere presenti altri separatori o spazi.

    Se viene rilevato un errore, verrà segnalato nella finestra degli errori e il file di risultati conterrà un messaggio di errore.

Accesso a più modelli da un modello di testo

Nota

Questo metodo consente di leggere più modelli nello stesso modello, ma non supporta i riferimenti ModelBus. Per leggere i modelli collegati dai riferimenti modelbus, vedere Uso di Visual Studio ModelBus in un modello di testo.

Se si vuole accedere a più modelli dallo stesso modello di testo, è necessario chiamare il processore di direttiva generato una volta per ogni modello. È necessario specificare il nome file di ogni modello nel requires parametro . È necessario specificare i nomi da usare per la classe di dominio radice nel provides parametro . È necessario specificare valori diversi per i provides parametri in ognuna delle chiamate di direttiva. Si supponga, ad esempio, di avere tre file di modello denominati Library.xyz, School.xyz e Work.xyz. Per accedervi dallo stesso modello di testo, è necessario scrivere tre chiamate di direttiva simili a quelle seguenti.

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

Questo codice di esempio è relativo a un linguaggio basato sul modello di soluzione Linguaggio minimo.

Per accedere ai modelli nel modello di testo, è ora possibile scrivere codice simile al codice nell'esempio seguente.

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

Caricamento di modelli in modo dinamico

Se si vuole determinare in fase di esecuzione quali modelli caricare, è possibile caricare un file di modello in modo dinamico nel codice del programma, anziché usare la direttiva specifica del linguaggio DSL.

Tuttavia, una delle funzioni della direttiva specifica dsl consiste nell'importare lo spazio dei nomi DSL, in modo che il codice modello possa usare le classi di dominio definite in tale dsl. Poiché non si usa la direttiva , è necessario aggiungere <direttive assembly> e <import> per tutti i modelli che è possibile caricare. Ciò è semplice se i diversi modelli che è possibile caricare sono tutte le istanze dello stesso DSL.

Per caricare il file, il metodo più efficace consiste nell'usare Visual Studio ModelBus. In uno scenario tipico, il modello di testo userà una direttiva specifica del linguaggio DSL per caricare il primo modello nel modo consueto. Tale modello conterrà riferimenti ModelBus a un altro modello. È possibile usare ModelBus per aprire il modello a cui si fa riferimento e accedere a un particolare elemento. Per altre informazioni, vedere Uso di Visual Studio ModelBus in un modello di testo.

In uno scenario meno consueto, potrebbe essere necessario aprire un file di modello per il quale si dispone solo di un nome file e che potrebbe non trovarsi nel progetto di Visual Studio corrente. In questo caso, è possibile aprire il file usando la tecnica descritta in Procedura: Aprire un modello da file nel codice programma.

Generazione di più file da un modello

Se si desidera generare diversi file, ad esempio per generare un file separato per ogni elemento in un modello, esistono diversi approcci possibili. Per impostazione predefinita, viene prodotto un solo file da ogni file modello.

Suddivisione di un file lungo

In questo metodo si usa un modello per generare un singolo file, separato da un delimitatore. Quindi si suddivide il file nelle relative parti. Esistono due modelli, uno per generare il singolo file e l'altro per suddividerlo.

LoopTemplate.t4 genera il singolo file lungo. Si noti che l'estensione del file è ".t4", perché non deve essere elaborata direttamente quando si fa clic su Trasforma tutti i modelli. Questo modello accetta un parametro che specifica la stringa del delimitatore che separa i segmenti:

<#@ 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.ttLoopTemplate.t4richiama e quindi suddivide il file risultante nei relativi segmenti. Si noti che questo modello non deve essere un modello di modellazione, perché non legge il modello.

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