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. Drop
s 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 yourDrop
object contains CamelCased properties, they will contain underscores when exposed in your template. For example,ProductId
on your drop becomesproduct_id
in your template. If you want to change this behaviour, you can setTemplate.NamingConvention
tonew 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 theRender
overloads that take aRenderParameters
instance and settingRethrowErrors = 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.