Skip to content

Commit 51dfc46

Browse files
authored
Write readers for reading WebEventData JSON payloads (#34000)
* Write readers for reading WebEventData JSON payloads * Adding source generators without doing all of the work results in a size increase. We can avoid issues with reflection and the additional size hit by writing bespoke readers. * An additional optimziation this PR includes is to avoid a unicode string -> utf8 byte conversion along with the associated string allocation by using JsonElement as the contract for deserializing JSON payload. At least in the Blazor Server scenario, we could further consider pooling the byte array used for receiving and deserializing the JSON payload making parsing browser events largely allocation free. Fixes #33967
1 parent d804e5d commit 51dfc46

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+1918
-370
lines changed

src/Components/Components.slnf

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,11 @@
7070
"src\\Http\\Routing\\src\\Microsoft.AspNetCore.Routing.csproj",
7171
"src\\Http\\WebUtilities\\src\\Microsoft.AspNetCore.WebUtilities.csproj",
7272
"src\\Identity\\ApiAuthorization.IdentityServer\\src\\Microsoft.AspNetCore.ApiAuthorization.IdentityServer.csproj",
73+
"src\\Identity\\Core\\src\\Microsoft.AspNetCore.Identity.csproj",
7374
"src\\Identity\\EntityFrameworkCore\\src\\Microsoft.AspNetCore.Identity.EntityFrameworkCore.csproj",
7475
"src\\Identity\\UI\\src\\Microsoft.AspNetCore.Identity.UI.csproj",
76+
"src\\Identity\\Extensions.Core\\src\\Microsoft.Extensions.Identity.Core.csproj",
77+
"src\\Identity\\Extensions.Stores\\src\\Microsoft.Extensions.Identity.Stores.csproj",
7578
"src\\JSInterop\\Microsoft.JSInterop\\src\\Microsoft.JSInterop.csproj",
7679
"src\\Middleware\\CORS\\src\\Microsoft.AspNetCore.Cors.csproj",
7780
"src\\Middleware\\Diagnostics.Abstractions\\src\\Microsoft.AspNetCore.Diagnostics.Abstractions.csproj",

src/Components/Ignitor/src/ElementNode.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -111,10 +111,10 @@ public Task ClickAsync(HubConnection connection)
111111
return DispatchEventCore(connection, Serialize(webEventDescriptor), Serialize(mouseEventArgs));
112112
}
113113

114-
private static string Serialize<T>(T payload) =>
115-
JsonSerializer.Serialize(payload, new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase });
114+
private static byte[] Serialize<T>(T payload) =>
115+
JsonSerializer.SerializeToUtf8Bytes(payload, new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase });
116116

117-
private static Task DispatchEventCore(HubConnection connection, string descriptor, string eventArgs) =>
117+
private static Task DispatchEventCore(HubConnection connection, byte[] descriptor, byte[] eventArgs) =>
118118
connection.InvokeAsync("DispatchBrowserEvent", descriptor, eventArgs);
119119

120120
public class ElementEventDescriptor

src/Components/Server/src/BlazorPack/BlazorPackHubProtocolWorker.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System;
55
using System.Buffers;
66
using System.IO;
7+
using System.Text.Json;
78
using MessagePack;
89
using Microsoft.AspNetCore.SignalR.Protocol;
910

