Использование DotLiquid для создания пользовательского шаблона в Asp.Net Core


Введение

Liquid — это язык шаблонов с открытым исходным кодом, созданный компанией Shopify и написанный на языке Ruby. Его можно использовать для добавления динамического содержимого на страницы и создания широкого спектра пользовательских шаблонов. DotLiquid — это система шаблонов, перенесенная во фреймворк .NET из Liquid Markup в Ruby.

Несмотря на то, что Liquid широко используется, документация по DotLiquid не настолько исчерпывающая, и поиск руководств для конкретных случаев использования может не принести никаких результатов.

Я сам немного помучился, пытаясь его использовать, особенно со сложными JSON-объектами, но в конце концов добился некоторого успеха.

Мы перейдем непосредственно к тому, как использовать DotLiquid для создания шаблона в Asp.Net Core (.Net 6).

Настройка

Сначала нам понадобится рабочий .net проект, используя вашу любимую IDE создайте новое Asp.Net Core Web App, используя шаблон MVC:

Если вы предпочитаете использовать CLI, введите эту команду в каталог, куда вы хотите добавить ваш проект:

Затем, используя либо Nuget Package Manager, либо CLI, мы должны добавить пакет DotLiquid

пакет NewtonSoft.Json в проект

Демонстрация

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

Сначала в папке wwwroot (или webroot) создадим каталог шаблонов и добавим в него файл шаблона — это файлы, которые заканчиваются расширением .liquid и используют комбинацию объектов, тегов и фильтров, чтобы сделать содержимое динамичным.
Вкратце это обычный синтаксис HTML + некоторый дополнительный синтаксис для логики.

Назовем наш файл example.liquid, и пока поместим в него один div, чтобы проверить, что рендеринг шаблона работает.

<div>
    <h3>The template content</h3>
</div>
Вход в полноэкранный режим Выйдите из полноэкранного режима

Затем в HomeController добавим логику рендеринга шаблона в его метод действия Index:

 public IActionResult Index()
{
    // we need to read the contents of the template file
    string liquidTemplateContent = System.IO.File.ReadAllText("wwwroot/template/example.liquid");
    // then we parse the contents of the template file into a liquid template
    Template template = Template.Parse(liquidTemplateContent);
    // then we render the template
    string result = template.Render();
    // and then we return the result to the view in a view bag object
    ViewBag.template = result;

    return View();
}
Войти в полноэкранный режим Выйти из полноэкранного режима

Затем все, что нам нужно сделать, это поместить наш шаблон в наш файл представления, перезаписать содержимое Views/Index.cshtml следующим образом:

@{
    ViewData["Title"] = "Home Page";
}

<div class="text-center">
    @Html.Raw(ViewBag.template)
</div>
Вход в полноэкранный режим Выйти из полноэкранного режима

Здесь мы использовали вспомогательный тег @Html.Raw для рендеринга html внутри шаблона.

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

Теперь, чтобы использовать динамические данные, давайте начнем с добавления класса с именем Employee в каталог Model, который должен выглядеть следующим образом:

Предположим, что мы хотим отобразить данные о сотрудниках в нашем шаблоне, для обеспечения безопасности DotLiquid не позволяет вам получить доступ к атрибуту Models напрямую, как объясняется в их документации:

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

Поэтому для включения свойств нам придется создать объект Drop, который использует подход «opt-in» для раскрытия данных.
Класс Drop — это класс, который наследуется от класса DotLiquid Drop и раскрывает данные, которые мы хотим передать нашему шаблону.

Давайте добавим следующее в наш класс Employee:

public class EmployeeDrop : Drop
{
    private readonly Employee _employee;

    public EmployeeDrop(Employee employee)
    {
        _employee = employee;
    }

    public string Name => _employee.Name;
    public string Email => _employee.Email;
    public string Phone => _employee.Phone;
}
Войти в полноэкранный режим Выйти из полноэкранного режима

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

Затем в HomeController нам нужно внести следующее изменение в метод действия Index:

 public IActionResult Index()
    {
        // we need to read the contents of the template file
        string liquidTemplateContent = System.IO.File.ReadAllText("wwwroot/template/example.liquid");
        // then we parse the contents of the template file into a liquid template
        Template template = Template.Parse(liquidTemplateContent);
        // then we create a new instance of the model class
        Employee employee = new Employee
        {
            Name = "John Doe",
            Email = "john.doe@example.com",
            Phone = "555-555-5555",
            Address = "123 Main St."
        };

        Hash hash = Hash.FromAnonymousObject(new
        {
            employee = new EmployeeDrop(employee)
        });
        // then we render the template
        string result = template.Render(hash);
        // and then we return the result to the view in a view bag object
        ViewBag.template = result;

        return View();
    }
Войти в полноэкранный режим Выйти из полноэкранного режима

Не забудем добавить данные в наш шаблон example.liquid:

<div>
    <h3>The template content</h3>
    <p>
        Name: {{ employee.name }}
    </p>
    <p>
        Email: {{ employee.email }}
    </p>
    <p>
        Phone: {{ employee.phone }}
    </p>
    <p>
        Address: {{ employee.address }}
    </p>
</div>
Войти в полноэкранный режим Выход из полноэкранного режима

