Drops

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 is best explained by example.

If you execute the following code, it might not produce the result you expect.

public class User
{
    public string Name { get; set; }
}

Template template = Template.Parse("{{ user.name }}");

string result = template.Render(Hash.FromAnonymousObject(new
{
    user = new User { Name = "Tim" }
}));

You could reasonably expect the output to be Tim. However, it will actually be a blank string. The explanation follows.

Explanation

DotLiquid, by default, only accepts a limited number of types as parameters to the Render method - including the .NET primitive types(int, float, string, etc.), and some collection types including IDictionary, IList and IIndexable (a custom DotLiquid interface).

If it supported arbitrary types, then it could result in properties or methods being unintentionally exposed to template authors. To prevent this, DotLiquid uses Drop objects. Drops use an opt-in approach to exposing object data.

You can derive your own Drop class, and pass that to Template.Render instead of the original User object:

public class User
{
    public string Name { get; set; }
    public string Email { get; set; }
}

public class UserDrop : Drop
{
    private readonly User _user;

    public string Name
    {
        get { return _user.Name; }
    }

    public UserDrop(User user)
    {
        _user = user;
    }
}

Template template = Template.Parse("Name: {{ user.name }}; Email: {{ user.email }};");
string result = template.Render(Hash.FromAnonymousObject(new
{
    user = new UserDrop(new User
    {
        Name = "Tim",
        Email = "me@mydomain.com"
    })
}));

The result is Name: Tim; Email:, so the attempt to call the email property was ignored, since this is not exposed by UserDrop.

POCO classes

As of v1.7.0, DotLiquid provides an API to register specific types as "safe", which means you can use types that don't inherit from Drop. Read more on the format.

Tips

  • By default, DotLiquid uses a Ruby naming convention for Drop properties. So if your Drop object contains CamelCased properties, they will contain underscores when exposed in your template. For example, ProductId on your drop becomes product_id in your template. If you want to change this behaviour, you can set Template.NamingConvention to new NamingConventions.CSharpNamingConvention().

  • Exceptions thrown by your Drop objects will result in the general message Liquid error: Exception has been thrown by the target of an invocation.. You can make DotLiquid throw the original exception by using the Render overloads that take a RenderParameters instance and setting RethrowErrors = true.

Taking it further

Drops are quite flexible. You can also use them to provide a "catch-all" method:

public class CatchAllDrop : Drop
{
    public override object BeforeMethod(string method)
    {
        return "method: " + method;
    }
}

Template template = Template.Parse(" {{ catchall.unknown }} ");
string result = template.Render(Hash.FromAnonymousObject(new { catchall = new CatchAllDrop() }));

The result is method: unknown.

For a more hands-on explanation of all the capabilities of Drops, have a look at the Drop tests within DotLiquid's unit test suite.