Skip to content

Commit

Permalink
Write readers for reading WebEventData JSON payloads (#34000)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
pranavkm authored Jul 6, 2021
1 parent d804e5d commit 51dfc46
Show file tree
Hide file tree
Showing 44 changed files with 1,918 additions and 370 deletions.
3 changes: 3 additions & 0 deletions src/Components/Components.slnf
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,11 @@
"src\\Http\\Routing\\src\\Microsoft.AspNetCore.Routing.csproj",
"src\\Http\\WebUtilities\\src\\Microsoft.AspNetCore.WebUtilities.csproj",
"src\\Identity\\ApiAuthorization.IdentityServer\\src\\Microsoft.AspNetCore.ApiAuthorization.IdentityServer.csproj",
"src\\Identity\\Core\\src\\Microsoft.AspNetCore.Identity.csproj",
"src\\Identity\\EntityFrameworkCore\\src\\Microsoft.AspNetCore.Identity.EntityFrameworkCore.csproj",
"src\\Identity\\UI\\src\\Microsoft.AspNetCore.Identity.UI.csproj",
"src\\Identity\\Extensions.Core\\src\\Microsoft.Extensions.Identity.Core.csproj",
"src\\Identity\\Extensions.Stores\\src\\Microsoft.Extensions.Identity.Stores.csproj",
"src\\JSInterop\\Microsoft.JSInterop\\src\\Microsoft.JSInterop.csproj",
"src\\Middleware\\CORS\\src\\Microsoft.AspNetCore.Cors.csproj",
"src\\Middleware\\Diagnostics.Abstractions\\src\\Microsoft.AspNetCore.Diagnostics.Abstractions.csproj",
Expand Down
6 changes: 3 additions & 3 deletions src/Components/Ignitor/src/ElementNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -111,10 +111,10 @@ public Task ClickAsync(HubConnection connection)
return DispatchEventCore(connection, Serialize(webEventDescriptor), Serialize(mouseEventArgs));
}

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

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

public class ElementEventDescriptor
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System;
using System.Buffers;
using System.IO;
using System.Text.Json;
using MessagePack;
using Microsoft.AspNetCore.SignalR.Protocol;

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

return bytes.Value.ToArray();
}
else if (type == typeof(JsonElement))
{
var bytes = reader.ReadBytes();
if (bytes is null)
{
return default;
}

var jsonReader = new Utf8JsonReader(bytes.Value);
return JsonElement.ParseValue(ref jsonReader);
}
}
catch (Exception ex)
{
Expand Down
10 changes: 2 additions & 8 deletions src/Components/Server/src/Circuits/CircuitHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ internal class CircuitHost : IAsyncDisposable
private readonly ILogger _logger;
private bool _initialized;
private bool _disposed;
private WebEventJsonContext _jsonContext;

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

// DispatchEvent is used in a fire-and-forget context, so it's responsible for its own
// error handling.
public async Task DispatchEvent(string eventDescriptorJson, string eventArgsJson)
public async Task DispatchEvent(JsonElement eventDescriptorJson, JsonElement eventArgsJson)
{
AssertInitialized();
AssertNotDisposed();
Expand All @@ -456,12 +455,7 @@ public async Task DispatchEvent(string eventDescriptorJson, string eventArgsJson
{
// JsonSerializerOptions are tightly bound to the JsonContext. Cache it on first use using a copy
// of the serializer settings.
if (_jsonContext is null)
{
_jsonContext = new(new JsonSerializerOptions(JSRuntime.ReadJsonSerializerOptions()));
}

webEventData = WebEventData.Parse(Renderer, _jsonContext, eventDescriptorJson, eventArgsJson);
webEventData = WebEventData.Parse(Renderer, JSRuntime.ReadJsonSerializerOptions(), eventDescriptorJson, eventArgsJson);
}
catch (Exception ex)
{
Expand Down
8 changes: 7 additions & 1 deletion src/Components/Server/src/ComponentHub.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@

using System;
using System.Buffers;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Server.Circuits;
using Microsoft.AspNetCore.DataProtection;
Expand Down Expand Up @@ -240,8 +242,12 @@ public async ValueTask<bool> ReceiveJSDataChunk(long streamId, long chunkId, byt
return await circuitHost.ReceiveJSDataChunk(streamId, chunkId, chunk, error);
}

public async ValueTask DispatchBrowserEvent(string eventDescriptor, string eventArgs)
public async ValueTask DispatchBrowserEvent(JsonElement eventInfo)
{
Debug.Assert(eventInfo.GetArrayLength() == 2, "Array length should be 2");
var eventDescriptor = eventInfo[0];
var eventArgs = eventInfo[1];

var circuitHost = await GetActiveCircuitAsync();
if (circuitHost == null)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@

<Compile Include="..\..\Shared\src\BrowserNavigationManagerInterop.cs" />
<Compile Include="..\..\Shared\src\JsonSerializerOptionsProvider.cs" />
<Compile Include="..\..\Shared\src\WebEventData.cs" />
<Compile Include="..\..\Shared\src\WebEventData\*.cs" LinkBase="WebEventData" />

<Compile Include="$(RepoRoot)src\SignalR\common\Shared\BinaryMessageFormatter.cs" LinkBase="BlazorPack" />
<Compile Include="$(RepoRoot)src\SignalR\common\Shared\BinaryMessageParser.cs" LinkBase="BlazorPack" />
Expand Down
23 changes: 13 additions & 10 deletions src/Components/Server/test/Circuits/ComponentHubTest.cs
Original file line number Diff line number Diff line change
@@ -1,26 +1,22 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Security.Claims;
using System.Text.Json;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Lifetime;
using Microsoft.AspNetCore.Components.Server.Circuits;
using Microsoft.AspNetCore.Components.Web.Rendering;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.SignalR;
using Microsoft.AspNetCore.Components.Lifetime;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Microsoft.JSInterop;
using System.Security.Claims;
using Moq;
using Xunit;
using System.Text.RegularExpressions;

namespace Microsoft.AspNetCore.Components.Server
{
Expand Down Expand Up @@ -78,10 +74,17 @@ public async Task CannotDispatchBrowserEventsBeforeInitialization()
{
var (mockClientProxy, hub) = InitializeComponentHub();

await hub.DispatchBrowserEvent("", "");
await hub.DispatchBrowserEvent(GetJsonElement());

var errorMessage = "Circuit not initialized.";
mockClientProxy.Verify(m => m.SendCoreAsync("JS.Error", new[] { errorMessage }, It.IsAny<CancellationToken>()), Times.Once());

static JsonElement GetJsonElement()
{
var utf8JsonBytes = JsonSerializer.SerializeToUtf8Bytes(new object[2]);
var jsonReader = new Utf8JsonReader(utf8JsonBytes);
return JsonElement.ParseValue(ref jsonReader);
}
}

[Fact]
Expand Down
Loading

0 comments on commit 51dfc46

Please sign in to comment.