是否可以将回调添加到Javascript并在Blazor中获取结果?除了JS Promises。
例如,假设我要加载文件
JavaScript代码
window.readFile = function(filePath, callBack) {
var reader = new FileReader();
reader.onload = function (evt) {
callBack(evt.target.result);
};
reader.readAsText(filePath);
}
我能在Blazor C#中拥有类似的东西吗
// read file content and output result to console
void GetFileContent() {
JsRuntime.InvokeAsync<object>("readFile", "file.txt", (string text) => {
Console.Write(text);
});
}
或者也许是这样
// read with javascript
void ReadFileContent() {
JsRuntime.InvokeAsync<object>("readFile", "file.txt", "resultCallbackMethod");
}
// output result callback to console
void resultCallbackMethod(string text) {
Console.Write(text);
}
谢谢
答案 0 :(得分:3)
更新1:
重新阅读您的问题后,我认为这将涵盖您的第二个示例
我认为您可以选择实现用于处理调用的JS代理功能。像这样:
更新2:
代码已更新为功能版本(但未经深度测试),您也可以在blazorfiddle.com中找到有效的示例
JAVASCRIPT CODE
// Target Javascript function
window.readFile = function (filePath, callBack) {
var fileInput = document.getElementById('fileInput');
var file = fileInput.files[0];
var reader = new FileReader();
reader.onload = function (evt) {
callBack(evt.target.result);
};
reader.readAsText(file);
}
// Proxy function
// blazorInstance: A reference to the actual C# class instance, required to invoke C# methods inside it
// blazorCallbackName: parameter that will get the name of the C# method used as callback
window.readFileProxy = (instance, callbackMethod, fileName) => {
// Execute function that will do the actual job
window.readFile(fileName, result => {
// Invoke the C# callback method passing the result as parameter
instance.invokeMethodAsync(callbackMethod, result);
});
}
C#代码
@page "/"
@inject IJSRuntime jsRuntime
<div>
Select a text file:
<input type="file" id="fileInput" @onchange="@ReadFileContent" />
</div>
<pre>
@fileContent
</pre>
Welcome to your new app.
@code{
private string fileContent { get; set; }
public static object CreateDotNetObjectRefSyncObj = new object();
public async Task ReadFileContent(UIChangeEventArgs ea)
{
// Fire & Forget: ConfigureAwait(false) is telling "I'm not expecting this call to return a thing"
await jsRuntime.InvokeAsync<object>("readFileProxy", CreateDotNetObjectRef(this), "ReadFileCallback", ea.Value.ToString()).ConfigureAwait(false);
}
[JSInvokable] // This is required in order to JS be able to execute it
public void ReadFileCallback(string response)
{
fileContent = response?.ToString();
StateHasChanged();
}
// Hack to fix https://github.com/aspnet/AspNetCore/issues/11159
protected DotNetObjectRef<T> CreateDotNetObjectRef<T>(T value) where T : class
{
lock (CreateDotNetObjectRefSyncObj)
{
JSRuntime.SetCurrentJSRuntime(jsRuntime);
return DotNetObjectRef.Create(value);
}
}
}
答案 1 :(得分:1)
我相信您正在此处寻找有关文档的信息: https://docs.microsoft.com/en-us/aspnet/core/blazor/javascript-interop?view=aspnetcore-3.0#invoke-net-methods-from-javascript-functions
它显示了如何从Javascript调用Razor.Net。 该文档提供了更多信息,但是从本质上讲,您将需要在razor中的方法上具有[JSInvokable]属性,并通过javascript中的DotNet.invokeMethod进行调用。
答案 2 :(得分:0)
感谢@Henry Rodriguez。我从中创建了一些东西,我相信它也可能会有所帮助。
请注意,DotNetObjectRef.Create(this)在其他方法中仍然可以正常工作。仅在Preview6中注意到Blazor生命周期事件存在问题。 https://github.com/aspnet/AspNetCore/issues/11159。
这是我的新实现。
<div>
Load the file content
<button @click="@ReadFileContent">Get File Content</button>
</div>
<pre>
@fileContent
</pre>
Welcome to your new app.
@code{
string fileContent;
//The button onclick will call this.
void GetFileContent() {
JsRuntime.InvokeAsync<object>("callbackProxy", DotNetObjectRef.Create(this), "readFile", "file.txt", "ReadFileCallback");
}
//and this is the ReadFileCallback
[JSInvokable] // This is required for callable function in JS
public void ReadFileCallback(string filedata) {
fileContent = filedata;
StateHasChanged();
}
在_Host.cshtml或index.html中,包含回调代理连接器
// Proxy function that serves as middlemen
window.callbackProxy = function(dotNetInstance, callMethod, param, callbackMethod){
// Execute function that will do the actual job
window[callMethod](param, function(result){
// Invoke the C# callback method passing the result as parameter
return dotNetInstance.invokeMethodAsync(callbackMethod, result);
});
return true;
};
// Then The Javascript function too
window.readFile = function(filePath, callBack) {
var reader = new FileReader();
reader.onload = function (evt) {
callBack(evt.target.result);
};
reader.readAsText(filePath);
}
这完全可以满足我的需要,并且可以重复使用。
答案 3 :(得分:0)
使用此页面上的提示,我想出了一个更通用的版本,几乎可以与任何基于回调的函数一起使用。
您现在可以调用最后一个参数是回调的任何函数。您可以将任意数量的参数传递给函数,并且回调可以返回任意数量的参数。
函数InvokeJS返回CallbackerResponse的实例,该实例可用于获取任何响应参数的类型化值。有关更多信息,请参见示例和代码。
基于OP回调(fileContents(字符串)):
示例1(带有等待状态的C#Blazor):
var response = await Callbacker.InvokeJS("window.readFile", filename);
var fileContents = response.GetArg<string>(0);
// fileContents available here
示例2(带有回调的C#Blazor):
Callbacker.InvokeJS((response) => {
var fileContents = response.GetArg<string>(0);
// fileContents available here
}, "window.readFile", filename);
基于常见的回调(错误(字符串),数据(对象)):
示例3(带有等待状态的C#Blazor):
// 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);
在Blazor Program.cs主要添加单例(或根据需要设置作用域)Callbacker
builder.Services.AddSingleton<Services.Callbacker>();
在Blazor页面中添加Callbacker服务。示例: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);
};