Использование Razor отдельно от ASP.NET MVC

в 15:44, , рубрики: .net, html, razor, кодогенерация, метки: , ,

По роду деятельности студентом мне часто приходится писать различные лабораторные и курсовые работы. Неотъемлемая часть этих работ(как и в 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

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js