Многие кто сталкивался с DSL Tools и ASP.Net MVC уже знакомы с системой генерации T4 (Text Template Transformation Toolkit), которая в данный момент уже встроена в Visual Studio 2008.
Начнём сразу с примера.
Пример
Открываем студию, создаём проект консольного приложения. Затем создаём файл с именем "MyGenerator.tt"
Примечание: Обращаем внимание на расширение *.tt – это стандартное расширение файлов для T4. Однако, шаблон этого типа файлов отсутствует в стандартном диалоге добавления новых элементов (Add New Item ...), но, наверняка, будет добавлен в новой версии.
После создания можно будет увидеть, что студия файл опознала: появилась иконка в виде текстового документа со стрелочкой (видимо, такая метафора транформации на лету), а также появился вложенный узел с пустым файлом "MyGenerator.cs" - в этом файле по умолчанию будет находится результат генерации.
Если заглянуть в свойства нового файла то можно увидеть такую ситуацию:
С расширением *.tt автоматически связался Custom Tool под названием TextTemplatingFileGenerator.
Примечание: Тем кто занимался созданием своих Custom Tools для студии, известно что автоматическое связывание определённого расширения файлов с некоторым Custom Tool делается через реестр. В данном случае можно изучить содержимое [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\9.0\Generators\{164B10B9-B200-11D0-8C61-00A0C91E29D5}\.tt] и [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\9.0\Generators\{FAE04EC1-301F-11D3-BF4B-00C04F79EFBC}\.tt].
Напишем небольшой кусочек кода в этом файле:
<#@ template language="C#" hostspecific="true" #>
// Этот файл был сгенерирован.
// Генератор: <#= Host.GetType() #>
// Время генерации: <#= DateTime.Now #>
После сохранения файла, в итоговом файле "MyGenerator.cs":
// Этот файл был сгенерирован.
// Генератор: Microsoft.VisualStudio.TextTemplating.VSHost.TextTemplatingService
// Время генерации: 02/01/2009 00:12:25
Вот и получился минимальный простой генератор кода, который, правда, не делает никакой полезной работы. Как можно заметить, шаблоны T4 пишутся на неком подобии ASP.Net, только используется иной тип скобок, вместо <% %> вставляем <# #>.
Пример генерации C# класса
Различных вариантов применения генераторов кода много, поэтому долго придумывать не будем и реализуем генерацию простого класса.
Код шаблона:
<#@ template language="C#v3.5" debug="True" #>
<#@ import namespace="System.Collections.Generic" #>
using System;
using System.Text;
namespace GeneratedCode
{
internal class <#= this.ClassName #>
{
public <#= this.ClassName #>()
{
Console.WriteLine("ctor");
}
<# foreach(var prop in this.ClassProperties) {#>
<#= String.Format("public {0} {1} {{ get; set; }}", prop.Key, prop.Value) #>
<#}#>
public override string ToString()
{
StringBuilder result = new StringBuilder();
<# foreach(var prop in this.ClassProperties) {#>
result.AppendFormat("{0} = {1}", "<#= prop.Value #>", this.<#= prop.Value #>);
<#}#>
return String.Format("<#= this.ClassName #>() {{ {0} }}", result.ToString());
}
}
}
<#+ string ClassName = "MyClass"; #>
<#+ Dictionary<string, string> ClassProperties = new Dictionary<string, string>{
{"string", "StringProperty"},
{"int", "IntProperty"}
}; #>
Заглянем "под капот", то есть в код полученного генератора (незначащие детали убраны):
Примечание: Чтобы посмотреть результирующий генератор, необходимо указать в директиве <#@template #> атрибут debug=”True”, затем искать результат в папке %Temp%, там появятся файлы со случайным именем и расширениями *.cs, *.cmdline, *.dll, *.err. *.out. *.pdb.
using System;
using System.Collections.Generic;
using Microsoft.VisualStudio.TextTemplating;
using Microsoft.VisualStudio.TextTemplating.VSHost;
namespace Microsoft.VisualStudio.TextTemplatingF99F90EB5000191E2F8B24816A1C3631
{
public class GeneratedTextTransformation : TextTransformation
{
public override string TransformText()
{
try
{
this.Write("using System;\r\nusing System.Text;\r\n\r\nnamespace GeneratedCode\r\n{\r\n\tinternal class " +
"");
this.Write(ToStringHelper.ToStringWithCulture(this.ClassName));
this.Write("\r\n\t{\r\n\t\tpublic ");
this.Write(ToStringHelper.ToStringWithCulture(this.ClassName));
this.Write("() \r\n\t\t{\r\n\t\t\tConsole.WriteLine(\"ctor\"); \r\n\t\t}\r\n\t\t\r\n");
foreach (var prop in this.ClassProperties)
{
this.Write("\t\t");
this.Write(ToStringHelper.ToStringWithCulture(String.Format("public {0} {1} {{ get; set; }}", prop.Key, prop.Value)));
this.Write("\r\n");
}
this.Write("\r\n\t\tpublic override string ToString()\r\n\t\t{\r\n\t\t\tStringBuilder result = new StringB" +
"uilder();\r\n\t\t\t\r\n");
foreach (var prop in this.ClassProperties)
{
this.Write("\t\t\tresult.AppendFormat(\"{0} = {1}\", \"");
this.Write(ToStringHelper.ToStringWithCulture(prop.Value));
this.Write("\", this.");
this.Write(ToStringHelper.ToStringWithCulture(prop.Value));
this.Write(");\r\n");
}
this.Write("\t\t\r\n\t\t\treturn String.Format(\"");
this.Write(ToStringHelper.ToStringWithCulture(this.ClassName));
this.Write("() {{ {0} }}\", result.ToString());\r\n\t\t}\r\n\t}\r\n}\r\n");
}
catch (System.Exception e)
{
System.CodeDom.Compiler.CompilerError error = new System.CodeDom.Compiler.CompilerError();
error.ErrorText = e.ToString();
error.FileName = "ClassGenerator.tt";
this.Errors.Add(error);
}
return this.GenerationEnvironment.ToString();
}
string ClassName = "MyClass";
Dictionary<string, string> ClassProperties = new Dictionary<string, string>{
{"string", "StringProperty"},
{"int", "IntProperty"}
};
}
}
Ничего неожиданного в принципе мы здесь не обнаруживаем. Генератор использует случайное имя пространства имён (подозреваю, что постфикс имени - это SHA1-хэш от текста шаблона). Класс транформации наследуется от стандартного TextTransformation и реализует логику метода TransformText (типичная реализация шаблона Template Method).
Ну и результат генерации:
using System;
using System.Text;
namespace GeneratedCode
{
internal class MyClass
{
public MyClass()
{
Console.WriteLine("ctor");
}
public string StringProperty { get; set; }
public int IntProperty { get; set; }
public override string ToString()
{
StringBuilder result = new StringBuilder();
result.AppendFormat("{0} = {1}", "StringProperty", this.StringProperty);
result.AppendFormat("{0} = {1}", "IntProperty", this.IntProperty);
return String.Format("MyClass() {{ {0} }}", result.ToString());
}
}
}
Техника использования без Visual Studio
Генерация различных текстовых файлов внутри студии полезна во время разработки, но также существует потребность производить генерацию файлов и во время компиляции и общей сборки приложения. Для этого также доступны все средства.
Для использования вне процесса студии имеется консольная утилита TextTransform.exe, которую можно найти в [%CommonProgramFiles%\Microsoft Shared\TextTemplating\1.2\].
Использование, вполне, прямолинейно:
"%CommonProgramFiles%\Microsoft Shared\TextTemplating\1.2\TextTransform.exe"MyGenerator.tt -out MyGenerator.cs
Для генерации во время процесса сборки проекта и/или решения существует MSBuild Task от Элтона Стоунмена: http://geekswithblogs.net/EltonStoneman/archive/2008/07/25/an-msbuild-task-to-execute-t4-templates.aspx.Реализован этот Task довольно прямолинейно/примитивно, и использует вызов процесса TextTransform.exe.
Примечание: Более новая версия этого таска, которая работает и под MSBuild 3.5, доступна по адресу: http://geekswithblogs.net/EltonStoneman/archive/2008/10/04/executet4template-msbuild-task-updated.aspx.