Поделиться через


Доступ к моделям из текстовых шаблонов

С помощью текстовых шаблонов можно создавать файлы отчетов, файлы исходного кода и другие текстовые файлы, основанные на языковых моделях конкретного домена. Основные сведения о текстовых шаблонах см. в разделе "Создание кода" и "Шаблоны текста T4". Текстовые шаблоны будут работать в экспериментальном режиме при отладке DSL, а также будут работать на компьютере, на котором развернут DSL.

Примечание.

При создании решения DSL в проекте отладки создаются примеры текстовых шаблонов *.tt . При изменении имен классов домена эти шаблоны больше не будут работать. Тем не менее, они включают основные директивы, необходимые, и предоставляют примеры, которые можно обновить для соответствия DSL.

Чтобы получить доступ к модели из текстового шаблона:

  • Задайте для наследуемого свойства директивы шаблона значение Microsoft.VisualStudio.TextTemplating.VSHost.ModelingTextTransformation. Это обеспечивает доступ к Магазину.

  • Укажите процессоры директив для DSL, к которому требуется получить доступ. Это загружает сборки для DSL, чтобы использовать его классы домена, свойства и связи в коде текстового шаблона. Он также загружает указанный файл модели.

    Файл .tt , аналогичный следующему примеру, создается в проекте отладки при создании нового решения Visual Studio из шаблона 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 #>
<#
  }
#>

Обратите внимание на следующие моменты этого шаблона:

  • Шаблон может использовать классы домена, свойства и связи, определенные в определении DSL.

  • Шаблон загружает файл модели, указанный в свойстве requires .

  • Свойство, содержащее this корневой элемент. Оттуда код может перейти к другим элементам модели. Имя свойства обычно совпадает с корневым классом домена dsL. В нашем примере поисковый запрос будет выглядеть так: this.ExampleModel.

  • Хотя язык, на котором написаны фрагменты кода, — C#, можно создать текст любого вида. Можно также написать код в Visual Basic, добавив свойство language="VB" в директиву template .

  • Чтобы выполнить отладку шаблона, добавьте debug="true" в директиву template . Шаблон откроется в другом экземпляре Visual Studio, если возникает исключение. Если вы хотите разбить отладчик в определенный момент в коде, вставьте инструкцию. System.Diagnostics.Debugger.Break();

    Дополнительные сведения см. в разделе Отладка шаблона текста T4.

Сведения о обработчике директив DSL

Шаблон может использовать классы домена, определенные в определении DSL. Это связано с директивой, которая обычно отображается ближе к началу шаблона. В предыдущем примере это следующее.

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

Имя директивы ( MyLanguageв этом примере) является производным от имени DSL. Он вызывает обработчик директив, созданный в рамках DSL. Исходный код можно найти в dsl\GeneratedCode\DirectiveProcessor.cs.

Обработчик директив DSL выполняет две основные задачи:

  • Он эффективно вставляет директивы сборки и импорта в шаблон, ссылающийся на DSL. Это позволяет использовать классы домена в коде шаблона.

  • Он загружает файл, указанный в параметре requires , и задает свойство, this которое ссылается на корневой элемент загруженной модели.

Проверка модели перед запуском шаблона

Вы можете вызвать проверку модели перед выполнением шаблона.

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

Обратите внимание на указанные ниже моменты.

  1. validation Параметры filename разделены ";" и не должны быть других разделителей или пробелов.

  2. Список категорий проверки определяет, какие методы проверки будут выполняться. Несколько категорий должны быть разделены на "|" и не должно быть других разделителей или пробелов.

    Если обнаружена ошибка, в окне ошибок будет сообщено сообщение об ошибке, а в результирующем файле появится сообщение об ошибке.

Доступ к нескольким моделям из текстового шаблона

Примечание.

Этот метод позволяет читать несколько моделей в одном шаблоне, но не поддерживает ссылки ModelBus. Сведения о чтении моделей, которые связаны ссылками modelBus, см. в разделе "Использование Visual Studio ModelBus" в текстовом шаблоне.

Если вы хотите получить доступ к нескольким моделям из одного текстового шаблона, необходимо вызвать созданный обработчик директив один раз для каждой модели. Необходимо указать имя файла каждой модели в параметре requires . Необходимо указать имена, которые необходимо использовать для корневого класса домена в параметре provides . Необходимо указать разные значения provides параметров в каждом из вызовов директивы. Например, предположим, что у вас есть три файла модели с именем Library.xyz, School.xyz и Work.xyz. Чтобы получить доступ к ним из одного текстового шаблона, необходимо написать три вызова директивы, которые похожи на следующие.

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

Примечание.

Этот пример кода предназначен для языка, основанного на шаблоне решения "Минимальный язык".

Чтобы получить доступ к моделям в текстовом шаблоне, теперь можно написать код, аналогичный коду в следующем примере.

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

Динамическое загрузка моделей

Если вы хотите определить во время выполнения, какие модели нужно загрузить, можно динамически загрузить файл модели в коде программы, а не использовать директиву DSL.

Однако одной из функций директивы DSL является импорт пространства имен DSL, поэтому код шаблона может использовать классы домена, определенные в этом DSL. Так как директива не используется, необходимо добавить <директивы сборки> и< импорта> для всех моделей, которые можно загрузить. Это легко, если разные модели, которые могут быть загружены, являются всеми экземплярами одного и того же DSL.

Для загрузки файла наиболее эффективным методом является использование Visual Studio ModelBus. В типичном сценарии текстовый шаблон будет использовать директиву DSL для загрузки первой модели обычным способом. Эта модель будет содержать ссылки ModelBus на другую модель. С помощью ModelBus можно открыть эталонную модель и получить доступ к конкретному элементу. Дополнительные сведения см. в разделе "Использование Visual Studio ModelBus" в текстовом шаблоне.

В менее обычном сценарии может потребоваться открыть файл модели, для которого у вас есть только имя файла, и который может не находиться в текущем проекте Visual Studio. В этом случае файл можно открыть с помощью метода, описанного в разделе "Практическое руководство. Открытие модели из файла в коде программы".

Создание нескольких файлов из шаблона

Если вы хотите создать несколько файлов, например для создания отдельного файла для каждого элемента в модели, существует несколько возможных подходов. По умолчанию создается только один файл из каждого файла шаблона.

Разделение длинного файла

В этом методе используется шаблон для создания одного файла, разделенного разделителем. Затем файл разделяется на его части. Существует два шаблона, один для создания одного файла, а другой — для разделения.

LoopTemplate.t4 создает длинный один файл. Обратите внимание, что его расширение файла — "t4", так как оно не должно обрабатываться непосредственно при нажатии кнопки "Преобразовать все шаблоны". Этот шаблон принимает параметр, указывающий строку разделителя, разделяющую сегменты:

<#@ 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.t4вызывается, а затем разбивает полученный файл на его сегменты. Обратите внимание, что этот шаблон не должен быть шаблоном моделирования, так как он не считывает модель.

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