Remove .Web IVTs and refactor. Fixes #12713 (#12967)

* Remove IVTs from Microsoft.AspNetCore.Components.Web to .Blazor/.Server

* Eliminate RendererRegistry from .Web, as it's not needed for server-side Blazor

* Reintroduce RendererRegistry as a WebAssembly-specific concept. Refactor event data parsing.

* Remove redundant guard

* Corresponding test updates

* Update ref sources

* CR: Remove renderer ID fully from server-side Blazor code

* CR: Make WebEventData internal and shared-source

* Hub test updates

* Clean whitespace

* Update binaries

* Regenerate binaries AGAIN

* Update Jest test

* CR: Replace constructor with static parse

* Yet again attempt to refresh the .js binaries

* Fix ref assembly

* Fix test
This commit is contained in:
Steve Sanderson 2019-08-09 00:03:40 +01:00 committed by GitHub
parent 4c49a9206e
commit d716ca9d62
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 290 additions and 383 deletions

View File

@ -65,16 +65,10 @@ namespace Microsoft.AspNetCore.Blazor.Http
} }
namespace Microsoft.AspNetCore.Blazor.Rendering namespace Microsoft.AspNetCore.Blazor.Rendering
{ {
public partial class WebAssemblyRenderer : Microsoft.AspNetCore.Components.Rendering.Renderer public static partial class WebAssemblyEventDispatcher
{ {
public WebAssemblyRenderer(System.IServiceProvider serviceProvider, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) : base (default(System.IServiceProvider), default(Microsoft.Extensions.Logging.ILoggerFactory)) { } [Microsoft.JSInterop.JSInvokableAttribute("DispatchEvent")]
public override Microsoft.AspNetCore.Components.Dispatcher Dispatcher { get { throw null; } } public static System.Threading.Tasks.Task DispatchEvent(Microsoft.AspNetCore.Components.Web.WebEventDescriptor eventDescriptor, string eventArgsJson) { throw null; }
public System.Threading.Tasks.Task AddComponentAsync(System.Type componentType, string domElementSelector) { throw null; }
public System.Threading.Tasks.Task AddComponentAsync<TComponent>(string domElementSelector) where TComponent : Microsoft.AspNetCore.Components.IComponent { throw null; }
public override System.Threading.Tasks.Task DispatchEventAsync(ulong eventHandlerId, Microsoft.AspNetCore.Components.Rendering.EventFieldInfo eventFieldInfo, System.EventArgs eventArgs) { throw null; }
protected override void Dispose(bool disposing) { }
protected override void HandleException(System.Exception exception) { }
protected override System.Threading.Tasks.Task UpdateDisplayAsync(in Microsoft.AspNetCore.Components.Rendering.RenderBatch batch) { throw null; }
} }
} }
namespace Microsoft.AspNetCore.Components.Builder namespace Microsoft.AspNetCore.Components.Builder

View File

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework> <TargetFramework>netstandard2.0</TargetFramework>
@ -11,4 +11,10 @@
<Reference Include="Microsoft.AspNetCore.Components.Web" /> <Reference Include="Microsoft.AspNetCore.Components.Web" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Compile Include="..\..\..\Shared\src\BrowserNavigationManagerInterop.cs" />
<Compile Include="..\..\..\Shared\src\JsonSerializerOptionsProvider.cs" />
<Compile Include="..\..\..\Shared\src\WebEventData.cs" />
</ItemGroup>
</Project> </Project>

View File

@ -0,0 +1,46 @@
// 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;
namespace Microsoft.AspNetCore.Blazor.Rendering
{
internal static class RendererRegistry
{
// In case there are multiple concurrent Blazor renderers in the same .NET WebAssembly
// process, we track them by ID. This allows events to be dispatched to the correct one,
// as well as rooting them for GC purposes, since nothing would otherwise be referencing
// them even though we might still receive incoming events from JS.
private static int _nextId;
private static Dictionary<int, WebAssemblyRenderer> _renderers = new Dictionary<int, WebAssemblyRenderer>();
internal static WebAssemblyRenderer Find(int rendererId)
{
return _renderers.ContainsKey(rendererId)
? _renderers[rendererId]
: throw new ArgumentException($"There is no renderer with ID {rendererId}.");
}
public static int Add(WebAssemblyRenderer renderer)
{
var id = _nextId++;
_renderers.Add(id, renderer);
return id;
}
public static bool TryRemove(int rendererId)
{
if (_renderers.ContainsKey(rendererId))
{
_renderers.Remove(rendererId);
return true;
}
else
{
return false;
}
}
}
}

View File

@ -0,0 +1,30 @@
// 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.Threading.Tasks;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.JSInterop;
namespace Microsoft.AspNetCore.Blazor.Rendering
{
/// <summary>
/// Dispatches events from JavaScript to a Blazor WebAssembly renderer.
/// Intended for internal use only.
/// </summary>
public static class WebAssemblyEventDispatcher
{
/// <summary>
/// For framework use only.
/// </summary>
[JSInvokable(nameof(DispatchEvent))]
public static Task DispatchEvent(WebEventDescriptor eventDescriptor, string eventArgsJson)
{
var webEvent = WebEventData.Parse(eventDescriptor, eventArgsJson);
var renderer = RendererRegistry.Find(eventDescriptor.BrowserRendererId);
return renderer.DispatchEventAsync(
webEvent.EventHandlerId,
webEvent.EventFieldInfo,
webEvent.EventArgs);
}
}
}

View File

@ -6,7 +6,6 @@ using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Blazor.Services; using Microsoft.AspNetCore.Blazor.Services;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.Rendering; using Microsoft.AspNetCore.Components.Rendering;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -16,7 +15,7 @@ namespace Microsoft.AspNetCore.Blazor.Rendering
/// Provides mechanisms for rendering <see cref="IComponent"/> instances in a /// Provides mechanisms for rendering <see cref="IComponent"/> instances in a
/// web browser, dispatching events to them, and refreshing the UI as required. /// web browser, dispatching events to them, and refreshing the UI as required.
/// </summary> /// </summary>
public class WebAssemblyRenderer : Renderer internal class WebAssemblyRenderer : Renderer
{ {
private readonly int _webAssemblyRendererId; private readonly int _webAssemblyRendererId;
@ -31,10 +30,8 @@ namespace Microsoft.AspNetCore.Blazor.Rendering
public WebAssemblyRenderer(IServiceProvider serviceProvider, ILoggerFactory loggerFactory) public WebAssemblyRenderer(IServiceProvider serviceProvider, ILoggerFactory loggerFactory)
: base(serviceProvider, loggerFactory) : base(serviceProvider, loggerFactory)
{ {
// The browser renderer registers and unregisters itself with the static // The WebAssembly renderer registers and unregisters itself with the static registry
// registry. This works well with the WebAssembly runtime, and is simple for the _webAssemblyRendererId = RendererRegistry.Add(this);
// case where Blazor is running in process.
_webAssemblyRendererId = RendererRegistry.Current.Add(this);
} }
public override Dispatcher Dispatcher => NullDispatcher.Instance; public override Dispatcher Dispatcher => NullDispatcher.Instance;
@ -77,9 +74,9 @@ namespace Microsoft.AspNetCore.Blazor.Rendering
WebAssemblyJSRuntime.Instance.Invoke<object>( WebAssemblyJSRuntime.Instance.Invoke<object>(
"Blazor._internal.attachRootComponentToElement", "Blazor._internal.attachRootComponentToElement",
_webAssemblyRendererId,
domElementSelector, domElementSelector,
componentId); componentId,
_webAssemblyRendererId);
return RenderRootComponentAsync(componentId); return RenderRootComponentAsync(componentId);
} }
@ -88,7 +85,7 @@ namespace Microsoft.AspNetCore.Blazor.Rendering
protected override void Dispose(bool disposing) protected override void Dispose(bool disposing)
{ {
base.Dispose(disposing); base.Dispose(disposing);
RendererRegistry.Current.TryRemove(_webAssemblyRendererId); RendererRegistry.TryRemove(_webAssemblyRendererId);
} }
/// <inheritdoc /> /// <inheritdoc />

