Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Possible to Support Async members in RenderAsync? #368

Open
snosrap opened this issue Jul 6, 2021 · 1 comment
Open

Possible to Support Async members in RenderAsync? #368

snosrap opened this issue Jul 6, 2021 · 1 comment

Comments

@snosrap
Copy link

snosrap commented Jul 6, 2021

I have some Async members in an object that I would like to render with RenderAsync.

For example:

var data = new { value = Task.FromResult(42) };

However, if I send this data to RenderAsync, value remains a Task<int> — it doesn't get awaited to its underlying value:

var result = await Template.Parse("Hello {{ value }}").RenderAsync(data);
Console.WriteLine(result);

// Output: Hello System.Threading.Tasks.Task`1[System.Int32]

Of course, it wouldn't make sense to await Tasks in Render, but for RenderAsync it would be useful to await any Task objects and display their result values (i.e., "Hello 42" in the above example)

Any thoughts on how to best accomplish this? Am I missing some configuration setting that enables this?

@snosrap
Copy link
Author

snosrap commented Jul 6, 2021

Here's the approach I've come up with, but open to suggestions if there's a better approach

using System;
using System.Threading.Tasks;
using Scriban;
using Scriban.Runtime;
using Scriban.Syntax;

namespace ScribanAsync
{
    public class TemplateContextAsync : TemplateContext
    {
        public override async ValueTask<object> EvaluateAsync(ScriptNode scriptNode, bool aliasReturnedFunction)
        {
            // base implementation
            var value = base.EvaluateAsync(scriptNode, aliasReturnedFunction);

            // is the object also a Task? If so, await its value
            if ((await value) is Task valueTask)
            {
                // TODO: Is there an alternative to using dynamic?
                // TResult of Task<TResult> is unknown here.
                var result = await (dynamic)valueTask;

                // return a ValueTask of the awaited value
                return new ValueTask<object>(result);
            }

            // object is not a Task, just return the ValueTask<object> as usual
            return value;
        }
    }

    class Program
    {
        static async Task Main(string[] args)
        {
            var template = "Hello {{ a }} | {{ b }} | {{ c }}";
            var data = new
            {
                a = "World",
                b = Task.FromResult(42),
                c = Task.Run(async () => { await Task.Delay(5000); return "Thanks for waiting"; })
            };

            // Current approach
            // > Hello World | System.Threading.Tasks.Task`1[System.Int32] | System.Threading.Tasks.UnwrapPromise`1[System.String]
            var result1 = await Template.Parse(template).RenderAsync(data);
            Console.WriteLine(result1);

            // New approach
            // > Hello World | 42 | Thanks for waiting
            var scriptObject = new ScriptObject();
            scriptObject.Import(data);
            var context = new TemplateContextAsync();
            context.PushGlobal(scriptObject);
            var result2 = await Template.Parse(template).RenderAsync(context);
            Console.WriteLine(result2);
            context.PopGlobal();
        }
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants