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


Пошаговое руководство. Создание кода с помощью текстовых шаблонов

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

Типизированный код для чтения XML

Пространство имен System.Xml предоставляет комплексные средства загрузки документов XML и свободного их поиска в памяти.К сожалению, все узлы принадлежат одному типу — XmlNode.Поэтому очень легко допустить такие ошибки программирования, как ожидание неверного типа дочернего узла или неверного атрибута.

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

ПримечаниеПримечание

Приложение xsd.exe, включенное в состав Visual Studio, может создавать строго типизированные классы из файлов XML.Представленный здесь шаблон приведен в качестве примера.

Образец файла:

<?xml version="1.0" encoding="utf-8" ?>
<catalog>
  <artist id ="Mike%20Nash" name="Mike Nash Quartet">
    <song id ="MikeNashJazzBeforeTeatime">Jazz Before Teatime</song>
    <song id ="MikeNashJazzAfterBreakfast">Jazz After Breakfast</song>
  </artist>
  <artist id ="Euan%20Garden" name="Euan Garden">
    <song id ="GardenScottishCountry">Scottish Country Garden</song>
  </artist>
</catalog>

В проекте, построенном с использованием данного пошагового руководства, можно написать представленный ниже код и IntelliSense запросит верные имена атрибута и дочернего элементы во время ввода:

Catalog catalog = new Catalog(xmlDocument);
foreach (Artist artist in catalog.Artist)
{
  Console.WriteLine(artist.name);
  foreach (Song song in artist.Song)
  {
    Console.WriteLine("   " + song.Text);
  }
}

Сравните это с нетипизированным кодом, который можно написать без шаблона:

XmlNode catalog = xmlDocument.SelectSingleNode("catalog");
foreach (XmlNode artist in catalog.SelectNodes("artist"))
{
    Console.WriteLine(artist.Attributes["name"].Value);
    foreach (XmlNode song in artist.SelectNodes("song"))
    {
         Console.WriteLine("   " + song.InnerText);
     }
}

В строго типизированной версии изменение схемы XML приведет к изменению классов.Компилятор выделит части кода приложения, которые следует изменить.В нетипизированной версии, использующей универсальный код XML, такая поддержка не реализована.

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

Настройка проекта

Dd820614.collapse_all(ru-ru,VS.110).gifСоздание или открытие проекта C#

Этот метод можно применять к любому проекту кода.В этом пошаговом руководстве используется проект C#, а для тестирования используется консольное приложение.

Создание проекта

  1. В меню Файл последовательно выберите пункты Создать и Проект.

  2. Щелкните узел Visual C# и в области Шаблоны выберите Консольное приложение.

Dd820614.collapse_all(ru-ru,VS.110).gifДобавление прототипа файла XML в проект

Назначение этого файла — предоставление примеров типов узлов XML, которые должна быть способна читать ваша программа.Это может быть файл, который планируется использовать при тестировании приложения.Шаблон создаст класс C# для каждого типа узлов в данном файле.

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

Добавление файла XML

  1. Откройте Обозреватель решений, щелкните правой кнопкой мыши проект, нажмите кнопку Добавить и щелкните Создать элемент.

  2. В диалоговом окне Добавление нового элемента выберите элемент Файл XML на панели Шаблоны.

  3. Добавьте образец содержимого в файл.

  4. Для этого пошагового руководства присвойте файлу имя exampleXml.xml.Используйте в качестве содержимого файла код XML, приведенный выше.

..

Dd820614.collapse_all(ru-ru,VS.110).gifДобавление файла теста кода

Добавьте файл C# в проект и напишите в нем пример кода, который вы должны иметь возможность написать.Примеры.

using System;
namespace MyProject
{
  class CodeGeneratorTest
  {
    public void TestMethod()
    {
      Catalog catalog = new Catalog(@"..\..\exampleXml.xml");
      foreach (Artist artist in catalog.Artist)
      {
        Console.WriteLine(artist.name);
        foreach (Song song in artist.Song)
        {
          Console.WriteLine("   " + song.Text);
} } } } }