View File

@ -4,7 +4,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Security.Claims; using System.Security.Claims;
using System.Text.Json;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Web; using Microsoft.AspNetCore.Components.Web;
@ -41,7 +40,6 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
} }
JSInterop.JSRuntime.SetCurrentJSRuntime(circuitHost.JSRuntime); JSInterop.JSRuntime.SetCurrentJSRuntime(circuitHost.JSRuntime);
RendererRegistry.SetCurrentRendererRegistry(circuitHost.RendererRegistry);
} }
public event UnhandledExceptionEventHandler UnhandledException; public event UnhandledExceptionEventHandler UnhandledException;
@ -50,7 +48,6 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
string circuitId, string circuitId,
IServiceScope scope, IServiceScope scope,
CircuitClientProxy client, CircuitClientProxy client,
RendererRegistry rendererRegistry,
RemoteRenderer renderer, RemoteRenderer renderer,
IReadOnlyList<ComponentDescriptor> descriptors, IReadOnlyList<ComponentDescriptor> descriptors,
RemoteJSRuntime jsRuntime, RemoteJSRuntime jsRuntime,
@ -60,7 +57,6 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
CircuitId = circuitId; CircuitId = circuitId;
_scope = scope ?? throw new ArgumentNullException(nameof(scope)); _scope = scope ?? throw new ArgumentNullException(nameof(scope));
Client = client; Client = client;
RendererRegistry = rendererRegistry ?? throw new ArgumentNullException(nameof(rendererRegistry));
Descriptors = descriptors ?? throw new ArgumentNullException(nameof(descriptors)); Descriptors = descriptors ?? throw new ArgumentNullException(nameof(descriptors));
Renderer = renderer ?? throw new ArgumentNullException(nameof(renderer)); Renderer = renderer ?? throw new ArgumentNullException(nameof(renderer));
JSRuntime = jsRuntime ?? throw new ArgumentNullException(nameof(jsRuntime)); JSRuntime = jsRuntime ?? throw new ArgumentNullException(nameof(jsRuntime));
@ -85,8 +81,6 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
public RemoteRenderer Renderer { get; } public RemoteRenderer Renderer { get; }
public RendererRegistry RendererRegistry { get; }
public IReadOnlyList<ComponentDescriptor> Descriptors { get; } public IReadOnlyList<ComponentDescriptor> Descriptors { get; }
public IServiceProvider Services { get; } public IServiceProvider Services { get; }
@ -137,46 +131,38 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
} }
} }
public async Task DispatchEvent(string eventDescriptorJson, string eventArgs) public async Task DispatchEvent(string eventDescriptorJson, string eventArgsJson)
{ {
RendererRegistryEventDispatcher.BrowserEventDescriptor eventDescriptor = null; WebEventData webEventData;
try try
{ {
AssertInitialized(); AssertInitialized();
eventDescriptor = ParseEventDescriptor(eventDescriptorJson); webEventData = WebEventData.Parse(eventDescriptorJson, eventArgsJson);
if (eventDescriptor == null) }
{ catch (Exception ex)
return; {
} Log.DispatchEventFailedToParseEventData(_logger, ex);
return;
}
try
{
await Renderer.Dispatcher.InvokeAsync(() => await Renderer.Dispatcher.InvokeAsync(() =>
{ {
SetCurrentCircuitHost(this); SetCurrentCircuitHost(this);
return RendererRegistryEventDispatcher.DispatchEvent(eventDescriptor, eventArgs); return Renderer.DispatchEventAsync(
webEventData.EventHandlerId,
webEventData.EventFieldInfo,
webEventData.EventArgs);
}); });
} }
catch (Exception ex) catch (Exception ex)
{ {
Log.DispatchEventFailedToDispatchEvent(_logger, eventDescriptor != null ? eventDescriptor.EventHandlerId.ToString() : null, ex); Log.DispatchEventFailedToDispatchEvent(_logger, webEventData.EventHandlerId.ToString(), ex);
UnhandledException?.Invoke(this, new UnhandledExceptionEventArgs(ex, isTerminating: false)); UnhandledException?.Invoke(this, new UnhandledExceptionEventArgs(ex, isTerminating: false));
} }
} }
private RendererRegistryEventDispatcher.BrowserEventDescriptor ParseEventDescriptor(string eventDescriptorJson)
{
try
{
return JsonSerializer.Deserialize<RendererRegistryEventDispatcher.BrowserEventDescriptor>(
eventDescriptorJson,
JsonSerializerOptionsProvider.Options);
}
catch (Exception ex)
{
Log.DispatchEventFailedToParseEventDescriptor(_logger, ex);
return null;
}
}
public async Task InitializeAsync(CancellationToken cancellationToken) public async Task InitializeAsync(CancellationToken cancellationToken)
{ {
await Renderer.Dispatcher.InvokeAsync(async () => await Renderer.Dispatcher.InvokeAsync(async () =>
@ -214,11 +200,6 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
try try
{ {
AssertInitialized(); AssertInitialized();
if (assemblyName == "Microsoft.AspNetCore.Components.Web" && methodIdentifier == "DispatchEvent")
{
Log.DispatchEventTroughJSInterop(_logger);
return;
}
await Renderer.Dispatcher.InvokeAsync(() => await Renderer.Dispatcher.InvokeAsync(() =>
{ {
@ -410,9 +391,8 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
private static readonly Action<ILogger, Exception> _endInvokeDispatchException; private static readonly Action<ILogger, Exception> _endInvokeDispatchException;
private static readonly Action<ILogger, long, string, Exception> _endInvokeJSFailed; private static readonly Action<ILogger, long, string, Exception> _endInvokeJSFailed;
private static readonly Action<ILogger, long, Exception> _endInvokeJSSucceeded; private static readonly Action<ILogger, long, Exception> _endInvokeJSSucceeded;
private static readonly Action<ILogger, Exception> _dispatchEventFailedToParseEventDescriptor; private static readonly Action<ILogger, Exception> _dispatchEventFailedToParseEventData;
private static readonly Action<ILogger, string, Exception> _dispatchEventFailedToDispatchEvent; private static readonly Action<ILogger, string, Exception> _dispatchEventFailedToDispatchEvent;
private static readonly Action<ILogger, Exception> _dispatchEventThroughJSInterop;
private static readonly Action<ILogger, string, string, Exception> _locationChange; private static readonly Action<ILogger, string, string, Exception> _locationChange;
private static readonly Action<ILogger, string, string, Exception> _locationChangeSucceeded; private static readonly Action<ILogger, string, string, Exception> _locationChangeSucceeded;
private static readonly Action<ILogger, string, string, Exception> _locationChangeFailed; private static readonly Action<ILogger, string, string, Exception> _locationChangeFailed;
@ -426,7 +406,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
public static readonly EventId OnConnectionDown = new EventId(104, "OnConnectionDown"); public static readonly EventId OnConnectionDown = new EventId(104, "OnConnectionDown");
public static readonly EventId OnCircuitClosed = new EventId(105, "OnCircuitClosed"); public static readonly EventId OnCircuitClosed = new EventId(105, "OnCircuitClosed");
public static readonly EventId InvalidBrowserEventFormat = new EventId(106, "InvalidBrowserEventFormat"); public static readonly EventId InvalidBrowserEventFormat = new EventId(106, "InvalidBrowserEventFormat");
public static readonly EventId DispatchEventFailedToParseEventDescriptor = new EventId(107, "DispatchEventFailedToParseEventDescriptor"); public static readonly EventId DispatchEventFailedToParseEventData = new EventId(107, "DispatchEventFailedToParseEventData");
public static readonly EventId DispatchEventFailedToDispatchEvent = new EventId(108, "DispatchEventFailedToDispatchEvent"); public static readonly EventId DispatchEventFailedToDispatchEvent = new EventId(108, "DispatchEventFailedToDispatchEvent");
public static readonly EventId BeginInvokeDotNet = new EventId(109, "BeginInvokeDotNet"); public static readonly EventId BeginInvokeDotNet = new EventId(109, "BeginInvokeDotNet");
public static readonly EventId EndInvokeDispatchException = new EventId(110, "EndInvokeDispatchException"); public static readonly EventId EndInvokeDispatchException = new EventId(110, "EndInvokeDispatchException");
@ -495,21 +475,16 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
EventIds.EndInvokeJSSucceeded, EventIds.EndInvokeJSSucceeded,
"The JS interop call with callback id '{AsyncCall}' succeeded."); "The JS interop call with callback id '{AsyncCall}' succeeded.");
_dispatchEventFailedToParseEventDescriptor = LoggerMessage.Define( _dispatchEventFailedToParseEventData = LoggerMessage.Define(
LogLevel.Debug, LogLevel.Debug,
EventIds.DispatchEventFailedToParseEventDescriptor, EventIds.DispatchEventFailedToParseEventData,
"Failed to parse the event descriptor data when trying to dispatch an event."); "Failed to parse the event data when trying to dispatch an event.");
_dispatchEventFailedToDispatchEvent = LoggerMessage.Define<string>( _dispatchEventFailedToDispatchEvent = LoggerMessage.Define<string>(
LogLevel.Debug, LogLevel.Debug,
EventIds.DispatchEventFailedToDispatchEvent, EventIds.DispatchEventFailedToDispatchEvent,
"There was an error dispatching the event '{EventHandlerId}' to the application."); "There was an error dispatching the event '{EventHandlerId}' to the application.");
_dispatchEventThroughJSInterop = LoggerMessage.Define(
LogLevel.Debug,
EventIds.DispatchEventThroughJSInterop,
"There was an intent to dispatch a browser event through JS interop.");
_locationChange = LoggerMessage.Define<string, string>( _locationChange = LoggerMessage.Define<string, string>(
LogLevel.Debug, LogLevel.Debug,
EventIds.LocationChange, EventIds.LocationChange,
@ -555,7 +530,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
public static void EndInvokeJSSucceeded(ILogger logger, long asyncCall) => _endInvokeJSSucceeded(logger, asyncCall, null); public static void EndInvokeJSSucceeded(ILogger logger, long asyncCall) => _endInvokeJSSucceeded(logger, asyncCall, null);
public static void DispatchEventFailedToParseEventDescriptor(ILogger logger, Exception ex) => _dispatchEventFailedToParseEventDescriptor(logger, ex); public static void DispatchEventFailedToParseEventData(ILogger logger, Exception ex) => _dispatchEventFailedToParseEventData(logger, ex);
public static void DispatchEventFailedToDispatchEvent(ILogger logger, string eventHandlerId, Exception ex) => _dispatchEventFailedToDispatchEvent(logger, eventHandlerId ?? "", ex); public static void DispatchEventFailedToDispatchEvent(ILogger logger, string eventHandlerId, Exception ex) => _dispatchEventFailedToDispatchEvent(logger, eventHandlerId ?? "", ex);
@ -571,8 +546,6 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
} }
} }
public static void DispatchEventTroughJSInterop(ILogger logger) => _dispatchEventThroughJSInterop(logger, null);
public static void LocationChange(ILogger logger, string circuitId, string uri) => _locationChange(logger, circuitId, uri, null); public static void LocationChange(ILogger logger, string circuitId, string uri) => _locationChange(logger, circuitId, uri, null);
public static void LocationChangeSucceeded(ILogger logger, string circuitId, string uri) => _locationChangeSucceeded(logger, circuitId, uri, null); public static void LocationChangeSucceeded(ILogger logger, string circuitId, string uri) => _locationChangeSucceeded(logger, circuitId, uri, null);

View File

@ -72,11 +72,9 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
navigationManager.Initialize(baseUri, uri); navigationManager.Initialize(baseUri, uri);
} }
var rendererRegistry = new RendererRegistry();
var renderer = new RemoteRenderer( var renderer = new RemoteRenderer(
scope.ServiceProvider, scope.ServiceProvider,
_loggerFactory, _loggerFactory,
rendererRegistry,
_options, _options,
jsRuntime, jsRuntime,
client, client,
@ -91,7 +89,6 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
_circuitIdFactory.CreateCircuitId(), _circuitIdFactory.CreateCircuitId(),
scope, scope,
client, client,
rendererRegistry,
renderer, renderer,
components, components,
jsRuntime, jsRuntime,

View File

@ -24,7 +24,6 @@ namespace Microsoft.AspNetCore.Components.Web.Rendering
private readonly IJSRuntime _jsRuntime; private readonly IJSRuntime _jsRuntime;
private readonly CircuitClientProxy _client; private readonly CircuitClientProxy _client;
private readonly CircuitOptions _options; private readonly CircuitOptions _options;
private readonly RendererRegistry _rendererRegistry;
private readonly ILogger _logger; private readonly ILogger _logger;
internal readonly ConcurrentQueue<UnacknowledgedRenderBatch> _unacknowledgedRenderBatches = new ConcurrentQueue<UnacknowledgedRenderBatch>(); internal readonly ConcurrentQueue<UnacknowledgedRenderBatch> _unacknowledgedRenderBatches = new ConcurrentQueue<UnacknowledgedRenderBatch>();
private long _nextRenderId = 1; private long _nextRenderId = 1;
@ -41,7 +40,6 @@ namespace Microsoft.AspNetCore.Components.Web.Rendering
public RemoteRenderer( public RemoteRenderer(
IServiceProvider serviceProvider, IServiceProvider serviceProvider,
ILoggerFactory loggerFactory, ILoggerFactory loggerFactory,
RendererRegistry rendererRegistry,
CircuitOptions options, CircuitOptions options,
IJSRuntime jsRuntime, IJSRuntime jsRuntime,
CircuitClientProxy client, CircuitClientProxy client,
@ -49,19 +47,14 @@ namespace Microsoft.AspNetCore.Components.Web.Rendering
ILogger logger) ILogger logger)
: base(serviceProvider, loggerFactory, encoder.Encode) : base(serviceProvider, loggerFactory, encoder.Encode)
{ {
_rendererRegistry = rendererRegistry;
_jsRuntime = jsRuntime; _jsRuntime = jsRuntime;
_client = client; _client = client;
_options = options; _options = options;
Id = _rendererRegistry.Add(this);
_logger = logger; _logger = logger;
} }
public override Dispatcher Dispatcher { get; } = Dispatcher.CreateDefault(); public override Dispatcher Dispatcher { get; } = Dispatcher.CreateDefault();
public int Id { get; }
/// <summary> /// <summary>
/// Associates the <see cref="IComponent"/> with the <see cref="RemoteRenderer"/>, /// Associates the <see cref="IComponent"/> with the <see cref="RemoteRenderer"/>,
/// causing it to be displayed in the specified DOM element. /// causing it to be displayed in the specified DOM element.
@ -75,7 +68,6 @@ namespace Microsoft.AspNetCore.Components.Web.Rendering
var attachComponentTask = _jsRuntime.InvokeAsync<object>( var attachComponentTask = _jsRuntime.InvokeAsync<object>(
"Blazor._internal.attachRootComponentToElement", "Blazor._internal.attachRootComponentToElement",
Id,
domElementSelector, domElementSelector,
componentId); componentId);
CaptureAsyncExceptions(attachComponentTask); CaptureAsyncExceptions(attachComponentTask);
@ -133,7 +125,6 @@ namespace Microsoft.AspNetCore.Components.Web.Rendering
protected override void Dispose(bool disposing) protected override void Dispose(bool disposing)
{ {
_disposing = true; _disposing = true;
_rendererRegistry.TryRemove(Id);
while (_unacknowledgedRenderBatches.TryDequeue(out var entry)) while (_unacknowledgedRenderBatches.TryDequeue(out var entry))
{ {
entry.CompletionSource.TrySetCanceled(); entry.CompletionSource.TrySetCanceled();
@ -220,7 +211,7 @@ namespace Microsoft.AspNetCore.Components.Web.Rendering
Log.BeginUpdateDisplayAsync(_logger, _client.ConnectionId, pending.BatchId, pending.Data.Count); Log.BeginUpdateDisplayAsync(_logger, _client.ConnectionId, pending.BatchId, pending.Data.Count);
var segment = new ArraySegment<byte>(pending.Data.Buffer, 0, pending.Data.Count); var segment = new ArraySegment<byte>(pending.Data.Buffer, 0, pending.Data.Count);
await _client.SendAsync("JS.RenderBatch", Id, pending.BatchId, segment); await _client.SendAsync("JS.RenderBatch", pending.BatchId, segment);
} }
catch (Exception e) catch (Exception e)
{ {

View File

@ -41,6 +41,10 @@
<Compile Include="$(ComponentsSharedSourceRoot)src\CacheHeaderSettings.cs" Link="Shared\CacheHeaderSettings.cs" /> <Compile Include="$(ComponentsSharedSourceRoot)src\CacheHeaderSettings.cs" Link="Shared\CacheHeaderSettings.cs" />
<Compile Include="$(ComponentsSharedSourceRoot)src\ArrayBuilder.cs" LinkBase="Circuits" /> <Compile Include="$(ComponentsSharedSourceRoot)src\ArrayBuilder.cs" LinkBase="Circuits" />
<Compile Include="..\..\Shared\src\BrowserNavigationManagerInterop.cs" />
<Compile Include="..\..\Shared\src\JsonSerializerOptionsProvider.cs" />
<Compile Include="..\..\Shared\src\WebEventData.cs" />
<Compile Include="$(RepoRoot)src\SignalR\common\Shared\BinaryMessageFormatter.cs" LinkBase="BlazorPack" /> <Compile Include="$(RepoRoot)src\SignalR\common\Shared\BinaryMessageFormatter.cs" LinkBase="BlazorPack" />
<Compile Include="$(RepoRoot)src\SignalR\common\Shared\BinaryMessageParser.cs" LinkBase="BlazorPack" /> <Compile Include="$(RepoRoot)src\SignalR\common\Shared\BinaryMessageParser.cs" LinkBase="BlazorPack" />
<Compile Include="$(RepoRoot)src\SignalR\common\Shared\MemoryBufferWriter.cs" LinkBase="BlazorPack" /> <Compile Include="$(RepoRoot)src\SignalR\common\Shared\MemoryBufferWriter.cs" LinkBase="BlazorPack" />

View File

@ -232,15 +232,14 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
{ {
return new TestRemoteRenderer( return new TestRemoteRenderer(
Mock.Of<IServiceProvider>(), Mock.Of<IServiceProvider>(),
new RendererRegistry(),
Mock.Of<IJSRuntime>(), Mock.Of<IJSRuntime>(),
Mock.Of<IClientProxy>()); Mock.Of<IClientProxy>());
} }
private class TestRemoteRenderer : RemoteRenderer private class TestRemoteRenderer : RemoteRenderer
{ {
public TestRemoteRenderer(IServiceProvider serviceProvider, RendererRegistry rendererRegistry, IJSRuntime jsRuntime, IClientProxy client) public TestRemoteRenderer(IServiceProvider serviceProvider, IJSRuntime jsRuntime, IClientProxy client)
: base(serviceProvider, NullLoggerFactory.Instance, rendererRegistry, new CircuitOptions(), jsRuntime, new CircuitClientProxy(client, "connection"), HtmlEncoder.Default, NullLogger.Instance) : base(serviceProvider, NullLoggerFactory.Instance, new CircuitOptions(), jsRuntime, new CircuitClientProxy(client, "connection"), HtmlEncoder.Default, NullLogger.Instance)
{ {
} }

View File

@ -142,7 +142,7 @@ namespace Microsoft.AspNetCore.Components.Web.Rendering
var initialClient = new Mock<IClientProxy>(); var initialClient = new Mock<IClientProxy>();
initialClient.Setup(c => c.SendCoreAsync(It.IsAny<string>(), It.IsAny<object[]>(), It.IsAny<CancellationToken>())) initialClient.Setup(c => c.SendCoreAsync(It.IsAny<string>(), It.IsAny<object[]>(), It.IsAny<CancellationToken>()))
.Callback((string name, object[] value, CancellationToken token) => renderIds.Add((long)value[1])) .Callback((string name, object[] value, CancellationToken token) => renderIds.Add((long)value[0]))
.Returns(firstBatchTCS.Task); .Returns(firstBatchTCS.Task);
var circuitClient = new CircuitClientProxy(initialClient.Object, "connection0"); var circuitClient = new CircuitClientProxy(initialClient.Object, "connection0");
var renderer = GetRemoteRenderer(serviceProvider, circuitClient); var renderer = GetRemoteRenderer(serviceProvider, circuitClient);
@ -155,8 +155,8 @@ namespace Microsoft.AspNetCore.Components.Web.Rendering
var client = new Mock<IClientProxy>(); var client = new Mock<IClientProxy>();
client.Setup(c => c.SendCoreAsync(It.IsAny<string>(), It.IsAny<object[]>(), It.IsAny<CancellationToken>())) client.Setup(c => c.SendCoreAsync(It.IsAny<string>(), It.IsAny<object[]>(), It.IsAny<CancellationToken>()))
.Callback((string name, object[] value, CancellationToken token) => renderIds.Add((long)value[1])) .Callback((string name, object[] value, CancellationToken token) => renderIds.Add((long)value[0]))
.Returns<string, object[], CancellationToken>((n, v, t) => (long)v[1] == 3 ? secondBatchTCS.Task : thirdBatchTCS.Task); .Returns<string, object[], CancellationToken>((n, v, t) => (long)v[0] == 3 ? secondBatchTCS.Task : thirdBatchTCS.Task);
var componentId = renderer.AssignRootComponentId(component); var componentId = renderer.AssignRootComponentId(component);
component.TriggerRender(); component.TriggerRender();
@ -465,7 +465,6 @@ namespace Microsoft.AspNetCore.Components.Web.Rendering
return new RemoteRenderer( return new RemoteRenderer(
serviceProvider, serviceProvider,
NullLoggerFactory.Instance, NullLoggerFactory.Instance,
new RendererRegistry(),
new CircuitOptions(), new CircuitOptions(),
jsRuntime.Object, jsRuntime.Object,
circuitClientProxy, circuitClientProxy,

View File

@ -18,8 +18,8 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
{ {
internal class TestCircuitHost : CircuitHost internal class TestCircuitHost : CircuitHost
{ {
private TestCircuitHost(string circuitId, IServiceScope scope, CircuitClientProxy client, RendererRegistry rendererRegistry, RemoteRenderer renderer, IReadOnlyList<ComponentDescriptor> descriptors, RemoteJSRuntime jsRuntime, CircuitHandler[] circuitHandlers, ILogger logger) private TestCircuitHost(string circuitId, IServiceScope scope, CircuitClientProxy client, RemoteRenderer renderer, IReadOnlyList<ComponentDescriptor> descriptors, RemoteJSRuntime jsRuntime, CircuitHandler[] circuitHandlers, ILogger logger)
: base(circuitId, scope, client, rendererRegistry, renderer, descriptors, jsRuntime, circuitHandlers, logger) : base(circuitId, scope, client, renderer, descriptors, jsRuntime, circuitHandlers, logger)
{ {
} }
@ -37,7 +37,6 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
{ {
serviceScope = serviceScope ?? Mock.Of<IServiceScope>(); serviceScope = serviceScope ?? Mock.Of<IServiceScope>();
clientProxy = clientProxy ?? new CircuitClientProxy(Mock.Of<IClientProxy>(), Guid.NewGuid().ToString()); clientProxy = clientProxy ?? new CircuitClientProxy(Mock.Of<IClientProxy>(), Guid.NewGuid().ToString());
var renderRegistry = new RendererRegistry();
var jsRuntime = new RemoteJSRuntime(Options.Create(new CircuitOptions()), Mock.Of<ILogger<RemoteJSRuntime>>()); var jsRuntime = new RemoteJSRuntime(Options.Create(new CircuitOptions()), Mock.Of<ILogger<RemoteJSRuntime>>());
if (remoteRenderer == null) if (remoteRenderer == null)
@ -45,7 +44,6 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
remoteRenderer = new RemoteRenderer( remoteRenderer = new RemoteRenderer(
serviceScope.ServiceProvider ?? Mock.Of<IServiceProvider>(), serviceScope.ServiceProvider ?? Mock.Of<IServiceProvider>(),
NullLoggerFactory.Instance, NullLoggerFactory.Instance,
new RendererRegistry(),
new CircuitOptions(), new CircuitOptions(),
jsRuntime, jsRuntime,
clientProxy, clientProxy,
@ -58,7 +56,6 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
circuitId ?? Guid.NewGuid().ToString(), circuitId ?? Guid.NewGuid().ToString(),
serviceScope, serviceScope,
clientProxy, clientProxy,
renderRegistry,
remoteRenderer, remoteRenderer,
new List<ComponentDescriptor>(), new List<ComponentDescriptor>(),
jsRuntime, jsRuntime,

View File

@ -3,57 +3,47 @@
using System; using System;
using System.Text.Json; using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Rendering; using Microsoft.AspNetCore.Components.Rendering;
using Microsoft.JSInterop;
namespace Microsoft.AspNetCore.Components.Web namespace Microsoft.AspNetCore.Components.Web
{ {
/// <summary> internal class WebEventData
/// Provides mechanisms for dispatching events to components in a <see cref="Renderer"/>.
/// </summary>
public static class RendererRegistryEventDispatcher
{ {
/// <summary> // This class represents the second half of parsing incoming event data,
/// For framework use only. // once the type of the eventArgs becomes known.
/// </summary>
[JSInvokable(nameof(DispatchEvent))] public static WebEventData Parse(string eventDescriptorJson, string eventArgsJson)
public static Task DispatchEvent(BrowserEventDescriptor eventDescriptor, string eventArgsJson)
{ {
InterpretEventDescriptor(eventDescriptor); return Parse(
var eventArgs = ParseEventArgsJson(eventDescriptor.EventArgsType, eventArgsJson); Deserialize<WebEventDescriptor>(eventDescriptorJson),
var renderer = RendererRegistry.Current.Find(eventDescriptor.BrowserRendererId); eventArgsJson);
return renderer.DispatchEventAsync(eventDescriptor.EventHandlerId, eventDescriptor.EventFieldInfo, eventArgs);
} }
private static void InterpretEventDescriptor(BrowserEventDescriptor eventDescriptor) public static WebEventData Parse(WebEventDescriptor eventDescriptor, string eventArgsJson)
{ {
// The incoming field value can be either a bool or a string, but since the .NET property return new WebEventData(
// type is 'object', it will deserialize initially as a JsonElement eventDescriptor.BrowserRendererId,
var fieldInfo = eventDescriptor.EventFieldInfo; eventDescriptor.EventHandlerId,
if (fieldInfo != null) InterpretEventFieldInfo(eventDescriptor.EventFieldInfo),
{ ParseEventArgsJson(eventDescriptor.EventArgsType, eventArgsJson));
if (fieldInfo.FieldValue is JsonElement attributeValueJsonElement)
{
switch (attributeValueJsonElement.ValueKind)
{
case JsonValueKind.True:
case JsonValueKind.False:
fieldInfo.FieldValue = attributeValueJsonElement.GetBoolean();
break;
default:
fieldInfo.FieldValue = attributeValueJsonElement.GetString();
break;
}
}
else
{
// Unanticipated value type. Ensure we don't do anything with it.
eventDescriptor.EventFieldInfo = null;
}
}
} }
private WebEventData(int browserRendererId, ulong eventHandlerId, EventFieldInfo eventFieldInfo, EventArgs eventArgs)
{
BrowserRendererId = browserRendererId;
EventHandlerId = eventHandlerId;
EventFieldInfo = eventFieldInfo;
EventArgs = eventArgs;
}
public int BrowserRendererId { get; }
public ulong EventHandlerId { get; }
public EventFieldInfo EventFieldInfo { get; }
public EventArgs EventArgs { get; }
private static EventArgs ParseEventArgsJson(string eventArgsType, string eventArgsJson) private static EventArgs ParseEventArgsJson(string eventArgsType, string eventArgsJson)
{ {
switch (eventArgsType) switch (eventArgsType)
@ -83,13 +73,40 @@ namespace Microsoft.AspNetCore.Components.Web
case "wheel": case "wheel":
return Deserialize<UIWheelEventArgs>(eventArgsJson); return Deserialize<UIWheelEventArgs>(eventArgsJson);
default: default:
throw new ArgumentException($"Unsupported value '{eventArgsType}'.", nameof(eventArgsType)); throw new ArgumentException($"Unsupported value '{eventArgsType}'.", nameof(eventArgsType));
} }
} }
private static T Deserialize<T>(string eventArgsJson) private static T Deserialize<T>(string json)
{ {
return JsonSerializer.Deserialize<T>(eventArgsJson, JsonSerializerOptionsProvider.Options); return JsonSerializer.Deserialize<T>(json, JsonSerializerOptionsProvider.Options);
}
private static EventFieldInfo InterpretEventFieldInfo(EventFieldInfo fieldInfo)
{
// The incoming field value can be either a bool or a string, but since the .NET property
// type is 'object', it will deserialize initially as a JsonElement
if (fieldInfo?.FieldValue is JsonElement attributeValueJsonElement)
{
switch (attributeValueJsonElement.ValueKind)
{
case JsonValueKind.True:
case JsonValueKind.False:
return new EventFieldInfo
{
ComponentId = fieldInfo.ComponentId,
FieldValue = attributeValueJsonElement.GetBoolean()
};
default:
return new EventFieldInfo
{
ComponentId = fieldInfo.ComponentId,
FieldValue = attributeValueJsonElement.GetString()
};
}
}
return null;
} }
private static ChangeEventArgs DeserializeChangeEventArgs(string eventArgsJson) private static ChangeEventArgs DeserializeChangeEventArgs(string eventArgsJson)
@ -113,31 +130,5 @@ namespace Microsoft.AspNetCore.Components.Web
} }
return changeArgs; return changeArgs;
} }
/// <summary>
/// For framework use only.
/// </summary>
public class BrowserEventDescriptor
{
/// <summary>
/// For framework use only.
/// </summary>
public int BrowserRendererId { get; set; }
/// <summary>
/// For framework use only.
/// </summary>
public ulong EventHandlerId { get; set; }
/// <summary>
/// For framework use only.
/// </summary>
public string EventArgsType { get; set; }
/// <summary>
/// For framework use only.
/// </summary>
public EventFieldInfo EventFieldInfo { get; set; }
}
} }
} }

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -85,12 +85,11 @@ async function initializeConnection(options: BlazorOptions, logger: Logger): Pro
connection.on('JS.BeginInvokeJS', DotNet.jsCallDispatcher.beginInvokeJSFromDotNet); connection.on('JS.BeginInvokeJS', DotNet.jsCallDispatcher.beginInvokeJSFromDotNet);
connection.on('JS.EndInvokeDotNet', (args: string) => DotNet.jsCallDispatcher.endInvokeDotNetFromJS(...(JSON.parse(args) as [string, boolean, unknown]))); connection.on('JS.EndInvokeDotNet', (args: string) => DotNet.jsCallDispatcher.endInvokeDotNetFromJS(...(JSON.parse(args) as [string, boolean, unknown])));
connection.on('JS.RenderBatch', (browserRendererId: number, batchId: number, batchData: Uint8Array) => {
logger.log(LogLevel.Debug, `Received render batch for ${browserRendererId} with id ${batchId} and ${batchData.byteLength} bytes.`);
const queue = RenderQueue.getOrCreateQueue(browserRendererId, logger); const renderQueue = new RenderQueue(/* renderer ID unused with remote renderer */ 0, logger);
connection.on('JS.RenderBatch', (batchId: number, batchData: Uint8Array) => {
queue.processBatch(batchId, batchData, connection); logger.log(LogLevel.Debug, `Received render batch with id ${batchId} and ${batchData.byteLength} bytes.`);
renderQueue.processBatch(batchId, batchData, connection);
}); });
connection.onclose(error => !renderingFailed && options.reconnectionHandler!.onConnectionDown(options.reconnectionOptions, error)); connection.onclose(error => !renderingFailed && options.reconnectionHandler!.onConnectionDown(options.reconnectionOptions, error));

View File

@ -18,7 +18,7 @@ async function boot(options?: any): Promise<void> {
} }
started = true; started = true;
setEventDispatcher((eventDescriptor, eventArgs) => DotNet.invokeMethodAsync('Microsoft.AspNetCore.Components.Web', 'DispatchEvent', eventDescriptor, JSON.stringify(eventArgs))); setEventDispatcher((eventDescriptor, eventArgs) => DotNet.invokeMethodAsync('Microsoft.AspNetCore.Blazor', 'DispatchEvent', eventDescriptor, JSON.stringify(eventArgs)));
// Configure environment for execution under Mono WebAssembly with shared-memory rendering // Configure environment for execution under Mono WebAssembly with shared-memory rendering
const platform = Environment.setPlatform(monoPlatform); const platform = Environment.setPlatform(monoPlatform);

View File

@ -4,8 +4,6 @@ import { Logger, LogLevel } from '../Logging/Logger';
import { HubConnection } from '@aspnet/signalr'; import { HubConnection } from '@aspnet/signalr';
export class RenderQueue { export class RenderQueue {
private static renderQueues = new Map<number, RenderQueue>();
private nextBatchId = 2; private nextBatchId = 2;
private fatalError?: string; private fatalError?: string;
@ -19,17 +17,6 @@ export class RenderQueue {
this.logger = logger; this.logger = logger;
} }
public static getOrCreateQueue(browserRendererId: number, logger: Logger): RenderQueue {
const queue = this.renderQueues.get(browserRendererId);
if (queue) {
return queue;
}
const newQueue = new RenderQueue(browserRendererId, logger);
this.renderQueues.set(browserRendererId, newQueue);
return newQueue;
}
public async processBatch(receivedBatchId: number, batchData: Uint8Array, connection: HubConnection): Promise<void> { public async processBatch(receivedBatchId: number, batchData: Uint8Array, connection: HubConnection): Promise<void> {
if (receivedBatchId < this.nextBatchId) { if (receivedBatchId < this.nextBatchId) {
// SignalR delivers messages in order, but it does not guarantee that the message gets delivered. // SignalR delivers messages in order, but it does not guarantee that the message gets delivered.

View File

@ -12,7 +12,6 @@ const browserRenderers: BrowserRendererRegistry = {};
let shouldResetScrollAfterNextBatch = false; let shouldResetScrollAfterNextBatch = false;
export function attachRootComponentToLogicalElement(browserRendererId: number, logicalElement: LogicalElement, componentId: number): void { export function attachRootComponentToLogicalElement(browserRendererId: number, logicalElement: LogicalElement, componentId: number): void {
let browserRenderer = browserRenderers[browserRendererId]; let browserRenderer = browserRenderers[browserRendererId];
if (!browserRenderer) { if (!browserRenderer) {
browserRenderer = browserRenderers[browserRendererId] = new BrowserRenderer(browserRendererId); browserRenderer = browserRenderers[browserRendererId] = new BrowserRenderer(browserRendererId);
@ -21,15 +20,15 @@ export function attachRootComponentToLogicalElement(browserRendererId: number, l
browserRenderer.attachRootComponentToLogicalElement(componentId, logicalElement); browserRenderer.attachRootComponentToLogicalElement(componentId, logicalElement);
} }
export function attachRootComponentToElement(browserRendererId: number, elementSelector: string, componentId: number): void { export function attachRootComponentToElement(elementSelector: string, componentId: number, browserRendererId?: number): void {
const element = document.querySelector(elementSelector); const element = document.querySelector(elementSelector);
if (!element) { if (!element) {
throw new Error(`Could not find any element matching selector '${elementSelector}'.`); throw new Error(`Could not find any element matching selector '${elementSelector}'.`);
} }
// 'allowExistingContents' to keep any prerendered content until we do the first client-side render // 'allowExistingContents' to keep any prerendered content until we do the first client-side render
attachRootComponentToLogicalElement(browserRendererId, toLogicalElement(element, /* allow existing contents */ true), componentId); // Only client-side Blazor supplies a browser renderer ID
attachRootComponentToLogicalElement(browserRendererId || 0, toLogicalElement(element, /* allow existing contents */ true), componentId);
} }
export function renderBatch(browserRendererId: number, batch: RenderBatch): void { export function renderBatch(browserRendererId: number, batch: RenderBatch): void {

View File

@ -10,23 +10,8 @@ jest.mock('../src/Rendering/Renderer', () => ({
describe('RenderQueue', () => { describe('RenderQueue', () => {
it('getOrCreateRenderQueue returns a new queue if one does not exist for a renderer', () => {
const queue = RenderQueue.getOrCreateQueue(1, NullLogger.instance);
expect(queue).toBeDefined();
});
it('getOrCreateRenderQueue returns an existing queue if one exists for a renderer', () => {
const queue = RenderQueue.getOrCreateQueue(2, NullLogger.instance);
const secondQueue = RenderQueue.getOrCreateQueue(2, NullLogger.instance);
expect(secondQueue).toBe(queue);
});
it('processBatch acknowledges previously rendered batches', () => { it('processBatch acknowledges previously rendered batches', () => {
const queue = RenderQueue.getOrCreateQueue(3, NullLogger.instance); const queue = new RenderQueue(0, NullLogger.instance);
const sendMock = jest.fn(); const sendMock = jest.fn();
const connection = { send: sendMock } as any as signalR.HubConnection; const connection = { send: sendMock } as any as signalR.HubConnection;
@ -37,7 +22,7 @@ describe('RenderQueue', () => {
}); });
it('processBatch does not render out of order batches', () => { it('processBatch does not render out of order batches', () => {
const queue = RenderQueue.getOrCreateQueue(4, NullLogger.instance); const queue = new RenderQueue(0, NullLogger.instance);
const sendMock = jest.fn(); const sendMock = jest.fn();
const connection = { send: sendMock } as any as signalR.HubConnection; const connection = { send: sendMock } as any as signalR.HubConnection;
@ -47,7 +32,7 @@ describe('RenderQueue', () => {
}); });
it('processBatch renders pending batches', () => { it('processBatch renders pending batches', () => {
const queue = RenderQueue.getOrCreateQueue(5, NullLogger.instance); const queue = new RenderQueue(0, NullLogger.instance);
const sendMock = jest.fn(); const sendMock = jest.fn();
const connection = { send: sendMock } as any as signalR.HubConnection; const connection = { send: sendMock } as any as signalR.HubConnection;

View File

@ -422,17 +422,12 @@ namespace Microsoft.AspNetCore.Components.Routing
} }
namespace Microsoft.AspNetCore.Components.Web namespace Microsoft.AspNetCore.Components.Web
{ {
public static partial class RendererRegistryEventDispatcher public sealed partial class WebEventDescriptor
{ {
[Microsoft.JSInterop.JSInvokableAttribute("DispatchEvent")] public WebEventDescriptor() { }
public static System.Threading.Tasks.Task DispatchEvent(Microsoft.AspNetCore.Components.Web.RendererRegistryEventDispatcher.BrowserEventDescriptor eventDescriptor, string eventArgsJson) { throw null; } public int BrowserRendererId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
public partial class BrowserEventDescriptor public string EventArgsType { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
{ public Microsoft.AspNetCore.Components.Rendering.EventFieldInfo EventFieldInfo { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
public BrowserEventDescriptor() { } public ulong EventHandlerId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
public int BrowserRendererId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
public string EventArgsType { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
public Microsoft.AspNetCore.Components.Rendering.EventFieldInfo EventFieldInfo { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
public ulong EventHandlerId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
}
} }
} }

View File

@ -1,6 +1,3 @@
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Blazor, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Components.Server, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Components.Server.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] [assembly: InternalsVisibleTo("Microsoft.AspNetCore.Components.Server.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]

View File

@ -1,95 +0,0 @@
// 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 Microsoft.AspNetCore.Components.Rendering;
using System.Collections.Generic;
using System.Threading;
namespace Microsoft.AspNetCore.Components.Web
{
// Provides mechanisms for locating <see cref="Renderer"/> instances
// by ID. This is used when receiving incoming events. It also implicitly
// roots <see cref="Renderer"/> instances and their associated component instances
// so they cannot be GCed while they are still registered for events.
/// <summary>
/// Framework infrastructure, not intended to be used by application code.
/// </summary>
internal class RendererRegistry
{
private static AsyncLocal<RendererRegistry> _current;
private static readonly RendererRegistry _globalRegistry;
// By default the registry will be set to a default value. This means that
// things will 'just work when running in the browser.
//
// Running in Server-Side Components - any call into the Circuit will set this value via
// the async local. This will ensure that the incoming call can resolve the correct
// renderer associated with the user context.
static RendererRegistry()
{
_current = new AsyncLocal<RendererRegistry>();
_globalRegistry = new RendererRegistry();
}
/// <summary>
/// Framework infrastructure, not intended to be used by application code.
/// </summary>
public static RendererRegistry Current => _current.Value ?? _globalRegistry;
/// <summary>
/// Framework infrastructure, not intended by used by application code.
/// </summary>
public static void SetCurrentRendererRegistry(RendererRegistry registry)
{
_current.Value = registry;
}
private int _nextId;
private IDictionary<int, Renderer> _renderers = new Dictionary<int, Renderer>();
/// <summary>
/// Framework infrastructure, not intended by used by application code.
/// </summary>
public int Add(Renderer renderer)
{
lock (_renderers)
{
var id = _nextId++;
_renderers.Add(id, renderer);
return id;
}
}
/// <summary>
/// Framework infrastructure, not intended by used by application code.
/// </summary>
public Renderer Find(int rendererId)
{
lock (_renderers)
{
return _renderers[rendererId];
}
}
/// <summary>
/// Framework infrastructure, not intended by used by application code.
/// </summary>
public bool TryRemove(int rendererId)
{
lock (_renderers)
{
if (_renderers.ContainsKey(rendererId))
{
_renderers.Remove(rendererId);
return true;
}
else
{
return false;
}
}
}
}
}

View File

@ -0,0 +1,38 @@
// 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 Microsoft.AspNetCore.Components.Rendering;
namespace Microsoft.AspNetCore.Components.Web
{
/// <summary>
/// For framework use only.
/// </summary>
public sealed class WebEventDescriptor
{
// We split the incoming event data in two, because we don't know what type
// to use when deserializing the args until we've deserialized the descriptor.
// This class represents the first half of the parsing process.
// It's public only because it's part of the signature of a [JSInvokable] method.
/// <summary>
/// For framework use only.
/// </summary>
public int BrowserRendererId { get; set; }
/// <summary>
/// For framework use only.
/// </summary>
public ulong EventHandlerId { get; set; }
/// <summary>
/// For framework use only.
/// </summary>
public string EventArgsType { get; set; }
/// <summary>
/// For framework use only.
/// </summary>
public EventFieldInfo EventFieldInfo { get; set; }
}
}

View File

@ -37,7 +37,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
private void CreateDefaultConfiguration() private void CreateDefaultConfiguration()
{ {
Client = new BlazorClient() { DefaultLatencyTimeout = DefaultLatencyTimeout }; Client = new BlazorClient() { DefaultLatencyTimeout = DefaultLatencyTimeout };
Client.RenderBatchReceived += (id, rendererId, data) => Batches.Add(new Batch(id, rendererId, data)); Client.RenderBatchReceived += (id, data) => Batches.Add(new Batch(id, data));
Client.OnCircuitError += (error) => Errors.Add(error); Client.OnCircuitError += (error) => Errors.Add(error);
_ = _serverFixture.RootUri; // this is needed for the side-effects of getting the URI. _ = _serverFixture.RootUri; // this is needed for the side-effects of getting the URI.
@ -267,15 +267,13 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
private class Batch private class Batch
{ {
public Batch(int id, int rendererId, byte [] data) public Batch(int id, byte [] data)
{ {
Id = id; Id = id;
RendererId = rendererId;
Data = data; Data = data;
} }
public int Id { get; } public int Id { get; }
public int RendererId { get; }
public byte[] Data { get; } public byte[] Data { get; }
} }
} }

View File

@ -386,7 +386,30 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
null); null);
Assert.Contains( Assert.Contains(
(LogLevel.Debug, "DispatchEventFailedToParseEventDescriptor"), (LogLevel.Debug, "DispatchEventFailedToParseEventData"),
logEvents);
await ValidateClientKeepsWorking(Client, batches);
}
[Fact]
public async Task DispatchingEventsWithInvalidEventDescriptor()
{
// Arrange
var (interopCalls, dotNetCompletions, batches) = ConfigureClient();
await GoToTestComponent(batches);
var sink = _serverFixture.Host.Services.GetRequiredService<TestSink>();
var logEvents = new List<(LogLevel logLevel, string)>();
sink.MessageLogged += (wc) => logEvents.Add((wc.LogLevel, wc.EventId.Name));
// Act
await Client.HubConnection.InvokeAsync(
"DispatchBrowserEvent",
"{Invalid:{\"payload}",
"{}");
Assert.Contains(
(LogLevel.Debug, "DispatchEventFailedToParseEventData"),
logEvents); logEvents);
await ValidateClientKeepsWorking(Client, batches); await ValidateClientKeepsWorking(Client, batches);
@ -403,7 +426,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
sink.MessageLogged += (wc) => logEvents.Add((wc.LogLevel, wc.EventId.Name)); sink.MessageLogged += (wc) => logEvents.Add((wc.LogLevel, wc.EventId.Name));
// Act // Act
var browserDescriptor = new RendererRegistryEventDispatcher.BrowserEventDescriptor() var browserDescriptor = new WebEventDescriptor()
{ {
BrowserRendererId = 0, BrowserRendererId = 0,
EventHandlerId = 6, EventHandlerId = 6,
@ -416,7 +439,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
"{Invalid:{\"payload}"); "{Invalid:{\"payload}");
Assert.Contains( Assert.Contains(
(LogLevel.Debug, "DispatchEventFailedToDispatchEvent"), (LogLevel.Debug, "DispatchEventFailedToParseEventData"),
logEvents); logEvents);
await ValidateClientKeepsWorking(Client, batches); await ValidateClientKeepsWorking(Client, batches);
@ -438,7 +461,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
Type = "click", Type = "click",
Detail = 1 Detail = 1
}; };
var browserDescriptor = new RendererRegistryEventDispatcher.BrowserEventDescriptor() var browserDescriptor = new WebEventDescriptor()
{ {
BrowserRendererId = 0, BrowserRendererId = 0,
EventHandlerId = 1, EventHandlerId = 1,
@ -458,47 +481,6 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
await ValidateClientKeepsWorking(Client, batches); await ValidateClientKeepsWorking(Client, batches);
} }
[Fact]
public async Task DispatchingEventThroughJSInterop()
{
// Arrange
var (interopCalls, dotNetCompletions, batches) = ConfigureClient();
await GoToTestComponent(batches);
var sink = _serverFixture.Host.Services.GetRequiredService<TestSink>();
var logEvents = new List<(LogLevel logLevel, string eventIdName)>();
sink.MessageLogged += (wc) => logEvents.Add((wc.LogLevel, wc.EventId.Name));
// Act
var mouseEventArgs = new UIMouseEventArgs()
{
Type = "click",
Detail = 1
};
var browserDescriptor = new RendererRegistryEventDispatcher.BrowserEventDescriptor()
{
BrowserRendererId = 0,
EventHandlerId = 1,
EventArgsType = "mouse",
};
var serializerOptions = TestJsonSerializerOptionsProvider.Options;
var uiArgs = JsonSerializer.Serialize(mouseEventArgs, serializerOptions);
await Assert.ThrowsAsync<TaskCanceledException>(() => Client.InvokeDotNetMethod(
0,
"Microsoft.AspNetCore.Components.Web",
"DispatchEvent",
null,
JsonSerializer.Serialize(new object[] { browserDescriptor, uiArgs }, serializerOptions)));
Assert.Contains(
(LogLevel.Debug, "DispatchEventThroughJSInterop"),
logEvents);
await ValidateClientKeepsWorking(Client, batches);
}
[Fact] [Fact]
public async Task EventHandlerThrowsSyncExceptionTerminatesTheCircuit() public async Task EventHandlerThrowsSyncExceptionTerminatesTheCircuit()
{ {
@ -519,7 +501,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
"Handler threw an exception" == e.exception.Message); "Handler threw an exception" == e.exception.Message);
} }
private Task ValidateClientKeepsWorking(BlazorClient Client, List<(int, int, byte[])> batches) => private Task ValidateClientKeepsWorking(BlazorClient Client, List<(int, byte[])> batches) =>
ValidateClientKeepsWorking(Client, () => batches.Count); ValidateClientKeepsWorking(Client, () => batches.Count);
private async Task ValidateClientKeepsWorking(BlazorClient Client, Func<int> countAccessor) private async Task ValidateClientKeepsWorking(BlazorClient Client, Func<int> countAccessor)
@ -530,7 +512,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
Assert.Equal(currentBatches + 1, countAccessor()); Assert.Equal(currentBatches + 1, countAccessor());
} }
private async Task GoToTestComponent(List<(int, int, byte[])> batches) private async Task GoToTestComponent(List<(int, byte[])> batches)
{ {
var rootUri = _serverFixture.RootUri; var rootUri = _serverFixture.RootUri;
Assert.True(await Client.ConnectAsync(new Uri(rootUri, "/subdir"), prerendered: false), "Couldn't connect to the app"); Assert.True(await Client.ConnectAsync(new Uri(rootUri, "/subdir"), prerendered: false), "Couldn't connect to the app");
@ -540,12 +522,12 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
Assert.Equal(2, batches.Count); Assert.Equal(2, batches.Count);
} }
private (List<(int id, string identifier, string args)>, List<string>, List<(int, int, byte[])>) ConfigureClient() private (List<(int id, string identifier, string args)>, List<string>, List<(int, byte[])>) ConfigureClient()
{ {
var interopCalls = new List<(int, string, string)>(); var interopCalls = new List<(int, string, string)>();
Client.JSInterop += (int arg1, string arg2, string arg3) => interopCalls.Add((arg1, arg2, arg3)); Client.JSInterop += (int arg1, string arg2, string arg3) => interopCalls.Add((arg1, arg2, arg3));
var batches = new List<(int, int, byte[])>(); var batches = new List<(int, byte[])>();
Client.RenderBatchReceived += (id, renderer, data) => batches.Add((id, renderer, data)); Client.RenderBatchReceived += (renderer, data) => batches.Add((renderer, data));
var endInvokeDotNetCompletions = new List<string>(); var endInvokeDotNetCompletions = new List<string>();
Client.DotNetInteropCompletion += (completion) => endInvokeDotNetCompletions.Add(completion); Client.DotNetInteropCompletion += (completion) => endInvokeDotNetCompletions.Add(completion);
return (interopCalls, endInvokeDotNetCompletions, batches); return (interopCalls, endInvokeDotNetCompletions, batches);

View File

@ -31,7 +31,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
_ = _serverFixture.RootUri; _ = _serverFixture.RootUri;
Client = new BlazorClient() { DefaultLatencyTimeout = DefaultLatencyTimeout }; Client = new BlazorClient() { DefaultLatencyTimeout = DefaultLatencyTimeout };
Client.RenderBatchReceived += (rendererId, id, data) => Batches.Add(new Batch(rendererId, id, data)); Client.RenderBatchReceived += (id, data) => Batches.Add(new Batch(id, data));
Sink = _serverFixture.Host.Services.GetRequiredService<TestSink>(); Sink = _serverFixture.Host.Services.GetRequiredService<TestSink>();
Sink.MessageLogged += LogMessages; Sink.MessageLogged += LogMessages;
@ -101,15 +101,13 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
private class Batch private class Batch
{ {
public Batch(int rendererId, int id, byte[] data) public Batch(int id, byte[] data)
{ {
Id = id; Id = id;
RendererId = rendererId;
Data = data; Data = data;
} }
public int Id { get; } public int Id { get; }
public int RendererId { get; }
public byte[] Data { get; } public byte[] Data { get; }
} }

View File

@ -50,7 +50,7 @@ namespace Ignitor
public event Action<int, string, string> JSInterop; public event Action<int, string, string> JSInterop;
public event Action<int, int, byte[]> RenderBatchReceived; public event Action<int, byte[]> RenderBatchReceived;
public event Action<string> DotNetInteropCompletion; public event Action<string> DotNetInteropCompletion;
@ -232,7 +232,7 @@ namespace Ignitor
HubConnection.On<int, string, string>("JS.BeginInvokeJS", OnBeginInvokeJS); HubConnection.On<int, string, string>("JS.BeginInvokeJS", OnBeginInvokeJS);
HubConnection.On<string>("JS.EndInvokeDotNet", OnEndInvokeDotNet); HubConnection.On<string>("JS.EndInvokeDotNet", OnEndInvokeDotNet);
HubConnection.On<int, int, byte[]>("JS.RenderBatch", OnRenderBatch); HubConnection.On<int, byte[]>("JS.RenderBatch", OnRenderBatch);
HubConnection.On<string>("JS.Error", OnError); HubConnection.On<string>("JS.Error", OnError);
HubConnection.Closed += OnClosedAsync; HubConnection.Closed += OnClosedAsync;
@ -286,11 +286,11 @@ namespace Ignitor
} }
} }
private void OnRenderBatch(int browserRendererId, int batchId, byte[] batchData) private void OnRenderBatch(int batchId, byte[] batchData)
{ {
try try
{ {
RenderBatchReceived?.Invoke(browserRendererId, batchId, batchData); RenderBatchReceived?.Invoke(batchId, batchData);
var batch = RenderBatchReader.Read(batchData); var batch = RenderBatchReader.Read(batchData);

View File

@ -75,7 +75,7 @@ namespace Ignitor
Value = value Value = value
}; };
var browserDescriptor = new RendererRegistryEventDispatcher.BrowserEventDescriptor() var webEventDescriptor = new WebEventDescriptor()
{ {
BrowserRendererId = 0, BrowserRendererId = 0,
EventHandlerId = changeEventDescriptor.EventId, EventHandlerId = changeEventDescriptor.EventId,
@ -87,7 +87,7 @@ namespace Ignitor
} }
}; };
return DispatchEventCore(connection, Serialize(browserDescriptor), Serialize(args)); return DispatchEventCore(connection, Serialize(webEventDescriptor), Serialize(args));
} }
public Task ClickAsync(HubConnection connection) public Task ClickAsync(HubConnection connection)
@ -102,14 +102,14 @@ namespace Ignitor
Type = clickEventDescriptor.EventName, Type = clickEventDescriptor.EventName,
Detail = 1 Detail = 1
}; };
var browserDescriptor = new RendererRegistryEventDispatcher.BrowserEventDescriptor() var webEventDescriptor = new WebEventDescriptor
{ {
BrowserRendererId = 0, BrowserRendererId = 0,
EventHandlerId = clickEventDescriptor.EventId, EventHandlerId = clickEventDescriptor.EventId,
EventArgsType = "mouse", EventArgsType = "mouse",
}; };
return DispatchEventCore(connection, Serialize(browserDescriptor), Serialize(mouseEventArgs)); return DispatchEventCore(connection, Serialize(webEventDescriptor), Serialize(mouseEventArgs));
} }
private static string Serialize<T>(T payload) => private static string Serialize<T>(T payload) =>

View File

@ -38,7 +38,7 @@ namespace Ignitor
var done = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously); var done = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
// Click the counter button 1000 times // Click the counter button 1000 times
client.RenderBatchReceived += (int browserRendererId, int batchId, byte[] data) => client.RenderBatchReceived += (int batchId, byte[] data) =>
{ {
if (batchId < 1000) if (batchId < 1000)
{ {