第六章 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 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方法的泛型类型参数直接指定,只要返回的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; }} 