@@ -49,6 +50,17 @@ protected override object DeserializeObject(ref MessagePackReader reader, Type t
4950

5051
return bytes.Value.ToArray();
5152
}
53+
else if (type == typeof(JsonElement))
54+
{
55+
var bytes = reader.ReadBytes();
56+
if (bytes is null)
57+
{
58+
return default;
59+
}
60+
61+
var jsonReader = new Utf8JsonReader(bytes.Value);
62+
return JsonElement.ParseValue(ref jsonReader);
63+
}
5264
}
5365
catch (Exception ex)
5466
{

src/Components/Server/src/Circuits/CircuitHost.cs

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ internal class CircuitHost : IAsyncDisposable
2626
private readonly ILogger _logger;
2727
private bool _initialized;
2828
private bool _disposed;
29-
private WebEventJsonContext _jsonContext;
3029

3130
// This event is fired when there's an unrecoverable exception coming from the circuit, and
3231
// it need so be torn down. The registry listens to this even so that the circuit can
@@ -446,7 +445,7 @@ internal async Task<bool> ReceiveJSDataChunk(long streamId, long chunkId, byte[]
446445

447446
// DispatchEvent is used in a fire-and-forget context, so it's responsible for its own
448447
// error handling.
449-
public async Task DispatchEvent(string eventDescriptorJson, string eventArgsJson)
448+
public async Task DispatchEvent(JsonElement eventDescriptorJson, JsonElement eventArgsJson)
450449
{
451450
AssertInitialized();
452451
AssertNotDisposed();
@@ -456,12 +455,7 @@ public async Task DispatchEvent(string eventDescriptorJson, string eventArgsJson
456455
{
457456
// JsonSerializerOptions are tightly bound to the JsonContext. Cache it on first use using a copy
458457
// of the serializer settings.
459-
if (_jsonContext is null)
460-
{
461-
_jsonContext = new(new JsonSerializerOptions(JSRuntime.ReadJsonSerializerOptions()));
462-
}
463-
464-
webEventData = WebEventData.Parse(Renderer, _jsonContext, eventDescriptorJson, eventArgsJson);
458+
webEventData = WebEventData.Parse(Renderer, JSRuntime.ReadJsonSerializerOptions(), eventDescriptorJson, eventArgsJson);
465459
}
466460
catch (Exception ex)
467461
{

src/Components/Server/src/ComponentHub.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33

44
using System;
55
using System.Buffers;
6+
using System.Diagnostics;
67
using System.Runtime.CompilerServices;
8+
using System.Text.Json;
79
using System.Threading.Tasks;
810
using Microsoft.AspNetCore.Components.Server.Circuits;
911
using Microsoft.AspNetCore.DataProtection;
@@ -240,8 +242,12 @@ public async ValueTask<bool> ReceiveJSDataChunk(long streamId, long chunkId, byt
240242
return await circuitHost.ReceiveJSDataChunk(streamId, chunkId, chunk, error);
241243
}
242244

243-
public async ValueTask DispatchBrowserEvent(string eventDescriptor, string eventArgs)
245+
public async ValueTask DispatchBrowserEvent(JsonElement eventInfo)
244246
{
247+
Debug.Assert(eventInfo.GetArrayLength() == 2, "Array length should be 2");
248+
var eventDescriptor = eventInfo[0];
249+
var eventArgs = eventInfo[1];
250+
245251
var circuitHost = await GetActiveCircuitAsync();
246252
if (circuitHost == null)
247253
{

src/Components/Server/src/Microsoft.AspNetCore.Components.Server.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@
5858

5959
<Compile Include="..\..\Shared\src\BrowserNavigationManagerInterop.cs" />
6060
<Compile Include="..\..\Shared\src\JsonSerializerOptionsProvider.cs" />
61-
<Compile Include="..\..\Shared\src\WebEventData.cs" />
61+
<Compile Include="..\..\Shared\src\WebEventData\*.cs" LinkBase="WebEventData" />
6262

6363
<Compile Include="$(RepoRoot)src\SignalR\common\Shared\BinaryMessageFormatter.cs" LinkBase="BlazorPack" />
6464
<Compile Include="$(RepoRoot)src\SignalR\common\Shared\BinaryMessageParser.cs" LinkBase="BlazorPack" />

src/Components/Server/test/Circuits/ComponentHubTest.cs

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,22 @@
11
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

4-
using System;
54
using System.Collections.Generic;
6-
using System.Linq;
7-
using System.Reflection;
5+
using System.Security.Claims;
6+
using System.Text.Json;
7+
using System.Text.RegularExpressions;
88
using System.Threading;
99
using System.Threading.Tasks;
10+
using Microsoft.AspNetCore.Components.Lifetime;
1011
using Microsoft.AspNetCore.Components.Server.Circuits;
11-
using Microsoft.AspNetCore.Components.Web.Rendering;
1212
using Microsoft.AspNetCore.DataProtection;
1313
using Microsoft.AspNetCore.SignalR;
14-
using Microsoft.AspNetCore.Components.Lifetime;
1514
using Microsoft.Extensions.DependencyInjection;
16-
using Microsoft.Extensions.Logging.Abstractions;
1715
using Microsoft.Extensions.Logging;
16+
using Microsoft.Extensions.Logging.Abstractions;
1817
using Microsoft.Extensions.Options;
19-
using Microsoft.JSInterop;
20-
using System.Security.Claims;
2118
using Moq;
2219
using Xunit;
23-
using System.Text.RegularExpressions;
2420

2521
namespace Microsoft.AspNetCore.Components.Server
2622
{
@@ -78,10 +74,17 @@ public async Task CannotDispatchBrowserEventsBeforeInitialization()
7874
{
7975
var (mockClientProxy, hub) = InitializeComponentHub();
8076

81-
await hub.DispatchBrowserEvent("", "");
77+
await hub.DispatchBrowserEvent(GetJsonElement());
8278

8379
var errorMessage = "Circuit not initialized.";
8480
mockClientProxy.Verify(m => m.SendCoreAsync("JS.Error", new[] { errorMessage }, It.IsAny<CancellationToken>()), Times.Once());
81+
82+
static JsonElement GetJsonElement()
83+
{
84+
var utf8JsonBytes = JsonSerializer.SerializeToUtf8Bytes(new object[2]);
85+
var jsonReader = new Utf8JsonReader(utf8JsonBytes);
86+
return JsonElement.ParseValue(ref jsonReader);
87+
}
8588
}
8689

8790
[Fact]

0 commit comments

Comments
 (0)