Update and improve Blazor unmarshalled JS (#20747)

pull/20762/head
Luke Latham 2020-11-25 09:50:13 -06:00 committed by GitHub
parent ee222600e3
commit 4a0612f56d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 154 additions and 24 deletions

View File

@ -5,13 +5,13 @@ description: Learn how to invoke JavaScript functions from .NET methods in Blazo
monikerRange: '>= aspnetcore-3.1'
ms.author: riande
ms.custom: mvc, devx-track-js
ms.date: 10/20/2020
ms.date: 11/25/2020
no-loc: [appsettings.json, "ASP.NET Core Identity", cookie, Cookie, Blazor, "Blazor Server", "Blazor WebAssembly", "Identity", "Let's Encrypt", Razor, SignalR]
uid: blazor/call-javascript-from-dotnet
---
# Call JavaScript functions from .NET methods in ASP.NET Core Blazor
By [Javier Calvarro Nelson](https://github.com/javiercn), [Daniel Roth](https://github.com/danroth27), and [Luke Latham](https://github.com/guardrex)
By [Javier Calvarro Nelson](https://github.com/javiercn), [Daniel Roth](https://github.com/danroth27), [Pranav Krishnamoorthy](https://github.com/pranavkm), and [Luke Latham](https://github.com/guardrex)
A Blazor app can invoke JavaScript functions from .NET methods and .NET methods from JavaScript functions. These scenarios are called *JavaScript interoperability* (*JS interop*).
@ -526,28 +526,6 @@ public async ValueTask<string> Prompt(string message)
`IJSInProcessObjectReference` represents a reference to a JavaScript object whose functions can be invoked synchronously.
`IJSUnmarshalledObjectReference` represents a reference to an JavaScript object whose functions can be invoked without the overhead of serializing .NET data. This can be used in Blazor WebAssembly when performance is crucial:
```javascript
window.unmarshalledInstance = {
helloWorld: function (personNamePointer) {
const personName = Blazor.platform.readStringField(value, 0);
return `Hello ${personName}`;
}
};
```
```csharp
var unmarshalledRuntime = (IJSUnmarshalledRuntime)js;
var jsUnmarshalledReference = unmarshalledRuntime
.InvokeUnmarshalled<IJSUnmarshalledObjectReference>("unmarshalledInstance");
string helloWorldString = jsUnmarshalledReference.InvokeUnmarshalled<string, string>(
"helloWorld");
```
In the preceding example, the <xref:Microsoft.JSInterop.IJSRuntime> service is injected into the class and assigned to `js` (not shown).
## Use of JavaScript libraries that render UI (DOM elements)
Sometimes you may wish to use JavaScript libraries that produce visible user interface elements within the browser DOM. At first glance, this might seem difficult because Blazor's diffing system relies on having control over the tree of DOM elements and runs into errors if some external code mutates the DOM tree and invalidates its mechanism for applying diffs. This isn't a Blazor-specific limitation. The same challenge occurs with any diff-based UI framework.
@ -691,6 +669,158 @@ Consider the following guidance when developing code that transfers a large amou
For JS isolation, JS interop works with the browser's default support for [EcmaScript modules (ESM)](https://developer.mozilla.org/docs/Web/JavaScript/Guide/Modules) ([ECMAScript specification](https://tc39.es/ecma262/#sec-modules)).
## Unmarshalled JS interop
Blazor WebAssembly components may experience poor performance when .NET objects are serialized for JS interop and either of the following are true:
* A high volume of .NET objects are rapidly serialized. Example: JS interop calls are made on the basis of moving an input device, such as spinning a mouse wheel.
* Large .NET objects or many .NET objects must be serialized for JS interop. Example: JS interop calls require serializing dozens of files.
<xref:Microsoft.JSInterop.IJSUnmarshalledObjectReference> represents a reference to an JavaScript object whose functions can be invoked without the overhead of serializing .NET data.
In the following example:
* A [struct](/dotnet/csharp/language-reference/builtin-types/struct) containing a string and an integer is passed unserialized to JavaScript.
* JavaScript functions process the data and return either a boolean or string to the caller.
* A JavaScript string isn't directly convertible into a .NET `string` object. The `unmarshalledFunctionReturnString` function calls `BINDING.js_string_to_mono_string` to manage the conversion of a Javascript string.
> [!NOTE]
> The following examples aren't typical use cases for this scenario because the [struct](/dotnet/csharp/language-reference/builtin-types/struct) passed to JavaScript doesn't result in poor component performance. The example uses a small object merely to demonstrate the concepts for passing unserialized .NET data.
Content of a `<script>` block in `wwwroot/index.html` or an external Javascript file referenced by `wwwroot/index.html`:
```javascript
window.returnJSObjectReference = () => {
return {
unmarshalledFunctionReturnBoolean: function (fields) {
const name = Blazor.platform.readStringField(fields, 0);
const year = Blazor.platform.readInt32Field(fields, 8);
return name === "Brigadier Alistair Gordon Lethbridge-Stewart" &&
year === 1968;
},
unmarshalledFunctionReturnString: function (fields) {
const name = Blazor.platform.readStringField(fields, 0);
const year = Blazor.platform.readInt32Field(fields, 8);
return BINDING.js_string_to_mono_string(`Hello, ${name} (${year})!`);
}
};
}
```
> [!WARNING]
> The `js_string_to_mono_string` function name, behavior, and existence is subject to change in a future release of .NET. For example:
>
> * The function is likely to be renamed.
> * The function itself might be removed in favor of automatic conversion of strings by the framework.
`Pages/UnmarshalledJSInterop.razor` (URL: `/unmarshalled-js-interop`):
```razor
@page "/unmarshalled-js-interop"
@using System.Runtime.InteropServices
@using Microsoft.JSInterop
@inject IJSRuntime JS
<h1>Unmarshalled JS interop</h1>
@if (callResultForBoolean)
{
<p>JS interop was successful!</p>
}
@if (!string.IsNullOrEmpty(callResultForString))
{
<p>@callResultForString</p>
}
<p>
<button @onclick="CallJSUnmarshalledForBoolean">
Call Unmarshalled JS & Return Boolean
</button>
<button @onclick="CallJSUnmarshalledForString">
Call Unmarshalled JS & Return String
</button>
</p>
<p>
<a href="https://www.doctorwho.tv">Doctor Who</a>
is a registered trademark of the <a href="https://www.bbc.com/">BBC</a>.
</p>
@code {
private bool callResultForBoolean;
private string callResultForString;
private void CallJSUnmarshalledForBoolean()
{
var unmarshalledRuntime = (IJSUnmarshalledRuntime)JS;
var jsUnmarshalledReference = unmarshalledRuntime
.InvokeUnmarshalled<IJSUnmarshalledObjectReference>(
"returnJSObjectReference");
callResultForBoolean =
jsUnmarshalledReference.InvokeUnmarshalled<InteropStruct, bool>(
"unmarshalledFunctionReturnBoolean", GetStruct());
}
private void CallJSUnmarshalledForString()
{
var unmarshalledRuntime = (IJSUnmarshalledRuntime)JS;
var jsUnmarshalledReference = unmarshalledRuntime
.InvokeUnmarshalled<IJSUnmarshalledObjectReference>(
"returnJSObjectReference");
callResultForString =
jsUnmarshalledReference.InvokeUnmarshalled<InteropStruct, string>(
"unmarshalledFunctionReturnString", GetStruct());
}
private InteropStruct GetStruct()
{
return new InteropStruct
{
Name = "Brigadier Alistair Gordon Lethbridge-Stewart",
Year = 1968,
};
}
[StructLayout(LayoutKind.Explicit)]
public struct InteropStruct
{
[FieldOffset(0)]
public string Name;
[FieldOffset(8)]
public int Year;
}
}
```
If an `IJSUnmarshalledObjectReference` instance isn't disposed in C# code, it can be disposed in JavaScript. The following `dispose` function disposes the object reference when called from JavaScript:
```javascript
window.exampleJSObjectReferenceNotDisposedInCSharp = () => {
return {
dispose: function () {
DotNet.disposeJSObjectReference(this);
},
...
};
}
```
Array types can be converted from JavaScript objects into .NET objects using `js_typed_array_to_array`, but the JavaScript array must be a typed array. Arrays from JavaScript can be read in C# code as a .NET object array (`object[]`).
Other data types, such as string arrays, can be converted but require creating a new Mono array object (`mono_obj_array_new`) and setting its value (`mono_obj_array_set`).
> [!WARNING]
> JavaScript functions provided by the Blazor framework, such as `js_typed_array_to_array`, `mono_obj_array_new`, and `mono_obj_array_set`, are subject to name changes, behavioral changes, or removal in future releases of .NET.
## Additional resources
* <xref:blazor/call-dotnet-from-javascript>