From 88bb43881fe949dce87e670e24dc53ba8c029a72 Mon Sep 17 00:00:00 2001
From: guardrex <1622880+guardrex@users.noreply.github.com>
Date: Thu, 27 Apr 2023 07:28:07 -0400
Subject: [PATCH] Blazor JS import/export interop updates
---
.../8.0/import-export-interop-mappings.md | 48 +++++++++++++++++
.../includes/js-interop/8.0/size-limits.md | 53 +++++++++++++++++++
.../import-export-interop.md | 38 ++++++++-----
3 files changed, 125 insertions(+), 14 deletions(-)
create mode 100644 aspnetcore/blazor/includes/js-interop/8.0/import-export-interop-mappings.md
create mode 100644 aspnetcore/blazor/includes/js-interop/8.0/size-limits.md
diff --git a/aspnetcore/blazor/includes/js-interop/8.0/import-export-interop-mappings.md b/aspnetcore/blazor/includes/js-interop/8.0/import-export-interop-mappings.md
new file mode 100644
index 000000000000..d9c51384c97b
--- /dev/null
+++ b/aspnetcore/blazor/includes/js-interop/8.0/import-export-interop-mappings.md
@@ -0,0 +1,48 @@
+The following table indicates the supported type mappings.
+
+| .NET | JavaScript | `Nullable` | `Task` ➔to `Promise` | `JSMarshalAs` optional | `Array of` |
+| --- | --- | :---: | :---: | :---: | :---: |
+| `Boolean` | `Boolean` | ✅Supported | ✅Supported | ✅Supported | Not supported |
+| `Byte` | `Number` | ✅Supported | ✅Supported | ✅Supported | ✅Supported |
+| `Char` | `String` | ✅Supported | ✅Supported | ✅Supported | Not supported |
+| `Int16` | `Number` | ✅Supported | ✅Supported | ✅Supported | Not supported |
+| `Int32` | `Number` | ✅Supported | ✅Supported | ✅Supported | ✅Supported |
+| `Int64` | `Number` | ✅Supported | ✅Supported | Not supported | Not supported |
+| `Int64` | `BigInt` | ✅Supported | ✅Supported | Not supported | Not supported |
+| `Single` | `Number` | ✅Supported | ✅Supported | ✅Supported | Not supported |
+| `Double` | `Number` | ✅Supported | ✅Supported | ✅Supported | ✅Supported |
+| `IntPtr` | `Number` | ✅Supported | ✅Supported | ✅Supported | Not supported |
+| `DateTime` | `Date` | ✅Supported | ✅Supported | Not supported | Not supported |
+| `DateTimeOffset` | `Date` | ✅Supported | ✅Supported | Not supported | Not supported |
+| `Exception` | `Error` | Not supported | ✅Supported | ✅Supported | Not supported |
+| `JSObject` | `Object` | Not supported | ✅Supported | ✅Supported | ✅Supported |
+| `String` | `String` | Not supported | ✅Supported | ✅Supported | ✅Supported |
+| `Object` | `Any` | Not supported | ✅Supported | Not supported | ✅Supported |
+| `Span` | `MemoryView` | Not supported | Not supported | Not supported | Not supported |
+| `Span` | `MemoryView` | Not supported | Not supported | Not supported | Not supported |
+| `Span` | `MemoryView` | Not supported | Not supported | Not supported | Not supported |
+| `ArraySegment` | `MemoryView` | Not supported | Not supported | Not supported | Not supported |
+| `ArraySegment` | `MemoryView` | Not supported | Not supported | Not supported | Not supported |
+| `ArraySegment` | `MemoryView` | Not supported | Not supported | Not supported | Not supported |
+| `Task` | `Promise` | Not supported | Not supported | ✅Supported | Not supported |
+| `Action` | `Function` | Not supported | Not supported | Not supported | Not supported |
+| `Action` | `Function` | Not supported | Not supported | Not supported | Not supported |
+| `Action` | `Function` | Not supported | Not supported | Not supported | Not supported |
+| `Action` | `Function` | Not supported | Not supported | Not supported | Not supported |
+| `Func` | `Function` | Not supported | Not supported | Not supported | Not supported |
+| `Func` | `Function` | Not supported | Not supported | Not supported | Not supported |
+| `Func` | `Function` | Not supported | Not supported | Not supported | Not supported |
+| `Func` | `Function` | Not supported | Not supported | Not supported | Not supported |
+
+The following conditions apply to type mapping and marshalled values:
+
+* The `Array of` column indicates if the .NET type can be marshalled as a JS [`Array`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array). Example: C# `int[]` (`Int32`) mapped to JS `Array` of `Number`s.
+* When passing a JS value to C# with a value of the wrong type, the framework throws an exception in most cases. The framework doesn't perform compile-time type checking in JS.
+* `JSObject`, `Exception`, `Task` and `ArraySegment` create `GCHandle` and a proxy. You can trigger disposal in developer code or allow [.NET garbage collection (GC)](/dotnet/standard/garbage-collection/) to dispose of the objects later. These types carry significant performance overhead.
+* `Array`: Marshaling an array creates a copy of the array in JS or .NET.
+* `MemoryView`
+ * `MemoryView` is a JS class for the .NET WebAssembly runtime to marshal `Span` and `ArraySegment`.
+ * Unlike marshaling an array, marshaling a `Span` or `ArraySegment` doesn't create a copy of the underlying memory.
+ * `MemoryView` can only be properly instantiated by the .NET WebAssembly runtime. Therefore, it isn't possible to import a JS function as a .NET method that has a parameter of `Span` or `ArraySegment`.
+ * `MemoryView` created for a `Span` is only valid for the duration of the interop call. As `Span` is allocated on the call stack, which doesn't persist after the interop call, it isn't possible to export a .NET method that returns a `Span`.
+ * `MemoryView` created for an `ArraySegment` survives after the interop call and is useful for sharing a buffer. Calling `dispose()` on a `MemoryView` created for an `ArraySegment` disposes the proxy and unpins the underlying .NET array. We recommend calling `dispose()` in a `try-finally` block for `MemoryView`.
diff --git a/aspnetcore/blazor/includes/js-interop/8.0/size-limits.md b/aspnetcore/blazor/includes/js-interop/8.0/size-limits.md
new file mode 100644
index 000000000000..c8d54e562ff6
--- /dev/null
+++ b/aspnetcore/blazor/includes/js-interop/8.0/size-limits.md
@@ -0,0 +1,53 @@
+*This section only applies to Blazor Server apps. In Blazor WebAssembly, the framework doesn't impose a limit on the size of JavaScript (JS) interop inputs and outputs.*
+
+In Blazor Server, JS interop calls are limited in size by the maximum incoming SignalR message size permitted for hub methods, which is enforced by (default: 32 KB). JS to .NET SignalR messages larger than throw an error. The framework doesn't impose a limit on the size of a SignalR message from the hub to a client.
+
+When SignalR logging isn't set to [Debug](xref:Microsoft.Extensions.Logging.LogLevel) or [Trace](xref:Microsoft.Extensions.Logging.LogLevel), a message size error only appears in the browser's developer tools console:
+
+> Error: Connection disconnected with error 'Error: Server returned an error on close: Connection closed with an error.'.
+
+When [SignalR server-side logging](xref:signalr/diagnostics#server-side-logging) is set to [Debug](xref:Microsoft.Extensions.Logging.LogLevel) or [Trace](xref:Microsoft.Extensions.Logging.LogLevel), server-side logging surfaces an for a message size error.
+
+`appsettings.Development.json`:
+
+```json
+{
+ "DetailedErrors": true,
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft": "Warning",
+ "Microsoft.Hosting.Lifetime": "Information",
+ "Microsoft.AspNetCore.SignalR": "Debug"
+ }
+ }
+}
+```
+
+Error:
+
+> System.IO.InvalidDataException: The maximum message size of 32768B was exceeded. The message size can be configured in AddHubOptions.
+
+Increase the limit by setting in `Program.cs`. The following example sets the maximum receive message size to 64 KB:
+
+```csharp
+builder.Services.AddServerSideBlazor()
+ .AddHubOptions(options => options.MaximumReceiveMessageSize = 64 * 1024);
+```
+
+Increasing the SignalR incoming message size limit comes at the cost of requiring more server resources, and it exposes the server to increased risks from a malicious user. Additionally, reading a large amount of content in to memory as strings or byte arrays can also result in allocations that work poorly with the garbage collector, resulting in additional performance penalties.
+
+Consider the following guidance when developing code that transfers a large amount of data between JS and Blazor in Blazor Server apps:
+
+* Leverage the native streaming interop support to transfer data larger than the SignalR incoming message size limit:
+ *
+ *
+* General tips:
+ * Don't allocate large objects in JS and C# code.
+ * Free consumed memory when the process is completed or cancelled.
+ * Enforce the following additional requirements for security purposes:
+ * Declare the maximum file or data size that can be passed.
+ * Declare the minimum upload rate from the client to the server.
+ * After the data is received by the server, the data can be:
+ * Temporarily stored in a memory buffer until all of the segments are collected.
+ * Consumed immediately. For example, the data can be stored immediately in a database or written to disk as each segment is received.
diff --git a/aspnetcore/blazor/javascript-interoperability/import-export-interop.md b/aspnetcore/blazor/javascript-interoperability/import-export-interop.md
index 248f9d3e6d74..749d06fea63d 100644
--- a/aspnetcore/blazor/javascript-interoperability/import-export-interop.md
+++ b/aspnetcore/blazor/javascript-interoperability/import-export-interop.md
@@ -2,7 +2,7 @@
title: JavaScript JSImport/JSExport interop with ASP.NET Core Blazor WebAssembly
author: guardrex
description: Learn how to interact with JavaScript in Blazor WebAssembly apps using JavaScript `[JSImport]`/`[JSExport]` interop.
-monikerRange: '= aspnetcore-7.0'
+monikerRange: '>= aspnetcore-7.0'
ms.author: riande
ms.custom: mvc
ms.date: 11/10/2022
@@ -10,17 +10,7 @@ uid: blazor/js-interop/import-export-interop
---
# JavaScript `[JSImport]`/`[JSExport]` interop with ASP.NET Core Blazor
-
-
-This article explains how to interact with JavaScript (JS) in Blazor WebAssembly apps using JavaScript (JS) `[JSImport]`/`[JSExport]` interop API released with .NET 7.
+This article explains how to interact with JavaScript (JS) in Blazor WebAssembly apps using JavaScript (JS) `[JSImport]`/`[JSExport]` interop API released for apps that adopt .NET 7 or later.
Blazor provides its own JS interop mechanism based on the interface, which is uniformly supported across Blazor hosting models and described in the following articles:
@@ -29,19 +19,29 @@ Blazor provides its own JS interop mechanism based on the enables library authors to build JS interop libraries that can be shared across the Blazor ecosystem and remains the recommended approach for JS interop in Blazor.
-This article describes an alternative JS interop approach specific to WebAssembly-based apps available for the first time with the release of .NET 7. These approaches are appropriate when you only expect to run on client-side WebAssembly and not in the other Blazor hosting models. Library authors can use these approaches to optimize JS interop by checking at runtime if the app is running on WebAssembly in a browser (). The approaches described in this article should be used to replace the obsolete unmarshalled JS interop API when migrating to .NET 7.
+This article describes an alternative JS interop approach specific to WebAssembly-based apps available for the first time with the release of .NET 7. These approaches are appropriate when you only expect to run on client-side WebAssembly and not in the other Blazor hosting models. Library authors can use these approaches to optimize JS interop by checking at runtime if the app is running on WebAssembly in a browser (). The approaches described in this article should be used to replace the obsolete unmarshalled JS interop API when migrating to .NET 7 or later.
> [!NOTE]
> This article focuses on JS interop in Blazor WebAssembly apps. For guidance on calling .NET in JavaScript apps, see .
## Obsolete JavaScript interop API
-Unmarshalled JS interop using API is obsolete in ASP.NET Core 7.0. Follow the guidance in this article to replace the obsolete API.
+Unmarshalled JS interop using API is obsolete in ASP.NET Core 7.0 or later. Follow the guidance in this article to replace the obsolete API.
## Prerequisites
+:::moniker range=">= aspnetcore-8.0"
+
+[!INCLUDE[](~/includes/8.0-SDK.md)]
+
+:::moniker-end
+
+:::moniker range="< aspnetcore-8.0"
+
[!INCLUDE[](~/includes/7.0-SDK.md)]
+:::moniker-end
+
## Namespace
The JS interop API described in this article is controlled by attributes in the namespace.
@@ -122,8 +122,18 @@ The app's namespace for the preceding `CallJavaScript1` partial class is `Blazor
In the imported method signature, you can use .NET types for parameters and return values, which are marshalled automatically by the runtime. Use to control how the imported method parameters are marshalled. For example, you might choose to marshal a `long` as or . You can pass / callbacks as parameters, which are marshalled as callable JS functions. You can pass both JS and managed object references, and they are marshaled as proxy objects, keeping the object alive across the boundary until the proxy is garbage collected. You can also import and export asynchronous methods with a result, which are marshaled as [JS promises](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise). Most of the marshalled types work in both directions, as parameters and as return values, on both imported and exported methods, which are covered in the [Call .NET from JavaScript](#call-net-from-javascript) section later in this article.
+:::moniker range=">= aspnetcore-8.0"
+
+[!INCLUDE[](~/blazor/includes/js-interop/8.0/import-export-interop-mappings.md)]
+
+:::moniker-end
+
+:::moniker range="< aspnetcore-8.0"
+
[!INCLUDE[](~/blazor/includes/js-interop/7.0/import-export-interop-mappings.md)]
+:::moniker-end
+
The module name in the `[JSImport]` attribute and the call to load the module in the component with must match and be unique in the app. When authoring a library for deployment in a NuGet package, we recommend using the NuGet package namespace as a prefix in module names. In the following example, the module name reflects the `Contoso.InteropServices.JavaScript` package and a folder of user message interop classes (`UserMessages`):
```csharp