FluentEmail icon indicating copy to clipboard operation
FluentEmail copied to clipboard

ForEach (Liquid) does't work

Open michaelsync opened this issue 4 years ago • 2 comments

I have this code. This Name: {{Name }} works but not the for loop. What did I miss?

I am sending this from .NET core console app. (Not MVC, I don't need the layout file too)

var template = @"
                            Name: {{ Name }} <br/>

                            <!-- if array = [1,2,3,4,5,6] -->
                            {% for item in array limit:2 %}
                              {{ item }}
                            {% endfor %}

                            ";


            SmtpClient client = new SmtpClient();
            client.UseDefaultCredentials = true;
            client.DeliveryMethod = SmtpDeliveryMethod.Network;
            client.Host = smtpServer;
            client.Port = 25;


            FluentEmail.Core.Email.DefaultSender = new SmtpSender(client);
            var options = new LiquidRendererOptions
            {};
            FluentEmail.Core.Email.DefaultRenderer = new LiquidRenderer(Options.Create(options));

            await FluentEmail.Core.Email
                .From(sender)
                .To(reportingEmail)
                .Subject(subject)
                .UsingTemplate(template, model)
                .SendAsync();

michaelsync avatar Nov 20 '21 07:11 michaelsync

Had the same issue, documentation is non-existent but after trawling through the code you can use the following. Issue stems from the fact that Liquid is a secure language and by default it doesn't trust unknown classes.

var options = new LiquidRendererOptions
{
    FileProvider = fileProvider,
    ConfigureTemplateContext = (context, model) =>
    {
        context.MemberAccessStrategy = new UnsafeMemberAccessStrategy();
    }
};

FluentEmail.Core.Email.DefaultRenderer = new LiquidRenderer(Microsoft.Extensions.Options.Options.Create(options));
public class UnsafeMemberAccessStrategy : IMemberAccessStrategy
{
    public MemberNameStrategy MemberNameStrategy { get; set; } = MemberNameStrategies.Default;
    private readonly MemberAccessStrategy baseMemberAccessStrategy = new MemberAccessStrategy();
    public bool IgnoreCasing { get; set; }

    public IMemberAccessor GetAccessor(Type type, string name)
    {
        var accessor = baseMemberAccessStrategy.GetAccessor(type, name);
        if (accessor != null)
        {
            return accessor;
        }

        baseMemberAccessStrategy.Register(type, name);
        return baseMemberAccessStrategy.GetAccessor(type, name);
    }
    public void Register(Type type, string name, IMemberAccessor getter)
    {
        baseMemberAccessStrategy.Register(type, name, getter);
    }
}

twisted89 avatar Oct 18 '24 12:10 twisted89

@twisted89 thank you so much, battled with this for a whole day, how you found this solution is pure genius. I suspected this and created defined types thinking this would solve the problem:

 Task SendEmailAsync<T>(string tag, string mailTo, string subject, string template, T model) where T : class;
 public async Task SendEmailAsync<T>(string tag, string mailTo, string subject, string template, T model) where T : class
 {
     var path = Path.Combine($"{Directory.GetCurrentDirectory()}/EmailTemplates", template);
     var res = await _fluentEmail
         .To(mailTo)
         .Tag(tag)
         .Subject(subject)
         .UsingTemplateFromFile<T>(path, model)
         .SendAsync();
     Console.WriteLine(res);
 }

Raw data: {{ Alerts | json }} shows a json dump which means it works, but the values weren't being displayed, thanks once again

FrankDupree avatar Apr 18 '25 03:04 FrankDupree