На этом этапе код компилироваться не будет.При написании шаблона вы создадите классы, позволяющие успешно скомпилировать код.

Более полный тест позволит проверить исход реализации этой функции теста с известным содержимым образцового файла XML.Однако в данном пошаговом руководстве нам достаточно успешного выполнения метода теста.

Dd820614.collapse_all(ru-ru,VS.110).gifДобавление файла текстового шаблона

Добавьте файл текстового шаблона и установите расширение файла вывода "CS".

Добавление файла текстового шаблона в проект

  1. Откройте Обозреватель решений, щелкните правой кнопкой мыши проект, нажмите кнопку Добавить и щелкните Создать элемент.

  2. В диалоговом окне Добавление нового элемента выберите элемент Шаблон текста на панели Шаблоны.

    ПримечаниеПримечание

    Убедитесь, что выполняется добавление "текстового шаблона", а не "обработанного текстового шаблона".

  3. В директиве template файла измените значение атрибута hostspecific на true.

    Это изменение позволит коду шаблона получить доступ к службам Visual Studio.

  4. В директиве output измените атрибут расширения на "CS", чтобы шаблон создавал файл C#.В проекте Visual Basic необходимо установить расширение "VB".

  5. Сохраните файл.Теперь файл текстового шаблона должен содержать следующие строки:

    <#@ template debug="false" hostspecific="true" language="C#" #>
    <#@ output extension=".cs" #>
    

.

Обратите внимание, что CS-файл отображается в обозревателе решений как дочерний файл шаблона.Чтобы его просмотреть щелкните [+] рядом с именем файла шаблона.Этот файл создается из файла шаблона при каждом сохранении или перемещении фокуса с файла шаблона.Созданный файл компилируется как часть проекта.

Для удобства в процессе разработки файла шаблона расположите окна файла шаблона и создаваемого файла рядом так, чтобы можно было видеть оба файла.Это позволит сразу просматривать вывод шаблона.Вы также сразу сможете заметить ошибки в окне сообщений, когда шаблон создаст недопустимый код C#.

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

Разработка текстового шаблона

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

Dd820614.collapse_all(ru-ru,VS.110).gifСоздайте прототип будущего кода

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

  class Catalog {} 
  class Artist {}
  class Song {}

Это позволит видеть требуемые классы, но сами объявления должны быть созданы из типов узлов в образце файла XML.Удалите из шаблона экспериментальные строки.

Dd820614.collapse_all(ru-ru,VS.110).gifСоздание кода приложения из модели файла XML

Чтобы прочитать файл XML и создать объявления классов, замените содержимое шаблона следующим кодом шаблона:

<#@ template debug="false" hostspecific="true" language="C#" #>
<#@ output extension=".cs" #>
<#@ assembly name="System.Xml"#>
<#@ import namespace="System.Xml" #>
<#
 XmlDocument doc = new XmlDocument();
 // Replace this file path with yours:
 doc.Load(@"C:\MySolution\MyProject\exampleXml.xml");
 foreach (XmlNode node in doc.SelectNodes("//*"))
 {
#>
  public partial class <#= node.Name #> {}
<#
 }
#>

Замените путь к файлу на путь к проекте.

Обратите внимание на разделители блока кода <#...#>.В эти разделители заключается фрагмент кода программы, создаваемый текст.Разделите блока выражения <#=...#> обозначают выражение, которое рассматривается как строка.

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

Директива <#@assembly#> ведет себя так же, как ссылка, предоставляя коду шаблона доступ к сборке.Список сборок, видимый шаблоном, отделен от списка "Ссылки" в проекте приложения.

Директива <#@import#> действует как оператор using, позволяя использовать короткие имена классов в импортированном пространстве имен.

К сожалению, хотя этот шаблон и создает код, он создает объявление класса для каждого узла в примере файла XML, поэтому, если существует несколько экземпляров узла <song>, будет создано несколько объявлений класса.