После перезапуска приложения мы получим что-то вроде этого:

Обратите внимание, что мы получили пустой адрес, так как мы не раскрыли его в капле.
Далее давайте сделаем именно это, но вместо обычной строки пусть у нас будет JSON-объект, так мы закроем одну из наиболее часто встречающихся сейчас проблем.
В EmployeeDrop добавим атрибут Address вот так, для десериализации JSON будем использовать NewtonSoft.Json.

public class EmployeeDrop : Drop
{
    // ... old code
    public IDictionary<string, object>? Address =>
        JsonConvert.DeserializeObject<IDictionary<string, object>>(_employee.Address);
}
Вход в полноэкранный режим Выход из полноэкранного режима

Затем изменим формат адреса на правильный JSON в методе действия Index:

 public IActionResult Index()
    {
        // ... old code

        var address = @"
        {
            ""Street"" : ""123 Main St"",
            ""City"" : ""Anytown"",
            ""State"" : ""WA"",
            ""Zip"" : ""12345""
        }
        ";
        Employee employee = new Employee
        {
            Name = "John Doe",
            Email = "john.doe@example.com",
            Phone = "555-555-5555",
            Address = address
        };

        // ... old code

        return View();
    }
Войти в полноэкранный режим Выйти из полноэкранного режима

Последним шагом будет использование нового атрибута внутри нашего шаблона:

<div>
    <h3>The template content</h3>
    <p>
        Name: {{ employee.name }}
    </p>
    <p>
        Email: {{ employee.email }}
    </p>
    <p>
        Phone: {{ employee.phone }}
    </p>
    <p>Address:</p>
    <p>Street: {{ employee.address.Street }}</p>
    <p>City: {{ employee.address.City }}</p>
    <p>State: {{ employee.address.State }}</p>
    <p>Zip: {{ employee.address.Zip }}</p>
</div>
Войти в полноэкранный режим Выйти из полноэкранного режима

Хотя поля drop не чувствительны к регистру, знайте, что поля JSON чувствительны к регистру.

А вот и предварительный просмотр:

Может показаться, что так можно отобразить любой JSON, но если бы мы попытались десериализовать сложный объект и использовать его в нашем шаблоне, все, что мы получили бы для вложенных полей — пустые строки.

Допустим, Employee может иметь несколько адресов, и у нас есть объект JSON, содержащий массив адресов, давайте внесем следующие изменения в метод действия Index:

 public IActionResult Index()
    {
        // old code
        var addresses = @"
        {
            ""Addresses"": [{
                    ""Address"": ""123 Main Street"",
                    ""City"": ""Montreal"",
                    ""State"": ""QC"",
                    ""Zip"": ""H1S1M5"",
                    ""Country"": ""Canada""
                },
                {
                    ""Address"": ""456 Main Street"",
                    ""City"": ""Montreal"",
                    ""State"": ""QC"",
                    ""Zip"": ""H1S1M5"",
                    ""Country"": ""Canada""
                }
            ]
        }";
        Employee employee = new Employee
        {
            Name = "John Doe",
            Email = "john.doe@example.com",
            Phone = "555-555-5555",
            Address = addresses
        };

        // ... old code

        return View();
    }
Войти в полноэкранный режим Выйти из полноэкранного режима

Чтобы наш шаблон распознал вложенный JSON, нам нужно использовать пользовательский конвертер словарей с нашим десериализатором, здесь вы найдете один, который вы можете использовать, вы можете добавить его как есть в класс Employee и затем изменить атрибут Address в EmployeeDrop, чтобы использовать его следующим образом:

public class EmployeeDrop : Drop
{
    // ... old code
    public IDictionary<string, object>? Address =>
        JsonConvert.DeserializeObject<IDictionary<string, object>>(_employee.Address, new DictionaryConverter());
}
Войти в полноэкранный режим Выйти из полноэкранного режима

Последним шагом будет изменение шаблона:

<div>
    <h3>The template content</h3>
    <p>
        Name: {{ employee.name }}
    </p>
    <p>
        Email: {{ employee.email }}
    </p>
    <p>
        Phone: {{ employee.phone }}
    </p>
    <p>Addresses:</p>
    <div>
        {% for addr in employee.address.Addresses -%}
            <div>
                <p>Street: {{ addr.Street }}</p>
                <p>City: {{ addr.City }}</p>
                <p>State: {{ addr.State }}</p>
                <p>Zip: {{ addr.Zip }}</p>
                <p>Country: {{ addr.Country }}</p>
            </div>
        {% endfor %}
    </div>
</div>
Войти в полноэкранный режим Выйти из полноэкранного режима

После перезапуска вы получите вот такой предварительный просмотр:

Заключение

Надеюсь, в этой статье вы найдете ответы на вопросы о том, как создать шаблон DotLiquid в Asp.Net Core. Я не стал рассматривать обычные теги потока управления или итерации, так как в документации шаблонов Liquid уже есть все основы, и что хорошо в DotLiquid, так это то, что вы можете использовать почти все из языка шаблонов Liquid как есть, вы можете проверить их документацию здесь.

Окончательный код вы найдете на этом репозитории Github.

Оцените статью
devanswers.ru
Добавить комментарий