第六章 JavaScript 互操(3)JS调用.NET
调用.NET方法
一、调用静态.NET方法
Blazor框架中提供了DotNet.invokeMethodAsync
和DotNet.invokeMethod
静态方法,用于在JS脚本中直接调用指定的.NET方法
同步调用
DotNet.invokeMethod('{ASSEMBLY NAME}', '{.NET METHOD ID}', {ARGUMENTS})
:通过这个方法,可以在Blazor应用程序中通过JavaScript代码来调用.NET方法,仅对客户端组件执行同步操作,并返回操作结果。
异步调用
DotNet.invokeMethodAsync('{ASSEMBLY NAME}', '{.NET METHOD ID}', {ARGUMENTS})
:通过这个方法,可以在Blazor应用程序中通过JavaScript代码来调用.NET方法,同时对服务器端和客户端组件执行异步操作,返回表示操作结果的JS Promise
{ASSEMBLY NAME}
:应用的程序集名称{.NET METHOD ID}
:要调用的.NET静态方法名{ARGUMENTS}
:传递给该方法参数,以逗号分隔,每个参数都必须可执行 JSON 序列化- 调用.NET 静态方法后,返回的是一个
Promise
对象,而我们需要的结果是包装在PromiseResult
中的。若要获取PromiseResult
中的结果,需要使用then()
方法
对于服务器端组件,建议使用异步函数 (invokeMethodAsync
) 而不是同步版本 (invokeMethod
)。
注意,JS直接调用的方法必须是公开、静态的,且需要通过[JSInvokable]
特性定义
-
示例-CallDotnet1.razor(无参的静态方法)
@page "/call-dotnet-1" @rendermode InteractiveServer <PageTitle>Call .NET 1</PageTitle><h1>Call .NET Example 1</h1><p><button onclick="returnArrayAsync()">Trigger .NET static method</button> </p><p>See the result in the developer tools console. </p><script>window.returnArrayAsync = () => {DotNet.invokeMethodAsync('BlazorAppServer', 'ReturnArrayAsync1').then(data => { console.log(data); });}; </script>@code {[JSInvokable]public static Task<int[]> ReturnArrayAsync1(){return Task.FromResult(new int[] { 1, 2, 3 });} }
-
示例-CallDotnet2.razor(带参的静态方法)
@page "/call-dotnet-2" @rendermode InteractiveServer<PageTitle>Call .NET 2</PageTitle><h1>Call .NET Example 2</h1><p><button onclick="returnArrayAsync(5)">Trigger .NET static method</button> </p><p>See the result in the developer tools console. </p><script>window.returnArrayAsync = (startPosition) => {DotNet.invokeMethodAsync('BlazorAppServer', 'ReturnArrayAsync2', startPosition).then(data => { console.log(data); });}; </script>@code {[JSInvokable]public static Task<int[]> ReturnArrayAsync2(int startPosition){return Task.FromResult(Enumerable.Range(startPosition, 3).ToArray());} }
另起别名
在使用时发现,如果在Blazor应用中存在两个同名字的静态.NET方法(即使参数列表不同),且在不同的组件中使用JS函数调用了同一个名字的.NET静态方法,会出现异常情况。可以通过给函数起不同的别名来避免这种异常。
要给JS调用的.NET静态方法起别名,可以使用[JSInvokable(anotherName)]
来指定别名,然后在JS脚本中使用不同的别名来调用.NET静态方法就好。
-
示例
......@code {[JSInvokable("DifferentMethodName")]public static Task<int[]> ReturnArrayAsync(){return Task.FromResult(new int[] { 1, 2, 3 });} }
二、调用.NET实例方法
如果要在JS上调用实例的.NET方法,可以进行如下操作:
- 通过使用 DotNetObjectReference.Create() 将.NET实例进行封装,然后再将封装后的实例引用传递给 JS
- 在JS中,使用传递过来的
DotNetObjectReference
中的invokeMethodAsync
(推荐)或invokeMethod
(仅限客户端组件) 调用 .NET 实例方法
invokeMethodAsync('{.NET METHOD ID}', {ARGUMENTS})
:异步调用.NET对象中的实例方法,返回表示操作结果的 JS Promise
invokeMethod('{.NET METHOD ID}', {ARGUMENTS})
:同步调用.NET对象中的实例方法,返回操作的结果
-
示例
@page "/js-invoke-dotnet-method" @rendermode InteractiveServer @implements IDisposable @inject IJSRuntime JS<h3>JsInvokeDotNetMethod</h3><p><label>Name: <input @bind="name" /></label> </p><p><button @onclick="TriggerDotNetInstanceMethod">Trigger .NET instance method</button> </p><p>@result </p><script>window.invokeDotNetMethod = (dotNetHelper, name) => {return dotNetHelper.invokeMethodAsync('GetHelloMessage', name);}; </script>@code {private string? name;private string? result;private DotNetObjectReference<JsInvokeDotNetMethod>? objRef;protected override void OnInitialized(){objRef = DotNetObjectReference.Create(this);}public async Task TriggerDotNetInstanceMethod(){result = await JS.InvokeAsync<string>("invokeDotNetMethod", objRef, name);}[JSInvokable]public string GetHelloMessage(string passedName) => $"Hello, {passedName}!";public void Dispose() => objRef?.Dispose(); }
注意,在JS中调用的.NET方法不管是不是静态的,都必须使用[JSInvokable]
特性注解,此外,要记得释放资源,防止内存泄漏
在上述的实例中,.NET的DotNetObjectReference
传递给了单个JavaScript方法进行使用,如果希望DotNetObjectReference
可以共给多个函数使用,可以在DotNetObjectReference
传递到JS后,使用JS的变量来进行保存。
-
示例
@page "/call-dotnet-example-one-helper" @rendermode InteractiveServer @implements IDisposable @inject IJSRuntime JS<PageTitle>Call .NET Example</PageTitle><HeadContent><script>class GreetingHelpers {static dotNetHelper;static setDotNetHelper(value) {GreetingHelpers.dotNetHelper = value;}static async sayHello() {const msg = await GreetingHelpers.dotNetHelper.invokeMethodAsync('GetHelloMessage');alert(`Message from .NET: "${msg}"`);}static async welcomeVisitor() {const msg = await GreetingHelpers.dotNetHelper.invokeMethodAsync('GetWelcomeMessage');alert(`Message from .NET: "${msg}"`);}}window.GreetingHelpers = GreetingHelpers;</script> </HeadContent><h1>Pass <code>DotNetObjectReference</code> to a JavaScript class</h1><p><label>Message: <input @bind="name" /></label> </p><p><button onclick="GreetingHelpers.sayHello()">Trigger JS function <code>sayHello</code></button> </p><p><button onclick="GreetingHelpers.welcomeVisitor()">Trigger JS function <code>welcomeVisitor</code></button> </p>@code {private string? name;private DotNetObjectReference<CallDotNetExampleOneHelper>? dotNetHelper;protected override async Task OnAfterRenderAsync(bool firstRender){if (firstRender){dotNetHelper = DotNetObjectReference.Create(this);await JS.InvokeVoidAsync("GreetingHelpers.setDotNetHelper", dotNetHelper);}}[JSInvokable]public string GetHelloMessage() => $"Hello, {name}!";[JSInvokable]public string GetWelcomeMessage() => $"Welcome, {name}!";public void Dispose(){dotNetHelper?.Dispose();} }
三、调用.NET泛型类方法
-
GenericType.cs
public class GenericType<TValue> {public TValue? Value { get; set; }[JSInvokable]public void Update(TValue newValue){Value = newValue;Console.WriteLine($"Update: GenericType<{typeof(TValue)}>: {Value}");}[JSInvokable]public async void UpdateAsync(TValue newValue){await Task.Yield();Value = newValue;Console.WriteLine($"UpdateAsync: GenericType<{typeof(TValue)}>: {Value}");} }
-
GenericsExample.razor
@page "/generics-example" @rendermode InteractiveServer @using System.Runtime.InteropServices @implements IDisposable @inject IJSRuntime JS<p><button @onclick="InvokeInterop">Invoke Interop</button> </p><ul><li>genericType1: @genericType1?.Value</li><li>genericType2: @genericType2?.Value</li> </ul><script>const randomInt = () => Math.floor(Math.random() * 99999);window.invokeMethodsAsync = async (syncInterop, dotNetHelper1, dotNetHelper2) => {var n = randomInt();console.log(`JS: invokeMethodAsync:Update('string ${n}')`);await dotNetHelper1.invokeMethodAsync('Update', `string ${n}`);n = randomInt();console.log(`JS: invokeMethodAsync:UpdateAsync('string ${n}')`);await dotNetHelper1.invokeMethodAsync('UpdateAsync', `string ${n}`);if (syncInterop) {n = randomInt();console.log(`JS: invokeMethod:Update('string ${n}')`);dotNetHelper1.invokeMethod('Update', `string ${n}`);}n = randomInt();console.log(`JS: invokeMethodAsync:Update(${n})`);await dotNetHelper2.invokeMethodAsync('Update', n);n = randomInt();console.log(`JS: invokeMethodAsync:UpdateAsync(${n})`);await dotNetHelper2.invokeMethodAsync('UpdateAsync', n);if (syncInterop) {n = randomInt();console.log(`JS: invokeMethod:Update(${n})`);dotNetHelper2.invokeMethod('Update', n);}}; </script>@code {private GenericType<string> genericType1 = new() { Value = "string 0" };private GenericType<int> genericType2 = new() { Value = 0 };private DotNetObjectReference<GenericType<string>>? objRef1;private DotNetObjectReference<GenericType<int>>? objRef2;protected override void OnInitialized(){objRef1 = DotNetObjectReference.Create(genericType1);objRef2 = DotNetObjectReference.Create(genericType2);}public async Task InvokeInterop(){var syncInterop = RuntimeInformation.IsOSPlatform(OSPlatform.Create("BROWSER"));await JS.InvokeVoidAsync("invokeMethodsAsync", syncInterop, objRef1, objRef2);}public void Dispose(){objRef1?.Dispose();objRef2?.Dispose();} }
四、调用.NET实例委托
利用.NET中的实例委托,在方便在JS中去安全的更新Blazor的UI,JS中调用.NET实例委托的方式与调用实例方法是一样的。
-
MessageUpdateInvokeHelper.cs
public class MessageUpdateInvokeHelper(Action action) {private readonly Action action = action;[JSInvokable]public void UpdateMessageCaller(){action.Invoke();} }
-
App.razor
...... <body>......<script>window.updateMessageCaller = (dotNetHelper) => {dotNetHelper.invokeMethodAsync('UpdateMessageCaller');dotNetHelper.dispose();}</script> </body> ......
-
ListItemTest.razor
@inject IJSRuntime JS<li>@message<button @onclick="InteropCall" style="display:@display">InteropCall</button> </li>@code {private string message = "Select one of these list item buttons.";private string display = "inline-block";private MessageUpdateInvokeHelper? messageUpdateInvokeHelper;protected override void OnInitialized(){messageUpdateInvokeHelper = new MessageUpdateInvokeHelper(UpdateMessage);}protected async Task InteropCall(){if (messageUpdateInvokeHelper is not null){await JS.InvokeVoidAsync("updateMessageCaller", DotNetObjectReference.Create(messageUpdateInvokeHelper));}}private void UpdateMessage(){message = "UpdateMessage Called!";display = "none";StateHasChanged();} }
-
JSInvokeActionTest.razor
@page "/js-invoke-action" @rendermode InteractiveServer<PageTitle>Js Invoke Action</PageTitle><h1>Js Invoke Action</h1><ul><ListItemTest/><ListItemTest/><ListItemTest/><ListItemTest/> </ul>
传递JavaScript引用到.NET
如果想要将JS中的引用传递给JS中所调用的.NET方法,需要在JS脚本中使用DotNet.createJSObjectReference(jsObject)
或DotNet.createJSStreamReference(streamReference)
方法对JS的引用对象再次封装一下,再进行传递。
一、引用传递
IJSObjectReference DotNet.createJSObjectReference(jsObject)
:使用指定的JavaScript的Object
对象创建可以传递给.NET方法的IJSObjectReference
对象
-
示例
@page "/js-object-to-dotnet" @rendermode InteractiveServer @inject IJSRuntime JS <HeadContent><script>window.passObject = () => {DotNet.invokeMethodAsync('BlazorAppServer', 'ReceiveWindowObject',DotNet.createJSObjectReference(window));};</script> </HeadContent><h3>JSObjectToDotNet</h3><button @onclick="InvokeJS">传递JS引用 </button>@code {private async Task InvokeJS(){await JS.InvokeVoidAsync("passObject");}[JSInvokable]public static void ReceiveWindowObject(IJSObjectReference objRef){Console.WriteLine(objRef.ToString());} }
注意,在使用引用传递时候要记得释放资源,上面的例子中,因为JS的Object
对象并没有创建变量保存在JS中,因此并不需要手动去释放。如果在JS中保存了传递的引用变量,那么则需要通过DotNet.disposeJSObjectReference(jsObjectReference)
释放以避免JS的内存泄露。
-
示例
@page "/js-object-to-dotnet" @rendermode InteractiveServer @inject IJSRuntime JS <HeadContent><script>window.passObject = () => {var jsObjectReference = DotNet.createJSObjectReference(window);DotNet.invokeMethodAsync('BlazorAppServer', 'ReceiveWindowObject', jsObjectReference);DotNet.disposeJSObjectReference(jsObjectReference);};</script> </HeadContent><h3>JSObjectToDotNet</h3><button @onclick="InvokeJS">传递JS引用 </button>@code {private async Task InvokeJS(){await JS.InvokeVoidAsync("passObject");}[JSInvokable]public static void ReceiveWindowObject(IJSObjectReference objRef){Console.WriteLine(objRef.ToString());} }
二、流传递
JS中创建流对象
DotNet.createJSStreamReference(streamReference)
:使用指定的JS流对象创建可以传递给.NET方法的IJSStreamReference
对象。
streamReference
:为ArrayBuffer
、Blob
或任何类型化数组(例如Uint8Array
或Float32Array
)
IJSStreamReference对象
IJSStreamReference
为在C#中使用的,用于承载JavaScript流对象的类型。
-
Length
:IJSStreamReference
对象的属性,获取流中数据的长度。 -
ValueTask<Stream> OpenReadStreamAsync(long maxAllowedSize = 512000, CancellationToken cancellationToken = default)
:IJSStreamReference
对象的方法,用于打开流对象。maxAllowedSize
:JavaScript 中读取操作允许的最大字节数,如果未指定,则默认为 512,000 个字节cancellationToken
:CancellationToken
用于取消读取。
-
示例
@page "/js-stream-to-dotnet" @rendermode InteractiveServer @inject IJSRuntime JS <HeadContent><script>window.passStream = () => {var data = new Uint8Array(10000000);var jsStreamReference = DotNet.createJSStreamReference(data);DotNet.invokeMethodAsync('BlazorAppServer', 'ReceiveWindowStream', jsStreamReference);};</script> </HeadContent><h3>JSObjectToDotNet</h3><button @onclick="InvokeJS">传递JS引用 </button>@code {private async Task InvokeJS(){await JS.InvokeVoidAsync("passStream");}[JSInvokable]public static async void ReceiveWindowStream(IJSStreamReference streamRef){using var dataReferenceStream = await streamRef.OpenReadStreamAsync(maxAllowedSize: 10_000_000);var outputPath = Path.Combine(Path.GetTempPath(), "file.txt");using var outputFileStream = File.OpenWrite(outputPath);await dataReferenceStream.CopyToAsync(outputFileStream);await streamRef.DisposeAsync();} }
简便方式
如果JavaScript的流是直接在调用的JS方法中返回的,那么可以通过InvokeAsync<IJSStreamReference>
方法的泛型类型参数直接指定,只要返回的JavaScript对象是可以转换为IJSStreamReference
对象的(ArrayBuffer
、Blob
或任何类型化数组,例如 Uint8Array
或 Float32Array
),都会自动转换
-
示例
@page "/js-stream-to-dotnet" @rendermode InteractiveServer @inject IJSRuntime JS <HeadContent><script>window.passStream = () => {return new Uint8Array(10000000)};</script> </HeadContent><h3>JSObjectToDotNet</h3><button @onclick="InvokeJS">传递JS引用 </button>@code {private async Task InvokeJS(){var streamRef = await JS.InvokeAsync<IJSStreamReference>("passStream");using var dataReferenceStream = await streamRef.OpenReadStreamAsync(maxAllowedSize: 10_000_000);var outputPath = Path.Combine(Path.GetTempPath(), "file.txt");using var outputFileStream = File.OpenWrite(outputPath);await dataReferenceStream.CopyToAsync(outputFileStream);} }
三、字节数组支持
Blazor 支持优化的字节数组 JavaScript (JS) 互操作,这可以避免将字节数组编码/解码为 Base64。 以下示例使用 JS 互操作将字节数组传递给 .NET。
-
示例
@page "/js-bytes-to-dotnet" @rendermode InteractiveServer @using System.Text<PageTitle>Js bytes to dotnet</PageTitle><HeadContent><script>window.sendByteArray = () => {const data = new Uint8Array([0x45, 0x76, 0x65, 0x72, 0x79, 0x74, 0x68, 0x69,0x6e, 0x67, 0x27, 0x73, 0x20, 0x73, 0x68, 0x69, 0x6e, 0x79, 0x2c,0x20, 0x43, 0x61, 0x70, 0x74, 0x61, 0x69, 0x6e, 0x2e, 0x20, 0x4e,0x6f, 0x74, 0x20, 0x74, 0x6f, 0x20, 0x66, 0x72, 0x65, 0x74, 0x2e]);DotNet.invokeMethodAsync('BlazorAppServer', 'ReceiveByteArray', data).then( str => { alert(str); });};</script> </HeadContent><h1>Js bytes to dotnet</h1><p><button onclick="sendByteArray()">Send Bytes</button> </p><p>Quote ©2005 <a href="https://www.uphe.com">Universal Pictures</a>:<a href="https://www.uphe.com/movies/serenity-2005">Serenity</a><br><a href="https://www.imdb.com/name/nm0821612/">Jewel Staite on IMDB</a> </p>@code {[JSInvokable]public static Task<string> ReceiveByteArray(byte[] receivedBytes){var data = Task.FromResult(Encoding.UTF8.GetString(receivedBytes, 0, receivedBytes.Length));return data;} }