Dd820614.collapse_all(ru-ru,VS.110).gifЧтение модели файла и создание кода

Многие текстовые шаблоны следуют форме, в которой первая часть шаблона считывает исходный файл, а вторая — создает шаблон.Необходимо выполнить чтение всего файла примера, чтобы составить сводку содержащихся в нем типов узлов, а затем создать объявления классов.Еще один <#@import#> требуется для использования Dictionary<>:

<#@ template debug="false" hostspecific="true" language="C#" #>
<#@ output extension=".cs" #>
<#@ assembly name="System.Xml"#>
<#@ import namespace="System.Xml" #>
<#@ import namespace="System.Collections.Generic" #>
<#
 // Read the model file
 XmlDocument doc = new XmlDocument();
 doc.Load(@"C:\MySolution\MyProject\exampleXml.xml");
 Dictionary <string, string> nodeTypes = 
        new Dictionary<string, string>();
 foreach (XmlNode node in doc.SelectNodes("//*"))
 {
   nodeTypes[node.Name] = "";
 }
 // Generate the code
 foreach (string nodeName in nodeTypes.Keys)
 {
#>
  public partial class <#= nodeName #> {}
<#
 }
#>

Dd820614.collapse_all(ru-ru,VS.110).gifДобавление вспомогательного метода

Блок элемента управления функцией класса — это блок, в котором можно определить вспомогательные методы.Блок обозначен разделителем <#+...#> и в файле должен быть последним блоком.

Если вы предпочитаете, чтобы имена классов начинались с прописной буквы, замените последнюю часть шаблона следующим кодом:

// Generate the code
 foreach (string nodeName in nodeTypes.Keys)
 {
#>
  public partial class <#= UpperInitial(nodeName) #> {}
<#
 }
#>
<#+
 private string UpperInitial(string name)
 { return name[0].ToString().ToUpperInvariant() + name.Substring(1); }
#>

Теперь создаваемый CS-файл будет содержать следующие объявления:

  public partial class Catalog {}
  public partial class Artist {}
  public partial class Song {}

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

Dd820614.collapse_all(ru-ru,VS.110).gifДоступ к API в Visual Studio

Задание атрибута hostspecific определения <#@template#> позволяет шаблону получить доступ к API Visual Studio.Шаблон может использовать его для получения сведений о местоположении файлов проекта, избегая использования абсолютного пути в коде шаблона.

<#@ template debug="false" hostspecific="true" language="C#" #>
...
<#@ assembly name="EnvDTE" #>
...
EnvDTE.DTE dte = (EnvDTE.DTE) ((IServiceProvider) this.Host)
                       .GetService(typeof(EnvDTE.DTE));
// Open the prototype document.
XmlDocument doc = new XmlDocument();
doc.Load(System.IO.Path.Combine(dte.ActiveDocument.Path, "exampleXml.xml"));

Заполнение текстового шаблона

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

<#@ template debug="false" hostspecific="true" language="C#" #>
<#@ output extension=".cs" #>
<#@ assembly name="System.Xml" #>
<#@ assembly name="EnvDTE" #>
<#@ import namespace="System.Xml" #>
<#@ import namespace="System.Collections.Generic" #>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml;
namespace MyProject
{
<#
 // Map node name --> child name --> child node type
 Dictionary<string, Dictionary<string, XmlNodeType>> nodeTypes = new Dictionary<string, Dictionary<string, XmlNodeType>>();

