第六章 JavaScript 互操(3)JS调用.NET

发布于:2025-07-28 ⋅ 阅读:(16) ⋅ 点赞:(0)

调用.NET方法

一、调用静态.NET方法

Blazor框架中提供了DotNet.invokeMethodAsyncDotNet.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:为ArrayBufferBlob 或任何类型化数组(例如 Uint8ArrayFloat32Array

IJSStreamReference对象

IJSStreamReference为在C#中使用的,用于承载JavaScript流对象的类型。

  • LengthIJSStreamReference对象的属性,获取流中数据的长度。

  • ValueTask<Stream> OpenReadStreamAsync(long maxAllowedSize = 512000, CancellationToken cancellationToken = default)IJSStreamReference对象的方法,用于打开流对象。

    • maxAllowedSize:JavaScript 中读取操作允许的最大字节数,如果未指定,则默认为 512,000 个字节
    • cancellationTokenCancellationToken用于取消读取。
  • 示例

    @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对象的(ArrayBufferBlob 或任何类型化数组,例如 Uint8ArrayFloat32Array),都会自动转换

  • 示例

    @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;
        }
    }
    

网站公告

今日签到

点亮在社区的每一天
去签到