Итак, вчера Microsoft выпустила ASP.NET MVC3 RTM, который включает в себя новый движок представлений Razor. Как вы наверняка уже знаете, Razor не содержит каких-то компонентов, специфичных для web, а значит, его можно использовать и в других приложениях. Ну, а если вы этого еще не знаете – то самое время узнать!
В этом посте я покажу, как использовать Razor в качестве движка шаблонов для ваших нужд. Источником для него послужил блог-пост Andrew Nurse «Hosting Razor outside of ASP.Net», но это не прямой перевод.
Для примера я создам текст письма, который содержит информацию о сделанном заказе.
Заказ описывается двумя классами, OrderModel и OrderItemModel:
Шаблон, который я буду использовать для создания e-mail, показан ниже. Более подробную информацию о синтаксисе Razor вы можете получить, например, в блог-посте Андрея Тарицына «Краткий справочник по синтаксису Razor [Перевод]».
Мне потребуется класс, который будет использоваться как базовый для моего шаблона:
Свойство Output задает TextWriter, который получает результат выполнения шаблона.
Свойство Model используется для передачи в шаблон параметров.
Метод Execute() в унаследованном классе будет содержать код шаблона.
Методы Write() и WriteLiteral() используются для вывода, соответственно, результатов вычисления выражений и текстовых строк. Т.к. мне не нужна дополнительная обработка ни для результатов вычислений, ни для строк, код этих методов совпадает.
Примечание: имена Execute(), Write() и WriteLiteral() используются по умолчанию, при необходимости вы можете указать другие имена используя свойство GeneratedClassContext экземпляра класса RazorEngineHost.
Теперь я создам хост для Razor, указав при этом, что в моем шаблоне используется C# (Razor поддерживает C# и VB, при необходимости вы можете использовать VB, передав к конструктор RazorEngineHost экземпляр VBRazorCodeLanguage):
Далее я указываю имя базового класса, пространство имен, в котором будет находиться код шаблона, и имя класса-шаблона:
Добавляю набор пространств имен, которые будут доступны в коде класса-шаблона:
И, наконец, создаю движок шаблонов:
Теперь я попробую разобрать свой шаблон:
Свойство Success класса GeneratorResults показывает, насколько успешно прошел разбор. Если при разборе возникли проблемы, я показываю список ошибок:
Если разбор завершился успешно, свойство GeneratedCode содержит DOM tree, который далее можно использовать для генерации кода:
Теперь мне нужно скомпилировать шаблон:
Приведенный выше метод возвращает имя сборки, которая содержит скомпилированный шаблон.
Код, заключенный в #if DEBUG … #endif используется для отладки и позволяет посмотреть, во что же превратился шаблон после всех проведенных над ним манипуляций.
Все, что теперь мне нужно сделать, это загрузить сборку, создать экземпляр класса-шаблона и «выполнить» его:
Метод GetModel() определяется следующим образом:
Теперь в файле «out.txt» содержится результат «выполнения» шаблона:
Вот и все!
Код примера:
StandaloneRazorDemo.zip
В этом посте я покажу, как использовать Razor в качестве движка шаблонов для ваших нужд. Источником для него послужил блог-пост Andrew Nurse «Hosting Razor outside of ASP.Net», но это не прямой перевод.
Для примера я создам текст письма, который содержит информацию о сделанном заказе.
Заказ описывается двумя классами, OrderModel и OrderItemModel:
public class OrderModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
public List<OrderItemModel> Items { get; set; }
}
public class OrderItemModel
{
public string ProductName { get; set; }
public Decimal Price { get; set; }
public int Qty { get; set; }
}
Шаблон, который я буду использовать для создания e-mail, показан ниже. Более подробную информацию о синтаксисе Razor вы можете получить, например, в блог-посте Андрея Тарицына «Краткий справочник по синтаксису Razor [Перевод]».
Здравствуйте, @Model.FirstName @Model.LastName!
# Продукт Цена Кол-во Итог
- ---------- ------ ------ ------
@for (var i = 0; i < Model.Items.Count; i++)
{
var item = Model.Items[i];
@String.Format(
"{0} {1,-10} {2,6} {3,6} {4,6}\r\n",
i + 1,
@item.ProductName,
@item.Price,
@item.Qty,
item.Price * item.Qty)
}
Всего: @(((OrderModel)Model).Items.Sum(x => x.Price * x.Qty))
WBR,
ACME team
Мне потребуется класс, который будет использоваться как базовый для моего шаблона:
public abstract class TemplateBase
{
public TextWriter Output { get; set; }
public dynamic Model { get; set; }
public abstract void Execute();
public virtual void Write(object value)
{
this.Output.Write(value);
}
public virtual void WriteLiteral(object value)
{
this.Output.Write(value);
}
}
Свойство Output задает TextWriter, который получает результат выполнения шаблона.
Свойство Model используется для передачи в шаблон параметров.
Метод Execute() в унаследованном классе будет содержать код шаблона.
Методы Write() и WriteLiteral() используются для вывода, соответственно, результатов вычисления выражений и текстовых строк. Т.к. мне не нужна дополнительная обработка ни для результатов вычислений, ни для строк, код этих методов совпадает.
Примечание: имена Execute(), Write() и WriteLiteral() используются по умолчанию, при необходимости вы можете указать другие имена используя свойство GeneratedClassContext экземпляра класса RazorEngineHost.
Теперь я создам хост для Razor, указав при этом, что в моем шаблоне используется C# (Razor поддерживает C# и VB, при необходимости вы можете использовать VB, передав к конструктор RazorEngineHost экземпляр VBRazorCodeLanguage):
var razorHost = new RazorEngineHost(new CSharpRazorCodeLanguage());
Далее я указываю имя базового класса, пространство имен, в котором будет находиться код шаблона, и имя класса-шаблона:
razorHost.DefaultBaseClass = typeof(TemplateBase).FullName;
razorHost.DefaultNamespace = "StandaloneRazorDemo";
razorHost.DefaultClassName = "Template";
Добавляю набор пространств имен, которые будут доступны в коде класса-шаблона:
razorHost.NamespaceImports.Add("System");
razorHost.NamespaceImports.Add("System.Collections.Generic");
razorHost.NamespaceImports.Add("System.Linq");
razorHost.NamespaceImports.Add("System.Text");
И, наконец, создаю движок шаблонов:
var razorEngine = new RazorTemplateEngine(razorHost);
Теперь я попробую разобрать свой шаблон:
var templateText = File.ReadAllText("template.txt");
GeneratorResults generatorResults = null;
using (var reader = new StringReader(templateText))
{
generatorResults = razorEngine.GenerateCode(reader);
}
Свойство Success класса GeneratorResults показывает, насколько успешно прошел разбор. Если при разборе возникли проблемы, я показываю список ошибок:
if (!generatorResults.Success)
{
foreach (var error in generatorResults.ParserErrors)
{
Console.WriteLine(
"Razor error: ({0}, {1}) {2}",
error.Location.LineIndex + 1,
error.Location.CharacterIndex + 1,
error.Message);
}
throw new ApplicationException();
}
Если разбор завершился успешно, свойство GeneratedCode содержит DOM tree, который далее можно использовать для генерации кода:
return generatorResults.GeneratedCode;
Теперь мне нужно скомпилировать шаблон:
private static string CompileTemplate(CodeCompileUnit generatedCode)
{
var codeProvider = new CSharpCodeProvider();
#if DEBUG
using (var writer = new StreamWriter("out.cs", false, Encoding.UTF8))
{
codeProvider.GenerateCodeFromCompileUnit(
generatedCode, writer, new CodeGeneratorOptions());
}
#endif
var outDirectory = "temp";
Directory.CreateDirectory(outDirectory);
var outAssemblyName = Path.Combine(outDirectory,
String.Format("{0}.dll", Guid.NewGuid().ToString("N")));
var refAssemblyNames = new List<string>();
refAssemblyNames.Add(new Uri(typeof(TemplateBase).Assembly.CodeBase).AbsolutePath);
refAssemblyNames.Add("System.Core.dll");
refAssemblyNames.Add("Microsoft.CSharp.dll");
var compilerResults = codeProvider.CompileAssemblyFromDom(
new CompilerParameters(refAssemblyNames.ToArray(), outAssemblyName),
generatedCode);
if (compilerResults.Errors.HasErrors)
{
var errors = compilerResults
.Errors
.OfType<CompilerError>()
.Where(x => !x.IsWarning);
foreach (var error in errors)
{
Console.WriteLine("Compiler error: ({0}, {1}) {2}",
error.Line, error.Column, error.ErrorText);
}
throw new ApplicationException();
}
return outAssemblyName;
}
Приведенный выше метод возвращает имя сборки, которая содержит скомпилированный шаблон.
Код, заключенный в #if DEBUG … #endif используется для отладки и позволяет посмотреть, во что же превратился шаблон после всех проведенных над ним манипуляций.
Все, что теперь мне нужно сделать, это загрузить сборку, создать экземпляр класса-шаблона и «выполнить» его:
var assembly = Assembly.LoadFrom(outAssemblyName);
var type = assembly.GetType("StandaloneRazorDemo.Template", true);
var template = Activator.CreateInstance(type) as TemplateBase;
using (var writer = new StringWriter())
{
template.Output = writer;
template.Model = GetModel();
template.Execute();
File.WriteAllText("out.txt", writer.ToString(), Encoding.UTF8);
}
Метод GetModel() определяется следующим образом:
private static OrderModel GetModel()
{
var model = new OrderModel { FirstName = "Джеймс", LastName = "Бонд" };
model.Items = new List<OrderItemModel>();
model.Items.Add(new OrderItemModel { ProductName = "Apple", Price = 4.95m, Qty = 1 });
model.Items.Add(new OrderItemModel { ProductName = "Kiwi", Price = 9.95m, Qty = 2 });
return model;
}
Теперь в файле «out.txt» содержится результат «выполнения» шаблона:
Здравствуйте, Джеймс Бонд!
# Продукт Цена Кол-во Итог
- ---------- ------ ------ ------
1 Apple 4,95 1 4,95
2 Kiwi 9,95 2 19,90
Всего: 24,85
WBR,
ACME team
Вот и все!
Код примера:
StandaloneRazorDemo.zip