 // The Visual Studio host, to get the local file path.
 EnvDTE.DTE dte = (EnvDTE.DTE) ((IServiceProvider) this.Host)
                       .GetService(typeof(EnvDTE.DTE));
 // Open the prototype document.
 XmlDocument doc = new XmlDocument();
 doc.Load(System.IO.Path.Combine(dte.ActiveDocument.Path, "exampleXml.xml"));
 // Inspect all the nodes in the document.
 // The example might contain many nodes of the same type, 
 // so make a dictionary of node types and their children.
 foreach (XmlNode node in doc.SelectNodes("//*"))
 {
   Dictionary<string, XmlNodeType> subs = null;
   if (!nodeTypes.TryGetValue(node.Name, out subs))
   {
     subs = new Dictionary<string, XmlNodeType>();
     nodeTypes.Add(node.Name, subs);
   }
   foreach (XmlNode child in node.ChildNodes)
   {
     subs[child.Name] = child.NodeType;
   } 
   foreach (XmlNode child in node.Attributes)
   {
     subs[child.Name] = child.NodeType;
   }
 }
 // Generate a class for each node type.
 foreach (string className in nodeTypes.Keys)
 {
    // Capitalize the first character of the name.
#>
    partial class <#= UpperInitial(className) #>
    {
      private XmlNode thisNode;
      public <#= UpperInitial(className) #>(XmlNode node) 
      { thisNode = node; }

<#
    // Generate a property for each child.
    foreach (string childName in nodeTypes[className].Keys)
    {
      // Allow for different types of child.
      switch (nodeTypes[className][childName])
      {
         // Child nodes:
         case XmlNodeType.Element:
#>
      public IEnumerable<<#=UpperInitial(childName)#>><#=UpperInitial(childName) #>
      { 
        get 
        { 
           foreach (XmlNode node in
                thisNode.SelectNodes("<#=childName#>")) 
             yield return new <#=UpperInitial(childName)#>(node); 
      } }
<#
         break;
         // Child attributes:
         case XmlNodeType.Attribute:
#>
      public string <#=childName #>
      { get { return thisNode.Attributes["<#=childName#>"].Value; } }
<#
         break;
         // Plain text:
         case XmlNodeType.Text:
#>
      public string Text  { get { return thisNode.InnerText; } }
<#
         break;
       } // switch
     } // foreach class child
  // End of the generated class:
#>
   } 
<#
 } // foreach class

   // Add a constructor for the root class 
   // that accepts an XML filename.
   string rootClassName = doc.SelectSingleNode("*").Name;
#>
   partial class <#= UpperInitial(rootClassName) #>
   {
      public <#= UpperInitial(rootClassName) #>(string fileName) 
      {
        XmlDocument doc = new XmlDocument();
        doc.Load(fileName);
        thisNode = doc.SelectSingleNode("<#=rootClassName#>");
      }
   }
}
<#+
   private string UpperInitial(string name)
   {
      return name[0].ToString().ToUpperInvariant() + name.Substring(1);
   }
#>

Dd820614.collapse_all(ru-ru,VS.110).gifЗапуск тестовой программы

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

using System;
namespace MyProject
{ class Program
  { static void Main(string[] args)
    { new CodeGeneratorTest().TestMethod();
      // Allow user to see the output:
      Console.ReadLine();
} } }

Dd820614.collapse_all(ru-ru,VS.110).gifНаписание и обновление приложения

Теперь можно написать приложение в строго типизированном стиле, используя вместо универсального кода XML созданные классы.

При изменении схемы XML можно легко создать новые классы.Компилятор подскажет разработчику о необходимости обновления кода приложения.

Для повторного создания классов после изменения образца файла XML, нажмите на панели инструментов обозревателя решений кнопку Преобразовать все шаблоны.

Заключение

Это пошаговое руководство демонстрирует несколько методов и преимуществ создания кода:

  • Создание кода — это создание части исходного кода на основе модели.Модель содержит сведения в форме, приемлемой для домена приложения, и может изменяться в течение жизненного цикла приложения.

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

  • IntelliSense и компилятор помогают создавать код, соответствующий схеме модели, как при написании нового кода, так и при обновлении схемы.

  • Все эти преимущества способно предоставить добавление одного упрощенного файла шаблона в проект.

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

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

Устранение неполадок текстовых шаблонов

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

См. также

Основные понятия

Создание кода во время разработки с помощью текстовых шаблонов T4

Написание текстового шаблона T4