По роду деятельности студентом мне часто приходится писать различные лабораторные и курсовые работы. Неотъемлемая часть этих работ(как и в 80% задач бизнес-программирования) — код составления отчета о найденных элементах в базе данных или выполненной длительной и сложной операции. В десктопном программировании в рамках .NET Framework есть различные способы решения подобных проблем:
- Старая школа: формат RTF никто не отменял, однако вручную составить что-либо сложнее цветного текста со шрифтами разного цвета и ссылками лично у меня не получалось. Способ действенный, но стандарт не самый легкий для понимания, на последнюю версию даже смотреть страшно
- Для Silverlight/WPF — формирование FlowDocument своими руками. Объектная модель однозначно удобнее формирования старого формата вручную, но вариант тоже только для смелых
- Сторонние решения вроде Crystal Reports, возможно и очень мощный инструмент для создания отчетов, но не всегда в этом есть необходимость, к тому же не всегда есть возможность тянуть пару-тройку дополнительных программных продуктов1
- Использование Microsoft Office Interop. Нет, это издевательство. Ручная работа с памятью в управляемой среде вызывает лишь бурю радости. Начинающий программист выпьет не одну упаковку кофе, прежде чем смирится с подобным принципом работы.
- Формирование HTML. На нашу радость, стандарт позволяет создать документы средней степени форматированности, с помощью CSS можно довести до ума. Минусы — оформить документ по всем требованиям крайне затруднительно. Но это если в этом есть необходимость. Плюсы: простота стандарта и всеобщий переход «в облака и браузеры» стимулирует развитие HTML.
О последнем я и буду вести речь.
Самый простой и очевидный вариант — формирование HTML строки вручную. Те, кто только знакомится с платформой, тут же начнут заниматься конкатенацией объектов String, кто читал документацию — обратят внимание на класс StringBuilder. В любом случае, String.Format не всегда самый удобный способ формирования отчетов. Зато коллеги по платформе, пользующиеся ASP.NET MVC никак не могут нарадоваться движку представлений Razor. И тут возникает вопрос: а почему бы и не использовать мощь ASP.NET Razor в своих целях?
Прежде всего, необходимо подключить сборку System.Web.Razor. Она из поставки ASP.NET MVC, так что если у целевого компьютера он не установлен, следует позаботиться о локальном копировании необходимых dll'ок.
Объявим базовый класс, от которого будет наследоваться наше представление
public abstract class TemplateBase
{
public StringBuilder Buffer { get; set; }
public StringWriter Writer { get; set; }
public TemplateBase()
{
Buffer = new StringBuilder();
Writer = new StringWriter(Buffer);
}
public abstract void Execute();
// Записывает в строку выражения типа: "@foo.Bar"
public virtual void Write(object value)
{
// Don't need to do anything special
// Razor for ASP.Net does HTML encoding here.
WriteLiteral(value);
}
// Записывает литералы разметки: "<p>Foo</p>"
public virtual void WriteLiteral(object value)
{
Buffer.Append(value);
}
// ... а здесь мы можем указать еще какие-либо публичные свойства, которые будут передаваться движку Razor вне зависимости от того, хочет он этого или нет
}
Прежде всего, необходимо инициализировать движок.
private RazorTemplateEngine SetupRazorEngine()
{
// 1. Указываем, что хотим использовать C# описания представления
RazorEngineHost host = new RazorEngineHost(new CSharpRazorCodeLanguage());
// 2. Указываем базовый класс, от которого наследуется представление. Тот самый, что описывали выше
host.DefaultBaseClass = typeof(TemplateBase).FullName;
// 3. Указываем пространство имен и название класса представления
host.DefaultNamespace = "RazorOutput";
host.DefaultClassName = "Template";
// 4. Указываем используемые по умолчанию пространства имен (using)
host.NamespaceImports.Add("System");
// 5. Создаем новый движок представления
return new RazorTemplateEngine(host);
}
Как известно, .NET поддерживает компиляцию кода «на лету». Эту особенность и использует Razor. Следовательно в нашем случае надо прочитать файл с шаблоном и сохранить его в отдельную сборку.
// Генерируем код шаблона
GeneratorResults razorResult = null;
using (TextReader rdr = new StringReader( MyTemplatesString ))
razorResult = _engine.GenerateCode(rdr);
CSharpCodeProvider codeProvider = new CSharpCodeProvider();
// Записываем компилированный код в файл сборки
string outputAssemblyName = String.Format("Temp_{0}.dll", Guid.NewGuid().ToString("N"));
CompilerResults results = codeProvider.CompileAssemblyFromDom(
new CompilerParameters(new string[] {
typeof(Form1).Assembly.CodeBase.Replace("file:///", "").Replace("/", "\")
}, outputAssemblyName),
razorResult.GeneratedCode);
Если все прошло хорошо, то дальше использование простое:
Assembly asm = Assembly.LoadFrom(outputAssemblyName);
if (asm == null)
{
MessageBox.Show("Ошибка во время загрузки сборки");
}
else
{
Type typ = asm.GetType("RazorOutput.Template");
if (typ == null)
{
MessageBox.Show("Не найден RazorOutput.Template в сборке {0}", asm.FullName);
}
else
{
TemplateBase newTemplate = Activator.CreateInstance(typ) as TemplateBase;
if (newTemplate == null)
{
MessageBox.Show("Невозможно создать экземпляр RazorOutput.Template или он не наследуется TemplateBase");
}
else
{
// здесь мы присваиваем newTemplate все необходимые нам данные
// ...
newTemplate.Execute();
string resultHTML = newTemplate.Buffer.ToString();
newTemplate.Buffer.Clear();
return resultHTML; // разумеется никто в здравом уме не будет здесь возвращать String, но это исключительно для примера
}
}
}
Вот, собственно и все. Остается только вопрос, как именно указывать данные представления — через TemplateBase, или же через Razor-синтаксис вида
@functions {
public string CustomerName { get; set; }
public string ResetLink { get; set; }
}
В последнем случае вам придется отказаться от динамической генерации шаблонов, зато очень легко получить доступ к этим данным.
Итак, с помощью нехитрых движений мы избежали искушения написания «еще одного» шаблонизатора, воспользовавшись мощным и удобным средством от MS
1 предполагается, что раз вы пишете под .NET Framework, то он предполагается установленным у клиента. А вот Office, Crystal Reports, SQL Server, Visual Studio — не обязательно
Автор: Dima_Sharihin