Using DotLiquid to create a custom template in Asp.Net Core
Tags: dotnet, dotliquid, csharp, json
Published: 2022-09-01 12:58:35

### Introduction
[Liquid](https://shopify.github.io/liquid/) is an open-source template language created by Shopify and written in Ruby. It can be used to add dynamic content to pages, and to create a wide variety of custom templates. While [DotLiquid](http://dotliquidmarkup.org/) is a templating system ported to the .NET framework from Ruby’s Liquid Markup.

Even though the fact that Liquid is vastly used, the documentation for DotLiquid isn't that exhaustive and finding guides for specific use cases might not bear any fruits.

I myself struggled a bit while trying to use it, especially with complex JSON objects but at the end I've finally got some success.

We'll move directly to how to use DotLiquid to create a template in Asp.Net Core (.Net 6).

### Setup
First we'll need a working .net project, using your favorite IDE create a new Asp.Net Core Web App using the MVC template:

![Project Creation Menu](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qi00um71b087p6p6kfb1.png)

If you prefer using CLI type in this command in the directory where you want to add your project:
`dotnet new mvc -au None`

Then using either Nuget Package Manager or the CLI, we'll have to add the [DotLiquid](https://www.nuget.org/packages/DotLiquid/) package
`dotnet add package DotLiquid` and the 
[NewtonSoft.Json](https://www.nuget.org/packages/Newtonsoft.Json/) package to the project
`dotnet add package Newtonsoft.Json`

### Demo
On creation our project would look like this:

![Project Structure](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5rc58204f3qqzt9fkiaq.png)

First in our `wwwroot` (or webroot) folder let's create a template directory and add a template file to it, which are files that end with the `.liquid` extension and use a combination of objects, tags, and filters to make the content dynamic. 
For short it's a normal `HTML` syntax + some extra syntax for the logic.

Let's name our file `example.liquid`, and for the moment we'll put a single `div` in it, to test that the template rendering works.

```html
<div>
    <h3>The template content</h3>
</div>
```

Then in the `HomeController` let’s add the logic to render a template to its `Index` Action Method:
```c#
 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();
}
```
Then all we need to do is put our template in our view file, overwrite the content of `Views/Index.cshtml` with the following:
```html
@{
    ViewData["Title"] = "Home Page";
}

<div class="text-center">
    @Html.Raw(ViewBag.template)
</div>
```
_Here we used the `@Html.Raw` helper tag to render the html inside the template_

Finally run your project to see the result, that should look like this:
![Run Screenshot](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ejz6spapg1otlww9elyu.png)

Now to use dynamic data, let's start by by adding a class named Employee to the Model directory, that should look like this:

![Employee Model Structure](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7pp9cfn5z8akjws0x2rs.png)

Assuming we want to render employees data in our template, to enforce security DotLiquid doesn't let you access a Models attribute directly, as explained in their docs:

> DotLiquid is focused on making your templates safe. It does this by making sure templates can only access properties and methods that have been specifically enabled.
This means that you can't just pass a model instance to the template and access the properties directly.

So to enable the properties we'll have to create a `Drop` object which uses an opt-in approach to expose data. 
A `Drop` class is a class that inherits from the DotLiquid `Drop` class and exposes the data we want to pass to our template. 

Let's add the following to our `Employee` class:
```c#
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;
}
```
_I omitted adding the `address` for the moment to show that if we don't add an attribute to the drop the template wouldn't be able to output it and instead would show a blank string._ 

Then in the `HomeController` we need to make the following change to the `Index` action method: 
```c#
 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();
    }
```
Let's not forget adding the data to our `example.liquid` template:
```html
<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>
```
After restarting the App we'll get something like this:

![Run Preview](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dvh44jc60e8l8oq6a72u.png)

Notice that we got an empty address as we didn't expose it in the drop.
Next let's do just that, but instead of a normal `string` let's say we have a JSON object so we'd cover one of the most recurring problems nowadays.
In the `EmployeeDrop` let's add the `Address` attribute like so, we'll use `NewtonSoft.Json` to deserialize the JSON.
```c#
public class EmployeeDrop : Drop
{
    // ... old code
    public IDictionary<string, object>? Address =>
        JsonConvert.DeserializeObject<IDictionary<string, object>>(_employee.Address);
}
```
Then let's change the address format to a valid JSON in the `Index` action method:
```c#
 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();
    }
```
The last step is to use the new attribute inside our template:
```html
<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>
```
Although the drop fields is case insensitive, know that the JSON **fields** are case sensitive.

And here's the preview:

![preview](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8stwr0dtobz5znn2leyd.png)

It might seem like you can render any JSON like this but if we tried deserializing a complex object and using it in our template all we'd get for the nested fields are blank strings.

Let's say an `Employee` can have multiple addresses and we have a JSON object containing an array of addresses, let's make the following changes to the `Index` action method:
```c#
 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();
    }
```

To make our template recognize the Nested JSON we need to use a custom Dictionary Converter with our deserializer, [here](https://stackoverflow.com/a/31250524/9438728) you'll find one that you can use, you can add it as is to the `Employee` class and then modify the `Address` attribute in `EmployeeDrop` to use it like so:
```c#
public class EmployeeDrop : Drop
{
    // ... old code
    public IDictionary<string, object>? Address =>
        JsonConvert.DeserializeObject<IDictionary<string, object>>(_employee.Address, new DictionaryConverter());
}
```
The final step is to change the template:
```html
<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>
```
Then after restarting you'll get a preview like this:

![Final preview](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3iv99df5w6o8sm9lxuzi.png)

### Conclusion

I hope you'll find some answers on how to create a DotLiquid template in Asp.Net Core in this article. I didn't cover the usual control flow or iteration tags as the documentation of the Liquid templates already has all the basics and what's nice in DotLiquid is that you can use almost anything from the liquid template language as is, you can check their docs [here](https://shopify.github.io/liquid/basics/introduction/).

You'll find the final code on [this](https://github.com/elf-is/DotLiquidTest) Github repository.