Using the tips on this page I came up with a more generic version that works with nearly any callback based function.
Update:
You can now call any function whose last argument is a callback. You can pass any number of arguments to the function and the callback can have any number of arguments returned.
The function InvokeJS returns an instance of CallbackerResponse which can be used to get the typed value of any of the response arguments. See examples and code for more information.
Based on OP callback (fileContents (string)):
Example 1 (C# Blazor with await):
var response = await Callbacker.InvokeJS("window.readFile", filename);
var fileContents = response.GetArg<string>(0);
// fileContents available here
Example 2 (C# Blazor with callback):
Callbacker.InvokeJS((response) => {
var fileContents = response.GetArg<string>(0);
// fileContents available here
}, "window.readFile", filename);
Based on common callback (error (string), data (object)):
Example 3 (C# Blazor with await):
// To call a javascript function with the arguments (arg1, arg2, arg3, callback)
// and where the callback arguments are (err, data)
var response = await Callbacker.InvokeJS("window.myObject.myFunction", arg1, arg2, arg3);
// deserialize callback argument 0 into C# string
var err = response.GetArg<string>(0);
// deserialize callback argument 1 into C# object
var data = response.GetArg<MyObjectType>(1);
In your Blazor Program.cs Main add singleton (or scoped if desired) Callbacker
builder.Services.AddSingleton<Services.Callbacker>();
Add Callbacker service in your Blazor page. Example: MyPage.razor.cs
[Inject]
public Callbacker Callbacker { get; set; }
C#
using Microsoft.JSInterop;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Home.Services
{
public class CallbackerResponse
{
public string[] arguments { get; private set; }
public CallbackerResponse(string[] arguments)
{
this.arguments = arguments;
}
public T GetArg<T>(int i)
{
return JsonConvert.DeserializeObject<T>(arguments[i]);
}
}
public class Callbacker
{
private IJSRuntime _js = null;
private DotNetObjectReference<Callbacker> _this = null;
private Dictionary<string, Action<string[]>> _callbacks = new Dictionary<string, Action<string[]>>();
public Callbacker(IJSRuntime JSRuntime)
{
_js = JSRuntime;
_this = DotNetObjectReference.Create(this);
}
[JSInvokable]
public void _Callback(string callbackId, string[] arguments)
{
if (_callbacks.TryGetValue(callbackId, out Action<string[]> callback))
{
_callbacks.Remove(callbackId);
callback(arguments);
}
}
public Task<CallbackerResponse> InvokeJS(string cmd, params object[] args)
{
var t = new TaskCompletionSource<CallbackerResponse>();
_InvokeJS((string[] arguments) => {
t.TrySetResult(new CallbackerResponse(arguments));
}, cmd, args);
return t.Task;
}
public void InvokeJS(Action<CallbackerResponse> callback, string cmd, params object[] args)
{
_InvokeJS((string[] arguments) => {
callback(new CallbackerResponse(arguments));
}, cmd, args);
}
private void _InvokeJS(Action<string[]> callback, string cmd, object[] args)
{
string callbackId;
do
{
callbackId = Guid.NewGuid().ToString();
} while (_callbacks.ContainsKey(callbackId));
_callbacks[callbackId] = callback;
_js.InvokeVoidAsync("window._callbacker", _this, "_Callback", callbackId, cmd, JsonConvert.SerializeObject(args));
}
}
}
JS
window._callbacker = function(callbackObjectInstance, callbackMethod, callbackId, cmd, args){
var parts = cmd.split('.');
var targetFunc = window;
var parentObject = window;
for(var i = 0; i < parts.length; i++){
if (i == 0 && part == 'window') continue;
var part = parts[i];
parentObject = targetFunc;
targetFunc = targetFunc[part];
}
args = JSON.parse(args);
args.push(function(e, d){
var args = [];
for(var i in arguments) args.push(JSON.stringify(arguments[i]));
callbackObjectInstance.invokeMethodAsync(callbackMethod, callbackId, args);
});
targetFunc.apply(parentObject, args);
};