Clearscript: Pass C# object to JavaScript callback

Created on 11 Aug 2020  路  4Comments  路  Source: microsoft/ClearScript

Hi, I'm new with ClearScript and I could not figure out how to call C# object function after passing it to JavaScript callback.

Here is the code:

C#

public class RowData : RowInterface
{
        public string raw(string name)
        {
            return "Something";
        }
}

public void execute(string query, ScriptObject cb) {
    var row = new RowData();
    cb.Invoke(false, row);
}

In JavaScript

.execute("Some query", row => {
     row.raw("abc");
});

I got error: raw is not a function

I can call the object function if I add it with the addHostObject

engine.AddHostObject("row", row);

However, I can't do it because it's a js callback

=============
In Swift, I can do it by implementing the JSExport interface

public protocol RowDataProtocolExport: JSExport {
  // Get raw data
  func raw(_ name: String) -> String?
}

So any object that confirms the protocol can be passed directly to JavaScript and we can call the raw function.

Is there any way that achieves the same thing in ClearScript?

Thank you!

question

Most helpful comment

Hi @huyphams,

Regarding these lines:

``` C#
var list = cb.Engine.Script.Array(new List { new RowData(), new RowData() });
var data = cb.Engine.Script.Object(new { rows = list });

You're invoking `Array` and `Object` as functions rather than constructors, which is probably not your intention. Particularly problematic is the `Array` invocation, as passing in a managed list is unlikely to have the desired effect. The `Object` call has no effect at all; that is, `data` ends up being set to `new { rows = list }`.

Since `list` must be a JavaScript array, you can construct it like this:

``` C#
var arrayFunc = (ScriptObject)cb.Engine.Script.Array;
var list = arrayFunc.Invoke(true, new RowData(), new RowData());

On the other hand, if you can avoid Array.prototype.forEach, you can avoid script objects completely:

``` C#
// C#
var list = new List { new RowData(), new RowData() };
var data = new { rows = list };
cb.Invoke(false, data);

And then:

``` JavaScript
// JavaScript
for (let row of data.rows) {
    Console.WriteLine(row.raw('abc'));
}

Good luck!

All 4 comments

Hi @huyphams,

We can't reproduce your issue with the code you provided. Here's the full test program we used:

C# public class RowData { public string raw(string name) { return "Something"; } } public class Executor { public void execute(string query, ScriptObject cb) { var row = new RowData(); cb.Invoke(false, row); } } public static class TestApp { public static void Main() { using (var engine = new V8ScriptEngine()) { engine.AddHostType(typeof(Console)); engine.Script.exec = new Executor(); engine.Execute(@" exec.execute('Some query', row => { Console.WriteLine(row.raw('abc')); }); "); } } }

This program correctly writes "Something" to the console. Note that you didn't post the RowInterface definition, so we left it out.

Is there something you're doing differently?

Thanks!

Oh thanks @ClearScriptLib I can confirm that your code works, here is my code. I think the issue was related to the way I pass the value:

            var list = cb.Engine.Script.Array(new List<RowData> { new RowData(), new RowData() });
            var data = cb.Engine.Script.Object(new { rows = list });
            cb.Invoke(false, data);

Full code:

    public class RowData
    {
        public string raw(string name)
        {
            return "Something";
        }
    }

    public partial class MainWindow : Window
    {
        public void execute(string query, ScriptObject cb)
        {
            var list = cb.Engine.Script.Array(new List<RowData> { new RowData(), new RowData() });
            var data = cb.Engine.Script.Object(new { rows = list });
            cb.Invoke(false, data);
        }

        public MainWindow()
        {
            InitializeComponent();

            using (var engine = new V8ScriptEngine())
            {
                engine.AddHostType(typeof(Console));
                engine.AddHostObject("context", this);
                engine.Execute(@"
                     var demo = function(context) {
                         context.execute('some query', data => {
                                data.rows.forEach(function(row) {
                                    Console.WriteLine(row.raw('abc'));     
                                });
                         });
                     }
                ");
                engine.Evaluate("demo(context);");
            }
        }
    }

Hi @huyphams,

Regarding these lines:

``` C#
var list = cb.Engine.Script.Array(new List { new RowData(), new RowData() });
var data = cb.Engine.Script.Object(new { rows = list });

You're invoking `Array` and `Object` as functions rather than constructors, which is probably not your intention. Particularly problematic is the `Array` invocation, as passing in a managed list is unlikely to have the desired effect. The `Object` call has no effect at all; that is, `data` ends up being set to `new { rows = list }`.

Since `list` must be a JavaScript array, you can construct it like this:

``` C#
var arrayFunc = (ScriptObject)cb.Engine.Script.Array;
var list = arrayFunc.Invoke(true, new RowData(), new RowData());

On the other hand, if you can avoid Array.prototype.forEach, you can avoid script objects completely:

``` C#
// C#
var list = new List { new RowData(), new RowData() };
var data = new { rows = list };
cb.Invoke(false, data);

And then:

``` JavaScript
// JavaScript
for (let row of data.rows) {
    Console.WriteLine(row.raw('abc'));
}

Good luck!

Wow, it works! I can't thank you enough.

On the other hand, if you can avoid Array.prototype.forEach, you can avoid script objects completely

Thanks for the tip, however, it was a shared library on multiple platforms so I could not avoid it.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

egooner picture egooner  路  3Comments

fasihrana picture fasihrana  路  8Comments

JohnLudlow picture JohnLudlow  路  7Comments

flat-eric147 picture flat-eric147  路  5Comments

andrejpk picture andrejpk  路  7Comments