From 00d73256660cb7ace69dce20fa3982bba38a571d Mon Sep 17 00:00:00 2001 From: jacalvar Date: Tue, 11 Apr 2023 16:15:14 +0200 Subject: [PATCH 01/28] Enable handling POST requests --- .../Endpoints/src/Builder/RazorComponentEndpointFactory.cs | 4 ++-- .../Endpoints/test/RazorComponentEndpointFactoryTest.cs | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Components/Endpoints/src/Builder/RazorComponentEndpointFactory.cs b/src/Components/Endpoints/src/Builder/RazorComponentEndpointFactory.cs index 60fc5413faa6..4a6975d6c7fd 100644 --- a/src/Components/Endpoints/src/Builder/RazorComponentEndpointFactory.cs +++ b/src/Components/Endpoints/src/Builder/RazorComponentEndpointFactory.cs @@ -10,7 +10,7 @@ namespace Microsoft.AspNetCore.Components.Endpoints; internal class RazorComponentEndpointFactory { - private static readonly HttpMethodMetadata HttpGet = new(new[] { HttpMethods.Get }); + private static readonly HttpMethodMetadata HttpMethodsMetadata = new(new[] { HttpMethods.Get, HttpMethods.Post }); #pragma warning disable CA1822 // It's a singleton internal void AddEndpoints( @@ -36,7 +36,7 @@ internal class RazorComponentEndpointFactory // We do not support link generation, so explicitly opt-out. builder.Metadata.Add(new SuppressLinkGenerationMetadata()); - builder.Metadata.Add(HttpGet); + builder.Metadata.Add(HttpMethodsMetadata); builder.Metadata.Add(new ComponentTypeMetadata(pageDefinition.Type)); foreach (var convention in conventions) diff --git a/src/Components/Endpoints/test/RazorComponentEndpointFactoryTest.cs b/src/Components/Endpoints/test/RazorComponentEndpointFactoryTest.cs index 27c8bdda493f..b0a4f80cf483 100644 --- a/src/Components/Endpoints/test/RazorComponentEndpointFactoryTest.cs +++ b/src/Components/Endpoints/test/RazorComponentEndpointFactoryTest.cs @@ -36,8 +36,10 @@ public void AddEndpoints_CreatesEndpointWithExpectedMetadata() Assert.NotNull(endpoint.RequestDelegate); var methods = Assert.Single(endpoint.Metadata.GetOrderedMetadata()); - var method = Assert.Single(methods.HttpMethods); - Assert.Equal("GET", method); + Assert.Collection(methods.HttpMethods, + method => Assert.Equal("GET", method), + method => Assert.Equal("POST", method) + ); } [Fact] From bac4bf5c3ad866d1f888222cb082c03301b2d2a3 Mon Sep 17 00:00:00 2001 From: jacalvar Date: Wed, 12 Apr 2023 16:26:51 +0200 Subject: [PATCH 02/28] Add DebuggerDisplay to component state --- src/Components/Components/src/Rendering/ComponentState.cs | 7 +++++++ .../Components/src/Rendering/RenderQueueEntry.cs | 8 ++++++++ 2 files changed, 15 insertions(+) diff --git a/src/Components/Components/src/Rendering/ComponentState.cs b/src/Components/Components/src/Rendering/ComponentState.cs index 47c30bef1422..d0260f0d2374 100644 --- a/src/Components/Components/src/Rendering/ComponentState.cs +++ b/src/Components/Components/src/Rendering/ComponentState.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using Microsoft.AspNetCore.Components.RenderTree; @@ -11,6 +12,7 @@ namespace Microsoft.AspNetCore.Components.Rendering; /// within the context of a . This is an internal implementation /// detail of . /// +[DebuggerDisplay($"{{{nameof(GetDebuggerDisplay)}(),nq}}")] public class ComponentState : IDisposable { private readonly Renderer _renderer; @@ -297,4 +299,9 @@ internal Task DisposeInBatchAsync(RenderBatchBuilder batchBuilder) return Task.FromException(e); } } + + private string GetDebuggerDisplay() + { + return $"{ComponentId} - {Component.GetType().Name} - Disposed: {_componentWasDisposed}"; + } } diff --git a/src/Components/Components/src/Rendering/RenderQueueEntry.cs b/src/Components/Components/src/Rendering/RenderQueueEntry.cs index 5e1b00517c09..f0a13f2a5554 100644 --- a/src/Components/Components/src/Rendering/RenderQueueEntry.cs +++ b/src/Components/Components/src/Rendering/RenderQueueEntry.cs @@ -1,8 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Diagnostics; + namespace Microsoft.AspNetCore.Components.Rendering; +[DebuggerDisplay($"{{{nameof(GetDebuggerDisplay)}(),nq}}")] internal readonly struct RenderQueueEntry { public readonly ComponentState ComponentState; @@ -13,4 +16,9 @@ public RenderQueueEntry(ComponentState componentState, RenderFragment renderFrag ComponentState = componentState; RenderFragment = renderFragment ?? throw new ArgumentNullException(nameof(renderFragment)); } + + private string GetDebuggerDisplay() + { + return $"{ComponentState.ComponentId} - {ComponentState.Component.GetType().Name}"; + } } From 55922e7c7e2335588a3ea19f1b33307972a7876c Mon Sep 17 00:00:00 2001 From: jacalvar Date: Wed, 12 Apr 2023 16:32:45 +0200 Subject: [PATCH 03/28] Set form name --- .../Builder/RazorComponentEndpointFactory.cs | 10 +--- .../src/RazorComponentEndpointInvoker.cs | 51 ++++++++++++++++--- .../src/Rendering/EndpointHtmlRenderer.cs | 7 +++ 3 files changed, 53 insertions(+), 15 deletions(-) diff --git a/src/Components/Endpoints/src/Builder/RazorComponentEndpointFactory.cs b/src/Components/Endpoints/src/Builder/RazorComponentEndpointFactory.cs index 4a6975d6c7fd..4763eea15f81 100644 --- a/src/Components/Endpoints/src/Builder/RazorComponentEndpointFactory.cs +++ b/src/Components/Endpoints/src/Builder/RazorComponentEndpointFactory.cs @@ -55,16 +55,8 @@ internal class RazorComponentEndpointFactory // The display name is for debug purposes by endpoint routing. builder.DisplayName = $"{builder.RoutePattern.RawText} ({pageDefinition.DisplayName})"; - builder.RequestDelegate = CreateRouteDelegate(pageDefinition.Type); + builder.RequestDelegate = httpContext => new RazorComponentEndpointInvoker(httpContext, pageDefinition.Type).RenderComponent(); endpoints.Add(builder.Build()); } - - private static RequestDelegate CreateRouteDelegate(Type componentType) - { - return httpContext => - { - return new RazorComponentEndpointInvoker(httpContext, componentType).RenderComponent(); - }; - } } diff --git a/src/Components/Endpoints/src/RazorComponentEndpointInvoker.cs b/src/Components/Endpoints/src/RazorComponentEndpointInvoker.cs index 40c67538a276..d21342e96fce 100644 --- a/src/Components/Endpoints/src/RazorComponentEndpointInvoker.cs +++ b/src/Components/Endpoints/src/RazorComponentEndpointInvoker.cs @@ -1,9 +1,11 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Buffers; +using System.Net.Http; using System.Text; using System.Text.Encodings.Web; +using Microsoft.AspNetCore.Components.RenderTree; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.WebUtilities; using Microsoft.Extensions.DependencyInjection; @@ -32,14 +34,21 @@ private async Task RenderComponentCore() { _context.Response.ContentType = RazorComponentResultExecutor.DefaultContentType; + if (await ValidateRequestAsync()) + { + // If the request is not valid we've already set the response to a 400 or similar + // and we can just exit early. + return; + } + // We could pool these dictionary instances if we wanted, and possibly even the ParameterView // backing buffers could come from a pool like they do during rendering. var hostParameters = ParameterView.FromDictionary(new Dictionary - { - { nameof(RazorComponentEndpointHost.RenderMode), RenderMode.Static }, - { nameof(RazorComponentEndpointHost.ComponentType), _componentType }, - { nameof(RazorComponentEndpointHost.ComponentParameters), null }, - }); + { + { nameof(RazorComponentEndpointHost.RenderMode), RenderMode.Static }, + { nameof(RazorComponentEndpointHost.ComponentType), _componentType }, + { nameof(RazorComponentEndpointHost.ComponentParameters), null }, + }); await using var writer = CreateResponseWriter(_context.Response.Body); @@ -69,6 +78,36 @@ private async Task RenderComponentCore() await writer.FlushAsync(); } + private Task ValidateRequestAsync() + { + if (HttpMethods.IsPost(_context.Request.Method)) + { + return Task.FromResult(TrySetFormHandler()); + } + + return Task.FromResult(true); + } + + private bool TrySetFormHandler() + { + var handler = _context.Request.Path.Value; + if (_context.Request.Query.TryGetValue("handler", out var value)) + { + if (value.Count != 1) + { + _context.Response.StatusCode = StatusCodes.Status400BadRequest; + return false; + } + else + { + handler = value[0]; + } + } + + _renderer.SetFormHandlerName(handler!); + return true; + } + private static TextWriter CreateResponseWriter(Stream bodyStream) { // Matches MVC's MemoryPoolHttpResponseStreamWriterFactory.DefaultBufferSize diff --git a/src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.cs b/src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.cs index 1537690de994..d7f13cb73295 100644 --- a/src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.cs +++ b/src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.cs @@ -31,6 +31,8 @@ internal sealed partial class EndpointHtmlRenderer : StaticHtmlRenderer, ICompon private readonly IServiceProvider _services; private Task? _servicesInitializedTask; + private string? _formHandler; + // The underlying Renderer always tracks the pending tasks representing *full* quiescence, i.e., // when everything (regardless of streaming SSR) is fully complete. In this subclass we also track // the subset of those that are from the non-streaming subtrees, since we want the response to @@ -61,6 +63,11 @@ private static async Task InitializeStandardComponentServicesAsync(HttpContext h await componentApplicationLifetime.RestoreStateAsync(new PrerenderComponentApplicationStore()); } + internal void SetFormHandlerName(string name) + { + _formHandler = name; + } + protected override ComponentState CreateComponentState(int componentId, IComponent component, ComponentState? parentComponentState) => new EndpointComponentState(this, componentId, component, parentComponentState); From ed7c7933558b8e89d5595716034f833cb884e1ab Mon Sep 17 00:00:00 2001 From: jacalvar Date: Thu, 13 Apr 2023 16:20:35 +0200 Subject: [PATCH 04/28] Track named event handlers --- .../perf/RenderTreeDiffBuilderBenchmark.cs | 2 +- .../Components/src/PublicAPI.Unshipped.txt | 3 + .../src/RenderTree/RenderTreeDiffBuilder.cs | 34 +- .../Components/src/RenderTree/Renderer.cs | 18 +- .../src/Rendering/ComponentState.cs | 4 +- .../src/Rendering/RenderTreeBuilder.cs | 47 +++ .../test/RenderTreeDiffBuilderTest.cs | 18 +- .../Components/test/RendererTest.cs | 314 ++++++++++++++++-- .../src/RazorComponentEndpointInvoker.cs | 2 - .../src/Rendering/EndpointHtmlRenderer.cs | 71 ++++ src/Components/Shared/test/TestRenderer.cs | 14 + 11 files changed, 481 insertions(+), 46 deletions(-) diff --git a/src/Components/Components/perf/RenderTreeDiffBuilderBenchmark.cs b/src/Components/Components/perf/RenderTreeDiffBuilderBenchmark.cs index 172dc920d2de..19171f878d73 100644 --- a/src/Components/Components/perf/RenderTreeDiffBuilderBenchmark.cs +++ b/src/Components/Components/perf/RenderTreeDiffBuilderBenchmark.cs @@ -79,7 +79,7 @@ public RenderTreeDiffBuilderBenchmark() public void ComputeDiff_SingleFormField() { builder.ClearStateForCurrentBatch(); - var diff = RenderTreeDiffBuilder.ComputeDiff(renderer, builder, 0, original.GetFrames(), modified.GetFrames()); + var diff = RenderTreeDiffBuilder.ComputeDiff(renderer, builder, 0, modified.GetFrames(), original.GetFrames(), original.GetNamedEvents()); GC.KeepAlive(diff); } diff --git a/src/Components/Components/src/PublicAPI.Unshipped.txt b/src/Components/Components/src/PublicAPI.Unshipped.txt index 7132b2ce2088..529c2873b6c9 100644 --- a/src/Components/Components/src/PublicAPI.Unshipped.txt +++ b/src/Components/Components/src/PublicAPI.Unshipped.txt @@ -4,6 +4,7 @@ Microsoft.AspNetCore.Components.Infrastructure.ComponentStatePersistenceManager. Microsoft.AspNetCore.Components.RenderHandle.DispatchExceptionAsync(System.Exception! exception) -> System.Threading.Tasks.Task! *REMOVED*Microsoft.AspNetCore.Components.NavigationManager.ToAbsoluteUri(string! relativeUri) -> System.Uri! Microsoft.AspNetCore.Components.NavigationManager.ToAbsoluteUri(string? relativeUri) -> System.Uri! +Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder.SetEventHandlerName(string! eventHandlerName) -> void Microsoft.AspNetCore.Components.Routing.IScrollToLocationHash Microsoft.AspNetCore.Components.Routing.IScrollToLocationHash.RefreshScrollPositionForHash(string! locationAbsolute) -> System.Threading.Tasks.Task! Microsoft.AspNetCore.Components.Rendering.ComponentState @@ -39,3 +40,5 @@ override Microsoft.AspNetCore.Components.EventCallback.GetHashCode() -> override Microsoft.AspNetCore.Components.EventCallback.Equals(object? obj) -> bool virtual Microsoft.AspNetCore.Components.RenderTree.Renderer.AddPendingTask(Microsoft.AspNetCore.Components.Rendering.ComponentState? componentState, System.Threading.Tasks.Task! task) -> void virtual Microsoft.AspNetCore.Components.RenderTree.Renderer.CreateComponentState(int componentId, Microsoft.AspNetCore.Components.IComponent! component, Microsoft.AspNetCore.Components.Rendering.ComponentState? parentComponentState) -> Microsoft.AspNetCore.Components.Rendering.ComponentState! +virtual Microsoft.AspNetCore.Components.RenderTree.Renderer.ShouldTrackNamedEventHandlers() -> bool +virtual Microsoft.AspNetCore.Components.RenderTree.Renderer.TrackNamedEventId(ulong eventHandlerId, int componentId, string! eventHandlerName) -> void diff --git a/src/Components/Components/src/RenderTree/RenderTreeDiffBuilder.cs b/src/Components/Components/src/RenderTree/RenderTreeDiffBuilder.cs index be07fb06a385..492b1eb6292b 100644 --- a/src/Components/Components/src/RenderTree/RenderTreeDiffBuilder.cs +++ b/src/Components/Components/src/RenderTree/RenderTreeDiffBuilder.cs @@ -23,7 +23,8 @@ enum DiffAction { Match, Insert, Delete } RenderBatchBuilder batchBuilder, int componentId, ArrayRange oldTree, - ArrayRange newTree) + ArrayRange newTree, + Dictionary? namedEventIndexes) { var editsBuffer = batchBuilder.EditsBuffer; var editsBufferStartLength = editsBuffer.Count; @@ -33,6 +34,37 @@ enum DiffAction { Match, Insert, Delete } var editsSegment = editsBuffer.ToSegment(editsBufferStartLength, editsBuffer.Count); var result = new RenderTreeDiff(componentId, editsSegment); + + // Named event handlers name must be unique globally and stable over the time period we are deciding where to + // dispatch a given named event. + // Once a component has defined a named event handler with a concrete name, no other component instance can + // define a named event handler with that name. + // + // This is only enforced when we are trying to dispatch an event to a named event handler, since in any other + // case we don't actually track the named event handlers. + // + // We don't enforce the global uniqueness of the event handler name inside the base renderer either. That's + // handled by the EndpointRenderer that takes care of ensuring that only one component defined the expected + // named event handler before dispatching the named event. + // + // At this stage, we only ensure that the named event handler is unique per component instance, as that, + // combined with the check that the EndpointRenderer does, is enough to ensure the uniqueness and the stability + // of the named event handler over time **globally**. + // + // Note that we only enforce this condition at the time we are going to dispatch a named event to a specific + // named event handler. In any other case, even though is an error, we don't care, as: + // 1) We don't want to break the user's app if we don't have to. + // 2) We don't have to pay the cost of continously tracking all events all the time to throw. + // That's why raising the error is delayed until we are forced to make a decission. + if (namedEventIndexes != null) + { + foreach (var (name, index) in namedEventIndexes) + { + ref var frame = ref newTree.Array[index]; + renderer.TrackNamedEventId(frame.AttributeEventHandlerId, componentId, name); + } + } + return result; } diff --git a/src/Components/Components/src/RenderTree/Renderer.cs b/src/Components/Components/src/RenderTree/Renderer.cs index bf883d227976..015c10b15724 100644 --- a/src/Components/Components/src/RenderTree/Renderer.cs +++ b/src/Components/Components/src/RenderTree/Renderer.cs @@ -56,7 +56,7 @@ public abstract partial class Renderer : IDisposable, IAsyncDisposable { Dispatcher.UnhandledException -= value; } - } + } /// /// Constructs an instance of . @@ -564,6 +564,22 @@ internal void TrackReplacedEventHandlerId(ulong oldEventHandlerId, ulong newEven _eventHandlerIdReplacements.Add(oldEventHandlerId, newEventHandlerId); } + /// + /// Tracks named events defined during rendering. + /// + /// The event handler ID associated with the named event. + /// The component ID defining the name. + /// The event name. + protected internal virtual void TrackNamedEventId(ulong eventHandlerId, int componentId, string eventHandlerName) + { + } + + /// + /// Indicates whether named event handlers should be tracked. + /// + /// true if named event handlers should be tracked; false otherwise. + protected internal virtual bool ShouldTrackNamedEventHandlers() => false; + private EventCallback GetRequiredEventCallback(ulong eventHandlerId) { if (!_eventBindings.TryGetValue(eventHandlerId, out var callback)) diff --git a/src/Components/Components/src/Rendering/ComponentState.cs b/src/Components/Components/src/Rendering/ComponentState.cs index d0260f0d2374..8252c36a1a6d 100644 --- a/src/Components/Components/src/Rendering/ComponentState.cs +++ b/src/Components/Components/src/Rendering/ComponentState.cs @@ -76,6 +76,7 @@ internal void RenderIntoBatch(RenderBatchBuilder batchBuilder, RenderFragment re } _nextRenderTree.Clear(); + _nextRenderTree.TrackNamedEventHandlers = _renderer.ShouldTrackNamedEventHandlers(); try { @@ -101,7 +102,8 @@ internal void RenderIntoBatch(RenderBatchBuilder batchBuilder, RenderFragment re batchBuilder, ComponentId, _nextRenderTree.GetFrames(), - CurrentRenderTree.GetFrames()); + CurrentRenderTree.GetFrames(), + CurrentRenderTree.GetNamedEvents()); batchBuilder.UpdatedComponentDiffs.Append(diff); batchBuilder.InvalidateParameterViews(); } diff --git a/src/Components/Components/src/Rendering/RenderTreeBuilder.cs b/src/Components/Components/src/Rendering/RenderTreeBuilder.cs index ae97374c6c36..874a32a99a39 100644 --- a/src/Components/Components/src/Rendering/RenderTreeBuilder.cs +++ b/src/Components/Components/src/Rendering/RenderTreeBuilder.cs @@ -27,6 +27,10 @@ public sealed class RenderTreeBuilder : IDisposable private RenderTreeFrameType? _lastNonAttributeFrameType; private bool _hasSeenAddMultipleAttributes; private Dictionary? _seenAttributeNames; + private Dictionary? _seenEventHandlerNames; + + // Configure the render tree builder to capture the event handler names. + internal bool TrackNamedEventHandlers { get; set; } /// /// The reserved parameter name used for supplying child content. @@ -482,6 +486,42 @@ public void SetUpdatesAttributeName(string updatesAttributeName) prevFrame.AttributeEventUpdatesAttributeNameField = updatesAttributeName; } + /// + /// + /// Indicates that the preceding attribute represents a named event handler + /// with the given . + /// + /// + /// This information is used by the rendering system to support dispatching + /// external events by name. + /// + /// + /// The name associated with this event handler. + public void SetEventHandlerName(string eventHandlerName) + { + if (!TrackNamedEventHandlers) + { + return; + } + + if (_entries.Count == 0) + { + throw new InvalidOperationException("No preceding attribute frame exists."); + } + + ref var prevFrame = ref _entries.Buffer[_entries.Count - 1]; + if (prevFrame.FrameTypeField != RenderTreeFrameType.Attribute && !(prevFrame.AttributeValue is MulticastDelegate or IEventCallback)) + { + throw new InvalidOperationException($"The previous attribute is not an event handler."); + } + + _seenEventHandlerNames ??= new(); + if (!_seenEventHandlerNames.TryAdd(eventHandlerName, _entries.Count - 1)) + { + throw new InvalidOperationException($"An event handler '{eventHandlerName}' is already defined in this component."); + } + } + /// /// Appends a frame representing a child component. /// @@ -690,6 +730,8 @@ public void Clear() _lastNonAttributeFrameType = null; _hasSeenAddMultipleAttributes = false; _seenAttributeNames?.Clear(); + _seenEventHandlerNames?.Clear(); + TrackNamedEventHandlers = false; } // internal because this should only be used during the post-event tree patching logic @@ -826,4 +868,9 @@ public void Dispose() { _entries.Dispose(); } + + internal Dictionary? GetNamedEvents() + { + return _seenEventHandlerNames; + } } diff --git a/src/Components/Components/test/RenderTreeDiffBuilderTest.cs b/src/Components/Components/test/RenderTreeDiffBuilderTest.cs index 917365b1afb1..05d76ffff267 100644 --- a/src/Components/Components/test/RenderTreeDiffBuilderTest.cs +++ b/src/Components/Components/test/RenderTreeDiffBuilderTest.cs @@ -816,7 +816,7 @@ public void RecognizesComponentTypeChangesAtSameSequenceNumber() using var batchBuilder = new RenderBatchBuilder(); // Act - var diff = RenderTreeDiffBuilder.ComputeDiff(renderer, batchBuilder, 0, oldTree.GetFrames(), newTree.GetFrames()); + var diff = RenderTreeDiffBuilder.ComputeDiff(renderer, batchBuilder, 0, newTree.GetFrames(), oldTree.GetFrames(), oldTree.GetNamedEvents()); // Assert: We're going to dispose the old component and render the new one Assert.Equal(new[] { 0 }, batchBuilder.ComponentDisposalQueue); @@ -1627,7 +1627,7 @@ public void RetainsChildComponentsForExistingFrames() using var batchBuilder = new RenderBatchBuilder(); using var renderTreeBuilder = new RenderTreeBuilder(); - RenderTreeDiffBuilder.ComputeDiff(renderer, batchBuilder, 0, renderTreeBuilder.GetFrames(), oldTree.GetFrames()); + RenderTreeDiffBuilder.ComputeDiff(renderer, batchBuilder, 0, oldTree.GetFrames(), renderTreeBuilder.GetFrames(), renderTreeBuilder.GetNamedEvents()); var originalFakeComponentInstance = oldTree.GetFrames().Array[2].Component; var originalFakeComponent2Instance = oldTree.GetFrames().Array[3].Component; @@ -1713,7 +1713,7 @@ public void SetsUpdatedParametersOnChildComponents() using var batchBuilder = new RenderBatchBuilder(); using var renderTree = new RenderTreeBuilder(); - RenderTreeDiffBuilder.ComputeDiff(renderer, batchBuilder, 0, renderTree.GetFrames(), oldTree.GetFrames()); + RenderTreeDiffBuilder.ComputeDiff(renderer, batchBuilder, 0, oldTree.GetFrames(), renderTree.GetFrames(), renderTree.GetNamedEvents()); var originalComponentInstance = (FakeComponent)oldTree.GetFrames().Array[0].Component; // Act @@ -1763,7 +1763,7 @@ public void SkipsUpdatingParametersOnChildComponentsIfAllAreDefinitelyImmutableA using var batchBuilder = new RenderBatchBuilder(); using var renderTreeBuilder = new RenderTreeBuilder(); - RenderTreeDiffBuilder.ComputeDiff(renderer, batchBuilder, 0, renderTreeBuilder.GetFrames(), oldTree.GetFrames()); + RenderTreeDiffBuilder.ComputeDiff(renderer, batchBuilder, 0, oldTree.GetFrames(), renderTreeBuilder.GetFrames(), renderTreeBuilder.GetNamedEvents()); var originalComponentInstance = (CaptureSetParametersComponent)oldTree.GetFrames().Array[0].Component; Assert.Equal(1, originalComponentInstance.SetParametersCallCount); @@ -1793,7 +1793,7 @@ public void AlwaysRegardsRenderFragmentAsPossiblyChanged() using var batchBuilder = new RenderBatchBuilder(); using var renderTreeBuilder = new RenderTreeBuilder(); - RenderTreeDiffBuilder.ComputeDiff(renderer, batchBuilder, 0, renderTreeBuilder.GetFrames(), oldTree.GetFrames()); + RenderTreeDiffBuilder.ComputeDiff(renderer, batchBuilder, 0, oldTree.GetFrames(), renderTreeBuilder.GetFrames(), renderTreeBuilder.GetNamedEvents()); var componentInstance = (CaptureSetParametersComponent)oldTree.GetFrames().Array[0].Component; Assert.Equal(1, componentInstance.SetParametersCallCount); @@ -1819,13 +1819,13 @@ public void QueuesRemovedChildComponentsForDisposal() using var batchBuilder = new RenderBatchBuilder(); using var renderTree = new RenderTreeBuilder(); - RenderTreeDiffBuilder.ComputeDiff(renderer, batchBuilder, 0, renderTree.GetFrames(), oldTree.GetFrames()); + RenderTreeDiffBuilder.ComputeDiff(renderer, batchBuilder, 0, oldTree.GetFrames(), renderTree.GetFrames(), renderTree.GetNamedEvents()); // Act/Assert // Note that we track NonDisposableComponent was disposed even though it's not IDisposable, // because it's up to the upstream renderer to decide what "disposing" a component means Assert.Empty(batchBuilder.ComponentDisposalQueue); - RenderTreeDiffBuilder.ComputeDiff(renderer, batchBuilder, 0, oldTree.GetFrames(), newTree.GetFrames()); + RenderTreeDiffBuilder.ComputeDiff(renderer, batchBuilder, 0, newTree.GetFrames(), oldTree.GetFrames(), oldTree.GetNamedEvents()); Assert.Equal(new[] { 0, 1 }, batchBuilder.ComponentDisposalQueue); } @@ -2238,14 +2238,14 @@ private RenderBatch GetRenderedBatch(RenderTreeBuilder from, RenderTreeBuilder t var emptyFrames = renderTreeBuilder.GetFrames(); var oldFrames = from.GetFrames(); - RenderTreeDiffBuilder.ComputeDiff(renderer, initializeBatchBuilder, 0, emptyFrames, oldFrames); + RenderTreeDiffBuilder.ComputeDiff(renderer, initializeBatchBuilder, 0, oldFrames, emptyFrames, null); } batchBuilder?.Dispose(); // This gets disposed as part of the test type's Dispose batchBuilder = new RenderBatchBuilder(); - var diff = RenderTreeDiffBuilder.ComputeDiff(renderer, batchBuilder, 0, from.GetFrames(), to.GetFrames()); + var diff = RenderTreeDiffBuilder.ComputeDiff(renderer, batchBuilder, 0, to.GetFrames(), from.GetFrames(), from.GetNamedEvents()); batchBuilder.UpdatedComponentDiffs.Append(diff); return batchBuilder.ToBatch(); } diff --git a/src/Components/Components/test/RendererTest.cs b/src/Components/Components/test/RendererTest.cs index 8f4ac760dc68..92ffaeae8b6e 100644 --- a/src/Components/Components/test/RendererTest.cs +++ b/src/Components/Components/test/RendererTest.cs @@ -1333,8 +1333,9 @@ public async Task DispatchEventAsync_Delegate_SynchronousCompletion() { // Arrange var renderer = new TestRenderer(); - var parentComponent = new OuterEventComponent(); - parentComponent.RenderFragment = (builder) => + var parentComponent = new OuterEventComponent + { + RenderFragment = (builder) => { builder.OpenComponent(0); builder.AddComponentParameter(1, nameof(EventComponent.OnClickAction), (Action)(() => @@ -1342,6 +1343,7 @@ public async Task DispatchEventAsync_Delegate_SynchronousCompletion() // Do nothing. })); builder.CloseComponent(); + } }; var parentComponentId = renderer.AssignRootComponentId(parentComponent); @@ -1432,8 +1434,9 @@ public async Task DispatchEventAsync_Delegate_SynchronousCancellation() { // Arrange var renderer = new TestRenderer(); - var parentComponent = new OuterEventComponent(); - parentComponent.RenderFragment = (builder) => + var parentComponent = new OuterEventComponent + { + RenderFragment = (builder) => { builder.OpenComponent(0); builder.AddComponentParameter(1, nameof(EventComponent.OnClickAction), (Action)(() => @@ -1441,6 +1444,7 @@ public async Task DispatchEventAsync_Delegate_SynchronousCancellation() throw new OperationCanceledException(); })); builder.CloseComponent(); + } }; var parentComponentId = renderer.AssignRootComponentId(parentComponent); @@ -1532,8 +1536,9 @@ public async Task DispatchEventAsync_Delegate_SynchronousException() { // Arrange var renderer = new TestRenderer(); - var parentComponent = new OuterEventComponent(); - parentComponent.RenderFragment = (builder) => + var parentComponent = new OuterEventComponent + { + RenderFragment = (builder) => { builder.OpenComponent(0); builder.AddComponentParameter(1, nameof(EventComponent.OnClickAction), (Action)(() => @@ -1541,6 +1546,7 @@ public async Task DispatchEventAsync_Delegate_SynchronousException() throw new InvalidTimeZoneException(); })); builder.CloseComponent(); + } }; var parentComponentId = renderer.AssignRootComponentId(parentComponent); @@ -1634,8 +1640,9 @@ public async Task DispatchEventAsync_Delegate_AsynchronousCompletion() var tcs = new TaskCompletionSource(); var renderer = new TestRenderer(); - var parentComponent = new OuterEventComponent(); - parentComponent.RenderFragment = (builder) => + var parentComponent = new OuterEventComponent + { + RenderFragment = (builder) => { builder.OpenComponent(0); builder.AddComponentParameter(1, nameof(EventComponent.OnClickAsyncAction), (Func)(async () => @@ -1643,6 +1650,7 @@ public async Task DispatchEventAsync_Delegate_AsynchronousCompletion() await tcs.Task; })); builder.CloseComponent(); + } }; var parentComponentId = renderer.AssignRootComponentId(parentComponent); @@ -1743,8 +1751,9 @@ public async Task DispatchEventAsync_Delegate_AsynchronousCancellation() var tcs = new TaskCompletionSource(); var renderer = new TestRenderer(); - var parentComponent = new OuterEventComponent(); - parentComponent.RenderFragment = (builder) => + var parentComponent = new OuterEventComponent + { + RenderFragment = (builder) => { builder.OpenComponent(0); builder.AddComponentParameter(1, nameof(EventComponent.OnClickAsyncAction), (Func)(async () => @@ -1753,6 +1762,7 @@ public async Task DispatchEventAsync_Delegate_AsynchronousCancellation() throw new TaskCanceledException(); })); builder.CloseComponent(); + } }; var parentComponentId = renderer.AssignRootComponentId(parentComponent); @@ -1861,8 +1871,9 @@ public async Task DispatchEventAsync_Delegate_AsynchronousException() var tcs = new TaskCompletionSource(); var renderer = new TestRenderer(); - var parentComponent = new OuterEventComponent(); - parentComponent.RenderFragment = (builder) => + var parentComponent = new OuterEventComponent + { + RenderFragment = (builder) => { builder.OpenComponent(0); builder.AddComponentParameter(1, nameof(EventComponent.OnClickAsyncAction), (Func)(async () => @@ -1871,6 +1882,7 @@ public async Task DispatchEventAsync_Delegate_AsynchronousException() throw new InvalidTimeZoneException(); })); builder.CloseComponent(); + } }; var parentComponentId = renderer.AssignRootComponentId(parentComponent); @@ -2365,8 +2377,11 @@ public void RenderBatch_CanDisposeAsynchronousAsyncDisposables() { // Arrange var semaphore = new Semaphore(0, 1); - var renderer = new TestRenderer { ShouldHandleExceptions = true }; - renderer.OnExceptionHandled = () => semaphore.Release(); + var renderer = new TestRenderer + { + ShouldHandleExceptions = true, + OnExceptionHandled = () => semaphore.Release() + }; var exception1 = new InvalidOperationException(); var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); @@ -2407,8 +2422,11 @@ public void RenderBatch_HandlesAsynchronousExceptionsInAsyncDisposableComponents { // Arrange var semaphore = new Semaphore(0, 1); - var renderer = new TestRenderer { ShouldHandleExceptions = true }; - renderer.OnExceptionHandled = () => semaphore.Release(); + var renderer = new TestRenderer + { + ShouldHandleExceptions = true, + OnExceptionHandled = () => semaphore.Release() + }; var exception1 = new InvalidOperationException(); var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); @@ -2855,7 +2873,7 @@ public void ComponentCannotTriggerRenderBeforeRenderHandleAssigned() var component = new TestComponent(builder => { }); // Act/Assert - var ex = Assert.Throws(() => component.TriggerRender()); + var ex = Assert.Throws(component.TriggerRender); Assert.Equal("The render handle is not yet assigned.", ex.Message); } @@ -3272,7 +3290,7 @@ public void CallsAfterRenderAfterTheUIHasFinishedUpdatingAsynchronously() var onAfterRenderCallCountLog = new List(); var component = new AsyncAfterRenderComponent(afterRenderTcs.Task) { - OnAfterRenderComplete = () => @event.Set(), + OnAfterRenderComplete = @event.Set, }; var renderer = new AsyncUpdateTestRenderer() { @@ -3301,7 +3319,7 @@ public void CallsAfterRenderAfterTheUIHasFinishedUpdatingSynchronously() var onAfterRenderCallCountLog = new List(); var component = new AsyncAfterRenderComponent(afterRenderTcs.Task) { - OnAfterRenderComplete = () => @event.Set(), + OnAfterRenderComplete = @event.Set, }; var renderer = new AsyncUpdateTestRenderer() { @@ -3639,7 +3657,7 @@ public async Task ExceptionsThrownAsynchronouslyAfterFirstRenderCanBeHandled() var renderer = new TestRenderer() { ShouldHandleExceptions = true, - OnExceptionHandled = () => { @event.Set(); }, + OnExceptionHandled = @event.Set, }; var taskToAwait = Task.CompletedTask; var component = new TestComponent(builder => @@ -4357,14 +4375,7 @@ public void CannotStartOverlappingBatches() { // Arrange var renderer = new InvalidRecursiveRenderer(); - var component = new CallbackOnRenderComponent(() => - { - // The renderer disallows one batch to be started inside another, because that - // would violate all kinds of state tracking invariants. It's not something that - // would ever happen except if you subclass the renderer and do something unsupported - // that commences batches from inside each other. - renderer.ProcessPendingRender(); - }); + var component = new CallbackOnRenderComponent(renderer.ProcessPendingRender); var componentId = renderer.AssignRootComponentId(component); // Act/Assert @@ -4399,7 +4410,7 @@ public void CannotAccessParameterViewAfterSynchronousReturn() Assert.Throws(() => parameterView.GetEnumerator()); Assert.Throws(() => parameterView.GetValueOrDefault("anything")); Assert.Throws(() => parameterView.SetParameterProperties(new object())); - Assert.Throws(() => parameterView.ToDictionary()); + Assert.Throws(parameterView.ToDictionary); var ex = Assert.Throws(() => parameterView.TryGetValue("anything", out _)); // It's enough to assert about one of the messages @@ -4834,8 +4845,11 @@ public void RemoveRootComponentHandlesDisposalExceptions() { // Arrange var autoResetEvent = new AutoResetEvent(false); - var renderer = new TestRenderer { ShouldHandleExceptions = true }; - renderer.OnExceptionHandled = () => autoResetEvent.Set(); + var renderer = new TestRenderer + { + ShouldHandleExceptions = true, + OnExceptionHandled = () => autoResetEvent.Set() + }; var exception1 = new InvalidTimeZoneException(); var exception2Tcs = new TaskCompletionSource(); var rootComponent = new TestComponent(builder => @@ -4965,6 +4979,244 @@ public async Task DisposingRenderer_UnsubsribesFromHotReloadManager() Assert.False(hotReloadManager.IsSubscribedTo); } + [Fact] + public void DoesNotTrackNamedEventHandlersWhenNotEnabled() + { + // Arrange + var renderer = new TestRenderer(); + var namedEvents = new List<(ulong eventHandlerId, int componentId, string eventHandlerName)>(); + renderer.OnNamedEvent = namedEvents.Add; + + var component = new TestComponent(builder => + { + builder.OpenElement(0, "form"); + builder.AddAttribute(1, "onsubmit", () => { }); + builder.SetEventHandlerName("MyFormSubmit"); + builder.CloseElement(); + }); + + // Act + var componentId = renderer.AssignRootComponentId(component); + component.TriggerRender(); + + // Assert + var batch = renderer.Batches.Single(); + var diff = batch.DiffsByComponentId[componentId].Single(); + Assert.Collection(diff.Edits, + edit => + { + Assert.Equal(RenderTreeEditType.PrependFrame, edit.Type); + Assert.Equal(0, edit.ReferenceFrameIndex); + }); + AssertFrame.Element(batch.ReferenceFrames[0], "form", 2); + AssertFrame.Attribute(batch.ReferenceFrames[1], "onsubmit"); + + Assert.Empty(namedEvents); + } + + [Fact] + public void CanCreateNamedEventHandlers() + { + // Arrange + var renderer = new TestRenderer + { + TrackNamedEventHandlers = true + }; + var namedEvents = new List<(ulong eventHandlerId, int componentId, string eventHandlerName)>(); + renderer.OnNamedEvent = namedEvents.Add; + + var component = new TestComponent(builder => + { + builder.OpenElement(0, "form"); + builder.AddAttribute(1, "onsubmit", () => { }); + builder.SetEventHandlerName("MyFormSubmit"); + builder.CloseElement(); + }); + + // Act + var componentId = renderer.AssignRootComponentId(component); + component.TriggerRender(); + + // Assert + var batch = renderer.Batches.Single(); + var diff = batch.DiffsByComponentId[componentId].Single(); + var evt = Assert.Single(namedEvents); + Assert.Collection(diff.Edits, + edit => + { + Assert.Equal(RenderTreeEditType.PrependFrame, edit.Type); + Assert.Equal(0, edit.ReferenceFrameIndex); + }); + AssertFrame.Element(batch.ReferenceFrames[0], "form", 2); + AssertFrame.Attribute(batch.ReferenceFrames[1], "onsubmit"); + Assert.Equal(batch.ReferenceFrames[1].AttributeEventHandlerId, evt.eventHandlerId); + Assert.Equal("MyFormSubmit", evt.eventHandlerName); + Assert.Equal(componentId, evt.componentId); + } + + [Fact] + public void CanCreateMultipleNamedEventHandlersPerComponent() + { + // Arrange + var renderer = new TestRenderer + { + TrackNamedEventHandlers = true + }; + var namedEvents = new List<(ulong eventHandlerId, int componentId, string eventHandlerName)>(); + renderer.OnNamedEvent = namedEvents.Add; + + var component = new TestComponent(builder => + { + builder.OpenElement(0, "form"); + builder.AddAttribute(1, "onsubmit", () => { }); + builder.SetEventHandlerName("MyFormSubmit"); + builder.CloseElement(); + builder.OpenElement(2, "form"); + builder.AddAttribute(3, "onsubmit", () => { }); + builder.SetEventHandlerName("MyOtherFormSubmit"); + builder.CloseElement(); + }); + + // Act + var componentId = renderer.AssignRootComponentId(component); + component.TriggerRender(); + + // Assert + var batch = renderer.Batches.Single(); + var diff = batch.DiffsByComponentId[componentId].Single(); + Assert.Equal(2, namedEvents.Count); + Assert.Collection(diff.Edits, + edit => + { + Assert.Equal(RenderTreeEditType.PrependFrame, edit.Type); + Assert.Equal(0, edit.ReferenceFrameIndex); + }, + edit => + { + Assert.Equal(RenderTreeEditType.PrependFrame, edit.Type); + Assert.Equal(2, edit.ReferenceFrameIndex); + }); + + AssertFrame.Element(batch.ReferenceFrames[0], "form", 2); + AssertFrame.Attribute(batch.ReferenceFrames[1], "onsubmit"); + Assert.Equal(batch.ReferenceFrames[1].AttributeEventHandlerId, namedEvents[0].eventHandlerId); + Assert.Equal("MyFormSubmit", namedEvents[0].eventHandlerName); + Assert.Equal(componentId, namedEvents[0].componentId); + + AssertFrame.Element(batch.ReferenceFrames[2], "form", 2); + AssertFrame.Attribute(batch.ReferenceFrames[3], "onsubmit"); + Assert.Equal(batch.ReferenceFrames[3].AttributeEventHandlerId, namedEvents[1].eventHandlerId); + Assert.Equal("MyOtherFormSubmit", namedEvents[1].eventHandlerName); + Assert.Equal(componentId, namedEvents[1].componentId); + } + + [Fact] + public void CanCreateMultipleNamedEventHandlersPerElement() + { + // Arrange + var renderer = new TestRenderer + { + TrackNamedEventHandlers = true + }; + var namedEvents = new List<(ulong eventHandlerId, int componentId, string eventHandlerName)>(); + renderer.OnNamedEvent = namedEvents.Add; + + var component = new TestComponent(builder => + { + builder.OpenElement(0, "form"); + builder.AddAttribute(1, "onsubmit", () => { }); + builder.SetEventHandlerName("MyFormSubmit"); + builder.AddAttribute(2, "onclick", () => { }); + builder.SetEventHandlerName("MyFormClick"); + builder.CloseElement(); + }); + + // Act + var componentId = renderer.AssignRootComponentId(component); + component.TriggerRender(); + + // Assert + var batch = renderer.Batches.Single(); + var diff = batch.DiffsByComponentId[componentId].Single(); + Assert.Equal(2, namedEvents.Count); + Assert.Collection(diff.Edits, + edit => + { + Assert.Equal(RenderTreeEditType.PrependFrame, edit.Type); + Assert.Equal(0, edit.ReferenceFrameIndex); + }); + AssertFrame.Element(batch.ReferenceFrames[0], "form", 3); + AssertFrame.Attribute(batch.ReferenceFrames[1], "onsubmit"); + AssertFrame.Attribute(batch.ReferenceFrames[2], "onclick"); + + Assert.Equal(batch.ReferenceFrames[1].AttributeEventHandlerId, namedEvents[0].eventHandlerId); + Assert.Equal("MyFormSubmit", namedEvents[0].eventHandlerName); + Assert.Equal(componentId, namedEvents[0].componentId); + + Assert.Equal(batch.ReferenceFrames[2].AttributeEventHandlerId, namedEvents[1].eventHandlerId); + Assert.Equal("MyFormClick", namedEvents[1].eventHandlerName); + Assert.Equal(componentId, namedEvents[1].componentId); + } + + [Fact] + public void DuplicateNamedEventHandlersOnComponentThrows() + { + // Arrange + var renderer = new TestRenderer + { + TrackNamedEventHandlers = true + }; + var namedEvents = new List<(ulong eventHandlerId, int componentId, string eventHandlerName)>(); + renderer.OnNamedEvent = namedEvents.Add; + + var component = new TestComponent(builder => + { + builder.OpenElement(0, "form"); + builder.AddAttribute(1, "onsubmit", () => { }); + builder.SetEventHandlerName("MyFormSubmit"); + builder.CloseElement(); + builder.OpenElement(2, "form"); + builder.AddAttribute(3, "onsubmit", () => { }); + builder.SetEventHandlerName("MyFormSubmit"); + builder.CloseElement(); + }); + + // Act + var componentId = renderer.AssignRootComponentId(component); + + var exception = Assert.Throws(component.TriggerRender); + Assert.Equal("An event handler 'MyFormSubmit' is already defined in this component.", exception.Message); + } + + [Fact] + public void DuplicateNamedEventHandlersOnElementThrows() + { + // Arrange + var renderer = new TestRenderer + { + TrackNamedEventHandlers = true + }; + var namedEvents = new List<(ulong eventHandlerId, int componentId, string eventHandlerName)>(); + renderer.OnNamedEvent = namedEvents.Add; + + var component = new TestComponent(builder => + { + builder.OpenElement(0, "form"); + builder.AddAttribute(1, "onsubmit", () => { }); + builder.SetEventHandlerName("MyFormSubmit"); + builder.AddAttribute(2, "onclick", () => { }); + builder.SetEventHandlerName("MyFormSubmit"); + builder.CloseElement(); + }); + + // Act + var componentId = renderer.AssignRootComponentId(component); + + // Assert + var exception = Assert.Throws(component.TriggerRender); + Assert.Equal("An event handler 'MyFormSubmit' is already defined in this component.", exception.Message); + } + private class TestComponentActivator : IComponentActivator where TResult : IComponent, new() { public List RequestedComponentTypes { get; } = new List(); diff --git a/src/Components/Endpoints/src/RazorComponentEndpointInvoker.cs b/src/Components/Endpoints/src/RazorComponentEndpointInvoker.cs index d21342e96fce..a472aa7c22f9 100644 --- a/src/Components/Endpoints/src/RazorComponentEndpointInvoker.cs +++ b/src/Components/Endpoints/src/RazorComponentEndpointInvoker.cs @@ -2,10 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Buffers; -using System.Net.Http; using System.Text; using System.Text.Encodings.Web; -using Microsoft.AspNetCore.Components.RenderTree; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.WebUtilities; using Microsoft.Extensions.DependencyInjection; diff --git a/src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.cs b/src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.cs index d7f13cb73295..b0d740b75c9f 100644 --- a/src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.cs +++ b/src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Text; using Microsoft.AspNetCore.Components.Authorization; using Microsoft.AspNetCore.Components.HtmlRendering.Infrastructure; using Microsoft.AspNetCore.Components.Infrastructure; @@ -32,6 +33,7 @@ internal sealed partial class EndpointHtmlRenderer : StaticHtmlRenderer, ICompon private Task? _servicesInitializedTask; private string? _formHandler; + private NamedEvent _capturedNamedEvent; // The underlying Renderer always tracks the pending tasks representing *full* quiescence, i.e., // when everything (regardless of streaming SSR) is fully complete. In this subclass we also track @@ -68,6 +70,73 @@ internal void SetFormHandlerName(string name) _formHandler = name; } + protected override void TrackNamedEventId(ulong eventHandlerId, int componentId, string eventNameId) + { + if (_formHandler == null || !string.Equals(eventNameId, _formHandler, StringComparison.Ordinal)) + { + // We only track event names when we are deciding how to dispatch an event and when the event + // matches the identifier for the event we are trying to dispatch. + return; + } + + if (_capturedNamedEvent.EventNameId == null) + { + // This is the first time we see the event being tracked, we capture it. + _capturedNamedEvent = new(eventHandlerId, componentId, eventNameId); + return; + } + + if (_capturedNamedEvent.ComponentId != componentId) + { + // At this point we have already seen this event once. Once a component instance defines a named + // event, that component is the owner of that event name until we dispatch the event. + // Dispatching the event happens after we've achieved quiesce. + // * No two separate components can define an event with the same name identifier. + // * No other component can define the same named event even if the existing registration + // is no longer part of the set of rendered components. + // * This gives customers a clear an easy rule about how forms need to be rendered when you + // want to support handling POST requests. + // * Note that this only affects receiving POST request, it does not impact interactive components + // and it does not impact prerendering components. + try + { + // Two components are trying to simultaneously define the same name. + var state = GetComponentState(_capturedNamedEvent.ComponentId); + throw new InvalidOperationException( + $@"Two different components are trying to define the same named event '{eventNameId}': +'{GenerateComponentPath(state)}' +'{GenerateComponentPath(GetComponentState(componentId))}'"""); + } + catch (ArgumentException) + { + // The component that originally defined the name was disposed. + throw new InvalidOperationException( + $"The named event '{eventNameId}' was already defined earlier by a component with id '{_capturedNamedEvent.ComponentId}' but this" + + $"component was removed before receiving the dispatched event."); + } + } + } + + private static string GenerateComponentPath(ComponentState state) + { + // We are generating a path from the root component with te component type names like: + // App > Router > RouteView > LayoutView > Index > PartA + // App > Router > RouteView > LayoutView > MainLayout > NavigationMenu + // To help developers identify when they have multiple forms with the same handler. + Stack stack = new(); + + for (var current = state; current != null; current = current.ParentComponentState) + { + stack.Push(GetName(current)); + } + + var builder = new StringBuilder(); + builder.AppendJoin(" > ", stack); + return builder.ToString(); + + static string GetName(ComponentState current) => current.Component.GetType().Name; + } + protected override ComponentState CreateComponentState(int componentId, IComponent component, ComponentState? parentComponentState) => new EndpointComponentState(this, componentId, component, parentComponentState); @@ -128,4 +197,6 @@ private static string GetContextBaseUri(HttpRequest request) // it has to end with a trailing slash return result.EndsWith('/') ? result : result += "/"; } + + private record struct NamedEvent(ulong EventHandlerId, int ComponentId, string EventNameId); } diff --git a/src/Components/Shared/test/TestRenderer.cs b/src/Components/Shared/test/TestRenderer.cs index e790b6c4ee98..1cc98044ca7f 100644 --- a/src/Components/Shared/test/TestRenderer.cs +++ b/src/Components/Shared/test/TestRenderer.cs @@ -29,8 +29,22 @@ public TestRenderer(IServiceProvider serviceProvider, IComponentActivator compon Dispatcher = Dispatcher.CreateDefault(); } + protected internal override void TrackNamedEventId(ulong eventHandlerId, int componentId, string eventName) + { + OnNamedEvent?.Invoke((eventHandlerId, componentId, eventName)); + } + + protected internal override bool ShouldTrackNamedEventHandlers() + { + return TrackNamedEventHandlers; + } + public override Dispatcher Dispatcher { get; } + public bool TrackNamedEventHandlers { get; set; } + + public Action<(ulong eventHandlerId, int componentId, string eventName)> OnNamedEvent { get; set; } + public Action OnExceptionHandled { get; set; } public Action OnUpdateDisplay { get; set; } From 0c9bb3f000becde89044b0134a4da8749530c45c Mon Sep 17 00:00:00 2001 From: jacalvar Date: Thu, 13 Apr 2023 20:06:04 +0200 Subject: [PATCH 05/28] Add tests for tracking named event handlers in EndpointHtmlRenderer --- .../EndpointHtmlRenderer.Prerendering.cs | 2 +- .../EndpointHtmlRenderer.PrerenderingState.cs | 2 +- .../src/Rendering/EndpointHtmlRenderer.cs | 8 +- .../test/EndpointHtmlRendererTest.cs | 158 +++++++++++++++++- ...pNetCore.Components.Endpoints.Tests.csproj | 4 + 5 files changed, 166 insertions(+), 8 deletions(-) diff --git a/src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.Prerendering.cs b/src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.Prerendering.cs index f57b63d62e80..4abc88c817a3 100644 --- a/src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.Prerendering.cs +++ b/src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.Prerendering.cs @@ -9,7 +9,7 @@ namespace Microsoft.AspNetCore.Components.Endpoints; -internal sealed partial class EndpointHtmlRenderer +internal partial class EndpointHtmlRenderer { private static readonly object ComponentSequenceKey = new object(); diff --git a/src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.PrerenderingState.cs b/src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.PrerenderingState.cs index 49935da70371..b406665b2c6b 100644 --- a/src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.PrerenderingState.cs +++ b/src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.PrerenderingState.cs @@ -10,7 +10,7 @@ namespace Microsoft.AspNetCore.Components.Endpoints; -internal sealed partial class EndpointHtmlRenderer +internal partial class EndpointHtmlRenderer { private static readonly object InvokedRenderModesKey = new object(); diff --git a/src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.cs b/src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.cs index b0d740b75c9f..8b7da19105ef 100644 --- a/src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.cs +++ b/src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.cs @@ -27,7 +27,7 @@ namespace Microsoft.AspNetCore.Components.Endpoints; /// output with prerendering markers so the content can later switch into interactive mode when used with /// blazor.*.js. It also deals with initializing the standard component DI services once per request. /// -internal sealed partial class EndpointHtmlRenderer : StaticHtmlRenderer, IComponentPrerenderer +internal partial class EndpointHtmlRenderer : StaticHtmlRenderer, IComponentPrerenderer { private readonly IServiceProvider _services; private Task? _servicesInitializedTask; @@ -70,6 +70,8 @@ internal void SetFormHandlerName(string name) _formHandler = name; } + protected override bool ShouldTrackNamedEventHandlers() => _formHandler != null; + protected override void TrackNamedEventId(ulong eventHandlerId, int componentId, string eventNameId) { if (_formHandler == null || !string.Equals(eventNameId, _formHandler, StringComparison.Ordinal)) @@ -105,13 +107,13 @@ protected override void TrackNamedEventId(ulong eventHandlerId, int componentId, throw new InvalidOperationException( $@"Two different components are trying to define the same named event '{eventNameId}': '{GenerateComponentPath(state)}' -'{GenerateComponentPath(GetComponentState(componentId))}'"""); +'{GenerateComponentPath(GetComponentState(componentId))}'"); } catch (ArgumentException) { // The component that originally defined the name was disposed. throw new InvalidOperationException( - $"The named event '{eventNameId}' was already defined earlier by a component with id '{_capturedNamedEvent.ComponentId}' but this" + + $"The named event '{eventNameId}' was already defined earlier by a component with id '{_capturedNamedEvent.ComponentId}' but this " + $"component was removed before receiving the dispatched event."); } } diff --git a/src/Components/Endpoints/test/EndpointHtmlRendererTest.cs b/src/Components/Endpoints/test/EndpointHtmlRendererTest.cs index 699501931b1c..c6a3c3093ffc 100644 --- a/src/Components/Endpoints/test/EndpointHtmlRendererTest.cs +++ b/src/Components/Endpoints/test/EndpointHtmlRendererTest.cs @@ -7,6 +7,7 @@ using Microsoft.AspNetCore.Components.Endpoints.Tests.TestComponents; using Microsoft.AspNetCore.Components.Infrastructure; using Microsoft.AspNetCore.Components.Rendering; +using Microsoft.AspNetCore.Components.Test.Helpers; using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.Html; using Microsoft.AspNetCore.Http; @@ -29,7 +30,7 @@ public class EndpointHtmlRendererTest private static readonly IDataProtectionProvider _dataprotectorProvider = new EphemeralDataProtectionProvider(); private readonly IServiceProvider _services = CreateDefaultServiceCollection().BuildServiceProvider(); - private readonly EndpointHtmlRenderer renderer; + private readonly TestEndpointHtmlRenderer renderer; public EndpointHtmlRendererTest() { @@ -810,6 +811,145 @@ public async Task CanRender_AsyncComponent() Assert.Equal("Loaded", content); } + [Fact] + public void DuplicateNamedEventHandlersAcrossComponentsThrows() + { + // Arrange + var expectedError = @"Two different components are trying to define the same named event 'default': +'TestComponent > NamedEventHandlerComponent' +'TestComponent > OtherNamedEventHandlerComponent'"; + + var renderer = GetEndpointHtmlRenderer(); + renderer.SetFormHandlerName("default"); + + var component = new TestComponent(builder => + { + builder.OpenComponent(0); + builder.CloseComponent(); + builder.OpenComponent(1); + builder.CloseComponent(); + }); + + // Act + var componentId = renderer.TestAssignRootComponentId(component); + + var exception = Assert.Throws(component.TriggerRender); + Assert.Equal(expectedError, exception.Message); + } + + [Fact] + public void RecreatedComponent_AcrossDifferentBatches_WithNamedEventHandler_Throws() + { + // Arrange + var expectedError = "The named event 'default' was already defined earlier by a component with id '1' but this " + + "component was removed before receiving the dispatched event."; + + var renderer = GetEndpointHtmlRenderer(); + renderer.SetFormHandlerName("default"); + + RenderFragment addComponentRender = builder => + { + builder.OpenComponent(0); + builder.CloseComponent(); + }; + RenderFragment removeComponentRender = builder => + { + }; + + var currentRender = addComponentRender; + var component = new TestComponent(builder => + { + currentRender(builder); + }); + + // Act + var componentId = renderer.TestAssignRootComponentId(component); + renderer.Dispatcher.InvokeAsync(component.TriggerRender); + currentRender = removeComponentRender; + renderer.Dispatcher.InvokeAsync(component.TriggerRender); + currentRender = addComponentRender; + + var exception = Assert.Throws(component.TriggerRender); + Assert.Equal(expectedError, exception.Message); + } + + [Fact] + public void NamedEventHandlers_DifferentComponents_SameNamedHandlerInDifferentBatches_Throws() + { + // Arrange + var expectedError = "The named event 'default' was already defined earlier by a component with id '1' but this " + + "component was removed before receiving the dispatched event."; + + var renderer = GetEndpointHtmlRenderer(); + renderer.SetFormHandlerName("default"); + + RenderFragment addComponentRender = builder => + { + builder.OpenComponent(0); + builder.CloseComponent(); + }; + + RenderFragment removeComponentRender = builder => + { + }; + RenderFragment thirdRender = builder => + { + builder.OpenComponent(0); + builder.CloseComponent(); + }; + + var currentRender = addComponentRender; + var component = new TestComponent(builder => + { + currentRender(builder); + }); + + // Act + var componentId = renderer.TestAssignRootComponentId(component); + renderer.Dispatcher.InvokeAsync(component.TriggerRender); + currentRender = removeComponentRender; + renderer.Dispatcher.InvokeAsync(component.TriggerRender); + currentRender = thirdRender; + + var exception = Assert.Throws(component.TriggerRender); + Assert.Equal(expectedError, exception.Message); + } + + private class NamedEventHandlerComponent : ComponentBase + { + protected override void BuildRenderTree(RenderTreeBuilder builder) + { + builder.OpenElement(0, "form"); + builder.AddAttribute(1, "onsubmit", () => { }); + builder.SetEventHandlerName("default"); + builder.CloseElement(); + } + } + + private class OtherNamedEventHandlerComponent : ComponentBase + { + protected override void BuildRenderTree(RenderTreeBuilder builder) + { + builder.OpenElement(0, "form"); + builder.AddAttribute(1, "onsubmit", () => { }); + builder.SetEventHandlerName("default"); + builder.CloseElement(); + } + } + + class TestComponent : AutoRenderComponent + { + private readonly RenderFragment _renderFragment; + + public TestComponent(RenderFragment renderFragment) + { + _renderFragment = renderFragment; + } + + protected override void BuildRenderTree(RenderTreeBuilder builder) + => _renderFragment(builder); + } + private static string HtmlContentToString(IHtmlAsyncContent result) { var writer = new StringWriter(); @@ -817,10 +957,22 @@ private static string HtmlContentToString(IHtmlAsyncContent result) return writer.ToString(); } - private EndpointHtmlRenderer GetEndpointHtmlRenderer(IServiceProvider services = null) + private TestEndpointHtmlRenderer GetEndpointHtmlRenderer(IServiceProvider services = null) { var effectiveServices = services ?? _services; - return new EndpointHtmlRenderer(effectiveServices, NullLoggerFactory.Instance); + return new TestEndpointHtmlRenderer(effectiveServices, NullLoggerFactory.Instance); + } + + private class TestEndpointHtmlRenderer : EndpointHtmlRenderer + { + public TestEndpointHtmlRenderer(IServiceProvider serviceProvider, ILoggerFactory loggerFactory) : base(serviceProvider, loggerFactory) + { + } + + internal int TestAssignRootComponentId(IComponent component) + { + return base.AssignRootComponentId(component); + } } private HttpContext GetHttpContext(HttpContext context = null) diff --git a/src/Components/Endpoints/test/Microsoft.AspNetCore.Components.Endpoints.Tests.csproj b/src/Components/Endpoints/test/Microsoft.AspNetCore.Components.Endpoints.Tests.csproj index da95aebe852b..2742b193db4c 100644 --- a/src/Components/Endpoints/test/Microsoft.AspNetCore.Components.Endpoints.Tests.csproj +++ b/src/Components/Endpoints/test/Microsoft.AspNetCore.Components.Endpoints.Tests.csproj @@ -4,6 +4,10 @@ $(DefaultNetCoreTargetFramework) + + + + From 9ab5011800cffbf9b7fe3f60497334f2be87a2a0 Mon Sep 17 00:00:00 2001 From: jacalvar Date: Fri, 14 Apr 2023 12:26:44 +0200 Subject: [PATCH 06/28] Define CascadingModelBinder and ModelBindingContext --- .../src/Binding/CascadingModelBinder.cs | 42 +++++++++++++++++++ .../src/Binding/ModelBindingContext.cs | 9 ++++ 2 files changed, 51 insertions(+) create mode 100644 src/Components/Components/src/Binding/CascadingModelBinder.cs create mode 100644 src/Components/Components/src/Binding/ModelBindingContext.cs diff --git a/src/Components/Components/src/Binding/CascadingModelBinder.cs b/src/Components/Components/src/Binding/CascadingModelBinder.cs new file mode 100644 index 000000000000..029cae24e74c --- /dev/null +++ b/src/Components/Components/src/Binding/CascadingModelBinder.cs @@ -0,0 +1,42 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Reflection.Metadata; + +namespace Microsoft.AspNetCore.Components.Binding; + +internal class CascadingModelBinder : IComponent +{ + private RenderHandle _handle; + private bool _hasRendered; + private ModelBindingContext? _bindingContext; + + [Parameter] public string Name { get; set; } = default!; + + [Parameter] public RenderFragment ChildContent { get; set; } = default!; + + public void Attach(RenderHandle renderHandle) + { + _handle = renderHandle; + } + + public Task SetParametersAsync(ParameterView parameters) + { + if (!_hasRendered) + { + _hasRendered = true; + _bindingContext = new ModelBindingContext(Name); + parameters.SetParameterProperties(this); + _handle.Render(builder => + { + builder.OpenComponent>(0); + builder.AddComponentParameter(1, "IsFixed", true); + builder.AddComponentParameter(2, "Value", _bindingContext); + builder.AddComponentParameter(3, "ChildContent", ChildContent); + builder.CloseComponent(); + }); + } + + return Task.CompletedTask; + } +} diff --git a/src/Components/Components/src/Binding/ModelBindingContext.cs b/src/Components/Components/src/Binding/ModelBindingContext.cs new file mode 100644 index 000000000000..3ad3e65d9cf7 --- /dev/null +++ b/src/Components/Components/src/Binding/ModelBindingContext.cs @@ -0,0 +1,9 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.AspNetCore.Components.Binding; + +public class ModelBindingContext(string name) +{ + public string Name { get; } = name; +} From 44d4e2a692b106fc550d42a5aaacc514963bd519 Mon Sep 17 00:00:00 2001 From: jacalvar Date: Fri, 14 Apr 2023 12:56:45 +0200 Subject: [PATCH 07/28] Add specialized RenderFragment AddComponentParameter overload and remove unnecessary delegate caching from RouteView (the compiler caches the delegate automatically now) --- .../Components/src/PublicAPI.Unshipped.txt | 1 + .../src/Rendering/RenderTreeBuilder.cs | 12 ++++++++++++ src/Components/Components/src/RouteView.cs | 16 ++-------------- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/Components/Components/src/PublicAPI.Unshipped.txt b/src/Components/Components/src/PublicAPI.Unshipped.txt index 529c2873b6c9..6c6a6b135a1e 100644 --- a/src/Components/Components/src/PublicAPI.Unshipped.txt +++ b/src/Components/Components/src/PublicAPI.Unshipped.txt @@ -4,6 +4,7 @@ Microsoft.AspNetCore.Components.Infrastructure.ComponentStatePersistenceManager. Microsoft.AspNetCore.Components.RenderHandle.DispatchExceptionAsync(System.Exception! exception) -> System.Threading.Tasks.Task! *REMOVED*Microsoft.AspNetCore.Components.NavigationManager.ToAbsoluteUri(string! relativeUri) -> System.Uri! Microsoft.AspNetCore.Components.NavigationManager.ToAbsoluteUri(string? relativeUri) -> System.Uri! +Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder.AddComponentParameter(int sequence, string! name, Microsoft.AspNetCore.Components.RenderFragment? value) -> void Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder.SetEventHandlerName(string! eventHandlerName) -> void Microsoft.AspNetCore.Components.Routing.IScrollToLocationHash Microsoft.AspNetCore.Components.Routing.IScrollToLocationHash.RefreshScrollPositionForHash(string! locationAbsolute) -> System.Threading.Tasks.Task! diff --git a/src/Components/Components/src/Rendering/RenderTreeBuilder.cs b/src/Components/Components/src/Rendering/RenderTreeBuilder.cs index 874a32a99a39..11a7335230e5 100644 --- a/src/Components/Components/src/Rendering/RenderTreeBuilder.cs +++ b/src/Components/Components/src/Rendering/RenderTreeBuilder.cs @@ -557,6 +557,18 @@ public void AddComponentParameter(int sequence, string name, object? value) _entries.AppendAttribute(sequence, name, value); } + /// + /// Appends a frame representing a component parameter. + /// + /// An integer that represents the position of the instruction in the source code. + /// The name of the attribute. + /// The value of the attribute. + public void AddComponentParameter(int sequence, string name, RenderFragment? value) + { + AssertCanAddComponentParameter(); + _entries.AppendAttribute(sequence, name, value); + } + /// /// Assigns the specified key value to the current element or component. /// diff --git a/src/Components/Components/src/RouteView.cs b/src/Components/Components/src/RouteView.cs index d5e7df552d4b..d5024564b731 100644 --- a/src/Components/Components/src/RouteView.cs +++ b/src/Components/Components/src/RouteView.cs @@ -16,8 +16,6 @@ namespace Microsoft.AspNetCore.Components; /// public class RouteView : IComponent { - private readonly RenderFragment _renderDelegate; - private readonly RenderFragment _renderPageWithParametersDelegate; private RenderHandle _renderHandle; [Inject] @@ -39,16 +37,6 @@ public class RouteView : IComponent [Parameter] public Type DefaultLayout { get; set; } - /// - /// Initializes a new instance of . - /// - public RouteView() - { - // Cache the delegate instances - _renderDelegate = Render; - _renderPageWithParametersDelegate = RenderPageWithParameters; - } - /// public void Attach(RenderHandle renderHandle) { @@ -65,7 +53,7 @@ public Task SetParametersAsync(ParameterView parameters) throw new InvalidOperationException($"The {nameof(RouteView)} component requires a non-null value for the parameter {nameof(RouteData)}."); } - _renderHandle.Render(_renderDelegate); + _renderHandle.Render(Render); return Task.CompletedTask; } @@ -82,7 +70,7 @@ protected virtual void Render(RenderTreeBuilder builder) builder.OpenComponent(0); builder.AddComponentParameter(1, nameof(LayoutView.Layout), pageLayoutType); - builder.AddComponentParameter(2, nameof(LayoutView.ChildContent), _renderPageWithParametersDelegate); + builder.AddComponentParameter(2, nameof(LayoutView.ChildContent), RenderPageWithParameters); builder.CloseComponent(); } From 9c59e87b1504ce408678bd3ac9fce24c019f9f6e Mon Sep 17 00:00:00 2001 From: jacalvar Date: Fri, 14 Apr 2023 13:21:24 +0200 Subject: [PATCH 08/28] Add CascadingModelBinder to RouteView --- src/Components/Components/src/RouteView.cs | 50 ++++++++++++++-------- 1 file changed, 33 insertions(+), 17 deletions(-) diff --git a/src/Components/Components/src/RouteView.cs b/src/Components/Components/src/RouteView.cs index d5024564b731..f0765a0b8641 100644 --- a/src/Components/Components/src/RouteView.cs +++ b/src/Components/Components/src/RouteView.cs @@ -3,8 +3,10 @@ #nullable disable warnings +using System; using System.Diagnostics.CodeAnalysis; using System.Reflection; +using Microsoft.AspNetCore.Components.Binding; using Microsoft.AspNetCore.Components.Rendering; using Microsoft.AspNetCore.Components.Routing; @@ -76,29 +78,43 @@ protected virtual void Render(RenderTreeBuilder builder) private void RenderPageWithParameters(RenderTreeBuilder builder) { - builder.OpenComponent(0, RouteData.PageType); + var pathStart = NavigationManager.BaseUri.Length; + var name = NavigationManager.Uri.Substring( + pathStart, + NavigationManager.Uri.AsSpan().IndexOfAny("?#") switch + { + -1 => NavigationManager.Uri.Length - pathStart, + var index => index - pathStart + }); - foreach (var kvp in RouteData.RouteValues) + builder.AddComponentParameter(1, nameof(CascadingModelBinder.Name), name); + builder.AddComponentParameter(2, nameof(CascadingModelBinder.ChildContent), builder => { - builder.AddComponentParameter(1, kvp.Key, kvp.Value); - } + builder.OpenComponent(0, RouteData.PageType); - var queryParameterSupplier = QueryParameterValueSupplier.ForType(RouteData.PageType); - if (queryParameterSupplier is not null) - { - // Since this component does accept some parameters from query, we must supply values for all of them, - // even if the querystring in the URI is empty. So don't skip the following logic. - var url = NavigationManager.Uri; - ReadOnlyMemory query = default; - var queryStartPos = url.IndexOf('?'); - if (queryStartPos >= 0) + foreach (var kvp in RouteData.RouteValues) { - var queryEndPos = url.IndexOf('#', queryStartPos); - query = url.AsMemory(queryStartPos..(queryEndPos < 0 ? url.Length : queryEndPos)); + builder.AddComponentParameter(1, kvp.Key, kvp.Value); + } + + var queryParameterSupplier = QueryParameterValueSupplier.ForType(RouteData.PageType); + if (queryParameterSupplier is not null) + { + // Since this component does accept some parameters from query, we must supply values for all of them, + // even if the querystring in the URI is empty. So don't skip the following logic. + var url = NavigationManager.Uri; + ReadOnlyMemory query = default; + var queryStartPos = url.IndexOf('?'); + if (queryStartPos >= 0) + { + var queryEndPos = url.IndexOf('#', queryStartPos); + query = url.AsMemory(queryStartPos..(queryEndPos < 0 ? url.Length : queryEndPos)); + } + queryParameterSupplier.RenderParametersFromQueryString(builder, query); } - queryParameterSupplier.RenderParametersFromQueryString(builder, query); - } + builder.CloseComponent(); + }); builder.CloseComponent(); } } From 3906d9c4005de93be3eecd3b4cad8abed86474f4 Mon Sep 17 00:00:00 2001 From: jacalvar Date: Fri, 14 Apr 2023 17:45:17 +0200 Subject: [PATCH 09/28] Dispatch forms with streaming rendering --- .../src/Binding/CascadingModelBinder.cs | 27 +++++++++++----- .../src/Binding/ModelBindingContext.cs | 23 +++++++++++-- .../Components/src/PublicAPI.Unshipped.txt | 10 ++++++ .../Components/src/RenderTree/Renderer.cs | 32 +++++++++++++++++++ src/Components/Components/src/RouteView.cs | 2 -- .../src/RazorComponentEndpointHost.cs | 2 +- .../src/RazorComponentEndpointInvoker.cs | 16 ++++++---- .../src/Rendering/EndpointHtmlRenderer.cs | 7 ++++ .../Web.JS/dist/Release/blazor.server.js | 2 +- .../Web.JS/dist/Release/blazor.webview.js | 2 +- src/Components/Web/src/Forms/EditForm.cs | 10 ++++++ .../Web/src/PublicAPI.Unshipped.txt | 2 ++ 12 files changed, 113 insertions(+), 22 deletions(-) diff --git a/src/Components/Components/src/Binding/CascadingModelBinder.cs b/src/Components/Components/src/Binding/CascadingModelBinder.cs index 029cae24e74c..64609731b974 100644 --- a/src/Components/Components/src/Binding/CascadingModelBinder.cs +++ b/src/Components/Components/src/Binding/CascadingModelBinder.cs @@ -2,37 +2,48 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Reflection.Metadata; +using Microsoft.AspNetCore.Components.Binding; -namespace Microsoft.AspNetCore.Components.Binding; +namespace Microsoft.AspNetCore.Components; -internal class CascadingModelBinder : IComponent +/// +/// Defines the binding context for data bound from external sources. +/// +public class CascadingModelBinder : IComponent { private RenderHandle _handle; private bool _hasRendered; private ModelBindingContext? _bindingContext; + /// + /// The binding context name. + /// [Parameter] public string Name { get; set; } = default!; + /// + /// Specifies the content to be rendered inside this . + /// [Parameter] public RenderFragment ChildContent { get; set; } = default!; - public void Attach(RenderHandle renderHandle) + void IComponent.Attach(RenderHandle renderHandle) { _handle = renderHandle; } - public Task SetParametersAsync(ParameterView parameters) + Task IComponent.SetParametersAsync(ParameterView parameters) { if (!_hasRendered) { _hasRendered = true; - _bindingContext = new ModelBindingContext(Name); parameters.SetParameterProperties(this); + + _bindingContext = new ModelBindingContext(Name); _handle.Render(builder => { builder.OpenComponent>(0); - builder.AddComponentParameter(1, "IsFixed", true); - builder.AddComponentParameter(2, "Value", _bindingContext); - builder.AddComponentParameter(3, "ChildContent", ChildContent); + builder.AddComponentParameter(1, nameof(CascadingValue.IsFixed), true); + builder.AddComponentParameter(2, nameof(CascadingValue.Value), _bindingContext); + builder.AddComponentParameter(3, nameof(CascadingValue.ChildContent), ChildContent); builder.CloseComponent(); }); } diff --git a/src/Components/Components/src/Binding/ModelBindingContext.cs b/src/Components/Components/src/Binding/ModelBindingContext.cs index 3ad3e65d9cf7..4aa7fd49b316 100644 --- a/src/Components/Components/src/Binding/ModelBindingContext.cs +++ b/src/Components/Components/src/Binding/ModelBindingContext.cs @@ -1,9 +1,26 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. namespace Microsoft.AspNetCore.Components.Binding; -public class ModelBindingContext(string name) +/// +/// The binding context associated with a given model binding operation. +/// +public class ModelBindingContext { - public string Name { get; } = name; + /// + /// Initializes a new instance of . + /// + /// The context name. + public ModelBindingContext(string name) + { + ArgumentNullException.ThrowIfNull(name); + + Name = name; + } + + /// + /// The context name. + /// + public string Name { get; } } diff --git a/src/Components/Components/src/PublicAPI.Unshipped.txt b/src/Components/Components/src/PublicAPI.Unshipped.txt index 6c6a6b135a1e..76c08dad5091 100644 --- a/src/Components/Components/src/PublicAPI.Unshipped.txt +++ b/src/Components/Components/src/PublicAPI.Unshipped.txt @@ -1,4 +1,13 @@ #nullable enable +Microsoft.AspNetCore.Components.Binding.ModelBindingContext +Microsoft.AspNetCore.Components.Binding.ModelBindingContext.ModelBindingContext(string! name) -> void +Microsoft.AspNetCore.Components.Binding.ModelBindingContext.Name.get -> string! +Microsoft.AspNetCore.Components.CascadingModelBinder +Microsoft.AspNetCore.Components.CascadingModelBinder.CascadingModelBinder() -> void +Microsoft.AspNetCore.Components.CascadingModelBinder.ChildContent.get -> Microsoft.AspNetCore.Components.RenderFragment! +Microsoft.AspNetCore.Components.CascadingModelBinder.ChildContent.set -> void +Microsoft.AspNetCore.Components.CascadingModelBinder.Name.get -> string! +Microsoft.AspNetCore.Components.CascadingModelBinder.Name.set -> void Microsoft.AspNetCore.Components.ComponentBase.DispatchExceptionAsync(System.Exception! exception) -> System.Threading.Tasks.Task! Microsoft.AspNetCore.Components.Infrastructure.ComponentStatePersistenceManager.PersistStateAsync(Microsoft.AspNetCore.Components.IPersistentComponentStateStore! store, Microsoft.AspNetCore.Components.Dispatcher! dispatcher) -> System.Threading.Tasks.Task! Microsoft.AspNetCore.Components.RenderHandle.DispatchExceptionAsync(System.Exception! exception) -> System.Threading.Tasks.Task! @@ -41,5 +50,6 @@ override Microsoft.AspNetCore.Components.EventCallback.GetHashCode() -> override Microsoft.AspNetCore.Components.EventCallback.Equals(object? obj) -> bool virtual Microsoft.AspNetCore.Components.RenderTree.Renderer.AddPendingTask(Microsoft.AspNetCore.Components.Rendering.ComponentState? componentState, System.Threading.Tasks.Task! task) -> void virtual Microsoft.AspNetCore.Components.RenderTree.Renderer.CreateComponentState(int componentId, Microsoft.AspNetCore.Components.IComponent! component, Microsoft.AspNetCore.Components.Rendering.ComponentState? parentComponentState) -> Microsoft.AspNetCore.Components.Rendering.ComponentState! +virtual Microsoft.AspNetCore.Components.RenderTree.Renderer.DispatchEventAsync(ulong eventHandlerId, Microsoft.AspNetCore.Components.RenderTree.EventFieldInfo? fieldInfo, System.EventArgs! eventArgs, bool quiesce) -> System.Threading.Tasks.Task! virtual Microsoft.AspNetCore.Components.RenderTree.Renderer.ShouldTrackNamedEventHandlers() -> bool virtual Microsoft.AspNetCore.Components.RenderTree.Renderer.TrackNamedEventId(ulong eventHandlerId, int componentId, string! eventHandlerName) -> void diff --git a/src/Components/Components/src/RenderTree/Renderer.cs b/src/Components/Components/src/RenderTree/Renderer.cs index 015c10b15724..14d22a0fab4b 100644 --- a/src/Components/Components/src/RenderTree/Renderer.cs +++ b/src/Components/Components/src/RenderTree/Renderer.cs @@ -372,6 +372,22 @@ protected virtual ComponentState CreateComponentState(int componentId, IComponen /// has completed. /// public virtual Task DispatchEventAsync(ulong eventHandlerId, EventFieldInfo? fieldInfo, EventArgs eventArgs) + { + return DispatchEventAsync(eventHandlerId, fieldInfo, eventArgs, quiesce: false); + } + + /// + /// Notifies the renderer that an event has occurred. + /// + /// The value from the original event attribute. + /// Arguments to be passed to the event handler. + /// Information that the renderer can use to update the state of the existing render tree to match the UI. + /// Whether to wait for quiescence or not. + /// + /// A which will complete once all asynchronous processing related to the event + /// has completed. + /// + public virtual Task DispatchEventAsync(ulong eventHandlerId, EventFieldInfo? fieldInfo, EventArgs eventArgs, bool quiesce) { Dispatcher.AssertAccess(); @@ -402,6 +418,17 @@ public virtual Task DispatchEventAsync(ulong eventHandlerId, EventFieldInfo? fie _isBatchInProgress = true; task = callback.InvokeAsync(eventArgs); + if (quiesce) + { + if (_ongoingQuiescenceTask == null) + { + _ongoingQuiescenceTask = task; + } + else + { + AddToPendingTasksWithErrorHandling(task, receiverComponentState); + } + } } catch (Exception e) { @@ -417,6 +444,11 @@ public virtual Task DispatchEventAsync(ulong eventHandlerId, EventFieldInfo? fie ProcessPendingRender(); } + if (quiesce) + { + return WaitForQuiescence(); + } + // Task completed synchronously or is still running. We already processed all of the rendering // work that was queued so let our error handler deal with it. var result = GetErrorHandledTask(task, receiverComponentState); diff --git a/src/Components/Components/src/RouteView.cs b/src/Components/Components/src/RouteView.cs index f0765a0b8641..ec458d7559b6 100644 --- a/src/Components/Components/src/RouteView.cs +++ b/src/Components/Components/src/RouteView.cs @@ -3,10 +3,8 @@ #nullable disable warnings -using System; using System.Diagnostics.CodeAnalysis; using System.Reflection; -using Microsoft.AspNetCore.Components.Binding; using Microsoft.AspNetCore.Components.Rendering; using Microsoft.AspNetCore.Components.Routing; diff --git a/src/Components/Endpoints/src/RazorComponentEndpointHost.cs b/src/Components/Endpoints/src/RazorComponentEndpointHost.cs index 064941afc074..626a93847e8f 100644 --- a/src/Components/Endpoints/src/RazorComponentEndpointHost.cs +++ b/src/Components/Endpoints/src/RazorComponentEndpointHost.cs @@ -39,7 +39,7 @@ private void BuildRenderTree(RenderTreeBuilder builder) builder.OpenComponent(0); builder.AddComponentParameter(1, nameof(LayoutView.Layout), pageLayoutType); - builder.AddComponentParameter(2, nameof(LayoutView.ChildContent), (RenderFragment)RenderPageWithParameters); + builder.AddComponentParameter(2, nameof(LayoutView.ChildContent), RenderPageWithParameters); builder.CloseComponent(); } diff --git a/src/Components/Endpoints/src/RazorComponentEndpointInvoker.cs b/src/Components/Endpoints/src/RazorComponentEndpointInvoker.cs index a472aa7c22f9..7beba465eb62 100644 --- a/src/Components/Endpoints/src/RazorComponentEndpointInvoker.cs +++ b/src/Components/Endpoints/src/RazorComponentEndpointInvoker.cs @@ -4,6 +4,7 @@ using System.Buffers; using System.Text; using System.Text.Encodings.Web; +using Microsoft.AspNetCore.Components.Web.HtmlRendering; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.WebUtilities; using Microsoft.Extensions.DependencyInjection; @@ -32,7 +33,7 @@ private async Task RenderComponentCore() { _context.Response.ContentType = RazorComponentResultExecutor.DefaultContentType; - if (await ValidateRequestAsync()) + if (!await TryValidateRequestAsync(out var isPost)) { // If the request is not valid we've already set the response to a 400 or similar // and we can just exit early. @@ -57,7 +58,9 @@ private async Task RenderComponentCore() _context, typeof(RazorComponentEndpointHost), hostParameters, - waitForQuiescence: false); + waitForQuiescence: isPost); + + var quiesceTask = isPost ? _renderer.DispatchCapturedEvent() : htmlContent.QuiescenceTask; // Importantly, we must not yield this thread (which holds exclusive access to the renderer sync context) // in between the first call to htmlContent.WriteTo and the point where we start listening for subsequent @@ -65,9 +68,9 @@ private async Task RenderComponentCore() // renderer sync context and cause a batch that would get missed. htmlContent.WriteTo(writer, HtmlEncoder.Default); // Don't use WriteToAsync, as per the comment above - if (!htmlContent.QuiescenceTask.IsCompleted) + if (!quiesceTask.IsCompleted) { - await _renderer.SendStreamingUpdatesAsync(_context, htmlContent.QuiescenceTask, writer); + await _renderer.SendStreamingUpdatesAsync(_context, quiesceTask, writer); } // Invoke FlushAsync to ensure any buffered content is asynchronously written to the underlying @@ -76,9 +79,10 @@ private async Task RenderComponentCore() await writer.FlushAsync(); } - private Task ValidateRequestAsync() + private Task TryValidateRequestAsync(out bool isPost) { - if (HttpMethods.IsPost(_context.Request.Method)) + isPost = HttpMethods.IsPost(_context.Request.Method); + if (isPost) { return Task.FromResult(TrySetFormHandler()); } diff --git a/src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.cs b/src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.cs index 8b7da19105ef..833112d060be 100644 --- a/src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.cs +++ b/src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.cs @@ -119,6 +119,13 @@ protected override void TrackNamedEventId(ulong eventHandlerId, int componentId, } } + internal Task DispatchCapturedEvent() + { + // Clear the list of non-streaming rendering tasks, since we've waited for quiesce before dispatching the event. + _nonStreamingPendingTasks.Clear(); + return DispatchEventAsync(_capturedNamedEvent.EventHandlerId, null, EventArgs.Empty, quiesce: true); + } + private static string GenerateComponentPath(ComponentState state) { // We are generating a path from the root component with te component type names like: diff --git a/src/Components/Web.JS/dist/Release/blazor.server.js b/src/Components/Web.JS/dist/Release/blazor.server.js index aab279636e30..a40c15479777 100644 --- a/src/Components/Web.JS/dist/Release/blazor.server.js +++ b/src/Components/Web.JS/dist/Release/blazor.server.js @@ -1 +1 @@ -(()=>{"use strict";var e,t,n,r={};r.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),function(e){window.DotNet=e;const t=[],n=new Map,r=new Map,o="__jsObjectId",i="__dotNetObject",s="__byte[]",a="__dotNetStream",c="__jsStreamReferenceLength";class l{constructor(e){this._jsObject=e,this._cachedFunctions=new Map}findFunction(e){const t=this._cachedFunctions.get(e);if(t)return t;let n,r=this._jsObject;if(e.split(".").forEach((t=>{if(!(t in r))throw new Error(`Could not find '${e}' ('${t}' was undefined).`);n=r,r=r[t]})),r instanceof Function)return r=r.bind(n),this._cachedFunctions.set(e,r),r;throw new Error(`The value '${e}' is not a function.`)}getWrappedObject(){return this._jsObject}}const h={},u={0:new l(window)};u[0]._cachedFunctions.set("import",(e=>("string"==typeof e&&e.startsWith("./")&&(e=new URL(e.substr(2),document.baseURI).toString()),import(e))));let d,p=1,f=1,g=null;function m(e){t.push(e)}function y(e){if(e&&"object"==typeof e){u[f]=new l(e);const t={[o]:f};return f++,t}throw new Error(`Cannot create a JSObjectReference from the value '${e}'.`)}function w(e){let t=-1;if(e instanceof ArrayBuffer&&(e=new Uint8Array(e)),e instanceof Blob)t=e.size;else{if(!(e.buffer instanceof ArrayBuffer))throw new Error("Supplied value is not a typed array or blob.");if(void 0===e.byteLength)throw new Error(`Cannot create a JSStreamReference from the value '${e}' as it doesn't have a byteLength.`);t=e.byteLength}const n={[c]:t};try{const t=y(e);n[o]=t[o]}catch(t){throw new Error(`Cannot create a JSStreamReference from the value '${e}'.`)}return n}function v(e){return e?JSON.parse(e,((e,n)=>t.reduce(((t,n)=>n(e,t)),n))):null}function b(e,t,n,r){const o=E();if(o.invokeDotNetFromJS){const i=U(r),s=o.invokeDotNetFromJS(e,t,n,i);return s?v(s):null}throw new Error("The current dispatcher does not support synchronous calls from JS to .NET. Use invokeMethodAsync instead.")}function _(e,t,n,r){if(e&&n)throw new Error(`For instance method calls, assemblyName should be null. Received '${e}'.`);const o=p++,i=new Promise(((e,t)=>{h[o]={resolve:e,reject:t}}));try{const i=U(r);E().beginInvokeDotNetFromJS(o,e,t,n,i)}catch(e){S(o,!1,e)}return i}function E(){if(null!==g)return g;throw new Error("No .NET call dispatcher has been set.")}function S(e,t,n){if(!h.hasOwnProperty(e))throw new Error(`There is no pending async call with ID ${e}.`);const r=h[e];delete h[e],t?r.resolve(n):r.reject(n)}function C(e){return e instanceof Error?`${e.message}\n${e.stack}`:e?e.toString():"null"}function I(e,t){const n=u[t];if(n)return n.findFunction(e);throw new Error(`JS object instance with ID ${t} does not exist (has it been disposed?).`)}function k(e){delete u[e]}e.attachDispatcher=function(e){g=e},e.attachReviver=m,e.invokeMethod=function(e,t,...n){return b(e,t,null,n)},e.invokeMethodAsync=function(e,t,...n){return _(e,t,null,n)},e.createJSObjectReference=y,e.createJSStreamReference=w,e.disposeJSObjectReference=function(e){const t=e&&e[o];"number"==typeof t&&k(t)},function(e){e[e.Default=0]="Default",e[e.JSObjectReference=1]="JSObjectReference",e[e.JSStreamReference=2]="JSStreamReference",e[e.JSVoidResult=3]="JSVoidResult"}(d=e.JSCallResultType||(e.JSCallResultType={})),e.jsCallDispatcher={findJSFunction:I,disposeJSObjectReferenceById:k,invokeJSFromDotNet:(e,t,n,r)=>{const o=R(I(e,r).apply(null,v(t)),n);return null==o?null:U(o)},beginInvokeJSFromDotNet:(e,t,n,r,o)=>{const i=new Promise((e=>{e(I(t,o).apply(null,v(n)))}));e&&i.then((t=>U([e,!0,R(t,r)]))).then((t=>E().endInvokeJSFromDotNet(e,!0,t)),(t=>E().endInvokeJSFromDotNet(e,!1,JSON.stringify([e,!1,C(t)]))))},endInvokeDotNetFromJS:(e,t,n)=>{const r=t?v(n):new Error(n);S(parseInt(e,10),t,r)},receiveByteArray:(e,t)=>{n.set(e,t)},supplyDotNetStream:(e,t)=>{if(r.has(e)){const n=r.get(e);r.delete(e),n.resolve(t)}else{const n=new D;n.resolve(t),r.set(e,n)}}};class T{constructor(e){this._id=e}invokeMethod(e,...t){return b(null,e,this._id,t)}invokeMethodAsync(e,...t){return _(null,e,this._id,t)}dispose(){_(null,"__Dispose",this._id,null).catch((e=>console.error(e)))}serializeAsArg(){return{__dotNetObject:this._id}}}e.DotNetObject=T,m((function(e,t){if(t&&"object"==typeof t){if(t.hasOwnProperty(i))return new T(t[i]);if(t.hasOwnProperty(o)){const e=t[o],n=u[e];if(n)return n.getWrappedObject();throw new Error(`JS object instance with Id '${e}' does not exist. It may have been disposed.`)}if(t.hasOwnProperty(s)){const e=t[s],r=n.get(e);if(void 0===r)throw new Error(`Byte array index '${e}' does not exist.`);return n.delete(e),r}if(t.hasOwnProperty(a))return new x(t[a])}return t}));class x{constructor(e){if(r.has(e))this._streamPromise=r.get(e).streamPromise,r.delete(e);else{const t=new D;r.set(e,t),this._streamPromise=t.streamPromise}}stream(){return this._streamPromise}async arrayBuffer(){return new Response(await this.stream()).arrayBuffer()}}class D{constructor(){this.streamPromise=new Promise(((e,t)=>{this.resolve=e,this.reject=t}))}}function R(e,t){switch(t){case d.Default:return e;case d.JSObjectReference:return y(e);case d.JSStreamReference:return w(e);case d.JSVoidResult:return null;default:throw new Error(`Invalid JS call result type '${t}'.`)}}let P=0;function U(e){return P=0,JSON.stringify(e,A)}function A(e,t){if(t instanceof T)return t.serializeAsArg();if(t instanceof Uint8Array){g.sendByteArray(P,t);const e={[s]:P};return P++,e}return t}}(e||(e={})),function(e){e[e.prependFrame=1]="prependFrame",e[e.removeFrame=2]="removeFrame",e[e.setAttribute=3]="setAttribute",e[e.removeAttribute=4]="removeAttribute",e[e.updateText=5]="updateText",e[e.stepIn=6]="stepIn",e[e.stepOut=7]="stepOut",e[e.updateMarkup=8]="updateMarkup",e[e.permutationListEntry=9]="permutationListEntry",e[e.permutationListEnd=10]="permutationListEnd"}(t||(t={})),function(e){e[e.element=1]="element",e[e.text=2]="text",e[e.attribute=3]="attribute",e[e.component=4]="component",e[e.region=5]="region",e[e.elementReferenceCapture=6]="elementReferenceCapture",e[e.markup=8]="markup"}(n||(n={}));class o{constructor(e,t){this.componentId=e,this.fieldValue=t}static fromEvent(e,t){const n=t.target;if(n instanceof Element){const t=function(e){return e instanceof HTMLInputElement?e.type&&"checkbox"===e.type.toLowerCase()?{value:e.checked}:{value:e.value}:e instanceof HTMLSelectElement||e instanceof HTMLTextAreaElement?{value:e.value}:null}(n);if(t)return new o(e,t.value)}return null}}const i=new Map,s=new Map,a=[];function c(e){return i.get(e)}function l(e){const t=i.get(e);return(null==t?void 0:t.browserEventName)||e}function h(e,t){e.forEach((e=>i.set(e,t)))}function u(e){const t=[];for(let n=0;ne.selected)).map((e=>e.value))}}{const e=function(e){return!!e&&"INPUT"===e.tagName&&"checkbox"===e.getAttribute("type")}(t);return{value:e?!!t.checked:t.value}}}}),h(["copy","cut","paste"],{createEventArgs:e=>({type:e.type})}),h(["drag","dragend","dragenter","dragleave","dragover","dragstart","drop"],{createEventArgs:e=>{return{...d(t=e),dataTransfer:t.dataTransfer?{dropEffect:t.dataTransfer.dropEffect,effectAllowed:t.dataTransfer.effectAllowed,files:Array.from(t.dataTransfer.files).map((e=>e.name)),items:Array.from(t.dataTransfer.items).map((e=>({kind:e.kind,type:e.type}))),types:t.dataTransfer.types}:null};var t}}),h(["focus","blur","focusin","focusout"],{createEventArgs:e=>({type:e.type})}),h(["keydown","keyup","keypress"],{createEventArgs:e=>{return{key:(t=e).key,code:t.code,location:t.location,repeat:t.repeat,ctrlKey:t.ctrlKey,shiftKey:t.shiftKey,altKey:t.altKey,metaKey:t.metaKey,type:t.type};var t}}),h(["contextmenu","click","mouseover","mouseout","mousemove","mousedown","mouseup","mouseleave","mouseenter","dblclick"],{createEventArgs:e=>d(e)}),h(["error"],{createEventArgs:e=>{return{message:(t=e).message,filename:t.filename,lineno:t.lineno,colno:t.colno,type:t.type};var t}}),h(["loadstart","timeout","abort","load","loadend","progress"],{createEventArgs:e=>{return{lengthComputable:(t=e).lengthComputable,loaded:t.loaded,total:t.total,type:t.type};var t}}),h(["touchcancel","touchend","touchmove","touchenter","touchleave","touchstart"],{createEventArgs:e=>{return{detail:(t=e).detail,touches:u(t.touches),targetTouches:u(t.targetTouches),changedTouches:u(t.changedTouches),ctrlKey:t.ctrlKey,shiftKey:t.shiftKey,altKey:t.altKey,metaKey:t.metaKey,type:t.type};var t}}),h(["gotpointercapture","lostpointercapture","pointercancel","pointerdown","pointerenter","pointerleave","pointermove","pointerout","pointerover","pointerup"],{createEventArgs:e=>{return{...d(t=e),pointerId:t.pointerId,width:t.width,height:t.height,pressure:t.pressure,tiltX:t.tiltX,tiltY:t.tiltY,pointerType:t.pointerType,isPrimary:t.isPrimary};var t}}),h(["wheel","mousewheel"],{createEventArgs:e=>{return{...d(t=e),deltaX:t.deltaX,deltaY:t.deltaY,deltaZ:t.deltaZ,deltaMode:t.deltaMode};var t}}),h(["toggle"],{createEventArgs:()=>({})});const p=["date","datetime-local","month","time","week"],f=new Map;let g,m,y=0;const w={async add(e,t,n){if(!n)throw new Error("initialParameters must be an object, even if empty.");const r="__bl-dynamic-root:"+(++y).toString();f.set(r,e);const o=await _().invokeMethodAsync("AddRootComponent",t,r),i=new b(o,m[t]);return await i.setParameters(n),i}};class v{invoke(e){return this._callback(e)}setCallback(t){this._selfJSObjectReference||(this._selfJSObjectReference=e.createJSObjectReference(this)),this._callback=t}getJSObjectReference(){return this._selfJSObjectReference}dispose(){this._selfJSObjectReference&&e.disposeJSObjectReference(this._selfJSObjectReference)}}class b{constructor(e,t){this._jsEventCallbackWrappers=new Map,this._componentId=e;for(const e of t)"eventcallback"===e.type&&this._jsEventCallbackWrappers.set(e.name.toLowerCase(),new v)}setParameters(e){const t={},n=Object.entries(e||{}),r=n.length;for(const[e,r]of n){const n=this._jsEventCallbackWrappers.get(e.toLowerCase());n&&r?(n.setCallback(r),t[e]=n.getJSObjectReference()):t[e]=r}return _().invokeMethodAsync("SetRootComponentParameters",this._componentId,r,t)}async dispose(){if(null!==this._componentId){await _().invokeMethodAsync("RemoveRootComponent",this._componentId),this._componentId=null;for(const e of this._jsEventCallbackWrappers.values())e.dispose()}}}function _(){if(!g)throw new Error("Dynamic root components have not been enabled in this application.");return g}const E=new Map;let S;const C=new Promise((e=>{S=e}));function I(e,t,n){return T(e,t.eventHandlerId,(()=>k(e).invokeMethodAsync("DispatchEventAsync",t,n)))}function k(e){const t=E.get(e);if(!t)throw new Error(`No interop methods are registered for renderer ${e}`);return t}let T=(e,t,n)=>n();const x=N(["abort","blur","canplay","canplaythrough","change","cuechange","durationchange","emptied","ended","error","focus","load","loadeddata","loadedmetadata","loadend","loadstart","mouseenter","mouseleave","pointerenter","pointerleave","pause","play","playing","progress","ratechange","reset","scroll","seeked","seeking","stalled","submit","suspend","timeupdate","toggle","unload","volumechange","waiting","DOMNodeInsertedIntoDocument","DOMNodeRemovedFromDocument"]),D={submit:!0},R=N(["click","dblclick","mousedown","mousemove","mouseup"]);class P{constructor(e){this.browserRendererId=e,this.afterClickCallbacks=[];const t=++P.nextEventDelegatorId;this.eventsCollectionKey=`_blazorEvents_${t}`,this.eventInfoStore=new U(this.onGlobalEvent.bind(this))}setListener(e,t,n,r){const o=this.getEventHandlerInfosForElement(e,!0),i=o.getHandler(t);if(i)this.eventInfoStore.update(i.eventHandlerId,n);else{const i={element:e,eventName:t,eventHandlerId:n,renderingComponentId:r};this.eventInfoStore.add(i),o.setHandler(t,i)}}getHandler(e){return this.eventInfoStore.get(e)}removeListener(e){const t=this.eventInfoStore.remove(e);if(t){const e=t.element,n=this.getEventHandlerInfosForElement(e,!1);n&&n.removeHandler(t.eventName)}}notifyAfterClick(e){this.afterClickCallbacks.push(e),this.eventInfoStore.addGlobalListener("click")}setStopPropagation(e,t,n){this.getEventHandlerInfosForElement(e,!0).stopPropagation(t,n)}setPreventDefault(e,t,n){this.getEventHandlerInfosForElement(e,!0).preventDefault(t,n)}onGlobalEvent(e){if(!(e.target instanceof Element))return;this.dispatchGlobalEventToAllElements(e.type,e);const t=(n=e.type,s.get(n));var n;t&&t.forEach((t=>this.dispatchGlobalEventToAllElements(t,e))),"click"===e.type&&this.afterClickCallbacks.forEach((t=>t(e)))}dispatchGlobalEventToAllElements(e,t){const n=t.composedPath();let r=n.shift(),i=null,s=!1;const a=Object.prototype.hasOwnProperty.call(x,e);let l=!1;for(;r;){const d=r,p=this.getEventHandlerInfosForElement(d,!1);if(p){const n=p.getHandler(e);if(n&&(h=d,u=t.type,!((h instanceof HTMLButtonElement||h instanceof HTMLInputElement||h instanceof HTMLTextAreaElement||h instanceof HTMLSelectElement)&&Object.prototype.hasOwnProperty.call(R,u)&&h.disabled))){if(!s){const n=c(e);i=(null==n?void 0:n.createEventArgs)?n.createEventArgs(t):{},s=!0}Object.prototype.hasOwnProperty.call(D,t.type)&&t.preventDefault(),I(this.browserRendererId,{eventHandlerId:n.eventHandlerId,eventName:e,eventFieldInfo:o.fromEvent(n.renderingComponentId,t)},i)}p.stopPropagation(e)&&(l=!0),p.preventDefault(e)&&t.preventDefault()}r=a||l?void 0:n.shift()}var h,u}getEventHandlerInfosForElement(e,t){return Object.prototype.hasOwnProperty.call(e,this.eventsCollectionKey)?e[this.eventsCollectionKey]:t?e[this.eventsCollectionKey]=new A:null}}P.nextEventDelegatorId=0;class U{constructor(e){this.globalListener=e,this.infosByEventHandlerId={},this.countByEventName={},a.push(this.handleEventNameAliasAdded.bind(this))}add(e){if(this.infosByEventHandlerId[e.eventHandlerId])throw new Error(`Event ${e.eventHandlerId} is already tracked`);this.infosByEventHandlerId[e.eventHandlerId]=e,this.addGlobalListener(e.eventName)}get(e){return this.infosByEventHandlerId[e]}addGlobalListener(e){if(e=l(e),Object.prototype.hasOwnProperty.call(this.countByEventName,e))this.countByEventName[e]++;else{this.countByEventName[e]=1;const t=Object.prototype.hasOwnProperty.call(x,e);document.addEventListener(e,this.globalListener,t)}}update(e,t){if(Object.prototype.hasOwnProperty.call(this.infosByEventHandlerId,t))throw new Error(`Event ${t} is already tracked`);const n=this.infosByEventHandlerId[e];delete this.infosByEventHandlerId[e],n.eventHandlerId=t,this.infosByEventHandlerId[t]=n}remove(e){const t=this.infosByEventHandlerId[e];if(t){delete this.infosByEventHandlerId[e];const n=l(t.eventName);0==--this.countByEventName[n]&&(delete this.countByEventName[n],document.removeEventListener(n,this.globalListener))}return t}handleEventNameAliasAdded(e,t){if(Object.prototype.hasOwnProperty.call(this.countByEventName,e)){const n=this.countByEventName[e];delete this.countByEventName[e],document.removeEventListener(e,this.globalListener),this.addGlobalListener(t),this.countByEventName[t]+=n-1}}}class A{constructor(){this.handlers={},this.preventDefaultFlags=null,this.stopPropagationFlags=null}getHandler(e){return Object.prototype.hasOwnProperty.call(this.handlers,e)?this.handlers[e]:null}setHandler(e,t){this.handlers[e]=t}removeHandler(e){delete this.handlers[e]}preventDefault(e,t){return void 0!==t&&(this.preventDefaultFlags=this.preventDefaultFlags||{},this.preventDefaultFlags[e]=t),!!this.preventDefaultFlags&&this.preventDefaultFlags[e]}stopPropagation(e,t){return void 0!==t&&(this.stopPropagationFlags=this.stopPropagationFlags||{},this.stopPropagationFlags[e]=t),!!this.stopPropagationFlags&&this.stopPropagationFlags[e]}}function N(e){const t={};return e.forEach((e=>{t[e]=!0})),t}const L=G("_blazorLogicalChildren"),$=G("_blazorLogicalParent"),B=G("_blazorLogicalEnd");function M(e,t){if(e.childNodes.length>0&&!t)throw new Error("New logical elements must start empty, or allowExistingContents must be true");return L in e||(e[L]=[]),e}function O(e,t){const n=document.createComment("!");return F(n,e,t),n}function F(e,t,n){const r=e;if(e instanceof Comment&&J(r)&&J(r).length>0)throw new Error("Not implemented: inserting non-empty logical container");if(j(r))throw new Error("Not implemented: moving existing logical children");const o=J(t);if(n0;)H(n,0)}const r=n;r.parentNode.removeChild(r)}function j(e){return e[$]||null}function W(e,t){return J(e)[t]}function z(e){const t=V(e);return"http://www.w3.org/2000/svg"===t.namespaceURI&&"foreignObject"!==t.tagName}function J(e){return e[L]}function q(e,t){const n=J(e);t.forEach((e=>{e.moveRangeStart=n[e.fromSiblingIndex],e.moveRangeEnd=Y(e.moveRangeStart)})),t.forEach((t=>{const r=document.createComment("marker");t.moveToBeforeMarker=r;const o=n[t.toSiblingIndex+1];o?o.parentNode.insertBefore(r,o):X(r,e)})),t.forEach((e=>{const t=e.moveToBeforeMarker,n=t.parentNode,r=e.moveRangeStart,o=e.moveRangeEnd;let i=r;for(;i;){const e=i.nextSibling;if(n.insertBefore(i,t),i===o)break;i=e}n.removeChild(t)})),t.forEach((e=>{n[e.toSiblingIndex]=e.moveRangeStart}))}function V(e){if(e instanceof Element||e instanceof DocumentFragment)return e;if(e instanceof Comment)return e.parentNode;throw new Error("Not a valid logical element")}function K(e){const t=J(j(e));return t[Array.prototype.indexOf.call(t,e)+1]||null}function X(e,t){if(t instanceof Element||t instanceof DocumentFragment)t.appendChild(e);else{if(!(t instanceof Comment))throw new Error(`Cannot append node because the parent is not a valid logical element. Parent: ${t}`);{const n=K(t);n?n.parentNode.insertBefore(e,n):X(e,j(t))}}}function Y(e){if(e instanceof Element||e instanceof DocumentFragment)return e;const t=K(e);if(t)return t.previousSibling;{const t=j(e);return t instanceof Element||t instanceof DocumentFragment?t.lastChild:Y(t)}}function G(e){return"function"==typeof Symbol?Symbol():e}function Q(e){return`_bl_${e}`}const Z="__internalId";e.attachReviver(((e,t)=>t&&"object"==typeof t&&Object.prototype.hasOwnProperty.call(t,Z)&&"string"==typeof t[Z]?function(e){const t=`[${Q(e)}]`;return document.querySelector(t)}(t[Z]):t));const ee="_blazorDeferredValue",te=document.createElement("template"),ne=document.createElementNS("http://www.w3.org/2000/svg","g"),re={},oe="__internal_",ie="preventDefault_",se="stopPropagation_";class ae{constructor(e){this.rootComponentIds=new Set,this.childComponentLocations={},this.eventDelegator=new P(e),this.eventDelegator.notifyAfterClick((e=>{if(!ge)return;if(0!==e.button||function(e){return e.ctrlKey||e.shiftKey||e.altKey||e.metaKey}(e))return;if(e.defaultPrevented)return;const t=function(e){const t=!window._blazorDisableComposedPath&&e.composedPath&&e.composedPath();if(t){for(let e=0;edocument.baseURI,getLocationHref:()=>location.href};function Ie(e,t,n=!1){const r=Ae(e);!t.forceLoad&&Le(r)?ke(r,!1,t.replaceHistoryEntry,t.historyEntryState,n):function(e,t){if(location.href===e){const t=e+"?";history.replaceState(null,"",t),location.replace(e)}else t?location.replace(e):location.href=e}(e,t.replaceHistoryEntry)}async function ke(e,t,n,r,o=!1){xe(),(o||!ye||await De(e,r,t))&&(fe=!0,n?history.replaceState({userState:r,_index:we},"",e):(we++,history.pushState({userState:r,_index:we},"",e)),await Re(t))}function Te(e){return new Promise((t=>{const n=Ee;Ee=()=>{Ee=n,t()},history.go(e)}))}function xe(){Se&&(Se(!1),Se=null)}function De(e,t,n){return new Promise((r=>{xe(),_e?(ve++,Se=r,_e(ve,e,t,n)):r(!1)}))}async function Re(e){var t;be&&await be(location.href,null===(t=history.state)||void 0===t?void 0:t.userState,e)}async function Pe(e){var t,n;Ee&&await Ee(e),we=null!==(n=null===(t=history.state)||void 0===t?void 0:t._index)&&void 0!==n?n:0}let Ue;function Ae(e){return Ue=Ue||document.createElement("a"),Ue.href=e,Ue.href}function Ne(e,t){return e?e.tagName===t?e:Ne(e.parentElement,t):null}function Le(e){const t=(n=document.baseURI).substring(0,n.lastIndexOf("/"));var n;const r=e.charAt(t.length);return e.startsWith(t)&&(""===r||"/"===r||"?"===r||"#"===r)}const $e={focus:function(e,t){if(e instanceof HTMLElement)e.focus({preventScroll:t});else{if(!(e instanceof SVGElement))throw new Error("Unable to focus an invalid element.");if(!e.hasAttribute("tabindex"))throw new Error("Unable to focus an SVG element that does not have a tabindex.");e.focus({preventScroll:t})}},focusBySelector:function(e){const t=document.querySelector(e);t&&(t.hasAttribute("tabindex")||(t.tabIndex=-1),t.focus())}},Be={init:function(e,t,n,r=50){const o=Oe(t);(o||document.documentElement).style.overflowAnchor="none";const i=document.createRange();h(n.parentElement)&&(t.style.display="table-row",n.style.display="table-row");const s=new IntersectionObserver((function(r){r.forEach((r=>{var o;if(!r.isIntersecting)return;i.setStartAfter(t),i.setEndBefore(n);const s=i.getBoundingClientRect().height,a=null===(o=r.rootBounds)||void 0===o?void 0:o.height;r.target===t?e.invokeMethodAsync("OnSpacerBeforeVisible",r.intersectionRect.top-r.boundingClientRect.top,s,a):r.target===n&&n.offsetHeight>0&&e.invokeMethodAsync("OnSpacerAfterVisible",r.boundingClientRect.bottom-r.intersectionRect.bottom,s,a)}))}),{root:o,rootMargin:`${r}px`});s.observe(t),s.observe(n);const a=l(t),c=l(n);function l(e){const t={attributes:!0},n=new MutationObserver(((n,r)=>{h(e.parentElement)&&(r.disconnect(),e.style.display="table-row",r.observe(e,t)),s.unobserve(e),s.observe(e)}));return n.observe(e,t),n}function h(e){return null!==e&&(e instanceof HTMLTableElement&&""===e.style.display||"table"===e.style.display||e instanceof HTMLTableSectionElement&&""===e.style.display||"table-row-group"===e.style.display)}Me[e._id]={intersectionObserver:s,mutationObserverBefore:a,mutationObserverAfter:c}},dispose:function(e){const t=Me[e._id];t&&(t.intersectionObserver.disconnect(),t.mutationObserverBefore.disconnect(),t.mutationObserverAfter.disconnect(),e.dispose(),delete Me[e._id])}},Me={};function Oe(e){return e&&e!==document.body&&e!==document.documentElement?"visible"!==getComputedStyle(e).overflowY?e:Oe(e.parentElement):null}const Fe={getAndRemoveExistingTitle:function(){var e;const t=document.head?document.head.getElementsByTagName("title"):[];if(0===t.length)return null;let n=null;for(let r=t.length-1;r>=0;r--){const o=t[r],i=o.previousSibling;i instanceof Comment&&null!==j(i)||(null===n&&(n=o.textContent),null===(e=o.parentNode)||void 0===e||e.removeChild(o))}return n}},He={init:function(e,t){t._blazorInputFileNextFileId=0,t.addEventListener("click",(function(){t.value=""})),t.addEventListener("change",(function(){t._blazorFilesById={};const n=Array.prototype.map.call(t.files,(function(e){const n={id:++t._blazorInputFileNextFileId,lastModified:new Date(e.lastModified).toISOString(),name:e.name,size:e.size,contentType:e.type,readPromise:void 0,arrayBuffer:void 0,blob:e};return t._blazorFilesById[n.id]=n,n}));e.invokeMethodAsync("NotifyChange",n)}))},toImageFile:async function(e,t,n,r,o){const i=je(e,t),s=await new Promise((function(e){const t=new Image;t.onload=function(){URL.revokeObjectURL(t.src),e(t)},t.onerror=function(){t.onerror=null,URL.revokeObjectURL(t.src)},t.src=URL.createObjectURL(i.blob)})),a=await new Promise((function(e){var t;const i=Math.min(1,r/s.width),a=Math.min(1,o/s.height),c=Math.min(i,a),l=document.createElement("canvas");l.width=Math.round(s.width*c),l.height=Math.round(s.height*c),null===(t=l.getContext("2d"))||void 0===t||t.drawImage(s,0,0,l.width,l.height),l.toBlob(e,n)})),c={id:++e._blazorInputFileNextFileId,lastModified:i.lastModified,name:i.name,size:(null==a?void 0:a.size)||0,contentType:n,blob:a||i.blob};return e._blazorFilesById[c.id]=c,c},readFileData:async function(e,t){return je(e,t).blob}};function je(e,t){const n=e._blazorFilesById[t];if(!n)throw new Error(`There is no file with ID ${t}. The file list may have changed. See https://aka.ms/aspnet/blazor-input-file-multiple-selections.`);return n}const We=new Set,ze={enableNavigationPrompt:function(e){0===We.size&&window.addEventListener("beforeunload",Je),We.add(e)},disableNavigationPrompt:function(e){We.delete(e),0===We.size&&window.removeEventListener("beforeunload",Je)}};function Je(e){e.preventDefault(),e.returnValue=!0}async function qe(e,t,n){return e instanceof Blob?await async function(e,t,n){const r=e.slice(t,t+n),o=await r.arrayBuffer();return new Uint8Array(o)}(e,t,n):function(e,t,n){return new Uint8Array(e.buffer,e.byteOffset+t,n)}(e,t,n)}const Ve=new Map,Ke={navigateTo:function(e,t,n=!1){Ie(e,t instanceof Object?t:{forceLoad:t,replaceHistoryEntry:n})},registerCustomEventType:function(e,t){if(!t)throw new Error("The options parameter is required.");if(i.has(e))throw new Error(`The event '${e}' is already registered.`);if(t.browserEventName){const n=s.get(t.browserEventName);n?n.push(e):s.set(t.browserEventName,[e]),a.forEach((n=>n(e,t.browserEventName)))}i.set(e,t)},rootComponents:w,_internal:{navigationManager:Ce,domWrapper:$e,Virtualize:Be,PageTitle:Fe,InputFile:He,NavigationLock:ze,getJSDataStreamChunk:qe,receiveDotNetDataStream:function(t,n,r,o){let i=Ve.get(t);if(!i){const n=new ReadableStream({start(e){Ve.set(t,e),i=e}});e.jsCallDispatcher.supplyDotNetStream(t,n)}o?(i.error(o),Ve.delete(t)):0===r?(i.close(),Ve.delete(t)):i.enqueue(n.length===r?n:n.subarray(0,r))},attachWebRendererInterop:function(t,n,r,o){if(E.has(t))throw new Error(`Interop methods are already registered for renderer ${t}`);E.set(t,n),Object.keys(r).length>0&&function(t,n,r){if(g)throw new Error("Dynamic root components have already been enabled.");g=t,m=n;for(const[t,o]of Object.entries(r)){const r=e.jsCallDispatcher.findJSFunction(t,0);for(const e of o)r(e,n[e])}}(k(t),r,o),S()}}};window.Blazor=Ke;const Xe=[0,2e3,1e4,3e4,null];class Ye{constructor(e){this._retryDelays=void 0!==e?[...e,null]:Xe}nextRetryDelayInMilliseconds(e){return this._retryDelays[e.previousRetryCount]}}class Ge{}Ge.Authorization="Authorization",Ge.Cookie="Cookie";class Qe{constructor(e,t,n){this.statusCode=e,this.statusText=t,this.content=n}}class Ze{get(e,t){return this.send({...t,method:"GET",url:e})}post(e,t){return this.send({...t,method:"POST",url:e})}delete(e,t){return this.send({...t,method:"DELETE",url:e})}getCookieString(e){return""}}class et extends Ze{constructor(e,t){super(),this._innerClient=e,this._accessTokenFactory=t}async send(e){let t=!0;this._accessTokenFactory&&(!this._accessToken||e.url&&e.url.indexOf("/negotiate?")>0)&&(t=!1,this._accessToken=await this._accessTokenFactory()),this._setAuthorizationHeader(e);const n=await this._innerClient.send(e);return t&&401===n.statusCode&&this._accessTokenFactory?(this._accessToken=await this._accessTokenFactory(),this._setAuthorizationHeader(e),await this._innerClient.send(e)):n}_setAuthorizationHeader(e){e.headers||(e.headers={}),this._accessToken?e.headers[Ge.Authorization]=`Bearer ${this._accessToken}`:this._accessTokenFactory&&e.headers[Ge.Authorization]&&delete e.headers[Ge.Authorization]}getCookieString(e){return this._innerClient.getCookieString(e)}}class tt extends Error{constructor(e,t){const n=new.target.prototype;super(`${e}: Status code '${t}'`),this.statusCode=t,this.__proto__=n}}class nt extends Error{constructor(e="A timeout occurred."){const t=new.target.prototype;super(e),this.__proto__=t}}class rt extends Error{constructor(e="An abort occurred."){const t=new.target.prototype;super(e),this.__proto__=t}}class ot extends Error{constructor(e,t){const n=new.target.prototype;super(e),this.transport=t,this.errorType="UnsupportedTransportError",this.__proto__=n}}class it extends Error{constructor(e,t){const n=new.target.prototype;super(e),this.transport=t,this.errorType="DisabledTransportError",this.__proto__=n}}class st extends Error{constructor(e,t){const n=new.target.prototype;super(e),this.transport=t,this.errorType="FailedToStartTransportError",this.__proto__=n}}class at extends Error{constructor(e){const t=new.target.prototype;super(e),this.errorType="FailedToNegotiateWithServerError",this.__proto__=t}}class ct extends Error{constructor(e,t){const n=new.target.prototype;super(e),this.innerErrors=t,this.__proto__=n}}var lt;!function(e){e[e.Trace=0]="Trace",e[e.Debug=1]="Debug",e[e.Information=2]="Information",e[e.Warning=3]="Warning",e[e.Error=4]="Error",e[e.Critical=5]="Critical",e[e.None=6]="None"}(lt||(lt={}));class ht{constructor(){}log(e,t){}}ht.instance=new ht;const ut="0.0.0-DEV_BUILD";class dt{static isRequired(e,t){if(null==e)throw new Error(`The '${t}' argument is required.`)}static isNotEmpty(e,t){if(!e||e.match(/^\s*$/))throw new Error(`The '${t}' argument should not be empty.`)}static isIn(e,t,n){if(!(e in t))throw new Error(`Unknown ${n} value: ${e}.`)}}class pt{static get isBrowser(){return"object"==typeof window&&"object"==typeof window.document}static get isWebWorker(){return"object"==typeof self&&"importScripts"in self}static get isReactNative(){return"object"==typeof window&&void 0===window.document}static get isNode(){return!this.isBrowser&&!this.isWebWorker&&!this.isReactNative}}function ft(e,t){let n="";return gt(e)?(n=`Binary data of length ${e.byteLength}`,t&&(n+=`. Content: '${function(e){const t=new Uint8Array(e);let n="";return t.forEach((e=>{n+=`0x${e<16?"0":""}${e.toString(16)} `})),n.substr(0,n.length-1)}(e)}'`)):"string"==typeof e&&(n=`String data of length ${e.length}`,t&&(n+=`. Content: '${e}'`)),n}function gt(e){return e&&"undefined"!=typeof ArrayBuffer&&(e instanceof ArrayBuffer||e.constructor&&"ArrayBuffer"===e.constructor.name)}async function mt(e,t,n,r,o,i){const s={},[a,c]=vt();s[a]=c,e.log(lt.Trace,`(${t} transport) sending data. ${ft(o,i.logMessageContent)}.`);const l=gt(o)?"arraybuffer":"text",h=await n.post(r,{content:o,headers:{...s,...i.headers},responseType:l,timeout:i.timeout,withCredentials:i.withCredentials});e.log(lt.Trace,`(${t} transport) request complete. Response status: ${h.statusCode}.`)}class yt{constructor(e,t){this._subject=e,this._observer=t}dispose(){const e=this._subject.observers.indexOf(this._observer);e>-1&&this._subject.observers.splice(e,1),0===this._subject.observers.length&&this._subject.cancelCallback&&this._subject.cancelCallback().catch((e=>{}))}}class wt{constructor(e){this._minLevel=e,this.out=console}log(e,t){if(e>=this._minLevel){const n=`[${(new Date).toISOString()}] ${lt[e]}: ${t}`;switch(e){case lt.Critical:case lt.Error:this.out.error(n);break;case lt.Warning:this.out.warn(n);break;case lt.Information:this.out.info(n);break;default:this.out.log(n)}}}}function vt(){let e="X-SignalR-User-Agent";return pt.isNode&&(e="User-Agent"),[e,bt(ut,_t(),pt.isNode?"NodeJS":"Browser",Et())]}function bt(e,t,n,r){let o="Microsoft SignalR/";const i=e.split(".");return o+=`${i[0]}.${i[1]}`,o+=` (${e}; `,o+=t&&""!==t?`${t}; `:"Unknown OS; ",o+=`${n}`,o+=r?`; ${r}`:"; Unknown Runtime Version",o+=")",o}function _t(){if(!pt.isNode)return"";switch(process.platform){case"win32":return"Windows NT";case"darwin":return"macOS";case"linux":return"Linux";default:return process.platform}}function Et(){if(pt.isNode)return process.versions.node}function St(e){return e.stack?e.stack:e.message?e.message:`${e}`}class Ct extends Ze{constructor(e){if(super(),this._logger=e,"undefined"==typeof fetch){const e=require;this._jar=new(e("tough-cookie").CookieJar),this._fetchType=e("node-fetch"),this._fetchType=e("fetch-cookie")(this._fetchType,this._jar)}else this._fetchType=fetch.bind(function(){if("undefined"!=typeof globalThis)return globalThis;if("undefined"!=typeof self)return self;if("undefined"!=typeof window)return window;if(void 0!==r.g)return r.g;throw new Error("could not find global")}());if("undefined"==typeof AbortController){const e=require;this._abortControllerType=e("abort-controller")}else this._abortControllerType=AbortController}async send(e){if(e.abortSignal&&e.abortSignal.aborted)throw new rt;if(!e.method)throw new Error("No method defined.");if(!e.url)throw new Error("No url defined.");const t=new this._abortControllerType;let n;e.abortSignal&&(e.abortSignal.onabort=()=>{t.abort(),n=new rt});let r,o=null;if(e.timeout){const r=e.timeout;o=setTimeout((()=>{t.abort(),this._logger.log(lt.Warning,"Timeout from HTTP request."),n=new nt}),r)}""===e.content&&(e.content=void 0),e.content&&(e.headers=e.headers||{},gt(e.content)?e.headers["Content-Type"]="application/octet-stream":e.headers["Content-Type"]="text/plain;charset=UTF-8");try{r=await this._fetchType(e.url,{body:e.content,cache:"no-cache",credentials:!0===e.withCredentials?"include":"same-origin",headers:{"X-Requested-With":"XMLHttpRequest",...e.headers},method:e.method,mode:"cors",redirect:"follow",signal:t.signal})}catch(e){if(n)throw n;throw this._logger.log(lt.Warning,`Error from HTTP request. ${e}.`),e}finally{o&&clearTimeout(o),e.abortSignal&&(e.abortSignal.onabort=null)}if(!r.ok){const e=await It(r,"text");throw new tt(e||r.statusText,r.status)}const i=It(r,e.responseType),s=await i;return new Qe(r.status,r.statusText,s)}getCookieString(e){return""}}function It(e,t){let n;switch(t){case"arraybuffer":n=e.arrayBuffer();break;case"text":default:n=e.text();break;case"blob":case"document":case"json":throw new Error(`${t} is not supported.`)}return n}class kt extends Ze{constructor(e){super(),this._logger=e}send(e){return e.abortSignal&&e.abortSignal.aborted?Promise.reject(new rt):e.method?e.url?new Promise(((t,n)=>{const r=new XMLHttpRequest;r.open(e.method,e.url,!0),r.withCredentials=void 0===e.withCredentials||e.withCredentials,r.setRequestHeader("X-Requested-With","XMLHttpRequest"),""===e.content&&(e.content=void 0),e.content&&(gt(e.content)?r.setRequestHeader("Content-Type","application/octet-stream"):r.setRequestHeader("Content-Type","text/plain;charset=UTF-8"));const o=e.headers;o&&Object.keys(o).forEach((e=>{r.setRequestHeader(e,o[e])})),e.responseType&&(r.responseType=e.responseType),e.abortSignal&&(e.abortSignal.onabort=()=>{r.abort(),n(new rt)}),e.timeout&&(r.timeout=e.timeout),r.onload=()=>{e.abortSignal&&(e.abortSignal.onabort=null),r.status>=200&&r.status<300?t(new Qe(r.status,r.statusText,r.response||r.responseText)):n(new tt(r.response||r.responseText||r.statusText,r.status))},r.onerror=()=>{this._logger.log(lt.Warning,`Error from HTTP request. ${r.status}: ${r.statusText}.`),n(new tt(r.statusText,r.status))},r.ontimeout=()=>{this._logger.log(lt.Warning,"Timeout from HTTP request."),n(new nt)},r.send(e.content)})):Promise.reject(new Error("No url defined.")):Promise.reject(new Error("No method defined."))}}class Tt extends Ze{constructor(e){if(super(),"undefined"!=typeof fetch)this._httpClient=new Ct(e);else{if("undefined"==typeof XMLHttpRequest)throw new Error("No usable HttpClient found.");this._httpClient=new kt(e)}}send(e){return e.abortSignal&&e.abortSignal.aborted?Promise.reject(new rt):e.method?e.url?this._httpClient.send(e):Promise.reject(new Error("No url defined.")):Promise.reject(new Error("No method defined."))}getCookieString(e){return this._httpClient.getCookieString(e)}}var xt,Dt,Rt,Pt;!function(e){e[e.None=0]="None",e[e.WebSockets=1]="WebSockets",e[e.ServerSentEvents=2]="ServerSentEvents",e[e.LongPolling=4]="LongPolling"}(xt||(xt={})),function(e){e[e.Text=1]="Text",e[e.Binary=2]="Binary"}(Dt||(Dt={}));class Ut{constructor(){this._isAborted=!1,this.onabort=null}abort(){this._isAborted||(this._isAborted=!0,this.onabort&&this.onabort())}get signal(){return this}get aborted(){return this._isAborted}}class At{get pollAborted(){return this._pollAbort.aborted}constructor(e,t,n){this._httpClient=e,this._logger=t,this._pollAbort=new Ut,this._options=n,this._running=!1,this.onreceive=null,this.onclose=null}async connect(e,t){if(dt.isRequired(e,"url"),dt.isRequired(t,"transferFormat"),dt.isIn(t,Dt,"transferFormat"),this._url=e,this._logger.log(lt.Trace,"(LongPolling transport) Connecting."),t===Dt.Binary&&"undefined"!=typeof XMLHttpRequest&&"string"!=typeof(new XMLHttpRequest).responseType)throw new Error("Binary protocols over XmlHttpRequest not implementing advanced features are not supported.");const[n,r]=vt(),o={[n]:r,...this._options.headers},i={abortSignal:this._pollAbort.signal,headers:o,timeout:1e5,withCredentials:this._options.withCredentials};t===Dt.Binary&&(i.responseType="arraybuffer");const s=`${e}&_=${Date.now()}`;this._logger.log(lt.Trace,`(LongPolling transport) polling: ${s}.`);const a=await this._httpClient.get(s,i);200!==a.statusCode?(this._logger.log(lt.Error,`(LongPolling transport) Unexpected response code: ${a.statusCode}.`),this._closeError=new tt(a.statusText||"",a.statusCode),this._running=!1):this._running=!0,this._receiving=this._poll(this._url,i)}async _poll(e,t){try{for(;this._running;)try{const n=`${e}&_=${Date.now()}`;this._logger.log(lt.Trace,`(LongPolling transport) polling: ${n}.`);const r=await this._httpClient.get(n,t);204===r.statusCode?(this._logger.log(lt.Information,"(LongPolling transport) Poll terminated by server."),this._running=!1):200!==r.statusCode?(this._logger.log(lt.Error,`(LongPolling transport) Unexpected response code: ${r.statusCode}.`),this._closeError=new tt(r.statusText||"",r.statusCode),this._running=!1):r.content?(this._logger.log(lt.Trace,`(LongPolling transport) data received. ${ft(r.content,this._options.logMessageContent)}.`),this.onreceive&&this.onreceive(r.content)):this._logger.log(lt.Trace,"(LongPolling transport) Poll timed out, reissuing.")}catch(e){this._running?e instanceof nt?this._logger.log(lt.Trace,"(LongPolling transport) Poll timed out, reissuing."):(this._closeError=e,this._running=!1):this._logger.log(lt.Trace,`(LongPolling transport) Poll errored after shutdown: ${e.message}`)}}finally{this._logger.log(lt.Trace,"(LongPolling transport) Polling complete."),this.pollAborted||this._raiseOnClose()}}async send(e){return this._running?mt(this._logger,"LongPolling",this._httpClient,this._url,e,this._options):Promise.reject(new Error("Cannot send until the transport is connected"))}async stop(){this._logger.log(lt.Trace,"(LongPolling transport) Stopping polling."),this._running=!1,this._pollAbort.abort();try{await this._receiving,this._logger.log(lt.Trace,`(LongPolling transport) sending DELETE request to ${this._url}.`);const e={},[t,n]=vt();e[t]=n;const r={headers:{...e,...this._options.headers},timeout:this._options.timeout,withCredentials:this._options.withCredentials};await this._httpClient.delete(this._url,r),this._logger.log(lt.Trace,"(LongPolling transport) DELETE request sent.")}finally{this._logger.log(lt.Trace,"(LongPolling transport) Stop finished."),this._raiseOnClose()}}_raiseOnClose(){if(this.onclose){let e="(LongPolling transport) Firing onclose event.";this._closeError&&(e+=" Error: "+this._closeError),this._logger.log(lt.Trace,e),this.onclose(this._closeError)}}}class Nt{constructor(e,t,n,r){this._httpClient=e,this._accessToken=t,this._logger=n,this._options=r,this.onreceive=null,this.onclose=null}async connect(e,t){return dt.isRequired(e,"url"),dt.isRequired(t,"transferFormat"),dt.isIn(t,Dt,"transferFormat"),this._logger.log(lt.Trace,"(SSE transport) Connecting."),this._url=e,this._accessToken&&(e+=(e.indexOf("?")<0?"?":"&")+`access_token=${encodeURIComponent(this._accessToken)}`),new Promise(((n,r)=>{let o,i=!1;if(t===Dt.Text){if(pt.isBrowser||pt.isWebWorker)o=new this._options.EventSource(e,{withCredentials:this._options.withCredentials});else{const t=this._httpClient.getCookieString(e),n={};n.Cookie=t;const[r,i]=vt();n[r]=i,o=new this._options.EventSource(e,{withCredentials:this._options.withCredentials,headers:{...n,...this._options.headers}})}try{o.onmessage=e=>{if(this.onreceive)try{this._logger.log(lt.Trace,`(SSE transport) data received. ${ft(e.data,this._options.logMessageContent)}.`),this.onreceive(e.data)}catch(e){return void this._close(e)}},o.onerror=e=>{i?this._close():r(new Error("EventSource failed to connect. The connection could not be found on the server, either the connection ID is not present on the server, or a proxy is refusing/buffering the connection. If you have multiple servers check that sticky sessions are enabled."))},o.onopen=()=>{this._logger.log(lt.Information,`SSE connected to ${this._url}`),this._eventSource=o,i=!0,n()}}catch(e){return void r(e)}}else r(new Error("The Server-Sent Events transport only supports the 'Text' transfer format"))}))}async send(e){return this._eventSource?mt(this._logger,"SSE",this._httpClient,this._url,e,this._options):Promise.reject(new Error("Cannot send until the transport is connected"))}stop(){return this._close(),Promise.resolve()}_close(e){this._eventSource&&(this._eventSource.close(),this._eventSource=void 0,this.onclose&&this.onclose(e))}}class Lt{constructor(e,t,n,r,o,i){this._logger=n,this._accessTokenFactory=t,this._logMessageContent=r,this._webSocketConstructor=o,this._httpClient=e,this.onreceive=null,this.onclose=null,this._headers=i}async connect(e,t){let n;return dt.isRequired(e,"url"),dt.isRequired(t,"transferFormat"),dt.isIn(t,Dt,"transferFormat"),this._logger.log(lt.Trace,"(WebSockets transport) Connecting."),this._accessTokenFactory&&(n=await this._accessTokenFactory()),new Promise(((r,o)=>{let i;e=e.replace(/^http/,"ws");const s=this._httpClient.getCookieString(e);let a=!1;if(pt.isReactNative){const t={},[r,o]=vt();t[r]=o,n&&(t[Ge.Authorization]=`Bearer ${n}`),s&&(t[Ge.Cookie]=s),i=new this._webSocketConstructor(e,void 0,{headers:{...t,...this._headers}})}else n&&(e+=(e.indexOf("?")<0?"?":"&")+`access_token=${encodeURIComponent(n)}`);i||(i=new this._webSocketConstructor(e)),t===Dt.Binary&&(i.binaryType="arraybuffer"),i.onopen=t=>{this._logger.log(lt.Information,`WebSocket connected to ${e}.`),this._webSocket=i,a=!0,r()},i.onerror=e=>{let t=null;t="undefined"!=typeof ErrorEvent&&e instanceof ErrorEvent?e.error:"There was an error with the transport",this._logger.log(lt.Information,`(WebSockets transport) ${t}.`)},i.onmessage=e=>{if(this._logger.log(lt.Trace,`(WebSockets transport) data received. ${ft(e.data,this._logMessageContent)}.`),this.onreceive)try{this.onreceive(e.data)}catch(e){return void this._close(e)}},i.onclose=e=>{if(a)this._close(e);else{let t=null;t="undefined"!=typeof ErrorEvent&&e instanceof ErrorEvent?e.error:"WebSocket failed to connect. The connection could not be found on the server, either the endpoint may not be a SignalR endpoint, the connection ID is not present on the server, or there is a proxy blocking WebSockets. If you have multiple servers check that sticky sessions are enabled.",o(new Error(t))}}}))}send(e){return this._webSocket&&this._webSocket.readyState===this._webSocketConstructor.OPEN?(this._logger.log(lt.Trace,`(WebSockets transport) sending data. ${ft(e,this._logMessageContent)}.`),this._webSocket.send(e),Promise.resolve()):Promise.reject("WebSocket is not in the OPEN state")}stop(){return this._webSocket&&this._close(void 0),Promise.resolve()}_close(e){this._webSocket&&(this._webSocket.onclose=()=>{},this._webSocket.onmessage=()=>{},this._webSocket.onerror=()=>{},this._webSocket.close(),this._webSocket=void 0),this._logger.log(lt.Trace,"(WebSockets transport) socket closed."),this.onclose&&(!this._isCloseEvent(e)||!1!==e.wasClean&&1e3===e.code?e instanceof Error?this.onclose(e):this.onclose():this.onclose(new Error(`WebSocket closed with status code: ${e.code} (${e.reason||"no reason given"}).`)))}_isCloseEvent(e){return e&&"boolean"==typeof e.wasClean&&"number"==typeof e.code}}class $t{constructor(e,t={}){var n;if(this._stopPromiseResolver=()=>{},this.features={},this._negotiateVersion=1,dt.isRequired(e,"url"),this._logger=void 0===(n=t.logger)?new wt(lt.Information):null===n?ht.instance:void 0!==n.log?n:new wt(n),this.baseUrl=this._resolveUrl(e),(t=t||{}).logMessageContent=void 0!==t.logMessageContent&&t.logMessageContent,"boolean"!=typeof t.withCredentials&&void 0!==t.withCredentials)throw new Error("withCredentials option was not a 'boolean' or 'undefined' value");t.withCredentials=void 0===t.withCredentials||t.withCredentials,t.timeout=void 0===t.timeout?1e5:t.timeout,"undefined"==typeof WebSocket||t.WebSocket||(t.WebSocket=WebSocket),"undefined"==typeof EventSource||t.EventSource||(t.EventSource=EventSource),this._httpClient=new et(t.httpClient||new Tt(this._logger),t.accessTokenFactory),this._connectionState="Disconnected",this._connectionStarted=!1,this._options=t,this.onreceive=null,this.onclose=null}async start(e){if(e=e||Dt.Binary,dt.isIn(e,Dt,"transferFormat"),this._logger.log(lt.Debug,`Starting connection with transfer format '${Dt[e]}'.`),"Disconnected"!==this._connectionState)return Promise.reject(new Error("Cannot start an HttpConnection that is not in the 'Disconnected' state."));if(this._connectionState="Connecting",this._startInternalPromise=this._startInternal(e),await this._startInternalPromise,"Disconnecting"===this._connectionState){const e="Failed to start the HttpConnection before stop() was called.";return this._logger.log(lt.Error,e),await this._stopPromise,Promise.reject(new rt(e))}if("Connected"!==this._connectionState){const e="HttpConnection.startInternal completed gracefully but didn't enter the connection into the connected state!";return this._logger.log(lt.Error,e),Promise.reject(new rt(e))}this._connectionStarted=!0}send(e){return"Connected"!==this._connectionState?Promise.reject(new Error("Cannot send data if the connection is not in the 'Connected' State.")):(this._sendQueue||(this._sendQueue=new Bt(this.transport)),this._sendQueue.send(e))}async stop(e){return"Disconnected"===this._connectionState?(this._logger.log(lt.Debug,`Call to HttpConnection.stop(${e}) ignored because the connection is already in the disconnected state.`),Promise.resolve()):"Disconnecting"===this._connectionState?(this._logger.log(lt.Debug,`Call to HttpConnection.stop(${e}) ignored because the connection is already in the disconnecting state.`),this._stopPromise):(this._connectionState="Disconnecting",this._stopPromise=new Promise((e=>{this._stopPromiseResolver=e})),await this._stopInternal(e),void await this._stopPromise)}async _stopInternal(e){this._stopError=e;try{await this._startInternalPromise}catch(e){}if(this.transport){try{await this.transport.stop()}catch(e){this._logger.log(lt.Error,`HttpConnection.transport.stop() threw error '${e}'.`),this._stopConnection()}this.transport=void 0}else this._logger.log(lt.Debug,"HttpConnection.transport is undefined in HttpConnection.stop() because start() failed.")}async _startInternal(e){let t=this.baseUrl;this._accessTokenFactory=this._options.accessTokenFactory,this._httpClient._accessTokenFactory=this._accessTokenFactory;try{if(this._options.skipNegotiation){if(this._options.transport!==xt.WebSockets)throw new Error("Negotiation can only be skipped when using the WebSocket transport directly.");this.transport=this._constructTransport(xt.WebSockets),await this._startTransport(t,e)}else{let n=null,r=0;do{if(n=await this._getNegotiationResponse(t),"Disconnecting"===this._connectionState||"Disconnected"===this._connectionState)throw new rt("The connection was stopped during negotiation.");if(n.error)throw new Error(n.error);if(n.ProtocolVersion)throw new Error("Detected a connection attempt to an ASP.NET SignalR Server. This client only supports connecting to an ASP.NET Core SignalR Server. See https://aka.ms/signalr-core-differences for details.");if(n.url&&(t=n.url),n.accessToken){const e=n.accessToken;this._accessTokenFactory=()=>e,this._httpClient._accessToken=e,this._httpClient._accessTokenFactory=void 0}r++}while(n.url&&r<100);if(100===r&&n.url)throw new Error("Negotiate redirection limit exceeded.");await this._createTransport(t,this._options.transport,n,e)}this.transport instanceof At&&(this.features.inherentKeepAlive=!0),"Connecting"===this._connectionState&&(this._logger.log(lt.Debug,"The HttpConnection connected successfully."),this._connectionState="Connected")}catch(e){return this._logger.log(lt.Error,"Failed to start the connection: "+e),this._connectionState="Disconnected",this.transport=void 0,this._stopPromiseResolver(),Promise.reject(e)}}async _getNegotiationResponse(e){const t={},[n,r]=vt();t[n]=r;const o=this._resolveNegotiateUrl(e);this._logger.log(lt.Debug,`Sending negotiation request: ${o}.`);try{const e=await this._httpClient.post(o,{content:"",headers:{...t,...this._options.headers},timeout:this._options.timeout,withCredentials:this._options.withCredentials});if(200!==e.statusCode)return Promise.reject(new Error(`Unexpected status code returned from negotiate '${e.statusCode}'`));const n=JSON.parse(e.content);return(!n.negotiateVersion||n.negotiateVersion<1)&&(n.connectionToken=n.connectionId),n}catch(e){let t="Failed to complete negotiation with the server: "+e;return e instanceof tt&&404===e.statusCode&&(t+=" Either this is not a SignalR endpoint or there is a proxy blocking the connection."),this._logger.log(lt.Error,t),Promise.reject(new at(t))}}_createConnectUrl(e,t){return t?e+(-1===e.indexOf("?")?"?":"&")+`id=${t}`:e}async _createTransport(e,t,n,r){let o=this._createConnectUrl(e,n.connectionToken);if(this._isITransport(t))return this._logger.log(lt.Debug,"Connection was provided an instance of ITransport, using that directly."),this.transport=t,await this._startTransport(o,r),void(this.connectionId=n.connectionId);const i=[],s=n.availableTransports||[];let a=n;for(const n of s){const s=this._resolveTransportOrError(n,t,r);if(s instanceof Error)i.push(`${n.transport} failed:`),i.push(s);else if(this._isITransport(s)){if(this.transport=s,!a){try{a=await this._getNegotiationResponse(e)}catch(e){return Promise.reject(e)}o=this._createConnectUrl(e,a.connectionToken)}try{return await this._startTransport(o,r),void(this.connectionId=a.connectionId)}catch(e){if(this._logger.log(lt.Error,`Failed to start the transport '${n.transport}': ${e}`),a=void 0,i.push(new st(`${n.transport} failed: ${e}`,xt[n.transport])),"Connecting"!==this._connectionState){const e="Failed to select transport before stop() was called.";return this._logger.log(lt.Debug,e),Promise.reject(new rt(e))}}}}return i.length>0?Promise.reject(new ct(`Unable to connect to the server with any of the available transports. ${i.join(" ")}`,i)):Promise.reject(new Error("None of the transports supported by the client are supported by the server."))}_constructTransport(e){switch(e){case xt.WebSockets:if(!this._options.WebSocket)throw new Error("'WebSocket' is not supported in your environment.");return new Lt(this._httpClient,this._accessTokenFactory,this._logger,this._options.logMessageContent,this._options.WebSocket,this._options.headers||{});case xt.ServerSentEvents:if(!this._options.EventSource)throw new Error("'EventSource' is not supported in your environment.");return new Nt(this._httpClient,this._httpClient._accessToken,this._logger,this._options);case xt.LongPolling:return new At(this._httpClient,this._logger,this._options);default:throw new Error(`Unknown transport: ${e}.`)}}_startTransport(e,t){return this.transport.onreceive=this.onreceive,this.transport.onclose=e=>this._stopConnection(e),this.transport.connect(e,t)}_resolveTransportOrError(e,t,n){const r=xt[e.transport];if(null==r)return this._logger.log(lt.Debug,`Skipping transport '${e.transport}' because it is not supported by this client.`),new Error(`Skipping transport '${e.transport}' because it is not supported by this client.`);if(!function(e,t){return!e||0!=(t&e)}(t,r))return this._logger.log(lt.Debug,`Skipping transport '${xt[r]}' because it was disabled by the client.`),new it(`'${xt[r]}' is disabled by the client.`,r);if(!(e.transferFormats.map((e=>Dt[e])).indexOf(n)>=0))return this._logger.log(lt.Debug,`Skipping transport '${xt[r]}' because it does not support the requested transfer format '${Dt[n]}'.`),new Error(`'${xt[r]}' does not support ${Dt[n]}.`);if(r===xt.WebSockets&&!this._options.WebSocket||r===xt.ServerSentEvents&&!this._options.EventSource)return this._logger.log(lt.Debug,`Skipping transport '${xt[r]}' because it is not supported in your environment.'`),new ot(`'${xt[r]}' is not supported in your environment.`,r);this._logger.log(lt.Debug,`Selecting transport '${xt[r]}'.`);try{return this._constructTransport(r)}catch(e){return e}}_isITransport(e){return e&&"object"==typeof e&&"connect"in e}_stopConnection(e){if(this._logger.log(lt.Debug,`HttpConnection.stopConnection(${e}) called while in state ${this._connectionState}.`),this.transport=void 0,e=this._stopError||e,this._stopError=void 0,"Disconnected"!==this._connectionState){if("Connecting"===this._connectionState)throw this._logger.log(lt.Warning,`Call to HttpConnection.stopConnection(${e}) was ignored because the connection is still in the connecting state.`),new Error(`HttpConnection.stopConnection(${e}) was called while the connection is still in the connecting state.`);if("Disconnecting"===this._connectionState&&this._stopPromiseResolver(),e?this._logger.log(lt.Error,`Connection disconnected with error '${e}'.`):this._logger.log(lt.Information,"Connection disconnected."),this._sendQueue&&(this._sendQueue.stop().catch((e=>{this._logger.log(lt.Error,`TransportSendQueue.stop() threw error '${e}'.`)})),this._sendQueue=void 0),this.connectionId=void 0,this._connectionState="Disconnected",this._connectionStarted){this._connectionStarted=!1;try{this.onclose&&this.onclose(e)}catch(t){this._logger.log(lt.Error,`HttpConnection.onclose(${e}) threw error '${t}'.`)}}}else this._logger.log(lt.Debug,`Call to HttpConnection.stopConnection(${e}) was ignored because the connection is already in the disconnected state.`)}_resolveUrl(e){if(0===e.lastIndexOf("https://",0)||0===e.lastIndexOf("http://",0))return e;if(!pt.isBrowser)throw new Error(`Cannot resolve '${e}'.`);const t=window.document.createElement("a");return t.href=e,this._logger.log(lt.Information,`Normalizing '${e}' to '${t.href}'.`),t.href}_resolveNegotiateUrl(e){const t=e.indexOf("?");let n=e.substring(0,-1===t?e.length:t);return"/"!==n[n.length-1]&&(n+="/"),n+="negotiate",n+=-1===t?"":e.substring(t),-1===n.indexOf("negotiateVersion")&&(n+=-1===t?"?":"&",n+="negotiateVersion="+this._negotiateVersion),n}}class Bt{constructor(e){this._transport=e,this._buffer=[],this._executing=!0,this._sendBufferedData=new Mt,this._transportResult=new Mt,this._sendLoopPromise=this._sendLoop()}send(e){return this._bufferData(e),this._transportResult||(this._transportResult=new Mt),this._transportResult.promise}stop(){return this._executing=!1,this._sendBufferedData.resolve(),this._sendLoopPromise}_bufferData(e){if(this._buffer.length&&typeof this._buffer[0]!=typeof e)throw new Error(`Expected data to be of type ${typeof this._buffer} but was of type ${typeof e}`);this._buffer.push(e),this._sendBufferedData.resolve()}async _sendLoop(){for(;;){if(await this._sendBufferedData.promise,!this._executing){this._transportResult&&this._transportResult.reject("Connection stopped.");break}this._sendBufferedData=new Mt;const e=this._transportResult;this._transportResult=void 0;const t="string"==typeof this._buffer[0]?this._buffer.join(""):Bt._concatBuffers(this._buffer);this._buffer.length=0;try{await this._transport.send(t),e.resolve()}catch(t){e.reject(t)}}}static _concatBuffers(e){const t=e.map((e=>e.byteLength)).reduce(((e,t)=>e+t)),n=new Uint8Array(t);let r=0;for(const t of e)n.set(new Uint8Array(t),r),r+=t.byteLength;return n.buffer}}class Mt{constructor(){this.promise=new Promise(((e,t)=>[this._resolver,this._rejecter]=[e,t]))}resolve(){this._resolver()}reject(e){this._rejecter(e)}}class Ot{static write(e){return`${e}${Ot.RecordSeparator}`}static parse(e){if(e[e.length-1]!==Ot.RecordSeparator)throw new Error("Message is incomplete.");const t=e.split(Ot.RecordSeparator);return t.pop(),t}}Ot.RecordSeparatorCode=30,Ot.RecordSeparator=String.fromCharCode(Ot.RecordSeparatorCode);class Ft{writeHandshakeRequest(e){return Ot.write(JSON.stringify(e))}parseHandshakeResponse(e){let t,n;if(gt(e)){const r=new Uint8Array(e),o=r.indexOf(Ot.RecordSeparatorCode);if(-1===o)throw new Error("Message is incomplete.");const i=o+1;t=String.fromCharCode.apply(null,Array.prototype.slice.call(r.slice(0,i))),n=r.byteLength>i?r.slice(i).buffer:null}else{const r=e,o=r.indexOf(Ot.RecordSeparator);if(-1===o)throw new Error("Message is incomplete.");const i=o+1;t=r.substring(0,i),n=r.length>i?r.substring(i):null}const r=Ot.parse(t),o=JSON.parse(r[0]);if(o.type)throw new Error("Expected a handshake response from the server.");return[n,o]}}!function(e){e[e.Invocation=1]="Invocation",e[e.StreamItem=2]="StreamItem",e[e.Completion=3]="Completion",e[e.StreamInvocation=4]="StreamInvocation",e[e.CancelInvocation=5]="CancelInvocation",e[e.Ping=6]="Ping",e[e.Close=7]="Close"}(Rt||(Rt={}));class Ht{constructor(){this.observers=[]}next(e){for(const t of this.observers)t.next(e)}error(e){for(const t of this.observers)t.error&&t.error(e)}complete(){for(const e of this.observers)e.complete&&e.complete()}subscribe(e){return this.observers.push(e),new yt(this,e)}}!function(e){e.Disconnected="Disconnected",e.Connecting="Connecting",e.Connected="Connected",e.Disconnecting="Disconnecting",e.Reconnecting="Reconnecting"}(Pt||(Pt={}));class jt{static create(e,t,n,r,o,i){return new jt(e,t,n,r,o,i)}constructor(e,t,n,r,o,i){this._nextKeepAlive=0,this._freezeEventListener=()=>{this._logger.log(lt.Warning,"The page is being frozen, this will likely lead to the connection being closed and messages being lost. For more information see the docs at https://learn.microsoft.com/aspnet/core/signalr/javascript-client#bsleep")},dt.isRequired(e,"connection"),dt.isRequired(t,"logger"),dt.isRequired(n,"protocol"),this.serverTimeoutInMilliseconds=null!=o?o:3e4,this.keepAliveIntervalInMilliseconds=null!=i?i:15e3,this._logger=t,this._protocol=n,this.connection=e,this._reconnectPolicy=r,this._handshakeProtocol=new Ft,this.connection.onreceive=e=>this._processIncomingData(e),this.connection.onclose=e=>this._connectionClosed(e),this._callbacks={},this._methods={},this._closedCallbacks=[],this._reconnectingCallbacks=[],this._reconnectedCallbacks=[],this._invocationId=0,this._receivedHandshakeResponse=!1,this._connectionState=Pt.Disconnected,this._connectionStarted=!1,this._cachedPingMessage=this._protocol.writeMessage({type:Rt.Ping})}get state(){return this._connectionState}get connectionId(){return this.connection&&this.connection.connectionId||null}get baseUrl(){return this.connection.baseUrl||""}set baseUrl(e){if(this._connectionState!==Pt.Disconnected&&this._connectionState!==Pt.Reconnecting)throw new Error("The HubConnection must be in the Disconnected or Reconnecting state to change the url.");if(!e)throw new Error("The HubConnection url must be a valid url.");this.connection.baseUrl=e}start(){return this._startPromise=this._startWithStateTransitions(),this._startPromise}async _startWithStateTransitions(){if(this._connectionState!==Pt.Disconnected)return Promise.reject(new Error("Cannot start a HubConnection that is not in the 'Disconnected' state."));this._connectionState=Pt.Connecting,this._logger.log(lt.Debug,"Starting HubConnection.");try{await this._startInternal(),pt.isBrowser&&window.document.addEventListener("freeze",this._freezeEventListener),this._connectionState=Pt.Connected,this._connectionStarted=!0,this._logger.log(lt.Debug,"HubConnection connected successfully.")}catch(e){return this._connectionState=Pt.Disconnected,this._logger.log(lt.Debug,`HubConnection failed to start successfully because of error '${e}'.`),Promise.reject(e)}}async _startInternal(){this._stopDuringStartError=void 0,this._receivedHandshakeResponse=!1;const e=new Promise(((e,t)=>{this._handshakeResolver=e,this._handshakeRejecter=t}));await this.connection.start(this._protocol.transferFormat);try{const t={protocol:this._protocol.name,version:this._protocol.version};if(this._logger.log(lt.Debug,"Sending handshake request."),await this._sendMessage(this._handshakeProtocol.writeHandshakeRequest(t)),this._logger.log(lt.Information,`Using HubProtocol '${this._protocol.name}'.`),this._cleanupTimeout(),this._resetTimeoutPeriod(),this._resetKeepAliveInterval(),await e,this._stopDuringStartError)throw this._stopDuringStartError;this.connection.features.inherentKeepAlive||await this._sendMessage(this._cachedPingMessage)}catch(e){throw this._logger.log(lt.Debug,`Hub handshake failed with error '${e}' during start(). Stopping HubConnection.`),this._cleanupTimeout(),this._cleanupPingTimer(),await this.connection.stop(e),e}}async stop(){const e=this._startPromise;this._stopPromise=this._stopInternal(),await this._stopPromise;try{await e}catch(e){}}_stopInternal(e){return this._connectionState===Pt.Disconnected?(this._logger.log(lt.Debug,`Call to HubConnection.stop(${e}) ignored because it is already in the disconnected state.`),Promise.resolve()):this._connectionState===Pt.Disconnecting?(this._logger.log(lt.Debug,`Call to HttpConnection.stop(${e}) ignored because the connection is already in the disconnecting state.`),this._stopPromise):(this._connectionState=Pt.Disconnecting,this._logger.log(lt.Debug,"Stopping HubConnection."),this._reconnectDelayHandle?(this._logger.log(lt.Debug,"Connection stopped during reconnect delay. Done reconnecting."),clearTimeout(this._reconnectDelayHandle),this._reconnectDelayHandle=void 0,this._completeClose(),Promise.resolve()):(this._cleanupTimeout(),this._cleanupPingTimer(),this._stopDuringStartError=e||new rt("The connection was stopped before the hub handshake could complete."),this.connection.stop(e)))}stream(e,...t){const[n,r]=this._replaceStreamingParams(t),o=this._createStreamInvocation(e,t,r);let i;const s=new Ht;return s.cancelCallback=()=>{const e=this._createCancelInvocation(o.invocationId);return delete this._callbacks[o.invocationId],i.then((()=>this._sendWithProtocol(e)))},this._callbacks[o.invocationId]=(e,t)=>{t?s.error(t):e&&(e.type===Rt.Completion?e.error?s.error(new Error(e.error)):s.complete():s.next(e.item))},i=this._sendWithProtocol(o).catch((e=>{s.error(e),delete this._callbacks[o.invocationId]})),this._launchStreams(n,i),s}_sendMessage(e){return this._resetKeepAliveInterval(),this.connection.send(e)}_sendWithProtocol(e){return this._sendMessage(this._protocol.writeMessage(e))}send(e,...t){const[n,r]=this._replaceStreamingParams(t),o=this._sendWithProtocol(this._createInvocation(e,t,!0,r));return this._launchStreams(n,o),o}invoke(e,...t){const[n,r]=this._replaceStreamingParams(t),o=this._createInvocation(e,t,!1,r);return new Promise(((e,t)=>{this._callbacks[o.invocationId]=(n,r)=>{r?t(r):n&&(n.type===Rt.Completion?n.error?t(new Error(n.error)):e(n.result):t(new Error(`Unexpected message type: ${n.type}`)))};const r=this._sendWithProtocol(o).catch((e=>{t(e),delete this._callbacks[o.invocationId]}));this._launchStreams(n,r)}))}on(e,t){e&&t&&(e=e.toLowerCase(),this._methods[e]||(this._methods[e]=[]),-1===this._methods[e].indexOf(t)&&this._methods[e].push(t))}off(e,t){if(!e)return;e=e.toLowerCase();const n=this._methods[e];if(n)if(t){const r=n.indexOf(t);-1!==r&&(n.splice(r,1),0===n.length&&delete this._methods[e])}else delete this._methods[e]}onclose(e){e&&this._closedCallbacks.push(e)}onreconnecting(e){e&&this._reconnectingCallbacks.push(e)}onreconnected(e){e&&this._reconnectedCallbacks.push(e)}_processIncomingData(e){if(this._cleanupTimeout(),this._receivedHandshakeResponse||(e=this._processHandshakeResponse(e),this._receivedHandshakeResponse=!0),e){const t=this._protocol.parseMessages(e,this._logger);for(const e of t)switch(e.type){case Rt.Invocation:this._invokeClientMethod(e);break;case Rt.StreamItem:case Rt.Completion:{const t=this._callbacks[e.invocationId];if(t){e.type===Rt.Completion&&delete this._callbacks[e.invocationId];try{t(e)}catch(e){this._logger.log(lt.Error,`Stream callback threw error: ${St(e)}`)}}break}case Rt.Ping:break;case Rt.Close:{this._logger.log(lt.Information,"Close message received from server.");const t=e.error?new Error("Server returned an error on close: "+e.error):void 0;!0===e.allowReconnect?this.connection.stop(t):this._stopPromise=this._stopInternal(t);break}default:this._logger.log(lt.Warning,`Invalid message type: ${e.type}.`)}}this._resetTimeoutPeriod()}_processHandshakeResponse(e){let t,n;try{[n,t]=this._handshakeProtocol.parseHandshakeResponse(e)}catch(e){const t="Error parsing handshake response: "+e;this._logger.log(lt.Error,t);const n=new Error(t);throw this._handshakeRejecter(n),n}if(t.error){const e="Server returned handshake error: "+t.error;this._logger.log(lt.Error,e);const n=new Error(e);throw this._handshakeRejecter(n),n}return this._logger.log(lt.Debug,"Server handshake complete."),this._handshakeResolver(),n}_resetKeepAliveInterval(){this.connection.features.inherentKeepAlive||(this._nextKeepAlive=(new Date).getTime()+this.keepAliveIntervalInMilliseconds,this._cleanupPingTimer())}_resetTimeoutPeriod(){if(!(this.connection.features&&this.connection.features.inherentKeepAlive||(this._timeoutHandle=setTimeout((()=>this.serverTimeout()),this.serverTimeoutInMilliseconds),void 0!==this._pingServerHandle))){let e=this._nextKeepAlive-(new Date).getTime();e<0&&(e=0),this._pingServerHandle=setTimeout((async()=>{if(this._connectionState===Pt.Connected)try{await this._sendMessage(this._cachedPingMessage)}catch{this._cleanupPingTimer()}}),e)}}serverTimeout(){this.connection.stop(new Error("Server timeout elapsed without receiving a message from the server."))}async _invokeClientMethod(e){const t=e.target.toLowerCase(),n=this._methods[t];if(!n)return this._logger.log(lt.Warning,`No client method with the name '${t}' found.`),void(e.invocationId&&(this._logger.log(lt.Warning,`No result given for '${t}' method and invocation ID '${e.invocationId}'.`),await this._sendWithProtocol(this._createCompletionMessage(e.invocationId,"Client didn't provide a result.",null))));const r=n.slice(),o=!!e.invocationId;let i,s,a;for(const n of r)try{const r=i;i=await n.apply(this,e.arguments),o&&i&&r&&(this._logger.log(lt.Error,`Multiple results provided for '${t}'. Sending error to server.`),a=this._createCompletionMessage(e.invocationId,"Client provided multiple results.",null)),s=void 0}catch(e){s=e,this._logger.log(lt.Error,`A callback for the method '${t}' threw error '${e}'.`)}a?await this._sendWithProtocol(a):o?(s?a=this._createCompletionMessage(e.invocationId,`${s}`,null):void 0!==i?a=this._createCompletionMessage(e.invocationId,null,i):(this._logger.log(lt.Warning,`No result given for '${t}' method and invocation ID '${e.invocationId}'.`),a=this._createCompletionMessage(e.invocationId,"Client didn't provide a result.",null)),await this._sendWithProtocol(a)):i&&this._logger.log(lt.Error,`Result given for '${t}' method but server is not expecting a result.`)}_connectionClosed(e){this._logger.log(lt.Debug,`HubConnection.connectionClosed(${e}) called while in state ${this._connectionState}.`),this._stopDuringStartError=this._stopDuringStartError||e||new rt("The underlying connection was closed before the hub handshake could complete."),this._handshakeResolver&&this._handshakeResolver(),this._cancelCallbacksWithError(e||new Error("Invocation canceled due to the underlying connection being closed.")),this._cleanupTimeout(),this._cleanupPingTimer(),this._connectionState===Pt.Disconnecting?this._completeClose(e):this._connectionState===Pt.Connected&&this._reconnectPolicy?this._reconnect(e):this._connectionState===Pt.Connected&&this._completeClose(e)}_completeClose(e){if(this._connectionStarted){this._connectionState=Pt.Disconnected,this._connectionStarted=!1,pt.isBrowser&&window.document.removeEventListener("freeze",this._freezeEventListener);try{this._closedCallbacks.forEach((t=>t.apply(this,[e])))}catch(t){this._logger.log(lt.Error,`An onclose callback called with error '${e}' threw error '${t}'.`)}}}async _reconnect(e){const t=Date.now();let n=0,r=void 0!==e?e:new Error("Attempting to reconnect due to a unknown error."),o=this._getNextRetryDelay(n++,0,r);if(null===o)return this._logger.log(lt.Debug,"Connection not reconnecting because the IRetryPolicy returned null on the first reconnect attempt."),void this._completeClose(e);if(this._connectionState=Pt.Reconnecting,e?this._logger.log(lt.Information,`Connection reconnecting because of error '${e}'.`):this._logger.log(lt.Information,"Connection reconnecting."),0!==this._reconnectingCallbacks.length){try{this._reconnectingCallbacks.forEach((t=>t.apply(this,[e])))}catch(t){this._logger.log(lt.Error,`An onreconnecting callback called with error '${e}' threw error '${t}'.`)}if(this._connectionState!==Pt.Reconnecting)return void this._logger.log(lt.Debug,"Connection left the reconnecting state in onreconnecting callback. Done reconnecting.")}for(;null!==o;){if(this._logger.log(lt.Information,`Reconnect attempt number ${n} will start in ${o} ms.`),await new Promise((e=>{this._reconnectDelayHandle=setTimeout(e,o)})),this._reconnectDelayHandle=void 0,this._connectionState!==Pt.Reconnecting)return void this._logger.log(lt.Debug,"Connection left the reconnecting state during reconnect delay. Done reconnecting.");try{if(await this._startInternal(),this._connectionState=Pt.Connected,this._logger.log(lt.Information,"HubConnection reconnected successfully."),0!==this._reconnectedCallbacks.length)try{this._reconnectedCallbacks.forEach((e=>e.apply(this,[this.connection.connectionId])))}catch(e){this._logger.log(lt.Error,`An onreconnected callback called with connectionId '${this.connection.connectionId}; threw error '${e}'.`)}return}catch(e){if(this._logger.log(lt.Information,`Reconnect attempt failed because of error '${e}'.`),this._connectionState!==Pt.Reconnecting)return this._logger.log(lt.Debug,`Connection moved to the '${this._connectionState}' from the reconnecting state during reconnect attempt. Done reconnecting.`),void(this._connectionState===Pt.Disconnecting&&this._completeClose());r=e instanceof Error?e:new Error(e.toString()),o=this._getNextRetryDelay(n++,Date.now()-t,r)}}this._logger.log(lt.Information,`Reconnect retries have been exhausted after ${Date.now()-t} ms and ${n} failed attempts. Connection disconnecting.`),this._completeClose()}_getNextRetryDelay(e,t,n){try{return this._reconnectPolicy.nextRetryDelayInMilliseconds({elapsedMilliseconds:t,previousRetryCount:e,retryReason:n})}catch(n){return this._logger.log(lt.Error,`IRetryPolicy.nextRetryDelayInMilliseconds(${e}, ${t}) threw error '${n}'.`),null}}_cancelCallbacksWithError(e){const t=this._callbacks;this._callbacks={},Object.keys(t).forEach((n=>{const r=t[n];try{r(null,e)}catch(t){this._logger.log(lt.Error,`Stream 'error' callback called with '${e}' threw error: ${St(t)}`)}}))}_cleanupPingTimer(){this._pingServerHandle&&(clearTimeout(this._pingServerHandle),this._pingServerHandle=void 0)}_cleanupTimeout(){this._timeoutHandle&&clearTimeout(this._timeoutHandle)}_createInvocation(e,t,n,r){if(n)return 0!==r.length?{arguments:t,streamIds:r,target:e,type:Rt.Invocation}:{arguments:t,target:e,type:Rt.Invocation};{const n=this._invocationId;return this._invocationId++,0!==r.length?{arguments:t,invocationId:n.toString(),streamIds:r,target:e,type:Rt.Invocation}:{arguments:t,invocationId:n.toString(),target:e,type:Rt.Invocation}}}_launchStreams(e,t){if(0!==e.length){t||(t=Promise.resolve());for(const n in e)e[n].subscribe({complete:()=>{t=t.then((()=>this._sendWithProtocol(this._createCompletionMessage(n))))},error:e=>{let r;r=e instanceof Error?e.message:e&&e.toString?e.toString():"Unknown error",t=t.then((()=>this._sendWithProtocol(this._createCompletionMessage(n,r))))},next:e=>{t=t.then((()=>this._sendWithProtocol(this._createStreamItemMessage(n,e))))}})}}_replaceStreamingParams(e){const t=[],n=[];for(let r=0;r=55296&&o<=56319&&r65535&&(h-=65536,i.push(h>>>10&1023|55296),h=56320|1023&h),i.push(h)}else i.push(a);i.length>=rn&&(s+=String.fromCharCode.apply(String,i),i.length=0)}return i.length>0&&(s+=String.fromCharCode.apply(String,i)),s}var sn,an=Qt?new TextDecoder:null,cn=Qt?"undefined"!=typeof process&&"force"!==(null===(Kt=null===process||void 0===process?void 0:process.env)||void 0===Kt?void 0:Kt.TEXT_DECODER)?200:0:Xt,ln=function(e,t){this.type=e,this.data=t},hn=(sn=function(e,t){return sn=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n])},sn(e,t)},function(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Class extends value "+String(t)+" is not a constructor or null");function n(){this.constructor=e}sn(e,t),e.prototype=null===t?Object.create(t):(n.prototype=t.prototype,new n)}),un=function(e){function t(n){var r=e.call(this,n)||this,o=Object.create(t.prototype);return Object.setPrototypeOf(r,o),Object.defineProperty(r,"name",{configurable:!0,enumerable:!1,value:t.name}),r}return hn(t,e),t}(Error),dn={type:-1,encode:function(e){var t,n,r,o;return e instanceof Date?function(e){var t,n=e.sec,r=e.nsec;if(n>=0&&r>=0&&n<=17179869183){if(0===r&&n<=4294967295){var o=new Uint8Array(4);return(t=new DataView(o.buffer)).setUint32(0,n),o}var i=n/4294967296,s=4294967295&n;return o=new Uint8Array(8),(t=new DataView(o.buffer)).setUint32(0,r<<2|3&i),t.setUint32(4,s),o}return o=new Uint8Array(12),(t=new DataView(o.buffer)).setUint32(0,r),Yt(t,4,n),o}((r=1e6*((t=e.getTime())-1e3*(n=Math.floor(t/1e3))),{sec:n+(o=Math.floor(r/1e9)),nsec:r-1e9*o})):null},decode:function(e){var t=function(e){var t=new DataView(e.buffer,e.byteOffset,e.byteLength);switch(e.byteLength){case 4:return{sec:t.getUint32(0),nsec:0};case 8:var n=t.getUint32(0);return{sec:4294967296*(3&n)+t.getUint32(4),nsec:n>>>2};case 12:return{sec:Gt(t,4),nsec:t.getUint32(0)};default:throw new un("Unrecognized data size for timestamp (expected 4, 8, or 12): ".concat(e.length))}}(e);return new Date(1e3*t.sec+t.nsec/1e6)}},pn=function(){function e(){this.builtInEncoders=[],this.builtInDecoders=[],this.encoders=[],this.decoders=[],this.register(dn)}return e.prototype.register=function(e){var t=e.type,n=e.encode,r=e.decode;if(t>=0)this.encoders[t]=n,this.decoders[t]=r;else{var o=1+t;this.builtInEncoders[o]=n,this.builtInDecoders[o]=r}},e.prototype.tryToEncode=function(e,t){for(var n=0;nthis.maxDepth)throw new Error("Too deep objects in depth ".concat(t));null==e?this.encodeNil():"boolean"==typeof e?this.encodeBoolean(e):"number"==typeof e?this.encodeNumber(e):"string"==typeof e?this.encodeString(e):this.encodeObject(e,t)},e.prototype.ensureBufferSizeToWrite=function(e){var t=this.pos+e;this.view.byteLength=0?e<128?this.writeU8(e):e<256?(this.writeU8(204),this.writeU8(e)):e<65536?(this.writeU8(205),this.writeU16(e)):e<4294967296?(this.writeU8(206),this.writeU32(e)):(this.writeU8(207),this.writeU64(e)):e>=-32?this.writeU8(224|e+32):e>=-128?(this.writeU8(208),this.writeI8(e)):e>=-32768?(this.writeU8(209),this.writeI16(e)):e>=-2147483648?(this.writeU8(210),this.writeI32(e)):(this.writeU8(211),this.writeI64(e)):this.forceFloat32?(this.writeU8(202),this.writeF32(e)):(this.writeU8(203),this.writeF64(e))},e.prototype.writeStringHeader=function(e){if(e<32)this.writeU8(160+e);else if(e<256)this.writeU8(217),this.writeU8(e);else if(e<65536)this.writeU8(218),this.writeU16(e);else{if(!(e<4294967296))throw new Error("Too long string: ".concat(e," bytes in UTF-8"));this.writeU8(219),this.writeU32(e)}},e.prototype.encodeString=function(e){if(e.length>tn){var t=Zt(e);this.ensureBufferSizeToWrite(5+t),this.writeStringHeader(t),nn(e,this.bytes,this.pos),this.pos+=t}else t=Zt(e),this.ensureBufferSizeToWrite(5+t),this.writeStringHeader(t),function(e,t,n){for(var r=e.length,o=n,i=0;i>6&31|192;else{if(s>=55296&&s<=56319&&i>12&15|224,t[o++]=s>>6&63|128):(t[o++]=s>>18&7|240,t[o++]=s>>12&63|128,t[o++]=s>>6&63|128)}t[o++]=63&s|128}else t[o++]=s}}(e,this.bytes,this.pos),this.pos+=t},e.prototype.encodeObject=function(e,t){var n=this.extensionCodec.tryToEncode(e,this.context);if(null!=n)this.encodeExtension(n);else if(Array.isArray(e))this.encodeArray(e,t);else if(ArrayBuffer.isView(e))this.encodeBinary(e);else{if("object"!=typeof e)throw new Error("Unrecognized object: ".concat(Object.prototype.toString.apply(e)));this.encodeMap(e,t)}},e.prototype.encodeBinary=function(e){var t=e.byteLength;if(t<256)this.writeU8(196),this.writeU8(t);else if(t<65536)this.writeU8(197),this.writeU16(t);else{if(!(t<4294967296))throw new Error("Too large binary: ".concat(t));this.writeU8(198),this.writeU32(t)}var n=fn(e);this.writeU8a(n)},e.prototype.encodeArray=function(e,t){var n=e.length;if(n<16)this.writeU8(144+n);else if(n<65536)this.writeU8(220),this.writeU16(n);else{if(!(n<4294967296))throw new Error("Too large array: ".concat(n));this.writeU8(221),this.writeU32(n)}for(var r=0,o=e;r0&&e<=this.maxKeyLength},e.prototype.find=function(e,t,n){e:for(var r=0,o=this.caches[n-1];r=this.maxLengthPerKey?n[Math.random()*n.length|0]=r:n.push(r)},e.prototype.decode=function(e,t,n){var r=this.find(e,t,n);if(null!=r)return this.hit++,r;this.miss++;var o=on(e,t,n),i=Uint8Array.prototype.slice.call(e,t,t+n);return this.store(i,o),o},e}(),kn=function(e,t){var n,r,o,i,s={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return i={next:a(0),throw:a(1),return:a(2)},"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function a(i){return function(a){return function(i){if(n)throw new TypeError("Generator is already executing.");for(;s;)try{if(n=1,r&&(o=2&i[0]?r.return:i[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,i[1])).done)return o;switch(r=0,o&&(i=[2&i[0],o.value]),i[0]){case 0:case 1:o=i;break;case 4:return s.label++,{value:i[1],done:!1};case 5:s.label++,r=i[1],i=[0];continue;case 7:i=s.ops.pop(),s.trys.pop();continue;default:if(!((o=(o=s.trys).length>0&&o[o.length-1])||6!==i[0]&&2!==i[0])){s=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]1||a(e,t)}))})}function a(e,t){try{(n=o[e](t)).value instanceof xn?Promise.resolve(n.value.v).then(c,l):h(i[0][2],n)}catch(e){h(i[0][3],e)}var n}function c(e){a("next",e)}function l(e){a("throw",e)}function h(e,t){e(t),i.shift(),i.length&&a(i[0][0],i[0][1])}},Rn=-1,Pn=new DataView(new ArrayBuffer(0)),Un=new Uint8Array(Pn.buffer),An=function(){try{Pn.getInt8(0)}catch(e){return e.constructor}throw new Error("never reached")}(),Nn=new An("Insufficient data"),Ln=new In,$n=function(){function e(e,t,n,r,o,i,s,a){void 0===e&&(e=pn.defaultCodec),void 0===t&&(t=void 0),void 0===n&&(n=Xt),void 0===r&&(r=Xt),void 0===o&&(o=Xt),void 0===i&&(i=Xt),void 0===s&&(s=Xt),void 0===a&&(a=Ln),this.extensionCodec=e,this.context=t,this.maxStrLength=n,this.maxBinLength=r,this.maxArrayLength=o,this.maxMapLength=i,this.maxExtLength=s,this.keyDecoder=a,this.totalPos=0,this.pos=0,this.view=Pn,this.bytes=Un,this.headByte=Rn,this.stack=[]}return e.prototype.reinitializeState=function(){this.totalPos=0,this.headByte=Rn,this.stack.length=0},e.prototype.setBuffer=function(e){this.bytes=fn(e),this.view=function(e){if(e instanceof ArrayBuffer)return new DataView(e);var t=fn(e);return new DataView(t.buffer,t.byteOffset,t.byteLength)}(this.bytes),this.pos=0},e.prototype.appendBuffer=function(e){if(this.headByte!==Rn||this.hasRemaining(1)){var t=this.bytes.subarray(this.pos),n=fn(e),r=new Uint8Array(t.length+n.length);r.set(t),r.set(n,t.length),this.setBuffer(r)}else this.setBuffer(e)},e.prototype.hasRemaining=function(e){return this.view.byteLength-this.pos>=e},e.prototype.createExtraByteError=function(e){var t=this.view,n=this.pos;return new RangeError("Extra ".concat(t.byteLength-n," of ").concat(t.byteLength," byte(s) found at buffer[").concat(e,"]"))},e.prototype.decode=function(e){this.reinitializeState(),this.setBuffer(e);var t=this.doDecodeSync();if(this.hasRemaining(1))throw this.createExtraByteError(this.pos);return t},e.prototype.decodeMulti=function(e){return kn(this,(function(t){switch(t.label){case 0:this.reinitializeState(),this.setBuffer(e),t.label=1;case 1:return this.hasRemaining(1)?[4,this.doDecodeSync()]:[3,3];case 2:return t.sent(),[3,1];case 3:return[2]}}))},e.prototype.decodeAsync=function(e){var t,n,r,o,i,s,a;return i=this,void 0,a=function(){var i,s,a,c,l,h,u,d;return kn(this,(function(p){switch(p.label){case 0:i=!1,p.label=1;case 1:p.trys.push([1,6,7,12]),t=Tn(e),p.label=2;case 2:return[4,t.next()];case 3:if((n=p.sent()).done)return[3,5];if(a=n.value,i)throw this.createExtraByteError(this.totalPos);this.appendBuffer(a);try{s=this.doDecodeSync(),i=!0}catch(e){if(!(e instanceof An))throw e}this.totalPos+=this.pos,p.label=4;case 4:return[3,2];case 5:return[3,12];case 6:return c=p.sent(),r={error:c},[3,12];case 7:return p.trys.push([7,,10,11]),n&&!n.done&&(o=t.return)?[4,o.call(t)]:[3,9];case 8:p.sent(),p.label=9;case 9:return[3,11];case 10:if(r)throw r.error;return[7];case 11:return[7];case 12:if(i){if(this.hasRemaining(1))throw this.createExtraByteError(this.totalPos);return[2,s]}throw h=(l=this).headByte,u=l.pos,d=l.totalPos,new RangeError("Insufficient data in parsing ".concat(wn(h)," at ").concat(d," (").concat(u," in the current buffer)"))}}))},new((s=void 0)||(s=Promise))((function(e,t){function n(e){try{o(a.next(e))}catch(e){t(e)}}function r(e){try{o(a.throw(e))}catch(e){t(e)}}function o(t){var o;t.done?e(t.value):(o=t.value,o instanceof s?o:new s((function(e){e(o)}))).then(n,r)}o((a=a.apply(i,[])).next())}))},e.prototype.decodeArrayStream=function(e){return this.decodeMultiAsync(e,!0)},e.prototype.decodeStream=function(e){return this.decodeMultiAsync(e,!1)},e.prototype.decodeMultiAsync=function(e,t){return Dn(this,arguments,(function(){var n,r,o,i,s,a,c,l,h;return kn(this,(function(u){switch(u.label){case 0:n=t,r=-1,u.label=1;case 1:u.trys.push([1,13,14,19]),o=Tn(e),u.label=2;case 2:return[4,xn(o.next())];case 3:if((i=u.sent()).done)return[3,12];if(s=i.value,t&&0===r)throw this.createExtraByteError(this.totalPos);this.appendBuffer(s),n&&(r=this.readArraySize(),n=!1,this.complete()),u.label=4;case 4:u.trys.push([4,9,,10]),u.label=5;case 5:return[4,xn(this.doDecodeSync())];case 6:return[4,u.sent()];case 7:return u.sent(),0==--r?[3,8]:[3,5];case 8:return[3,10];case 9:if(!((a=u.sent())instanceof An))throw a;return[3,10];case 10:this.totalPos+=this.pos,u.label=11;case 11:return[3,2];case 12:return[3,19];case 13:return c=u.sent(),l={error:c},[3,19];case 14:return u.trys.push([14,,17,18]),i&&!i.done&&(h=o.return)?[4,xn(h.call(o))]:[3,16];case 15:u.sent(),u.label=16;case 16:return[3,18];case 17:if(l)throw l.error;return[7];case 18:return[7];case 19:return[2]}}))}))},e.prototype.doDecodeSync=function(){e:for(;;){var e=this.readHeadByte(),t=void 0;if(e>=224)t=e-256;else if(e<192)if(e<128)t=e;else if(e<144){if(0!=(r=e-128)){this.pushMapState(r),this.complete();continue e}t={}}else if(e<160){if(0!=(r=e-144)){this.pushArrayState(r),this.complete();continue e}t=[]}else{var n=e-160;t=this.decodeUtf8String(n,0)}else if(192===e)t=null;else if(194===e)t=!1;else if(195===e)t=!0;else if(202===e)t=this.readF32();else if(203===e)t=this.readF64();else if(204===e)t=this.readU8();else if(205===e)t=this.readU16();else if(206===e)t=this.readU32();else if(207===e)t=this.readU64();else if(208===e)t=this.readI8();else if(209===e)t=this.readI16();else if(210===e)t=this.readI32();else if(211===e)t=this.readI64();else if(217===e)n=this.lookU8(),t=this.decodeUtf8String(n,1);else if(218===e)n=this.lookU16(),t=this.decodeUtf8String(n,2);else if(219===e)n=this.lookU32(),t=this.decodeUtf8String(n,4);else if(220===e){if(0!==(r=this.readU16())){this.pushArrayState(r),this.complete();continue e}t=[]}else if(221===e){if(0!==(r=this.readU32())){this.pushArrayState(r),this.complete();continue e}t=[]}else if(222===e){if(0!==(r=this.readU16())){this.pushMapState(r),this.complete();continue e}t={}}else if(223===e){if(0!==(r=this.readU32())){this.pushMapState(r),this.complete();continue e}t={}}else if(196===e){var r=this.lookU8();t=this.decodeBinary(r,1)}else if(197===e)r=this.lookU16(),t=this.decodeBinary(r,2);else if(198===e)r=this.lookU32(),t=this.decodeBinary(r,4);else if(212===e)t=this.decodeExtension(1,0);else if(213===e)t=this.decodeExtension(2,0);else if(214===e)t=this.decodeExtension(4,0);else if(215===e)t=this.decodeExtension(8,0);else if(216===e)t=this.decodeExtension(16,0);else if(199===e)r=this.lookU8(),t=this.decodeExtension(r,1);else if(200===e)r=this.lookU16(),t=this.decodeExtension(r,2);else{if(201!==e)throw new un("Unrecognized type byte: ".concat(wn(e)));r=this.lookU32(),t=this.decodeExtension(r,4)}this.complete();for(var o=this.stack;o.length>0;){var i=o[o.length-1];if(0===i.type){if(i.array[i.position]=t,i.position++,i.position!==i.size)continue e;o.pop(),t=i.array}else{if(1===i.type){if("string"!=(s=typeof t)&&"number"!==s)throw new un("The type of key must be string or number but "+typeof t);if("__proto__"===t)throw new un("The key __proto__ is not allowed");i.key=t,i.type=2;continue e}if(i.map[i.key]=t,i.readCount++,i.readCount!==i.size){i.key=null,i.type=1;continue e}o.pop(),t=i.map}}return t}var s},e.prototype.readHeadByte=function(){return this.headByte===Rn&&(this.headByte=this.readU8()),this.headByte},e.prototype.complete=function(){this.headByte=Rn},e.prototype.readArraySize=function(){var e=this.readHeadByte();switch(e){case 220:return this.readU16();case 221:return this.readU32();default:if(e<160)return e-144;throw new un("Unrecognized array type byte: ".concat(wn(e)))}},e.prototype.pushMapState=function(e){if(e>this.maxMapLength)throw new un("Max length exceeded: map length (".concat(e,") > maxMapLengthLength (").concat(this.maxMapLength,")"));this.stack.push({type:1,size:e,key:null,readCount:0,map:{}})},e.prototype.pushArrayState=function(e){if(e>this.maxArrayLength)throw new un("Max length exceeded: array length (".concat(e,") > maxArrayLength (").concat(this.maxArrayLength,")"));this.stack.push({type:0,size:e,array:new Array(e),position:0})},e.prototype.decodeUtf8String=function(e,t){var n;if(e>this.maxStrLength)throw new un("Max length exceeded: UTF-8 byte length (".concat(e,") > maxStrLength (").concat(this.maxStrLength,")"));if(this.bytes.byteLengthcn?function(e,t,n){var r=e.subarray(t,t+n);return an.decode(r)}(this.bytes,o,e):on(this.bytes,o,e),this.pos+=t+e,r},e.prototype.stateIsMapKey=function(){return this.stack.length>0&&1===this.stack[this.stack.length-1].type},e.prototype.decodeBinary=function(e,t){if(e>this.maxBinLength)throw new un("Max length exceeded: bin length (".concat(e,") > maxBinLength (").concat(this.maxBinLength,")"));if(!this.hasRemaining(e+t))throw Nn;var n=this.pos+t,r=this.bytes.subarray(n,n+e);return this.pos+=t+e,r},e.prototype.decodeExtension=function(e,t){if(e>this.maxExtLength)throw new un("Max length exceeded: ext length (".concat(e,") > maxExtLength (").concat(this.maxExtLength,")"));var n=this.view.getInt8(this.pos+t),r=this.decodeBinary(e,t+1);return this.extensionCodec.decode(r,n,this.context)},e.prototype.lookU8=function(){return this.view.getUint8(this.pos)},e.prototype.lookU16=function(){return this.view.getUint16(this.pos)},e.prototype.lookU32=function(){return this.view.getUint32(this.pos)},e.prototype.readU8=function(){var e=this.view.getUint8(this.pos);return this.pos++,e},e.prototype.readI8=function(){var e=this.view.getInt8(this.pos);return this.pos++,e},e.prototype.readU16=function(){var e=this.view.getUint16(this.pos);return this.pos+=2,e},e.prototype.readI16=function(){var e=this.view.getInt16(this.pos);return this.pos+=2,e},e.prototype.readU32=function(){var e=this.view.getUint32(this.pos);return this.pos+=4,e},e.prototype.readI32=function(){var e=this.view.getInt32(this.pos);return this.pos+=4,e},e.prototype.readU64=function(){var e,t,n=(e=this.view,t=this.pos,4294967296*e.getUint32(t)+e.getUint32(t+4));return this.pos+=8,n},e.prototype.readI64=function(){var e=Gt(this.view,this.pos);return this.pos+=8,e},e.prototype.readF32=function(){var e=this.view.getFloat32(this.pos);return this.pos+=4,e},e.prototype.readF64=function(){var e=this.view.getFloat64(this.pos);return this.pos+=8,e},e}();!function(e){e[e.Invocation=1]="Invocation",e[e.StreamItem=2]="StreamItem",e[e.Completion=3]="Completion",e[e.StreamInvocation=4]="StreamInvocation",e[e.CancelInvocation=5]="CancelInvocation",e[e.Ping=6]="Ping",e[e.Close=7]="Close"}(vn||(vn={})),function(e){e[e.None=0]="None",e[e.WebSockets=1]="WebSockets",e[e.ServerSentEvents=2]="ServerSentEvents",e[e.LongPolling=4]="LongPolling"}(bn||(bn={})),function(e){e[e.Text=1]="Text",e[e.Binary=2]="Binary"}(_n||(_n={}));class Bn{constructor(){}log(e,t){}}Bn.instance=new Bn,function(e){e[e.Trace=0]="Trace",e[e.Debug=1]="Debug",e[e.Information=2]="Information",e[e.Warning=3]="Warning",e[e.Error=4]="Error",e[e.Critical=5]="Critical",e[e.None=6]="None"}(En||(En={}));class Mn{static write(e){let t=e.byteLength||e.length;const n=[];do{let e=127&t;t>>=7,t>0&&(e|=128),n.push(e)}while(t>0);t=e.byteLength||e.length;const r=new Uint8Array(n.length+t);return r.set(n,0),r.set(e,n.length),r.buffer}static parse(e){const t=[],n=new Uint8Array(e),r=[0,7,14,21,28];for(let o=0;o7)throw new Error("Messages bigger than 2GB are not supported.");if(!(n.byteLength>=o+s+a))throw new Error("Incomplete message.");t.push(n.slice?n.slice(o+s,o+s+a):n.subarray(o+s,o+s+a)),o=o+s+a}return t}}const On=new Uint8Array([145,vn.Ping]);class Fn{constructor(e){this.name="messagepack",this.version=1,this.transferFormat=_n.Binary,this._errorResult=1,this._voidResult=2,this._nonVoidResult=3,e=e||{},this._encoder=new yn(e.extensionCodec,e.context,e.maxDepth,e.initialBufferSize,e.sortKeys,e.forceFloat32,e.ignoreUndefined,e.forceIntegerToFloat),this._decoder=new $n(e.extensionCodec,e.context,e.maxStrLength,e.maxBinLength,e.maxArrayLength,e.maxMapLength,e.maxExtLength)}parseMessages(e,t){if(!(n=e)||"undefined"==typeof ArrayBuffer||!(n instanceof ArrayBuffer||n.constructor&&"ArrayBuffer"===n.constructor.name))throw new Error("Invalid input for MessagePack hub protocol. Expected an ArrayBuffer.");var n;null===t&&(t=Bn.instance);const r=Mn.parse(e),o=[];for(const e of r){const n=this._parseMessage(e,t);n&&o.push(n)}return o}writeMessage(e){switch(e.type){case vn.Invocation:return this._writeInvocation(e);case vn.StreamInvocation:return this._writeStreamInvocation(e);case vn.StreamItem:return this._writeStreamItem(e);case vn.Completion:return this._writeCompletion(e);case vn.Ping:return Mn.write(On);case vn.CancelInvocation:return this._writeCancelInvocation(e);default:throw new Error("Invalid message type.")}}_parseMessage(e,t){if(0===e.length)throw new Error("Invalid payload.");const n=this._decoder.decode(e);if(0===n.length||!(n instanceof Array))throw new Error("Invalid payload.");const r=n[0];switch(r){case vn.Invocation:return this._createInvocationMessage(this._readHeaders(n),n);case vn.StreamItem:return this._createStreamItemMessage(this._readHeaders(n),n);case vn.Completion:return this._createCompletionMessage(this._readHeaders(n),n);case vn.Ping:return this._createPingMessage(n);case vn.Close:return this._createCloseMessage(n);default:return t.log(En.Information,"Unknown message type '"+r+"' ignored."),null}}_createCloseMessage(e){if(e.length<2)throw new Error("Invalid payload for Close message.");return{allowReconnect:e.length>=3?e[2]:void 0,error:e[1],type:vn.Close}}_createPingMessage(e){if(e.length<1)throw new Error("Invalid payload for Ping message.");return{type:vn.Ping}}_createInvocationMessage(e,t){if(t.length<5)throw new Error("Invalid payload for Invocation message.");const n=t[2];return n?{arguments:t[4],headers:e,invocationId:n,streamIds:[],target:t[3],type:vn.Invocation}:{arguments:t[4],headers:e,streamIds:[],target:t[3],type:vn.Invocation}}_createStreamItemMessage(e,t){if(t.length<4)throw new Error("Invalid payload for StreamItem message.");return{headers:e,invocationId:t[2],item:t[3],type:vn.StreamItem}}_createCompletionMessage(e,t){if(t.length<4)throw new Error("Invalid payload for Completion message.");const n=t[3];if(n!==this._voidResult&&t.length<5)throw new Error("Invalid payload for Completion message.");let r,o;switch(n){case this._errorResult:r=t[4];break;case this._nonVoidResult:o=t[4]}return{error:r,headers:e,invocationId:t[2],result:o,type:vn.Completion}}_writeInvocation(e){let t;return t=e.streamIds?this._encoder.encode([vn.Invocation,e.headers||{},e.invocationId||null,e.target,e.arguments,e.streamIds]):this._encoder.encode([vn.Invocation,e.headers||{},e.invocationId||null,e.target,e.arguments]),Mn.write(t.slice())}_writeStreamInvocation(e){let t;return t=e.streamIds?this._encoder.encode([vn.StreamInvocation,e.headers||{},e.invocationId,e.target,e.arguments,e.streamIds]):this._encoder.encode([vn.StreamInvocation,e.headers||{},e.invocationId,e.target,e.arguments]),Mn.write(t.slice())}_writeStreamItem(e){const t=this._encoder.encode([vn.StreamItem,e.headers||{},e.invocationId,e.item]);return Mn.write(t.slice())}_writeCompletion(e){const t=e.error?this._errorResult:void 0!==e.result?this._nonVoidResult:this._voidResult;let n;switch(t){case this._errorResult:n=this._encoder.encode([vn.Completion,e.headers||{},e.invocationId,t,e.error]);break;case this._voidResult:n=this._encoder.encode([vn.Completion,e.headers||{},e.invocationId,t]);break;case this._nonVoidResult:n=this._encoder.encode([vn.Completion,e.headers||{},e.invocationId,t,e.result])}return Mn.write(n.slice())}_writeCancelInvocation(e){const t=this._encoder.encode([vn.CancelInvocation,e.headers||{},e.invocationId]);return Mn.write(t.slice())}_readHeaders(e){const t=e[1];if("object"!=typeof t)throw new Error("Invalid headers.");return t}}let Hn=!1;function jn(){const e=document.querySelector("#blazor-error-ui");e&&(e.style.display="block"),Hn||(Hn=!0,document.querySelectorAll("#blazor-error-ui .reload").forEach((e=>{e.onclick=function(e){location.reload(),e.preventDefault()}})),document.querySelectorAll("#blazor-error-ui .dismiss").forEach((e=>{e.onclick=function(e){const t=document.querySelector("#blazor-error-ui");t&&(t.style.display="none"),e.preventDefault()}})))}const Wn="function"==typeof TextDecoder?new TextDecoder("utf-8"):null,zn=Wn?Wn.decode.bind(Wn):function(e){let t=0;const n=e.length,r=[],o=[];for(;t65535&&(o-=65536,r.push(o>>>10&1023|55296),o=56320|1023&o),r.push(o)}r.length>1024&&(o.push(String.fromCharCode.apply(null,r)),r.length=0)}return o.push(String.fromCharCode.apply(null,r)),o.join("")},Jn=Math.pow(2,32),qn=Math.pow(2,21)-1;function Vn(e,t){return e[t]|e[t+1]<<8|e[t+2]<<16|e[t+3]<<24}function Kn(e,t){return e[t]+(e[t+1]<<8)+(e[t+2]<<16)+(e[t+3]<<24>>>0)}function Xn(e,t){const n=Kn(e,t+4);if(n>qn)throw new Error(`Cannot read uint64 with high order part ${n}, because the result would exceed Number.MAX_SAFE_INTEGER.`);return n*Jn+Kn(e,t)}class Yn{constructor(e){this.batchData=e;const t=new er(e);this.arrayRangeReader=new tr(e),this.arrayBuilderSegmentReader=new nr(e),this.diffReader=new Gn(e),this.editReader=new Qn(e,t),this.frameReader=new Zn(e,t)}updatedComponents(){return Vn(this.batchData,this.batchData.length-20)}referenceFrames(){return Vn(this.batchData,this.batchData.length-16)}disposedComponentIds(){return Vn(this.batchData,this.batchData.length-12)}disposedEventHandlerIds(){return Vn(this.batchData,this.batchData.length-8)}updatedComponentsEntry(e,t){const n=e+4*t;return Vn(this.batchData,n)}referenceFramesEntry(e,t){return e+20*t}disposedComponentIdsEntry(e,t){const n=e+4*t;return Vn(this.batchData,n)}disposedEventHandlerIdsEntry(e,t){const n=e+8*t;return Xn(this.batchData,n)}}class Gn{constructor(e){this.batchDataUint8=e}componentId(e){return Vn(this.batchDataUint8,e)}edits(e){return e+4}editsEntry(e,t){return e+16*t}}class Qn{constructor(e,t){this.batchDataUint8=e,this.stringReader=t}editType(e){return Vn(this.batchDataUint8,e)}siblingIndex(e){return Vn(this.batchDataUint8,e+4)}newTreeIndex(e){return Vn(this.batchDataUint8,e+8)}moveToSiblingIndex(e){return Vn(this.batchDataUint8,e+8)}removedAttributeName(e){const t=Vn(this.batchDataUint8,e+12);return this.stringReader.readString(t)}}class Zn{constructor(e,t){this.batchDataUint8=e,this.stringReader=t}frameType(e){return Vn(this.batchDataUint8,e)}subtreeLength(e){return Vn(this.batchDataUint8,e+4)}elementReferenceCaptureId(e){const t=Vn(this.batchDataUint8,e+4);return this.stringReader.readString(t)}componentId(e){return Vn(this.batchDataUint8,e+8)}elementName(e){const t=Vn(this.batchDataUint8,e+8);return this.stringReader.readString(t)}textContent(e){const t=Vn(this.batchDataUint8,e+4);return this.stringReader.readString(t)}markupContent(e){const t=Vn(this.batchDataUint8,e+4);return this.stringReader.readString(t)}attributeName(e){const t=Vn(this.batchDataUint8,e+4);return this.stringReader.readString(t)}attributeValue(e){const t=Vn(this.batchDataUint8,e+8);return this.stringReader.readString(t)}attributeEventHandlerId(e){return Xn(this.batchDataUint8,e+12)}}class er{constructor(e){this.batchDataUint8=e,this.stringTableStartIndex=Vn(e,e.length-4)}readString(e){if(-1===e)return null;{const n=Vn(this.batchDataUint8,this.stringTableStartIndex+4*e),r=function(e,t){let n=0,r=0;for(let o=0;o<4;o++){const i=e[t+o];if(n|=(127&i)<this.nextBatchId)return this.fatalError?(this.logger.log(rr.Debug,`Received a new batch ${e} but errored out on a previous batch ${this.nextBatchId-1}`),void await n.send("OnRenderCompleted",this.nextBatchId-1,this.fatalError.toString())):void this.logger.log(rr.Debug,`Waiting for batch ${this.nextBatchId}. Batch ${e} not processed.`);try{this.nextBatchId++,this.logger.log(rr.Debug,`Applying batch ${e}.`),function(e,t){const n=pe[e];if(!n)throw new Error(`There is no browser renderer with ID ${e}.`);const r=t.arrayRangeReader,o=t.updatedComponents(),i=r.values(o),s=r.count(o),a=t.referenceFrames(),c=r.values(a),l=t.diffReader;for(let e=0;e=this.minLevel){const n=`[${(new Date).toISOString()}] ${rr[e]}: ${t}`;switch(e){case rr.Critical:case rr.Error:console.error(n);break;case rr.Warning:console.warn(n);break;case rr.Information:console.info(n);break;default:console.log(n)}}}}class ar{constructor(e,t){this.circuitId=void 0,this.components=e,this.applicationState=t}reconnect(e){if(!this.circuitId)throw new Error("Circuit host not initialized.");return e.state!==Pt.Connected?Promise.resolve(!1):e.invoke("ConnectCircuit",this.circuitId)}initialize(e){if(this.circuitId)throw new Error(`Circuit host '${this.circuitId}' already initialized.`);this.circuitId=e}async startCircuit(e){if(e.state!==Pt.Connected)return!1;const t=await e.invoke("StartCircuit",Ce.getBaseURI(),Ce.getLocationHref(),JSON.stringify(this.components.map((e=>e.toRecord()))),this.applicationState||"");return!!t&&(this.initialize(t),!0)}resolveElement(e){const t=function(e){const t=f.get(e);if(t)return f.delete(e),t}(e);if(t)return M(t,!0);const n=Number.parseInt(e);if(!Number.isNaN(n))return function(e,t){if(!e.parentNode)throw new Error(`Comment not connected to the DOM ${e.textContent}`);const n=e.parentNode,r=M(n,!0),o=J(r);return Array.from(n.childNodes).forEach((e=>o.push(e))),e[$]=r,t&&(e[B]=t,M(t)),M(e)}(this.components[n].start,this.components[n].end);throw new Error(`Invalid sequence number or identifier '${e}'.`)}}const cr={configureSignalR:e=>{},logLevel:rr.Warning,reconnectionOptions:{maxRetries:8,retryIntervalMilliseconds:2e4,dialogId:"components-reconnect-modal"}};class lr{constructor(e,t,n,r){this.maxRetries=t,this.document=n,this.logger=r,this.addedToDom=!1,this.modal=this.document.createElement("div"),this.modal.id=e,this.maxRetries=t,this.modal.style.cssText=["position: fixed","top: 0","right: 0","bottom: 0","left: 0","z-index: 1050","display: none","overflow: hidden","background-color: #fff","opacity: 0.8","text-align: center","font-weight: bold","transition: visibility 0s linear 500ms"].join(";"),this.message=this.document.createElement("h5"),this.message.style.cssText="margin-top: 20px",this.button=this.document.createElement("button"),this.button.style.cssText="margin:5px auto 5px",this.button.textContent="Retry";const o=this.document.createElement("a");o.addEventListener("click",(()=>location.reload())),o.textContent="reload",this.reloadParagraph=this.document.createElement("p"),this.reloadParagraph.textContent="Alternatively, ",this.reloadParagraph.appendChild(o),this.modal.appendChild(this.message),this.modal.appendChild(this.button),this.modal.appendChild(this.reloadParagraph),this.loader=this.getLoader(),this.message.after(this.loader),this.button.addEventListener("click",(async()=>{this.show();try{await Ke.reconnect()||this.rejected()}catch(e){this.logger.log(rr.Error,e),this.failed()}}))}show(){this.addedToDom||(this.addedToDom=!0,this.document.body.appendChild(this.modal)),this.modal.style.display="block",this.loader.style.display="inline-block",this.button.style.display="none",this.reloadParagraph.style.display="none",this.message.textContent="Attempting to reconnect to the server...",this.modal.style.visibility="hidden",setTimeout((()=>{this.modal.style.visibility="visible"}),0)}update(e){this.message.textContent=`Attempting to reconnect to the server: ${e} of ${this.maxRetries}`}hide(){this.modal.style.display="none"}failed(){this.button.style.display="block",this.reloadParagraph.style.display="none",this.loader.style.display="none";const e=this.document.createTextNode("Reconnection failed. Try "),t=this.document.createElement("a");t.textContent="reloading",t.setAttribute("href",""),t.addEventListener("click",(()=>location.reload()));const n=this.document.createTextNode(" the page if you're unable to reconnect.");this.message.replaceChildren(e,t,n)}rejected(){this.button.style.display="none",this.reloadParagraph.style.display="none",this.loader.style.display="none";const e=this.document.createTextNode("Could not reconnect to the server. "),t=this.document.createElement("a");t.textContent="Reload",t.setAttribute("href",""),t.addEventListener("click",(()=>location.reload()));const n=this.document.createTextNode(" the page to restore functionality.");this.message.replaceChildren(e,t,n)}getLoader(){const e=this.document.createElement("div");return e.style.cssText=["border: 0.3em solid #f3f3f3","border-top: 0.3em solid #3498db","border-radius: 50%","width: 2em","height: 2em","display: inline-block"].join(";"),e.animate([{transform:"rotate(0deg)"},{transform:"rotate(360deg)"}],{duration:2e3,iterations:1/0}),e}}class hr{constructor(e,t,n){this.dialog=e,this.maxRetries=t,this.document=n,this.document=n;const r=this.document.getElementById(hr.MaxRetriesId);r&&(r.innerText=this.maxRetries.toString())}show(){this.removeClasses(),this.dialog.classList.add(hr.ShowClassName)}update(e){const t=this.document.getElementById(hr.CurrentAttemptId);t&&(t.innerText=e.toString())}hide(){this.removeClasses(),this.dialog.classList.add(hr.HideClassName)}failed(){this.removeClasses(),this.dialog.classList.add(hr.FailedClassName)}rejected(){this.removeClasses(),this.dialog.classList.add(hr.RejectedClassName)}removeClasses(){this.dialog.classList.remove(hr.ShowClassName,hr.HideClassName,hr.FailedClassName,hr.RejectedClassName)}}hr.ShowClassName="components-reconnect-show",hr.HideClassName="components-reconnect-hide",hr.FailedClassName="components-reconnect-failed",hr.RejectedClassName="components-reconnect-rejected",hr.MaxRetriesId="components-reconnect-max-retries",hr.CurrentAttemptId="components-reconnect-current-attempt";class ur{constructor(e,t,n){this._currentReconnectionProcess=null,this._logger=e,this._reconnectionDisplay=t,this._reconnectCallback=n||Ke.reconnect}onConnectionDown(e,t){if(!this._reconnectionDisplay){const t=document.getElementById(e.dialogId);this._reconnectionDisplay=t?new hr(t,e.maxRetries,document):new lr(e.dialogId,e.maxRetries,document,this._logger)}this._currentReconnectionProcess||(this._currentReconnectionProcess=new dr(e,this._logger,this._reconnectCallback,this._reconnectionDisplay))}onConnectionUp(){this._currentReconnectionProcess&&(this._currentReconnectionProcess.dispose(),this._currentReconnectionProcess=null)}}class dr{constructor(e,t,n,r){this.logger=t,this.reconnectCallback=n,this.isDisposed=!1,this.reconnectDisplay=r,this.reconnectDisplay.show(),this.attemptPeriodicReconnection(e)}dispose(){this.isDisposed=!0,this.reconnectDisplay.hide()}async attemptPeriodicReconnection(e){for(let t=0;tdr.MaximumFirstRetryInterval?dr.MaximumFirstRetryInterval:e.retryIntervalMilliseconds;if(await this.delay(n),this.isDisposed)break;try{return await this.reconnectCallback()?void 0:void this.reconnectDisplay.rejected()}catch(e){this.logger.log(rr.Error,e)}}this.reconnectDisplay.failed()}delay(e){return new Promise((t=>setTimeout(t,e)))}}dr.MaximumFirstRetryInterval=3e3;const pr=/^\s*Blazor-Component-State:(?[a-zA-Z0-9+/=]+)$/;function fr(e){var t;if(e.nodeType===Node.COMMENT_NODE){const n=e.textContent||"",r=pr.exec(n),o=r&&r.groups&&r.groups.state;return o&&(null===(t=e.parentNode)||void 0===t||t.removeChild(e)),o}if(!e.hasChildNodes())return;const n=e.childNodes;for(let e=0;e.*)$/);function yr(e,t){const n=e.currentElement;if(n&&n.nodeType===Node.COMMENT_NODE&&n.textContent){const r=mr.exec(n.textContent),o=r&&r.groups&&r.groups.descriptor;if(!o)return;try{const r=function(e){const t=JSON.parse(e),{type:n}=t;if("server"!==n&&"webassembly"!==n)throw new Error(`Invalid component type '${n}'.`);return t}(o);switch(t){case"webassembly":return function(e,t,n){const{type:r,assembly:o,typeName:i,parameterDefinitions:s,parameterValues:a,prerenderId:c}=e;if("webassembly"===r){if(!o)throw new Error("assembly must be defined when using a descriptor.");if(!i)throw new Error("typeName must be defined when using a descriptor.");if(c){const e=wr(c,n);if(!e)throw new Error(`Could not find an end component comment for '${t}'`);return{type:r,assembly:o,typeName:i,parameterDefinitions:s&&atob(s),parameterValues:a&&atob(a),start:t,prerenderId:c,end:e}}return{type:r,assembly:o,typeName:i,parameterDefinitions:s&&atob(s),parameterValues:a&&atob(a),start:t}}}(r,n,e);case"server":return function(e,t,n){const{type:r,descriptor:o,sequence:i,prerenderId:s}=e;if("server"===r){if(!o)throw new Error("descriptor must be defined when using a descriptor.");if(void 0===i)throw new Error("sequence must be defined when using a descriptor.");if(!Number.isInteger(i))throw new Error(`Error parsing the sequence '${i}' for component '${JSON.stringify(e)}'`);if(s){const e=wr(s,n);if(!e)throw new Error(`Could not find an end component comment for '${t}'`);return{type:r,sequence:i,descriptor:o,start:t,prerenderId:s,end:e}}return{type:r,sequence:i,descriptor:o,start:t}}}(r,n,e)}}catch(e){throw new Error(`Found malformed component comment at ${n.textContent}`)}}}function wr(e,t){for(;t.next()&&t.currentElement;){const n=t.currentElement;if(n.nodeType!==Node.COMMENT_NODE)continue;if(!n.textContent)continue;const r=mr.exec(n.textContent),o=r&&r[1];if(o)return vr(o,e),n}}function vr(e,t){const n=JSON.parse(e);if(1!==Object.keys(n).length)throw new Error(`Invalid end of component comment: '${e}'`);const r=n.prerenderId;if(!r)throw new Error(`End of component comment must have a value for the prerendered property: '${e}'`);if(r!==t)throw new Error(`End of component comment prerendered property must match the start comment prerender id: '${t}', '${r}'`)}class br{constructor(e){this.childNodes=e,this.currentIndex=-1,this.length=e.length}next(){return this.currentIndex++,this.currentIndexasync function(e,n){const r=function(e){const t=document.baseURI;return t.endsWith("/")?`${t}${e}`:`${t}/${e}`}(n),o=await import(r);if(void 0===o)return;const{beforeStart:i,afterStarted:s}=o;return s&&e.afterStartedCallbacks.push(s),i?i(...t):void 0}(this,e))))}async invokeAfterStartedCallbacks(e){await C,await Promise.all(this.afterStartedCallbacks.map((t=>t(e))))}}let Cr,Ir=!1,kr=!1;async function Tr(e){if(kr)throw new Error("Blazor has already started.");kr=!0;const t=function(e){const t={...cr,...e};return e&&e.reconnectionOptions&&(t.reconnectionOptions={...cr.reconnectionOptions,...e.reconnectionOptions}),t}(e),n=await async function(e){const t=await fetch("_blazor/initializers",{method:"GET",credentials:"include",cache:"no-cache"}),n=await t.json(),r=new Sr;return await r.importInitializersAsync(n,[e]),r}(t),r=new sr(t.logLevel);Ke.reconnect=async e=>{if(Ir)return!1;const n=e||await xr(t,r,s);return await s.reconnect(n)?(t.reconnectionHandler.onConnectionUp(),!0):(r.log(rr.Information,"Reconnection attempt to the circuit was rejected by the server. This may indicate that the associated state is no longer available on the server."),!1)},Ke.defaultReconnectionHandler=new ur(r),t.reconnectionHandler=t.reconnectionHandler||Ke.defaultReconnectionHandler,r.log(rr.Information,"Starting up Blazor server-side application.");const o=function(e,t){return function(e){const t=gr(e,"server"),n=[];for(let e=0;ee.sequence-t.sequence))}(e)}(document),i=fr(document),s=new ar(o,i||"");Ke._internal.navigationManager.listenForNavigationEvents(((e,t,n)=>Cr.send("OnLocationChanged",e,t,n)),((e,t,n,r)=>Cr.send("OnLocationChanging",e,t,n,r))),Ke._internal.forceCloseConnection=()=>Cr.stop(),Ke._internal.sendJSDataStream=(e,t,n)=>function(e,t,n,r){setTimeout((async()=>{let o=5,i=(new Date).valueOf();try{const s=t instanceof Blob?t.size:t.byteLength;let a=0,c=0;for(;a1)await e.send("ReceiveJSDataChunk",n,c,h,null);else{if(!await e.invoke("ReceiveJSDataChunk",n,c,h,null))break;const t=(new Date).valueOf(),r=t-i;i=t,o=Math.max(1,Math.round(500/Math.max(1,r)))}a+=l,c++}}catch(t){await e.send("ReceiveJSDataChunk",n,-1,null,t.toString())}}),0)}(Cr,e,t,n);const a=await xr(t,r,s);if(!await s.startCircuit(a))return void r.log(rr.Error,"Failed to start the circuit.");let c=!1;const l=()=>{if(!c){const e=new FormData,t=s.circuitId;e.append("circuitId",t),c=navigator.sendBeacon("_blazor/disconnect",e)}};Ke.disconnect=l,window.addEventListener("unload",l,{capture:!1,once:!0}),r.log(rr.Information,"Blazor server-side application started."),n.invokeAfterStartedCallbacks(Ke)}async function xr(t,n,r){var o,i;const s=new Fn;s.name="blazorpack";const a=(new Jt).withUrl("_blazor").withHubProtocol(s);t.configureSignalR(a);const c=a.build();c.on("JS.AttachComponent",((e,t)=>function(e,t,n,r){let o=pe[0];o||(o=new ae(0),pe[0]=o),o.attachRootComponentToLogicalElement(n,t,!1)}(0,r.resolveElement(t),e))),c.on("JS.BeginInvokeJS",e.jsCallDispatcher.beginInvokeJSFromDotNet),c.on("JS.EndInvokeDotNet",e.jsCallDispatcher.endInvokeDotNetFromJS),c.on("JS.ReceiveByteArray",e.jsCallDispatcher.receiveByteArray),c.on("JS.BeginTransmitStream",(t=>{const n=new ReadableStream({start(e){c.stream("SendDotNetStreamToJS",t).subscribe({next:t=>e.enqueue(t),complete:()=>e.close(),error:t=>e.error(t)})}});e.jsCallDispatcher.supplyDotNetStream(t,n)}));const l=or.getOrCreate(n);c.on("JS.RenderBatch",((e,t)=>{n.log(rr.Debug,`Received render batch with id ${e} and ${t.byteLength} bytes.`),l.processBatch(e,t,c)})),c.on("JS.EndLocationChanging",Ke._internal.navigationManager.endLocationChanging),c.onclose((e=>!Ir&&t.reconnectionHandler.onConnectionDown(t.reconnectionOptions,e))),c.on("JS.Error",(e=>{Ir=!0,Dr(c,e,n),jn()}));try{await c.start(),Cr=c}catch(e){if(Dr(c,e,n),"FailedToNegotiateWithServerError"===e.errorType)throw e;jn(),e.innerErrors&&(e.innerErrors.some((e=>"UnsupportedTransportError"===e.errorType&&e.transport===xt.WebSockets))?n.log(rr.Error,"Unable to connect, please ensure you are using an updated browser that supports WebSockets."):e.innerErrors.some((e=>"FailedToStartTransportError"===e.errorType&&e.transport===xt.WebSockets))?n.log(rr.Error,"Unable to connect, please ensure WebSockets are available. A VPN or proxy may be blocking the connection."):e.innerErrors.some((e=>"DisabledTransportError"===e.errorType&&e.transport===xt.LongPolling))&&n.log(rr.Error,"Unable to initiate a SignalR connection to the server. This might be because the server is not configured to support WebSockets. For additional details, visit https://aka.ms/blazor-server-websockets-error."))}return(null===(i=null===(o=c.connection)||void 0===o?void 0:o.features)||void 0===i?void 0:i.inherentKeepAlive)&&n.log(rr.Warning,"Failed to connect via WebSockets, using the Long Polling fallback transport. This may be due to a VPN or proxy blocking the connection. To troubleshoot this, visit https://aka.ms/blazor-server-using-fallback-long-polling."),e.attachDispatcher({beginInvokeDotNetFromJS:(e,t,n,r,o)=>{c.send("BeginInvokeDotNetFromJS",e?e.toString():null,t,n,r||0,o)},endInvokeJSFromDotNet:(e,t,n)=>{c.send("EndInvokeJSFromDotNet",e,t,n)},sendByteArray:(e,t)=>{c.send("ReceiveByteArray",e,t)}}),c}function Dr(e,t,n){n.log(rr.Error,t),e&&e.stop()}Ke.start=Tr,document&&document.currentScript&&"false"!==document.currentScript.getAttribute("autostart")&&Tr()})(); \ No newline at end of file +(()=>{"use strict";var e,t,n,r={};r.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),function(e){window.DotNet=e;const t=[],n=new Map,r=new Map,o="__jsObjectId",i="__dotNetObject",s="__byte[]",a="__dotNetStream",c="__jsStreamReferenceLength";class l{constructor(e){this._jsObject=e,this._cachedFunctions=new Map}findFunction(e){const t=this._cachedFunctions.get(e);if(t)return t;let n,r=this._jsObject;if(e.split(".").forEach((t=>{if(!(t in r))throw new Error(`Could not find '${e}' ('${t}' was undefined).`);n=r,r=r[t]})),r instanceof Function)return r=r.bind(n),this._cachedFunctions.set(e,r),r;throw new Error(`The value '${e}' is not a function.`)}getWrappedObject(){return this._jsObject}}const h={},u={0:new l(window)};u[0]._cachedFunctions.set("import",(e=>("string"==typeof e&&e.startsWith("./")&&(e=new URL(e.substr(2),document.baseURI).toString()),import(e))));let d,p=1,f=1,g=null;function m(e){t.push(e)}function y(e){if(e&&"object"==typeof e){u[f]=new l(e);const t={[o]:f};return f++,t}throw new Error(`Cannot create a JSObjectReference from the value '${e}'.`)}function w(e){let t=-1;if(e instanceof ArrayBuffer&&(e=new Uint8Array(e)),e instanceof Blob)t=e.size;else{if(!(e.buffer instanceof ArrayBuffer))throw new Error("Supplied value is not a typed array or blob.");if(void 0===e.byteLength)throw new Error(`Cannot create a JSStreamReference from the value '${e}' as it doesn't have a byteLength.`);t=e.byteLength}const n={[c]:t};try{const t=y(e);n[o]=t[o]}catch(t){throw new Error(`Cannot create a JSStreamReference from the value '${e}'.`)}return n}function v(e){return e?JSON.parse(e,((e,n)=>t.reduce(((t,n)=>n(e,t)),n))):null}function b(e,t,n,r){const o=E();if(o.invokeDotNetFromJS){const i=U(r),s=o.invokeDotNetFromJS(e,t,n,i);return s?v(s):null}throw new Error("The current dispatcher does not support synchronous calls from JS to .NET. Use invokeMethodAsync instead.")}function _(e,t,n,r){if(e&&n)throw new Error(`For instance method calls, assemblyName should be null. Received '${e}'.`);const o=p++,i=new Promise(((e,t)=>{h[o]={resolve:e,reject:t}}));try{const i=U(r);E().beginInvokeDotNetFromJS(o,e,t,n,i)}catch(e){S(o,!1,e)}return i}function E(){if(null!==g)return g;throw new Error("No .NET call dispatcher has been set.")}function S(e,t,n){if(!h.hasOwnProperty(e))throw new Error(`There is no pending async call with ID ${e}.`);const r=h[e];delete h[e],t?r.resolve(n):r.reject(n)}function C(e){return e instanceof Error?`${e.message}\n${e.stack}`:e?e.toString():"null"}function I(e,t){const n=u[t];if(n)return n.findFunction(e);throw new Error(`JS object instance with ID ${t} does not exist (has it been disposed?).`)}function k(e){delete u[e]}e.attachDispatcher=function(e){g=e},e.attachReviver=m,e.invokeMethod=function(e,t,...n){return b(e,t,null,n)},e.invokeMethodAsync=function(e,t,...n){return _(e,t,null,n)},e.createJSObjectReference=y,e.createJSStreamReference=w,e.disposeJSObjectReference=function(e){const t=e&&e[o];"number"==typeof t&&k(t)},function(e){e[e.Default=0]="Default",e[e.JSObjectReference=1]="JSObjectReference",e[e.JSStreamReference=2]="JSStreamReference",e[e.JSVoidResult=3]="JSVoidResult"}(d=e.JSCallResultType||(e.JSCallResultType={})),e.jsCallDispatcher={findJSFunction:I,disposeJSObjectReferenceById:k,invokeJSFromDotNet:(e,t,n,r)=>{const o=R(I(e,r).apply(null,v(t)),n);return null==o?null:U(o)},beginInvokeJSFromDotNet:(e,t,n,r,o)=>{const i=new Promise((e=>{e(I(t,o).apply(null,v(n)))}));e&&i.then((t=>U([e,!0,R(t,r)]))).then((t=>E().endInvokeJSFromDotNet(e,!0,t)),(t=>E().endInvokeJSFromDotNet(e,!1,JSON.stringify([e,!1,C(t)]))))},endInvokeDotNetFromJS:(e,t,n)=>{const r=t?v(n):new Error(n);S(parseInt(e,10),t,r)},receiveByteArray:(e,t)=>{n.set(e,t)},supplyDotNetStream:(e,t)=>{if(r.has(e)){const n=r.get(e);r.delete(e),n.resolve(t)}else{const n=new D;n.resolve(t),r.set(e,n)}}};class T{constructor(e){this._id=e}invokeMethod(e,...t){return b(null,e,this._id,t)}invokeMethodAsync(e,...t){return _(null,e,this._id,t)}dispose(){_(null,"__Dispose",this._id,null).catch((e=>console.error(e)))}serializeAsArg(){return{__dotNetObject:this._id}}}e.DotNetObject=T,m((function(e,t){if(t&&"object"==typeof t){if(t.hasOwnProperty(i))return new T(t[i]);if(t.hasOwnProperty(o)){const e=t[o],n=u[e];if(n)return n.getWrappedObject();throw new Error(`JS object instance with Id '${e}' does not exist. It may have been disposed.`)}if(t.hasOwnProperty(s)){const e=t[s],r=n.get(e);if(void 0===r)throw new Error(`Byte array index '${e}' does not exist.`);return n.delete(e),r}if(t.hasOwnProperty(a))return new x(t[a])}return t}));class x{constructor(e){if(r.has(e))this._streamPromise=r.get(e).streamPromise,r.delete(e);else{const t=new D;r.set(e,t),this._streamPromise=t.streamPromise}}stream(){return this._streamPromise}async arrayBuffer(){return new Response(await this.stream()).arrayBuffer()}}class D{constructor(){this.streamPromise=new Promise(((e,t)=>{this.resolve=e,this.reject=t}))}}function R(e,t){switch(t){case d.Default:return e;case d.JSObjectReference:return y(e);case d.JSStreamReference:return w(e);case d.JSVoidResult:return null;default:throw new Error(`Invalid JS call result type '${t}'.`)}}let P=0;function U(e){return P=0,JSON.stringify(e,A)}function A(e,t){if(t instanceof T)return t.serializeAsArg();if(t instanceof Uint8Array){g.sendByteArray(P,t);const e={[s]:P};return P++,e}return t}}(e||(e={})),function(e){e[e.prependFrame=1]="prependFrame",e[e.removeFrame=2]="removeFrame",e[e.setAttribute=3]="setAttribute",e[e.removeAttribute=4]="removeAttribute",e[e.updateText=5]="updateText",e[e.stepIn=6]="stepIn",e[e.stepOut=7]="stepOut",e[e.updateMarkup=8]="updateMarkup",e[e.permutationListEntry=9]="permutationListEntry",e[e.permutationListEnd=10]="permutationListEnd"}(t||(t={})),function(e){e[e.element=1]="element",e[e.text=2]="text",e[e.attribute=3]="attribute",e[e.component=4]="component",e[e.region=5]="region",e[e.elementReferenceCapture=6]="elementReferenceCapture",e[e.markup=8]="markup"}(n||(n={}));class o{constructor(e,t){this.componentId=e,this.fieldValue=t}static fromEvent(e,t){const n=t.target;if(n instanceof Element){const t=function(e){return e instanceof HTMLInputElement?e.type&&"checkbox"===e.type.toLowerCase()?{value:e.checked}:{value:e.value}:e instanceof HTMLSelectElement||e instanceof HTMLTextAreaElement?{value:e.value}:null}(n);if(t)return new o(e,t.value)}return null}}const i=new Map,s=new Map,a=[];function c(e){return i.get(e)}function l(e){const t=i.get(e);return(null==t?void 0:t.browserEventName)||e}function h(e,t){e.forEach((e=>i.set(e,t)))}function u(e){const t=[];for(let n=0;ne.selected)).map((e=>e.value))}}{const e=function(e){return!!e&&"INPUT"===e.tagName&&"checkbox"===e.getAttribute("type")}(t);return{value:e?!!t.checked:t.value}}}}),h(["copy","cut","paste"],{createEventArgs:e=>({type:e.type})}),h(["drag","dragend","dragenter","dragleave","dragover","dragstart","drop"],{createEventArgs:e=>{return{...d(t=e),dataTransfer:t.dataTransfer?{dropEffect:t.dataTransfer.dropEffect,effectAllowed:t.dataTransfer.effectAllowed,files:Array.from(t.dataTransfer.files).map((e=>e.name)),items:Array.from(t.dataTransfer.items).map((e=>({kind:e.kind,type:e.type}))),types:t.dataTransfer.types}:null};var t}}),h(["focus","blur","focusin","focusout"],{createEventArgs:e=>({type:e.type})}),h(["keydown","keyup","keypress"],{createEventArgs:e=>{return{key:(t=e).key,code:t.code,location:t.location,repeat:t.repeat,ctrlKey:t.ctrlKey,shiftKey:t.shiftKey,altKey:t.altKey,metaKey:t.metaKey,type:t.type};var t}}),h(["contextmenu","click","mouseover","mouseout","mousemove","mousedown","mouseup","mouseleave","mouseenter","dblclick"],{createEventArgs:e=>d(e)}),h(["error"],{createEventArgs:e=>{return{message:(t=e).message,filename:t.filename,lineno:t.lineno,colno:t.colno,type:t.type};var t}}),h(["loadstart","timeout","abort","load","loadend","progress"],{createEventArgs:e=>{return{lengthComputable:(t=e).lengthComputable,loaded:t.loaded,total:t.total,type:t.type};var t}}),h(["touchcancel","touchend","touchmove","touchenter","touchleave","touchstart"],{createEventArgs:e=>{return{detail:(t=e).detail,touches:u(t.touches),targetTouches:u(t.targetTouches),changedTouches:u(t.changedTouches),ctrlKey:t.ctrlKey,shiftKey:t.shiftKey,altKey:t.altKey,metaKey:t.metaKey,type:t.type};var t}}),h(["gotpointercapture","lostpointercapture","pointercancel","pointerdown","pointerenter","pointerleave","pointermove","pointerout","pointerover","pointerup"],{createEventArgs:e=>{return{...d(t=e),pointerId:t.pointerId,width:t.width,height:t.height,pressure:t.pressure,tiltX:t.tiltX,tiltY:t.tiltY,pointerType:t.pointerType,isPrimary:t.isPrimary};var t}}),h(["wheel","mousewheel"],{createEventArgs:e=>{return{...d(t=e),deltaX:t.deltaX,deltaY:t.deltaY,deltaZ:t.deltaZ,deltaMode:t.deltaMode};var t}}),h(["toggle"],{createEventArgs:()=>({})});const p=["date","datetime-local","month","time","week"],f=new Map;let g,m,y=0;const w={async add(e,t,n){if(!n)throw new Error("initialParameters must be an object, even if empty.");const r="__bl-dynamic-root:"+(++y).toString();f.set(r,e);const o=await _().invokeMethodAsync("AddRootComponent",t,r),i=new b(o,m[t]);return await i.setParameters(n),i}};class v{invoke(e){return this._callback(e)}setCallback(t){this._selfJSObjectReference||(this._selfJSObjectReference=e.createJSObjectReference(this)),this._callback=t}getJSObjectReference(){return this._selfJSObjectReference}dispose(){this._selfJSObjectReference&&e.disposeJSObjectReference(this._selfJSObjectReference)}}class b{constructor(e,t){this._jsEventCallbackWrappers=new Map,this._componentId=e;for(const e of t)"eventcallback"===e.type&&this._jsEventCallbackWrappers.set(e.name.toLowerCase(),new v)}setParameters(e){const t={},n=Object.entries(e||{}),r=n.length;for(const[e,r]of n){const n=this._jsEventCallbackWrappers.get(e.toLowerCase());n&&r?(n.setCallback(r),t[e]=n.getJSObjectReference()):t[e]=r}return _().invokeMethodAsync("SetRootComponentParameters",this._componentId,r,t)}async dispose(){if(null!==this._componentId){await _().invokeMethodAsync("RemoveRootComponent",this._componentId),this._componentId=null;for(const e of this._jsEventCallbackWrappers.values())e.dispose()}}}function _(){if(!g)throw new Error("Dynamic root components have not been enabled in this application.");return g}const E=new Map;let S;const C=new Promise((e=>{S=e}));function I(e,t,n){return T(e,t.eventHandlerId,(()=>k(e).invokeMethodAsync("DispatchEventAsync",t,n)))}function k(e){const t=E.get(e);if(!t)throw new Error(`No interop methods are registered for renderer ${e}`);return t}let T=(e,t,n)=>n();const x=N(["abort","blur","canplay","canplaythrough","change","cuechange","durationchange","emptied","ended","error","focus","load","loadeddata","loadedmetadata","loadend","loadstart","mouseenter","mouseleave","pointerenter","pointerleave","pause","play","playing","progress","ratechange","reset","scroll","seeked","seeking","stalled","submit","suspend","timeupdate","toggle","unload","volumechange","waiting","DOMNodeInsertedIntoDocument","DOMNodeRemovedFromDocument"]),D={submit:!0},R=N(["click","dblclick","mousedown","mousemove","mouseup"]);class P{constructor(e){this.browserRendererId=e,this.afterClickCallbacks=[];const t=++P.nextEventDelegatorId;this.eventsCollectionKey=`_blazorEvents_${t}`,this.eventInfoStore=new U(this.onGlobalEvent.bind(this))}setListener(e,t,n,r){const o=this.getEventHandlerInfosForElement(e,!0),i=o.getHandler(t);if(i)this.eventInfoStore.update(i.eventHandlerId,n);else{const i={element:e,eventName:t,eventHandlerId:n,renderingComponentId:r};this.eventInfoStore.add(i),o.setHandler(t,i)}}getHandler(e){return this.eventInfoStore.get(e)}removeListener(e){const t=this.eventInfoStore.remove(e);if(t){const e=t.element,n=this.getEventHandlerInfosForElement(e,!1);n&&n.removeHandler(t.eventName)}}notifyAfterClick(e){this.afterClickCallbacks.push(e),this.eventInfoStore.addGlobalListener("click")}setStopPropagation(e,t,n){this.getEventHandlerInfosForElement(e,!0).stopPropagation(t,n)}setPreventDefault(e,t,n){this.getEventHandlerInfosForElement(e,!0).preventDefault(t,n)}onGlobalEvent(e){if(!(e.target instanceof Element))return;this.dispatchGlobalEventToAllElements(e.type,e);const t=(n=e.type,s.get(n));var n;t&&t.forEach((t=>this.dispatchGlobalEventToAllElements(t,e))),"click"===e.type&&this.afterClickCallbacks.forEach((t=>t(e)))}dispatchGlobalEventToAllElements(e,t){const n=t.composedPath();let r=n.shift(),i=null,s=!1;const a=Object.prototype.hasOwnProperty.call(x,e);let l=!1;for(;r;){const d=r,p=this.getEventHandlerInfosForElement(d,!1);if(p){const n=p.getHandler(e);if(n&&(h=d,u=t.type,!((h instanceof HTMLButtonElement||h instanceof HTMLInputElement||h instanceof HTMLTextAreaElement||h instanceof HTMLSelectElement)&&Object.prototype.hasOwnProperty.call(R,u)&&h.disabled))){if(!s){const n=c(e);i=(null==n?void 0:n.createEventArgs)?n.createEventArgs(t):{},s=!0}Object.prototype.hasOwnProperty.call(D,t.type)&&t.preventDefault(),I(this.browserRendererId,{eventHandlerId:n.eventHandlerId,eventName:e,eventFieldInfo:o.fromEvent(n.renderingComponentId,t)},i)}p.stopPropagation(e)&&(l=!0),p.preventDefault(e)&&t.preventDefault()}r=a||l?void 0:n.shift()}var h,u}getEventHandlerInfosForElement(e,t){return Object.prototype.hasOwnProperty.call(e,this.eventsCollectionKey)?e[this.eventsCollectionKey]:t?e[this.eventsCollectionKey]=new A:null}}P.nextEventDelegatorId=0;class U{constructor(e){this.globalListener=e,this.infosByEventHandlerId={},this.countByEventName={},a.push(this.handleEventNameAliasAdded.bind(this))}add(e){if(this.infosByEventHandlerId[e.eventHandlerId])throw new Error(`Event ${e.eventHandlerId} is already tracked`);this.infosByEventHandlerId[e.eventHandlerId]=e,this.addGlobalListener(e.eventName)}get(e){return this.infosByEventHandlerId[e]}addGlobalListener(e){if(e=l(e),Object.prototype.hasOwnProperty.call(this.countByEventName,e))this.countByEventName[e]++;else{this.countByEventName[e]=1;const t=Object.prototype.hasOwnProperty.call(x,e);document.addEventListener(e,this.globalListener,t)}}update(e,t){if(Object.prototype.hasOwnProperty.call(this.infosByEventHandlerId,t))throw new Error(`Event ${t} is already tracked`);const n=this.infosByEventHandlerId[e];delete this.infosByEventHandlerId[e],n.eventHandlerId=t,this.infosByEventHandlerId[t]=n}remove(e){const t=this.infosByEventHandlerId[e];if(t){delete this.infosByEventHandlerId[e];const n=l(t.eventName);0==--this.countByEventName[n]&&(delete this.countByEventName[n],document.removeEventListener(n,this.globalListener))}return t}handleEventNameAliasAdded(e,t){if(Object.prototype.hasOwnProperty.call(this.countByEventName,e)){const n=this.countByEventName[e];delete this.countByEventName[e],document.removeEventListener(e,this.globalListener),this.addGlobalListener(t),this.countByEventName[t]+=n-1}}}class A{constructor(){this.handlers={},this.preventDefaultFlags=null,this.stopPropagationFlags=null}getHandler(e){return Object.prototype.hasOwnProperty.call(this.handlers,e)?this.handlers[e]:null}setHandler(e,t){this.handlers[e]=t}removeHandler(e){delete this.handlers[e]}preventDefault(e,t){return void 0!==t&&(this.preventDefaultFlags=this.preventDefaultFlags||{},this.preventDefaultFlags[e]=t),!!this.preventDefaultFlags&&this.preventDefaultFlags[e]}stopPropagation(e,t){return void 0!==t&&(this.stopPropagationFlags=this.stopPropagationFlags||{},this.stopPropagationFlags[e]=t),!!this.stopPropagationFlags&&this.stopPropagationFlags[e]}}function N(e){const t={};return e.forEach((e=>{t[e]=!0})),t}const $=G("_blazorLogicalChildren"),L=G("_blazorLogicalParent"),B=G("_blazorLogicalEnd");function M(e,t){if(e.childNodes.length>0&&!t)throw new Error("New logical elements must start empty, or allowExistingContents must be true");return $ in e||(e[$]=[]),e}function O(e,t){const n=document.createComment("!");return F(n,e,t),n}function F(e,t,n){const r=e;if(e instanceof Comment&&J(r)&&J(r).length>0)throw new Error("Not implemented: inserting non-empty logical container");if(j(r))throw new Error("Not implemented: moving existing logical children");const o=J(t);if(n0;)H(n,0)}const r=n;r.parentNode.removeChild(r)}function j(e){return e[L]||null}function W(e,t){return J(e)[t]}function z(e){const t=V(e);return"http://www.w3.org/2000/svg"===t.namespaceURI&&"foreignObject"!==t.tagName}function J(e){return e[$]}function q(e,t){const n=J(e);t.forEach((e=>{e.moveRangeStart=n[e.fromSiblingIndex],e.moveRangeEnd=Y(e.moveRangeStart)})),t.forEach((t=>{const r=document.createComment("marker");t.moveToBeforeMarker=r;const o=n[t.toSiblingIndex+1];o?o.parentNode.insertBefore(r,o):X(r,e)})),t.forEach((e=>{const t=e.moveToBeforeMarker,n=t.parentNode,r=e.moveRangeStart,o=e.moveRangeEnd;let i=r;for(;i;){const e=i.nextSibling;if(n.insertBefore(i,t),i===o)break;i=e}n.removeChild(t)})),t.forEach((e=>{n[e.toSiblingIndex]=e.moveRangeStart}))}function V(e){if(e instanceof Element||e instanceof DocumentFragment)return e;if(e instanceof Comment)return e.parentNode;throw new Error("Not a valid logical element")}function K(e){const t=J(j(e));return t[Array.prototype.indexOf.call(t,e)+1]||null}function X(e,t){if(t instanceof Element||t instanceof DocumentFragment)t.appendChild(e);else{if(!(t instanceof Comment))throw new Error(`Cannot append node because the parent is not a valid logical element. Parent: ${t}`);{const n=K(t);n?n.parentNode.insertBefore(e,n):X(e,j(t))}}}function Y(e){if(e instanceof Element||e instanceof DocumentFragment)return e;const t=K(e);if(t)return t.previousSibling;{const t=j(e);return t instanceof Element||t instanceof DocumentFragment?t.lastChild:Y(t)}}function G(e){return"function"==typeof Symbol?Symbol():e}function Q(e){return`_bl_${e}`}const Z="__internalId";e.attachReviver(((e,t)=>t&&"object"==typeof t&&Object.prototype.hasOwnProperty.call(t,Z)&&"string"==typeof t[Z]?function(e){const t=`[${Q(e)}]`;return document.querySelector(t)}(t[Z]):t));const ee="_blazorDeferredValue",te=document.createElement("template"),ne=document.createElementNS("http://www.w3.org/2000/svg","g"),re={},oe="__internal_",ie="preventDefault_",se="stopPropagation_";class ae{constructor(e){this.rootComponentIds=new Set,this.childComponentLocations={},this.eventDelegator=new P(e),this.eventDelegator.notifyAfterClick((e=>{if(!ge)return;if(0!==e.button||function(e){return e.ctrlKey||e.shiftKey||e.altKey||e.metaKey}(e))return;if(e.defaultPrevented)return;const t=function(e){const t=!window._blazorDisableComposedPath&&e.composedPath&&e.composedPath();if(t){for(let e=0;edocument.baseURI,getLocationHref:()=>location.href,scrollToElement:Ie};function Ie(e){const t=document.getElementById(e)||document.getElementsByName(e)[0];return!!t&&(t.scrollIntoView(),!0)}function ke(e,t,n=!1){const r=$e(e);!t.forceLoad&&Be(r)?Te(r,!1,t.replaceHistoryEntry,t.historyEntryState,n):function(e,t){if(location.href===e){const t=e+"?";history.replaceState(null,"",t),location.replace(e)}else t?location.replace(e):location.href=e}(e,t.replaceHistoryEntry)}async function Te(e,t,n,r,o=!1){if(Re(),function(e){const t=e.indexOf("#");return t>-1&&location.href.replace(location.hash,"")===e.substring(0,t)}(e))!function(e,t,n){xe(e,t,n);const r=e.indexOf("#");r!=e.length-1&&Ie(e.substring(r+1))}(e,n,r);else{if(!o&&ye&&!await Pe(e,r,t))return;fe=!0,xe(e,n,r),await Ue(t)}}function xe(e,t,n){t?history.replaceState({userState:n,_index:we},"",e):(we++,history.pushState({userState:n,_index:we},"",e))}function De(e){return new Promise((t=>{const n=Ee;Ee=()=>{Ee=n,t()},history.go(e)}))}function Re(){Se&&(Se(!1),Se=null)}function Pe(e,t,n){return new Promise((r=>{Re(),_e?(ve++,Se=r,_e(ve,e,t,n)):r(!1)}))}async function Ue(e){var t;be&&await be(location.href,null===(t=history.state)||void 0===t?void 0:t.userState,e)}async function Ae(e){var t,n;Ee&&await Ee(e),we=null!==(n=null===(t=history.state)||void 0===t?void 0:t._index)&&void 0!==n?n:0}let Ne;function $e(e){return Ne=Ne||document.createElement("a"),Ne.href=e,Ne.href}function Le(e,t){return e?e.tagName===t?e:Le(e.parentElement,t):null}function Be(e){const t=(n=document.baseURI).substring(0,n.lastIndexOf("/"));var n;const r=e.charAt(t.length);return e.startsWith(t)&&(""===r||"/"===r||"?"===r||"#"===r)}const Me={focus:function(e,t){if(e instanceof HTMLElement)e.focus({preventScroll:t});else{if(!(e instanceof SVGElement))throw new Error("Unable to focus an invalid element.");if(!e.hasAttribute("tabindex"))throw new Error("Unable to focus an SVG element that does not have a tabindex.");e.focus({preventScroll:t})}},focusBySelector:function(e,t){const n=document.querySelector(e);n&&(n.hasAttribute("tabindex")||(n.tabIndex=-1),n.focus({preventScroll:!0}))}},Oe={init:function(e,t,n,r=50){const o=He(t);(o||document.documentElement).style.overflowAnchor="none";const i=document.createRange();h(n.parentElement)&&(t.style.display="table-row",n.style.display="table-row");const s=new IntersectionObserver((function(r){r.forEach((r=>{var o;if(!r.isIntersecting)return;i.setStartAfter(t),i.setEndBefore(n);const s=i.getBoundingClientRect().height,a=null===(o=r.rootBounds)||void 0===o?void 0:o.height;r.target===t?e.invokeMethodAsync("OnSpacerBeforeVisible",r.intersectionRect.top-r.boundingClientRect.top,s,a):r.target===n&&n.offsetHeight>0&&e.invokeMethodAsync("OnSpacerAfterVisible",r.boundingClientRect.bottom-r.intersectionRect.bottom,s,a)}))}),{root:o,rootMargin:`${r}px`});s.observe(t),s.observe(n);const a=l(t),c=l(n);function l(e){const t={attributes:!0},n=new MutationObserver(((n,r)=>{h(e.parentElement)&&(r.disconnect(),e.style.display="table-row",r.observe(e,t)),s.unobserve(e),s.observe(e)}));return n.observe(e,t),n}function h(e){return null!==e&&(e instanceof HTMLTableElement&&""===e.style.display||"table"===e.style.display||e instanceof HTMLTableSectionElement&&""===e.style.display||"table-row-group"===e.style.display)}Fe[e._id]={intersectionObserver:s,mutationObserverBefore:a,mutationObserverAfter:c}},dispose:function(e){const t=Fe[e._id];t&&(t.intersectionObserver.disconnect(),t.mutationObserverBefore.disconnect(),t.mutationObserverAfter.disconnect(),e.dispose(),delete Fe[e._id])}},Fe={};function He(e){return e&&e!==document.body&&e!==document.documentElement?"visible"!==getComputedStyle(e).overflowY?e:He(e.parentElement):null}const je={getAndRemoveExistingTitle:function(){var e;const t=document.head?document.head.getElementsByTagName("title"):[];if(0===t.length)return null;let n=null;for(let r=t.length-1;r>=0;r--){const o=t[r],i=o.previousSibling;i instanceof Comment&&null!==j(i)||(null===n&&(n=o.textContent),null===(e=o.parentNode)||void 0===e||e.removeChild(o))}return n}},We={init:function(e,t){t._blazorInputFileNextFileId=0,t.addEventListener("click",(function(){t.value=""})),t.addEventListener("change",(function(){t._blazorFilesById={};const n=Array.prototype.map.call(t.files,(function(e){const n={id:++t._blazorInputFileNextFileId,lastModified:new Date(e.lastModified).toISOString(),name:e.name,size:e.size,contentType:e.type,readPromise:void 0,arrayBuffer:void 0,blob:e};return t._blazorFilesById[n.id]=n,n}));e.invokeMethodAsync("NotifyChange",n)}))},toImageFile:async function(e,t,n,r,o){const i=ze(e,t),s=await new Promise((function(e){const t=new Image;t.onload=function(){URL.revokeObjectURL(t.src),e(t)},t.onerror=function(){t.onerror=null,URL.revokeObjectURL(t.src)},t.src=URL.createObjectURL(i.blob)})),a=await new Promise((function(e){var t;const i=Math.min(1,r/s.width),a=Math.min(1,o/s.height),c=Math.min(i,a),l=document.createElement("canvas");l.width=Math.round(s.width*c),l.height=Math.round(s.height*c),null===(t=l.getContext("2d"))||void 0===t||t.drawImage(s,0,0,l.width,l.height),l.toBlob(e,n)})),c={id:++e._blazorInputFileNextFileId,lastModified:i.lastModified,name:i.name,size:(null==a?void 0:a.size)||0,contentType:n,blob:a||i.blob};return e._blazorFilesById[c.id]=c,c},readFileData:async function(e,t){return ze(e,t).blob}};function ze(e,t){const n=e._blazorFilesById[t];if(!n)throw new Error(`There is no file with ID ${t}. The file list may have changed. See https://aka.ms/aspnet/blazor-input-file-multiple-selections.`);return n}const Je=new Set,qe={enableNavigationPrompt:function(e){0===Je.size&&window.addEventListener("beforeunload",Ve),Je.add(e)},disableNavigationPrompt:function(e){Je.delete(e),0===Je.size&&window.removeEventListener("beforeunload",Ve)}};function Ve(e){e.preventDefault(),e.returnValue=!0}async function Ke(e,t,n){return e instanceof Blob?await async function(e,t,n){const r=e.slice(t,t+n),o=await r.arrayBuffer();return new Uint8Array(o)}(e,t,n):function(e,t,n){return new Uint8Array(e.buffer,e.byteOffset+t,n)}(e,t,n)}const Xe=new Map,Ye={navigateTo:function(e,t,n=!1){ke(e,t instanceof Object?t:{forceLoad:t,replaceHistoryEntry:n})},registerCustomEventType:function(e,t){if(!t)throw new Error("The options parameter is required.");if(i.has(e))throw new Error(`The event '${e}' is already registered.`);if(t.browserEventName){const n=s.get(t.browserEventName);n?n.push(e):s.set(t.browserEventName,[e]),a.forEach((n=>n(e,t.browserEventName)))}i.set(e,t)},rootComponents:w,_internal:{navigationManager:Ce,domWrapper:Me,Virtualize:Oe,PageTitle:je,InputFile:We,NavigationLock:qe,getJSDataStreamChunk:Ke,receiveDotNetDataStream:function(t,n,r,o){let i=Xe.get(t);if(!i){const n=new ReadableStream({start(e){Xe.set(t,e),i=e}});e.jsCallDispatcher.supplyDotNetStream(t,n)}o?(i.error(o),Xe.delete(t)):0===r?(i.close(),Xe.delete(t)):i.enqueue(n.length===r?n:n.subarray(0,r))},attachWebRendererInterop:function(t,n,r,o){if(E.has(t))throw new Error(`Interop methods are already registered for renderer ${t}`);E.set(t,n),Object.keys(r).length>0&&function(t,n,r){if(g)throw new Error("Dynamic root components have already been enabled.");g=t,m=n;for(const[t,o]of Object.entries(r)){const r=e.jsCallDispatcher.findJSFunction(t,0);for(const e of o)r(e,n[e])}}(k(t),r,o),S()}}};window.Blazor=Ye;const Ge=[0,2e3,1e4,3e4,null];class Qe{constructor(e){this._retryDelays=void 0!==e?[...e,null]:Ge}nextRetryDelayInMilliseconds(e){return this._retryDelays[e.previousRetryCount]}}class Ze{}Ze.Authorization="Authorization",Ze.Cookie="Cookie";class et{constructor(e,t,n){this.statusCode=e,this.statusText=t,this.content=n}}class tt{get(e,t){return this.send({...t,method:"GET",url:e})}post(e,t){return this.send({...t,method:"POST",url:e})}delete(e,t){return this.send({...t,method:"DELETE",url:e})}getCookieString(e){return""}}class nt extends tt{constructor(e,t){super(),this._innerClient=e,this._accessTokenFactory=t}async send(e){let t=!0;this._accessTokenFactory&&(!this._accessToken||e.url&&e.url.indexOf("/negotiate?")>0)&&(t=!1,this._accessToken=await this._accessTokenFactory()),this._setAuthorizationHeader(e);const n=await this._innerClient.send(e);return t&&401===n.statusCode&&this._accessTokenFactory?(this._accessToken=await this._accessTokenFactory(),this._setAuthorizationHeader(e),await this._innerClient.send(e)):n}_setAuthorizationHeader(e){e.headers||(e.headers={}),this._accessToken?e.headers[Ze.Authorization]=`Bearer ${this._accessToken}`:this._accessTokenFactory&&e.headers[Ze.Authorization]&&delete e.headers[Ze.Authorization]}getCookieString(e){return this._innerClient.getCookieString(e)}}class rt extends Error{constructor(e,t){const n=new.target.prototype;super(`${e}: Status code '${t}'`),this.statusCode=t,this.__proto__=n}}class ot extends Error{constructor(e="A timeout occurred."){const t=new.target.prototype;super(e),this.__proto__=t}}class it extends Error{constructor(e="An abort occurred."){const t=new.target.prototype;super(e),this.__proto__=t}}class st extends Error{constructor(e,t){const n=new.target.prototype;super(e),this.transport=t,this.errorType="UnsupportedTransportError",this.__proto__=n}}class at extends Error{constructor(e,t){const n=new.target.prototype;super(e),this.transport=t,this.errorType="DisabledTransportError",this.__proto__=n}}class ct extends Error{constructor(e,t){const n=new.target.prototype;super(e),this.transport=t,this.errorType="FailedToStartTransportError",this.__proto__=n}}class lt extends Error{constructor(e){const t=new.target.prototype;super(e),this.errorType="FailedToNegotiateWithServerError",this.__proto__=t}}class ht extends Error{constructor(e,t){const n=new.target.prototype;super(e),this.innerErrors=t,this.__proto__=n}}var ut;!function(e){e[e.Trace=0]="Trace",e[e.Debug=1]="Debug",e[e.Information=2]="Information",e[e.Warning=3]="Warning",e[e.Error=4]="Error",e[e.Critical=5]="Critical",e[e.None=6]="None"}(ut||(ut={}));class dt{constructor(){}log(e,t){}}dt.instance=new dt;const pt="0.0.0-DEV_BUILD";class ft{static isRequired(e,t){if(null==e)throw new Error(`The '${t}' argument is required.`)}static isNotEmpty(e,t){if(!e||e.match(/^\s*$/))throw new Error(`The '${t}' argument should not be empty.`)}static isIn(e,t,n){if(!(e in t))throw new Error(`Unknown ${n} value: ${e}.`)}}class gt{static get isBrowser(){return"object"==typeof window&&"object"==typeof window.document}static get isWebWorker(){return"object"==typeof self&&"importScripts"in self}static get isReactNative(){return"object"==typeof window&&void 0===window.document}static get isNode(){return!this.isBrowser&&!this.isWebWorker&&!this.isReactNative}}function mt(e,t){let n="";return yt(e)?(n=`Binary data of length ${e.byteLength}`,t&&(n+=`. Content: '${function(e){const t=new Uint8Array(e);let n="";return t.forEach((e=>{n+=`0x${e<16?"0":""}${e.toString(16)} `})),n.substr(0,n.length-1)}(e)}'`)):"string"==typeof e&&(n=`String data of length ${e.length}`,t&&(n+=`. Content: '${e}'`)),n}function yt(e){return e&&"undefined"!=typeof ArrayBuffer&&(e instanceof ArrayBuffer||e.constructor&&"ArrayBuffer"===e.constructor.name)}async function wt(e,t,n,r,o,i){const s={},[a,c]=_t();s[a]=c,e.log(ut.Trace,`(${t} transport) sending data. ${mt(o,i.logMessageContent)}.`);const l=yt(o)?"arraybuffer":"text",h=await n.post(r,{content:o,headers:{...s,...i.headers},responseType:l,timeout:i.timeout,withCredentials:i.withCredentials});e.log(ut.Trace,`(${t} transport) request complete. Response status: ${h.statusCode}.`)}class vt{constructor(e,t){this._subject=e,this._observer=t}dispose(){const e=this._subject.observers.indexOf(this._observer);e>-1&&this._subject.observers.splice(e,1),0===this._subject.observers.length&&this._subject.cancelCallback&&this._subject.cancelCallback().catch((e=>{}))}}class bt{constructor(e){this._minLevel=e,this.out=console}log(e,t){if(e>=this._minLevel){const n=`[${(new Date).toISOString()}] ${ut[e]}: ${t}`;switch(e){case ut.Critical:case ut.Error:this.out.error(n);break;case ut.Warning:this.out.warn(n);break;case ut.Information:this.out.info(n);break;default:this.out.log(n)}}}}function _t(){let e="X-SignalR-User-Agent";return gt.isNode&&(e="User-Agent"),[e,Et(pt,St(),gt.isNode?"NodeJS":"Browser",Ct())]}function Et(e,t,n,r){let o="Microsoft SignalR/";const i=e.split(".");return o+=`${i[0]}.${i[1]}`,o+=` (${e}; `,o+=t&&""!==t?`${t}; `:"Unknown OS; ",o+=`${n}`,o+=r?`; ${r}`:"; Unknown Runtime Version",o+=")",o}function St(){if(!gt.isNode)return"";switch(process.platform){case"win32":return"Windows NT";case"darwin":return"macOS";case"linux":return"Linux";default:return process.platform}}function Ct(){if(gt.isNode)return process.versions.node}function It(e){return e.stack?e.stack:e.message?e.message:`${e}`}class kt extends tt{constructor(e){if(super(),this._logger=e,"undefined"==typeof fetch){const e=require;this._jar=new(e("tough-cookie").CookieJar),this._fetchType=e("node-fetch"),this._fetchType=e("fetch-cookie")(this._fetchType,this._jar)}else this._fetchType=fetch.bind(function(){if("undefined"!=typeof globalThis)return globalThis;if("undefined"!=typeof self)return self;if("undefined"!=typeof window)return window;if(void 0!==r.g)return r.g;throw new Error("could not find global")}());if("undefined"==typeof AbortController){const e=require;this._abortControllerType=e("abort-controller")}else this._abortControllerType=AbortController}async send(e){if(e.abortSignal&&e.abortSignal.aborted)throw new it;if(!e.method)throw new Error("No method defined.");if(!e.url)throw new Error("No url defined.");const t=new this._abortControllerType;let n;e.abortSignal&&(e.abortSignal.onabort=()=>{t.abort(),n=new it});let r,o=null;if(e.timeout){const r=e.timeout;o=setTimeout((()=>{t.abort(),this._logger.log(ut.Warning,"Timeout from HTTP request."),n=new ot}),r)}""===e.content&&(e.content=void 0),e.content&&(e.headers=e.headers||{},yt(e.content)?e.headers["Content-Type"]="application/octet-stream":e.headers["Content-Type"]="text/plain;charset=UTF-8");try{r=await this._fetchType(e.url,{body:e.content,cache:"no-cache",credentials:!0===e.withCredentials?"include":"same-origin",headers:{"X-Requested-With":"XMLHttpRequest",...e.headers},method:e.method,mode:"cors",redirect:"follow",signal:t.signal})}catch(e){if(n)throw n;throw this._logger.log(ut.Warning,`Error from HTTP request. ${e}.`),e}finally{o&&clearTimeout(o),e.abortSignal&&(e.abortSignal.onabort=null)}if(!r.ok){const e=await Tt(r,"text");throw new rt(e||r.statusText,r.status)}const i=Tt(r,e.responseType),s=await i;return new et(r.status,r.statusText,s)}getCookieString(e){return""}}function Tt(e,t){let n;switch(t){case"arraybuffer":n=e.arrayBuffer();break;case"text":default:n=e.text();break;case"blob":case"document":case"json":throw new Error(`${t} is not supported.`)}return n}class xt extends tt{constructor(e){super(),this._logger=e}send(e){return e.abortSignal&&e.abortSignal.aborted?Promise.reject(new it):e.method?e.url?new Promise(((t,n)=>{const r=new XMLHttpRequest;r.open(e.method,e.url,!0),r.withCredentials=void 0===e.withCredentials||e.withCredentials,r.setRequestHeader("X-Requested-With","XMLHttpRequest"),""===e.content&&(e.content=void 0),e.content&&(yt(e.content)?r.setRequestHeader("Content-Type","application/octet-stream"):r.setRequestHeader("Content-Type","text/plain;charset=UTF-8"));const o=e.headers;o&&Object.keys(o).forEach((e=>{r.setRequestHeader(e,o[e])})),e.responseType&&(r.responseType=e.responseType),e.abortSignal&&(e.abortSignal.onabort=()=>{r.abort(),n(new it)}),e.timeout&&(r.timeout=e.timeout),r.onload=()=>{e.abortSignal&&(e.abortSignal.onabort=null),r.status>=200&&r.status<300?t(new et(r.status,r.statusText,r.response||r.responseText)):n(new rt(r.response||r.responseText||r.statusText,r.status))},r.onerror=()=>{this._logger.log(ut.Warning,`Error from HTTP request. ${r.status}: ${r.statusText}.`),n(new rt(r.statusText,r.status))},r.ontimeout=()=>{this._logger.log(ut.Warning,"Timeout from HTTP request."),n(new ot)},r.send(e.content)})):Promise.reject(new Error("No url defined.")):Promise.reject(new Error("No method defined."))}}class Dt extends tt{constructor(e){if(super(),"undefined"!=typeof fetch)this._httpClient=new kt(e);else{if("undefined"==typeof XMLHttpRequest)throw new Error("No usable HttpClient found.");this._httpClient=new xt(e)}}send(e){return e.abortSignal&&e.abortSignal.aborted?Promise.reject(new it):e.method?e.url?this._httpClient.send(e):Promise.reject(new Error("No url defined.")):Promise.reject(new Error("No method defined."))}getCookieString(e){return this._httpClient.getCookieString(e)}}var Rt,Pt,Ut,At;!function(e){e[e.None=0]="None",e[e.WebSockets=1]="WebSockets",e[e.ServerSentEvents=2]="ServerSentEvents",e[e.LongPolling=4]="LongPolling"}(Rt||(Rt={})),function(e){e[e.Text=1]="Text",e[e.Binary=2]="Binary"}(Pt||(Pt={}));class Nt{constructor(){this._isAborted=!1,this.onabort=null}abort(){this._isAborted||(this._isAborted=!0,this.onabort&&this.onabort())}get signal(){return this}get aborted(){return this._isAborted}}class $t{get pollAborted(){return this._pollAbort.aborted}constructor(e,t,n){this._httpClient=e,this._logger=t,this._pollAbort=new Nt,this._options=n,this._running=!1,this.onreceive=null,this.onclose=null}async connect(e,t){if(ft.isRequired(e,"url"),ft.isRequired(t,"transferFormat"),ft.isIn(t,Pt,"transferFormat"),this._url=e,this._logger.log(ut.Trace,"(LongPolling transport) Connecting."),t===Pt.Binary&&"undefined"!=typeof XMLHttpRequest&&"string"!=typeof(new XMLHttpRequest).responseType)throw new Error("Binary protocols over XmlHttpRequest not implementing advanced features are not supported.");const[n,r]=_t(),o={[n]:r,...this._options.headers},i={abortSignal:this._pollAbort.signal,headers:o,timeout:1e5,withCredentials:this._options.withCredentials};t===Pt.Binary&&(i.responseType="arraybuffer");const s=`${e}&_=${Date.now()}`;this._logger.log(ut.Trace,`(LongPolling transport) polling: ${s}.`);const a=await this._httpClient.get(s,i);200!==a.statusCode?(this._logger.log(ut.Error,`(LongPolling transport) Unexpected response code: ${a.statusCode}.`),this._closeError=new rt(a.statusText||"",a.statusCode),this._running=!1):this._running=!0,this._receiving=this._poll(this._url,i)}async _poll(e,t){try{for(;this._running;)try{const n=`${e}&_=${Date.now()}`;this._logger.log(ut.Trace,`(LongPolling transport) polling: ${n}.`);const r=await this._httpClient.get(n,t);204===r.statusCode?(this._logger.log(ut.Information,"(LongPolling transport) Poll terminated by server."),this._running=!1):200!==r.statusCode?(this._logger.log(ut.Error,`(LongPolling transport) Unexpected response code: ${r.statusCode}.`),this._closeError=new rt(r.statusText||"",r.statusCode),this._running=!1):r.content?(this._logger.log(ut.Trace,`(LongPolling transport) data received. ${mt(r.content,this._options.logMessageContent)}.`),this.onreceive&&this.onreceive(r.content)):this._logger.log(ut.Trace,"(LongPolling transport) Poll timed out, reissuing.")}catch(e){this._running?e instanceof ot?this._logger.log(ut.Trace,"(LongPolling transport) Poll timed out, reissuing."):(this._closeError=e,this._running=!1):this._logger.log(ut.Trace,`(LongPolling transport) Poll errored after shutdown: ${e.message}`)}}finally{this._logger.log(ut.Trace,"(LongPolling transport) Polling complete."),this.pollAborted||this._raiseOnClose()}}async send(e){return this._running?wt(this._logger,"LongPolling",this._httpClient,this._url,e,this._options):Promise.reject(new Error("Cannot send until the transport is connected"))}async stop(){this._logger.log(ut.Trace,"(LongPolling transport) Stopping polling."),this._running=!1,this._pollAbort.abort();try{await this._receiving,this._logger.log(ut.Trace,`(LongPolling transport) sending DELETE request to ${this._url}.`);const e={},[t,n]=_t();e[t]=n;const r={headers:{...e,...this._options.headers},timeout:this._options.timeout,withCredentials:this._options.withCredentials};await this._httpClient.delete(this._url,r),this._logger.log(ut.Trace,"(LongPolling transport) DELETE request sent.")}finally{this._logger.log(ut.Trace,"(LongPolling transport) Stop finished."),this._raiseOnClose()}}_raiseOnClose(){if(this.onclose){let e="(LongPolling transport) Firing onclose event.";this._closeError&&(e+=" Error: "+this._closeError),this._logger.log(ut.Trace,e),this.onclose(this._closeError)}}}class Lt{constructor(e,t,n,r){this._httpClient=e,this._accessToken=t,this._logger=n,this._options=r,this.onreceive=null,this.onclose=null}async connect(e,t){return ft.isRequired(e,"url"),ft.isRequired(t,"transferFormat"),ft.isIn(t,Pt,"transferFormat"),this._logger.log(ut.Trace,"(SSE transport) Connecting."),this._url=e,this._accessToken&&(e+=(e.indexOf("?")<0?"?":"&")+`access_token=${encodeURIComponent(this._accessToken)}`),new Promise(((n,r)=>{let o,i=!1;if(t===Pt.Text){if(gt.isBrowser||gt.isWebWorker)o=new this._options.EventSource(e,{withCredentials:this._options.withCredentials});else{const t=this._httpClient.getCookieString(e),n={};n.Cookie=t;const[r,i]=_t();n[r]=i,o=new this._options.EventSource(e,{withCredentials:this._options.withCredentials,headers:{...n,...this._options.headers}})}try{o.onmessage=e=>{if(this.onreceive)try{this._logger.log(ut.Trace,`(SSE transport) data received. ${mt(e.data,this._options.logMessageContent)}.`),this.onreceive(e.data)}catch(e){return void this._close(e)}},o.onerror=e=>{i?this._close():r(new Error("EventSource failed to connect. The connection could not be found on the server, either the connection ID is not present on the server, or a proxy is refusing/buffering the connection. If you have multiple servers check that sticky sessions are enabled."))},o.onopen=()=>{this._logger.log(ut.Information,`SSE connected to ${this._url}`),this._eventSource=o,i=!0,n()}}catch(e){return void r(e)}}else r(new Error("The Server-Sent Events transport only supports the 'Text' transfer format"))}))}async send(e){return this._eventSource?wt(this._logger,"SSE",this._httpClient,this._url,e,this._options):Promise.reject(new Error("Cannot send until the transport is connected"))}stop(){return this._close(),Promise.resolve()}_close(e){this._eventSource&&(this._eventSource.close(),this._eventSource=void 0,this.onclose&&this.onclose(e))}}class Bt{constructor(e,t,n,r,o,i){this._logger=n,this._accessTokenFactory=t,this._logMessageContent=r,this._webSocketConstructor=o,this._httpClient=e,this.onreceive=null,this.onclose=null,this._headers=i}async connect(e,t){let n;return ft.isRequired(e,"url"),ft.isRequired(t,"transferFormat"),ft.isIn(t,Pt,"transferFormat"),this._logger.log(ut.Trace,"(WebSockets transport) Connecting."),this._accessTokenFactory&&(n=await this._accessTokenFactory()),new Promise(((r,o)=>{let i;e=e.replace(/^http/,"ws");const s=this._httpClient.getCookieString(e);let a=!1;if(gt.isReactNative){const t={},[r,o]=_t();t[r]=o,n&&(t[Ze.Authorization]=`Bearer ${n}`),s&&(t[Ze.Cookie]=s),i=new this._webSocketConstructor(e,void 0,{headers:{...t,...this._headers}})}else n&&(e+=(e.indexOf("?")<0?"?":"&")+`access_token=${encodeURIComponent(n)}`);i||(i=new this._webSocketConstructor(e)),t===Pt.Binary&&(i.binaryType="arraybuffer"),i.onopen=t=>{this._logger.log(ut.Information,`WebSocket connected to ${e}.`),this._webSocket=i,a=!0,r()},i.onerror=e=>{let t=null;t="undefined"!=typeof ErrorEvent&&e instanceof ErrorEvent?e.error:"There was an error with the transport",this._logger.log(ut.Information,`(WebSockets transport) ${t}.`)},i.onmessage=e=>{if(this._logger.log(ut.Trace,`(WebSockets transport) data received. ${mt(e.data,this._logMessageContent)}.`),this.onreceive)try{this.onreceive(e.data)}catch(e){return void this._close(e)}},i.onclose=e=>{if(a)this._close(e);else{let t=null;t="undefined"!=typeof ErrorEvent&&e instanceof ErrorEvent?e.error:"WebSocket failed to connect. The connection could not be found on the server, either the endpoint may not be a SignalR endpoint, the connection ID is not present on the server, or there is a proxy blocking WebSockets. If you have multiple servers check that sticky sessions are enabled.",o(new Error(t))}}}))}send(e){return this._webSocket&&this._webSocket.readyState===this._webSocketConstructor.OPEN?(this._logger.log(ut.Trace,`(WebSockets transport) sending data. ${mt(e,this._logMessageContent)}.`),this._webSocket.send(e),Promise.resolve()):Promise.reject("WebSocket is not in the OPEN state")}stop(){return this._webSocket&&this._close(void 0),Promise.resolve()}_close(e){this._webSocket&&(this._webSocket.onclose=()=>{},this._webSocket.onmessage=()=>{},this._webSocket.onerror=()=>{},this._webSocket.close(),this._webSocket=void 0),this._logger.log(ut.Trace,"(WebSockets transport) socket closed."),this.onclose&&(!this._isCloseEvent(e)||!1!==e.wasClean&&1e3===e.code?e instanceof Error?this.onclose(e):this.onclose():this.onclose(new Error(`WebSocket closed with status code: ${e.code} (${e.reason||"no reason given"}).`)))}_isCloseEvent(e){return e&&"boolean"==typeof e.wasClean&&"number"==typeof e.code}}class Mt{constructor(e,t={}){var n;if(this._stopPromiseResolver=()=>{},this.features={},this._negotiateVersion=1,ft.isRequired(e,"url"),this._logger=void 0===(n=t.logger)?new bt(ut.Information):null===n?dt.instance:void 0!==n.log?n:new bt(n),this.baseUrl=this._resolveUrl(e),(t=t||{}).logMessageContent=void 0!==t.logMessageContent&&t.logMessageContent,"boolean"!=typeof t.withCredentials&&void 0!==t.withCredentials)throw new Error("withCredentials option was not a 'boolean' or 'undefined' value");t.withCredentials=void 0===t.withCredentials||t.withCredentials,t.timeout=void 0===t.timeout?1e5:t.timeout,"undefined"==typeof WebSocket||t.WebSocket||(t.WebSocket=WebSocket),"undefined"==typeof EventSource||t.EventSource||(t.EventSource=EventSource),this._httpClient=new nt(t.httpClient||new Dt(this._logger),t.accessTokenFactory),this._connectionState="Disconnected",this._connectionStarted=!1,this._options=t,this.onreceive=null,this.onclose=null}async start(e){if(e=e||Pt.Binary,ft.isIn(e,Pt,"transferFormat"),this._logger.log(ut.Debug,`Starting connection with transfer format '${Pt[e]}'.`),"Disconnected"!==this._connectionState)return Promise.reject(new Error("Cannot start an HttpConnection that is not in the 'Disconnected' state."));if(this._connectionState="Connecting",this._startInternalPromise=this._startInternal(e),await this._startInternalPromise,"Disconnecting"===this._connectionState){const e="Failed to start the HttpConnection before stop() was called.";return this._logger.log(ut.Error,e),await this._stopPromise,Promise.reject(new it(e))}if("Connected"!==this._connectionState){const e="HttpConnection.startInternal completed gracefully but didn't enter the connection into the connected state!";return this._logger.log(ut.Error,e),Promise.reject(new it(e))}this._connectionStarted=!0}send(e){return"Connected"!==this._connectionState?Promise.reject(new Error("Cannot send data if the connection is not in the 'Connected' State.")):(this._sendQueue||(this._sendQueue=new Ot(this.transport)),this._sendQueue.send(e))}async stop(e){return"Disconnected"===this._connectionState?(this._logger.log(ut.Debug,`Call to HttpConnection.stop(${e}) ignored because the connection is already in the disconnected state.`),Promise.resolve()):"Disconnecting"===this._connectionState?(this._logger.log(ut.Debug,`Call to HttpConnection.stop(${e}) ignored because the connection is already in the disconnecting state.`),this._stopPromise):(this._connectionState="Disconnecting",this._stopPromise=new Promise((e=>{this._stopPromiseResolver=e})),await this._stopInternal(e),void await this._stopPromise)}async _stopInternal(e){this._stopError=e;try{await this._startInternalPromise}catch(e){}if(this.transport){try{await this.transport.stop()}catch(e){this._logger.log(ut.Error,`HttpConnection.transport.stop() threw error '${e}'.`),this._stopConnection()}this.transport=void 0}else this._logger.log(ut.Debug,"HttpConnection.transport is undefined in HttpConnection.stop() because start() failed.")}async _startInternal(e){let t=this.baseUrl;this._accessTokenFactory=this._options.accessTokenFactory,this._httpClient._accessTokenFactory=this._accessTokenFactory;try{if(this._options.skipNegotiation){if(this._options.transport!==Rt.WebSockets)throw new Error("Negotiation can only be skipped when using the WebSocket transport directly.");this.transport=this._constructTransport(Rt.WebSockets),await this._startTransport(t,e)}else{let n=null,r=0;do{if(n=await this._getNegotiationResponse(t),"Disconnecting"===this._connectionState||"Disconnected"===this._connectionState)throw new it("The connection was stopped during negotiation.");if(n.error)throw new Error(n.error);if(n.ProtocolVersion)throw new Error("Detected a connection attempt to an ASP.NET SignalR Server. This client only supports connecting to an ASP.NET Core SignalR Server. See https://aka.ms/signalr-core-differences for details.");if(n.url&&(t=n.url),n.accessToken){const e=n.accessToken;this._accessTokenFactory=()=>e,this._httpClient._accessToken=e,this._httpClient._accessTokenFactory=void 0}r++}while(n.url&&r<100);if(100===r&&n.url)throw new Error("Negotiate redirection limit exceeded.");await this._createTransport(t,this._options.transport,n,e)}this.transport instanceof $t&&(this.features.inherentKeepAlive=!0),"Connecting"===this._connectionState&&(this._logger.log(ut.Debug,"The HttpConnection connected successfully."),this._connectionState="Connected")}catch(e){return this._logger.log(ut.Error,"Failed to start the connection: "+e),this._connectionState="Disconnected",this.transport=void 0,this._stopPromiseResolver(),Promise.reject(e)}}async _getNegotiationResponse(e){const t={},[n,r]=_t();t[n]=r;const o=this._resolveNegotiateUrl(e);this._logger.log(ut.Debug,`Sending negotiation request: ${o}.`);try{const e=await this._httpClient.post(o,{content:"",headers:{...t,...this._options.headers},timeout:this._options.timeout,withCredentials:this._options.withCredentials});if(200!==e.statusCode)return Promise.reject(new Error(`Unexpected status code returned from negotiate '${e.statusCode}'`));const n=JSON.parse(e.content);return(!n.negotiateVersion||n.negotiateVersion<1)&&(n.connectionToken=n.connectionId),n}catch(e){let t="Failed to complete negotiation with the server: "+e;return e instanceof rt&&404===e.statusCode&&(t+=" Either this is not a SignalR endpoint or there is a proxy blocking the connection."),this._logger.log(ut.Error,t),Promise.reject(new lt(t))}}_createConnectUrl(e,t){return t?e+(-1===e.indexOf("?")?"?":"&")+`id=${t}`:e}async _createTransport(e,t,n,r){let o=this._createConnectUrl(e,n.connectionToken);if(this._isITransport(t))return this._logger.log(ut.Debug,"Connection was provided an instance of ITransport, using that directly."),this.transport=t,await this._startTransport(o,r),void(this.connectionId=n.connectionId);const i=[],s=n.availableTransports||[];let a=n;for(const n of s){const s=this._resolveTransportOrError(n,t,r);if(s instanceof Error)i.push(`${n.transport} failed:`),i.push(s);else if(this._isITransport(s)){if(this.transport=s,!a){try{a=await this._getNegotiationResponse(e)}catch(e){return Promise.reject(e)}o=this._createConnectUrl(e,a.connectionToken)}try{return await this._startTransport(o,r),void(this.connectionId=a.connectionId)}catch(e){if(this._logger.log(ut.Error,`Failed to start the transport '${n.transport}': ${e}`),a=void 0,i.push(new ct(`${n.transport} failed: ${e}`,Rt[n.transport])),"Connecting"!==this._connectionState){const e="Failed to select transport before stop() was called.";return this._logger.log(ut.Debug,e),Promise.reject(new it(e))}}}}return i.length>0?Promise.reject(new ht(`Unable to connect to the server with any of the available transports. ${i.join(" ")}`,i)):Promise.reject(new Error("None of the transports supported by the client are supported by the server."))}_constructTransport(e){switch(e){case Rt.WebSockets:if(!this._options.WebSocket)throw new Error("'WebSocket' is not supported in your environment.");return new Bt(this._httpClient,this._accessTokenFactory,this._logger,this._options.logMessageContent,this._options.WebSocket,this._options.headers||{});case Rt.ServerSentEvents:if(!this._options.EventSource)throw new Error("'EventSource' is not supported in your environment.");return new Lt(this._httpClient,this._httpClient._accessToken,this._logger,this._options);case Rt.LongPolling:return new $t(this._httpClient,this._logger,this._options);default:throw new Error(`Unknown transport: ${e}.`)}}_startTransport(e,t){return this.transport.onreceive=this.onreceive,this.transport.onclose=e=>this._stopConnection(e),this.transport.connect(e,t)}_resolveTransportOrError(e,t,n){const r=Rt[e.transport];if(null==r)return this._logger.log(ut.Debug,`Skipping transport '${e.transport}' because it is not supported by this client.`),new Error(`Skipping transport '${e.transport}' because it is not supported by this client.`);if(!function(e,t){return!e||0!=(t&e)}(t,r))return this._logger.log(ut.Debug,`Skipping transport '${Rt[r]}' because it was disabled by the client.`),new at(`'${Rt[r]}' is disabled by the client.`,r);if(!(e.transferFormats.map((e=>Pt[e])).indexOf(n)>=0))return this._logger.log(ut.Debug,`Skipping transport '${Rt[r]}' because it does not support the requested transfer format '${Pt[n]}'.`),new Error(`'${Rt[r]}' does not support ${Pt[n]}.`);if(r===Rt.WebSockets&&!this._options.WebSocket||r===Rt.ServerSentEvents&&!this._options.EventSource)return this._logger.log(ut.Debug,`Skipping transport '${Rt[r]}' because it is not supported in your environment.'`),new st(`'${Rt[r]}' is not supported in your environment.`,r);this._logger.log(ut.Debug,`Selecting transport '${Rt[r]}'.`);try{return this._constructTransport(r)}catch(e){return e}}_isITransport(e){return e&&"object"==typeof e&&"connect"in e}_stopConnection(e){if(this._logger.log(ut.Debug,`HttpConnection.stopConnection(${e}) called while in state ${this._connectionState}.`),this.transport=void 0,e=this._stopError||e,this._stopError=void 0,"Disconnected"!==this._connectionState){if("Connecting"===this._connectionState)throw this._logger.log(ut.Warning,`Call to HttpConnection.stopConnection(${e}) was ignored because the connection is still in the connecting state.`),new Error(`HttpConnection.stopConnection(${e}) was called while the connection is still in the connecting state.`);if("Disconnecting"===this._connectionState&&this._stopPromiseResolver(),e?this._logger.log(ut.Error,`Connection disconnected with error '${e}'.`):this._logger.log(ut.Information,"Connection disconnected."),this._sendQueue&&(this._sendQueue.stop().catch((e=>{this._logger.log(ut.Error,`TransportSendQueue.stop() threw error '${e}'.`)})),this._sendQueue=void 0),this.connectionId=void 0,this._connectionState="Disconnected",this._connectionStarted){this._connectionStarted=!1;try{this.onclose&&this.onclose(e)}catch(t){this._logger.log(ut.Error,`HttpConnection.onclose(${e}) threw error '${t}'.`)}}}else this._logger.log(ut.Debug,`Call to HttpConnection.stopConnection(${e}) was ignored because the connection is already in the disconnected state.`)}_resolveUrl(e){if(0===e.lastIndexOf("https://",0)||0===e.lastIndexOf("http://",0))return e;if(!gt.isBrowser)throw new Error(`Cannot resolve '${e}'.`);const t=window.document.createElement("a");return t.href=e,this._logger.log(ut.Information,`Normalizing '${e}' to '${t.href}'.`),t.href}_resolveNegotiateUrl(e){const t=e.indexOf("?");let n=e.substring(0,-1===t?e.length:t);return"/"!==n[n.length-1]&&(n+="/"),n+="negotiate",n+=-1===t?"":e.substring(t),-1===n.indexOf("negotiateVersion")&&(n+=-1===t?"?":"&",n+="negotiateVersion="+this._negotiateVersion),n}}class Ot{constructor(e){this._transport=e,this._buffer=[],this._executing=!0,this._sendBufferedData=new Ft,this._transportResult=new Ft,this._sendLoopPromise=this._sendLoop()}send(e){return this._bufferData(e),this._transportResult||(this._transportResult=new Ft),this._transportResult.promise}stop(){return this._executing=!1,this._sendBufferedData.resolve(),this._sendLoopPromise}_bufferData(e){if(this._buffer.length&&typeof this._buffer[0]!=typeof e)throw new Error(`Expected data to be of type ${typeof this._buffer} but was of type ${typeof e}`);this._buffer.push(e),this._sendBufferedData.resolve()}async _sendLoop(){for(;;){if(await this._sendBufferedData.promise,!this._executing){this._transportResult&&this._transportResult.reject("Connection stopped.");break}this._sendBufferedData=new Ft;const e=this._transportResult;this._transportResult=void 0;const t="string"==typeof this._buffer[0]?this._buffer.join(""):Ot._concatBuffers(this._buffer);this._buffer.length=0;try{await this._transport.send(t),e.resolve()}catch(t){e.reject(t)}}}static _concatBuffers(e){const t=e.map((e=>e.byteLength)).reduce(((e,t)=>e+t)),n=new Uint8Array(t);let r=0;for(const t of e)n.set(new Uint8Array(t),r),r+=t.byteLength;return n.buffer}}class Ft{constructor(){this.promise=new Promise(((e,t)=>[this._resolver,this._rejecter]=[e,t]))}resolve(){this._resolver()}reject(e){this._rejecter(e)}}class Ht{static write(e){return`${e}${Ht.RecordSeparator}`}static parse(e){if(e[e.length-1]!==Ht.RecordSeparator)throw new Error("Message is incomplete.");const t=e.split(Ht.RecordSeparator);return t.pop(),t}}Ht.RecordSeparatorCode=30,Ht.RecordSeparator=String.fromCharCode(Ht.RecordSeparatorCode);class jt{writeHandshakeRequest(e){return Ht.write(JSON.stringify(e))}parseHandshakeResponse(e){let t,n;if(yt(e)){const r=new Uint8Array(e),o=r.indexOf(Ht.RecordSeparatorCode);if(-1===o)throw new Error("Message is incomplete.");const i=o+1;t=String.fromCharCode.apply(null,Array.prototype.slice.call(r.slice(0,i))),n=r.byteLength>i?r.slice(i).buffer:null}else{const r=e,o=r.indexOf(Ht.RecordSeparator);if(-1===o)throw new Error("Message is incomplete.");const i=o+1;t=r.substring(0,i),n=r.length>i?r.substring(i):null}const r=Ht.parse(t),o=JSON.parse(r[0]);if(o.type)throw new Error("Expected a handshake response from the server.");return[n,o]}}!function(e){e[e.Invocation=1]="Invocation",e[e.StreamItem=2]="StreamItem",e[e.Completion=3]="Completion",e[e.StreamInvocation=4]="StreamInvocation",e[e.CancelInvocation=5]="CancelInvocation",e[e.Ping=6]="Ping",e[e.Close=7]="Close"}(Ut||(Ut={}));class Wt{constructor(){this.observers=[]}next(e){for(const t of this.observers)t.next(e)}error(e){for(const t of this.observers)t.error&&t.error(e)}complete(){for(const e of this.observers)e.complete&&e.complete()}subscribe(e){return this.observers.push(e),new vt(this,e)}}!function(e){e.Disconnected="Disconnected",e.Connecting="Connecting",e.Connected="Connected",e.Disconnecting="Disconnecting",e.Reconnecting="Reconnecting"}(At||(At={}));class zt{static create(e,t,n,r,o,i){return new zt(e,t,n,r,o,i)}constructor(e,t,n,r,o,i){this._nextKeepAlive=0,this._freezeEventListener=()=>{this._logger.log(ut.Warning,"The page is being frozen, this will likely lead to the connection being closed and messages being lost. For more information see the docs at https://learn.microsoft.com/aspnet/core/signalr/javascript-client#bsleep")},ft.isRequired(e,"connection"),ft.isRequired(t,"logger"),ft.isRequired(n,"protocol"),this.serverTimeoutInMilliseconds=null!=o?o:3e4,this.keepAliveIntervalInMilliseconds=null!=i?i:15e3,this._logger=t,this._protocol=n,this.connection=e,this._reconnectPolicy=r,this._handshakeProtocol=new jt,this.connection.onreceive=e=>this._processIncomingData(e),this.connection.onclose=e=>this._connectionClosed(e),this._callbacks={},this._methods={},this._closedCallbacks=[],this._reconnectingCallbacks=[],this._reconnectedCallbacks=[],this._invocationId=0,this._receivedHandshakeResponse=!1,this._connectionState=At.Disconnected,this._connectionStarted=!1,this._cachedPingMessage=this._protocol.writeMessage({type:Ut.Ping})}get state(){return this._connectionState}get connectionId(){return this.connection&&this.connection.connectionId||null}get baseUrl(){return this.connection.baseUrl||""}set baseUrl(e){if(this._connectionState!==At.Disconnected&&this._connectionState!==At.Reconnecting)throw new Error("The HubConnection must be in the Disconnected or Reconnecting state to change the url.");if(!e)throw new Error("The HubConnection url must be a valid url.");this.connection.baseUrl=e}start(){return this._startPromise=this._startWithStateTransitions(),this._startPromise}async _startWithStateTransitions(){if(this._connectionState!==At.Disconnected)return Promise.reject(new Error("Cannot start a HubConnection that is not in the 'Disconnected' state."));this._connectionState=At.Connecting,this._logger.log(ut.Debug,"Starting HubConnection.");try{await this._startInternal(),gt.isBrowser&&window.document.addEventListener("freeze",this._freezeEventListener),this._connectionState=At.Connected,this._connectionStarted=!0,this._logger.log(ut.Debug,"HubConnection connected successfully.")}catch(e){return this._connectionState=At.Disconnected,this._logger.log(ut.Debug,`HubConnection failed to start successfully because of error '${e}'.`),Promise.reject(e)}}async _startInternal(){this._stopDuringStartError=void 0,this._receivedHandshakeResponse=!1;const e=new Promise(((e,t)=>{this._handshakeResolver=e,this._handshakeRejecter=t}));await this.connection.start(this._protocol.transferFormat);try{const t={protocol:this._protocol.name,version:this._protocol.version};if(this._logger.log(ut.Debug,"Sending handshake request."),await this._sendMessage(this._handshakeProtocol.writeHandshakeRequest(t)),this._logger.log(ut.Information,`Using HubProtocol '${this._protocol.name}'.`),this._cleanupTimeout(),this._resetTimeoutPeriod(),this._resetKeepAliveInterval(),await e,this._stopDuringStartError)throw this._stopDuringStartError;this.connection.features.inherentKeepAlive||await this._sendMessage(this._cachedPingMessage)}catch(e){throw this._logger.log(ut.Debug,`Hub handshake failed with error '${e}' during start(). Stopping HubConnection.`),this._cleanupTimeout(),this._cleanupPingTimer(),await this.connection.stop(e),e}}async stop(){const e=this._startPromise;this._stopPromise=this._stopInternal(),await this._stopPromise;try{await e}catch(e){}}_stopInternal(e){return this._connectionState===At.Disconnected?(this._logger.log(ut.Debug,`Call to HubConnection.stop(${e}) ignored because it is already in the disconnected state.`),Promise.resolve()):this._connectionState===At.Disconnecting?(this._logger.log(ut.Debug,`Call to HttpConnection.stop(${e}) ignored because the connection is already in the disconnecting state.`),this._stopPromise):(this._connectionState=At.Disconnecting,this._logger.log(ut.Debug,"Stopping HubConnection."),this._reconnectDelayHandle?(this._logger.log(ut.Debug,"Connection stopped during reconnect delay. Done reconnecting."),clearTimeout(this._reconnectDelayHandle),this._reconnectDelayHandle=void 0,this._completeClose(),Promise.resolve()):(this._cleanupTimeout(),this._cleanupPingTimer(),this._stopDuringStartError=e||new it("The connection was stopped before the hub handshake could complete."),this.connection.stop(e)))}stream(e,...t){const[n,r]=this._replaceStreamingParams(t),o=this._createStreamInvocation(e,t,r);let i;const s=new Wt;return s.cancelCallback=()=>{const e=this._createCancelInvocation(o.invocationId);return delete this._callbacks[o.invocationId],i.then((()=>this._sendWithProtocol(e)))},this._callbacks[o.invocationId]=(e,t)=>{t?s.error(t):e&&(e.type===Ut.Completion?e.error?s.error(new Error(e.error)):s.complete():s.next(e.item))},i=this._sendWithProtocol(o).catch((e=>{s.error(e),delete this._callbacks[o.invocationId]})),this._launchStreams(n,i),s}_sendMessage(e){return this._resetKeepAliveInterval(),this.connection.send(e)}_sendWithProtocol(e){return this._sendMessage(this._protocol.writeMessage(e))}send(e,...t){const[n,r]=this._replaceStreamingParams(t),o=this._sendWithProtocol(this._createInvocation(e,t,!0,r));return this._launchStreams(n,o),o}invoke(e,...t){const[n,r]=this._replaceStreamingParams(t),o=this._createInvocation(e,t,!1,r);return new Promise(((e,t)=>{this._callbacks[o.invocationId]=(n,r)=>{r?t(r):n&&(n.type===Ut.Completion?n.error?t(new Error(n.error)):e(n.result):t(new Error(`Unexpected message type: ${n.type}`)))};const r=this._sendWithProtocol(o).catch((e=>{t(e),delete this._callbacks[o.invocationId]}));this._launchStreams(n,r)}))}on(e,t){e&&t&&(e=e.toLowerCase(),this._methods[e]||(this._methods[e]=[]),-1===this._methods[e].indexOf(t)&&this._methods[e].push(t))}off(e,t){if(!e)return;e=e.toLowerCase();const n=this._methods[e];if(n)if(t){const r=n.indexOf(t);-1!==r&&(n.splice(r,1),0===n.length&&delete this._methods[e])}else delete this._methods[e]}onclose(e){e&&this._closedCallbacks.push(e)}onreconnecting(e){e&&this._reconnectingCallbacks.push(e)}onreconnected(e){e&&this._reconnectedCallbacks.push(e)}_processIncomingData(e){if(this._cleanupTimeout(),this._receivedHandshakeResponse||(e=this._processHandshakeResponse(e),this._receivedHandshakeResponse=!0),e){const t=this._protocol.parseMessages(e,this._logger);for(const e of t)switch(e.type){case Ut.Invocation:this._invokeClientMethod(e);break;case Ut.StreamItem:case Ut.Completion:{const t=this._callbacks[e.invocationId];if(t){e.type===Ut.Completion&&delete this._callbacks[e.invocationId];try{t(e)}catch(e){this._logger.log(ut.Error,`Stream callback threw error: ${It(e)}`)}}break}case Ut.Ping:break;case Ut.Close:{this._logger.log(ut.Information,"Close message received from server.");const t=e.error?new Error("Server returned an error on close: "+e.error):void 0;!0===e.allowReconnect?this.connection.stop(t):this._stopPromise=this._stopInternal(t);break}default:this._logger.log(ut.Warning,`Invalid message type: ${e.type}.`)}}this._resetTimeoutPeriod()}_processHandshakeResponse(e){let t,n;try{[n,t]=this._handshakeProtocol.parseHandshakeResponse(e)}catch(e){const t="Error parsing handshake response: "+e;this._logger.log(ut.Error,t);const n=new Error(t);throw this._handshakeRejecter(n),n}if(t.error){const e="Server returned handshake error: "+t.error;this._logger.log(ut.Error,e);const n=new Error(e);throw this._handshakeRejecter(n),n}return this._logger.log(ut.Debug,"Server handshake complete."),this._handshakeResolver(),n}_resetKeepAliveInterval(){this.connection.features.inherentKeepAlive||(this._nextKeepAlive=(new Date).getTime()+this.keepAliveIntervalInMilliseconds,this._cleanupPingTimer())}_resetTimeoutPeriod(){if(!(this.connection.features&&this.connection.features.inherentKeepAlive||(this._timeoutHandle=setTimeout((()=>this.serverTimeout()),this.serverTimeoutInMilliseconds),void 0!==this._pingServerHandle))){let e=this._nextKeepAlive-(new Date).getTime();e<0&&(e=0),this._pingServerHandle=setTimeout((async()=>{if(this._connectionState===At.Connected)try{await this._sendMessage(this._cachedPingMessage)}catch{this._cleanupPingTimer()}}),e)}}serverTimeout(){this.connection.stop(new Error("Server timeout elapsed without receiving a message from the server."))}async _invokeClientMethod(e){const t=e.target.toLowerCase(),n=this._methods[t];if(!n)return this._logger.log(ut.Warning,`No client method with the name '${t}' found.`),void(e.invocationId&&(this._logger.log(ut.Warning,`No result given for '${t}' method and invocation ID '${e.invocationId}'.`),await this._sendWithProtocol(this._createCompletionMessage(e.invocationId,"Client didn't provide a result.",null))));const r=n.slice(),o=!!e.invocationId;let i,s,a;for(const n of r)try{const r=i;i=await n.apply(this,e.arguments),o&&i&&r&&(this._logger.log(ut.Error,`Multiple results provided for '${t}'. Sending error to server.`),a=this._createCompletionMessage(e.invocationId,"Client provided multiple results.",null)),s=void 0}catch(e){s=e,this._logger.log(ut.Error,`A callback for the method '${t}' threw error '${e}'.`)}a?await this._sendWithProtocol(a):o?(s?a=this._createCompletionMessage(e.invocationId,`${s}`,null):void 0!==i?a=this._createCompletionMessage(e.invocationId,null,i):(this._logger.log(ut.Warning,`No result given for '${t}' method and invocation ID '${e.invocationId}'.`),a=this._createCompletionMessage(e.invocationId,"Client didn't provide a result.",null)),await this._sendWithProtocol(a)):i&&this._logger.log(ut.Error,`Result given for '${t}' method but server is not expecting a result.`)}_connectionClosed(e){this._logger.log(ut.Debug,`HubConnection.connectionClosed(${e}) called while in state ${this._connectionState}.`),this._stopDuringStartError=this._stopDuringStartError||e||new it("The underlying connection was closed before the hub handshake could complete."),this._handshakeResolver&&this._handshakeResolver(),this._cancelCallbacksWithError(e||new Error("Invocation canceled due to the underlying connection being closed.")),this._cleanupTimeout(),this._cleanupPingTimer(),this._connectionState===At.Disconnecting?this._completeClose(e):this._connectionState===At.Connected&&this._reconnectPolicy?this._reconnect(e):this._connectionState===At.Connected&&this._completeClose(e)}_completeClose(e){if(this._connectionStarted){this._connectionState=At.Disconnected,this._connectionStarted=!1,gt.isBrowser&&window.document.removeEventListener("freeze",this._freezeEventListener);try{this._closedCallbacks.forEach((t=>t.apply(this,[e])))}catch(t){this._logger.log(ut.Error,`An onclose callback called with error '${e}' threw error '${t}'.`)}}}async _reconnect(e){const t=Date.now();let n=0,r=void 0!==e?e:new Error("Attempting to reconnect due to a unknown error."),o=this._getNextRetryDelay(n++,0,r);if(null===o)return this._logger.log(ut.Debug,"Connection not reconnecting because the IRetryPolicy returned null on the first reconnect attempt."),void this._completeClose(e);if(this._connectionState=At.Reconnecting,e?this._logger.log(ut.Information,`Connection reconnecting because of error '${e}'.`):this._logger.log(ut.Information,"Connection reconnecting."),0!==this._reconnectingCallbacks.length){try{this._reconnectingCallbacks.forEach((t=>t.apply(this,[e])))}catch(t){this._logger.log(ut.Error,`An onreconnecting callback called with error '${e}' threw error '${t}'.`)}if(this._connectionState!==At.Reconnecting)return void this._logger.log(ut.Debug,"Connection left the reconnecting state in onreconnecting callback. Done reconnecting.")}for(;null!==o;){if(this._logger.log(ut.Information,`Reconnect attempt number ${n} will start in ${o} ms.`),await new Promise((e=>{this._reconnectDelayHandle=setTimeout(e,o)})),this._reconnectDelayHandle=void 0,this._connectionState!==At.Reconnecting)return void this._logger.log(ut.Debug,"Connection left the reconnecting state during reconnect delay. Done reconnecting.");try{if(await this._startInternal(),this._connectionState=At.Connected,this._logger.log(ut.Information,"HubConnection reconnected successfully."),0!==this._reconnectedCallbacks.length)try{this._reconnectedCallbacks.forEach((e=>e.apply(this,[this.connection.connectionId])))}catch(e){this._logger.log(ut.Error,`An onreconnected callback called with connectionId '${this.connection.connectionId}; threw error '${e}'.`)}return}catch(e){if(this._logger.log(ut.Information,`Reconnect attempt failed because of error '${e}'.`),this._connectionState!==At.Reconnecting)return this._logger.log(ut.Debug,`Connection moved to the '${this._connectionState}' from the reconnecting state during reconnect attempt. Done reconnecting.`),void(this._connectionState===At.Disconnecting&&this._completeClose());r=e instanceof Error?e:new Error(e.toString()),o=this._getNextRetryDelay(n++,Date.now()-t,r)}}this._logger.log(ut.Information,`Reconnect retries have been exhausted after ${Date.now()-t} ms and ${n} failed attempts. Connection disconnecting.`),this._completeClose()}_getNextRetryDelay(e,t,n){try{return this._reconnectPolicy.nextRetryDelayInMilliseconds({elapsedMilliseconds:t,previousRetryCount:e,retryReason:n})}catch(n){return this._logger.log(ut.Error,`IRetryPolicy.nextRetryDelayInMilliseconds(${e}, ${t}) threw error '${n}'.`),null}}_cancelCallbacksWithError(e){const t=this._callbacks;this._callbacks={},Object.keys(t).forEach((n=>{const r=t[n];try{r(null,e)}catch(t){this._logger.log(ut.Error,`Stream 'error' callback called with '${e}' threw error: ${It(t)}`)}}))}_cleanupPingTimer(){this._pingServerHandle&&(clearTimeout(this._pingServerHandle),this._pingServerHandle=void 0)}_cleanupTimeout(){this._timeoutHandle&&clearTimeout(this._timeoutHandle)}_createInvocation(e,t,n,r){if(n)return 0!==r.length?{arguments:t,streamIds:r,target:e,type:Ut.Invocation}:{arguments:t,target:e,type:Ut.Invocation};{const n=this._invocationId;return this._invocationId++,0!==r.length?{arguments:t,invocationId:n.toString(),streamIds:r,target:e,type:Ut.Invocation}:{arguments:t,invocationId:n.toString(),target:e,type:Ut.Invocation}}}_launchStreams(e,t){if(0!==e.length){t||(t=Promise.resolve());for(const n in e)e[n].subscribe({complete:()=>{t=t.then((()=>this._sendWithProtocol(this._createCompletionMessage(n))))},error:e=>{let r;r=e instanceof Error?e.message:e&&e.toString?e.toString():"Unknown error",t=t.then((()=>this._sendWithProtocol(this._createCompletionMessage(n,r))))},next:e=>{t=t.then((()=>this._sendWithProtocol(this._createStreamItemMessage(n,e))))}})}}_replaceStreamingParams(e){const t=[],n=[];for(let r=0;r=55296&&o<=56319&&r65535&&(h-=65536,i.push(h>>>10&1023|55296),h=56320|1023&h),i.push(h)}else i.push(a);i.length>=sn&&(s+=String.fromCharCode.apply(String,i),i.length=0)}return i.length>0&&(s+=String.fromCharCode.apply(String,i)),s}var cn,ln=en?new TextDecoder:null,hn=en?"undefined"!=typeof process&&"force"!==(null===(Yt=null===process||void 0===process?void 0:process.env)||void 0===Yt?void 0:Yt.TEXT_DECODER)?200:0:Gt,un=function(e,t){this.type=e,this.data=t},dn=(cn=function(e,t){return cn=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n])},cn(e,t)},function(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Class extends value "+String(t)+" is not a constructor or null");function n(){this.constructor=e}cn(e,t),e.prototype=null===t?Object.create(t):(n.prototype=t.prototype,new n)}),pn=function(e){function t(n){var r=e.call(this,n)||this,o=Object.create(t.prototype);return Object.setPrototypeOf(r,o),Object.defineProperty(r,"name",{configurable:!0,enumerable:!1,value:t.name}),r}return dn(t,e),t}(Error),fn={type:-1,encode:function(e){var t,n,r,o;return e instanceof Date?function(e){var t,n=e.sec,r=e.nsec;if(n>=0&&r>=0&&n<=17179869183){if(0===r&&n<=4294967295){var o=new Uint8Array(4);return(t=new DataView(o.buffer)).setUint32(0,n),o}var i=n/4294967296,s=4294967295&n;return o=new Uint8Array(8),(t=new DataView(o.buffer)).setUint32(0,r<<2|3&i),t.setUint32(4,s),o}return o=new Uint8Array(12),(t=new DataView(o.buffer)).setUint32(0,r),Qt(t,4,n),o}((r=1e6*((t=e.getTime())-1e3*(n=Math.floor(t/1e3))),{sec:n+(o=Math.floor(r/1e9)),nsec:r-1e9*o})):null},decode:function(e){var t=function(e){var t=new DataView(e.buffer,e.byteOffset,e.byteLength);switch(e.byteLength){case 4:return{sec:t.getUint32(0),nsec:0};case 8:var n=t.getUint32(0);return{sec:4294967296*(3&n)+t.getUint32(4),nsec:n>>>2};case 12:return{sec:Zt(t,4),nsec:t.getUint32(0)};default:throw new pn("Unrecognized data size for timestamp (expected 4, 8, or 12): ".concat(e.length))}}(e);return new Date(1e3*t.sec+t.nsec/1e6)}},gn=function(){function e(){this.builtInEncoders=[],this.builtInDecoders=[],this.encoders=[],this.decoders=[],this.register(fn)}return e.prototype.register=function(e){var t=e.type,n=e.encode,r=e.decode;if(t>=0)this.encoders[t]=n,this.decoders[t]=r;else{var o=1+t;this.builtInEncoders[o]=n,this.builtInDecoders[o]=r}},e.prototype.tryToEncode=function(e,t){for(var n=0;nthis.maxDepth)throw new Error("Too deep objects in depth ".concat(t));null==e?this.encodeNil():"boolean"==typeof e?this.encodeBoolean(e):"number"==typeof e?this.encodeNumber(e):"string"==typeof e?this.encodeString(e):this.encodeObject(e,t)},e.prototype.ensureBufferSizeToWrite=function(e){var t=this.pos+e;this.view.byteLength=0?e<128?this.writeU8(e):e<256?(this.writeU8(204),this.writeU8(e)):e<65536?(this.writeU8(205),this.writeU16(e)):e<4294967296?(this.writeU8(206),this.writeU32(e)):(this.writeU8(207),this.writeU64(e)):e>=-32?this.writeU8(224|e+32):e>=-128?(this.writeU8(208),this.writeI8(e)):e>=-32768?(this.writeU8(209),this.writeI16(e)):e>=-2147483648?(this.writeU8(210),this.writeI32(e)):(this.writeU8(211),this.writeI64(e)):this.forceFloat32?(this.writeU8(202),this.writeF32(e)):(this.writeU8(203),this.writeF64(e))},e.prototype.writeStringHeader=function(e){if(e<32)this.writeU8(160+e);else if(e<256)this.writeU8(217),this.writeU8(e);else if(e<65536)this.writeU8(218),this.writeU16(e);else{if(!(e<4294967296))throw new Error("Too long string: ".concat(e," bytes in UTF-8"));this.writeU8(219),this.writeU32(e)}},e.prototype.encodeString=function(e){if(e.length>rn){var t=tn(e);this.ensureBufferSizeToWrite(5+t),this.writeStringHeader(t),on(e,this.bytes,this.pos),this.pos+=t}else t=tn(e),this.ensureBufferSizeToWrite(5+t),this.writeStringHeader(t),function(e,t,n){for(var r=e.length,o=n,i=0;i>6&31|192;else{if(s>=55296&&s<=56319&&i>12&15|224,t[o++]=s>>6&63|128):(t[o++]=s>>18&7|240,t[o++]=s>>12&63|128,t[o++]=s>>6&63|128)}t[o++]=63&s|128}else t[o++]=s}}(e,this.bytes,this.pos),this.pos+=t},e.prototype.encodeObject=function(e,t){var n=this.extensionCodec.tryToEncode(e,this.context);if(null!=n)this.encodeExtension(n);else if(Array.isArray(e))this.encodeArray(e,t);else if(ArrayBuffer.isView(e))this.encodeBinary(e);else{if("object"!=typeof e)throw new Error("Unrecognized object: ".concat(Object.prototype.toString.apply(e)));this.encodeMap(e,t)}},e.prototype.encodeBinary=function(e){var t=e.byteLength;if(t<256)this.writeU8(196),this.writeU8(t);else if(t<65536)this.writeU8(197),this.writeU16(t);else{if(!(t<4294967296))throw new Error("Too large binary: ".concat(t));this.writeU8(198),this.writeU32(t)}var n=mn(e);this.writeU8a(n)},e.prototype.encodeArray=function(e,t){var n=e.length;if(n<16)this.writeU8(144+n);else if(n<65536)this.writeU8(220),this.writeU16(n);else{if(!(n<4294967296))throw new Error("Too large array: ".concat(n));this.writeU8(221),this.writeU32(n)}for(var r=0,o=e;r0&&e<=this.maxKeyLength},e.prototype.find=function(e,t,n){e:for(var r=0,o=this.caches[n-1];r=this.maxLengthPerKey?n[Math.random()*n.length|0]=r:n.push(r)},e.prototype.decode=function(e,t,n){var r=this.find(e,t,n);if(null!=r)return this.hit++,r;this.miss++;var o=an(e,t,n),i=Uint8Array.prototype.slice.call(e,t,t+n);return this.store(i,o),o},e}(),Cn=function(e,t){var n,r,o,i,s={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return i={next:a(0),throw:a(1),return:a(2)},"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function a(i){return function(a){return function(i){if(n)throw new TypeError("Generator is already executing.");for(;s;)try{if(n=1,r&&(o=2&i[0]?r.return:i[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,i[1])).done)return o;switch(r=0,o&&(i=[2&i[0],o.value]),i[0]){case 0:case 1:o=i;break;case 4:return s.label++,{value:i[1],done:!1};case 5:s.label++,r=i[1],i=[0];continue;case 7:i=s.ops.pop(),s.trys.pop();continue;default:if(!((o=(o=s.trys).length>0&&o[o.length-1])||6!==i[0]&&2!==i[0])){s=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]1||a(e,t)}))})}function a(e,t){try{(n=o[e](t)).value instanceof kn?Promise.resolve(n.value.v).then(c,l):h(i[0][2],n)}catch(e){h(i[0][3],e)}var n}function c(e){a("next",e)}function l(e){a("throw",e)}function h(e,t){e(t),i.shift(),i.length&&a(i[0][0],i[0][1])}},xn=-1,Dn=new DataView(new ArrayBuffer(0)),Rn=new Uint8Array(Dn.buffer),Pn=function(){try{Dn.getInt8(0)}catch(e){return e.constructor}throw new Error("never reached")}(),Un=new Pn("Insufficient data"),An=new Sn,Nn=function(){function e(e,t,n,r,o,i,s,a){void 0===e&&(e=gn.defaultCodec),void 0===t&&(t=void 0),void 0===n&&(n=Gt),void 0===r&&(r=Gt),void 0===o&&(o=Gt),void 0===i&&(i=Gt),void 0===s&&(s=Gt),void 0===a&&(a=An),this.extensionCodec=e,this.context=t,this.maxStrLength=n,this.maxBinLength=r,this.maxArrayLength=o,this.maxMapLength=i,this.maxExtLength=s,this.keyDecoder=a,this.totalPos=0,this.pos=0,this.view=Dn,this.bytes=Rn,this.headByte=xn,this.stack=[]}return e.prototype.reinitializeState=function(){this.totalPos=0,this.headByte=xn,this.stack.length=0},e.prototype.setBuffer=function(e){this.bytes=mn(e),this.view=function(e){if(e instanceof ArrayBuffer)return new DataView(e);var t=mn(e);return new DataView(t.buffer,t.byteOffset,t.byteLength)}(this.bytes),this.pos=0},e.prototype.appendBuffer=function(e){if(this.headByte!==xn||this.hasRemaining(1)){var t=this.bytes.subarray(this.pos),n=mn(e),r=new Uint8Array(t.length+n.length);r.set(t),r.set(n,t.length),this.setBuffer(r)}else this.setBuffer(e)},e.prototype.hasRemaining=function(e){return this.view.byteLength-this.pos>=e},e.prototype.createExtraByteError=function(e){var t=this.view,n=this.pos;return new RangeError("Extra ".concat(t.byteLength-n," of ").concat(t.byteLength," byte(s) found at buffer[").concat(e,"]"))},e.prototype.decode=function(e){this.reinitializeState(),this.setBuffer(e);var t=this.doDecodeSync();if(this.hasRemaining(1))throw this.createExtraByteError(this.pos);return t},e.prototype.decodeMulti=function(e){return Cn(this,(function(t){switch(t.label){case 0:this.reinitializeState(),this.setBuffer(e),t.label=1;case 1:return this.hasRemaining(1)?[4,this.doDecodeSync()]:[3,3];case 2:return t.sent(),[3,1];case 3:return[2]}}))},e.prototype.decodeAsync=function(e){var t,n,r,o,i,s,a;return i=this,void 0,a=function(){var i,s,a,c,l,h,u,d;return Cn(this,(function(p){switch(p.label){case 0:i=!1,p.label=1;case 1:p.trys.push([1,6,7,12]),t=In(e),p.label=2;case 2:return[4,t.next()];case 3:if((n=p.sent()).done)return[3,5];if(a=n.value,i)throw this.createExtraByteError(this.totalPos);this.appendBuffer(a);try{s=this.doDecodeSync(),i=!0}catch(e){if(!(e instanceof Pn))throw e}this.totalPos+=this.pos,p.label=4;case 4:return[3,2];case 5:return[3,12];case 6:return c=p.sent(),r={error:c},[3,12];case 7:return p.trys.push([7,,10,11]),n&&!n.done&&(o=t.return)?[4,o.call(t)]:[3,9];case 8:p.sent(),p.label=9;case 9:return[3,11];case 10:if(r)throw r.error;return[7];case 11:return[7];case 12:if(i){if(this.hasRemaining(1))throw this.createExtraByteError(this.totalPos);return[2,s]}throw h=(l=this).headByte,u=l.pos,d=l.totalPos,new RangeError("Insufficient data in parsing ".concat(bn(h)," at ").concat(d," (").concat(u," in the current buffer)"))}}))},new((s=void 0)||(s=Promise))((function(e,t){function n(e){try{o(a.next(e))}catch(e){t(e)}}function r(e){try{o(a.throw(e))}catch(e){t(e)}}function o(t){var o;t.done?e(t.value):(o=t.value,o instanceof s?o:new s((function(e){e(o)}))).then(n,r)}o((a=a.apply(i,[])).next())}))},e.prototype.decodeArrayStream=function(e){return this.decodeMultiAsync(e,!0)},e.prototype.decodeStream=function(e){return this.decodeMultiAsync(e,!1)},e.prototype.decodeMultiAsync=function(e,t){return Tn(this,arguments,(function(){var n,r,o,i,s,a,c,l,h;return Cn(this,(function(u){switch(u.label){case 0:n=t,r=-1,u.label=1;case 1:u.trys.push([1,13,14,19]),o=In(e),u.label=2;case 2:return[4,kn(o.next())];case 3:if((i=u.sent()).done)return[3,12];if(s=i.value,t&&0===r)throw this.createExtraByteError(this.totalPos);this.appendBuffer(s),n&&(r=this.readArraySize(),n=!1,this.complete()),u.label=4;case 4:u.trys.push([4,9,,10]),u.label=5;case 5:return[4,kn(this.doDecodeSync())];case 6:return[4,u.sent()];case 7:return u.sent(),0==--r?[3,8]:[3,5];case 8:return[3,10];case 9:if(!((a=u.sent())instanceof Pn))throw a;return[3,10];case 10:this.totalPos+=this.pos,u.label=11;case 11:return[3,2];case 12:return[3,19];case 13:return c=u.sent(),l={error:c},[3,19];case 14:return u.trys.push([14,,17,18]),i&&!i.done&&(h=o.return)?[4,kn(h.call(o))]:[3,16];case 15:u.sent(),u.label=16;case 16:return[3,18];case 17:if(l)throw l.error;return[7];case 18:return[7];case 19:return[2]}}))}))},e.prototype.doDecodeSync=function(){e:for(;;){var e=this.readHeadByte(),t=void 0;if(e>=224)t=e-256;else if(e<192)if(e<128)t=e;else if(e<144){if(0!=(r=e-128)){this.pushMapState(r),this.complete();continue e}t={}}else if(e<160){if(0!=(r=e-144)){this.pushArrayState(r),this.complete();continue e}t=[]}else{var n=e-160;t=this.decodeUtf8String(n,0)}else if(192===e)t=null;else if(194===e)t=!1;else if(195===e)t=!0;else if(202===e)t=this.readF32();else if(203===e)t=this.readF64();else if(204===e)t=this.readU8();else if(205===e)t=this.readU16();else if(206===e)t=this.readU32();else if(207===e)t=this.readU64();else if(208===e)t=this.readI8();else if(209===e)t=this.readI16();else if(210===e)t=this.readI32();else if(211===e)t=this.readI64();else if(217===e)n=this.lookU8(),t=this.decodeUtf8String(n,1);else if(218===e)n=this.lookU16(),t=this.decodeUtf8String(n,2);else if(219===e)n=this.lookU32(),t=this.decodeUtf8String(n,4);else if(220===e){if(0!==(r=this.readU16())){this.pushArrayState(r),this.complete();continue e}t=[]}else if(221===e){if(0!==(r=this.readU32())){this.pushArrayState(r),this.complete();continue e}t=[]}else if(222===e){if(0!==(r=this.readU16())){this.pushMapState(r),this.complete();continue e}t={}}else if(223===e){if(0!==(r=this.readU32())){this.pushMapState(r),this.complete();continue e}t={}}else if(196===e){var r=this.lookU8();t=this.decodeBinary(r,1)}else if(197===e)r=this.lookU16(),t=this.decodeBinary(r,2);else if(198===e)r=this.lookU32(),t=this.decodeBinary(r,4);else if(212===e)t=this.decodeExtension(1,0);else if(213===e)t=this.decodeExtension(2,0);else if(214===e)t=this.decodeExtension(4,0);else if(215===e)t=this.decodeExtension(8,0);else if(216===e)t=this.decodeExtension(16,0);else if(199===e)r=this.lookU8(),t=this.decodeExtension(r,1);else if(200===e)r=this.lookU16(),t=this.decodeExtension(r,2);else{if(201!==e)throw new pn("Unrecognized type byte: ".concat(bn(e)));r=this.lookU32(),t=this.decodeExtension(r,4)}this.complete();for(var o=this.stack;o.length>0;){var i=o[o.length-1];if(0===i.type){if(i.array[i.position]=t,i.position++,i.position!==i.size)continue e;o.pop(),t=i.array}else{if(1===i.type){if("string"!=(s=typeof t)&&"number"!==s)throw new pn("The type of key must be string or number but "+typeof t);if("__proto__"===t)throw new pn("The key __proto__ is not allowed");i.key=t,i.type=2;continue e}if(i.map[i.key]=t,i.readCount++,i.readCount!==i.size){i.key=null,i.type=1;continue e}o.pop(),t=i.map}}return t}var s},e.prototype.readHeadByte=function(){return this.headByte===xn&&(this.headByte=this.readU8()),this.headByte},e.prototype.complete=function(){this.headByte=xn},e.prototype.readArraySize=function(){var e=this.readHeadByte();switch(e){case 220:return this.readU16();case 221:return this.readU32();default:if(e<160)return e-144;throw new pn("Unrecognized array type byte: ".concat(bn(e)))}},e.prototype.pushMapState=function(e){if(e>this.maxMapLength)throw new pn("Max length exceeded: map length (".concat(e,") > maxMapLengthLength (").concat(this.maxMapLength,")"));this.stack.push({type:1,size:e,key:null,readCount:0,map:{}})},e.prototype.pushArrayState=function(e){if(e>this.maxArrayLength)throw new pn("Max length exceeded: array length (".concat(e,") > maxArrayLength (").concat(this.maxArrayLength,")"));this.stack.push({type:0,size:e,array:new Array(e),position:0})},e.prototype.decodeUtf8String=function(e,t){var n;if(e>this.maxStrLength)throw new pn("Max length exceeded: UTF-8 byte length (".concat(e,") > maxStrLength (").concat(this.maxStrLength,")"));if(this.bytes.byteLengthhn?function(e,t,n){var r=e.subarray(t,t+n);return ln.decode(r)}(this.bytes,o,e):an(this.bytes,o,e),this.pos+=t+e,r},e.prototype.stateIsMapKey=function(){return this.stack.length>0&&1===this.stack[this.stack.length-1].type},e.prototype.decodeBinary=function(e,t){if(e>this.maxBinLength)throw new pn("Max length exceeded: bin length (".concat(e,") > maxBinLength (").concat(this.maxBinLength,")"));if(!this.hasRemaining(e+t))throw Un;var n=this.pos+t,r=this.bytes.subarray(n,n+e);return this.pos+=t+e,r},e.prototype.decodeExtension=function(e,t){if(e>this.maxExtLength)throw new pn("Max length exceeded: ext length (".concat(e,") > maxExtLength (").concat(this.maxExtLength,")"));var n=this.view.getInt8(this.pos+t),r=this.decodeBinary(e,t+1);return this.extensionCodec.decode(r,n,this.context)},e.prototype.lookU8=function(){return this.view.getUint8(this.pos)},e.prototype.lookU16=function(){return this.view.getUint16(this.pos)},e.prototype.lookU32=function(){return this.view.getUint32(this.pos)},e.prototype.readU8=function(){var e=this.view.getUint8(this.pos);return this.pos++,e},e.prototype.readI8=function(){var e=this.view.getInt8(this.pos);return this.pos++,e},e.prototype.readU16=function(){var e=this.view.getUint16(this.pos);return this.pos+=2,e},e.prototype.readI16=function(){var e=this.view.getInt16(this.pos);return this.pos+=2,e},e.prototype.readU32=function(){var e=this.view.getUint32(this.pos);return this.pos+=4,e},e.prototype.readI32=function(){var e=this.view.getInt32(this.pos);return this.pos+=4,e},e.prototype.readU64=function(){var e,t,n=(e=this.view,t=this.pos,4294967296*e.getUint32(t)+e.getUint32(t+4));return this.pos+=8,n},e.prototype.readI64=function(){var e=Zt(this.view,this.pos);return this.pos+=8,e},e.prototype.readF32=function(){var e=this.view.getFloat32(this.pos);return this.pos+=4,e},e.prototype.readF64=function(){var e=this.view.getFloat64(this.pos);return this.pos+=8,e},e}();class $n{static write(e){let t=e.byteLength||e.length;const n=[];do{let e=127&t;t>>=7,t>0&&(e|=128),n.push(e)}while(t>0);t=e.byteLength||e.length;const r=new Uint8Array(n.length+t);return r.set(n,0),r.set(e,n.length),r.buffer}static parse(e){const t=[],n=new Uint8Array(e),r=[0,7,14,21,28];for(let o=0;o7)throw new Error("Messages bigger than 2GB are not supported.");if(!(n.byteLength>=o+s+a))throw new Error("Incomplete message.");t.push(n.slice?n.slice(o+s,o+s+a):n.subarray(o+s,o+s+a)),o=o+s+a}return t}}const Ln=new Uint8Array([145,Ut.Ping]);class Bn{constructor(e){this.name="messagepack",this.version=1,this.transferFormat=Pt.Binary,this._errorResult=1,this._voidResult=2,this._nonVoidResult=3,e=e||{},this._encoder=new vn(e.extensionCodec,e.context,e.maxDepth,e.initialBufferSize,e.sortKeys,e.forceFloat32,e.ignoreUndefined,e.forceIntegerToFloat),this._decoder=new Nn(e.extensionCodec,e.context,e.maxStrLength,e.maxBinLength,e.maxArrayLength,e.maxMapLength,e.maxExtLength)}parseMessages(e,t){if(!(n=e)||"undefined"==typeof ArrayBuffer||!(n instanceof ArrayBuffer||n.constructor&&"ArrayBuffer"===n.constructor.name))throw new Error("Invalid input for MessagePack hub protocol. Expected an ArrayBuffer.");var n;null===t&&(t=dt.instance);const r=$n.parse(e),o=[];for(const e of r){const n=this._parseMessage(e,t);n&&o.push(n)}return o}writeMessage(e){switch(e.type){case Ut.Invocation:return this._writeInvocation(e);case Ut.StreamInvocation:return this._writeStreamInvocation(e);case Ut.StreamItem:return this._writeStreamItem(e);case Ut.Completion:return this._writeCompletion(e);case Ut.Ping:return $n.write(Ln);case Ut.CancelInvocation:return this._writeCancelInvocation(e);default:throw new Error("Invalid message type.")}}_parseMessage(e,t){if(0===e.length)throw new Error("Invalid payload.");const n=this._decoder.decode(e);if(0===n.length||!(n instanceof Array))throw new Error("Invalid payload.");const r=n[0];switch(r){case Ut.Invocation:return this._createInvocationMessage(this._readHeaders(n),n);case Ut.StreamItem:return this._createStreamItemMessage(this._readHeaders(n),n);case Ut.Completion:return this._createCompletionMessage(this._readHeaders(n),n);case Ut.Ping:return this._createPingMessage(n);case Ut.Close:return this._createCloseMessage(n);default:return t.log(ut.Information,"Unknown message type '"+r+"' ignored."),null}}_createCloseMessage(e){if(e.length<2)throw new Error("Invalid payload for Close message.");return{allowReconnect:e.length>=3?e[2]:void 0,error:e[1],type:Ut.Close}}_createPingMessage(e){if(e.length<1)throw new Error("Invalid payload for Ping message.");return{type:Ut.Ping}}_createInvocationMessage(e,t){if(t.length<5)throw new Error("Invalid payload for Invocation message.");const n=t[2];return n?{arguments:t[4],headers:e,invocationId:n,streamIds:[],target:t[3],type:Ut.Invocation}:{arguments:t[4],headers:e,streamIds:[],target:t[3],type:Ut.Invocation}}_createStreamItemMessage(e,t){if(t.length<4)throw new Error("Invalid payload for StreamItem message.");return{headers:e,invocationId:t[2],item:t[3],type:Ut.StreamItem}}_createCompletionMessage(e,t){if(t.length<4)throw new Error("Invalid payload for Completion message.");const n=t[3];if(n!==this._voidResult&&t.length<5)throw new Error("Invalid payload for Completion message.");let r,o;switch(n){case this._errorResult:r=t[4];break;case this._nonVoidResult:o=t[4]}return{error:r,headers:e,invocationId:t[2],result:o,type:Ut.Completion}}_writeInvocation(e){let t;return t=e.streamIds?this._encoder.encode([Ut.Invocation,e.headers||{},e.invocationId||null,e.target,e.arguments,e.streamIds]):this._encoder.encode([Ut.Invocation,e.headers||{},e.invocationId||null,e.target,e.arguments]),$n.write(t.slice())}_writeStreamInvocation(e){let t;return t=e.streamIds?this._encoder.encode([Ut.StreamInvocation,e.headers||{},e.invocationId,e.target,e.arguments,e.streamIds]):this._encoder.encode([Ut.StreamInvocation,e.headers||{},e.invocationId,e.target,e.arguments]),$n.write(t.slice())}_writeStreamItem(e){const t=this._encoder.encode([Ut.StreamItem,e.headers||{},e.invocationId,e.item]);return $n.write(t.slice())}_writeCompletion(e){const t=e.error?this._errorResult:void 0!==e.result?this._nonVoidResult:this._voidResult;let n;switch(t){case this._errorResult:n=this._encoder.encode([Ut.Completion,e.headers||{},e.invocationId,t,e.error]);break;case this._voidResult:n=this._encoder.encode([Ut.Completion,e.headers||{},e.invocationId,t]);break;case this._nonVoidResult:n=this._encoder.encode([Ut.Completion,e.headers||{},e.invocationId,t,e.result])}return $n.write(n.slice())}_writeCancelInvocation(e){const t=this._encoder.encode([Ut.CancelInvocation,e.headers||{},e.invocationId]);return $n.write(t.slice())}_readHeaders(e){const t=e[1];if("object"!=typeof t)throw new Error("Invalid headers.");return t}}let Mn=!1;function On(){const e=document.querySelector("#blazor-error-ui");e&&(e.style.display="block"),Mn||(Mn=!0,document.querySelectorAll("#blazor-error-ui .reload").forEach((e=>{e.onclick=function(e){location.reload(),e.preventDefault()}})),document.querySelectorAll("#blazor-error-ui .dismiss").forEach((e=>{e.onclick=function(e){const t=document.querySelector("#blazor-error-ui");t&&(t.style.display="none"),e.preventDefault()}})))}const Fn="function"==typeof TextDecoder?new TextDecoder("utf-8"):null,Hn=Fn?Fn.decode.bind(Fn):function(e){let t=0;const n=e.length,r=[],o=[];for(;t65535&&(o-=65536,r.push(o>>>10&1023|55296),o=56320|1023&o),r.push(o)}r.length>1024&&(o.push(String.fromCharCode.apply(null,r)),r.length=0)}return o.push(String.fromCharCode.apply(null,r)),o.join("")},jn=Math.pow(2,32),Wn=Math.pow(2,21)-1;function zn(e,t){return e[t]|e[t+1]<<8|e[t+2]<<16|e[t+3]<<24}function Jn(e,t){return e[t]+(e[t+1]<<8)+(e[t+2]<<16)+(e[t+3]<<24>>>0)}function qn(e,t){const n=Jn(e,t+4);if(n>Wn)throw new Error(`Cannot read uint64 with high order part ${n}, because the result would exceed Number.MAX_SAFE_INTEGER.`);return n*jn+Jn(e,t)}class Vn{constructor(e){this.batchData=e;const t=new Gn(e);this.arrayRangeReader=new Qn(e),this.arrayBuilderSegmentReader=new Zn(e),this.diffReader=new Kn(e),this.editReader=new Xn(e,t),this.frameReader=new Yn(e,t)}updatedComponents(){return zn(this.batchData,this.batchData.length-20)}referenceFrames(){return zn(this.batchData,this.batchData.length-16)}disposedComponentIds(){return zn(this.batchData,this.batchData.length-12)}disposedEventHandlerIds(){return zn(this.batchData,this.batchData.length-8)}updatedComponentsEntry(e,t){const n=e+4*t;return zn(this.batchData,n)}referenceFramesEntry(e,t){return e+20*t}disposedComponentIdsEntry(e,t){const n=e+4*t;return zn(this.batchData,n)}disposedEventHandlerIdsEntry(e,t){const n=e+8*t;return qn(this.batchData,n)}}class Kn{constructor(e){this.batchDataUint8=e}componentId(e){return zn(this.batchDataUint8,e)}edits(e){return e+4}editsEntry(e,t){return e+16*t}}class Xn{constructor(e,t){this.batchDataUint8=e,this.stringReader=t}editType(e){return zn(this.batchDataUint8,e)}siblingIndex(e){return zn(this.batchDataUint8,e+4)}newTreeIndex(e){return zn(this.batchDataUint8,e+8)}moveToSiblingIndex(e){return zn(this.batchDataUint8,e+8)}removedAttributeName(e){const t=zn(this.batchDataUint8,e+12);return this.stringReader.readString(t)}}class Yn{constructor(e,t){this.batchDataUint8=e,this.stringReader=t}frameType(e){return zn(this.batchDataUint8,e)}subtreeLength(e){return zn(this.batchDataUint8,e+4)}elementReferenceCaptureId(e){const t=zn(this.batchDataUint8,e+4);return this.stringReader.readString(t)}componentId(e){return zn(this.batchDataUint8,e+8)}elementName(e){const t=zn(this.batchDataUint8,e+8);return this.stringReader.readString(t)}textContent(e){const t=zn(this.batchDataUint8,e+4);return this.stringReader.readString(t)}markupContent(e){const t=zn(this.batchDataUint8,e+4);return this.stringReader.readString(t)}attributeName(e){const t=zn(this.batchDataUint8,e+4);return this.stringReader.readString(t)}attributeValue(e){const t=zn(this.batchDataUint8,e+8);return this.stringReader.readString(t)}attributeEventHandlerId(e){return qn(this.batchDataUint8,e+12)}}class Gn{constructor(e){this.batchDataUint8=e,this.stringTableStartIndex=zn(e,e.length-4)}readString(e){if(-1===e)return null;{const n=zn(this.batchDataUint8,this.stringTableStartIndex+4*e),r=function(e,t){let n=0,r=0;for(let o=0;o<4;o++){const i=e[t+o];if(n|=(127&i)<this.nextBatchId)return this.fatalError?(this.logger.log(er.Debug,`Received a new batch ${e} but errored out on a previous batch ${this.nextBatchId-1}`),void await n.send("OnRenderCompleted",this.nextBatchId-1,this.fatalError.toString())):void this.logger.log(er.Debug,`Waiting for batch ${this.nextBatchId}. Batch ${e} not processed.`);try{this.nextBatchId++,this.logger.log(er.Debug,`Applying batch ${e}.`),function(e,t){const n=pe[e];if(!n)throw new Error(`There is no browser renderer with ID ${e}.`);const r=t.arrayRangeReader,o=t.updatedComponents(),i=r.values(o),s=r.count(o),a=t.referenceFrames(),c=r.values(a),l=t.diffReader;for(let e=0;e=this.minLevel){const n=`[${(new Date).toISOString()}] ${er[e]}: ${t}`;switch(e){case er.Critical:case er.Error:console.error(n);break;case er.Warning:console.warn(n);break;case er.Information:console.info(n);break;default:console.log(n)}}}}class or{constructor(e,t){this.circuitId=void 0,this.components=e,this.applicationState=t}reconnect(e){if(!this.circuitId)throw new Error("Circuit host not initialized.");return e.state!==At.Connected?Promise.resolve(!1):e.invoke("ConnectCircuit",this.circuitId)}initialize(e){if(this.circuitId)throw new Error(`Circuit host '${this.circuitId}' already initialized.`);this.circuitId=e}async startCircuit(e){if(e.state!==At.Connected)return!1;const t=await e.invoke("StartCircuit",Ce.getBaseURI(),Ce.getLocationHref(),JSON.stringify(this.components.map((e=>e.toRecord()))),this.applicationState||"");return!!t&&(this.initialize(t),!0)}resolveElement(e){const t=function(e){const t=f.get(e);if(t)return f.delete(e),t}(e);if(t)return M(t,!0);const n=Number.parseInt(e);if(!Number.isNaN(n))return function(e,t){if(!e.parentNode)throw new Error(`Comment not connected to the DOM ${e.textContent}`);const n=e.parentNode,r=M(n,!0),o=J(r);return Array.from(n.childNodes).forEach((e=>o.push(e))),e[L]=r,t&&(e[B]=t,M(t)),M(e)}(this.components[n].start,this.components[n].end);throw new Error(`Invalid sequence number or identifier '${e}'.`)}}const ir={configureSignalR:e=>{},logLevel:er.Warning,reconnectionOptions:{maxRetries:8,retryIntervalMilliseconds:2e4,dialogId:"components-reconnect-modal"}};class sr{constructor(e,t,n,r){this.maxRetries=t,this.document=n,this.logger=r,this.addedToDom=!1,this.modal=this.document.createElement("div"),this.modal.id=e,this.maxRetries=t,this.modal.style.cssText=["position: fixed","top: 0","right: 0","bottom: 0","left: 0","z-index: 1050","display: none","overflow: hidden","background-color: #fff","opacity: 0.8","text-align: center","font-weight: bold","transition: visibility 0s linear 500ms"].join(";"),this.message=this.document.createElement("h5"),this.message.style.cssText="margin-top: 20px",this.button=this.document.createElement("button"),this.button.style.cssText="margin:5px auto 5px",this.button.textContent="Retry";const o=this.document.createElement("a");o.addEventListener("click",(()=>location.reload())),o.textContent="reload",this.reloadParagraph=this.document.createElement("p"),this.reloadParagraph.textContent="Alternatively, ",this.reloadParagraph.appendChild(o),this.modal.appendChild(this.message),this.modal.appendChild(this.button),this.modal.appendChild(this.reloadParagraph),this.loader=this.getLoader(),this.message.after(this.loader),this.button.addEventListener("click",(async()=>{this.show();try{await Ye.reconnect()||this.rejected()}catch(e){this.logger.log(er.Error,e),this.failed()}}))}show(){this.addedToDom||(this.addedToDom=!0,this.document.body.appendChild(this.modal)),this.modal.style.display="block",this.loader.style.display="inline-block",this.button.style.display="none",this.reloadParagraph.style.display="none",this.message.textContent="Attempting to reconnect to the server...",this.modal.style.visibility="hidden",setTimeout((()=>{this.modal.style.visibility="visible"}),0)}update(e){this.message.textContent=`Attempting to reconnect to the server: ${e} of ${this.maxRetries}`}hide(){this.modal.style.display="none"}failed(){this.button.style.display="block",this.reloadParagraph.style.display="none",this.loader.style.display="none";const e=this.document.createTextNode("Reconnection failed. Try "),t=this.document.createElement("a");t.textContent="reloading",t.setAttribute("href",""),t.addEventListener("click",(()=>location.reload()));const n=this.document.createTextNode(" the page if you're unable to reconnect.");this.message.replaceChildren(e,t,n)}rejected(){this.button.style.display="none",this.reloadParagraph.style.display="none",this.loader.style.display="none";const e=this.document.createTextNode("Could not reconnect to the server. "),t=this.document.createElement("a");t.textContent="Reload",t.setAttribute("href",""),t.addEventListener("click",(()=>location.reload()));const n=this.document.createTextNode(" the page to restore functionality.");this.message.replaceChildren(e,t,n)}getLoader(){const e=this.document.createElement("div");return e.style.cssText=["border: 0.3em solid #f3f3f3","border-top: 0.3em solid #3498db","border-radius: 50%","width: 2em","height: 2em","display: inline-block"].join(";"),e.animate([{transform:"rotate(0deg)"},{transform:"rotate(360deg)"}],{duration:2e3,iterations:1/0}),e}}class ar{constructor(e,t,n){this.dialog=e,this.maxRetries=t,this.document=n,this.document=n;const r=this.document.getElementById(ar.MaxRetriesId);r&&(r.innerText=this.maxRetries.toString())}show(){this.removeClasses(),this.dialog.classList.add(ar.ShowClassName)}update(e){const t=this.document.getElementById(ar.CurrentAttemptId);t&&(t.innerText=e.toString())}hide(){this.removeClasses(),this.dialog.classList.add(ar.HideClassName)}failed(){this.removeClasses(),this.dialog.classList.add(ar.FailedClassName)}rejected(){this.removeClasses(),this.dialog.classList.add(ar.RejectedClassName)}removeClasses(){this.dialog.classList.remove(ar.ShowClassName,ar.HideClassName,ar.FailedClassName,ar.RejectedClassName)}}ar.ShowClassName="components-reconnect-show",ar.HideClassName="components-reconnect-hide",ar.FailedClassName="components-reconnect-failed",ar.RejectedClassName="components-reconnect-rejected",ar.MaxRetriesId="components-reconnect-max-retries",ar.CurrentAttemptId="components-reconnect-current-attempt";class cr{constructor(e,t,n){this._currentReconnectionProcess=null,this._logger=e,this._reconnectionDisplay=t,this._reconnectCallback=n||Ye.reconnect}onConnectionDown(e,t){if(!this._reconnectionDisplay){const t=document.getElementById(e.dialogId);this._reconnectionDisplay=t?new ar(t,e.maxRetries,document):new sr(e.dialogId,e.maxRetries,document,this._logger)}this._currentReconnectionProcess||(this._currentReconnectionProcess=new lr(e,this._logger,this._reconnectCallback,this._reconnectionDisplay))}onConnectionUp(){this._currentReconnectionProcess&&(this._currentReconnectionProcess.dispose(),this._currentReconnectionProcess=null)}}class lr{constructor(e,t,n,r){this.logger=t,this.reconnectCallback=n,this.isDisposed=!1,this.reconnectDisplay=r,this.reconnectDisplay.show(),this.attemptPeriodicReconnection(e)}dispose(){this.isDisposed=!0,this.reconnectDisplay.hide()}async attemptPeriodicReconnection(e){for(let t=0;tlr.MaximumFirstRetryInterval?lr.MaximumFirstRetryInterval:e.retryIntervalMilliseconds;if(await this.delay(n),this.isDisposed)break;try{return await this.reconnectCallback()?void 0:void this.reconnectDisplay.rejected()}catch(e){this.logger.log(er.Error,e)}}this.reconnectDisplay.failed()}delay(e){return new Promise((t=>setTimeout(t,e)))}}lr.MaximumFirstRetryInterval=3e3;const hr=/^\s*Blazor-Component-State:(?[a-zA-Z0-9+/=]+)$/;function ur(e){var t;if(e.nodeType===Node.COMMENT_NODE){const n=e.textContent||"",r=hr.exec(n),o=r&&r.groups&&r.groups.state;return o&&(null===(t=e.parentNode)||void 0===t||t.removeChild(e)),o}if(!e.hasChildNodes())return;const n=e.childNodes;for(let e=0;e.*)$/);function fr(e,t){const n=e.currentElement;if(n&&n.nodeType===Node.COMMENT_NODE&&n.textContent){const r=pr.exec(n.textContent),o=r&&r.groups&&r.groups.descriptor;if(!o)return;try{const r=function(e){const t=JSON.parse(e),{type:n}=t;if("server"!==n&&"webassembly"!==n)throw new Error(`Invalid component type '${n}'.`);return t}(o);switch(t){case"webassembly":return function(e,t,n){const{type:r,assembly:o,typeName:i,parameterDefinitions:s,parameterValues:a,prerenderId:c}=e;if("webassembly"===r){if(!o)throw new Error("assembly must be defined when using a descriptor.");if(!i)throw new Error("typeName must be defined when using a descriptor.");if(c){const e=gr(c,n);if(!e)throw new Error(`Could not find an end component comment for '${t}'`);return{type:r,assembly:o,typeName:i,parameterDefinitions:s&&atob(s),parameterValues:a&&atob(a),start:t,prerenderId:c,end:e}}return{type:r,assembly:o,typeName:i,parameterDefinitions:s&&atob(s),parameterValues:a&&atob(a),start:t}}}(r,n,e);case"server":return function(e,t,n){const{type:r,descriptor:o,sequence:i,prerenderId:s}=e;if("server"===r){if(!o)throw new Error("descriptor must be defined when using a descriptor.");if(void 0===i)throw new Error("sequence must be defined when using a descriptor.");if(!Number.isInteger(i))throw new Error(`Error parsing the sequence '${i}' for component '${JSON.stringify(e)}'`);if(s){const e=gr(s,n);if(!e)throw new Error(`Could not find an end component comment for '${t}'`);return{type:r,sequence:i,descriptor:o,start:t,prerenderId:s,end:e}}return{type:r,sequence:i,descriptor:o,start:t}}}(r,n,e)}}catch(e){throw new Error(`Found malformed component comment at ${n.textContent}`)}}}function gr(e,t){for(;t.next()&&t.currentElement;){const n=t.currentElement;if(n.nodeType!==Node.COMMENT_NODE)continue;if(!n.textContent)continue;const r=pr.exec(n.textContent),o=r&&r[1];if(o)return mr(o,e),n}}function mr(e,t){const n=JSON.parse(e);if(1!==Object.keys(n).length)throw new Error(`Invalid end of component comment: '${e}'`);const r=n.prerenderId;if(!r)throw new Error(`End of component comment must have a value for the prerendered property: '${e}'`);if(r!==t)throw new Error(`End of component comment prerendered property must match the start comment prerender id: '${t}', '${r}'`)}class yr{constructor(e){this.childNodes=e,this.currentIndex=-1,this.length=e.length}next(){return this.currentIndex++,this.currentIndexasync function(e,n){const r=function(e){const t=document.baseURI;return t.endsWith("/")?`${t}${e}`:`${t}/${e}`}(n),o=await import(r);if(void 0===o)return;const{beforeStart:i,afterStarted:s}=o;return s&&e.afterStartedCallbacks.push(s),i?i(...t):void 0}(this,e))))}async invokeAfterStartedCallbacks(e){await C,await Promise.all(this.afterStartedCallbacks.map((t=>t(e))))}}let _r,Er=!1,Sr=!1;async function Cr(e){if(Sr)throw new Error("Blazor has already started.");Sr=!0;const t=function(e){const t={...ir,...e};return e&&e.reconnectionOptions&&(t.reconnectionOptions={...ir.reconnectionOptions,...e.reconnectionOptions}),t}(e),n=await async function(e){const t=await fetch("_blazor/initializers",{method:"GET",credentials:"include",cache:"no-cache"}),n=await t.json(),r=new br;return await r.importInitializersAsync(n,[e]),r}(t),r=new rr(t.logLevel);Ye.reconnect=async e=>{if(Er)return!1;const n=e||await Ir(t,r,s);return await s.reconnect(n)?(t.reconnectionHandler.onConnectionUp(),!0):(r.log(er.Information,"Reconnection attempt to the circuit was rejected by the server. This may indicate that the associated state is no longer available on the server."),!1)},Ye.defaultReconnectionHandler=new cr(r),t.reconnectionHandler=t.reconnectionHandler||Ye.defaultReconnectionHandler,r.log(er.Information,"Starting up Blazor server-side application.");const o=function(e,t){return function(e){const t=dr(e,"server"),n=[];for(let e=0;ee.sequence-t.sequence))}(e)}(document),i=ur(document),s=new or(o,i||"");Ye._internal.navigationManager.listenForNavigationEvents(((e,t,n)=>_r.send("OnLocationChanged",e,t,n)),((e,t,n,r)=>_r.send("OnLocationChanging",e,t,n,r))),Ye._internal.forceCloseConnection=()=>_r.stop(),Ye._internal.sendJSDataStream=(e,t,n)=>function(e,t,n,r){setTimeout((async()=>{let o=5,i=(new Date).valueOf();try{const s=t instanceof Blob?t.size:t.byteLength;let a=0,c=0;for(;a1)await e.send("ReceiveJSDataChunk",n,c,h,null);else{if(!await e.invoke("ReceiveJSDataChunk",n,c,h,null))break;const t=(new Date).valueOf(),r=t-i;i=t,o=Math.max(1,Math.round(500/Math.max(1,r)))}a+=l,c++}}catch(t){await e.send("ReceiveJSDataChunk",n,-1,null,t.toString())}}),0)}(_r,e,t,n);const a=await Ir(t,r,s);if(!await s.startCircuit(a))return void r.log(er.Error,"Failed to start the circuit.");let c=!1;const l=()=>{if(!c){const e=new FormData,t=s.circuitId;e.append("circuitId",t),c=navigator.sendBeacon("_blazor/disconnect",e)}};Ye.disconnect=l,window.addEventListener("unload",l,{capture:!1,once:!0}),r.log(er.Information,"Blazor server-side application started."),n.invokeAfterStartedCallbacks(Ye)}async function Ir(t,n,r){var o,i;const s=new Bn;s.name="blazorpack";const a=(new Vt).withUrl("_blazor").withHubProtocol(s);t.configureSignalR(a);const c=a.build();c.on("JS.AttachComponent",((e,t)=>function(e,t,n,r){let o=pe[0];o||(o=new ae(0),pe[0]=o),o.attachRootComponentToLogicalElement(n,t,!1)}(0,r.resolveElement(t),e))),c.on("JS.BeginInvokeJS",e.jsCallDispatcher.beginInvokeJSFromDotNet),c.on("JS.EndInvokeDotNet",e.jsCallDispatcher.endInvokeDotNetFromJS),c.on("JS.ReceiveByteArray",e.jsCallDispatcher.receiveByteArray),c.on("JS.BeginTransmitStream",(t=>{const n=new ReadableStream({start(e){c.stream("SendDotNetStreamToJS",t).subscribe({next:t=>e.enqueue(t),complete:()=>e.close(),error:t=>e.error(t)})}});e.jsCallDispatcher.supplyDotNetStream(t,n)}));const l=tr.getOrCreate(n);c.on("JS.RenderBatch",((e,t)=>{n.log(er.Debug,`Received render batch with id ${e} and ${t.byteLength} bytes.`),l.processBatch(e,t,c)})),c.on("JS.EndLocationChanging",Ye._internal.navigationManager.endLocationChanging),c.onclose((e=>!Er&&t.reconnectionHandler.onConnectionDown(t.reconnectionOptions,e))),c.on("JS.Error",(e=>{Er=!0,kr(c,e,n),On()}));try{await c.start(),_r=c}catch(e){if(kr(c,e,n),"FailedToNegotiateWithServerError"===e.errorType)throw e;On(),e.innerErrors&&(e.innerErrors.some((e=>"UnsupportedTransportError"===e.errorType&&e.transport===Rt.WebSockets))?n.log(er.Error,"Unable to connect, please ensure you are using an updated browser that supports WebSockets."):e.innerErrors.some((e=>"FailedToStartTransportError"===e.errorType&&e.transport===Rt.WebSockets))?n.log(er.Error,"Unable to connect, please ensure WebSockets are available. A VPN or proxy may be blocking the connection."):e.innerErrors.some((e=>"DisabledTransportError"===e.errorType&&e.transport===Rt.LongPolling))&&n.log(er.Error,"Unable to initiate a SignalR connection to the server. This might be because the server is not configured to support WebSockets. For additional details, visit https://aka.ms/blazor-server-websockets-error."))}return(null===(i=null===(o=c.connection)||void 0===o?void 0:o.features)||void 0===i?void 0:i.inherentKeepAlive)&&n.log(er.Warning,"Failed to connect via WebSockets, using the Long Polling fallback transport. This may be due to a VPN or proxy blocking the connection. To troubleshoot this, visit https://aka.ms/blazor-server-using-fallback-long-polling."),e.attachDispatcher({beginInvokeDotNetFromJS:(e,t,n,r,o)=>{c.send("BeginInvokeDotNetFromJS",e?e.toString():null,t,n,r||0,o)},endInvokeJSFromDotNet:(e,t,n)=>{c.send("EndInvokeJSFromDotNet",e,t,n)},sendByteArray:(e,t)=>{c.send("ReceiveByteArray",e,t)}}),c}function kr(e,t,n){n.log(er.Error,t),e&&e.stop()}Ye.start=Cr,document&&document.currentScript&&"false"!==document.currentScript.getAttribute("autostart")&&Cr()})(); \ No newline at end of file diff --git a/src/Components/Web.JS/dist/Release/blazor.webview.js b/src/Components/Web.JS/dist/Release/blazor.webview.js index a334713d2dcc..3c8b84a49b57 100644 --- a/src/Components/Web.JS/dist/Release/blazor.webview.js +++ b/src/Components/Web.JS/dist/Release/blazor.webview.js @@ -1 +1 @@ -(()=>{"use strict";var e,t,n;!function(e){window.DotNet=e;const t=[],n=new Map,r=new Map,o="__jsObjectId",a="__dotNetObject",s="__byte[]",i="__dotNetStream",c="__jsStreamReferenceLength";class l{constructor(e){this._jsObject=e,this._cachedFunctions=new Map}findFunction(e){const t=this._cachedFunctions.get(e);if(t)return t;let n,r=this._jsObject;if(e.split(".").forEach((t=>{if(!(t in r))throw new Error(`Could not find '${e}' ('${t}' was undefined).`);n=r,r=r[t]})),r instanceof Function)return r=r.bind(n),this._cachedFunctions.set(e,r),r;throw new Error(`The value '${e}' is not a function.`)}getWrappedObject(){return this._jsObject}}const u={},d={0:new l(window)};d[0]._cachedFunctions.set("import",(e=>("string"==typeof e&&e.startsWith("./")&&(e=new URL(e.substr(2),document.baseURI).toString()),import(e))));let f,h=1,p=1,m=null;function v(e){t.push(e)}function g(e){if(e&&"object"==typeof e){d[p]=new l(e);const t={[o]:p};return p++,t}throw new Error(`Cannot create a JSObjectReference from the value '${e}'.`)}function b(e){let t=-1;if(e instanceof ArrayBuffer&&(e=new Uint8Array(e)),e instanceof Blob)t=e.size;else{if(!(e.buffer instanceof ArrayBuffer))throw new Error("Supplied value is not a typed array or blob.");if(void 0===e.byteLength)throw new Error(`Cannot create a JSStreamReference from the value '${e}' as it doesn't have a byteLength.`);t=e.byteLength}const n={[c]:t};try{const t=g(e);n[o]=t[o]}catch(t){throw new Error(`Cannot create a JSStreamReference from the value '${e}'.`)}return n}function y(e){return e?JSON.parse(e,((e,n)=>t.reduce(((t,n)=>n(e,t)),n))):null}function w(e,t,n,r){const o=S();if(o.invokeDotNetFromJS){const a=L(r),s=o.invokeDotNetFromJS(e,t,n,a);return s?y(s):null}throw new Error("The current dispatcher does not support synchronous calls from JS to .NET. Use invokeMethodAsync instead.")}function E(e,t,n,r){if(e&&n)throw new Error(`For instance method calls, assemblyName should be null. Received '${e}'.`);const o=h++,a=new Promise(((e,t)=>{u[o]={resolve:e,reject:t}}));try{const a=L(r);S().beginInvokeDotNetFromJS(o,e,t,n,a)}catch(e){I(o,!1,e)}return a}function S(){if(null!==m)return m;throw new Error("No .NET call dispatcher has been set.")}function I(e,t,n){if(!u.hasOwnProperty(e))throw new Error(`There is no pending async call with ID ${e}.`);const r=u[e];delete u[e],t?r.resolve(n):r.reject(n)}function C(e){return e instanceof Error?`${e.message}\n${e.stack}`:e?e.toString():"null"}function A(e,t){const n=d[t];if(n)return n.findFunction(e);throw new Error(`JS object instance with ID ${t} does not exist (has it been disposed?).`)}function D(e){delete d[e]}e.attachDispatcher=function(e){m=e},e.attachReviver=v,e.invokeMethod=function(e,t,...n){return w(e,t,null,n)},e.invokeMethodAsync=function(e,t,...n){return E(e,t,null,n)},e.createJSObjectReference=g,e.createJSStreamReference=b,e.disposeJSObjectReference=function(e){const t=e&&e[o];"number"==typeof t&&D(t)},function(e){e[e.Default=0]="Default",e[e.JSObjectReference=1]="JSObjectReference",e[e.JSStreamReference=2]="JSStreamReference",e[e.JSVoidResult=3]="JSVoidResult"}(f=e.JSCallResultType||(e.JSCallResultType={})),e.jsCallDispatcher={findJSFunction:A,disposeJSObjectReferenceById:D,invokeJSFromDotNet:(e,t,n,r)=>{const o=N(A(e,r).apply(null,y(t)),n);return null==o?null:L(o)},beginInvokeJSFromDotNet:(e,t,n,r,o)=>{const a=new Promise((e=>{e(A(t,o).apply(null,y(n)))}));e&&a.then((t=>L([e,!0,N(t,r)]))).then((t=>S().endInvokeJSFromDotNet(e,!0,t)),(t=>S().endInvokeJSFromDotNet(e,!1,JSON.stringify([e,!1,C(t)]))))},endInvokeDotNetFromJS:(e,t,n)=>{const r=t?y(n):new Error(n);I(parseInt(e,10),t,r)},receiveByteArray:(e,t)=>{n.set(e,t)},supplyDotNetStream:(e,t)=>{if(r.has(e)){const n=r.get(e);r.delete(e),n.resolve(t)}else{const n=new T;n.resolve(t),r.set(e,n)}}};class k{constructor(e){this._id=e}invokeMethod(e,...t){return w(null,e,this._id,t)}invokeMethodAsync(e,...t){return E(null,e,this._id,t)}dispose(){E(null,"__Dispose",this._id,null).catch((e=>console.error(e)))}serializeAsArg(){return{__dotNetObject:this._id}}}e.DotNetObject=k,v((function(e,t){if(t&&"object"==typeof t){if(t.hasOwnProperty(a))return new k(t[a]);if(t.hasOwnProperty(o)){const e=t[o],n=d[e];if(n)return n.getWrappedObject();throw new Error(`JS object instance with Id '${e}' does not exist. It may have been disposed.`)}if(t.hasOwnProperty(s)){const e=t[s],r=n.get(e);if(void 0===r)throw new Error(`Byte array index '${e}' does not exist.`);return n.delete(e),r}if(t.hasOwnProperty(i))return new R(t[i])}return t}));class R{constructor(e){if(r.has(e))this._streamPromise=r.get(e).streamPromise,r.delete(e);else{const t=new T;r.set(e,t),this._streamPromise=t.streamPromise}}stream(){return this._streamPromise}async arrayBuffer(){return new Response(await this.stream()).arrayBuffer()}}class T{constructor(){this.streamPromise=new Promise(((e,t)=>{this.resolve=e,this.reject=t}))}}function N(e,t){switch(t){case f.Default:return e;case f.JSObjectReference:return g(e);case f.JSStreamReference:return b(e);case f.JSVoidResult:return null;default:throw new Error(`Invalid JS call result type '${t}'.`)}}let O=0;function L(e){return O=0,JSON.stringify(e,_)}function _(e,t){if(t instanceof k)return t.serializeAsArg();if(t instanceof Uint8Array){m.sendByteArray(O,t);const e={[s]:O};return O++,e}return t}}(e||(e={})),function(e){e[e.prependFrame=1]="prependFrame",e[e.removeFrame=2]="removeFrame",e[e.setAttribute=3]="setAttribute",e[e.removeAttribute=4]="removeAttribute",e[e.updateText=5]="updateText",e[e.stepIn=6]="stepIn",e[e.stepOut=7]="stepOut",e[e.updateMarkup=8]="updateMarkup",e[e.permutationListEntry=9]="permutationListEntry",e[e.permutationListEnd=10]="permutationListEnd"}(t||(t={})),function(e){e[e.element=1]="element",e[e.text=2]="text",e[e.attribute=3]="attribute",e[e.component=4]="component",e[e.region=5]="region",e[e.elementReferenceCapture=6]="elementReferenceCapture",e[e.markup=8]="markup"}(n||(n={}));class r{constructor(e,t){this.componentId=e,this.fieldValue=t}static fromEvent(e,t){const n=t.target;if(n instanceof Element){const t=function(e){return e instanceof HTMLInputElement?e.type&&"checkbox"===e.type.toLowerCase()?{value:e.checked}:{value:e.value}:e instanceof HTMLSelectElement||e instanceof HTMLTextAreaElement?{value:e.value}:null}(n);if(t)return new r(e,t.value)}return null}}const o=new Map,a=new Map,s=[];function i(e){return o.get(e)}function c(e){const t=o.get(e);return(null==t?void 0:t.browserEventName)||e}function l(e,t){e.forEach((e=>o.set(e,t)))}function u(e){const t=[];for(let n=0;ne.selected)).map((e=>e.value))}}{const e=function(e){return!!e&&"INPUT"===e.tagName&&"checkbox"===e.getAttribute("type")}(t);return{value:e?!!t.checked:t.value}}}}),l(["copy","cut","paste"],{createEventArgs:e=>({type:e.type})}),l(["drag","dragend","dragenter","dragleave","dragover","dragstart","drop"],{createEventArgs:e=>{return{...d(t=e),dataTransfer:t.dataTransfer?{dropEffect:t.dataTransfer.dropEffect,effectAllowed:t.dataTransfer.effectAllowed,files:Array.from(t.dataTransfer.files).map((e=>e.name)),items:Array.from(t.dataTransfer.items).map((e=>({kind:e.kind,type:e.type}))),types:t.dataTransfer.types}:null};var t}}),l(["focus","blur","focusin","focusout"],{createEventArgs:e=>({type:e.type})}),l(["keydown","keyup","keypress"],{createEventArgs:e=>{return{key:(t=e).key,code:t.code,location:t.location,repeat:t.repeat,ctrlKey:t.ctrlKey,shiftKey:t.shiftKey,altKey:t.altKey,metaKey:t.metaKey,type:t.type};var t}}),l(["contextmenu","click","mouseover","mouseout","mousemove","mousedown","mouseup","mouseleave","mouseenter","dblclick"],{createEventArgs:e=>d(e)}),l(["error"],{createEventArgs:e=>{return{message:(t=e).message,filename:t.filename,lineno:t.lineno,colno:t.colno,type:t.type};var t}}),l(["loadstart","timeout","abort","load","loadend","progress"],{createEventArgs:e=>{return{lengthComputable:(t=e).lengthComputable,loaded:t.loaded,total:t.total,type:t.type};var t}}),l(["touchcancel","touchend","touchmove","touchenter","touchleave","touchstart"],{createEventArgs:e=>{return{detail:(t=e).detail,touches:u(t.touches),targetTouches:u(t.targetTouches),changedTouches:u(t.changedTouches),ctrlKey:t.ctrlKey,shiftKey:t.shiftKey,altKey:t.altKey,metaKey:t.metaKey,type:t.type};var t}}),l(["gotpointercapture","lostpointercapture","pointercancel","pointerdown","pointerenter","pointerleave","pointermove","pointerout","pointerover","pointerup"],{createEventArgs:e=>{return{...d(t=e),pointerId:t.pointerId,width:t.width,height:t.height,pressure:t.pressure,tiltX:t.tiltX,tiltY:t.tiltY,pointerType:t.pointerType,isPrimary:t.isPrimary};var t}}),l(["wheel","mousewheel"],{createEventArgs:e=>{return{...d(t=e),deltaX:t.deltaX,deltaY:t.deltaY,deltaZ:t.deltaZ,deltaMode:t.deltaMode};var t}}),l(["toggle"],{createEventArgs:()=>({})});const f=["date","datetime-local","month","time","week"],h=new Map;let p,m,v=0;const g={async add(e,t,n){if(!n)throw new Error("initialParameters must be an object, even if empty.");const r="__bl-dynamic-root:"+(++v).toString();h.set(r,e);const o=await w().invokeMethodAsync("AddRootComponent",t,r),a=new y(o,m[t]);return await a.setParameters(n),a}};class b{invoke(e){return this._callback(e)}setCallback(t){this._selfJSObjectReference||(this._selfJSObjectReference=e.createJSObjectReference(this)),this._callback=t}getJSObjectReference(){return this._selfJSObjectReference}dispose(){this._selfJSObjectReference&&e.disposeJSObjectReference(this._selfJSObjectReference)}}class y{constructor(e,t){this._jsEventCallbackWrappers=new Map,this._componentId=e;for(const e of t)"eventcallback"===e.type&&this._jsEventCallbackWrappers.set(e.name.toLowerCase(),new b)}setParameters(e){const t={},n=Object.entries(e||{}),r=n.length;for(const[e,r]of n){const n=this._jsEventCallbackWrappers.get(e.toLowerCase());n&&r?(n.setCallback(r),t[e]=n.getJSObjectReference()):t[e]=r}return w().invokeMethodAsync("SetRootComponentParameters",this._componentId,r,t)}async dispose(){if(null!==this._componentId){await w().invokeMethodAsync("RemoveRootComponent",this._componentId),this._componentId=null;for(const e of this._jsEventCallbackWrappers.values())e.dispose()}}}function w(){if(!p)throw new Error("Dynamic root components have not been enabled in this application.");return p}const E=new Map;let S;const I=new Promise((e=>{S=e}));function C(e,t,n){return D(e,t.eventHandlerId,(()=>A(e).invokeMethodAsync("DispatchEventAsync",t,n)))}function A(e){const t=E.get(e);if(!t)throw new Error(`No interop methods are registered for renderer ${e}`);return t}let D=(e,t,n)=>n();const k=_(["abort","blur","canplay","canplaythrough","change","cuechange","durationchange","emptied","ended","error","focus","load","loadeddata","loadedmetadata","loadend","loadstart","mouseenter","mouseleave","pointerenter","pointerleave","pause","play","playing","progress","ratechange","reset","scroll","seeked","seeking","stalled","submit","suspend","timeupdate","toggle","unload","volumechange","waiting","DOMNodeInsertedIntoDocument","DOMNodeRemovedFromDocument"]),R={submit:!0},T=_(["click","dblclick","mousedown","mousemove","mouseup"]);class N{constructor(e){this.browserRendererId=e,this.afterClickCallbacks=[];const t=++N.nextEventDelegatorId;this.eventsCollectionKey=`_blazorEvents_${t}`,this.eventInfoStore=new O(this.onGlobalEvent.bind(this))}setListener(e,t,n,r){const o=this.getEventHandlerInfosForElement(e,!0),a=o.getHandler(t);if(a)this.eventInfoStore.update(a.eventHandlerId,n);else{const a={element:e,eventName:t,eventHandlerId:n,renderingComponentId:r};this.eventInfoStore.add(a),o.setHandler(t,a)}}getHandler(e){return this.eventInfoStore.get(e)}removeListener(e){const t=this.eventInfoStore.remove(e);if(t){const e=t.element,n=this.getEventHandlerInfosForElement(e,!1);n&&n.removeHandler(t.eventName)}}notifyAfterClick(e){this.afterClickCallbacks.push(e),this.eventInfoStore.addGlobalListener("click")}setStopPropagation(e,t,n){this.getEventHandlerInfosForElement(e,!0).stopPropagation(t,n)}setPreventDefault(e,t,n){this.getEventHandlerInfosForElement(e,!0).preventDefault(t,n)}onGlobalEvent(e){if(!(e.target instanceof Element))return;this.dispatchGlobalEventToAllElements(e.type,e);const t=(n=e.type,a.get(n));var n;t&&t.forEach((t=>this.dispatchGlobalEventToAllElements(t,e))),"click"===e.type&&this.afterClickCallbacks.forEach((t=>t(e)))}dispatchGlobalEventToAllElements(e,t){const n=t.composedPath();let o=n.shift(),a=null,s=!1;const c=Object.prototype.hasOwnProperty.call(k,e);let l=!1;for(;o;){const f=o,h=this.getEventHandlerInfosForElement(f,!1);if(h){const n=h.getHandler(e);if(n&&(u=f,d=t.type,!((u instanceof HTMLButtonElement||u instanceof HTMLInputElement||u instanceof HTMLTextAreaElement||u instanceof HTMLSelectElement)&&Object.prototype.hasOwnProperty.call(T,d)&&u.disabled))){if(!s){const n=i(e);a=(null==n?void 0:n.createEventArgs)?n.createEventArgs(t):{},s=!0}Object.prototype.hasOwnProperty.call(R,t.type)&&t.preventDefault(),C(this.browserRendererId,{eventHandlerId:n.eventHandlerId,eventName:e,eventFieldInfo:r.fromEvent(n.renderingComponentId,t)},a)}h.stopPropagation(e)&&(l=!0),h.preventDefault(e)&&t.preventDefault()}o=c||l?void 0:n.shift()}var u,d}getEventHandlerInfosForElement(e,t){return Object.prototype.hasOwnProperty.call(e,this.eventsCollectionKey)?e[this.eventsCollectionKey]:t?e[this.eventsCollectionKey]=new L:null}}N.nextEventDelegatorId=0;class O{constructor(e){this.globalListener=e,this.infosByEventHandlerId={},this.countByEventName={},s.push(this.handleEventNameAliasAdded.bind(this))}add(e){if(this.infosByEventHandlerId[e.eventHandlerId])throw new Error(`Event ${e.eventHandlerId} is already tracked`);this.infosByEventHandlerId[e.eventHandlerId]=e,this.addGlobalListener(e.eventName)}get(e){return this.infosByEventHandlerId[e]}addGlobalListener(e){if(e=c(e),Object.prototype.hasOwnProperty.call(this.countByEventName,e))this.countByEventName[e]++;else{this.countByEventName[e]=1;const t=Object.prototype.hasOwnProperty.call(k,e);document.addEventListener(e,this.globalListener,t)}}update(e,t){if(Object.prototype.hasOwnProperty.call(this.infosByEventHandlerId,t))throw new Error(`Event ${t} is already tracked`);const n=this.infosByEventHandlerId[e];delete this.infosByEventHandlerId[e],n.eventHandlerId=t,this.infosByEventHandlerId[t]=n}remove(e){const t=this.infosByEventHandlerId[e];if(t){delete this.infosByEventHandlerId[e];const n=c(t.eventName);0==--this.countByEventName[n]&&(delete this.countByEventName[n],document.removeEventListener(n,this.globalListener))}return t}handleEventNameAliasAdded(e,t){if(Object.prototype.hasOwnProperty.call(this.countByEventName,e)){const n=this.countByEventName[e];delete this.countByEventName[e],document.removeEventListener(e,this.globalListener),this.addGlobalListener(t),this.countByEventName[t]+=n-1}}}class L{constructor(){this.handlers={},this.preventDefaultFlags=null,this.stopPropagationFlags=null}getHandler(e){return Object.prototype.hasOwnProperty.call(this.handlers,e)?this.handlers[e]:null}setHandler(e,t){this.handlers[e]=t}removeHandler(e){delete this.handlers[e]}preventDefault(e,t){return void 0!==t&&(this.preventDefaultFlags=this.preventDefaultFlags||{},this.preventDefaultFlags[e]=t),!!this.preventDefaultFlags&&this.preventDefaultFlags[e]}stopPropagation(e,t){return void 0!==t&&(this.stopPropagationFlags=this.stopPropagationFlags||{},this.stopPropagationFlags[e]=t),!!this.stopPropagationFlags&&this.stopPropagationFlags[e]}}function _(e){const t={};return e.forEach((e=>{t[e]=!0})),t}const F=G("_blazorLogicalChildren"),x=G("_blazorLogicalParent"),P=G("_blazorLogicalEnd");function j(e,t){if(e.childNodes.length>0&&!t)throw new Error("New logical elements must start empty, or allowExistingContents must be true");return F in e||(e[F]=[]),e}function H(e,t){const n=document.createComment("!");return M(n,e,t),n}function M(e,t,n){const r=e;if(e instanceof Comment&&z(r)&&z(r).length>0)throw new Error("Not implemented: inserting non-empty logical container");if(U(r))throw new Error("Not implemented: moving existing logical children");const o=z(t);if(n0;)B(n,0)}const r=n;r.parentNode.removeChild(r)}function U(e){return e[x]||null}function J(e,t){return z(e)[t]}function $(e){const t=V(e);return"http://www.w3.org/2000/svg"===t.namespaceURI&&"foreignObject"!==t.tagName}function z(e){return e[F]}function K(e,t){const n=z(e);t.forEach((e=>{e.moveRangeStart=n[e.fromSiblingIndex],e.moveRangeEnd=W(e.moveRangeStart)})),t.forEach((t=>{const r=document.createComment("marker");t.moveToBeforeMarker=r;const o=n[t.toSiblingIndex+1];o?o.parentNode.insertBefore(r,o):Y(r,e)})),t.forEach((e=>{const t=e.moveToBeforeMarker,n=t.parentNode,r=e.moveRangeStart,o=e.moveRangeEnd;let a=r;for(;a;){const e=a.nextSibling;if(n.insertBefore(a,t),a===o)break;a=e}n.removeChild(t)})),t.forEach((e=>{n[e.toSiblingIndex]=e.moveRangeStart}))}function V(e){if(e instanceof Element||e instanceof DocumentFragment)return e;if(e instanceof Comment)return e.parentNode;throw new Error("Not a valid logical element")}function X(e){const t=z(U(e));return t[Array.prototype.indexOf.call(t,e)+1]||null}function Y(e,t){if(t instanceof Element||t instanceof DocumentFragment)t.appendChild(e);else{if(!(t instanceof Comment))throw new Error(`Cannot append node because the parent is not a valid logical element. Parent: ${t}`);{const n=X(t);n?n.parentNode.insertBefore(e,n):Y(e,U(t))}}}function W(e){if(e instanceof Element||e instanceof DocumentFragment)return e;const t=X(e);if(t)return t.previousSibling;{const t=U(e);return t instanceof Element||t instanceof DocumentFragment?t.lastChild:W(t)}}function G(e){return"function"==typeof Symbol?Symbol():e}function q(e){return`_bl_${e}`}const Z="__internalId";e.attachReviver(((e,t)=>t&&"object"==typeof t&&Object.prototype.hasOwnProperty.call(t,Z)&&"string"==typeof t[Z]?function(e){const t=`[${q(e)}]`;return document.querySelector(t)}(t[Z]):t));const Q="_blazorDeferredValue",ee=document.createElement("template"),te=document.createElementNS("http://www.w3.org/2000/svg","g"),ne={},re="__internal_",oe="preventDefault_",ae="stopPropagation_";class se{constructor(e){this.rootComponentIds=new Set,this.childComponentLocations={},this.eventDelegator=new N(e),this.eventDelegator.notifyAfterClick((e=>{if(!pe)return;if(0!==e.button||function(e){return e.ctrlKey||e.shiftKey||e.altKey||e.metaKey}(e))return;if(e.defaultPrevented)return;const t=function(e){const t=!window._blazorDisableComposedPath&&e.composedPath&&e.composedPath();if(t){for(let e=0;edocument.baseURI,getLocationHref:()=>location.href};function Ce(e,t,n=!1){const r=Le(e);!t.forceLoad&&Fe(r)?Ae(r,!1,t.replaceHistoryEntry,t.historyEntryState,n):function(e,t){if(location.href===e){const t=e+"?";history.replaceState(null,"",t),location.replace(e)}else t?location.replace(e):location.href=e}(e,t.replaceHistoryEntry)}async function Ae(e,t,n,r,o=!1){ke(),(o||!ve||await Re(e,r,t))&&(he=!0,n?history.replaceState({userState:r,_index:ge},"",e):(ge++,history.pushState({userState:r,_index:ge},"",e)),await Te(t))}function De(e){return new Promise((t=>{const n=Ee;Ee=()=>{Ee=n,t()},history.go(e)}))}function ke(){Se&&(Se(!1),Se=null)}function Re(e,t,n){return new Promise((r=>{ke(),we?(be++,Se=r,we(be,e,t,n)):r(!1)}))}async function Te(e){var t;ye&&await ye(location.href,null===(t=history.state)||void 0===t?void 0:t.userState,e)}async function Ne(e){var t,n;Ee&&await Ee(e),ge=null!==(n=null===(t=history.state)||void 0===t?void 0:t._index)&&void 0!==n?n:0}let Oe;function Le(e){return Oe=Oe||document.createElement("a"),Oe.href=e,Oe.href}function _e(e,t){return e?e.tagName===t?e:_e(e.parentElement,t):null}function Fe(e){const t=(n=document.baseURI).substring(0,n.lastIndexOf("/"));var n;const r=e.charAt(t.length);return e.startsWith(t)&&(""===r||"/"===r||"?"===r||"#"===r)}const xe={focus:function(e,t){if(e instanceof HTMLElement)e.focus({preventScroll:t});else{if(!(e instanceof SVGElement))throw new Error("Unable to focus an invalid element.");if(!e.hasAttribute("tabindex"))throw new Error("Unable to focus an SVG element that does not have a tabindex.");e.focus({preventScroll:t})}},focusBySelector:function(e){const t=document.querySelector(e);t&&(t.hasAttribute("tabindex")||(t.tabIndex=-1),t.focus())}},Pe={init:function(e,t,n,r=50){const o=He(t);(o||document.documentElement).style.overflowAnchor="none";const a=document.createRange();u(n.parentElement)&&(t.style.display="table-row",n.style.display="table-row");const s=new IntersectionObserver((function(r){r.forEach((r=>{var o;if(!r.isIntersecting)return;a.setStartAfter(t),a.setEndBefore(n);const s=a.getBoundingClientRect().height,i=null===(o=r.rootBounds)||void 0===o?void 0:o.height;r.target===t?e.invokeMethodAsync("OnSpacerBeforeVisible",r.intersectionRect.top-r.boundingClientRect.top,s,i):r.target===n&&n.offsetHeight>0&&e.invokeMethodAsync("OnSpacerAfterVisible",r.boundingClientRect.bottom-r.intersectionRect.bottom,s,i)}))}),{root:o,rootMargin:`${r}px`});s.observe(t),s.observe(n);const i=l(t),c=l(n);function l(e){const t={attributes:!0},n=new MutationObserver(((n,r)=>{u(e.parentElement)&&(r.disconnect(),e.style.display="table-row",r.observe(e,t)),s.unobserve(e),s.observe(e)}));return n.observe(e,t),n}function u(e){return null!==e&&(e instanceof HTMLTableElement&&""===e.style.display||"table"===e.style.display||e instanceof HTMLTableSectionElement&&""===e.style.display||"table-row-group"===e.style.display)}je[e._id]={intersectionObserver:s,mutationObserverBefore:i,mutationObserverAfter:c}},dispose:function(e){const t=je[e._id];t&&(t.intersectionObserver.disconnect(),t.mutationObserverBefore.disconnect(),t.mutationObserverAfter.disconnect(),e.dispose(),delete je[e._id])}},je={};function He(e){return e&&e!==document.body&&e!==document.documentElement?"visible"!==getComputedStyle(e).overflowY?e:He(e.parentElement):null}const Me={getAndRemoveExistingTitle:function(){var e;const t=document.head?document.head.getElementsByTagName("title"):[];if(0===t.length)return null;let n=null;for(let r=t.length-1;r>=0;r--){const o=t[r],a=o.previousSibling;a instanceof Comment&&null!==U(a)||(null===n&&(n=o.textContent),null===(e=o.parentNode)||void 0===e||e.removeChild(o))}return n}},Be={init:function(e,t){t._blazorInputFileNextFileId=0,t.addEventListener("click",(function(){t.value=""})),t.addEventListener("change",(function(){t._blazorFilesById={};const n=Array.prototype.map.call(t.files,(function(e){const n={id:++t._blazorInputFileNextFileId,lastModified:new Date(e.lastModified).toISOString(),name:e.name,size:e.size,contentType:e.type,readPromise:void 0,arrayBuffer:void 0,blob:e};return t._blazorFilesById[n.id]=n,n}));e.invokeMethodAsync("NotifyChange",n)}))},toImageFile:async function(e,t,n,r,o){const a=Ue(e,t),s=await new Promise((function(e){const t=new Image;t.onload=function(){URL.revokeObjectURL(t.src),e(t)},t.onerror=function(){t.onerror=null,URL.revokeObjectURL(t.src)},t.src=URL.createObjectURL(a.blob)})),i=await new Promise((function(e){var t;const a=Math.min(1,r/s.width),i=Math.min(1,o/s.height),c=Math.min(a,i),l=document.createElement("canvas");l.width=Math.round(s.width*c),l.height=Math.round(s.height*c),null===(t=l.getContext("2d"))||void 0===t||t.drawImage(s,0,0,l.width,l.height),l.toBlob(e,n)})),c={id:++e._blazorInputFileNextFileId,lastModified:a.lastModified,name:a.name,size:(null==i?void 0:i.size)||0,contentType:n,blob:i||a.blob};return e._blazorFilesById[c.id]=c,c},readFileData:async function(e,t){return Ue(e,t).blob}};function Ue(e,t){const n=e._blazorFilesById[t];if(!n)throw new Error(`There is no file with ID ${t}. The file list may have changed. See https://aka.ms/aspnet/blazor-input-file-multiple-selections.`);return n}const Je=new Set,$e={enableNavigationPrompt:function(e){0===Je.size&&window.addEventListener("beforeunload",ze),Je.add(e)},disableNavigationPrompt:function(e){Je.delete(e),0===Je.size&&window.removeEventListener("beforeunload",ze)}};function ze(e){e.preventDefault(),e.returnValue=!0}const Ke=new Map,Ve={navigateTo:function(e,t,n=!1){Ce(e,t instanceof Object?t:{forceLoad:t,replaceHistoryEntry:n})},registerCustomEventType:function(e,t){if(!t)throw new Error("The options parameter is required.");if(o.has(e))throw new Error(`The event '${e}' is already registered.`);if(t.browserEventName){const n=a.get(t.browserEventName);n?n.push(e):a.set(t.browserEventName,[e]),s.forEach((n=>n(e,t.browserEventName)))}o.set(e,t)},rootComponents:g,_internal:{navigationManager:Ie,domWrapper:xe,Virtualize:Pe,PageTitle:Me,InputFile:Be,NavigationLock:$e,getJSDataStreamChunk:async function(e,t,n){return e instanceof Blob?await async function(e,t,n){const r=e.slice(t,t+n),o=await r.arrayBuffer();return new Uint8Array(o)}(e,t,n):function(e,t,n){return new Uint8Array(e.buffer,e.byteOffset+t,n)}(e,t,n)},receiveDotNetDataStream:function(t,n,r,o){let a=Ke.get(t);if(!a){const n=new ReadableStream({start(e){Ke.set(t,e),a=e}});e.jsCallDispatcher.supplyDotNetStream(t,n)}o?(a.error(o),Ke.delete(t)):0===r?(a.close(),Ke.delete(t)):a.enqueue(n.length===r?n:n.subarray(0,r))},attachWebRendererInterop:function(t,n,r,o){if(E.has(t))throw new Error(`Interop methods are already registered for renderer ${t}`);E.set(t,n),Object.keys(r).length>0&&function(t,n,r){if(p)throw new Error("Dynamic root components have already been enabled.");p=t,m=n;for(const[t,o]of Object.entries(r)){const r=e.jsCallDispatcher.findJSFunction(t,0);for(const e of o)r(e,n[e])}}(A(t),r,o),S()}}};window.Blazor=Ve;let Xe=!1;const Ye="function"==typeof TextDecoder?new TextDecoder("utf-8"):null,We=Ye?Ye.decode.bind(Ye):function(e){let t=0;const n=e.length,r=[],o=[];for(;t65535&&(o-=65536,r.push(o>>>10&1023|55296),o=56320|1023&o),r.push(o)}r.length>1024&&(o.push(String.fromCharCode.apply(null,r)),r.length=0)}return o.push(String.fromCharCode.apply(null,r)),o.join("")},Ge=Math.pow(2,32),qe=Math.pow(2,21)-1;function Ze(e,t){return e[t]|e[t+1]<<8|e[t+2]<<16|e[t+3]<<24}function Qe(e,t){return e[t]+(e[t+1]<<8)+(e[t+2]<<16)+(e[t+3]<<24>>>0)}function et(e,t){const n=Qe(e,t+4);if(n>qe)throw new Error(`Cannot read uint64 with high order part ${n}, because the result would exceed Number.MAX_SAFE_INTEGER.`);return n*Ge+Qe(e,t)}class tt{constructor(e){this.batchData=e;const t=new at(e);this.arrayRangeReader=new st(e),this.arrayBuilderSegmentReader=new it(e),this.diffReader=new nt(e),this.editReader=new rt(e,t),this.frameReader=new ot(e,t)}updatedComponents(){return Ze(this.batchData,this.batchData.length-20)}referenceFrames(){return Ze(this.batchData,this.batchData.length-16)}disposedComponentIds(){return Ze(this.batchData,this.batchData.length-12)}disposedEventHandlerIds(){return Ze(this.batchData,this.batchData.length-8)}updatedComponentsEntry(e,t){const n=e+4*t;return Ze(this.batchData,n)}referenceFramesEntry(e,t){return e+20*t}disposedComponentIdsEntry(e,t){const n=e+4*t;return Ze(this.batchData,n)}disposedEventHandlerIdsEntry(e,t){const n=e+8*t;return et(this.batchData,n)}}class nt{constructor(e){this.batchDataUint8=e}componentId(e){return Ze(this.batchDataUint8,e)}edits(e){return e+4}editsEntry(e,t){return e+16*t}}class rt{constructor(e,t){this.batchDataUint8=e,this.stringReader=t}editType(e){return Ze(this.batchDataUint8,e)}siblingIndex(e){return Ze(this.batchDataUint8,e+4)}newTreeIndex(e){return Ze(this.batchDataUint8,e+8)}moveToSiblingIndex(e){return Ze(this.batchDataUint8,e+8)}removedAttributeName(e){const t=Ze(this.batchDataUint8,e+12);return this.stringReader.readString(t)}}class ot{constructor(e,t){this.batchDataUint8=e,this.stringReader=t}frameType(e){return Ze(this.batchDataUint8,e)}subtreeLength(e){return Ze(this.batchDataUint8,e+4)}elementReferenceCaptureId(e){const t=Ze(this.batchDataUint8,e+4);return this.stringReader.readString(t)}componentId(e){return Ze(this.batchDataUint8,e+8)}elementName(e){const t=Ze(this.batchDataUint8,e+8);return this.stringReader.readString(t)}textContent(e){const t=Ze(this.batchDataUint8,e+4);return this.stringReader.readString(t)}markupContent(e){const t=Ze(this.batchDataUint8,e+4);return this.stringReader.readString(t)}attributeName(e){const t=Ze(this.batchDataUint8,e+4);return this.stringReader.readString(t)}attributeValue(e){const t=Ze(this.batchDataUint8,e+8);return this.stringReader.readString(t)}attributeEventHandlerId(e){return et(this.batchDataUint8,e+12)}}class at{constructor(e){this.batchDataUint8=e,this.stringTableStartIndex=Ze(e,e.length-4)}readString(e){if(-1===e)return null;{const n=Ze(this.batchDataUint8,this.stringTableStartIndex+4*e),r=function(e,t){let n=0,r=0;for(let o=0;o<4;o++){const a=e[t+o];if(n|=(127&a)<async function(e,n){const r=function(e){const t=document.baseURI;return t.endsWith("/")?`${t}${e}`:`${t}/${e}`}(n),o=await import(r);if(void 0===o)return;const{beforeStart:a,afterStarted:s}=o;return s&&e.afterStartedCallbacks.push(s),a?a(...t):void 0}(this,e))))}async invokeAfterStartedCallbacks(e){await I,await Promise.all(this.afterStartedCallbacks.map((t=>t(e))))}}let wt=!1;async function Et(){if(wt)throw new Error("Blazor has already started.");wt=!0;const t=await async function(){const e=await fetch("_framework/blazor.modules.json",{method:"GET",credentials:"include",cache:"no-cache"}),t=await e.json(),n=new yt;return await n.importInitializersAsync(t,[]),n}();(function(){const t={AttachToDocument:(e,t)=>{!function(e,t,n){const r="::after",o="::before";let a=!1;if(e.endsWith(r))e=e.slice(0,-r.length),a=!0;else if(e.endsWith(o))throw new Error(`The '${o}' selector is not supported.`);const s=function(e){const t=h.get(e);if(t)return h.delete(e),t}(e)||document.querySelector(e);if(!s)throw new Error(`Could not find any element matching selector '${e}'.`);!function(e,t,n,r){let o=fe[0];o||(o=new se(0),fe[0]=o),o.attachRootComponentToLogicalElement(n,t,r)}(0,j(s,!0),t,a)}(t,e)},RenderBatch:(e,t)=>{try{const n=bt(t);(function(e,t){const n=fe[0];if(!n)throw new Error("There is no browser renderer with ID 0.");const r=t.arrayRangeReader,o=t.updatedComponents(),a=r.values(o),s=r.count(o),i=t.referenceFrames(),c=r.values(i),l=t.diffReader;for(let e=0;e{lt=!0,console.error(`${e}\n${t}`),function(){const e=document.querySelector("#blazor-error-ui");e&&(e.style.display="block"),Xe||(Xe=!0,document.querySelectorAll("#blazor-error-ui .reload").forEach((e=>{e.onclick=function(e){location.reload(),e.preventDefault()}})),document.querySelectorAll("#blazor-error-ui .dismiss").forEach((e=>{e.onclick=function(e){const t=document.querySelector("#blazor-error-ui");t&&(t.style.display="none"),e.preventDefault()}})))}()},BeginInvokeJS:e.jsCallDispatcher.beginInvokeJSFromDotNet,EndInvokeDotNet:e.jsCallDispatcher.endInvokeDotNetFromJS,SendByteArrayToJS:gt,Navigate:Ie.navigateTo,SetHasLocationChangingListeners:Ie.setHasLocationChangingListeners,EndLocationChanging:Ie.endLocationChanging};window.external.receiveMessage((e=>{const n=function(e){if(lt||!e||!e.startsWith(ct))return null;const t=e.substring(ct.length),[n,...r]=JSON.parse(t);return{messageType:n,args:r}}(e);if(n){if(!Object.prototype.hasOwnProperty.call(t,n.messageType))throw new Error(`Unsupported IPC message type '${n.messageType}'`);t[n.messageType].apply(null,n.args)}}))})(),e.attachDispatcher({beginInvokeDotNetFromJS:dt,endInvokeJSFromDotNet:ft,sendByteArray:ht}),Ie.enableNavigationInterception(),Ie.listenForNavigationEvents(pt,mt),vt("AttachPage",Ie.getBaseURI(),Ie.getLocationHref()),await t.invokeAfterStartedCallbacks(Ve)}Ve.start=Et,document&&document.currentScript&&"false"!==document.currentScript.getAttribute("autostart")&&Et()})(); \ No newline at end of file +(()=>{"use strict";var e,t,n;!function(e){window.DotNet=e;const t=[],n=new Map,r=new Map,o="__jsObjectId",a="__dotNetObject",s="__byte[]",i="__dotNetStream",c="__jsStreamReferenceLength";class l{constructor(e){this._jsObject=e,this._cachedFunctions=new Map}findFunction(e){const t=this._cachedFunctions.get(e);if(t)return t;let n,r=this._jsObject;if(e.split(".").forEach((t=>{if(!(t in r))throw new Error(`Could not find '${e}' ('${t}' was undefined).`);n=r,r=r[t]})),r instanceof Function)return r=r.bind(n),this._cachedFunctions.set(e,r),r;throw new Error(`The value '${e}' is not a function.`)}getWrappedObject(){return this._jsObject}}const u={},d={0:new l(window)};d[0]._cachedFunctions.set("import",(e=>("string"==typeof e&&e.startsWith("./")&&(e=new URL(e.substr(2),document.baseURI).toString()),import(e))));let f,h=1,p=1,m=null;function v(e){t.push(e)}function g(e){if(e&&"object"==typeof e){d[p]=new l(e);const t={[o]:p};return p++,t}throw new Error(`Cannot create a JSObjectReference from the value '${e}'.`)}function b(e){let t=-1;if(e instanceof ArrayBuffer&&(e=new Uint8Array(e)),e instanceof Blob)t=e.size;else{if(!(e.buffer instanceof ArrayBuffer))throw new Error("Supplied value is not a typed array or blob.");if(void 0===e.byteLength)throw new Error(`Cannot create a JSStreamReference from the value '${e}' as it doesn't have a byteLength.`);t=e.byteLength}const n={[c]:t};try{const t=g(e);n[o]=t[o]}catch(t){throw new Error(`Cannot create a JSStreamReference from the value '${e}'.`)}return n}function y(e){return e?JSON.parse(e,((e,n)=>t.reduce(((t,n)=>n(e,t)),n))):null}function w(e,t,n,r){const o=S();if(o.invokeDotNetFromJS){const a=L(r),s=o.invokeDotNetFromJS(e,t,n,a);return s?y(s):null}throw new Error("The current dispatcher does not support synchronous calls from JS to .NET. Use invokeMethodAsync instead.")}function E(e,t,n,r){if(e&&n)throw new Error(`For instance method calls, assemblyName should be null. Received '${e}'.`);const o=h++,a=new Promise(((e,t)=>{u[o]={resolve:e,reject:t}}));try{const a=L(r);S().beginInvokeDotNetFromJS(o,e,t,n,a)}catch(e){I(o,!1,e)}return a}function S(){if(null!==m)return m;throw new Error("No .NET call dispatcher has been set.")}function I(e,t,n){if(!u.hasOwnProperty(e))throw new Error(`There is no pending async call with ID ${e}.`);const r=u[e];delete u[e],t?r.resolve(n):r.reject(n)}function C(e){return e instanceof Error?`${e.message}\n${e.stack}`:e?e.toString():"null"}function A(e,t){const n=d[t];if(n)return n.findFunction(e);throw new Error(`JS object instance with ID ${t} does not exist (has it been disposed?).`)}function D(e){delete d[e]}e.attachDispatcher=function(e){m=e},e.attachReviver=v,e.invokeMethod=function(e,t,...n){return w(e,t,null,n)},e.invokeMethodAsync=function(e,t,...n){return E(e,t,null,n)},e.createJSObjectReference=g,e.createJSStreamReference=b,e.disposeJSObjectReference=function(e){const t=e&&e[o];"number"==typeof t&&D(t)},function(e){e[e.Default=0]="Default",e[e.JSObjectReference=1]="JSObjectReference",e[e.JSStreamReference=2]="JSStreamReference",e[e.JSVoidResult=3]="JSVoidResult"}(f=e.JSCallResultType||(e.JSCallResultType={})),e.jsCallDispatcher={findJSFunction:A,disposeJSObjectReferenceById:D,invokeJSFromDotNet:(e,t,n,r)=>{const o=N(A(e,r).apply(null,y(t)),n);return null==o?null:L(o)},beginInvokeJSFromDotNet:(e,t,n,r,o)=>{const a=new Promise((e=>{e(A(t,o).apply(null,y(n)))}));e&&a.then((t=>L([e,!0,N(t,r)]))).then((t=>S().endInvokeJSFromDotNet(e,!0,t)),(t=>S().endInvokeJSFromDotNet(e,!1,JSON.stringify([e,!1,C(t)]))))},endInvokeDotNetFromJS:(e,t,n)=>{const r=t?y(n):new Error(n);I(parseInt(e,10),t,r)},receiveByteArray:(e,t)=>{n.set(e,t)},supplyDotNetStream:(e,t)=>{if(r.has(e)){const n=r.get(e);r.delete(e),n.resolve(t)}else{const n=new T;n.resolve(t),r.set(e,n)}}};class k{constructor(e){this._id=e}invokeMethod(e,...t){return w(null,e,this._id,t)}invokeMethodAsync(e,...t){return E(null,e,this._id,t)}dispose(){E(null,"__Dispose",this._id,null).catch((e=>console.error(e)))}serializeAsArg(){return{__dotNetObject:this._id}}}e.DotNetObject=k,v((function(e,t){if(t&&"object"==typeof t){if(t.hasOwnProperty(a))return new k(t[a]);if(t.hasOwnProperty(o)){const e=t[o],n=d[e];if(n)return n.getWrappedObject();throw new Error(`JS object instance with Id '${e}' does not exist. It may have been disposed.`)}if(t.hasOwnProperty(s)){const e=t[s],r=n.get(e);if(void 0===r)throw new Error(`Byte array index '${e}' does not exist.`);return n.delete(e),r}if(t.hasOwnProperty(i))return new R(t[i])}return t}));class R{constructor(e){if(r.has(e))this._streamPromise=r.get(e).streamPromise,r.delete(e);else{const t=new T;r.set(e,t),this._streamPromise=t.streamPromise}}stream(){return this._streamPromise}async arrayBuffer(){return new Response(await this.stream()).arrayBuffer()}}class T{constructor(){this.streamPromise=new Promise(((e,t)=>{this.resolve=e,this.reject=t}))}}function N(e,t){switch(t){case f.Default:return e;case f.JSObjectReference:return g(e);case f.JSStreamReference:return b(e);case f.JSVoidResult:return null;default:throw new Error(`Invalid JS call result type '${t}'.`)}}let O=0;function L(e){return O=0,JSON.stringify(e,_)}function _(e,t){if(t instanceof k)return t.serializeAsArg();if(t instanceof Uint8Array){m.sendByteArray(O,t);const e={[s]:O};return O++,e}return t}}(e||(e={})),function(e){e[e.prependFrame=1]="prependFrame",e[e.removeFrame=2]="removeFrame",e[e.setAttribute=3]="setAttribute",e[e.removeAttribute=4]="removeAttribute",e[e.updateText=5]="updateText",e[e.stepIn=6]="stepIn",e[e.stepOut=7]="stepOut",e[e.updateMarkup=8]="updateMarkup",e[e.permutationListEntry=9]="permutationListEntry",e[e.permutationListEnd=10]="permutationListEnd"}(t||(t={})),function(e){e[e.element=1]="element",e[e.text=2]="text",e[e.attribute=3]="attribute",e[e.component=4]="component",e[e.region=5]="region",e[e.elementReferenceCapture=6]="elementReferenceCapture",e[e.markup=8]="markup"}(n||(n={}));class r{constructor(e,t){this.componentId=e,this.fieldValue=t}static fromEvent(e,t){const n=t.target;if(n instanceof Element){const t=function(e){return e instanceof HTMLInputElement?e.type&&"checkbox"===e.type.toLowerCase()?{value:e.checked}:{value:e.value}:e instanceof HTMLSelectElement||e instanceof HTMLTextAreaElement?{value:e.value}:null}(n);if(t)return new r(e,t.value)}return null}}const o=new Map,a=new Map,s=[];function i(e){return o.get(e)}function c(e){const t=o.get(e);return(null==t?void 0:t.browserEventName)||e}function l(e,t){e.forEach((e=>o.set(e,t)))}function u(e){const t=[];for(let n=0;ne.selected)).map((e=>e.value))}}{const e=function(e){return!!e&&"INPUT"===e.tagName&&"checkbox"===e.getAttribute("type")}(t);return{value:e?!!t.checked:t.value}}}}),l(["copy","cut","paste"],{createEventArgs:e=>({type:e.type})}),l(["drag","dragend","dragenter","dragleave","dragover","dragstart","drop"],{createEventArgs:e=>{return{...d(t=e),dataTransfer:t.dataTransfer?{dropEffect:t.dataTransfer.dropEffect,effectAllowed:t.dataTransfer.effectAllowed,files:Array.from(t.dataTransfer.files).map((e=>e.name)),items:Array.from(t.dataTransfer.items).map((e=>({kind:e.kind,type:e.type}))),types:t.dataTransfer.types}:null};var t}}),l(["focus","blur","focusin","focusout"],{createEventArgs:e=>({type:e.type})}),l(["keydown","keyup","keypress"],{createEventArgs:e=>{return{key:(t=e).key,code:t.code,location:t.location,repeat:t.repeat,ctrlKey:t.ctrlKey,shiftKey:t.shiftKey,altKey:t.altKey,metaKey:t.metaKey,type:t.type};var t}}),l(["contextmenu","click","mouseover","mouseout","mousemove","mousedown","mouseup","mouseleave","mouseenter","dblclick"],{createEventArgs:e=>d(e)}),l(["error"],{createEventArgs:e=>{return{message:(t=e).message,filename:t.filename,lineno:t.lineno,colno:t.colno,type:t.type};var t}}),l(["loadstart","timeout","abort","load","loadend","progress"],{createEventArgs:e=>{return{lengthComputable:(t=e).lengthComputable,loaded:t.loaded,total:t.total,type:t.type};var t}}),l(["touchcancel","touchend","touchmove","touchenter","touchleave","touchstart"],{createEventArgs:e=>{return{detail:(t=e).detail,touches:u(t.touches),targetTouches:u(t.targetTouches),changedTouches:u(t.changedTouches),ctrlKey:t.ctrlKey,shiftKey:t.shiftKey,altKey:t.altKey,metaKey:t.metaKey,type:t.type};var t}}),l(["gotpointercapture","lostpointercapture","pointercancel","pointerdown","pointerenter","pointerleave","pointermove","pointerout","pointerover","pointerup"],{createEventArgs:e=>{return{...d(t=e),pointerId:t.pointerId,width:t.width,height:t.height,pressure:t.pressure,tiltX:t.tiltX,tiltY:t.tiltY,pointerType:t.pointerType,isPrimary:t.isPrimary};var t}}),l(["wheel","mousewheel"],{createEventArgs:e=>{return{...d(t=e),deltaX:t.deltaX,deltaY:t.deltaY,deltaZ:t.deltaZ,deltaMode:t.deltaMode};var t}}),l(["toggle"],{createEventArgs:()=>({})});const f=["date","datetime-local","month","time","week"],h=new Map;let p,m,v=0;const g={async add(e,t,n){if(!n)throw new Error("initialParameters must be an object, even if empty.");const r="__bl-dynamic-root:"+(++v).toString();h.set(r,e);const o=await w().invokeMethodAsync("AddRootComponent",t,r),a=new y(o,m[t]);return await a.setParameters(n),a}};class b{invoke(e){return this._callback(e)}setCallback(t){this._selfJSObjectReference||(this._selfJSObjectReference=e.createJSObjectReference(this)),this._callback=t}getJSObjectReference(){return this._selfJSObjectReference}dispose(){this._selfJSObjectReference&&e.disposeJSObjectReference(this._selfJSObjectReference)}}class y{constructor(e,t){this._jsEventCallbackWrappers=new Map,this._componentId=e;for(const e of t)"eventcallback"===e.type&&this._jsEventCallbackWrappers.set(e.name.toLowerCase(),new b)}setParameters(e){const t={},n=Object.entries(e||{}),r=n.length;for(const[e,r]of n){const n=this._jsEventCallbackWrappers.get(e.toLowerCase());n&&r?(n.setCallback(r),t[e]=n.getJSObjectReference()):t[e]=r}return w().invokeMethodAsync("SetRootComponentParameters",this._componentId,r,t)}async dispose(){if(null!==this._componentId){await w().invokeMethodAsync("RemoveRootComponent",this._componentId),this._componentId=null;for(const e of this._jsEventCallbackWrappers.values())e.dispose()}}}function w(){if(!p)throw new Error("Dynamic root components have not been enabled in this application.");return p}const E=new Map;let S;const I=new Promise((e=>{S=e}));function C(e,t,n){return D(e,t.eventHandlerId,(()=>A(e).invokeMethodAsync("DispatchEventAsync",t,n)))}function A(e){const t=E.get(e);if(!t)throw new Error(`No interop methods are registered for renderer ${e}`);return t}let D=(e,t,n)=>n();const k=_(["abort","blur","canplay","canplaythrough","change","cuechange","durationchange","emptied","ended","error","focus","load","loadeddata","loadedmetadata","loadend","loadstart","mouseenter","mouseleave","pointerenter","pointerleave","pause","play","playing","progress","ratechange","reset","scroll","seeked","seeking","stalled","submit","suspend","timeupdate","toggle","unload","volumechange","waiting","DOMNodeInsertedIntoDocument","DOMNodeRemovedFromDocument"]),R={submit:!0},T=_(["click","dblclick","mousedown","mousemove","mouseup"]);class N{constructor(e){this.browserRendererId=e,this.afterClickCallbacks=[];const t=++N.nextEventDelegatorId;this.eventsCollectionKey=`_blazorEvents_${t}`,this.eventInfoStore=new O(this.onGlobalEvent.bind(this))}setListener(e,t,n,r){const o=this.getEventHandlerInfosForElement(e,!0),a=o.getHandler(t);if(a)this.eventInfoStore.update(a.eventHandlerId,n);else{const a={element:e,eventName:t,eventHandlerId:n,renderingComponentId:r};this.eventInfoStore.add(a),o.setHandler(t,a)}}getHandler(e){return this.eventInfoStore.get(e)}removeListener(e){const t=this.eventInfoStore.remove(e);if(t){const e=t.element,n=this.getEventHandlerInfosForElement(e,!1);n&&n.removeHandler(t.eventName)}}notifyAfterClick(e){this.afterClickCallbacks.push(e),this.eventInfoStore.addGlobalListener("click")}setStopPropagation(e,t,n){this.getEventHandlerInfosForElement(e,!0).stopPropagation(t,n)}setPreventDefault(e,t,n){this.getEventHandlerInfosForElement(e,!0).preventDefault(t,n)}onGlobalEvent(e){if(!(e.target instanceof Element))return;this.dispatchGlobalEventToAllElements(e.type,e);const t=(n=e.type,a.get(n));var n;t&&t.forEach((t=>this.dispatchGlobalEventToAllElements(t,e))),"click"===e.type&&this.afterClickCallbacks.forEach((t=>t(e)))}dispatchGlobalEventToAllElements(e,t){const n=t.composedPath();let o=n.shift(),a=null,s=!1;const c=Object.prototype.hasOwnProperty.call(k,e);let l=!1;for(;o;){const f=o,h=this.getEventHandlerInfosForElement(f,!1);if(h){const n=h.getHandler(e);if(n&&(u=f,d=t.type,!((u instanceof HTMLButtonElement||u instanceof HTMLInputElement||u instanceof HTMLTextAreaElement||u instanceof HTMLSelectElement)&&Object.prototype.hasOwnProperty.call(T,d)&&u.disabled))){if(!s){const n=i(e);a=(null==n?void 0:n.createEventArgs)?n.createEventArgs(t):{},s=!0}Object.prototype.hasOwnProperty.call(R,t.type)&&t.preventDefault(),C(this.browserRendererId,{eventHandlerId:n.eventHandlerId,eventName:e,eventFieldInfo:r.fromEvent(n.renderingComponentId,t)},a)}h.stopPropagation(e)&&(l=!0),h.preventDefault(e)&&t.preventDefault()}o=c||l?void 0:n.shift()}var u,d}getEventHandlerInfosForElement(e,t){return Object.prototype.hasOwnProperty.call(e,this.eventsCollectionKey)?e[this.eventsCollectionKey]:t?e[this.eventsCollectionKey]=new L:null}}N.nextEventDelegatorId=0;class O{constructor(e){this.globalListener=e,this.infosByEventHandlerId={},this.countByEventName={},s.push(this.handleEventNameAliasAdded.bind(this))}add(e){if(this.infosByEventHandlerId[e.eventHandlerId])throw new Error(`Event ${e.eventHandlerId} is already tracked`);this.infosByEventHandlerId[e.eventHandlerId]=e,this.addGlobalListener(e.eventName)}get(e){return this.infosByEventHandlerId[e]}addGlobalListener(e){if(e=c(e),Object.prototype.hasOwnProperty.call(this.countByEventName,e))this.countByEventName[e]++;else{this.countByEventName[e]=1;const t=Object.prototype.hasOwnProperty.call(k,e);document.addEventListener(e,this.globalListener,t)}}update(e,t){if(Object.prototype.hasOwnProperty.call(this.infosByEventHandlerId,t))throw new Error(`Event ${t} is already tracked`);const n=this.infosByEventHandlerId[e];delete this.infosByEventHandlerId[e],n.eventHandlerId=t,this.infosByEventHandlerId[t]=n}remove(e){const t=this.infosByEventHandlerId[e];if(t){delete this.infosByEventHandlerId[e];const n=c(t.eventName);0==--this.countByEventName[n]&&(delete this.countByEventName[n],document.removeEventListener(n,this.globalListener))}return t}handleEventNameAliasAdded(e,t){if(Object.prototype.hasOwnProperty.call(this.countByEventName,e)){const n=this.countByEventName[e];delete this.countByEventName[e],document.removeEventListener(e,this.globalListener),this.addGlobalListener(t),this.countByEventName[t]+=n-1}}}class L{constructor(){this.handlers={},this.preventDefaultFlags=null,this.stopPropagationFlags=null}getHandler(e){return Object.prototype.hasOwnProperty.call(this.handlers,e)?this.handlers[e]:null}setHandler(e,t){this.handlers[e]=t}removeHandler(e){delete this.handlers[e]}preventDefault(e,t){return void 0!==t&&(this.preventDefaultFlags=this.preventDefaultFlags||{},this.preventDefaultFlags[e]=t),!!this.preventDefaultFlags&&this.preventDefaultFlags[e]}stopPropagation(e,t){return void 0!==t&&(this.stopPropagationFlags=this.stopPropagationFlags||{},this.stopPropagationFlags[e]=t),!!this.stopPropagationFlags&&this.stopPropagationFlags[e]}}function _(e){const t={};return e.forEach((e=>{t[e]=!0})),t}const F=G("_blazorLogicalChildren"),x=G("_blazorLogicalParent"),P=G("_blazorLogicalEnd");function j(e,t){if(e.childNodes.length>0&&!t)throw new Error("New logical elements must start empty, or allowExistingContents must be true");return F in e||(e[F]=[]),e}function H(e,t){const n=document.createComment("!");return M(n,e,t),n}function M(e,t,n){const r=e;if(e instanceof Comment&&z(r)&&z(r).length>0)throw new Error("Not implemented: inserting non-empty logical container");if(U(r))throw new Error("Not implemented: moving existing logical children");const o=z(t);if(n0;)B(n,0)}const r=n;r.parentNode.removeChild(r)}function U(e){return e[x]||null}function J(e,t){return z(e)[t]}function $(e){const t=V(e);return"http://www.w3.org/2000/svg"===t.namespaceURI&&"foreignObject"!==t.tagName}function z(e){return e[F]}function K(e,t){const n=z(e);t.forEach((e=>{e.moveRangeStart=n[e.fromSiblingIndex],e.moveRangeEnd=Y(e.moveRangeStart)})),t.forEach((t=>{const r=document.createComment("marker");t.moveToBeforeMarker=r;const o=n[t.toSiblingIndex+1];o?o.parentNode.insertBefore(r,o):W(r,e)})),t.forEach((e=>{const t=e.moveToBeforeMarker,n=t.parentNode,r=e.moveRangeStart,o=e.moveRangeEnd;let a=r;for(;a;){const e=a.nextSibling;if(n.insertBefore(a,t),a===o)break;a=e}n.removeChild(t)})),t.forEach((e=>{n[e.toSiblingIndex]=e.moveRangeStart}))}function V(e){if(e instanceof Element||e instanceof DocumentFragment)return e;if(e instanceof Comment)return e.parentNode;throw new Error("Not a valid logical element")}function X(e){const t=z(U(e));return t[Array.prototype.indexOf.call(t,e)+1]||null}function W(e,t){if(t instanceof Element||t instanceof DocumentFragment)t.appendChild(e);else{if(!(t instanceof Comment))throw new Error(`Cannot append node because the parent is not a valid logical element. Parent: ${t}`);{const n=X(t);n?n.parentNode.insertBefore(e,n):W(e,U(t))}}}function Y(e){if(e instanceof Element||e instanceof DocumentFragment)return e;const t=X(e);if(t)return t.previousSibling;{const t=U(e);return t instanceof Element||t instanceof DocumentFragment?t.lastChild:Y(t)}}function G(e){return"function"==typeof Symbol?Symbol():e}function q(e){return`_bl_${e}`}const Z="__internalId";e.attachReviver(((e,t)=>t&&"object"==typeof t&&Object.prototype.hasOwnProperty.call(t,Z)&&"string"==typeof t[Z]?function(e){const t=`[${q(e)}]`;return document.querySelector(t)}(t[Z]):t));const Q="_blazorDeferredValue",ee=document.createElement("template"),te=document.createElementNS("http://www.w3.org/2000/svg","g"),ne={},re="__internal_",oe="preventDefault_",ae="stopPropagation_";class se{constructor(e){this.rootComponentIds=new Set,this.childComponentLocations={},this.eventDelegator=new N(e),this.eventDelegator.notifyAfterClick((e=>{if(!pe)return;if(0!==e.button||function(e){return e.ctrlKey||e.shiftKey||e.altKey||e.metaKey}(e))return;if(e.defaultPrevented)return;const t=function(e){const t=!window._blazorDisableComposedPath&&e.composedPath&&e.composedPath();if(t){for(let e=0;edocument.baseURI,getLocationHref:()=>location.href,scrollToElement:Ce};function Ce(e){const t=document.getElementById(e)||document.getElementsByName(e)[0];return!!t&&(t.scrollIntoView(),!0)}function Ae(e,t,n=!1){const r=Fe(e);!t.forceLoad&&Pe(r)?De(r,!1,t.replaceHistoryEntry,t.historyEntryState,n):function(e,t){if(location.href===e){const t=e+"?";history.replaceState(null,"",t),location.replace(e)}else t?location.replace(e):location.href=e}(e,t.replaceHistoryEntry)}async function De(e,t,n,r,o=!1){if(Te(),function(e){const t=e.indexOf("#");return t>-1&&location.href.replace(location.hash,"")===e.substring(0,t)}(e))!function(e,t,n){ke(e,t,n);const r=e.indexOf("#");r!=e.length-1&&Ce(e.substring(r+1))}(e,n,r);else{if(!o&&ve&&!await Ne(e,r,t))return;he=!0,ke(e,n,r),await Oe(t)}}function ke(e,t,n){t?history.replaceState({userState:n,_index:ge},"",e):(ge++,history.pushState({userState:n,_index:ge},"",e))}function Re(e){return new Promise((t=>{const n=Ee;Ee=()=>{Ee=n,t()},history.go(e)}))}function Te(){Se&&(Se(!1),Se=null)}function Ne(e,t,n){return new Promise((r=>{Te(),we?(be++,Se=r,we(be,e,t,n)):r(!1)}))}async function Oe(e){var t;ye&&await ye(location.href,null===(t=history.state)||void 0===t?void 0:t.userState,e)}async function Le(e){var t,n;Ee&&await Ee(e),ge=null!==(n=null===(t=history.state)||void 0===t?void 0:t._index)&&void 0!==n?n:0}let _e;function Fe(e){return _e=_e||document.createElement("a"),_e.href=e,_e.href}function xe(e,t){return e?e.tagName===t?e:xe(e.parentElement,t):null}function Pe(e){const t=(n=document.baseURI).substring(0,n.lastIndexOf("/"));var n;const r=e.charAt(t.length);return e.startsWith(t)&&(""===r||"/"===r||"?"===r||"#"===r)}const je={focus:function(e,t){if(e instanceof HTMLElement)e.focus({preventScroll:t});else{if(!(e instanceof SVGElement))throw new Error("Unable to focus an invalid element.");if(!e.hasAttribute("tabindex"))throw new Error("Unable to focus an SVG element that does not have a tabindex.");e.focus({preventScroll:t})}},focusBySelector:function(e,t){const n=document.querySelector(e);n&&(n.hasAttribute("tabindex")||(n.tabIndex=-1),n.focus({preventScroll:!0}))}},He={init:function(e,t,n,r=50){const o=Be(t);(o||document.documentElement).style.overflowAnchor="none";const a=document.createRange();u(n.parentElement)&&(t.style.display="table-row",n.style.display="table-row");const s=new IntersectionObserver((function(r){r.forEach((r=>{var o;if(!r.isIntersecting)return;a.setStartAfter(t),a.setEndBefore(n);const s=a.getBoundingClientRect().height,i=null===(o=r.rootBounds)||void 0===o?void 0:o.height;r.target===t?e.invokeMethodAsync("OnSpacerBeforeVisible",r.intersectionRect.top-r.boundingClientRect.top,s,i):r.target===n&&n.offsetHeight>0&&e.invokeMethodAsync("OnSpacerAfterVisible",r.boundingClientRect.bottom-r.intersectionRect.bottom,s,i)}))}),{root:o,rootMargin:`${r}px`});s.observe(t),s.observe(n);const i=l(t),c=l(n);function l(e){const t={attributes:!0},n=new MutationObserver(((n,r)=>{u(e.parentElement)&&(r.disconnect(),e.style.display="table-row",r.observe(e,t)),s.unobserve(e),s.observe(e)}));return n.observe(e,t),n}function u(e){return null!==e&&(e instanceof HTMLTableElement&&""===e.style.display||"table"===e.style.display||e instanceof HTMLTableSectionElement&&""===e.style.display||"table-row-group"===e.style.display)}Me[e._id]={intersectionObserver:s,mutationObserverBefore:i,mutationObserverAfter:c}},dispose:function(e){const t=Me[e._id];t&&(t.intersectionObserver.disconnect(),t.mutationObserverBefore.disconnect(),t.mutationObserverAfter.disconnect(),e.dispose(),delete Me[e._id])}},Me={};function Be(e){return e&&e!==document.body&&e!==document.documentElement?"visible"!==getComputedStyle(e).overflowY?e:Be(e.parentElement):null}const Ue={getAndRemoveExistingTitle:function(){var e;const t=document.head?document.head.getElementsByTagName("title"):[];if(0===t.length)return null;let n=null;for(let r=t.length-1;r>=0;r--){const o=t[r],a=o.previousSibling;a instanceof Comment&&null!==U(a)||(null===n&&(n=o.textContent),null===(e=o.parentNode)||void 0===e||e.removeChild(o))}return n}},Je={init:function(e,t){t._blazorInputFileNextFileId=0,t.addEventListener("click",(function(){t.value=""})),t.addEventListener("change",(function(){t._blazorFilesById={};const n=Array.prototype.map.call(t.files,(function(e){const n={id:++t._blazorInputFileNextFileId,lastModified:new Date(e.lastModified).toISOString(),name:e.name,size:e.size,contentType:e.type,readPromise:void 0,arrayBuffer:void 0,blob:e};return t._blazorFilesById[n.id]=n,n}));e.invokeMethodAsync("NotifyChange",n)}))},toImageFile:async function(e,t,n,r,o){const a=$e(e,t),s=await new Promise((function(e){const t=new Image;t.onload=function(){URL.revokeObjectURL(t.src),e(t)},t.onerror=function(){t.onerror=null,URL.revokeObjectURL(t.src)},t.src=URL.createObjectURL(a.blob)})),i=await new Promise((function(e){var t;const a=Math.min(1,r/s.width),i=Math.min(1,o/s.height),c=Math.min(a,i),l=document.createElement("canvas");l.width=Math.round(s.width*c),l.height=Math.round(s.height*c),null===(t=l.getContext("2d"))||void 0===t||t.drawImage(s,0,0,l.width,l.height),l.toBlob(e,n)})),c={id:++e._blazorInputFileNextFileId,lastModified:a.lastModified,name:a.name,size:(null==i?void 0:i.size)||0,contentType:n,blob:i||a.blob};return e._blazorFilesById[c.id]=c,c},readFileData:async function(e,t){return $e(e,t).blob}};function $e(e,t){const n=e._blazorFilesById[t];if(!n)throw new Error(`There is no file with ID ${t}. The file list may have changed. See https://aka.ms/aspnet/blazor-input-file-multiple-selections.`);return n}const ze=new Set,Ke={enableNavigationPrompt:function(e){0===ze.size&&window.addEventListener("beforeunload",Ve),ze.add(e)},disableNavigationPrompt:function(e){ze.delete(e),0===ze.size&&window.removeEventListener("beforeunload",Ve)}};function Ve(e){e.preventDefault(),e.returnValue=!0}const Xe=new Map,We={navigateTo:function(e,t,n=!1){Ae(e,t instanceof Object?t:{forceLoad:t,replaceHistoryEntry:n})},registerCustomEventType:function(e,t){if(!t)throw new Error("The options parameter is required.");if(o.has(e))throw new Error(`The event '${e}' is already registered.`);if(t.browserEventName){const n=a.get(t.browserEventName);n?n.push(e):a.set(t.browserEventName,[e]),s.forEach((n=>n(e,t.browserEventName)))}o.set(e,t)},rootComponents:g,_internal:{navigationManager:Ie,domWrapper:je,Virtualize:He,PageTitle:Ue,InputFile:Je,NavigationLock:Ke,getJSDataStreamChunk:async function(e,t,n){return e instanceof Blob?await async function(e,t,n){const r=e.slice(t,t+n),o=await r.arrayBuffer();return new Uint8Array(o)}(e,t,n):function(e,t,n){return new Uint8Array(e.buffer,e.byteOffset+t,n)}(e,t,n)},receiveDotNetDataStream:function(t,n,r,o){let a=Xe.get(t);if(!a){const n=new ReadableStream({start(e){Xe.set(t,e),a=e}});e.jsCallDispatcher.supplyDotNetStream(t,n)}o?(a.error(o),Xe.delete(t)):0===r?(a.close(),Xe.delete(t)):a.enqueue(n.length===r?n:n.subarray(0,r))},attachWebRendererInterop:function(t,n,r,o){if(E.has(t))throw new Error(`Interop methods are already registered for renderer ${t}`);E.set(t,n),Object.keys(r).length>0&&function(t,n,r){if(p)throw new Error("Dynamic root components have already been enabled.");p=t,m=n;for(const[t,o]of Object.entries(r)){const r=e.jsCallDispatcher.findJSFunction(t,0);for(const e of o)r(e,n[e])}}(A(t),r,o),S()}}};window.Blazor=We;let Ye=!1;const Ge="function"==typeof TextDecoder?new TextDecoder("utf-8"):null,qe=Ge?Ge.decode.bind(Ge):function(e){let t=0;const n=e.length,r=[],o=[];for(;t65535&&(o-=65536,r.push(o>>>10&1023|55296),o=56320|1023&o),r.push(o)}r.length>1024&&(o.push(String.fromCharCode.apply(null,r)),r.length=0)}return o.push(String.fromCharCode.apply(null,r)),o.join("")},Ze=Math.pow(2,32),Qe=Math.pow(2,21)-1;function et(e,t){return e[t]|e[t+1]<<8|e[t+2]<<16|e[t+3]<<24}function tt(e,t){return e[t]+(e[t+1]<<8)+(e[t+2]<<16)+(e[t+3]<<24>>>0)}function nt(e,t){const n=tt(e,t+4);if(n>Qe)throw new Error(`Cannot read uint64 with high order part ${n}, because the result would exceed Number.MAX_SAFE_INTEGER.`);return n*Ze+tt(e,t)}class rt{constructor(e){this.batchData=e;const t=new it(e);this.arrayRangeReader=new ct(e),this.arrayBuilderSegmentReader=new lt(e),this.diffReader=new ot(e),this.editReader=new at(e,t),this.frameReader=new st(e,t)}updatedComponents(){return et(this.batchData,this.batchData.length-20)}referenceFrames(){return et(this.batchData,this.batchData.length-16)}disposedComponentIds(){return et(this.batchData,this.batchData.length-12)}disposedEventHandlerIds(){return et(this.batchData,this.batchData.length-8)}updatedComponentsEntry(e,t){const n=e+4*t;return et(this.batchData,n)}referenceFramesEntry(e,t){return e+20*t}disposedComponentIdsEntry(e,t){const n=e+4*t;return et(this.batchData,n)}disposedEventHandlerIdsEntry(e,t){const n=e+8*t;return nt(this.batchData,n)}}class ot{constructor(e){this.batchDataUint8=e}componentId(e){return et(this.batchDataUint8,e)}edits(e){return e+4}editsEntry(e,t){return e+16*t}}class at{constructor(e,t){this.batchDataUint8=e,this.stringReader=t}editType(e){return et(this.batchDataUint8,e)}siblingIndex(e){return et(this.batchDataUint8,e+4)}newTreeIndex(e){return et(this.batchDataUint8,e+8)}moveToSiblingIndex(e){return et(this.batchDataUint8,e+8)}removedAttributeName(e){const t=et(this.batchDataUint8,e+12);return this.stringReader.readString(t)}}class st{constructor(e,t){this.batchDataUint8=e,this.stringReader=t}frameType(e){return et(this.batchDataUint8,e)}subtreeLength(e){return et(this.batchDataUint8,e+4)}elementReferenceCaptureId(e){const t=et(this.batchDataUint8,e+4);return this.stringReader.readString(t)}componentId(e){return et(this.batchDataUint8,e+8)}elementName(e){const t=et(this.batchDataUint8,e+8);return this.stringReader.readString(t)}textContent(e){const t=et(this.batchDataUint8,e+4);return this.stringReader.readString(t)}markupContent(e){const t=et(this.batchDataUint8,e+4);return this.stringReader.readString(t)}attributeName(e){const t=et(this.batchDataUint8,e+4);return this.stringReader.readString(t)}attributeValue(e){const t=et(this.batchDataUint8,e+8);return this.stringReader.readString(t)}attributeEventHandlerId(e){return nt(this.batchDataUint8,e+12)}}class it{constructor(e){this.batchDataUint8=e,this.stringTableStartIndex=et(e,e.length-4)}readString(e){if(-1===e)return null;{const n=et(this.batchDataUint8,this.stringTableStartIndex+4*e),r=function(e,t){let n=0,r=0;for(let o=0;o<4;o++){const a=e[t+o];if(n|=(127&a)<async function(e,n){const r=function(e){const t=document.baseURI;return t.endsWith("/")?`${t}${e}`:`${t}/${e}`}(n),o=await import(r);if(void 0===o)return;const{beforeStart:a,afterStarted:s}=o;return s&&e.afterStartedCallbacks.push(s),a?a(...t):void 0}(this,e))))}async invokeAfterStartedCallbacks(e){await I,await Promise.all(this.afterStartedCallbacks.map((t=>t(e))))}}let St=!1;async function It(){if(St)throw new Error("Blazor has already started.");St=!0;const t=await async function(){const e=await fetch("_framework/blazor.modules.json",{method:"GET",credentials:"include",cache:"no-cache"}),t=await e.json(),n=new Et;return await n.importInitializersAsync(t,[]),n}();(function(){const t={AttachToDocument:(e,t)=>{!function(e,t,n){const r="::after",o="::before";let a=!1;if(e.endsWith(r))e=e.slice(0,-r.length),a=!0;else if(e.endsWith(o))throw new Error(`The '${o}' selector is not supported.`);const s=function(e){const t=h.get(e);if(t)return h.delete(e),t}(e)||document.querySelector(e);if(!s)throw new Error(`Could not find any element matching selector '${e}'.`);!function(e,t,n,r){let o=fe[0];o||(o=new se(0),fe[0]=o),o.attachRootComponentToLogicalElement(n,t,r)}(0,j(s,!0),t,a)}(t,e)},RenderBatch:(e,t)=>{try{const n=wt(t);(function(e,t){const n=fe[0];if(!n)throw new Error("There is no browser renderer with ID 0.");const r=t.arrayRangeReader,o=t.updatedComponents(),a=r.values(o),s=r.count(o),i=t.referenceFrames(),c=r.values(i),l=t.diffReader;for(let e=0;e{dt=!0,console.error(`${e}\n${t}`),function(){const e=document.querySelector("#blazor-error-ui");e&&(e.style.display="block"),Ye||(Ye=!0,document.querySelectorAll("#blazor-error-ui .reload").forEach((e=>{e.onclick=function(e){location.reload(),e.preventDefault()}})),document.querySelectorAll("#blazor-error-ui .dismiss").forEach((e=>{e.onclick=function(e){const t=document.querySelector("#blazor-error-ui");t&&(t.style.display="none"),e.preventDefault()}})))}()},BeginInvokeJS:e.jsCallDispatcher.beginInvokeJSFromDotNet,EndInvokeDotNet:e.jsCallDispatcher.endInvokeDotNetFromJS,SendByteArrayToJS:yt,Navigate:Ie.navigateTo,SetHasLocationChangingListeners:Ie.setHasLocationChangingListeners,EndLocationChanging:Ie.endLocationChanging};window.external.receiveMessage((e=>{const n=function(e){if(dt||!e||!e.startsWith(ut))return null;const t=e.substring(ut.length),[n,...r]=JSON.parse(t);return{messageType:n,args:r}}(e);if(n){if(!Object.prototype.hasOwnProperty.call(t,n.messageType))throw new Error(`Unsupported IPC message type '${n.messageType}'`);t[n.messageType].apply(null,n.args)}}))})(),e.attachDispatcher({beginInvokeDotNetFromJS:ht,endInvokeJSFromDotNet:pt,sendByteArray:mt}),Ie.enableNavigationInterception(),Ie.listenForNavigationEvents(vt,gt),bt("AttachPage",Ie.getBaseURI(),Ie.getLocationHref()),await t.invokeAfterStartedCallbacks(We)}We.start=It,document&&document.currentScript&&"false"!==document.currentScript.getAttribute("autostart")&&It()})(); \ No newline at end of file diff --git a/src/Components/Web/src/Forms/EditForm.cs b/src/Components/Web/src/Forms/EditForm.cs index 955b0ea9bc6f..6abbb7d1f96d 100644 --- a/src/Components/Web/src/Forms/EditForm.cs +++ b/src/Components/Web/src/Forms/EditForm.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; +using Microsoft.AspNetCore.Components.Binding; using Microsoft.AspNetCore.Components.Rendering; namespace Microsoft.AspNetCore.Components.Forms; @@ -77,6 +78,11 @@ public EditForm() /// [Parameter] public EventCallback OnInvalidSubmit { get; set; } + /// + /// Gets the context associated with data bound to the EditContext in this form. + /// + [CascadingParameter] public ModelBindingContext? BindingContext { get; set; } + /// protected override void OnParametersSet() { @@ -122,6 +128,10 @@ protected override void BuildRenderTree(RenderTreeBuilder builder) builder.OpenElement(0, "form"); builder.AddMultipleAttributes(1, AdditionalAttributes); builder.AddAttribute(2, "onsubmit", _handleSubmitDelegate); + if (BindingContext != null) + { + builder.SetEventHandlerName(BindingContext.Name); + } builder.OpenComponent>(3); builder.AddComponentParameter(4, "IsFixed", true); builder.AddComponentParameter(5, "Value", _editContext); diff --git a/src/Components/Web/src/PublicAPI.Unshipped.txt b/src/Components/Web/src/PublicAPI.Unshipped.txt index f490997416ed..2a678ad4fe15 100644 --- a/src/Components/Web/src/PublicAPI.Unshipped.txt +++ b/src/Components/Web/src/PublicAPI.Unshipped.txt @@ -1,6 +1,8 @@ #nullable enable *REMOVED*override Microsoft.AspNetCore.Components.Forms.InputFile.OnInitialized() -> void Microsoft.AspNetCore.Components.Forms.InputBase.NameAttributeValue.get -> string! +Microsoft.AspNetCore.Components.Forms.EditForm.BindingContext.get -> Microsoft.AspNetCore.Components.Binding.ModelBindingContext! +Microsoft.AspNetCore.Components.Forms.EditForm.BindingContext.set -> void Microsoft.AspNetCore.Components.HtmlRendering.Infrastructure.StaticHtmlRenderer Microsoft.AspNetCore.Components.HtmlRendering.Infrastructure.StaticHtmlRenderer.BeginRenderingComponent(System.Type! componentType, Microsoft.AspNetCore.Components.ParameterView initialParameters) -> Microsoft.AspNetCore.Components.Web.HtmlRendering.HtmlRootComponent Microsoft.AspNetCore.Components.HtmlRendering.Infrastructure.StaticHtmlRenderer.StaticHtmlRenderer(System.IServiceProvider! serviceProvider, Microsoft.Extensions.Logging.ILoggerFactory! loggerFactory) -> void From 4dd972678583eb13cb970be2c7d531efeaeab3a4 Mon Sep 17 00:00:00 2001 From: jacalvar Date: Fri, 14 Apr 2023 20:40:58 +0200 Subject: [PATCH 10/28] More tests --- .../src/RazorComponentEndpointInvoker.cs | 1 - .../src/Rendering/EndpointHtmlRenderer.cs | 5 + .../test/EndpointHtmlRendererTest.cs | 163 +++++++++++++++++- .../Web/src/PublicAPI.Unshipped.txt | 2 +- 4 files changed, 167 insertions(+), 4 deletions(-) diff --git a/src/Components/Endpoints/src/RazorComponentEndpointInvoker.cs b/src/Components/Endpoints/src/RazorComponentEndpointInvoker.cs index 7beba465eb62..b240175fbe96 100644 --- a/src/Components/Endpoints/src/RazorComponentEndpointInvoker.cs +++ b/src/Components/Endpoints/src/RazorComponentEndpointInvoker.cs @@ -4,7 +4,6 @@ using System.Buffers; using System.Text; using System.Text.Encodings.Web; -using Microsoft.AspNetCore.Components.Web.HtmlRendering; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.WebUtilities; using Microsoft.Extensions.DependencyInjection; diff --git a/src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.cs b/src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.cs index 833112d060be..516e729d8122 100644 --- a/src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.cs +++ b/src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.cs @@ -121,6 +121,11 @@ protected override void TrackNamedEventId(ulong eventHandlerId, int componentId, internal Task DispatchCapturedEvent() { + if (_capturedNamedEvent == default) + { + throw new InvalidOperationException($"No named event handler was captured for '{_formHandler}'."); + } + // Clear the list of non-streaming rendering tasks, since we've waited for quiesce before dispatching the event. _nonStreamingPendingTasks.Clear(); return DispatchEventAsync(_capturedNamedEvent.EventHandlerId, null, EventArgs.Empty, quiesce: true); diff --git a/src/Components/Endpoints/test/EndpointHtmlRendererTest.cs b/src/Components/Endpoints/test/EndpointHtmlRendererTest.cs index c6a3c3093ffc..a0a3d1004060 100644 --- a/src/Components/Endpoints/test/EndpointHtmlRendererTest.cs +++ b/src/Components/Endpoints/test/EndpointHtmlRendererTest.cs @@ -812,7 +812,7 @@ public async Task CanRender_AsyncComponent() } [Fact] - public void DuplicateNamedEventHandlersAcrossComponentsThrows() + public void Duplicate_NamedEventHandlers_AcrossComponents_Throws() { // Arrange var expectedError = @"Two different components are trying to define the same named event 'default': @@ -873,6 +873,97 @@ public void RecreatedComponent_AcrossDifferentBatches_WithNamedEventHandler_Thro Assert.Equal(expectedError, exception.Message); } + [Fact] + public void SameComponent_WithNamedEvent_CanRenderSynchronously_MultipleTimes() + { + // Arrange + var renderer = GetEndpointHtmlRenderer(); + renderer.SetFormHandlerName("default"); + + var component = new TestComponent(builder => + { + builder.OpenComponent(0); + builder.CloseComponent(); + }); + + // Act + var componentId = renderer.TestAssignRootComponentId(component); + renderer.Dispatcher.InvokeAsync(component.TriggerRender); + + // Assert + Assert.Equal(2, renderer.TrackedNamedEvents.Count); + } + + [Fact] + public async Task SameComponent_WithNamedEvent_CanRenderAsynchronously_MultipleTimes() + { + // Arrange + var renderer = GetEndpointHtmlRenderer(); + renderer.SetFormHandlerName("default"); + var continueTaskCompletion = new TaskCompletionSource(); + + // Act + var result = await renderer.Dispatcher.InvokeAsync(() => renderer.BeginRenderingComponent( + typeof(MultiAsyncRenderNamedEventHandlerComponent), + ParameterView.FromDictionary(new Dictionary + { + [nameof(MultiAsyncRenderNamedEventHandlerComponent.Continue)] = continueTaskCompletion.Task + }))); + + // Assert + continueTaskCompletion.SetResult(); + await result.QuiescenceTask; + Assert.Equal(2, renderer.TrackedNamedEvents.Count); + } + + [Fact] + public async Task CanDispatchNamedEvent_ToComponent() + { + // Arrange + var renderer = GetEndpointHtmlRenderer(); + renderer.SetFormHandlerName("default"); + var continueTaskCompletion = new TaskCompletionSource(); + var invoked = false; + Action handler = () => invoked = true; + await renderer.Dispatcher.InvokeAsync(async () => + { + var result = renderer.BeginRenderingComponent( + typeof(NamedEventHandlerComponent), + ParameterView.FromDictionary(new Dictionary + { + [nameof(NamedEventHandlerComponent.Handler)] = handler + })); + + await result.QuiescenceTask; + + // Act + await renderer.DispatchCapturedEvent(); + }); + + // Assert + Assert.True(invoked); + } + + [Fact] + public async Task Dispatching_WhenEventHasNotBeenFound_Throws() + { + // Arrange + var renderer = GetEndpointHtmlRenderer(); + renderer.SetFormHandlerName("other"); + + var exception = await Assert.ThrowsAsync(() => + renderer.Dispatcher.InvokeAsync(async () => + { + var result = renderer.BeginRenderingComponent(typeof(NamedEventHandlerComponent), ParameterView.Empty); + await result.QuiescenceTask; + + // Act + await renderer.DispatchCapturedEvent(); + })); + + Assert.Equal("No named event handler was captured for 'other'.", exception.Message); + } + [Fact] public void NamedEventHandlers_DifferentComponents_SameNamedHandlerInDifferentBatches_Throws() { @@ -917,15 +1008,73 @@ public void NamedEventHandlers_DifferentComponents_SameNamedHandlerInDifferentBa private class NamedEventHandlerComponent : ComponentBase { + [Parameter] + public Action Handler { get; set; } + protected override void BuildRenderTree(RenderTreeBuilder builder) { builder.OpenElement(0, "form"); - builder.AddAttribute(1, "onsubmit", () => { }); + builder.AddAttribute(1, "onsubmit", Handler ?? (() => { })); builder.SetEventHandlerName("default"); builder.CloseElement(); } } + private class MultiRenderNamedEventHandlerComponent : ComponentBase + { + private bool hasRendered; + + protected override void BuildRenderTree(RenderTreeBuilder builder) + { + builder.OpenElement(0, "form"); + if (!hasRendered) + { + builder.AddAttribute(1, "onsubmit", () => { }); + builder.SetEventHandlerName("default"); + } + else + { + builder.AddAttribute(1, "onsubmit", () => { GC.KeepAlive(new object()); }); + builder.SetEventHandlerName("default"); + } + builder.CloseElement(); + if (!hasRendered) + { + hasRendered = true; + StateHasChanged(); + } + } + } + + private class MultiAsyncRenderNamedEventHandlerComponent : ComponentBase + { + private bool hasRendered; + + [Parameter] public Task Continue { get; set; } + + protected override void BuildRenderTree(RenderTreeBuilder builder) + { + builder.OpenElement(0, "form"); + if (!hasRendered) + { + builder.AddAttribute(1, "onsubmit", () => { }); + builder.SetEventHandlerName("default"); + } + else + { + builder.AddAttribute(1, "onsubmit", () => { GC.KeepAlive(new object()); }); + builder.SetEventHandlerName("default"); + } + builder.CloseElement(); + } + + protected override async Task OnInitializedAsync() + { + await Continue; + hasRendered = true; + } + } + private class OtherNamedEventHandlerComponent : ComponentBase { protected override void BuildRenderTree(RenderTreeBuilder builder) @@ -973,6 +1122,16 @@ internal int TestAssignRootComponentId(IComponent component) { return base.AssignRootComponentId(component); } + + protected override void TrackNamedEventId(ulong eventHandlerId, int componentId, string eventNameId) + { + TrackedNamedEvents.Add(new TrackedNamedEvent(eventHandlerId, componentId, eventNameId)); + base.TrackNamedEventId(eventHandlerId, componentId, eventNameId); + } + + public List TrackedNamedEvents { get; set; } = new List(); + + public readonly record struct TrackedNamedEvent(ulong EventHandlerId, int ComponentId, string EventNameId); } private HttpContext GetHttpContext(HttpContext context = null) diff --git a/src/Components/Web/src/PublicAPI.Unshipped.txt b/src/Components/Web/src/PublicAPI.Unshipped.txt index 2a678ad4fe15..e99094027c4d 100644 --- a/src/Components/Web/src/PublicAPI.Unshipped.txt +++ b/src/Components/Web/src/PublicAPI.Unshipped.txt @@ -1,7 +1,7 @@ #nullable enable *REMOVED*override Microsoft.AspNetCore.Components.Forms.InputFile.OnInitialized() -> void Microsoft.AspNetCore.Components.Forms.InputBase.NameAttributeValue.get -> string! -Microsoft.AspNetCore.Components.Forms.EditForm.BindingContext.get -> Microsoft.AspNetCore.Components.Binding.ModelBindingContext! +Microsoft.AspNetCore.Components.Forms.EditForm.BindingContext.get -> Microsoft.AspNetCore.Components.Binding.ModelBindingContext? Microsoft.AspNetCore.Components.Forms.EditForm.BindingContext.set -> void Microsoft.AspNetCore.Components.HtmlRendering.Infrastructure.StaticHtmlRenderer Microsoft.AspNetCore.Components.HtmlRendering.Infrastructure.StaticHtmlRenderer.BeginRenderingComponent(System.Type! componentType, Microsoft.AspNetCore.Components.ParameterView initialParameters) -> Microsoft.AspNetCore.Components.Web.HtmlRendering.HtmlRootComponent From b6e4c48932050e0734c267ce5fcf044a33ad18b6 Mon Sep 17 00:00:00 2001 From: jacalvar Date: Fri, 14 Apr 2023 21:00:22 +0200 Subject: [PATCH 11/28] Add E2E test --- .../StreamingRenderingTest.cs | 16 +++++++++ .../Pages/FormStreamingRendering.razor | 36 +++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/FormStreamingRendering.razor diff --git a/src/Components/test/E2ETest/ServerExecutionTests/StreamingRenderingTest.cs b/src/Components/test/E2ETest/ServerExecutionTests/StreamingRenderingTest.cs index 5d0b35b55297..4091ae807d86 100644 --- a/src/Components/test/E2ETest/ServerExecutionTests/StreamingRenderingTest.cs +++ b/src/Components/test/E2ETest/ServerExecutionTests/StreamingRenderingTest.cs @@ -64,4 +64,20 @@ public void CanPerformStreamingRendering() Browser.FindElement(By.Id("end-response-link")).Click(); Browser.Equal("Finished", () => getStatusText().Text); } + + [Fact] + public void CanPerformFormPostWithStreamedResponses() + { + Navigate($"{ServerPathBase}/form-streaming"); + + // Initial "waiting" state + var submit = Browser.Exists(By.CssSelector("input[type=submit]")); + var getStatusText = () => Browser.Exists(By.Id("status")); + Assert.Equal("", getStatusText().Text); + + submit.Click(); + + Assert.Equal("Processing form...", getStatusText().Text); + Assert.Equal("Completed", getStatusText().Text); + } } diff --git a/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/FormStreamingRendering.razor b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/FormStreamingRendering.razor new file mode 100644 index 000000000000..84cf118e6aaa --- /dev/null +++ b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/FormStreamingRendering.razor @@ -0,0 +1,36 @@ +@page "/form-streaming" +@using Microsoft.AspNetCore.Components.Forms +@attribute [StreamRendering(true)] + +

Streaming Rendering

+ +@* This just sets the context for the binding, this happens normally inside RouteView *@ + + + + + + +

+ @if (_processing) + { + Processing form... + } + else if (_done) + { + Completed + } +

+ +@code { + bool _processing = false; + bool _done = false; + + private async Task ProcessForm() + { + _processing = true; + await Task.Yield(); + _processing = false; + _done = true; + } +} From 52001ca54ab5e1f5527a45d35b4fe9ac90d72b8b Mon Sep 17 00:00:00 2001 From: jacalvar Date: Mon, 17 Apr 2023 13:56:36 +0200 Subject: [PATCH 12/28] Support named forms and hierarchical handlers --- .../src/Binding/CascadingModelBinder.cs | 20 +- .../src/Binding/ModelBindingContext.cs | 36 +- .../src/Rendering/RenderTreeBuilder.cs | 12 + src/Components/Components/src/RouteView.cs | 5 +- .../Samples/BlazorUnitedApp/Pages/Index.razor | 22 +- src/Components/Web/src/Forms/EditForm.cs | 52 ++- src/Components/Web/test/Forms/EditFormTest.cs | 332 +++++++++++++++++- 7 files changed, 446 insertions(+), 33 deletions(-) diff --git a/src/Components/Components/src/Binding/CascadingModelBinder.cs b/src/Components/Components/src/Binding/CascadingModelBinder.cs index 64609731b974..a386f53363ca 100644 --- a/src/Components/Components/src/Binding/CascadingModelBinder.cs +++ b/src/Components/Components/src/Binding/CascadingModelBinder.cs @@ -20,10 +20,17 @@ public class CascadingModelBinder : IComponent /// [Parameter] public string Name { get; set; } = default!; + /// + /// The binding context name. + /// + [Parameter] public string BindingId { get; set; } = default!; + /// /// Specifies the content to be rendered inside this . /// - [Parameter] public RenderFragment ChildContent { get; set; } = default!; + [Parameter] public RenderFragment ChildContent { get; set; } = default!; + + [CascadingParameter] ModelBindingContext? ParentContext { get; set; } void IComponent.Attach(RenderHandle renderHandle) { @@ -36,14 +43,21 @@ Task IComponent.SetParametersAsync(ParameterView parameters) { _hasRendered = true; parameters.SetParameterProperties(this); + if (ParentContext != null && string.IsNullOrEmpty(Name)) + { + throw new InvalidOperationException("Nested binding contexts must define a Name."); + } + + var name = string.IsNullOrEmpty(ParentContext?.Name) ? Name : $"{ParentContext.Name}.{Name}"; + var bindingId = !string.IsNullOrEmpty(name) ? null : BindingId; + _bindingContext = new ModelBindingContext(name, bindingId); - _bindingContext = new ModelBindingContext(Name); _handle.Render(builder => { builder.OpenComponent>(0); builder.AddComponentParameter(1, nameof(CascadingValue.IsFixed), true); builder.AddComponentParameter(2, nameof(CascadingValue.Value), _bindingContext); - builder.AddComponentParameter(3, nameof(CascadingValue.ChildContent), ChildContent); + builder.AddComponentParameter(3, nameof(CascadingValue.ChildContent), ChildContent?.Invoke(_bindingContext)); builder.CloseComponent(); }); } diff --git a/src/Components/Components/src/Binding/ModelBindingContext.cs b/src/Components/Components/src/Binding/ModelBindingContext.cs index 4aa7fd49b316..e4a0146aaaed 100644 --- a/src/Components/Components/src/Binding/ModelBindingContext.cs +++ b/src/Components/Components/src/Binding/ModelBindingContext.cs @@ -8,19 +8,43 @@ namespace Microsoft.AspNetCore.Components.Binding; /// public class ModelBindingContext { - /// - /// Initializes a new instance of . - /// - /// The context name. - public ModelBindingContext(string name) + // Default binder + // Name = URL.Path + // FormAction = "" + + // Named from default binder + // Name = <> (<> -> <>) + // FormAction = ?handler=<> ("" -> ?handler=<>) + + // Named binder + // Name = <> + // FormAction = ?handler=<> + + // Nested named binder + // Name = <>.<> + // FormAction = ?handler=<>.<> + public ModelBindingContext(string name, string? bindingId = null) { - ArgumentNullException.ThrowIfNull(name); + // We are initializing the root context, that can be a "named" root context, or the default context. + // A named root context only provides a name, and that acts as the BindingId + // A "default" root context does not provide a name, and instead it provides an explicit Binding ID. + // The explicit binding ID matches that of the default handler, which is the URL Path. + if (!(string.IsNullOrEmpty(name) ^ string.IsNullOrEmpty(bindingId))) + { + throw new InvalidOperationException("A root binding context needs to provide either a name or explicit binding ID."); + } Name = name; + BindingId = bindingId ?? name; } /// /// The context name. /// public string Name { get; } + + /// + /// The computed identifier used to determine what parts of the app can bind data. + /// + public string BindingId { get; } } diff --git a/src/Components/Components/src/Rendering/RenderTreeBuilder.cs b/src/Components/Components/src/Rendering/RenderTreeBuilder.cs index 11a7335230e5..401557745c80 100644 --- a/src/Components/Components/src/Rendering/RenderTreeBuilder.cs +++ b/src/Components/Components/src/Rendering/RenderTreeBuilder.cs @@ -569,6 +569,18 @@ public void AddComponentParameter(int sequence, string name, RenderFragment? val _entries.AppendAttribute(sequence, name, value); } + /// + /// Appends a frame representing a component parameter. + /// + /// An integer that represents the position of the instruction in the source code. + /// The name of the attribute. + /// The value of the attribute. + public void AddComponentParameter(int sequence, string name, RenderFragment? value) + { + AssertCanAddComponentParameter(); + _entries.AppendAttribute(sequence, name, value); + } + /// /// Assigns the specified key value to the current element or component. /// diff --git a/src/Components/Components/src/RouteView.cs b/src/Components/Components/src/RouteView.cs index ec458d7559b6..68017b5d326d 100644 --- a/src/Components/Components/src/RouteView.cs +++ b/src/Components/Components/src/RouteView.cs @@ -77,7 +77,7 @@ protected virtual void Render(RenderTreeBuilder builder) private void RenderPageWithParameters(RenderTreeBuilder builder) { var pathStart = NavigationManager.BaseUri.Length; - var name = NavigationManager.Uri.Substring( + var bindingId = NavigationManager.Uri.Substring( pathStart, NavigationManager.Uri.AsSpan().IndexOfAny("?#") switch { @@ -85,7 +85,8 @@ private void RenderPageWithParameters(RenderTreeBuilder builder) var index => index - pathStart }); - builder.AddComponentParameter(1, nameof(CascadingModelBinder.Name), name); + builder.AddComponentParameter(1, nameof(CascadingModelBinder.Name), ""); + builder.AddComponentParameter(1, nameof(CascadingModelBinder.BindingId), bindingId); builder.AddComponentParameter(2, nameof(CascadingModelBinder.ChildContent), builder => { builder.OpenComponent(0, RouteData.PageType); diff --git a/src/Components/Samples/BlazorUnitedApp/Pages/Index.razor b/src/Components/Samples/BlazorUnitedApp/Pages/Index.razor index 6085c4aa96f4..20026de2ad98 100644 --- a/src/Components/Samples/BlazorUnitedApp/Pages/Index.razor +++ b/src/Components/Samples/BlazorUnitedApp/Pages/Index.razor @@ -4,6 +4,24 @@

Hello, world!

-Welcome to your new app. + + + + + + + - + +@if (_submitted) +{ +

Submited.

+} + +@code{ + bool _submitted = false; + public void Submit() + { + _submitted = true; + } +} diff --git a/src/Components/Web/src/Forms/EditForm.cs b/src/Components/Web/src/Forms/EditForm.cs index 6abbb7d1f96d..6f68aac43ff5 100644 --- a/src/Components/Web/src/Forms/EditForm.cs +++ b/src/Components/Web/src/Forms/EditForm.cs @@ -83,6 +83,11 @@ public EditForm() /// [CascadingParameter] public ModelBindingContext? BindingContext { get; set; } + /// + /// Gets or sets the form name. + /// + [Parameter] public string Name { get; set; } + /// protected override void OnParametersSet() { @@ -125,21 +130,46 @@ protected override void BuildRenderTree(RenderTreeBuilder builder) // optimizing for the common case where _editContext never changes. builder.OpenRegion(_editContext.GetHashCode()); - builder.OpenElement(0, "form"); - builder.AddMultipleAttributes(1, AdditionalAttributes); - builder.AddAttribute(2, "onsubmit", _handleSubmitDelegate); - if (BindingContext != null) + if (Name != null) + { + builder.OpenComponent(0); + builder.AddComponentParameter(1, nameof(CascadingModelBinder.Name), Name); + builder.AddComponentParameter(2, nameof(CascadingModelBinder.ChildContent), RenderWithNamedContext); + builder.CloseComponent(); + } + else { - builder.SetEventHandlerName(BindingContext.Name); + RenderFormContents(builder, BindingContext); } - builder.OpenComponent>(3); - builder.AddComponentParameter(4, "IsFixed", true); - builder.AddComponentParameter(5, "Value", _editContext); - builder.AddComponentParameter(6, "ChildContent", ChildContent?.Invoke(_editContext)); - builder.CloseComponent(); - builder.CloseElement(); builder.CloseRegion(); + + RenderFragment RenderWithNamedContext(ModelBindingContext context) + { + return builder => RenderFormContents(builder, context); + } + + void RenderFormContents(RenderTreeBuilder builder, ModelBindingContext? bindingContext) + { + builder.OpenElement(0, "form"); + if (!string.IsNullOrEmpty(bindingContext?.Name)) + { + builder.AddAttribute(1, "name", bindingContext.Name); + builder.AddAttribute(2, "action", $"?handler={bindingContext.Name}"); + } + builder.AddMultipleAttributes(3, AdditionalAttributes); + builder.AddAttribute(4, "onsubmit", _handleSubmitDelegate); + if (bindingContext != null) + { + builder.SetEventHandlerName(bindingContext.BindingId); + } + builder.OpenComponent>(5); + builder.AddComponentParameter(6, "IsFixed", true); + builder.AddComponentParameter(7, "Value", _editContext); + builder.AddComponentParameter(8, "ChildContent", ChildContent?.Invoke(_editContext)); + builder.CloseComponent(); + builder.CloseElement(); + } } private async Task HandleSubmitAsync() diff --git a/src/Components/Web/test/Forms/EditFormTest.cs b/src/Components/Web/test/Forms/EditFormTest.cs index aaa43822d995..198c9a9b7f2f 100644 --- a/src/Components/Web/test/Forms/EditFormTest.cs +++ b/src/Components/Web/test/Forms/EditFormTest.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.ComponentModel; +using Microsoft.AspNetCore.Components.Binding; using Microsoft.AspNetCore.Components.Rendering; using Microsoft.AspNetCore.Components.RenderTree; using Microsoft.AspNetCore.Components.Test.Helpers; @@ -9,6 +11,7 @@ namespace Microsoft.AspNetCore.Components.Forms; public class EditFormTest { + private TestRenderer _testRenderer = new(); [Fact] public async Task ThrowsIfBothEditContextAndModelAreSupplied() @@ -79,6 +82,234 @@ public async Task ReturnsEditContextWhenEditContextParameterUsed() Assert.Same(editContext, returnedEditContext); } + [Fact] + public async Task FormElementNameAndAction_SetToComponentName_WhenFormNameIsProvided() + { + // Arrange + var model = new TestModel(); + var rootComponent = new TestEditFormHostComponent + { + Model = model, + FormName = "my-form", + }; + + // Act + _ = await RenderAndGetTestEditFormComponentAsync(rootComponent); + var attributes = GetFormElementAttributeFrames().ToArray(); + + // Assert + AssertFrame.Attribute(attributes[0], "name", "my-form"); + AssertFrame.Attribute(attributes[1], "action", "?handler=my-form"); + } + + [Fact] + public async Task FormElementNameAndAction_SetToComponentName_WhenCombiningWithDefaultParentBindingContext() + { + // Arrange + var model = new TestModel(); + var rootComponent = new TestEditFormHostComponent + { + Model = model, + FormName = "my-form", + BindingContext = new ModelBindingContext("", "/") + }; + + // Act + _ = await RenderAndGetTestEditFormComponentAsync(rootComponent); + var attributes = GetFormElementAttributeFrames().ToArray(); + + // Assert + AssertFrame.Attribute(attributes[0], "name", "my-form"); + AssertFrame.Attribute(attributes[1], "action", "?handler=my-form"); + } + + [Fact] + public async Task FormElementNameAndAction_SetToCombinedIdentifier_WhenCombiningWithNamedParentBindingContext() + { + // Arrange + var model = new TestModel(); + var rootComponent = new TestEditFormHostComponent + { + Model = model, + FormName = "my-form", + BindingContext = new ModelBindingContext("parent-context") + }; + + // Act + _ = await RenderAndGetTestEditFormComponentAsync(rootComponent); + var attributes = GetFormElementAttributeFrames().ToArray(); + + // Assert + AssertFrame.Attribute(attributes[0], "name", "parent-context.my-form"); + AssertFrame.Attribute(attributes[1], "action", "?handler=parent-context.my-form"); + } + + [Fact] + public async Task FormElementNameAndAction_CanBeExplicitlyOverriden() + { + // Arrange + var model = new TestModel(); + var rootComponent = new TestEditFormHostComponent + { + Model = model, + FormName = "my-form", + AdditionalFormAttributes = new Dictionary() { + ["name"] = "my-explicit-name", + ["action"] = "/somewhere/else", + }, + BindingContext = new ModelBindingContext("parent-context") + }; + + // Act + _ = await RenderAndGetTestEditFormComponentAsync(rootComponent); + var attributes = GetFormElementAttributeFrames().ToArray(); + + // Assert + AssertFrame.Attribute(attributes[0], "name", "my-explicit-name"); + AssertFrame.Attribute(attributes[1], "action", "/somewhere/else"); + } + + [Fact] + public async Task FormElementNameAndAction_NotSetOnDefaultBindingContext() + { + // Arrange + var model = new TestModel(); + var rootComponent = new TestEditFormHostComponent + { + Model = model, + BindingContext = new ModelBindingContext("", "/"), + SubmitHandler = ctx => { } + }; + + // Act + _ = await RenderAndGetTestEditFormComponentAsync(rootComponent); + var attributes = GetFormElementAttributeFrames(); + + // Assert + var frame = Assert.Single(attributes); + AssertFrame.Attribute(frame, "onsubmit"); + } + + [Fact] + public async Task FormElementNameAndAction_NotSetWhenNoFormNameAndNoBindingContext() + { + // Arrange + var model = new TestModel(); + var rootComponent = new TestEditFormHostComponent + { + Model = model, + SubmitHandler = ctx => { } + }; + + // Act + _ = await RenderAndGetTestEditFormComponentAsync(rootComponent); + var attributes = GetFormElementAttributeFrames(); + + // Assert + var frame = Assert.Single(attributes); + AssertFrame.Attribute(frame, "onsubmit"); + } + + [Fact] + public async Task EventHandlerName_NotSetWhenNoBindingContextProvided() + { + // Arrange + var tracker = TrackEventNames(); + + var model = new TestModel(); + var rootComponent = new TestEditFormHostComponent + { + Model = model, + SubmitHandler = ctx => { } + }; + + // Act + _ = await RenderAndGetTestEditFormComponentAsync(rootComponent); + + // Assert + Assert.Null(tracker.EventName); + } + + [Fact] + public async Task EventHandlerName_SetToBindingIdOnDefaultHandler() + { + // Arrange + var tracker = TrackEventNames(); + + var model = new TestModel(); + var rootComponent = new TestEditFormHostComponent + { + Model = model, + BindingContext = new ModelBindingContext("", "/") + }; + + // Act + _ = await RenderAndGetTestEditFormComponentAsync(rootComponent); + + // Assert + Assert.Equal("/", tracker.EventName); + } + + [Fact] + public async Task EventHandlerName_SetToFormNameWhenFormNameIsProvided() + { + // Arrange + var tracker = TrackEventNames(); + + var model = new TestModel(); + var rootComponent = new TestEditFormHostComponent + { + Model = model, + FormName = "my-form", + }; + + // Act + _ = await RenderAndGetTestEditFormComponentAsync(rootComponent); + + // Assert + Assert.Equal("my-form", tracker.EventName); + } + + [Fact] + public async Task EventHandlerName_SetToFormNameWhenParentBindingContextIsDefault() + { + // Arrange + var tracker = TrackEventNames(); + var model = new TestModel(); + var rootComponent = new TestEditFormHostComponent + { + Model = model, + FormName = "my-form", + BindingContext = new ModelBindingContext("", "/") + }; + + // Act + _ = await RenderAndGetTestEditFormComponentAsync(rootComponent); + + // Assert + Assert.Equal("my-form", tracker.EventName); + } + + [Fact] + public async Task EventHandlerName_SetToCombinedNameWhenParentBindingContextIsNamed() + { + // Arrange + var tracker = TrackEventNames(); + var model = new TestModel(); + var rootComponent = new TestEditFormHostComponent + { + Model = model, + FormName = "my-form", + BindingContext = new ModelBindingContext("parent-context") + }; + + // Act + _ = await RenderAndGetTestEditFormComponentAsync(rootComponent); + + // Assert + Assert.Equal("parent-context.my-form", tracker.EventName); + } + private static EditForm FindEditFormComponent(CapturedBatch batch) => batch.ReferenceFrames .Where(f => f.FrameType == RenderTreeFrameType.Component) @@ -86,12 +317,60 @@ private static EditForm FindEditFormComponent(CapturedBatch batch) .OfType() .Single(); - private static async Task RenderAndGetTestEditFormComponentAsync(TestEditFormHostComponent hostComponent) + private async Task RenderAndGetTestEditFormComponentAsync(TestEditFormHostComponent hostComponent) { - var testRenderer = new TestRenderer(); - var componentId = testRenderer.AssignRootComponentId(hostComponent); - await testRenderer.RenderRootComponentAsync(componentId); - return FindEditFormComponent(testRenderer.Batches.Single()); + var componentId = _testRenderer.AssignRootComponentId(hostComponent); + await _testRenderer.RenderRootComponentAsync(componentId); + return FindEditFormComponent(_testRenderer.Batches.Single()); + } + + private IEnumerable GetFormElementAttributeFrames() + { + var frames = _testRenderer.Batches.Single().ReferenceFrames; + var index = frames + .Select((frame, index) => (frame, index)) + .Where(pair => pair.frame.FrameType == RenderTreeFrameType.Element) + .Select(pair => pair.index) + .Single(); + + var attributes = frames + .Skip(index + 1) + .TakeWhile(f => f.FrameType == RenderTreeFrameType.Attribute); + + return attributes; + } + + private int GetComponentFrameIndex() + { + var frames = _testRenderer.Batches.Single().ReferenceFrames; + var frameIndex = frames + .Select((frame, index) => (frame, index)) + .Where(pair => pair.frame.FrameType == RenderTreeFrameType.Component && pair.frame.Component is EditForm) + .Select(pair => pair.index) + .Single(); + return frameIndex; + } + + private EventHandlerNameTracker TrackEventNames() + { + var tracker = new EventHandlerNameTracker(); + _testRenderer.TrackNamedEventHandlers = true; + _testRenderer.OnNamedEvent += tracker.Track; + return tracker; + } + + private class EventHandlerNameTracker + { + public ulong EventHandlerId { get; private set; } + + public int ComponentId { get; private set; } + + public string EventName { get; private set; } + + internal void Track((ulong, int, string) tuple) + { + (EventHandlerId, ComponentId, EventName) = tuple; + } } class TestModel @@ -102,14 +381,49 @@ class TestModel class TestEditFormHostComponent : AutoRenderComponent { public EditContext EditContext { get; set; } + public TestModel Model { get; set; } + public ModelBindingContext BindingContext { get; set; } + + public Action SubmitHandler { get; set; } + + public string FormName { get; set; } + + public Dictionary AdditionalFormAttributes { get; internal set; } + protected override void BuildRenderTree(RenderTreeBuilder builder) { - builder.OpenComponent(0); - builder.AddComponentParameter(1, "Model", Model); - builder.AddComponentParameter(2, "EditContext", EditContext); - builder.CloseComponent(); + if (BindingContext != null) + { + builder.OpenComponent(0); + builder.AddComponentParameter(1, nameof(CascadingModelBinder.Name), BindingContext.Name); + builder.AddComponentParameter(2, nameof(CascadingModelBinder.BindingId), BindingContext.BindingId); + builder.AddComponentParameter(3, nameof(CascadingModelBinder.ChildContent), (_) => RenderForm); + builder.CloseComponent(); + } + else + { + RenderForm(builder); + } + + void RenderForm(RenderTreeBuilder builder) + { + builder.OpenComponent(0); + // Order here is intentional to make sure that the test fails if we + // accidentally capture a parameter in a named property. + builder.AddMultipleAttributes(1, AdditionalFormAttributes); + + builder.AddComponentParameter(2, "Model", Model); + builder.AddComponentParameter(3, "EditContext", EditContext); + if (SubmitHandler != null) + { + builder.AddComponentParameter(4, "OnValidSubmit", new EventCallback(null, SubmitHandler)); + } + builder.AddComponentParameter(5, "Name", FormName); + + builder.CloseComponent(); + } } } } From 32f0f4cd2bfe9a83d192a708c07959d0bc67ad5a Mon Sep 17 00:00:00 2001 From: jacalvar Date: Mon, 17 Apr 2023 15:42:35 +0200 Subject: [PATCH 13/28] Fix tests --- .../test/AuthorizeRouteViewTest.cs | 51 +++++++++++++++++-- .../src/Rendering/RenderTreeBuilder.cs | 24 --------- src/Components/Components/src/RouteView.cs | 16 ++++-- .../test/RenderTreeDiffBuilderTest.cs | 2 +- .../Components/test/RouteViewTest.cs | 42 ++++++++++++--- .../Samples/BlazorUnitedApp/Pages/Index.razor | 6 +-- src/Components/Web/src/Forms/EditForm.cs | 12 +++-- src/Components/Web/test/Forms/EditFormTest.cs | 2 +- .../Pages/FormStreamingRendering.razor | 4 +- 9 files changed, 108 insertions(+), 51 deletions(-) diff --git a/src/Components/Authorization/test/AuthorizeRouteViewTest.cs b/src/Components/Authorization/test/AuthorizeRouteViewTest.cs index 98fc3d60b60b..48dae7d0aefe 100644 --- a/src/Components/Authorization/test/AuthorizeRouteViewTest.cs +++ b/src/Components/Authorization/test/AuthorizeRouteViewTest.cs @@ -3,6 +3,7 @@ using System.Security.Claims; using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Components.Binding; using Microsoft.AspNetCore.Components.Rendering; using Microsoft.AspNetCore.Components.RenderTree; using Microsoft.AspNetCore.Components.Test.Helpers; @@ -33,8 +34,10 @@ public AuthorizeRouteViewTest() serviceCollection.AddSingleton(_testAuthorizationService); serviceCollection.AddSingleton(); - _renderer = new TestRenderer(serviceCollection.BuildServiceProvider()); - _authorizeRouteViewComponent = new AuthorizeRouteView(); + var services = serviceCollection.BuildServiceProvider(); + _renderer = new TestRenderer(services); + var componentFactory = new ComponentFactory(new DefaultComponentActivator()); + _authorizeRouteViewComponent = (AuthorizeRouteView)componentFactory.InstantiateComponent(services, typeof(AuthorizeRouteView)); _authorizeRouteViewComponentId = _renderer.AssignRootComponentId(_authorizeRouteViewComponent); } @@ -63,10 +66,26 @@ public void WhenAuthorized_RendersPageInsideLayout() edit => { Assert.Equal(RenderTreeEditType.PrependFrame, edit.Type); - AssertFrame.Component(batch.ReferenceFrames[edit.ReferenceFrameIndex]); + AssertFrame.Component(batch.ReferenceFrames[edit.ReferenceFrameIndex]); }, edit => AssertPrependText(batch, edit, "Layout ends here")); + var cascadingModelBinderDiff = batch.GetComponentDiffs().Single(); + Assert.Collection(cascadingModelBinderDiff.Edits, + edit => + { + Assert.Equal(RenderTreeEditType.PrependFrame, edit.Type); + AssertFrame.Component>(batch.ReferenceFrames[edit.ReferenceFrameIndex]); + }); + + var cascadingValueDiff = batch.GetComponentDiffs>().Single(); + Assert.Collection(cascadingValueDiff.Edits, + edit => + { + Assert.Equal(RenderTreeEditType.PrependFrame, edit.Type); + AssertFrame.Component(batch.ReferenceFrames[edit.ReferenceFrameIndex]); + }); + // Assert: renders page var pageDiff = batch.GetComponentDiffs().Single(); Assert.Collection(pageDiff.Edits, @@ -100,10 +119,26 @@ public void AuthorizesWhenResourceIsSet() edit => { Assert.Equal(RenderTreeEditType.PrependFrame, edit.Type); - AssertFrame.Component(batch.ReferenceFrames[edit.ReferenceFrameIndex]); + AssertFrame.Component(batch.ReferenceFrames[edit.ReferenceFrameIndex]); }, edit => AssertPrependText(batch, edit, "Layout ends here")); + var cascadingModelBinderDiff = batch.GetComponentDiffs().Single(); + Assert.Collection(cascadingModelBinderDiff.Edits, + edit => + { + Assert.Equal(RenderTreeEditType.PrependFrame, edit.Type); + AssertFrame.Component>(batch.ReferenceFrames[edit.ReferenceFrameIndex]); + }); + + var cascadingValueDiff = batch.GetComponentDiffs>().Single(); + Assert.Collection(cascadingValueDiff.Edits, + edit => + { + Assert.Equal(RenderTreeEditType.PrependFrame, edit.Type); + AssertFrame.Component(batch.ReferenceFrames[edit.ReferenceFrameIndex]); + }); + // Assert: renders page var pageDiff = batch.GetComponentDiffs().Single(); Assert.Collection(pageDiff.Edits, @@ -291,6 +326,8 @@ public void WithoutCascadedAuthenticationState_WrapsOutputInCascadingAuthenticat component => Assert.IsType>>(component), component => Assert.IsAssignableFrom(component), component => Assert.IsType(component), + component => Assert.IsType(component), + component => Assert.IsType>(component), component => Assert.IsType(component)); } @@ -322,6 +359,8 @@ public void WithCascadedAuthenticationState_DoesNotWrapOutputInCascadingAuthenti // further CascadingAuthenticationState component => Assert.IsAssignableFrom(component), component => Assert.IsType(component), + component => Assert.IsType(component), + component => Assert.IsType>(component), component => Assert.IsType(component)); } @@ -424,5 +463,9 @@ protected override void BuildRenderTree(RenderTreeBuilder builder) class TestNavigationManager : NavigationManager { + public TestNavigationManager() + { + Initialize("https://localhost:85/subdir/", "https://localhost:85/subdir/path?query=value#hash"); + } } } diff --git a/src/Components/Components/src/Rendering/RenderTreeBuilder.cs b/src/Components/Components/src/Rendering/RenderTreeBuilder.cs index 401557745c80..874a32a99a39 100644 --- a/src/Components/Components/src/Rendering/RenderTreeBuilder.cs +++ b/src/Components/Components/src/Rendering/RenderTreeBuilder.cs @@ -557,30 +557,6 @@ public void AddComponentParameter(int sequence, string name, object? value) _entries.AppendAttribute(sequence, name, value); } - /// - /// Appends a frame representing a component parameter. - /// - /// An integer that represents the position of the instruction in the source code. - /// The name of the attribute. - /// The value of the attribute. - public void AddComponentParameter(int sequence, string name, RenderFragment? value) - { - AssertCanAddComponentParameter(); - _entries.AppendAttribute(sequence, name, value); - } - - /// - /// Appends a frame representing a component parameter. - /// - /// An integer that represents the position of the instruction in the source code. - /// The name of the attribute. - /// The value of the attribute. - public void AddComponentParameter(int sequence, string name, RenderFragment? value) - { - AssertCanAddComponentParameter(); - _entries.AppendAttribute(sequence, name, value); - } - /// /// Assigns the specified key value to the current element or component. /// diff --git a/src/Components/Components/src/RouteView.cs b/src/Components/Components/src/RouteView.cs index 68017b5d326d..025a832b5643 100644 --- a/src/Components/Components/src/RouteView.cs +++ b/src/Components/Components/src/RouteView.cs @@ -5,6 +5,7 @@ using System.Diagnostics.CodeAnalysis; using System.Reflection; +using Microsoft.AspNetCore.Components.Binding; using Microsoft.AspNetCore.Components.Rendering; using Microsoft.AspNetCore.Components.Routing; @@ -70,7 +71,7 @@ protected virtual void Render(RenderTreeBuilder builder) builder.OpenComponent(0); builder.AddComponentParameter(1, nameof(LayoutView.Layout), pageLayoutType); - builder.AddComponentParameter(2, nameof(LayoutView.ChildContent), RenderPageWithParameters); + builder.AddComponentParameter(2, nameof(LayoutView.ChildContent), (RenderFragment)RenderPageWithParameters); builder.CloseComponent(); } @@ -85,9 +86,15 @@ private void RenderPageWithParameters(RenderTreeBuilder builder) var index => index - pathStart }); + builder.OpenComponent(0); builder.AddComponentParameter(1, nameof(CascadingModelBinder.Name), ""); - builder.AddComponentParameter(1, nameof(CascadingModelBinder.BindingId), bindingId); - builder.AddComponentParameter(2, nameof(CascadingModelBinder.ChildContent), builder => + builder.AddComponentParameter(2, nameof(CascadingModelBinder.BindingId), bindingId); + builder.AddComponentParameter(3, nameof(CascadingModelBinder.ChildContent), (RenderFragment)RenderPageWithContext); + builder.CloseComponent(); + + RenderFragment RenderPageWithContext(ModelBindingContext context) => RenderPageCore; + + void RenderPageCore(RenderTreeBuilder builder) { builder.OpenComponent(0, RouteData.PageType); @@ -113,7 +120,6 @@ private void RenderPageWithParameters(RenderTreeBuilder builder) } builder.CloseComponent(); - }); - builder.CloseComponent(); + } } } diff --git a/src/Components/Components/test/RenderTreeDiffBuilderTest.cs b/src/Components/Components/test/RenderTreeDiffBuilderTest.cs index 05d76ffff267..57303b776fb6 100644 --- a/src/Components/Components/test/RenderTreeDiffBuilderTest.cs +++ b/src/Components/Components/test/RenderTreeDiffBuilderTest.cs @@ -1793,7 +1793,7 @@ public void AlwaysRegardsRenderFragmentAsPossiblyChanged() using var batchBuilder = new RenderBatchBuilder(); using var renderTreeBuilder = new RenderTreeBuilder(); - RenderTreeDiffBuilder.ComputeDiff(renderer, batchBuilder, 0, oldTree.GetFrames(), renderTreeBuilder.GetFrames(), renderTreeBuilder.GetNamedEvents()); + RenderTreeDiffBuilder.ComputeDiff(renderer, batchBuilder, 0, renderTreeBuilder.GetFrames(), oldTree.GetFrames(), renderTreeBuilder.GetNamedEvents()); var componentInstance = (CaptureSetParametersComponent)oldTree.GetFrames().Array[0].Component; Assert.Equal(1, componentInstance.SetParametersCallCount); diff --git a/src/Components/Components/test/RouteViewTest.cs b/src/Components/Components/test/RouteViewTest.cs index 35234abe2e83..7f89d70a6864 100644 --- a/src/Components/Components/test/RouteViewTest.cs +++ b/src/Components/Components/test/RouteViewTest.cs @@ -1,8 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using Microsoft.AspNetCore.Components.Binding; using Microsoft.AspNetCore.Components.Rendering; using Microsoft.AspNetCore.Components.Test.Helpers; +using Microsoft.Extensions.DependencyInjection; +using static Microsoft.AspNetCore.Components.Routing.RouterTest; namespace Microsoft.AspNetCore.Components.Test; @@ -14,8 +17,14 @@ public class RouteViewTest public RouteViewTest() { - _renderer = new TestRenderer(); - _routeViewComponent = new RouteView(); + var serviceCollection = new ServiceCollection(); + serviceCollection.AddSingleton(); + var services = serviceCollection.BuildServiceProvider(); + _renderer = new TestRenderer(services); + + var componentFactory = new ComponentFactory(new DefaultComponentActivator()); + _routeViewComponent = (RouteView)componentFactory.InstantiateComponent(services, typeof(RouteView)); + _routeViewComponentId = _renderer.AssignRootComponentId(_routeViewComponent); } @@ -62,16 +71,35 @@ public void RendersPageInsideLayoutView() frame => AssertFrame.Component(frame, subtreeLength: 2, sequence: 0), frame => AssertFrame.Attribute(frame, nameof(LayoutComponentBase.Body), sequence: 1)); - // Assert: TestLayout renders page + // Assert: TestLayout renders cascading model binder var testLayoutComponentId = batch.GetComponentFrames().Single().ComponentId; var testLayoutFrames = _renderer.GetCurrentRenderTreeFrames(testLayoutComponentId).AsEnumerable(); Assert.Collection(testLayoutFrames, frame => AssertFrame.Text(frame, "Layout starts here", sequence: 0), - frame => AssertFrame.Region(frame, subtreeLength: 3), - frame => AssertFrame.Component(frame, sequence: 0, subtreeLength: 2), - frame => AssertFrame.Attribute(frame, nameof(ComponentWithLayout.Message), "Test message", sequence: 1), + frame => AssertFrame.Region(frame, subtreeLength: 5), + frame => AssertFrame.Component(frame, sequence: 0, subtreeLength: 4), + frame => AssertFrame.Attribute(frame, nameof(CascadingModelBinder.Name), "", sequence: 1), + frame => AssertFrame.Attribute(frame, nameof(CascadingModelBinder.BindingId), "jan", sequence: 2), + frame => AssertFrame.Attribute(frame, nameof(CascadingModelBinder.ChildContent), typeof(RenderFragment), sequence: 3), frame => AssertFrame.Text(frame, "Layout ends here", sequence: 2)); + // Assert: Cascading model binder renders CascadingValue + var cascadingModelBinderComponentId = batch.GetComponentFrames().Single().ComponentId; + var cascadingModelBinderFrames = _renderer.GetCurrentRenderTreeFrames(cascadingModelBinderComponentId).AsEnumerable(); + Assert.Collection(cascadingModelBinderFrames, + frame => AssertFrame.Component>(frame, sequence: 0, subtreeLength: 4), + frame => AssertFrame.Attribute(frame, nameof(CascadingValue.IsFixed), true, sequence: 1), + frame => AssertFrame.Attribute(frame, nameof(CascadingValue.Value), typeof(ModelBindingContext), sequence: 2), + frame => AssertFrame.Attribute(frame, nameof(CascadingValue.ChildContent), typeof(RenderFragment), sequence: 3)); + + // Assert: CascadingValue renders page + var cascadingValueComponentId = batch.GetComponentFrames>().Single().ComponentId; + var cascadingValueFrames = _renderer.GetCurrentRenderTreeFrames(cascadingValueComponentId).AsEnumerable(); + Assert.Collection(cascadingValueFrames, + frame => AssertFrame.Region(frame, sequence: 0, subtreeLength: 3), + frame => AssertFrame.Component(frame, sequence: 0, subtreeLength: 2), + frame => AssertFrame.Attribute(frame, nameof(ComponentWithLayout.Message), "Test message", sequence: 1)); + // Assert: page itself is rendered, having received parameters from the original route data var pageComponentId = batch.GetComponentFrames().Single().ComponentId; var pageFrames = _renderer.GetCurrentRenderTreeFrames(pageComponentId).AsEnumerable(); @@ -79,7 +107,7 @@ public void RendersPageInsideLayoutView() frame => AssertFrame.Text(frame, "Hello from the page with message 'Test message'", sequence: 0)); // Assert: nothing else was rendered - Assert.Equal(4, batch.DiffsInOrder.Count); + Assert.Equal(6, batch.DiffsInOrder.Count); } [Fact] diff --git a/src/Components/Samples/BlazorUnitedApp/Pages/Index.razor b/src/Components/Samples/BlazorUnitedApp/Pages/Index.razor index 20026de2ad98..1684adfe5462 100644 --- a/src/Components/Samples/BlazorUnitedApp/Pages/Index.razor +++ b/src/Components/Samples/BlazorUnitedApp/Pages/Index.razor @@ -4,13 +4,13 @@

Hello, world!

- +@* - + - + *@ @if (_submitted) diff --git a/src/Components/Web/src/Forms/EditForm.cs b/src/Components/Web/src/Forms/EditForm.cs index 6f68aac43ff5..f17093344d3b 100644 --- a/src/Components/Web/src/Forms/EditForm.cs +++ b/src/Components/Web/src/Forms/EditForm.cs @@ -86,7 +86,11 @@ public EditForm() /// /// Gets or sets the form name. /// - [Parameter] public string Name { get; set; } + /// + /// The name attribute on the form element will default to + /// the unless an explicit name is provided. + /// + [Parameter] public string FormHandlerName { get; set; } /// protected override void OnParametersSet() @@ -130,11 +134,11 @@ protected override void BuildRenderTree(RenderTreeBuilder builder) // optimizing for the common case where _editContext never changes. builder.OpenRegion(_editContext.GetHashCode()); - if (Name != null) + if (FormHandlerName != null) { builder.OpenComponent(0); - builder.AddComponentParameter(1, nameof(CascadingModelBinder.Name), Name); - builder.AddComponentParameter(2, nameof(CascadingModelBinder.ChildContent), RenderWithNamedContext); + builder.AddComponentParameter(1, nameof(CascadingModelBinder.Name), FormHandlerName); + builder.AddComponentParameter(2, nameof(CascadingModelBinder.ChildContent), (RenderFragment)RenderWithNamedContext); builder.CloseComponent(); } else diff --git a/src/Components/Web/test/Forms/EditFormTest.cs b/src/Components/Web/test/Forms/EditFormTest.cs index 198c9a9b7f2f..57cea8ccadb5 100644 --- a/src/Components/Web/test/Forms/EditFormTest.cs +++ b/src/Components/Web/test/Forms/EditFormTest.cs @@ -420,7 +420,7 @@ void RenderForm(RenderTreeBuilder builder) { builder.AddComponentParameter(4, "OnValidSubmit", new EventCallback(null, SubmitHandler)); } - builder.AddComponentParameter(5, "Name", FormName); + builder.AddComponentParameter(5, "FormHandlerName", FormName); builder.CloseComponent(); } diff --git a/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/FormStreamingRendering.razor b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/FormStreamingRendering.razor index 84cf118e6aaa..8e3e75f93252 100644 --- a/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/FormStreamingRendering.razor +++ b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/FormStreamingRendering.razor @@ -5,11 +5,11 @@

Streaming Rendering

@* This just sets the context for the binding, this happens normally inside RouteView *@ - +@* - + *@

@if (_processing) From dbac6cedaba42407e1c21c8af2684ac5111a29a2 Mon Sep 17 00:00:00 2001 From: jacalvar Date: Mon, 17 Apr 2023 16:43:58 +0200 Subject: [PATCH 14/28] Cleanups and fix tests --- .../src/Binding/CascadingModelBinder.cs | 4 ++-- .../src/Binding/ModelBindingContext.cs | 6 +++--- .../Components/src/PublicAPI.Unshipped.txt | 7 ++++--- src/Components/Components/src/RouteView.cs | 2 +- .../test/RenderTreeDiffBuilderTest.cs | 18 +++++++++--------- .../Components/test/RouteViewTest.cs | 2 +- .../src/RazorComponentEndpointHost.cs | 2 +- src/Components/Web/src/Forms/EditForm.cs | 4 ++-- src/Components/Web/src/PublicAPI.Unshipped.txt | 2 ++ src/Components/Web/test/Forms/EditFormTest.cs | 4 ++-- 10 files changed, 27 insertions(+), 24 deletions(-) diff --git a/src/Components/Components/src/Binding/CascadingModelBinder.cs b/src/Components/Components/src/Binding/CascadingModelBinder.cs index a386f53363ca..8aa340acbf73 100644 --- a/src/Components/Components/src/Binding/CascadingModelBinder.cs +++ b/src/Components/Components/src/Binding/CascadingModelBinder.cs @@ -23,7 +23,7 @@ public class CascadingModelBinder : IComponent ///

/// The binding context name. /// - [Parameter] public string BindingId { get; set; } = default!; + [Parameter] public string BindingContextId { get; set; } = default!; /// /// Specifies the content to be rendered inside this . @@ -49,7 +49,7 @@ Task IComponent.SetParametersAsync(ParameterView parameters) } var name = string.IsNullOrEmpty(ParentContext?.Name) ? Name : $"{ParentContext.Name}.{Name}"; - var bindingId = !string.IsNullOrEmpty(name) ? null : BindingId; + var bindingId = !string.IsNullOrEmpty(name) ? null : BindingContextId; _bindingContext = new ModelBindingContext(name, bindingId); _handle.Render(builder => diff --git a/src/Components/Components/src/Binding/ModelBindingContext.cs b/src/Components/Components/src/Binding/ModelBindingContext.cs index e4a0146aaaed..080762ec40fe 100644 --- a/src/Components/Components/src/Binding/ModelBindingContext.cs +++ b/src/Components/Components/src/Binding/ModelBindingContext.cs @@ -23,7 +23,7 @@ public class ModelBindingContext // Nested named binder // Name = <>.<> // FormAction = ?handler=<>.<> - public ModelBindingContext(string name, string? bindingId = null) + internal ModelBindingContext(string name, string? bindingId = null) { // We are initializing the root context, that can be a "named" root context, or the default context. // A named root context only provides a name, and that acts as the BindingId @@ -35,7 +35,7 @@ public ModelBindingContext(string name, string? bindingId = null) } Name = name; - BindingId = bindingId ?? name; + BindingContextId = bindingId ?? name; } /// @@ -46,5 +46,5 @@ public ModelBindingContext(string name, string? bindingId = null) /// /// The computed identifier used to determine what parts of the app can bind data. /// - public string BindingId { get; } + public string BindingContextId { get; } } diff --git a/src/Components/Components/src/PublicAPI.Unshipped.txt b/src/Components/Components/src/PublicAPI.Unshipped.txt index 76c08dad5091..3211e60d1491 100644 --- a/src/Components/Components/src/PublicAPI.Unshipped.txt +++ b/src/Components/Components/src/PublicAPI.Unshipped.txt @@ -1,10 +1,12 @@ #nullable enable Microsoft.AspNetCore.Components.Binding.ModelBindingContext -Microsoft.AspNetCore.Components.Binding.ModelBindingContext.ModelBindingContext(string! name) -> void +Microsoft.AspNetCore.Components.Binding.ModelBindingContext.BindingContextId.get -> string! Microsoft.AspNetCore.Components.Binding.ModelBindingContext.Name.get -> string! Microsoft.AspNetCore.Components.CascadingModelBinder +Microsoft.AspNetCore.Components.CascadingModelBinder.BindingContextId.get -> string! +Microsoft.AspNetCore.Components.CascadingModelBinder.BindingContextId.set -> void Microsoft.AspNetCore.Components.CascadingModelBinder.CascadingModelBinder() -> void -Microsoft.AspNetCore.Components.CascadingModelBinder.ChildContent.get -> Microsoft.AspNetCore.Components.RenderFragment! +Microsoft.AspNetCore.Components.CascadingModelBinder.ChildContent.get -> Microsoft.AspNetCore.Components.RenderFragment! Microsoft.AspNetCore.Components.CascadingModelBinder.ChildContent.set -> void Microsoft.AspNetCore.Components.CascadingModelBinder.Name.get -> string! Microsoft.AspNetCore.Components.CascadingModelBinder.Name.set -> void @@ -13,7 +15,6 @@ Microsoft.AspNetCore.Components.Infrastructure.ComponentStatePersistenceManager. Microsoft.AspNetCore.Components.RenderHandle.DispatchExceptionAsync(System.Exception! exception) -> System.Threading.Tasks.Task! *REMOVED*Microsoft.AspNetCore.Components.NavigationManager.ToAbsoluteUri(string! relativeUri) -> System.Uri! Microsoft.AspNetCore.Components.NavigationManager.ToAbsoluteUri(string? relativeUri) -> System.Uri! -Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder.AddComponentParameter(int sequence, string! name, Microsoft.AspNetCore.Components.RenderFragment? value) -> void Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder.SetEventHandlerName(string! eventHandlerName) -> void Microsoft.AspNetCore.Components.Routing.IScrollToLocationHash Microsoft.AspNetCore.Components.Routing.IScrollToLocationHash.RefreshScrollPositionForHash(string! locationAbsolute) -> System.Threading.Tasks.Task! diff --git a/src/Components/Components/src/RouteView.cs b/src/Components/Components/src/RouteView.cs index 025a832b5643..c6daa21c6458 100644 --- a/src/Components/Components/src/RouteView.cs +++ b/src/Components/Components/src/RouteView.cs @@ -88,7 +88,7 @@ private void RenderPageWithParameters(RenderTreeBuilder builder) builder.OpenComponent(0); builder.AddComponentParameter(1, nameof(CascadingModelBinder.Name), ""); - builder.AddComponentParameter(2, nameof(CascadingModelBinder.BindingId), bindingId); + builder.AddComponentParameter(2, nameof(CascadingModelBinder.BindingContextId), bindingId); builder.AddComponentParameter(3, nameof(CascadingModelBinder.ChildContent), (RenderFragment)RenderPageWithContext); builder.CloseComponent(); diff --git a/src/Components/Components/test/RenderTreeDiffBuilderTest.cs b/src/Components/Components/test/RenderTreeDiffBuilderTest.cs index 57303b776fb6..8c92db2141f4 100644 --- a/src/Components/Components/test/RenderTreeDiffBuilderTest.cs +++ b/src/Components/Components/test/RenderTreeDiffBuilderTest.cs @@ -816,7 +816,7 @@ public void RecognizesComponentTypeChangesAtSameSequenceNumber() using var batchBuilder = new RenderBatchBuilder(); // Act - var diff = RenderTreeDiffBuilder.ComputeDiff(renderer, batchBuilder, 0, newTree.GetFrames(), oldTree.GetFrames(), oldTree.GetNamedEvents()); + var diff = RenderTreeDiffBuilder.ComputeDiff(renderer, batchBuilder, 0, oldTree.GetFrames(), newTree.GetFrames(), newTree.GetNamedEvents()); // Assert: We're going to dispose the old component and render the new one Assert.Equal(new[] { 0 }, batchBuilder.ComponentDisposalQueue); @@ -1627,7 +1627,7 @@ public void RetainsChildComponentsForExistingFrames() using var batchBuilder = new RenderBatchBuilder(); using var renderTreeBuilder = new RenderTreeBuilder(); - RenderTreeDiffBuilder.ComputeDiff(renderer, batchBuilder, 0, oldTree.GetFrames(), renderTreeBuilder.GetFrames(), renderTreeBuilder.GetNamedEvents()); + RenderTreeDiffBuilder.ComputeDiff(renderer, batchBuilder, 0, renderTreeBuilder.GetFrames(), oldTree.GetFrames(), oldTree.GetNamedEvents()); var originalFakeComponentInstance = oldTree.GetFrames().Array[2].Component; var originalFakeComponent2Instance = oldTree.GetFrames().Array[3].Component; @@ -1713,7 +1713,7 @@ public void SetsUpdatedParametersOnChildComponents() using var batchBuilder = new RenderBatchBuilder(); using var renderTree = new RenderTreeBuilder(); - RenderTreeDiffBuilder.ComputeDiff(renderer, batchBuilder, 0, oldTree.GetFrames(), renderTree.GetFrames(), renderTree.GetNamedEvents()); + RenderTreeDiffBuilder.ComputeDiff(renderer, batchBuilder, 0, renderTree.GetFrames(), oldTree.GetFrames(), oldTree.GetNamedEvents()); var originalComponentInstance = (FakeComponent)oldTree.GetFrames().Array[0].Component; // Act @@ -1763,7 +1763,7 @@ public void SkipsUpdatingParametersOnChildComponentsIfAllAreDefinitelyImmutableA using var batchBuilder = new RenderBatchBuilder(); using var renderTreeBuilder = new RenderTreeBuilder(); - RenderTreeDiffBuilder.ComputeDiff(renderer, batchBuilder, 0, oldTree.GetFrames(), renderTreeBuilder.GetFrames(), renderTreeBuilder.GetNamedEvents()); + RenderTreeDiffBuilder.ComputeDiff(renderer, batchBuilder, 0, renderTreeBuilder.GetFrames(), oldTree.GetFrames(), oldTree.GetNamedEvents()); var originalComponentInstance = (CaptureSetParametersComponent)oldTree.GetFrames().Array[0].Component; Assert.Equal(1, originalComponentInstance.SetParametersCallCount); @@ -1793,7 +1793,7 @@ public void AlwaysRegardsRenderFragmentAsPossiblyChanged() using var batchBuilder = new RenderBatchBuilder(); using var renderTreeBuilder = new RenderTreeBuilder(); - RenderTreeDiffBuilder.ComputeDiff(renderer, batchBuilder, 0, renderTreeBuilder.GetFrames(), oldTree.GetFrames(), renderTreeBuilder.GetNamedEvents()); + RenderTreeDiffBuilder.ComputeDiff(renderer, batchBuilder, 0, renderTreeBuilder.GetFrames(), oldTree.GetFrames(), oldTree.GetNamedEvents()); var componentInstance = (CaptureSetParametersComponent)oldTree.GetFrames().Array[0].Component; Assert.Equal(1, componentInstance.SetParametersCallCount); @@ -1819,13 +1819,13 @@ public void QueuesRemovedChildComponentsForDisposal() using var batchBuilder = new RenderBatchBuilder(); using var renderTree = new RenderTreeBuilder(); - RenderTreeDiffBuilder.ComputeDiff(renderer, batchBuilder, 0, oldTree.GetFrames(), renderTree.GetFrames(), renderTree.GetNamedEvents()); + RenderTreeDiffBuilder.ComputeDiff(renderer, batchBuilder, 0, renderTree.GetFrames(), oldTree.GetFrames(), oldTree.GetNamedEvents()); // Act/Assert // Note that we track NonDisposableComponent was disposed even though it's not IDisposable, // because it's up to the upstream renderer to decide what "disposing" a component means Assert.Empty(batchBuilder.ComponentDisposalQueue); - RenderTreeDiffBuilder.ComputeDiff(renderer, batchBuilder, 0, newTree.GetFrames(), oldTree.GetFrames(), oldTree.GetNamedEvents()); + RenderTreeDiffBuilder.ComputeDiff(renderer, batchBuilder, 0, oldTree.GetFrames(), newTree.GetFrames(), newTree.GetNamedEvents()); Assert.Equal(new[] { 0, 1 }, batchBuilder.ComponentDisposalQueue); } @@ -2238,14 +2238,14 @@ private RenderBatch GetRenderedBatch(RenderTreeBuilder from, RenderTreeBuilder t var emptyFrames = renderTreeBuilder.GetFrames(); var oldFrames = from.GetFrames(); - RenderTreeDiffBuilder.ComputeDiff(renderer, initializeBatchBuilder, 0, oldFrames, emptyFrames, null); + RenderTreeDiffBuilder.ComputeDiff(renderer, initializeBatchBuilder, 0, emptyFrames, oldFrames, from.GetNamedEvents()); } batchBuilder?.Dispose(); // This gets disposed as part of the test type's Dispose batchBuilder = new RenderBatchBuilder(); - var diff = RenderTreeDiffBuilder.ComputeDiff(renderer, batchBuilder, 0, to.GetFrames(), from.GetFrames(), from.GetNamedEvents()); + var diff = RenderTreeDiffBuilder.ComputeDiff(renderer, batchBuilder, 0, from.GetFrames(), to.GetFrames(), to.GetNamedEvents()); batchBuilder.UpdatedComponentDiffs.Append(diff); return batchBuilder.ToBatch(); } diff --git a/src/Components/Components/test/RouteViewTest.cs b/src/Components/Components/test/RouteViewTest.cs index 7f89d70a6864..2f00c61c0c86 100644 --- a/src/Components/Components/test/RouteViewTest.cs +++ b/src/Components/Components/test/RouteViewTest.cs @@ -79,7 +79,7 @@ public void RendersPageInsideLayoutView() frame => AssertFrame.Region(frame, subtreeLength: 5), frame => AssertFrame.Component(frame, sequence: 0, subtreeLength: 4), frame => AssertFrame.Attribute(frame, nameof(CascadingModelBinder.Name), "", sequence: 1), - frame => AssertFrame.Attribute(frame, nameof(CascadingModelBinder.BindingId), "jan", sequence: 2), + frame => AssertFrame.Attribute(frame, nameof(CascadingModelBinder.BindingContextId), "jan", sequence: 2), frame => AssertFrame.Attribute(frame, nameof(CascadingModelBinder.ChildContent), typeof(RenderFragment), sequence: 3), frame => AssertFrame.Text(frame, "Layout ends here", sequence: 2)); diff --git a/src/Components/Endpoints/src/RazorComponentEndpointHost.cs b/src/Components/Endpoints/src/RazorComponentEndpointHost.cs index 626a93847e8f..064941afc074 100644 --- a/src/Components/Endpoints/src/RazorComponentEndpointHost.cs +++ b/src/Components/Endpoints/src/RazorComponentEndpointHost.cs @@ -39,7 +39,7 @@ private void BuildRenderTree(RenderTreeBuilder builder) builder.OpenComponent(0); builder.AddComponentParameter(1, nameof(LayoutView.Layout), pageLayoutType); - builder.AddComponentParameter(2, nameof(LayoutView.ChildContent), RenderPageWithParameters); + builder.AddComponentParameter(2, nameof(LayoutView.ChildContent), (RenderFragment)RenderPageWithParameters); builder.CloseComponent(); } diff --git a/src/Components/Web/src/Forms/EditForm.cs b/src/Components/Web/src/Forms/EditForm.cs index f17093344d3b..f35109188313 100644 --- a/src/Components/Web/src/Forms/EditForm.cs +++ b/src/Components/Web/src/Forms/EditForm.cs @@ -90,7 +90,7 @@ public EditForm() /// The name attribute on the form element will default to /// the unless an explicit name is provided. /// - [Parameter] public string FormHandlerName { get; set; } + [Parameter] public string? FormHandlerName { get; set; } /// protected override void OnParametersSet() @@ -165,7 +165,7 @@ void RenderFormContents(RenderTreeBuilder builder, ModelBindingContext? bindingC builder.AddAttribute(4, "onsubmit", _handleSubmitDelegate); if (bindingContext != null) { - builder.SetEventHandlerName(bindingContext.BindingId); + builder.SetEventHandlerName(bindingContext.BindingContextId); } builder.OpenComponent>(5); builder.AddComponentParameter(6, "IsFixed", true); diff --git a/src/Components/Web/src/PublicAPI.Unshipped.txt b/src/Components/Web/src/PublicAPI.Unshipped.txt index e99094027c4d..d7db6732b502 100644 --- a/src/Components/Web/src/PublicAPI.Unshipped.txt +++ b/src/Components/Web/src/PublicAPI.Unshipped.txt @@ -3,6 +3,8 @@ Microsoft.AspNetCore.Components.Forms.InputBase.NameAttributeValue.get -> string! Microsoft.AspNetCore.Components.Forms.EditForm.BindingContext.get -> Microsoft.AspNetCore.Components.Binding.ModelBindingContext? Microsoft.AspNetCore.Components.Forms.EditForm.BindingContext.set -> void +Microsoft.AspNetCore.Components.Forms.EditForm.FormHandlerName.get -> string? +Microsoft.AspNetCore.Components.Forms.EditForm.FormHandlerName.set -> void Microsoft.AspNetCore.Components.HtmlRendering.Infrastructure.StaticHtmlRenderer Microsoft.AspNetCore.Components.HtmlRendering.Infrastructure.StaticHtmlRenderer.BeginRenderingComponent(System.Type! componentType, Microsoft.AspNetCore.Components.ParameterView initialParameters) -> Microsoft.AspNetCore.Components.Web.HtmlRendering.HtmlRootComponent Microsoft.AspNetCore.Components.HtmlRendering.Infrastructure.StaticHtmlRenderer.StaticHtmlRenderer(System.IServiceProvider! serviceProvider, Microsoft.Extensions.Logging.ILoggerFactory! loggerFactory) -> void diff --git a/src/Components/Web/test/Forms/EditFormTest.cs b/src/Components/Web/test/Forms/EditFormTest.cs index 57cea8ccadb5..0aa095665e7b 100644 --- a/src/Components/Web/test/Forms/EditFormTest.cs +++ b/src/Components/Web/test/Forms/EditFormTest.cs @@ -398,8 +398,8 @@ protected override void BuildRenderTree(RenderTreeBuilder builder) { builder.OpenComponent(0); builder.AddComponentParameter(1, nameof(CascadingModelBinder.Name), BindingContext.Name); - builder.AddComponentParameter(2, nameof(CascadingModelBinder.BindingId), BindingContext.BindingId); - builder.AddComponentParameter(3, nameof(CascadingModelBinder.ChildContent), (_) => RenderForm); + builder.AddComponentParameter(2, nameof(CascadingModelBinder.BindingContextId), BindingContext.BindingContextId); + builder.AddComponentParameter(3, nameof(CascadingModelBinder.ChildContent), (RenderFragment)((_) => RenderForm)); builder.CloseComponent(); } else From 571b1c33cac385e1be37359a2959999ad7320158 Mon Sep 17 00:00:00 2001 From: jacalvar Date: Mon, 17 Apr 2023 17:28:31 +0200 Subject: [PATCH 15/28] Add additional route view tests --- src/Components/Components/src/RouteView.cs | 2 +- .../Components/test/RouteViewTest.cs | 95 ++++++++++++++++++- 2 files changed, 94 insertions(+), 3 deletions(-) diff --git a/src/Components/Components/src/RouteView.cs b/src/Components/Components/src/RouteView.cs index c6daa21c6458..8fdb1e4ec255 100644 --- a/src/Components/Components/src/RouteView.cs +++ b/src/Components/Components/src/RouteView.cs @@ -77,7 +77,7 @@ protected virtual void Render(RenderTreeBuilder builder) private void RenderPageWithParameters(RenderTreeBuilder builder) { - var pathStart = NavigationManager.BaseUri.Length; + var pathStart = NavigationManager.BaseUri.Length - 1; var bindingId = NavigationManager.Uri.Substring( pathStart, NavigationManager.Uri.AsSpan().IndexOfAny("?#") switch diff --git a/src/Components/Components/test/RouteViewTest.cs b/src/Components/Components/test/RouteViewTest.cs index 2f00c61c0c86..65b507c3f951 100644 --- a/src/Components/Components/test/RouteViewTest.cs +++ b/src/Components/Components/test/RouteViewTest.cs @@ -5,20 +5,21 @@ using Microsoft.AspNetCore.Components.Rendering; using Microsoft.AspNetCore.Components.Test.Helpers; using Microsoft.Extensions.DependencyInjection; -using static Microsoft.AspNetCore.Components.Routing.RouterTest; namespace Microsoft.AspNetCore.Components.Test; public class RouteViewTest { private readonly TestRenderer _renderer; + private readonly RouteViewTestNavigationManager _navigationManager; private readonly RouteView _routeViewComponent; private readonly int _routeViewComponentId; public RouteViewTest() { var serviceCollection = new ServiceCollection(); - serviceCollection.AddSingleton(); + _navigationManager = new RouteViewTestNavigationManager(); + serviceCollection.AddSingleton(_navigationManager); var services = serviceCollection.BuildServiceProvider(); _renderer = new TestRenderer(services); @@ -110,6 +111,84 @@ public void RendersPageInsideLayoutView() Assert.Equal(6, batch.DiffsInOrder.Count); } + [Theory] + [InlineData("https://www.example.com/subdir/path", "/path")] + [InlineData("https://www.example.com/subdir/", "/")] + [InlineData("https://www.example.com/subdir/path/with/multiple/segments", "/path/with/multiple/segments")] + [InlineData("https://www.example.com/subdir/path/with/multiple/segments?and=query", "/path/with/multiple/segments")] + [InlineData("https://www.example.com/subdir/path/with/multiple/segments?and=query#hashtoo", "/path/with/multiple/segments")] + [InlineData("https://www.example.com/subdir/path/with/#multiple/segments?and=query#hashtoo", "/path/with/")] + [InlineData("https://www.example.com/subdir/path/with/#multiple/segments#hashtoo?and=query", "/path/with/multiple/segments")] + public void ProvidesDocumentPathAsBindingContextId(string url, string expectedBindingContextId) + { + // Arrange + _navigationManager.NotifyLocationChanged(url); + var routeParams = new Dictionary + { + { nameof(ComponentWithLayout.Message), "Test message" } + }; + var routeData = new RouteData(typeof(ComponentWithLayout), routeParams); + + // Act + _renderer.Dispatcher.InvokeAsync(() => _routeViewComponent.SetParametersAsync(ParameterView.FromDictionary(new Dictionary + { + { nameof(RouteView.RouteData), routeData }, + }))); + + // Assert: RouteView renders LayoutView + var batch = _renderer.Batches.Single(); + var routeViewFrames = _renderer.GetCurrentRenderTreeFrames(_routeViewComponentId).AsEnumerable(); + Assert.Collection(routeViewFrames, + frame => AssertFrame.Component(frame, subtreeLength: 3, sequence: 0), + frame => AssertFrame.Attribute(frame, nameof(LayoutView.Layout), (object)typeof(TestLayout), sequence: 1), + frame => AssertFrame.Attribute(frame, nameof(LayoutView.ChildContent), sequence: 2)); + + // Assert: LayoutView renders TestLayout + var layoutViewComponentId = batch.GetComponentFrames().Single().ComponentId; + var layoutViewFrames = _renderer.GetCurrentRenderTreeFrames(layoutViewComponentId).AsEnumerable(); + Assert.Collection(layoutViewFrames, + frame => AssertFrame.Component(frame, subtreeLength: 2, sequence: 0), + frame => AssertFrame.Attribute(frame, nameof(LayoutComponentBase.Body), sequence: 1)); + + // Assert: TestLayout renders cascading model binder + var testLayoutComponentId = batch.GetComponentFrames().Single().ComponentId; + var testLayoutFrames = _renderer.GetCurrentRenderTreeFrames(testLayoutComponentId).AsEnumerable(); + Assert.Collection(testLayoutFrames, + frame => AssertFrame.Text(frame, "Layout starts here", sequence: 0), + frame => AssertFrame.Region(frame, subtreeLength: 5), + frame => AssertFrame.Component(frame, sequence: 0, subtreeLength: 4), + frame => AssertFrame.Attribute(frame, nameof(CascadingModelBinder.Name), "", sequence: 1), + frame => AssertFrame.Attribute(frame, nameof(CascadingModelBinder.BindingContextId), expectedBindingContextId, sequence: 2), + frame => AssertFrame.Attribute(frame, nameof(CascadingModelBinder.ChildContent), typeof(RenderFragment), sequence: 3), + frame => AssertFrame.Text(frame, "Layout ends here", sequence: 2)); + + // Assert: Cascading model binder renders CascadingValue + var cascadingModelBinderComponentId = batch.GetComponentFrames().Single().ComponentId; + var cascadingModelBinderFrames = _renderer.GetCurrentRenderTreeFrames(cascadingModelBinderComponentId).AsEnumerable(); + Assert.Collection(cascadingModelBinderFrames, + frame => AssertFrame.Component>(frame, sequence: 0, subtreeLength: 4), + frame => AssertFrame.Attribute(frame, nameof(CascadingValue.IsFixed), true, sequence: 1), + frame => AssertFrame.Attribute(frame, nameof(CascadingValue.Value), typeof(ModelBindingContext), sequence: 2), + frame => AssertFrame.Attribute(frame, nameof(CascadingValue.ChildContent), typeof(RenderFragment), sequence: 3)); + + // Assert: CascadingValue renders page + var cascadingValueComponentId = batch.GetComponentFrames>().Single().ComponentId; + var cascadingValueFrames = _renderer.GetCurrentRenderTreeFrames(cascadingValueComponentId).AsEnumerable(); + Assert.Collection(cascadingValueFrames, + frame => AssertFrame.Region(frame, sequence: 0, subtreeLength: 3), + frame => AssertFrame.Component(frame, sequence: 0, subtreeLength: 2), + frame => AssertFrame.Attribute(frame, nameof(ComponentWithLayout.Message), "Test message", sequence: 1)); + + // Assert: page itself is rendered, having received parameters from the original route data + var pageComponentId = batch.GetComponentFrames().Single().ComponentId; + var pageFrames = _renderer.GetCurrentRenderTreeFrames(pageComponentId).AsEnumerable(); + Assert.Collection(pageFrames, + frame => AssertFrame.Text(frame, "Hello from the page with message 'Test message'", sequence: 0)); + + // Assert: nothing else was rendered + Assert.Equal(6, batch.DiffsInOrder.Count); + } + [Fact] public void UsesDefaultLayoutIfNoneSetOnPage() { @@ -181,6 +260,18 @@ public void PageLayoutSupersedesDefaultLayout() frame => AssertFrame.Attribute(frame, nameof(LayoutView.ChildContent), sequence: 2)); } + private class RouteViewTestNavigationManager : NavigationManager + { + public RouteViewTestNavigationManager() => + Initialize("https://www.example.com/subdir/", "https://www.example.com/subdir/"); + + public void NotifyLocationChanged(string uri) + { + Uri = uri; + NotifyLocationChanged(false); + } + } + private class ComponentWithoutLayout : AutoRenderComponent { [Parameter] public string Message { get; set; } From 4d315ee9bae38f2209f5baa0d7695dc7fc7f1023 Mon Sep 17 00:00:00 2001 From: jacalvar Date: Mon, 17 Apr 2023 19:14:54 +0200 Subject: [PATCH 16/28] CascadingModelBinder, ModelBindingContext and RouteView test --- .../src/Binding/CascadingModelBinder.cs | 2 +- .../src/Binding/ModelBindingContext.cs | 6 +- .../test/CascadingModelBinderTest.cs | 166 ++++++++++++++++++ .../test/ModelBindingContextTest.cs | 37 ++++ .../Components/test/RouteViewTest.cs | 2 +- 5 files changed, 208 insertions(+), 5 deletions(-) create mode 100644 src/Components/Components/test/CascadingModelBinderTest.cs create mode 100644 src/Components/Components/test/ModelBindingContextTest.cs diff --git a/src/Components/Components/src/Binding/CascadingModelBinder.cs b/src/Components/Components/src/Binding/CascadingModelBinder.cs index 8aa340acbf73..0c382abfc4aa 100644 --- a/src/Components/Components/src/Binding/CascadingModelBinder.cs +++ b/src/Components/Components/src/Binding/CascadingModelBinder.cs @@ -45,7 +45,7 @@ Task IComponent.SetParametersAsync(ParameterView parameters) parameters.SetParameterProperties(this); if (ParentContext != null && string.IsNullOrEmpty(Name)) { - throw new InvalidOperationException("Nested binding contexts must define a Name."); + throw new InvalidOperationException($"Nested binding contexts must define a Name. (Parent context) = '{ParentContext.BindingContextId}'."); } var name = string.IsNullOrEmpty(ParentContext?.Name) ? Name : $"{ParentContext.Name}.{Name}"; diff --git a/src/Components/Components/src/Binding/ModelBindingContext.cs b/src/Components/Components/src/Binding/ModelBindingContext.cs index 080762ec40fe..da31f0a3b95c 100644 --- a/src/Components/Components/src/Binding/ModelBindingContext.cs +++ b/src/Components/Components/src/Binding/ModelBindingContext.cs @@ -23,19 +23,19 @@ public class ModelBindingContext // Nested named binder // Name = <>.<> // FormAction = ?handler=<>.<> - internal ModelBindingContext(string name, string? bindingId = null) + internal ModelBindingContext(string name, string? bindingContextId = null) { // We are initializing the root context, that can be a "named" root context, or the default context. // A named root context only provides a name, and that acts as the BindingId // A "default" root context does not provide a name, and instead it provides an explicit Binding ID. // The explicit binding ID matches that of the default handler, which is the URL Path. - if (!(string.IsNullOrEmpty(name) ^ string.IsNullOrEmpty(bindingId))) + if (!(string.IsNullOrEmpty(name) ^ string.IsNullOrEmpty(bindingContextId))) { throw new InvalidOperationException("A root binding context needs to provide either a name or explicit binding ID."); } Name = name; - BindingContextId = bindingId ?? name; + BindingContextId = bindingContextId ?? name; } /// diff --git a/src/Components/Components/test/CascadingModelBinderTest.cs b/src/Components/Components/test/CascadingModelBinderTest.cs new file mode 100644 index 000000000000..9d72f8914e07 --- /dev/null +++ b/src/Components/Components/test/CascadingModelBinderTest.cs @@ -0,0 +1,166 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.AspNetCore.Components.Binding; +using Microsoft.AspNetCore.Components.Rendering; +using Microsoft.AspNetCore.Components.Test.Helpers; + +namespace Microsoft.AspNetCore.Components; + +public class CascadingModelBinderTest +{ + [Fact] + public void CascadingModelBinder_UsesBindingIdWhenNoDefaultName() + { + ModelBindingContext capturedContext = null; + RenderFragment contents = (ctx) => b => { capturedContext = ctx; }; + + var renderer = new TestRenderer(); + var testComponent = new TestComponent(builder => + { + builder.OpenComponent(0); + builder.AddAttribute(1, nameof(CascadingModelBinder.BindingContextId), "/path"); + builder.AddAttribute(2, nameof(CascadingModelBinder.ChildContent), contents); + builder.CloseComponent(); + }); + var id = renderer.AssignRootComponentId(testComponent); + + // Act + renderer.RenderRootComponent(id); + + // Assert + Assert.NotNull(capturedContext); + Assert.Null(capturedContext.Name); + Assert.Equal("/path", capturedContext.BindingContextId); + } + + [Fact] + public void CascadingModelBinder_CanProvideName() + { + ModelBindingContext capturedContext = null; + RenderFragment contents = (ctx) => b => { capturedContext = ctx; }; + + var renderer = new TestRenderer(); + var testComponent = new TestComponent(builder => + { + builder.OpenComponent(0); + builder.AddAttribute(1, nameof(CascadingModelBinder.Name), "named-context"); + builder.AddAttribute(2, nameof(CascadingModelBinder.ChildContent), contents); + builder.CloseComponent(); + }); + var id = renderer.AssignRootComponentId(testComponent); + + // Act + renderer.RenderRootComponent(id); + + // Assert + Assert.NotNull(capturedContext); + Assert.Equal("named-context", capturedContext.Name); + Assert.Equal("named-context", capturedContext.BindingContextId); + } + + [Fact] + public void CascadingModelBinder_CanNestNamedContexts() + { + ModelBindingContext capturedContext = null; + RenderFragment contents = (ctx) => b => { capturedContext = ctx; }; + RenderFragment nested = (ctx) => b => + { + b.OpenComponent(0); + b.AddAttribute(1, nameof(CascadingModelBinder.Name), "child-context"); + b.AddAttribute(2, nameof(CascadingModelBinder.ChildContent), contents); + b.CloseComponent(); + }; + + var renderer = new TestRenderer(); + var testComponent = new TestComponent(builder => + { + builder.OpenComponent(0); + builder.AddAttribute(1, nameof(CascadingModelBinder.Name), "parent-context"); + builder.AddAttribute(2, nameof(CascadingModelBinder.ChildContent), nested); + builder.CloseComponent(); + }); + var id = renderer.AssignRootComponentId(testComponent); + + // Act + renderer.RenderRootComponent(id); + + // Assert + Assert.NotNull(capturedContext); + Assert.Equal("parent-context.child-context", capturedContext.Name); + Assert.Equal("parent-context.child-context", capturedContext.BindingContextId); + } + + [Fact] + public void CascadingModelBinder_CanNestWithDefaultContext() + { + ModelBindingContext capturedContext = null; + RenderFragment contents = (ctx) => b => { capturedContext = ctx; }; + RenderFragment nested = (ctx) => b => + { + b.OpenComponent(0); + b.AddAttribute(1, nameof(CascadingModelBinder.Name), "child-context"); + b.AddAttribute(2, nameof(CascadingModelBinder.ChildContent), contents); + b.CloseComponent(); + }; + + var renderer = new TestRenderer(); + var testComponent = new TestComponent(builder => + { + builder.OpenComponent(0); + builder.AddAttribute(1, nameof(CascadingModelBinder.BindingContextId), "/path"); + builder.AddAttribute(2, nameof(CascadingModelBinder.ChildContent), nested); + builder.CloseComponent(); + }); + var id = renderer.AssignRootComponentId(testComponent); + + // Act + renderer.RenderRootComponent(id); + + // Assert + Assert.NotNull(capturedContext); + Assert.Equal("child-context", capturedContext.Name); + Assert.Equal("child-context", capturedContext.BindingContextId); + } + + [Fact] + public void Throws_IfDefaultContextIsNotTheRoot() + { + ModelBindingContext capturedContext = null; + RenderFragment contents = (ctx) => b => { capturedContext = ctx; }; + RenderFragment nested = (ctx) => b => + { + b.OpenComponent(0); + b.AddAttribute(1, nameof(CascadingModelBinder.BindingContextId), "/path"); + b.AddAttribute(2, nameof(CascadingModelBinder.ChildContent), contents); + b.CloseComponent(); + }; + + var renderer = new TestRenderer(); + var testComponent = new TestComponent(builder => + { + builder.OpenComponent(0); + builder.AddAttribute(1, nameof(CascadingModelBinder.Name), "parent-context"); + builder.AddAttribute(2, nameof(CascadingModelBinder.ChildContent), nested); + builder.CloseComponent(); + }); + var id = renderer.AssignRootComponentId(testComponent); + + // Act + var exception = Assert.Throws(() => renderer.RenderRootComponent(id)); + Assert.Equal($"Nested binding contexts must define a Name. (Parent context) = 'parent-context'.", exception.Message); + } + + class TestComponent : AutoRenderComponent + { + private readonly RenderFragment _renderFragment; + + public TestComponent(RenderFragment renderFragment) + { + _renderFragment = renderFragment; + } + + protected override void BuildRenderTree(RenderTreeBuilder builder) + => _renderFragment(builder); + } +} diff --git a/src/Components/Components/test/ModelBindingContextTest.cs b/src/Components/Components/test/ModelBindingContextTest.cs new file mode 100644 index 000000000000..8d0d4c492c12 --- /dev/null +++ b/src/Components/Components/test/ModelBindingContextTest.cs @@ -0,0 +1,37 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.AspNetCore.Components.Binding; + +namespace Microsoft.AspNetCore.Components; + +public class ModelBindingContextTest +{ + [Fact] + public void Throws_IfNameAndBindingContextId_AreProvided() + { + Assert.Throws(() => new ModelBindingContext("name", "id")); + } + + [Fact] + public void Throws_IfNoNameOrBindingContextId_AreProvided() + { + Assert.Throws(() => new ModelBindingContext("name", "id")); + } + + [Fact] + public void Name_UsedAsBindingContextId_WhenProvided() + { + var context = new ModelBindingContext("navigation"); + Assert.Equal("navigation", context.Name); + Assert.Equal("navigation", context.BindingContextId); + } + + [Fact] + public void CanProvide_BindingContextId_ForDefaultName() + { + var context = new ModelBindingContext("", "binding-context"); + Assert.Equal("", context.Name); + Assert.Equal("binding-context", context.BindingContextId); + } +} diff --git a/src/Components/Components/test/RouteViewTest.cs b/src/Components/Components/test/RouteViewTest.cs index 65b507c3f951..58a288e8459f 100644 --- a/src/Components/Components/test/RouteViewTest.cs +++ b/src/Components/Components/test/RouteViewTest.cs @@ -118,7 +118,7 @@ public void RendersPageInsideLayoutView() [InlineData("https://www.example.com/subdir/path/with/multiple/segments?and=query", "/path/with/multiple/segments")] [InlineData("https://www.example.com/subdir/path/with/multiple/segments?and=query#hashtoo", "/path/with/multiple/segments")] [InlineData("https://www.example.com/subdir/path/with/#multiple/segments?and=query#hashtoo", "/path/with/")] - [InlineData("https://www.example.com/subdir/path/with/#multiple/segments#hashtoo?and=query", "/path/with/multiple/segments")] + [InlineData("https://www.example.com/subdir/path/with/multiple/segments#hashtoo?and=query", "/path/with/multiple/segments")] public void ProvidesDocumentPathAsBindingContextId(string url, string expectedBindingContextId) { // Arrange From 37f1a6724fc61afba0282191786122596730d824 Mon Sep 17 00:00:00 2001 From: jacalvar Date: Tue, 18 Apr 2023 01:37:10 +0200 Subject: [PATCH 17/28] CascadingModelBinder updates * Add an IsFixed property to track the underlying BindingContext. * Retain BindingContext in CascadingModelBinder * Re-render when Name or BindingContextId change. * Throw when Name or BindingId change on fixed contexts. * Throw if IsFixed changes --- .../src/Binding/CascadingModelBinder.cs | 55 ++++--- .../Components/src/PublicAPI.Unshipped.txt | 2 + .../test/CascadingModelBinderTest.cs | 138 +++++++++++++++++- 3 files changed, 174 insertions(+), 21 deletions(-) diff --git a/src/Components/Components/src/Binding/CascadingModelBinder.cs b/src/Components/Components/src/Binding/CascadingModelBinder.cs index 0c382abfc4aa..d2da28c55b97 100644 --- a/src/Components/Components/src/Binding/CascadingModelBinder.cs +++ b/src/Components/Components/src/Binding/CascadingModelBinder.cs @@ -12,7 +12,6 @@ namespace Microsoft.AspNetCore.Components; public class CascadingModelBinder : IComponent { private RenderHandle _handle; - private bool _hasRendered; private ModelBindingContext? _bindingContext; /// @@ -20,6 +19,11 @@ public class CascadingModelBinder : IComponent /// [Parameter] public string Name { get; set; } = default!; + /// + /// The binding context name. + /// + [Parameter] public bool IsFixed { get; set; } + /// /// The binding context name. /// @@ -39,29 +43,40 @@ void IComponent.Attach(RenderHandle renderHandle) Task IComponent.SetParametersAsync(ParameterView parameters) { - if (!_hasRendered) + parameters.SetParameterProperties(this); + if (ParentContext != null && string.IsNullOrEmpty(Name)) { - _hasRendered = true; - parameters.SetParameterProperties(this); - if (ParentContext != null && string.IsNullOrEmpty(Name)) - { - throw new InvalidOperationException($"Nested binding contexts must define a Name. (Parent context) = '{ParentContext.BindingContextId}'."); - } - - var name = string.IsNullOrEmpty(ParentContext?.Name) ? Name : $"{ParentContext.Name}.{Name}"; - var bindingId = !string.IsNullOrEmpty(name) ? null : BindingContextId; - _bindingContext = new ModelBindingContext(name, bindingId); + throw new InvalidOperationException($"Nested binding contexts must define a Name. (Parent context) = '{ParentContext.BindingContextId}'."); + } - _handle.Render(builder => - { - builder.OpenComponent>(0); - builder.AddComponentParameter(1, nameof(CascadingValue.IsFixed), true); - builder.AddComponentParameter(2, nameof(CascadingValue.Value), _bindingContext); - builder.AddComponentParameter(3, nameof(CascadingValue.ChildContent), ChildContent?.Invoke(_bindingContext)); - builder.CloseComponent(); - }); + var name = string.IsNullOrEmpty(ParentContext?.Name) ? Name : $"{ParentContext.Name}.{Name}"; + var bindingId = !string.IsNullOrEmpty(name) ? null : BindingContextId; + var bindingContext = _bindingContext != null && + string.Equals(_bindingContext.Name, Name, StringComparison.Ordinal) && + string.Equals(_bindingContext.BindingContextId, BindingContextId, StringComparison.Ordinal) ? + _bindingContext : new ModelBindingContext(name, bindingId); + if (IsFixed && _bindingContext != null && _bindingContext != bindingContext) + { + // Throw an exception if either the Name or the BindingContextId changed. Once a CascadingModelBinder has been initialized + // as fixed, it can't change it's name nor its BindingContextId. This can happen in several situations: + // * Component ParentContext hierarchy changes. + // * Technically, the component won't be retained in this case and will be destroyed instead. + // * A parent changes Name. + // * A parent changes BindingContextId. + throw new InvalidOperationException($"'{nameof(CascadingModelBinder)}' 'Name' and 'BindingContextId' can't change after initialized."); } + // It doesn't matter that we don't check IsFixed, since the CascadingValue we are setting up will throw if the app changes. + _bindingContext = bindingContext; + _handle.Render(builder => + { + builder.OpenComponent>(0); + builder.AddComponentParameter(1, nameof(CascadingValue.IsFixed), IsFixed); + builder.AddComponentParameter(2, nameof(CascadingValue.Value), _bindingContext); + builder.AddComponentParameter(3, nameof(CascadingValue.ChildContent), ChildContent?.Invoke(_bindingContext)); + builder.CloseComponent(); + }); + return Task.CompletedTask; } } diff --git a/src/Components/Components/src/PublicAPI.Unshipped.txt b/src/Components/Components/src/PublicAPI.Unshipped.txt index 3211e60d1491..c6ce7a5ac7a5 100644 --- a/src/Components/Components/src/PublicAPI.Unshipped.txt +++ b/src/Components/Components/src/PublicAPI.Unshipped.txt @@ -8,6 +8,8 @@ Microsoft.AspNetCore.Components.CascadingModelBinder.BindingContextId.set -> voi Microsoft.AspNetCore.Components.CascadingModelBinder.CascadingModelBinder() -> void Microsoft.AspNetCore.Components.CascadingModelBinder.ChildContent.get -> Microsoft.AspNetCore.Components.RenderFragment! Microsoft.AspNetCore.Components.CascadingModelBinder.ChildContent.set -> void +Microsoft.AspNetCore.Components.CascadingModelBinder.IsFixed.get -> bool +Microsoft.AspNetCore.Components.CascadingModelBinder.IsFixed.set -> void Microsoft.AspNetCore.Components.CascadingModelBinder.Name.get -> string! Microsoft.AspNetCore.Components.CascadingModelBinder.Name.set -> void Microsoft.AspNetCore.Components.ComponentBase.DispatchExceptionAsync(System.Exception! exception) -> System.Threading.Tasks.Task! diff --git a/src/Components/Components/test/CascadingModelBinderTest.cs b/src/Components/Components/test/CascadingModelBinderTest.cs index 9d72f8914e07..8e8b23ac53fb 100644 --- a/src/Components/Components/test/CascadingModelBinderTest.cs +++ b/src/Components/Components/test/CascadingModelBinderTest.cs @@ -148,7 +148,143 @@ public void Throws_IfDefaultContextIsNotTheRoot() // Act var exception = Assert.Throws(() => renderer.RenderRootComponent(id)); - Assert.Equal($"Nested binding contexts must define a Name. (Parent context) = 'parent-context'.", exception.Message); + Assert.Equal("Nested binding contexts must define a Name. (Parent context) = 'parent-context'.", exception.Message); + } + + [Fact] + public void Throws_WhenIsFixedAndNameChanges() + { + ModelBindingContext capturedContext = null; + RenderFragment contents = (ctx) => b => { capturedContext = ctx; }; + var contextName = "parent-context"; + + var renderer = new TestRenderer(); + var testComponent = new TestComponent(builder => + { + builder.OpenComponent(0); + builder.AddAttribute(1, nameof(CascadingModelBinder.Name), contextName); + builder.AddAttribute(2, nameof(CascadingModelBinder.IsFixed), true); + builder.AddAttribute(3, nameof(CascadingModelBinder.ChildContent), contents); + builder.CloseComponent(); + }); + var id = renderer.AssignRootComponentId(testComponent); + renderer.RenderRootComponent(id); + + // Act + contextName = "changed"; + var exception = Assert.Throws(testComponent.TriggerRender); + + Assert.Equal("'CascadingModelBinder' 'Name' and 'BindingContextId' can't change after initialized.", exception.Message); + } + + [Fact] + public void Throws_WhenIsFixedAndBindingIdChanges() + { + ModelBindingContext capturedContext = null; + RenderFragment contents = (ctx) => b => { capturedContext = ctx; }; + var bindingId = "parent-context"; + + var renderer = new TestRenderer(); + var testComponent = new TestComponent(builder => + { + builder.OpenComponent(0); + builder.AddAttribute(1, nameof(CascadingModelBinder.BindingContextId), bindingId); + builder.AddAttribute(2, nameof(CascadingModelBinder.IsFixed), true); + builder.AddAttribute(3, nameof(CascadingModelBinder.ChildContent), contents); + builder.CloseComponent(); + }); + var id = renderer.AssignRootComponentId(testComponent); + renderer.RenderRootComponent(id); + + // Act + bindingId = "changed"; + var exception = Assert.Throws(testComponent.TriggerRender); + + Assert.Equal("'CascadingModelBinder' 'Name' and 'BindingContextId' can't change after initialized.", exception.Message); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void Throws_WhenIsFixed_Changes(bool isFixed) + { + ModelBindingContext capturedContext = null; + RenderFragment contents = (ctx) => b => { capturedContext = ctx; }; + var renderer = new TestRenderer(); + var testComponent = new TestComponent(builder => + { + builder.OpenComponent(0); + builder.AddAttribute(1, nameof(CascadingModelBinder.BindingContextId), "parent-context"); + builder.AddAttribute(2, nameof(CascadingModelBinder.IsFixed), isFixed); + builder.AddAttribute(3, nameof(CascadingModelBinder.ChildContent), contents); + builder.CloseComponent(); + }); + var id = renderer.AssignRootComponentId(testComponent); + renderer.RenderRootComponent(id); + + // Act + isFixed = !isFixed; + var exception = Assert.Throws(testComponent.TriggerRender); + + Assert.Equal("The value of IsFixed cannot be changed dynamically.", exception.Message); + } + + [Fact] + public void CanChange_Name_WhenNotFixed() + { + ModelBindingContext capturedContext = null; + ModelBindingContext originalContext = null; + RenderFragment contents = (ctx) => b => { capturedContext = ctx; }; + var contextName = "parent-context"; + + var renderer = new TestRenderer(); + var testComponent = new TestComponent(builder => + { + builder.OpenComponent(0); + builder.AddAttribute(1, nameof(CascadingModelBinder.Name), contextName); + builder.AddAttribute(3, nameof(CascadingModelBinder.ChildContent), contents); + builder.CloseComponent(); + }); + var id = renderer.AssignRootComponentId(testComponent); + renderer.RenderRootComponent(id); + + originalContext = capturedContext; + contextName = "changed"; + + // Act + testComponent.TriggerRender(); + + Assert.NotSame(capturedContext, originalContext); + Assert.Equal("changed", capturedContext.Name); + } + + [Fact] + public void CanChange_BindingContextId_WhenNotFixed() + { + ModelBindingContext capturedContext = null; + ModelBindingContext originalContext = null; + RenderFragment contents = (ctx) => b => { capturedContext = ctx; }; + var contextBindingId = "/fetch-data/5"; + + var renderer = new TestRenderer(); + var testComponent = new TestComponent(builder => + { + builder.OpenComponent(0); + builder.AddAttribute(1, nameof(CascadingModelBinder.BindingContextId), contextBindingId); + builder.AddAttribute(3, nameof(CascadingModelBinder.ChildContent), contents); + builder.CloseComponent(); + }); + var id = renderer.AssignRootComponentId(testComponent); + renderer.RenderRootComponent(id); + + originalContext = capturedContext; + contextBindingId = "/fetch-data/6"; + + // Act + testComponent.TriggerRender(); + + Assert.NotSame(capturedContext, originalContext); + Assert.Equal("/fetch-data/6", capturedContext.BindingContextId); } class TestComponent : AutoRenderComponent From 9b6dc0807e835f7b389fcf5c3bfc503a256379d8 Mon Sep 17 00:00:00 2001 From: jacalvar Date: Tue, 18 Apr 2023 01:43:58 +0200 Subject: [PATCH 18/28] Fix tests --- src/Components/Components/test/RouteViewTest.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Components/Components/test/RouteViewTest.cs b/src/Components/Components/test/RouteViewTest.cs index 58a288e8459f..d6729bc5d7ba 100644 --- a/src/Components/Components/test/RouteViewTest.cs +++ b/src/Components/Components/test/RouteViewTest.cs @@ -80,7 +80,7 @@ public void RendersPageInsideLayoutView() frame => AssertFrame.Region(frame, subtreeLength: 5), frame => AssertFrame.Component(frame, sequence: 0, subtreeLength: 4), frame => AssertFrame.Attribute(frame, nameof(CascadingModelBinder.Name), "", sequence: 1), - frame => AssertFrame.Attribute(frame, nameof(CascadingModelBinder.BindingContextId), "jan", sequence: 2), + frame => AssertFrame.Attribute(frame, nameof(CascadingModelBinder.BindingContextId), "/", sequence: 2), frame => AssertFrame.Attribute(frame, nameof(CascadingModelBinder.ChildContent), typeof(RenderFragment), sequence: 3), frame => AssertFrame.Text(frame, "Layout ends here", sequence: 2)); @@ -89,7 +89,7 @@ public void RendersPageInsideLayoutView() var cascadingModelBinderFrames = _renderer.GetCurrentRenderTreeFrames(cascadingModelBinderComponentId).AsEnumerable(); Assert.Collection(cascadingModelBinderFrames, frame => AssertFrame.Component>(frame, sequence: 0, subtreeLength: 4), - frame => AssertFrame.Attribute(frame, nameof(CascadingValue.IsFixed), true, sequence: 1), + frame => AssertFrame.Attribute(frame, nameof(CascadingValue.IsFixed), false, sequence: 1), frame => AssertFrame.Attribute(frame, nameof(CascadingValue.Value), typeof(ModelBindingContext), sequence: 2), frame => AssertFrame.Attribute(frame, nameof(CascadingValue.ChildContent), typeof(RenderFragment), sequence: 3)); @@ -167,7 +167,7 @@ public void ProvidesDocumentPathAsBindingContextId(string url, string expectedBi var cascadingModelBinderFrames = _renderer.GetCurrentRenderTreeFrames(cascadingModelBinderComponentId).AsEnumerable(); Assert.Collection(cascadingModelBinderFrames, frame => AssertFrame.Component>(frame, sequence: 0, subtreeLength: 4), - frame => AssertFrame.Attribute(frame, nameof(CascadingValue.IsFixed), true, sequence: 1), + frame => AssertFrame.Attribute(frame, nameof(CascadingValue.IsFixed), false, sequence: 1), frame => AssertFrame.Attribute(frame, nameof(CascadingValue.Value), typeof(ModelBindingContext), sequence: 2), frame => AssertFrame.Attribute(frame, nameof(CascadingValue.ChildContent), typeof(RenderFragment), sequence: 3)); From 6d3da0e3f0b93c0cb854a6524c1404f2facbcc95 Mon Sep 17 00:00:00 2001 From: jacalvar Date: Tue, 18 Apr 2023 18:30:49 +0200 Subject: [PATCH 19/28] Rework cascadingmodelbinder to get rid of BindingContextId --- .../src/Binding/CascadingModelBinder.cs | 85 +++++++++++++++---- src/Components/Components/src/RouteView.cs | 2 +- .../test/CascadingModelBinderTest.cs | 6 -- .../Components/test/RouteViewTest.cs | 2 - src/Components/Web/src/Forms/EditForm.cs | 9 +- src/Components/Web/test/Forms/EditFormTest.cs | 1 - 6 files changed, 75 insertions(+), 30 deletions(-) diff --git a/src/Components/Components/src/Binding/CascadingModelBinder.cs b/src/Components/Components/src/Binding/CascadingModelBinder.cs index d2da28c55b97..a87842d5c0ab 100644 --- a/src/Components/Components/src/Binding/CascadingModelBinder.cs +++ b/src/Components/Components/src/Binding/CascadingModelBinder.cs @@ -3,16 +3,18 @@ using System.Reflection.Metadata; using Microsoft.AspNetCore.Components.Binding; +using Microsoft.AspNetCore.Components.Routing; namespace Microsoft.AspNetCore.Components; /// /// Defines the binding context for data bound from external sources. /// -public class CascadingModelBinder : IComponent +public class CascadingModelBinder : IComponent, IDisposable { private RenderHandle _handle; private ModelBindingContext? _bindingContext; + private bool _hasPendingQueuedRender; /// /// The binding context name. @@ -24,11 +26,6 @@ public class CascadingModelBinder : IComponent /// [Parameter] public bool IsFixed { get; set; } - /// - /// The binding context name. - /// - [Parameter] public string BindingContextId { get; set; } = default!; - /// /// Specifies the content to be rendered inside this . /// @@ -36,6 +33,8 @@ public class CascadingModelBinder : IComponent [CascadingParameter] ModelBindingContext? ParentContext { get; set; } + [Inject] private NavigationManager Navigation { get; set; } = null!; + void IComponent.Attach(RenderHandle renderHandle) { _handle = renderHandle; @@ -43,18 +42,74 @@ void IComponent.Attach(RenderHandle renderHandle) Task IComponent.SetParametersAsync(ParameterView parameters) { + if (_bindingContext == null) + { + // First render + Navigation.LocationChanged += HandleLocationChanged; + } + parameters.SetParameterProperties(this); if (ParentContext != null && string.IsNullOrEmpty(Name)) { throw new InvalidOperationException($"Nested binding contexts must define a Name. (Parent context) = '{ParentContext.BindingContextId}'."); } + UpdateBindingInformation(Navigation.Uri); + Render(); + + return Task.CompletedTask; + } + + private void Render() + { + if (_hasPendingQueuedRender) + { + return; + } + _hasPendingQueuedRender = true; + _handle.Render(builder => + { + _hasPendingQueuedRender = false; + builder.OpenComponent>(0); + builder.AddComponentParameter(1, nameof(CascadingValue.IsFixed), IsFixed); + builder.AddComponentParameter(2, nameof(CascadingValue.Value), _bindingContext); + builder.AddComponentParameter(3, nameof(CascadingValue.ChildContent), ChildContent?.Invoke(_bindingContext!)); + builder.CloseComponent(); + }); + } + + private void HandleLocationChanged(object? sender, LocationChangedEventArgs e) + { + var url = e.Location; + UpdateBindingInformation(url); + Render(); + } + + private void UpdateBindingInformation(string url) + { + // BindingContextId: action parameter used to define the handler + // Name: form name and context used to bind + // Cases: + // 1) No name ("") + // Name = ""; + // BindingContextId = ""; + //
+ // 2) Name provided + // Name = "my-handler"; + // BindingContextId = <>((<>&)|?)handler=my-handler + // >((<>&)|?)handler=my-handler var name = string.IsNullOrEmpty(ParentContext?.Name) ? Name : $"{ParentContext.Name}.{Name}"; - var bindingId = !string.IsNullOrEmpty(name) ? null : BindingContextId; + var bindingId = !string.IsNullOrEmpty(name) ? null : Navigation.ToBaseRelativePath(Navigation.GetUriWithQueryParameter("handler", name)); + var bindingContext = _bindingContext != null && string.Equals(_bindingContext.Name, Name, StringComparison.Ordinal) && - string.Equals(_bindingContext.BindingContextId, BindingContextId, StringComparison.Ordinal) ? + string.Equals(_bindingContext.BindingContextId, bindingId, StringComparison.Ordinal) ? _bindingContext : new ModelBindingContext(name, bindingId); + + // It doesn't matter that we don't check IsFixed, since the CascadingValue we are setting up will throw if the app changes. if (IsFixed && _bindingContext != null && _bindingContext != bindingContext) { // Throw an exception if either the Name or the BindingContextId changed. Once a CascadingModelBinder has been initialized @@ -66,17 +121,11 @@ Task IComponent.SetParametersAsync(ParameterView parameters) throw new InvalidOperationException($"'{nameof(CascadingModelBinder)}' 'Name' and 'BindingContextId' can't change after initialized."); } - // It doesn't matter that we don't check IsFixed, since the CascadingValue we are setting up will throw if the app changes. _bindingContext = bindingContext; - _handle.Render(builder => - { - builder.OpenComponent>(0); - builder.AddComponentParameter(1, nameof(CascadingValue.IsFixed), IsFixed); - builder.AddComponentParameter(2, nameof(CascadingValue.Value), _bindingContext); - builder.AddComponentParameter(3, nameof(CascadingValue.ChildContent), ChildContent?.Invoke(_bindingContext)); - builder.CloseComponent(); - }); + } - return Task.CompletedTask; + void IDisposable.Dispose() + { + Navigation.LocationChanged -= HandleLocationChanged; } } diff --git a/src/Components/Components/src/RouteView.cs b/src/Components/Components/src/RouteView.cs index 8fdb1e4ec255..88bee0253270 100644 --- a/src/Components/Components/src/RouteView.cs +++ b/src/Components/Components/src/RouteView.cs @@ -88,7 +88,6 @@ private void RenderPageWithParameters(RenderTreeBuilder builder) builder.OpenComponent(0); builder.AddComponentParameter(1, nameof(CascadingModelBinder.Name), ""); - builder.AddComponentParameter(2, nameof(CascadingModelBinder.BindingContextId), bindingId); builder.AddComponentParameter(3, nameof(CascadingModelBinder.ChildContent), (RenderFragment)RenderPageWithContext); builder.CloseComponent(); @@ -108,6 +107,7 @@ void RenderPageCore(RenderTreeBuilder builder) { // Since this component does accept some parameters from query, we must supply values for all of them, // even if the querystring in the URI is empty. So don't skip the following logic. + var relativeUrl = NavigationManager.ToBaseRelativePath(NavigationManager.Uri); var url = NavigationManager.Uri; ReadOnlyMemory query = default; var queryStartPos = url.IndexOf('?'); diff --git a/src/Components/Components/test/CascadingModelBinderTest.cs b/src/Components/Components/test/CascadingModelBinderTest.cs index 8e8b23ac53fb..6f8666c6fba9 100644 --- a/src/Components/Components/test/CascadingModelBinderTest.cs +++ b/src/Components/Components/test/CascadingModelBinderTest.cs @@ -19,7 +19,6 @@ public void CascadingModelBinder_UsesBindingIdWhenNoDefaultName() var testComponent = new TestComponent(builder => { builder.OpenComponent(0); - builder.AddAttribute(1, nameof(CascadingModelBinder.BindingContextId), "/path"); builder.AddAttribute(2, nameof(CascadingModelBinder.ChildContent), contents); builder.CloseComponent(); }); @@ -108,7 +107,6 @@ public void CascadingModelBinder_CanNestWithDefaultContext() var testComponent = new TestComponent(builder => { builder.OpenComponent(0); - builder.AddAttribute(1, nameof(CascadingModelBinder.BindingContextId), "/path"); builder.AddAttribute(2, nameof(CascadingModelBinder.ChildContent), nested); builder.CloseComponent(); }); @@ -131,7 +129,6 @@ public void Throws_IfDefaultContextIsNotTheRoot() RenderFragment nested = (ctx) => b => { b.OpenComponent(0); - b.AddAttribute(1, nameof(CascadingModelBinder.BindingContextId), "/path"); b.AddAttribute(2, nameof(CascadingModelBinder.ChildContent), contents); b.CloseComponent(); }; @@ -188,7 +185,6 @@ public void Throws_WhenIsFixedAndBindingIdChanges() var testComponent = new TestComponent(builder => { builder.OpenComponent(0); - builder.AddAttribute(1, nameof(CascadingModelBinder.BindingContextId), bindingId); builder.AddAttribute(2, nameof(CascadingModelBinder.IsFixed), true); builder.AddAttribute(3, nameof(CascadingModelBinder.ChildContent), contents); builder.CloseComponent(); @@ -214,7 +210,6 @@ public void Throws_WhenIsFixed_Changes(bool isFixed) var testComponent = new TestComponent(builder => { builder.OpenComponent(0); - builder.AddAttribute(1, nameof(CascadingModelBinder.BindingContextId), "parent-context"); builder.AddAttribute(2, nameof(CascadingModelBinder.IsFixed), isFixed); builder.AddAttribute(3, nameof(CascadingModelBinder.ChildContent), contents); builder.CloseComponent(); @@ -270,7 +265,6 @@ public void CanChange_BindingContextId_WhenNotFixed() var testComponent = new TestComponent(builder => { builder.OpenComponent(0); - builder.AddAttribute(1, nameof(CascadingModelBinder.BindingContextId), contextBindingId); builder.AddAttribute(3, nameof(CascadingModelBinder.ChildContent), contents); builder.CloseComponent(); }); diff --git a/src/Components/Components/test/RouteViewTest.cs b/src/Components/Components/test/RouteViewTest.cs index d6729bc5d7ba..6fcacea1f783 100644 --- a/src/Components/Components/test/RouteViewTest.cs +++ b/src/Components/Components/test/RouteViewTest.cs @@ -80,7 +80,6 @@ public void RendersPageInsideLayoutView() frame => AssertFrame.Region(frame, subtreeLength: 5), frame => AssertFrame.Component(frame, sequence: 0, subtreeLength: 4), frame => AssertFrame.Attribute(frame, nameof(CascadingModelBinder.Name), "", sequence: 1), - frame => AssertFrame.Attribute(frame, nameof(CascadingModelBinder.BindingContextId), "/", sequence: 2), frame => AssertFrame.Attribute(frame, nameof(CascadingModelBinder.ChildContent), typeof(RenderFragment), sequence: 3), frame => AssertFrame.Text(frame, "Layout ends here", sequence: 2)); @@ -158,7 +157,6 @@ public void ProvidesDocumentPathAsBindingContextId(string url, string expectedBi frame => AssertFrame.Region(frame, subtreeLength: 5), frame => AssertFrame.Component(frame, sequence: 0, subtreeLength: 4), frame => AssertFrame.Attribute(frame, nameof(CascadingModelBinder.Name), "", sequence: 1), - frame => AssertFrame.Attribute(frame, nameof(CascadingModelBinder.BindingContextId), expectedBindingContextId, sequence: 2), frame => AssertFrame.Attribute(frame, nameof(CascadingModelBinder.ChildContent), typeof(RenderFragment), sequence: 3), frame => AssertFrame.Text(frame, "Layout ends here", sequence: 2)); diff --git a/src/Components/Web/src/Forms/EditForm.cs b/src/Components/Web/src/Forms/EditForm.cs index f35109188313..a56169c1f279 100644 --- a/src/Components/Web/src/Forms/EditForm.cs +++ b/src/Components/Web/src/Forms/EditForm.cs @@ -159,13 +159,18 @@ void RenderFormContents(RenderTreeBuilder builder, ModelBindingContext? bindingC if (!string.IsNullOrEmpty(bindingContext?.Name)) { builder.AddAttribute(1, "name", bindingContext.Name); - builder.AddAttribute(2, "action", $"?handler={bindingContext.Name}"); } + + if (!string.IsNullOrEmpty(bindingContext?.BindingContextId)) + { + builder.AddAttribute(2, "action", bindingContext.BindingContextId); + } + builder.AddMultipleAttributes(3, AdditionalAttributes); builder.AddAttribute(4, "onsubmit", _handleSubmitDelegate); if (bindingContext != null) { - builder.SetEventHandlerName(bindingContext.BindingContextId); + builder.SetEventHandlerName(bindingContext.Name); } builder.OpenComponent>(5); builder.AddComponentParameter(6, "IsFixed", true); diff --git a/src/Components/Web/test/Forms/EditFormTest.cs b/src/Components/Web/test/Forms/EditFormTest.cs index 0aa095665e7b..361f5cd64fce 100644 --- a/src/Components/Web/test/Forms/EditFormTest.cs +++ b/src/Components/Web/test/Forms/EditFormTest.cs @@ -398,7 +398,6 @@ protected override void BuildRenderTree(RenderTreeBuilder builder) { builder.OpenComponent(0); builder.AddComponentParameter(1, nameof(CascadingModelBinder.Name), BindingContext.Name); - builder.AddComponentParameter(2, nameof(CascadingModelBinder.BindingContextId), BindingContext.BindingContextId); builder.AddComponentParameter(3, nameof(CascadingModelBinder.ChildContent), (RenderFragment)((_) => RenderForm)); builder.CloseComponent(); } From 9921089a94904c9875b754b95bcaf6bcf980ba0f Mon Sep 17 00:00:00 2001 From: jacalvar Date: Tue, 18 Apr 2023 20:33:29 +0200 Subject: [PATCH 20/28] Cascading model binder updates --- .../src/Binding/CascadingModelBinder.cs | 4 ++-- .../src/Binding/ModelBindingContext.cs | 19 ++----------------- .../Components/src/PublicAPI.Unshipped.txt | 2 -- .../test/CascadingModelBinderTest.cs | 4 ---- .../src/RazorComponentEndpointInvoker.cs | 2 +- 5 files changed, 5 insertions(+), 26 deletions(-) diff --git a/src/Components/Components/src/Binding/CascadingModelBinder.cs b/src/Components/Components/src/Binding/CascadingModelBinder.cs index a87842d5c0ab..55bf8f74362c 100644 --- a/src/Components/Components/src/Binding/CascadingModelBinder.cs +++ b/src/Components/Components/src/Binding/CascadingModelBinder.cs @@ -19,7 +19,7 @@ public class CascadingModelBinder : IComponent, IDisposable /// /// The binding context name. /// - [Parameter] public string Name { get; set; } = default!; + [Parameter] public string Name { get; set; } = ""; /// /// The binding context name. @@ -102,7 +102,7 @@ private void UpdateBindingInformation(string url) // Name = "parent-name.my-handler"; // BindingContextId = <>((<>&)|?)handler=my-handler var name = string.IsNullOrEmpty(ParentContext?.Name) ? Name : $"{ParentContext.Name}.{Name}"; - var bindingId = !string.IsNullOrEmpty(name) ? null : Navigation.ToBaseRelativePath(Navigation.GetUriWithQueryParameter("handler", name)); + var bindingId = string.IsNullOrEmpty(name) ? null : Navigation.ToBaseRelativePath(Navigation.GetUriWithQueryParameter("handler", name)); var bindingContext = _bindingContext != null && string.Equals(_bindingContext.Name, Name, StringComparison.Ordinal) && diff --git a/src/Components/Components/src/Binding/ModelBindingContext.cs b/src/Components/Components/src/Binding/ModelBindingContext.cs index da31f0a3b95c..b0cf47e88552 100644 --- a/src/Components/Components/src/Binding/ModelBindingContext.cs +++ b/src/Components/Components/src/Binding/ModelBindingContext.cs @@ -8,30 +8,15 @@ namespace Microsoft.AspNetCore.Components.Binding; /// public class ModelBindingContext { - // Default binder - // Name = URL.Path - // FormAction = "" - - // Named from default binder - // Name = <> (<> -> <>) - // FormAction = ?handler=<> ("" -> ?handler=<>) - - // Named binder - // Name = <> - // FormAction = ?handler=<> - - // Nested named binder - // Name = <>.<> - // FormAction = ?handler=<>.<> internal ModelBindingContext(string name, string? bindingContextId = null) { // We are initializing the root context, that can be a "named" root context, or the default context. // A named root context only provides a name, and that acts as the BindingId // A "default" root context does not provide a name, and instead it provides an explicit Binding ID. // The explicit binding ID matches that of the default handler, which is the URL Path. - if (!(string.IsNullOrEmpty(name) ^ string.IsNullOrEmpty(bindingContextId))) + if ((string.IsNullOrEmpty(name) ^ string.IsNullOrEmpty(bindingContextId))) { - throw new InvalidOperationException("A root binding context needs to provide either a name or explicit binding ID."); + throw new InvalidOperationException("A root binding context needs to provide a name and explicit binding ID or none."); } Name = name; diff --git a/src/Components/Components/src/PublicAPI.Unshipped.txt b/src/Components/Components/src/PublicAPI.Unshipped.txt index c6ce7a5ac7a5..c7df16784a86 100644 --- a/src/Components/Components/src/PublicAPI.Unshipped.txt +++ b/src/Components/Components/src/PublicAPI.Unshipped.txt @@ -3,8 +3,6 @@ Microsoft.AspNetCore.Components.Binding.ModelBindingContext Microsoft.AspNetCore.Components.Binding.ModelBindingContext.BindingContextId.get -> string! Microsoft.AspNetCore.Components.Binding.ModelBindingContext.Name.get -> string! Microsoft.AspNetCore.Components.CascadingModelBinder -Microsoft.AspNetCore.Components.CascadingModelBinder.BindingContextId.get -> string! -Microsoft.AspNetCore.Components.CascadingModelBinder.BindingContextId.set -> void Microsoft.AspNetCore.Components.CascadingModelBinder.CascadingModelBinder() -> void Microsoft.AspNetCore.Components.CascadingModelBinder.ChildContent.get -> Microsoft.AspNetCore.Components.RenderFragment! Microsoft.AspNetCore.Components.CascadingModelBinder.ChildContent.set -> void diff --git a/src/Components/Components/test/CascadingModelBinderTest.cs b/src/Components/Components/test/CascadingModelBinderTest.cs index 6f8666c6fba9..437f0f581f4b 100644 --- a/src/Components/Components/test/CascadingModelBinderTest.cs +++ b/src/Components/Components/test/CascadingModelBinderTest.cs @@ -179,7 +179,6 @@ public void Throws_WhenIsFixedAndBindingIdChanges() { ModelBindingContext capturedContext = null; RenderFragment contents = (ctx) => b => { capturedContext = ctx; }; - var bindingId = "parent-context"; var renderer = new TestRenderer(); var testComponent = new TestComponent(builder => @@ -193,7 +192,6 @@ public void Throws_WhenIsFixedAndBindingIdChanges() renderer.RenderRootComponent(id); // Act - bindingId = "changed"; var exception = Assert.Throws(testComponent.TriggerRender); Assert.Equal("'CascadingModelBinder' 'Name' and 'BindingContextId' can't change after initialized.", exception.Message); @@ -259,7 +257,6 @@ public void CanChange_BindingContextId_WhenNotFixed() ModelBindingContext capturedContext = null; ModelBindingContext originalContext = null; RenderFragment contents = (ctx) => b => { capturedContext = ctx; }; - var contextBindingId = "/fetch-data/5"; var renderer = new TestRenderer(); var testComponent = new TestComponent(builder => @@ -272,7 +269,6 @@ public void CanChange_BindingContextId_WhenNotFixed() renderer.RenderRootComponent(id); originalContext = capturedContext; - contextBindingId = "/fetch-data/6"; // Act testComponent.TriggerRender(); diff --git a/src/Components/Endpoints/src/RazorComponentEndpointInvoker.cs b/src/Components/Endpoints/src/RazorComponentEndpointInvoker.cs index b240175fbe96..1d8097c803c1 100644 --- a/src/Components/Endpoints/src/RazorComponentEndpointInvoker.cs +++ b/src/Components/Endpoints/src/RazorComponentEndpointInvoker.cs @@ -91,7 +91,7 @@ private Task TryValidateRequestAsync(out bool isPost) private bool TrySetFormHandler() { - var handler = _context.Request.Path.Value; + var handler = ""; if (_context.Request.Query.TryGetValue("handler", out var value)) { if (value.Count != 1) From e8ba09fb8f791f8195afd8df4645cbef6ba5c120 Mon Sep 17 00:00:00 2001 From: jacalvar Date: Tue, 18 Apr 2023 20:33:48 +0200 Subject: [PATCH 21/28] E2E tests --- ...soft.AspNetCore.Components.E2ETests.csproj | 38 ++---- .../ServerRenderingTests/FormHandlingTest.cs | 118 ++++++++++++++++++ .../StreamingRenderingTest.cs | 22 +--- .../Components/ComponentWithFormInside.razor | 14 +++ .../Pages/FormStreamingRendering.razor | 36 ------ .../Pages/Forms/DefaultForm.razor | 18 +++ .../Forms/FormDefinedInsideComponent.razor | 8 ++ .../Pages/Forms/NamedForm.razor | 18 +++ .../Pages/Forms/NestedNamedForm.razor | 21 ++++ .../Pages/Forms/_Imports.razor | 2 + .../RazorComponents/Shared/FormsLayout.razor | 18 +++ 11 files changed, 231 insertions(+), 82 deletions(-) create mode 100644 src/Components/test/E2ETest/ServerRenderingTests/FormHandlingTest.cs rename src/Components/test/E2ETest/{ServerExecutionTests => ServerRenderingTests}/StreamingRenderingTest.cs (81%) create mode 100644 src/Components/test/testassets/Components.TestServer/RazorComponents/Components/ComponentWithFormInside.razor delete mode 100644 src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/FormStreamingRendering.razor create mode 100644 src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/DefaultForm.razor create mode 100644 src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/FormDefinedInsideComponent.razor create mode 100644 src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/NamedForm.razor create mode 100644 src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/NestedNamedForm.razor create mode 100644 src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/_Imports.razor create mode 100644 src/Components/test/testassets/Components.TestServer/RazorComponents/Shared/FormsLayout.razor diff --git a/src/Components/test/E2ETest/Microsoft.AspNetCore.Components.E2ETests.csproj b/src/Components/test/E2ETest/Microsoft.AspNetCore.Components.E2ETests.csproj index 880e4375bec2..4b38dfe2f957 100644 --- a/src/Components/test/E2ETest/Microsoft.AspNetCore.Components.E2ETests.csproj +++ b/src/Components/test/E2ETest/Microsoft.AspNetCore.Components.E2ETests.csproj @@ -5,10 +5,8 @@ EXECUTE_COMPONENTS_E2E_TESTS. At least build the Components E2E tests locally unless SkipTestBuild is set. --> <_BuildAndTest>false - <_BuildAndTest - Condition=" '$(ContinuousIntegrationBuild)' == 'true' AND '$(EXECUTE_COMPONENTS_E2E_TESTS)' == 'true' ">true - <_BuildAndTest - Condition=" '$(ContinuousIntegrationBuild)' != 'true' AND '$(SkipTestBuild)' != 'true' ">true + <_BuildAndTest Condition=" '$(ContinuousIntegrationBuild)' == 'true' AND '$(EXECUTE_COMPONENTS_E2E_TESTS)' == 'true' ">true + <_BuildAndTest Condition=" '$(ContinuousIntegrationBuild)' != 'true' AND '$(SkipTestBuild)' != 'true' ">true true true @@ -63,29 +61,15 @@ - - - - - - - - - + + + + + + + + + diff --git a/src/Components/test/E2ETest/ServerRenderingTests/FormHandlingTest.cs b/src/Components/test/E2ETest/ServerRenderingTests/FormHandlingTest.cs new file mode 100644 index 000000000000..2ae35e9bb0cc --- /dev/null +++ b/src/Components/test/E2ETest/ServerRenderingTests/FormHandlingTest.cs @@ -0,0 +1,118 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures; +using Microsoft.AspNetCore.Components.E2ETest.Infrastructure; +using Microsoft.AspNetCore.E2ETesting; +using TestServer; +using Xunit.Abstractions; +using OpenQA.Selenium; + +namespace Microsoft.AspNetCore.Components.E2ETests.ServerRenderingTests; + +public class FormHandlingTest : ServerTestBase> +{ + public FormHandlingTest( + BrowserFixture browserFixture, + BasicTestAppServerSiteFixture serverFixture, + ITestOutputHelper output) + : base(browserFixture, serverFixture, output) + { + } + + public override Task InitializeAsync() + => InitializeAsync(BrowserFixture.StreamingContext); + + // Can dispatch to the default form + // Can dispatch to a named form + // Rendering ambiguous forms doesn't cause an error. + // Dispatching to ambiguous forms raises an error. + // Can dispatch to a nested form + // Can dispatch to a nested named form inside the default binding context. + + [Fact] + public void CanDispatchToTheDefaultForm() + { + var dispatchToForm = new DispatchToForm(this) + { + Url = "forms/default-form", + FormCssSelector = "form", + ExpectedActionValue = null, + }; + DispatchToFormCore(dispatchToForm); + } + + [Fact] + public void CanDispatchToNamedForm() + { + var dispatchToForm = new DispatchToForm(this) + { + Url = "forms/named-form", + FormCssSelector = "form[name=named-form-handler]", + ExpectedActionValue = "forms/named-form?handler=named-form-handler", + }; + DispatchToFormCore(dispatchToForm); + } + + [Fact] + public void CanDispatchToNamedFormInNestedContext() + { + var dispatchToForm = new DispatchToForm(this) + { + Url = "forms/nested-named-form", + FormCssSelector = "form[name=\"parent-context.named-form-handler\"]", + ExpectedActionValue = "forms/nested-named-form?handler=parent-context.named-form-handler", + }; + DispatchToFormCore(dispatchToForm); + } + + [Fact] + public void CanDispatchToFormDefinedInNonPageComponent() + { + var dispatchToForm = new DispatchToForm(this) + { + Url = "/forms/form-defined-inside-component", + FormCssSelector = "form", + ExpectedActionValue = "", + }; + DispatchToFormCore(dispatchToForm); + } + + private void DispatchToFormCore(DispatchToForm dispatch) + { + GoTo(dispatch.Url); + + Browser.Exists(By.Id(dispatch.Ready)); + var form = Browser.Exists(By.CssSelector(dispatch.FormCssSelector)); + var formTarget = form.GetAttribute("action"); + var actionValue = form.GetDomAttribute("action"); + Assert.Equal(dispatch.ExpectedTarget, formTarget); + Assert.Equal(dispatch.ExpectedActionValue, actionValue); + + Browser.Exists(By.Id("send")).Click(); + + Browser.Exists(By.Id(dispatch.SubmitPassId)); + } + + private record struct DispatchToForm() + { + public DispatchToForm(FormHandlingTest test) : this() + { + Base = new Uri(test._serverFixture.RootUri, test.ServerPathBase).ToString(); + } + + public string Base; + public string Url; + public string Ready = "ready"; + public string SubmitPassId = "pass"; + public string FormCssSelector; + public string ExpectedActionValue; + public string ExpectedTarget => $"{Base}/{ExpectedActionValue ?? Url}"; + + } + + private void GoTo(string relativePath) + { + Navigate($"{ServerPathBase}/{relativePath}"); + } +} diff --git a/src/Components/test/E2ETest/ServerExecutionTests/StreamingRenderingTest.cs b/src/Components/test/E2ETest/ServerRenderingTests/StreamingRenderingTest.cs similarity index 81% rename from src/Components/test/E2ETest/ServerExecutionTests/StreamingRenderingTest.cs rename to src/Components/test/E2ETest/ServerRenderingTests/StreamingRenderingTest.cs index 4091ae807d86..5297552fa36d 100644 --- a/src/Components/test/E2ETest/ServerExecutionTests/StreamingRenderingTest.cs +++ b/src/Components/test/E2ETest/ServerRenderingTests/StreamingRenderingTest.cs @@ -1,14 +1,14 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures; using Microsoft.AspNetCore.Components.E2ETest.Infrastructure; +using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures; using Microsoft.AspNetCore.E2ETesting; +using OpenQA.Selenium; using TestServer; using Xunit.Abstractions; -using OpenQA.Selenium; -namespace Microsoft.AspNetCore.Components.E2ETests.ServerExecutionTests; +namespace Microsoft.AspNetCore.Components.E2ETests.ServerRenderingTests; public class StreamingRenderingTest : ServerTestBase> { @@ -64,20 +64,4 @@ public void CanPerformStreamingRendering() Browser.FindElement(By.Id("end-response-link")).Click(); Browser.Equal("Finished", () => getStatusText().Text); } - - [Fact] - public void CanPerformFormPostWithStreamedResponses() - { - Navigate($"{ServerPathBase}/form-streaming"); - - // Initial "waiting" state - var submit = Browser.Exists(By.CssSelector("input[type=submit]")); - var getStatusText = () => Browser.Exists(By.Id("status")); - Assert.Equal("", getStatusText().Text); - - submit.Click(); - - Assert.Equal("Processing form...", getStatusText().Text); - Assert.Equal("Completed", getStatusText().Text); - } } diff --git a/src/Components/test/testassets/Components.TestServer/RazorComponents/Components/ComponentWithFormInside.razor b/src/Components/test/testassets/Components.TestServer/RazorComponents/Components/ComponentWithFormInside.razor new file mode 100644 index 000000000000..8fc642afd0db --- /dev/null +++ b/src/Components/test/testassets/Components.TestServer/RazorComponents/Components/ComponentWithFormInside.razor @@ -0,0 +1,14 @@ +@using Microsoft.AspNetCore.Components.Forms + + + + +@if (_submitted) +{ +

Form submitted!

+} + +@code{ + bool _submitted = false; + EditContext _editContext = new EditContext(new object()); +} diff --git a/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/FormStreamingRendering.razor b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/FormStreamingRendering.razor deleted file mode 100644 index 8e3e75f93252..000000000000 --- a/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/FormStreamingRendering.razor +++ /dev/null @@ -1,36 +0,0 @@ -@page "/form-streaming" -@using Microsoft.AspNetCore.Components.Forms -@attribute [StreamRendering(true)] - -

Streaming Rendering

- -@* This just sets the context for the binding, this happens normally inside RouteView *@ -@* - - - - *@ - -

- @if (_processing) - { - Processing form... - } - else if (_done) - { - Completed - } -

- -@code { - bool _processing = false; - bool _done = false; - - private async Task ProcessForm() - { - _processing = true; - await Task.Yield(); - _processing = false; - _done = true; - } -} diff --git a/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/DefaultForm.razor b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/DefaultForm.razor new file mode 100644 index 000000000000..33139b56c129 --- /dev/null +++ b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/DefaultForm.razor @@ -0,0 +1,18 @@ +@page "/forms/default-form" +@using Microsoft.AspNetCore.Components.Forms + +

Default form

+ + + + + +@if (_submitted) +{ +

Form submitted!

+} + +@code{ + bool _submitted = false; + EditContext _editContext = new EditContext(new object()); +} diff --git a/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/FormDefinedInsideComponent.razor b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/FormDefinedInsideComponent.razor new file mode 100644 index 000000000000..0dff3db56b3d --- /dev/null +++ b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/FormDefinedInsideComponent.razor @@ -0,0 +1,8 @@ +@page "/forms/form-defined-inside-component" +@using Components.TestServer.RazorComponents.Components +@using Microsoft.AspNetCore.Components.Forms + +

Form defined in a sub-component

+ + + diff --git a/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/NamedForm.razor b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/NamedForm.razor new file mode 100644 index 000000000000..57fca3bc71b5 --- /dev/null +++ b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/NamedForm.razor @@ -0,0 +1,18 @@ +@page "/forms/named-form" +@using Microsoft.AspNetCore.Components.Forms + +

Named form

+ + + + + +@if (_submitted) +{ +

Form submitted!

+} + +@code{ + bool _submitted = false; + EditContext _editContext = new EditContext(new object()); +} diff --git a/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/NestedNamedForm.razor b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/NestedNamedForm.razor new file mode 100644 index 000000000000..bda765a7ba46 --- /dev/null +++ b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/NestedNamedForm.razor @@ -0,0 +1,21 @@ +@page "/forms/nested-named-form" +@using Microsoft.AspNetCore.Components.Forms + +

Nested named form

+ + + + + + + + +@if (_submitted) +{ +

Form submitted!

+} + +@code{ + bool _submitted = false; + EditContext _editContext = new EditContext(new object()); +} diff --git a/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/_Imports.razor b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/_Imports.razor new file mode 100644 index 000000000000..41f82f81f665 --- /dev/null +++ b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/_Imports.razor @@ -0,0 +1,2 @@ +@using Components.TestServer.RazorComponents.Shared; +@layout FormsLayout diff --git a/src/Components/test/testassets/Components.TestServer/RazorComponents/Shared/FormsLayout.razor b/src/Components/test/testassets/Components.TestServer/RazorComponents/Shared/FormsLayout.razor new file mode 100644 index 000000000000..d920bff06b8f --- /dev/null +++ b/src/Components/test/testassets/Components.TestServer/RazorComponents/Shared/FormsLayout.razor @@ -0,0 +1,18 @@ +@inherits LayoutComponentBase +@inject NavigationManager Navigation + + + + + + + + + Razor Components Forms +

Form test cases

+ + @Body + + + + From d67a5c446f068bddb2fbddd3096bd1f5569e4fd9 Mon Sep 17 00:00:00 2001 From: jacalvar Date: Wed, 19 Apr 2023 17:13:28 +0200 Subject: [PATCH 22/28] Update cascading model binder and tests --- .../src/Binding/CascadingModelBinder.cs | 18 +- .../src/Binding/ModelBindingContext.cs | 8 +- .../src/NavigationManagerExtensions.cs | 2 +- src/Components/Components/src/RouteView.cs | 3 +- .../test/CascadingModelBinderTest.cs | 164 +++++++++++------- .../test/ModelBindingContextTest.cs | 26 +-- .../Components/test/RouteViewTest.cs | 84 +-------- src/Components/Web/test/Forms/EditFormTest.cs | 38 ++-- 8 files changed, 166 insertions(+), 177 deletions(-) diff --git a/src/Components/Components/src/Binding/CascadingModelBinder.cs b/src/Components/Components/src/Binding/CascadingModelBinder.cs index 55bf8f74362c..b0f82288b9b7 100644 --- a/src/Components/Components/src/Binding/CascadingModelBinder.cs +++ b/src/Components/Components/src/Binding/CascadingModelBinder.cs @@ -10,7 +10,7 @@ namespace Microsoft.AspNetCore.Components; /// /// Defines the binding context for data bound from external sources. /// -public class CascadingModelBinder : IComponent, IDisposable +public sealed class CascadingModelBinder : IComponent, IDisposable { private RenderHandle _handle; private ModelBindingContext? _bindingContext; @@ -51,7 +51,7 @@ Task IComponent.SetParametersAsync(ParameterView parameters) parameters.SetParameterProperties(this); if (ParentContext != null && string.IsNullOrEmpty(Name)) { - throw new InvalidOperationException($"Nested binding contexts must define a Name. (Parent context) = '{ParentContext.BindingContextId}'."); + throw new InvalidOperationException($"Nested binding contexts must define a Name. (Parent context) = '{ParentContext.Name}'."); } UpdateBindingInformation(Navigation.Uri); @@ -101,8 +101,10 @@ private void UpdateBindingInformation(string url) // 3) Parent has a name "parent-name" // Name = "parent-name.my-handler"; // BindingContextId = <>((<>&)|?)handler=my-handler + + var name = string.IsNullOrEmpty(ParentContext?.Name) ? Name : $"{ParentContext.Name}.{Name}"; - var bindingId = string.IsNullOrEmpty(name) ? null : Navigation.ToBaseRelativePath(Navigation.GetUriWithQueryParameter("handler", name)); + var bindingId = string.IsNullOrEmpty(name) ? "" : GenerateBindingContextId(name); var bindingContext = _bindingContext != null && string.Equals(_bindingContext.Name, Name, StringComparison.Ordinal) && @@ -117,11 +119,17 @@ private void UpdateBindingInformation(string url) // * Component ParentContext hierarchy changes. // * Technically, the component won't be retained in this case and will be destroyed instead. // * A parent changes Name. - // * A parent changes BindingContextId. - throw new InvalidOperationException($"'{nameof(CascadingModelBinder)}' 'Name' and 'BindingContextId' can't change after initialized."); + throw new InvalidOperationException($"'{nameof(CascadingModelBinder)}' 'Name' can't change after initialized."); } _bindingContext = bindingContext; + + string GenerateBindingContextId(string name) + { + var bindingId = Navigation.ToBaseRelativePath(Navigation.GetUriWithQueryParameter("handler", name)); + var hashIndex = bindingId.IndexOf('#'); + return hashIndex == -1 ? bindingId : new string(bindingId.AsSpan(0, hashIndex)); + } } void IDisposable.Dispose() diff --git a/src/Components/Components/src/Binding/ModelBindingContext.cs b/src/Components/Components/src/Binding/ModelBindingContext.cs index b0cf47e88552..eca1934eed3b 100644 --- a/src/Components/Components/src/Binding/ModelBindingContext.cs +++ b/src/Components/Components/src/Binding/ModelBindingContext.cs @@ -8,15 +8,17 @@ namespace Microsoft.AspNetCore.Components.Binding; ///
public class ModelBindingContext { - internal ModelBindingContext(string name, string? bindingContextId = null) + internal ModelBindingContext(string name, string bindingContextId) { + ArgumentNullException.ThrowIfNull(name); + ArgumentNullException.ThrowIfNull(bindingContextId); // We are initializing the root context, that can be a "named" root context, or the default context. // A named root context only provides a name, and that acts as the BindingId // A "default" root context does not provide a name, and instead it provides an explicit Binding ID. // The explicit binding ID matches that of the default handler, which is the URL Path. - if ((string.IsNullOrEmpty(name) ^ string.IsNullOrEmpty(bindingContextId))) + if (string.IsNullOrEmpty(name) ^ string.IsNullOrEmpty(bindingContextId)) { - throw new InvalidOperationException("A root binding context needs to provide a name and explicit binding ID or none."); + throw new InvalidOperationException("A root binding context needs to provide a name and explicit binding context id or none."); } Name = name; diff --git a/src/Components/Components/src/NavigationManagerExtensions.cs b/src/Components/Components/src/NavigationManagerExtensions.cs index 24746270c942..22b555b0b425 100644 --- a/src/Components/Components/src/NavigationManagerExtensions.cs +++ b/src/Components/Components/src/NavigationManagerExtensions.cs @@ -738,7 +738,7 @@ private static QueryParameterFormatter GetFormatterFromParameterValueTyp var hashStartIndex = uri.IndexOf('#'); hash = hashStartIndex < 0 ? "" : uri.AsSpan(hashStartIndex); - var queryStartIndex = uri.IndexOf('?'); + var queryStartIndex = (hashStartIndex > 0 ? uri.AsSpan(0, hashStartIndex) : uri).IndexOf('?'); if (queryStartIndex < 0) { diff --git a/src/Components/Components/src/RouteView.cs b/src/Components/Components/src/RouteView.cs index 88bee0253270..ac489e821c26 100644 --- a/src/Components/Components/src/RouteView.cs +++ b/src/Components/Components/src/RouteView.cs @@ -87,8 +87,7 @@ private void RenderPageWithParameters(RenderTreeBuilder builder) }); builder.OpenComponent(0); - builder.AddComponentParameter(1, nameof(CascadingModelBinder.Name), ""); - builder.AddComponentParameter(3, nameof(CascadingModelBinder.ChildContent), (RenderFragment)RenderPageWithContext); + builder.AddComponentParameter(1, nameof(CascadingModelBinder.ChildContent), (RenderFragment)RenderPageWithContext); builder.CloseComponent(); RenderFragment RenderPageWithContext(ModelBindingContext context) => RenderPageCore; diff --git a/src/Components/Components/test/CascadingModelBinderTest.cs b/src/Components/Components/test/CascadingModelBinderTest.cs index 437f0f581f4b..0462ccd21695 100644 --- a/src/Components/Components/test/CascadingModelBinderTest.cs +++ b/src/Components/Components/test/CascadingModelBinderTest.cs @@ -1,36 +1,80 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Diagnostics.CodeAnalysis; using Microsoft.AspNetCore.Components.Binding; using Microsoft.AspNetCore.Components.Rendering; using Microsoft.AspNetCore.Components.Test.Helpers; +using Microsoft.Extensions.DependencyInjection; namespace Microsoft.AspNetCore.Components; public class CascadingModelBinderTest { + private readonly TestRenderer _renderer; + private TestNavigationManager _navigationManager; + + public CascadingModelBinderTest() + { + var serviceCollection = new ServiceCollection(); + _navigationManager = new TestNavigationManager(); + serviceCollection.AddSingleton(_navigationManager); + var services = serviceCollection.BuildServiceProvider(); + _renderer = new TestRenderer(services); + } + [Fact] - public void CascadingModelBinder_UsesBindingIdWhenNoDefaultName() + public void CascadingModelBinder_NoBindingContextId_ForDefaultName() { ModelBindingContext capturedContext = null; RenderFragment contents = (ctx) => b => { capturedContext = ctx; }; - var renderer = new TestRenderer(); var testComponent = new TestComponent(builder => { builder.OpenComponent(0); + builder.AddAttribute(1, nameof(CascadingModelBinder.ChildContent), contents); + builder.CloseComponent(); + }); + var id = _renderer.AssignRootComponentId(testComponent); + + // Act + _renderer.RenderRootComponent(id); + + // Assert + Assert.NotNull(capturedContext); + Assert.Empty(capturedContext.Name); + Assert.Empty(capturedContext.BindingContextId); + } + + [Theory] + [InlineData("path", "path?handler=named-context")] + [InlineData("", "?handler=named-context")] + [InlineData("path/with/multiple/segments", "path/with/multiple/segments?handler=named-context")] + [InlineData("path/with/multiple/segments?and=query", "path/with/multiple/segments?and=query&handler=named-context")] + [InlineData("path/with/multiple/segments?and=query#hashtoo", "path/with/multiple/segments?and=query&handler=named-context")] + [InlineData("path/with/#multiple/segments?and=query#hashtoo", "path/with/?handler=named-context")] + [InlineData("path/with/multiple/segments#hashtoo?and=query", "path/with/multiple/segments?handler=named-context")] + public void GeneratesCorrect_BindingContextId_ForNamedBinders(string url, string expectedBindingContextId) + { + ModelBindingContext capturedContext = null; + RenderFragment contents = (ctx) => b => { capturedContext = ctx; }; + _navigationManager.NavigateTo(_navigationManager.ToAbsoluteUri(url).ToString()); + + var testComponent = new TestComponent(builder => + { + builder.OpenComponent(0); + builder.AddAttribute(1, nameof(CascadingModelBinder.Name), "named-context"); builder.AddAttribute(2, nameof(CascadingModelBinder.ChildContent), contents); builder.CloseComponent(); }); - var id = renderer.AssignRootComponentId(testComponent); + var id = _renderer.AssignRootComponentId(testComponent); // Act - renderer.RenderRootComponent(id); + _renderer.RenderRootComponent(id); // Assert Assert.NotNull(capturedContext); - Assert.Null(capturedContext.Name); - Assert.Equal("/path", capturedContext.BindingContextId); + Assert.Equal(expectedBindingContextId, capturedContext.BindingContextId); } [Fact] @@ -39,7 +83,6 @@ public void CascadingModelBinder_CanProvideName() ModelBindingContext capturedContext = null; RenderFragment contents = (ctx) => b => { capturedContext = ctx; }; - var renderer = new TestRenderer(); var testComponent = new TestComponent(builder => { builder.OpenComponent(0); @@ -47,15 +90,15 @@ public void CascadingModelBinder_CanProvideName() builder.AddAttribute(2, nameof(CascadingModelBinder.ChildContent), contents); builder.CloseComponent(); }); - var id = renderer.AssignRootComponentId(testComponent); + var id = _renderer.AssignRootComponentId(testComponent); // Act - renderer.RenderRootComponent(id); + _renderer.RenderRootComponent(id); // Assert Assert.NotNull(capturedContext); Assert.Equal("named-context", capturedContext.Name); - Assert.Equal("named-context", capturedContext.BindingContextId); + Assert.Equal("path?query=value&handler=named-context", capturedContext.BindingContextId); } [Fact] @@ -71,7 +114,6 @@ public void CascadingModelBinder_CanNestNamedContexts() b.CloseComponent(); }; - var renderer = new TestRenderer(); var testComponent = new TestComponent(builder => { builder.OpenComponent(0); @@ -79,15 +121,15 @@ public void CascadingModelBinder_CanNestNamedContexts() builder.AddAttribute(2, nameof(CascadingModelBinder.ChildContent), nested); builder.CloseComponent(); }); - var id = renderer.AssignRootComponentId(testComponent); + var id = _renderer.AssignRootComponentId(testComponent); // Act - renderer.RenderRootComponent(id); + _renderer.RenderRootComponent(id); // Assert Assert.NotNull(capturedContext); Assert.Equal("parent-context.child-context", capturedContext.Name); - Assert.Equal("parent-context.child-context", capturedContext.BindingContextId); + Assert.Equal("path?query=value&handler=parent-context.child-context", capturedContext.BindingContextId); } [Fact] @@ -103,22 +145,21 @@ public void CascadingModelBinder_CanNestWithDefaultContext() b.CloseComponent(); }; - var renderer = new TestRenderer(); var testComponent = new TestComponent(builder => { builder.OpenComponent(0); builder.AddAttribute(2, nameof(CascadingModelBinder.ChildContent), nested); builder.CloseComponent(); }); - var id = renderer.AssignRootComponentId(testComponent); + var id = _renderer.AssignRootComponentId(testComponent); // Act - renderer.RenderRootComponent(id); + _renderer.RenderRootComponent(id); // Assert Assert.NotNull(capturedContext); Assert.Equal("child-context", capturedContext.Name); - Assert.Equal("child-context", capturedContext.BindingContextId); + Assert.Equal("path?query=value&handler=child-context", capturedContext.BindingContextId); } [Fact] @@ -129,11 +170,10 @@ public void Throws_IfDefaultContextIsNotTheRoot() RenderFragment nested = (ctx) => b => { b.OpenComponent(0); - b.AddAttribute(2, nameof(CascadingModelBinder.ChildContent), contents); + b.AddAttribute(1, nameof(CascadingModelBinder.ChildContent), contents); b.CloseComponent(); }; - var renderer = new TestRenderer(); var testComponent = new TestComponent(builder => { builder.OpenComponent(0); @@ -141,10 +181,10 @@ public void Throws_IfDefaultContextIsNotTheRoot() builder.AddAttribute(2, nameof(CascadingModelBinder.ChildContent), nested); builder.CloseComponent(); }); - var id = renderer.AssignRootComponentId(testComponent); + var id = _renderer.AssignRootComponentId(testComponent); // Act - var exception = Assert.Throws(() => renderer.RenderRootComponent(id)); + var exception = Assert.Throws(() => _renderer.RenderRootComponent(id)); Assert.Equal("Nested binding contexts must define a Name. (Parent context) = 'parent-context'.", exception.Message); } @@ -155,7 +195,6 @@ public void Throws_WhenIsFixedAndNameChanges() RenderFragment contents = (ctx) => b => { capturedContext = ctx; }; var contextName = "parent-context"; - var renderer = new TestRenderer(); var testComponent = new TestComponent(builder => { builder.OpenComponent(0); @@ -164,37 +203,14 @@ public void Throws_WhenIsFixedAndNameChanges() builder.AddAttribute(3, nameof(CascadingModelBinder.ChildContent), contents); builder.CloseComponent(); }); - var id = renderer.AssignRootComponentId(testComponent); - renderer.RenderRootComponent(id); + var id = _renderer.AssignRootComponentId(testComponent); + _renderer.RenderRootComponent(id); // Act contextName = "changed"; var exception = Assert.Throws(testComponent.TriggerRender); - Assert.Equal("'CascadingModelBinder' 'Name' and 'BindingContextId' can't change after initialized.", exception.Message); - } - - [Fact] - public void Throws_WhenIsFixedAndBindingIdChanges() - { - ModelBindingContext capturedContext = null; - RenderFragment contents = (ctx) => b => { capturedContext = ctx; }; - - var renderer = new TestRenderer(); - var testComponent = new TestComponent(builder => - { - builder.OpenComponent(0); - builder.AddAttribute(2, nameof(CascadingModelBinder.IsFixed), true); - builder.AddAttribute(3, nameof(CascadingModelBinder.ChildContent), contents); - builder.CloseComponent(); - }); - var id = renderer.AssignRootComponentId(testComponent); - renderer.RenderRootComponent(id); - - // Act - var exception = Assert.Throws(testComponent.TriggerRender); - - Assert.Equal("'CascadingModelBinder' 'Name' and 'BindingContextId' can't change after initialized.", exception.Message); + Assert.Equal("'CascadingModelBinder' 'Name' can't change after initialized.", exception.Message); } [Theory] @@ -204,16 +220,15 @@ public void Throws_WhenIsFixed_Changes(bool isFixed) { ModelBindingContext capturedContext = null; RenderFragment contents = (ctx) => b => { capturedContext = ctx; }; - var renderer = new TestRenderer(); var testComponent = new TestComponent(builder => { builder.OpenComponent(0); - builder.AddAttribute(2, nameof(CascadingModelBinder.IsFixed), isFixed); - builder.AddAttribute(3, nameof(CascadingModelBinder.ChildContent), contents); + builder.AddAttribute(1, nameof(CascadingModelBinder.IsFixed), isFixed); + builder.AddAttribute(2, nameof(CascadingModelBinder.ChildContent), contents); builder.CloseComponent(); }); - var id = renderer.AssignRootComponentId(testComponent); - renderer.RenderRootComponent(id); + var id = _renderer.AssignRootComponentId(testComponent); + _renderer.RenderRootComponent(id); // Act isFixed = !isFixed; @@ -230,7 +245,6 @@ public void CanChange_Name_WhenNotFixed() RenderFragment contents = (ctx) => b => { capturedContext = ctx; }; var contextName = "parent-context"; - var renderer = new TestRenderer(); var testComponent = new TestComponent(builder => { builder.OpenComponent(0); @@ -238,8 +252,8 @@ public void CanChange_Name_WhenNotFixed() builder.AddAttribute(3, nameof(CascadingModelBinder.ChildContent), contents); builder.CloseComponent(); }); - var id = renderer.AssignRootComponentId(testComponent); - renderer.RenderRootComponent(id); + var id = _renderer.AssignRootComponentId(testComponent); + _renderer.RenderRootComponent(id); originalContext = capturedContext; contextName = "changed"; @@ -258,23 +272,49 @@ public void CanChange_BindingContextId_WhenNotFixed() ModelBindingContext originalContext = null; RenderFragment contents = (ctx) => b => { capturedContext = ctx; }; - var renderer = new TestRenderer(); var testComponent = new TestComponent(builder => { builder.OpenComponent(0); - builder.AddAttribute(3, nameof(CascadingModelBinder.ChildContent), contents); + builder.AddComponentParameter(1, nameof(CascadingModelBinder.Name), "context-name"); + builder.AddComponentParameter(2, nameof(CascadingModelBinder.ChildContent), contents); builder.CloseComponent(); }); - var id = renderer.AssignRootComponentId(testComponent); - renderer.RenderRootComponent(id); + var id = _renderer.AssignRootComponentId(testComponent); + _renderer.RenderRootComponent(id); originalContext = capturedContext; // Act + _navigationManager.NavigateTo(_navigationManager.ToAbsoluteUri("fetch-data/6").ToString()); testComponent.TriggerRender(); Assert.NotSame(capturedContext, originalContext); - Assert.Equal("/fetch-data/6", capturedContext.BindingContextId); + Assert.Equal("fetch-data/6?handler=context-name", capturedContext.BindingContextId); + } + + private class RouteViewTestNavigationManager : NavigationManager + { + public RouteViewTestNavigationManager() => + Initialize("https://www.example.com/subdir/", "https://www.example.com/subdir/"); + + public void NotifyLocationChanged(string uri) + { + Uri = uri; + NotifyLocationChanged(false); + } + } + + class TestNavigationManager : NavigationManager + { + public TestNavigationManager() + { + Initialize("https://localhost:85/subdir/", "https://localhost:85/subdir/path?query=value#hash"); + } + + protected override void NavigateToCore([StringSyntax("Uri")] string uri, NavigationOptions options) + { + Uri = uri; + } } class TestComponent : AutoRenderComponent diff --git a/src/Components/Components/test/ModelBindingContextTest.cs b/src/Components/Components/test/ModelBindingContextTest.cs index 8d0d4c492c12..b28f29bcdc78 100644 --- a/src/Components/Components/test/ModelBindingContextTest.cs +++ b/src/Components/Components/test/ModelBindingContextTest.cs @@ -8,30 +8,32 @@ namespace Microsoft.AspNetCore.Components; public class ModelBindingContextTest { [Fact] - public void Throws_IfNameAndBindingContextId_AreProvided() + public void CanCreate_BindingContext_WithDefaultName() { - Assert.Throws(() => new ModelBindingContext("name", "id")); + var context = new ModelBindingContext("", ""); + Assert.Equal("", context.Name); + Assert.Equal("", context.BindingContextId); } [Fact] - public void Throws_IfNoNameOrBindingContextId_AreProvided() + public void CanCreate_BindingContext_WithName() { - Assert.Throws(() => new ModelBindingContext("name", "id")); + var context = new ModelBindingContext("name", "path?handler=name"); + Assert.Equal("name", context.Name); + Assert.Equal("path?handler=name", context.BindingContextId); } [Fact] - public void Name_UsedAsBindingContextId_WhenProvided() + public void Throws_WhenNameIsProvided_AndNoBindingContextId() { - var context = new ModelBindingContext("navigation"); - Assert.Equal("navigation", context.Name); - Assert.Equal("navigation", context.BindingContextId); + var exception = Assert.Throws(() => new ModelBindingContext("name", "")); + Assert.Equal("A root binding context needs to provide a name and explicit binding context id or none.", exception.Message); } [Fact] - public void CanProvide_BindingContextId_ForDefaultName() + public void Throws_WhenBindingContextId_IsProvidedForDefaultName() { - var context = new ModelBindingContext("", "binding-context"); - Assert.Equal("", context.Name); - Assert.Equal("binding-context", context.BindingContextId); + var exception = Assert.Throws(() => new ModelBindingContext("", "context")); + Assert.Equal("A root binding context needs to provide a name and explicit binding context id or none.", exception.Message); } } diff --git a/src/Components/Components/test/RouteViewTest.cs b/src/Components/Components/test/RouteViewTest.cs index 6fcacea1f783..8c0037fa3ef8 100644 --- a/src/Components/Components/test/RouteViewTest.cs +++ b/src/Components/Components/test/RouteViewTest.cs @@ -77,87 +77,9 @@ public void RendersPageInsideLayoutView() var testLayoutFrames = _renderer.GetCurrentRenderTreeFrames(testLayoutComponentId).AsEnumerable(); Assert.Collection(testLayoutFrames, frame => AssertFrame.Text(frame, "Layout starts here", sequence: 0), - frame => AssertFrame.Region(frame, subtreeLength: 5), - frame => AssertFrame.Component(frame, sequence: 0, subtreeLength: 4), - frame => AssertFrame.Attribute(frame, nameof(CascadingModelBinder.Name), "", sequence: 1), - frame => AssertFrame.Attribute(frame, nameof(CascadingModelBinder.ChildContent), typeof(RenderFragment), sequence: 3), - frame => AssertFrame.Text(frame, "Layout ends here", sequence: 2)); - - // Assert: Cascading model binder renders CascadingValue - var cascadingModelBinderComponentId = batch.GetComponentFrames().Single().ComponentId; - var cascadingModelBinderFrames = _renderer.GetCurrentRenderTreeFrames(cascadingModelBinderComponentId).AsEnumerable(); - Assert.Collection(cascadingModelBinderFrames, - frame => AssertFrame.Component>(frame, sequence: 0, subtreeLength: 4), - frame => AssertFrame.Attribute(frame, nameof(CascadingValue.IsFixed), false, sequence: 1), - frame => AssertFrame.Attribute(frame, nameof(CascadingValue.Value), typeof(ModelBindingContext), sequence: 2), - frame => AssertFrame.Attribute(frame, nameof(CascadingValue.ChildContent), typeof(RenderFragment), sequence: 3)); - - // Assert: CascadingValue renders page - var cascadingValueComponentId = batch.GetComponentFrames>().Single().ComponentId; - var cascadingValueFrames = _renderer.GetCurrentRenderTreeFrames(cascadingValueComponentId).AsEnumerable(); - Assert.Collection(cascadingValueFrames, - frame => AssertFrame.Region(frame, sequence: 0, subtreeLength: 3), - frame => AssertFrame.Component(frame, sequence: 0, subtreeLength: 2), - frame => AssertFrame.Attribute(frame, nameof(ComponentWithLayout.Message), "Test message", sequence: 1)); - - // Assert: page itself is rendered, having received parameters from the original route data - var pageComponentId = batch.GetComponentFrames().Single().ComponentId; - var pageFrames = _renderer.GetCurrentRenderTreeFrames(pageComponentId).AsEnumerable(); - Assert.Collection(pageFrames, - frame => AssertFrame.Text(frame, "Hello from the page with message 'Test message'", sequence: 0)); - - // Assert: nothing else was rendered - Assert.Equal(6, batch.DiffsInOrder.Count); - } - - [Theory] - [InlineData("https://www.example.com/subdir/path", "/path")] - [InlineData("https://www.example.com/subdir/", "/")] - [InlineData("https://www.example.com/subdir/path/with/multiple/segments", "/path/with/multiple/segments")] - [InlineData("https://www.example.com/subdir/path/with/multiple/segments?and=query", "/path/with/multiple/segments")] - [InlineData("https://www.example.com/subdir/path/with/multiple/segments?and=query#hashtoo", "/path/with/multiple/segments")] - [InlineData("https://www.example.com/subdir/path/with/#multiple/segments?and=query#hashtoo", "/path/with/")] - [InlineData("https://www.example.com/subdir/path/with/multiple/segments#hashtoo?and=query", "/path/with/multiple/segments")] - public void ProvidesDocumentPathAsBindingContextId(string url, string expectedBindingContextId) - { - // Arrange - _navigationManager.NotifyLocationChanged(url); - var routeParams = new Dictionary - { - { nameof(ComponentWithLayout.Message), "Test message" } - }; - var routeData = new RouteData(typeof(ComponentWithLayout), routeParams); - - // Act - _renderer.Dispatcher.InvokeAsync(() => _routeViewComponent.SetParametersAsync(ParameterView.FromDictionary(new Dictionary - { - { nameof(RouteView.RouteData), routeData }, - }))); - - // Assert: RouteView renders LayoutView - var batch = _renderer.Batches.Single(); - var routeViewFrames = _renderer.GetCurrentRenderTreeFrames(_routeViewComponentId).AsEnumerable(); - Assert.Collection(routeViewFrames, - frame => AssertFrame.Component(frame, subtreeLength: 3, sequence: 0), - frame => AssertFrame.Attribute(frame, nameof(LayoutView.Layout), (object)typeof(TestLayout), sequence: 1), - frame => AssertFrame.Attribute(frame, nameof(LayoutView.ChildContent), sequence: 2)); - - // Assert: LayoutView renders TestLayout - var layoutViewComponentId = batch.GetComponentFrames().Single().ComponentId; - var layoutViewFrames = _renderer.GetCurrentRenderTreeFrames(layoutViewComponentId).AsEnumerable(); - Assert.Collection(layoutViewFrames, - frame => AssertFrame.Component(frame, subtreeLength: 2, sequence: 0), - frame => AssertFrame.Attribute(frame, nameof(LayoutComponentBase.Body), sequence: 1)); - - // Assert: TestLayout renders cascading model binder - var testLayoutComponentId = batch.GetComponentFrames().Single().ComponentId; - var testLayoutFrames = _renderer.GetCurrentRenderTreeFrames(testLayoutComponentId).AsEnumerable(); - Assert.Collection(testLayoutFrames, - frame => AssertFrame.Text(frame, "Layout starts here", sequence: 0), - frame => AssertFrame.Region(frame, subtreeLength: 5), - frame => AssertFrame.Component(frame, sequence: 0, subtreeLength: 4), - frame => AssertFrame.Attribute(frame, nameof(CascadingModelBinder.Name), "", sequence: 1), - frame => AssertFrame.Attribute(frame, nameof(CascadingModelBinder.ChildContent), typeof(RenderFragment), sequence: 3), + frame => AssertFrame.Region(frame, subtreeLength: 3), + frame => AssertFrame.Component(frame, sequence: 0, subtreeLength: 2), + frame => AssertFrame.Attribute(frame, nameof(CascadingModelBinder.ChildContent), typeof(RenderFragment), sequence: 1), frame => AssertFrame.Text(frame, "Layout ends here", sequence: 2)); // Assert: Cascading model binder renders CascadingValue diff --git a/src/Components/Web/test/Forms/EditFormTest.cs b/src/Components/Web/test/Forms/EditFormTest.cs index 361f5cd64fce..8d944b6cda7f 100644 --- a/src/Components/Web/test/Forms/EditFormTest.cs +++ b/src/Components/Web/test/Forms/EditFormTest.cs @@ -6,6 +6,7 @@ using Microsoft.AspNetCore.Components.Rendering; using Microsoft.AspNetCore.Components.RenderTree; using Microsoft.AspNetCore.Components.Test.Helpers; +using Microsoft.Extensions.DependencyInjection; namespace Microsoft.AspNetCore.Components.Forms; @@ -13,6 +14,13 @@ public class EditFormTest { private TestRenderer _testRenderer = new(); + public EditFormTest() + { + var services = new ServiceCollection(); + services.AddSingleton(); + _testRenderer = new(services.BuildServiceProvider()); + } + [Fact] public async Task ThrowsIfBothEditContextAndModelAreSupplied() { @@ -99,7 +107,7 @@ public async Task FormElementNameAndAction_SetToComponentName_WhenFormNameIsProv // Assert AssertFrame.Attribute(attributes[0], "name", "my-form"); - AssertFrame.Attribute(attributes[1], "action", "?handler=my-form"); + AssertFrame.Attribute(attributes[1], "action", "path?query=value&handler=my-form"); } [Fact] @@ -111,7 +119,7 @@ public async Task FormElementNameAndAction_SetToComponentName_WhenCombiningWithD { Model = model, FormName = "my-form", - BindingContext = new ModelBindingContext("", "/") + BindingContext = new ModelBindingContext("", "") }; // Act @@ -120,7 +128,7 @@ public async Task FormElementNameAndAction_SetToComponentName_WhenCombiningWithD // Assert AssertFrame.Attribute(attributes[0], "name", "my-form"); - AssertFrame.Attribute(attributes[1], "action", "?handler=my-form"); + AssertFrame.Attribute(attributes[1], "action", "path?query=value&handler=my-form"); } [Fact] @@ -132,7 +140,7 @@ public async Task FormElementNameAndAction_SetToCombinedIdentifier_WhenCombining { Model = model, FormName = "my-form", - BindingContext = new ModelBindingContext("parent-context") + BindingContext = new ModelBindingContext("parent-context", "path?handler=parent-context") }; // Act @@ -141,7 +149,7 @@ public async Task FormElementNameAndAction_SetToCombinedIdentifier_WhenCombining // Assert AssertFrame.Attribute(attributes[0], "name", "parent-context.my-form"); - AssertFrame.Attribute(attributes[1], "action", "?handler=parent-context.my-form"); + AssertFrame.Attribute(attributes[1], "action", "path?query=value&handler=parent-context.my-form"); } [Fact] @@ -157,7 +165,7 @@ public async Task FormElementNameAndAction_CanBeExplicitlyOverriden() ["name"] = "my-explicit-name", ["action"] = "/somewhere/else", }, - BindingContext = new ModelBindingContext("parent-context") + BindingContext = new ModelBindingContext("parent-context", "path?handler=parent-context") }; // Act @@ -177,7 +185,7 @@ public async Task FormElementNameAndAction_NotSetOnDefaultBindingContext() var rootComponent = new TestEditFormHostComponent { Model = model, - BindingContext = new ModelBindingContext("", "/"), + BindingContext = new ModelBindingContext("", ""), SubmitHandler = ctx => { } }; @@ -240,14 +248,14 @@ public async Task EventHandlerName_SetToBindingIdOnDefaultHandler() var rootComponent = new TestEditFormHostComponent { Model = model, - BindingContext = new ModelBindingContext("", "/") + BindingContext = new ModelBindingContext("", "") }; // Act _ = await RenderAndGetTestEditFormComponentAsync(rootComponent); // Assert - Assert.Equal("/", tracker.EventName); + Assert.Equal("", tracker.EventName); } [Fact] @@ -280,7 +288,7 @@ public async Task EventHandlerName_SetToFormNameWhenParentBindingContextIsDefaul { Model = model, FormName = "my-form", - BindingContext = new ModelBindingContext("", "/") + BindingContext = new ModelBindingContext("", "") }; // Act @@ -300,7 +308,7 @@ public async Task EventHandlerName_SetToCombinedNameWhenParentBindingContextIsNa { Model = model, FormName = "my-form", - BindingContext = new ModelBindingContext("parent-context") + BindingContext = new ModelBindingContext("parent-context", "path?handler=parent-context") }; // Act @@ -425,4 +433,12 @@ void RenderForm(RenderTreeBuilder builder) } } } + + class TestNavigationManager : NavigationManager + { + public TestNavigationManager() + { + Initialize("https://localhost:85/subdir/", "https://localhost:85/subdir/path?query=value#hash"); + } + } } From 94e37022737fa4e18aa8e662b5da6964a6582408 Mon Sep 17 00:00:00 2001 From: jacalvar Date: Wed, 19 Apr 2023 17:14:49 +0200 Subject: [PATCH 23/28] Add E2E tests --- AspNetCore.sln | 2 +- .../ServerRenderingTests/FormHandlingTest.cs | 148 ++++++++++++++++-- .../RazorComponentEndpointsStartup.cs | 5 + .../RazorComponents/Components/_Imports.razor | 1 + .../Pages/Forms/AmbiguousForms.razor | 22 +++ .../Pages/Forms/AsyncRenderedForm.razor | 20 +++ .../Pages/Forms/DisappearingForm.razor | 25 +++ .../Forms/FormDefinedInsideComponent.razor | 3 +- .../FormOutsideBindingContextNoOps.razor | 19 +++ .../Pages/Forms/ModifyHttpContextForm.razor | 42 +++++ .../Pages/Forms/StreamingRenderingForm.razor | 46 ++++++ ...chingDispatchedComponentsDoesNotBind.razor | 27 ++++ .../RazorComponents/Shared/FormsLayout.razor | 1 - .../Shared/NoFormContextLayout.razor | 15 ++ .../Services/AsyncOperationService.cs | 24 +++ 15 files changed, 386 insertions(+), 14 deletions(-) create mode 100644 src/Components/test/testassets/Components.TestServer/RazorComponents/Components/_Imports.razor create mode 100644 src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/AmbiguousForms.razor create mode 100644 src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/AsyncRenderedForm.razor create mode 100644 src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/DisappearingForm.razor create mode 100644 src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/FormOutsideBindingContextNoOps.razor create mode 100644 src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/ModifyHttpContextForm.razor create mode 100644 src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/StreamingRenderingForm.razor create mode 100644 src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/SwitchingDispatchedComponentsDoesNotBind.razor create mode 100644 src/Components/test/testassets/Components.TestServer/RazorComponents/Shared/NoFormContextLayout.razor create mode 100644 src/Components/test/testassets/Components.TestServer/Services/AsyncOperationService.cs diff --git a/AspNetCore.sln b/AspNetCore.sln index 7e05768641f8..78d5c6afef38 100644 --- a/AspNetCore.sln +++ b/AspNetCore.sln @@ -1778,7 +1778,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Compon EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Components.Endpoints.Tests", "src\Components\Endpoints\test\Microsoft.AspNetCore.Components.Endpoints.Tests.csproj", "{5D438258-CB19-4282-814F-974ABBC71411}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BlazorUnitedApp", "src\Components\Samples\BlazorUnitedApp\BlazorUnitedApp.csproj", "{F5AE525F-F435-40F9-A567-4D5EC3B50D6E}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazorUnitedApp", "src\Components\Samples\BlazorUnitedApp\BlazorUnitedApp.csproj", "{F5AE525F-F435-40F9-A567-4D5EC3B50D6E}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/src/Components/test/E2ETest/ServerRenderingTests/FormHandlingTest.cs b/src/Components/test/E2ETest/ServerRenderingTests/FormHandlingTest.cs index 2ae35e9bb0cc..e54cae4eb413 100644 --- a/src/Components/test/E2ETest/ServerRenderingTests/FormHandlingTest.cs +++ b/src/Components/test/E2ETest/ServerRenderingTests/FormHandlingTest.cs @@ -7,6 +7,8 @@ using TestServer; using Xunit.Abstractions; using OpenQA.Selenium; +using System.Net.Http; +using static System.Net.Mime.MediaTypeNames; namespace Microsoft.AspNetCore.Components.E2ETests.ServerRenderingTests; @@ -23,13 +25,6 @@ public class FormHandlingTest : ServerTestBase InitializeAsync(BrowserFixture.StreamingContext); - // Can dispatch to the default form - // Can dispatch to a named form - // Rendering ambiguous forms doesn't cause an error. - // Dispatching to ambiguous forms raises an error. - // Can dispatch to a nested form - // Can dispatch to a nested named form inside the default binding context. - [Fact] public void CanDispatchToTheDefaultForm() { @@ -71,13 +66,138 @@ public void CanDispatchToFormDefinedInNonPageComponent() { var dispatchToForm = new DispatchToForm(this) { - Url = "/forms/form-defined-inside-component", + Url = "forms/form-defined-inside-component", + FormCssSelector = "form", + ExpectedActionValue = null, + }; + DispatchToFormCore(dispatchToForm); + } + + [Fact] + public void CanRenderAmbiguousForms() + { + var dispatchToForm = new DispatchToForm(this) + { + Url = "forms/ambiguous-forms", FormCssSelector = "form", - ExpectedActionValue = "", + ExpectedActionValue = null, + DispatchEvent = false + }; + DispatchToFormCore(dispatchToForm); + } + + [Fact] + public void DispatchingToAmbiguousFormFails() + { + var dispatchToForm = new DispatchToForm(this) + { + Url = "forms/ambiguous-forms", + FormCssSelector = "form", + ExpectedActionValue = null, + DispatchEvent = true, + SubmitButtonId = "send-second", + // This is an error ID on the page chrome shows from a 500. + SubmitPassId = "main-frame-error" }; DispatchToFormCore(dispatchToForm); } + [Fact] + public void FormWithoutBindingContextDoesNotBind() + { + var dispatchToForm = new DispatchToForm(this) + { + Url = "forms/no-form-context-no-op", + FormCssSelector = "form", + ExpectedActionValue = null, + SubmitPassId = "main-frame-error" + }; + DispatchToFormCore(dispatchToForm); + } + + [Fact] + public void CanDispatchToFormRenderedAsynchronously() + { + var dispatchToForm = new DispatchToForm(this) + { + Url = "forms/async-rendered-form", + FormCssSelector = "form", + ExpectedActionValue = null + }; + DispatchToFormCore(dispatchToForm); + } + + [Fact] + public void FormThatDisappearsBeforeQuiesceDoesNotBind() + { + var dispatchToForm = new DispatchToForm(this) + { + Url = "forms/disappears-before-dispatching", + FormCssSelector = "form", + ExpectedActionValue = null, + SubmitButtonId = "test-send", + SubmitPassId = "main-frame-error" + }; + DispatchToFormCore(dispatchToForm); + } + + [Fact] + public void ChangingComponentsToDispatchBeforeQuiesceDoesNotBind() + { + var dispatchToForm = new DispatchToForm(this) + { + Url = "forms/switching-components-does-not-bind", + FormCssSelector = "form", + ExpectedActionValue = null, + SubmitPassId = "main-frame-error" + }; + DispatchToFormCore(dispatchToForm); + } + + [Fact] + public async Task CanPostFormsWithStreamingRenderingAsync() + { + GoTo("forms/streaming-rendering/CanPostFormsWithStreamingRendering"); + + Browser.Exists(By.Id("ready")); + var form = Browser.Exists(By.CssSelector("form")); + var actionValue = form.GetDomAttribute("action"); + Assert.Null(actionValue); + + Browser.Click(By.Id("send")); + + Browser.Exists(By.Id("progress")); + + using var client = new HttpClient() { BaseAddress = _serverFixture.RootUri }; + var response = await client.PostAsync("subdir/forms/streaming-rendering/complete/CanPostFormsWithStreamingRendering", content: null); + response.EnsureSuccessStatusCode(); + + Browser.Exists(By.Id("pass")); + } + + [Fact] + public async Task CanModifyTheHttpResponseDuringEventHandling() + { + GoTo("forms/modify-http-context/ModifyHttpContext"); + + Browser.Exists(By.Id("ready")); + var form = Browser.Exists(By.CssSelector("form")); + var actionValue = form.GetDomAttribute("action"); + Assert.Null(actionValue); + + Browser.Click(By.Id("send")); + + Browser.Exists(By.Id("progress")); + + using var client = new HttpClient() { BaseAddress = _serverFixture.RootUri }; + var response = await client.PostAsync("subdir/forms/streaming-rendering/complete/ModifyHttpContext", content: null); + response.EnsureSuccessStatusCode(); + + Browser.Exists(By.Id("pass")); + var cookie = Browser.Manage().Cookies.GetCookieNamed("operation"); + Assert.Equal("ModifyHttpContext", cookie.Value); + } + private void DispatchToFormCore(DispatchToForm dispatch) { GoTo(dispatch.Url); @@ -89,7 +209,12 @@ private void DispatchToFormCore(DispatchToForm dispatch) Assert.Equal(dispatch.ExpectedTarget, formTarget); Assert.Equal(dispatch.ExpectedActionValue, actionValue); - Browser.Exists(By.Id("send")).Click(); + if (!dispatch.DispatchEvent) + { + return; + } + + Browser.Click(By.Id(dispatch.SubmitButtonId)); Browser.Exists(By.Id(dispatch.SubmitPassId)); } @@ -109,6 +234,9 @@ public DispatchToForm(FormHandlingTest test) : this() public string ExpectedActionValue; public string ExpectedTarget => $"{Base}/{ExpectedActionValue ?? Url}"; + public bool DispatchEvent { get; internal set; } = true; + + public string SubmitButtonId { get; internal set; } = "send"; } private void GoTo(string relativePath) diff --git a/src/Components/test/testassets/Components.TestServer/RazorComponentEndpointsStartup.cs b/src/Components/test/testassets/Components.TestServer/RazorComponentEndpointsStartup.cs index 864276a97827..b36c9a17f475 100644 --- a/src/Components/test/testassets/Components.TestServer/RazorComponentEndpointsStartup.cs +++ b/src/Components/test/testassets/Components.TestServer/RazorComponentEndpointsStartup.cs @@ -4,6 +4,8 @@ using System.Globalization; using Components.TestServer.RazorComponents; using Components.TestServer.RazorComponents.Pages; +using Components.TestServer.RazorComponents.Pages.Forms; +using Components.TestServer.Services; namespace TestServer; @@ -20,6 +22,8 @@ public RazorComponentEndpointsStartup(IConfiguration configuration) public void ConfigureServices(IServiceCollection services) { services.AddRazorComponents(); + services.AddHttpContextAccessor(); + services.AddSingleton(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. @@ -42,6 +46,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) endpoints.MapRazorComponents(); StreamingRendering.MapEndpoints(endpoints); + StreamingRenderingForm.MapEndpoints(endpoints); }); }); } diff --git a/src/Components/test/testassets/Components.TestServer/RazorComponents/Components/_Imports.razor b/src/Components/test/testassets/Components.TestServer/RazorComponents/Components/_Imports.razor new file mode 100644 index 000000000000..ea9c29694e05 --- /dev/null +++ b/src/Components/test/testassets/Components.TestServer/RazorComponents/Components/_Imports.razor @@ -0,0 +1 @@ +@namespace Components.TestServer.RazorComponents diff --git a/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/AmbiguousForms.razor b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/AmbiguousForms.razor new file mode 100644 index 000000000000..2228e10e6c92 --- /dev/null +++ b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/AmbiguousForms.razor @@ -0,0 +1,22 @@ +@page "/forms/ambiguous-forms" +@using Microsoft.AspNetCore.Components.Forms + +

Ambiguous forms

+ + + + + + + + + +@if (_submitted) +{ +

Form submitted!

+} + +@code{ + bool _submitted = false; + EditContext _editContext = new EditContext(new object()); +} diff --git a/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/AsyncRenderedForm.razor b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/AsyncRenderedForm.razor new file mode 100644 index 000000000000..119eff14a63c --- /dev/null +++ b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/AsyncRenderedForm.razor @@ -0,0 +1,20 @@ +@page "/forms/async-rendered-form" +@using Components.TestServer.RazorComponents + +

Async rendered form

+ +@if (_ready) +{ + + +} + +@code +{ + private bool _ready; + protected override async Task OnInitializedAsync() + { + await Task.Yield(); + _ready = true; + } +} diff --git a/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/DisappearingForm.razor b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/DisappearingForm.razor new file mode 100644 index 000000000000..574c6b52df0c --- /dev/null +++ b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/DisappearingForm.razor @@ -0,0 +1,25 @@ +@page "/forms/disappears-before-dispatching" +@using Components.TestServer.RazorComponents + +

Form disappears before dispatching

+ +@if (!_ready) +{ + + +} + +@* Just here so that the test can dispatch the event. *@ + + + + +@code +{ + private bool _ready; + protected override async Task OnInitializedAsync() + { + await Task.Yield(); + _ready = true; + } +} diff --git a/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/FormDefinedInsideComponent.razor b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/FormDefinedInsideComponent.razor index 0dff3db56b3d..9fff246f0d4a 100644 --- a/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/FormDefinedInsideComponent.razor +++ b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/FormDefinedInsideComponent.razor @@ -1,6 +1,5 @@ @page "/forms/form-defined-inside-component" -@using Components.TestServer.RazorComponents.Components -@using Microsoft.AspNetCore.Components.Forms +@using Components.TestServer.RazorComponents

Form defined in a sub-component

diff --git a/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/FormOutsideBindingContextNoOps.razor b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/FormOutsideBindingContextNoOps.razor new file mode 100644 index 000000000000..c8c47bd310d7 --- /dev/null +++ b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/FormOutsideBindingContextNoOps.razor @@ -0,0 +1,19 @@ +@page "/forms/no-form-context-no-op" +@using Microsoft.AspNetCore.Components.Forms +@layout NoFormContextLayout + +

Default form

+ + + + + +@if (_submitted) +{ +

Form submitted!

+} + +@code{ + bool _submitted = false; + EditContext _editContext = new EditContext(new object()); +} diff --git a/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/ModifyHttpContextForm.razor b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/ModifyHttpContextForm.razor new file mode 100644 index 000000000000..6459bae175d7 --- /dev/null +++ b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/ModifyHttpContextForm.razor @@ -0,0 +1,42 @@ +@page "/forms/modify-http-context/{OperationId}" +@attribute [StreamRendering(true)] +@inject AsyncOperationService AsyncOperation +@inject NavigationManager Navigation +@* This is not the recommended way to access the HttpContext in Blazor, this is just for test purposes *@ +@inject IHttpContextAccessor Accessor +@using Components.TestServer.Services; +@using Microsoft.AspNetCore.Components.Forms +@using Microsoft.AspNetCore.Mvc; + +

Default form

+ + + + + +@if (_submitting) +{ +

Form submitting!

+} +else if (_submitted) +{ +

Form submitted!

+} + +@code { + bool _submitted = false; + bool _submitting = false; + EditContext _editContext = new EditContext(new object()); + + public async Task HandleSubmit() + { + var id = new Uri(Navigation.Uri).AbsolutePath.Split('/')[^1]; + _submitting = true; + // The response can be accessed before any async work happens. + // We might want to provide APIs to control when streaming rendering starts. + Accessor.HttpContext.Response.Cookies.Append("operation", id); + await AsyncOperation.Start(id); + _submitting = false; + _submitted = true; + } +} diff --git a/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/StreamingRenderingForm.razor b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/StreamingRenderingForm.razor new file mode 100644 index 000000000000..531ff8aedfea --- /dev/null +++ b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/StreamingRenderingForm.razor @@ -0,0 +1,46 @@ +@page "/forms/streaming-rendering/{OperationId}" +@attribute [StreamRendering(true)] +@inject AsyncOperationService AsyncOperation +@inject NavigationManager Navigation; +@using Components.TestServer.Services; +@using Microsoft.AspNetCore.Components.Forms +@using Microsoft.AspNetCore.Mvc; + +

Default form

+ + + + + +@if (_submitting) +{ +

Form submitting!

+} +else if (_submitted) +{ +

Form submitted!

+} + +@code { + bool _submitted = false; + bool _submitting = false; + EditContext _editContext = new EditContext(new object()); + + public async Task HandleSubmit() + { + _submitting = true; + await AsyncOperation.Start(new Uri(Navigation.Uri).AbsolutePath.Split('/')[^1]); + _submitting = false; + _submitted = true; + } + + public static void MapEndpoints(IEndpointRouteBuilder endpoints) + { + endpoints.MapPost( + "/forms/streaming-rendering/complete/{operationId}", + ([FromServices] AsyncOperationService asyncOperation, [FromRoute] string operationId) => + { + asyncOperation.Complete(operationId); + }); + } +} diff --git a/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/SwitchingDispatchedComponentsDoesNotBind.razor b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/SwitchingDispatchedComponentsDoesNotBind.razor new file mode 100644 index 000000000000..5fd1ec73dd92 --- /dev/null +++ b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/SwitchingDispatchedComponentsDoesNotBind.razor @@ -0,0 +1,27 @@ +@page "/forms/switching-components-does-not-bind" +@using Components.TestServer.RazorComponents +@using Microsoft.AspNetCore.Components.Authorization + +

Form disappears before dispatching

+ +@if (!_ready) +{ + + +}else +{ + + + + +} + +@code +{ + private bool _ready; + protected override async Task OnInitializedAsync() + { + await Task.Yield(); + _ready = true; + } +} diff --git a/src/Components/test/testassets/Components.TestServer/RazorComponents/Shared/FormsLayout.razor b/src/Components/test/testassets/Components.TestServer/RazorComponents/Shared/FormsLayout.razor index d920bff06b8f..e500558eba23 100644 --- a/src/Components/test/testassets/Components.TestServer/RazorComponents/Shared/FormsLayout.razor +++ b/src/Components/test/testassets/Components.TestServer/RazorComponents/Shared/FormsLayout.razor @@ -1,5 +1,4 @@ @inherits LayoutComponentBase -@inject NavigationManager Navigation diff --git a/src/Components/test/testassets/Components.TestServer/RazorComponents/Shared/NoFormContextLayout.razor b/src/Components/test/testassets/Components.TestServer/RazorComponents/Shared/NoFormContextLayout.razor new file mode 100644 index 000000000000..e750067a6693 --- /dev/null +++ b/src/Components/test/testassets/Components.TestServer/RazorComponents/Shared/NoFormContextLayout.razor @@ -0,0 +1,15 @@ +@inherits LayoutComponentBase + + + + + + + + + Razor Components Forms +

Form test cases

+ @Body + + + diff --git a/src/Components/test/testassets/Components.TestServer/Services/AsyncOperationService.cs b/src/Components/test/testassets/Components.TestServer/Services/AsyncOperationService.cs new file mode 100644 index 000000000000..3ad6da3ca7ae --- /dev/null +++ b/src/Components/test/testassets/Components.TestServer/Services/AsyncOperationService.cs @@ -0,0 +1,24 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Concurrent; + +namespace Components.TestServer.Services; + +public class AsyncOperationService +{ + private readonly ConcurrentDictionary _tasks = new(); + + public Task Start(string id) + { + return _tasks.GetOrAdd(id, (id) => new TaskCompletionSource()).Task; + } + + public void Complete(string id) + { + if (_tasks.TryRemove(id, out var tcs)) + { + tcs.SetResult(); + } + } +} From be14184884369a41f90875e9615ad21b4587281b Mon Sep 17 00:00:00 2001 From: jacalvar Date: Wed, 19 Apr 2023 17:26:46 +0200 Subject: [PATCH 24/28] Cleanup empty lines --- src/Components/Components/src/Binding/CascadingModelBinder.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Components/Components/src/Binding/CascadingModelBinder.cs b/src/Components/Components/src/Binding/CascadingModelBinder.cs index b0f82288b9b7..24f9b4764fe2 100644 --- a/src/Components/Components/src/Binding/CascadingModelBinder.cs +++ b/src/Components/Components/src/Binding/CascadingModelBinder.cs @@ -101,8 +101,6 @@ private void UpdateBindingInformation(string url) // 3) Parent has a name "parent-name" // Name = "parent-name.my-handler"; // BindingContextId = <>((<>&)|?)handler=my-handler - - var name = string.IsNullOrEmpty(ParentContext?.Name) ? Name : $"{ParentContext.Name}.{Name}"; var bindingId = string.IsNullOrEmpty(name) ? "" : GenerateBindingContextId(name); From 7d72205242f7c254aedf756692124c659fffdc43 Mon Sep 17 00:00:00 2001 From: jacalvar Date: Thu, 20 Apr 2023 18:15:52 +0200 Subject: [PATCH 25/28] Another test --- .../Components/src/RenderTree/Renderer.cs | 19 ++++----- .../src/RazorComponentEndpointInvoker.cs | 5 +++ .../src/Rendering/EndpointHtmlRenderer.cs | 2 - .../ServerRenderingTests/FormHandlingTest.cs | 21 ++++++++++ .../Forms/NonStreamingRenderingForm.razor | 41 +++++++++++++++++++ 5 files changed, 75 insertions(+), 13 deletions(-) create mode 100644 src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/NonStreamingRenderingForm.razor diff --git a/src/Components/Components/src/RenderTree/Renderer.cs b/src/Components/Components/src/RenderTree/Renderer.cs index 14d22a0fab4b..9b792ad2999c 100644 --- a/src/Components/Components/src/RenderTree/Renderer.cs +++ b/src/Components/Components/src/RenderTree/Renderer.cs @@ -56,7 +56,7 @@ public abstract partial class Renderer : IDisposable, IAsyncDisposable { Dispatcher.UnhandledException -= value; } - } + } /// /// Constructs an instance of . @@ -416,18 +416,15 @@ public virtual Task DispatchEventAsync(ulong eventHandlerId, EventFieldInfo? fie // The event handler might request multiple renders in sequence. Capture them // all in a single batch. _isBatchInProgress = true; - - task = callback.InvokeAsync(eventArgs); if (quiesce) { - if (_ongoingQuiescenceTask == null) - { - _ongoingQuiescenceTask = task; - } - else - { - AddToPendingTasksWithErrorHandling(task, receiverComponentState); - } + _pendingTasks ??= new(); + task = callback.InvokeAsync(eventArgs); + AddToPendingTasksWithErrorHandling(task, receiverComponentState); + } + else + { + task = callback.InvokeAsync(eventArgs); } } catch (Exception e) diff --git a/src/Components/Endpoints/src/RazorComponentEndpointInvoker.cs b/src/Components/Endpoints/src/RazorComponentEndpointInvoker.cs index 1d8097c803c1..a06a3e69639d 100644 --- a/src/Components/Endpoints/src/RazorComponentEndpointInvoker.cs +++ b/src/Components/Endpoints/src/RazorComponentEndpointInvoker.cs @@ -61,6 +61,11 @@ private async Task RenderComponentCore() var quiesceTask = isPost ? _renderer.DispatchCapturedEvent() : htmlContent.QuiescenceTask; + if (isPost) + { + await Task.WhenAll(_renderer.NonStreamingPendingTasks); + } + // Importantly, we must not yield this thread (which holds exclusive access to the renderer sync context) // in between the first call to htmlContent.WriteTo and the point where we start listening for subsequent // streaming SSR batches (inside SendStreamingUpdatesAsync). Otherwise some other code might dispatch to the diff --git a/src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.cs b/src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.cs index 516e729d8122..546ab88efa37 100644 --- a/src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.cs +++ b/src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.cs @@ -126,8 +126,6 @@ internal Task DispatchCapturedEvent() throw new InvalidOperationException($"No named event handler was captured for '{_formHandler}'."); } - // Clear the list of non-streaming rendering tasks, since we've waited for quiesce before dispatching the event. - _nonStreamingPendingTasks.Clear(); return DispatchEventAsync(_capturedNamedEvent.EventHandlerId, null, EventArgs.Empty, quiesce: true); } diff --git a/src/Components/test/E2ETest/ServerRenderingTests/FormHandlingTest.cs b/src/Components/test/E2ETest/ServerRenderingTests/FormHandlingTest.cs index e54cae4eb413..24b1ccb6667f 100644 --- a/src/Components/test/E2ETest/ServerRenderingTests/FormHandlingTest.cs +++ b/src/Components/test/E2ETest/ServerRenderingTests/FormHandlingTest.cs @@ -198,6 +198,27 @@ public async Task CanModifyTheHttpResponseDuringEventHandling() Assert.Equal("ModifyHttpContext", cookie.Value); } + [Fact] + public async Task CanHandleFormPostNonStreamingRenderingAsyncHandler() + { + GoTo("forms/non-streaming-async-form-handler/CanHandleFormPostNonStreamingRenderingAsyncHandler"); + + Browser.Exists(By.Id("ready")); + var form = Browser.Exists(By.CssSelector("form")); + var actionValue = form.GetDomAttribute("action"); + Assert.Null(actionValue); + + Browser.Click(By.Id("send")); + + await Task.Yield(); + + using var client = new HttpClient() { BaseAddress = _serverFixture.RootUri }; + var response = await client.PostAsync("subdir/forms/streaming-rendering/complete/CanHandleFormPostNonStreamingRenderingAsyncHandler", content: null); + response.EnsureSuccessStatusCode(); + + Browser.Exists(By.Id("pass")); + } + private void DispatchToFormCore(DispatchToForm dispatch) { GoTo(dispatch.Url); diff --git a/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/NonStreamingRenderingForm.razor b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/NonStreamingRenderingForm.razor new file mode 100644 index 000000000000..4754399ce3b6 --- /dev/null +++ b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/NonStreamingRenderingForm.razor @@ -0,0 +1,41 @@ +@page "/forms/non-streaming-async-form-handler/{OperationId}" +@inject AsyncOperationService AsyncOperation +@inject NavigationManager Navigation; +@using Components.TestServer.Services; +@using Microsoft.AspNetCore.Components.Forms +@using Microsoft.AspNetCore.Mvc; + +

Default form

+ + + + + +@if (_submitting) +{ +

Form submitting!

+} +else if (_submitted) +{ +

Form submitted!

+} + +@code { + bool _submitted = false; + bool _submitting = false; + EditContext _editContext = new EditContext(new object()); + + protected override void OnInitialized() + { + // Due to test issues with threads, just register this task ahead of time. + _ = AsyncOperation.Start(new Uri(Navigation.Uri).AbsolutePath.Split('/')[^1]); + } + + public async Task HandleSubmit() + { + _submitting = true; + await Task.Delay(5000); + _submitting = false; + _submitted = true; + } +} From 298c17869dd5a8fff0cfee542d5b4b42c25b9fc1 Mon Sep 17 00:00:00 2001 From: jacalvar Date: Thu, 20 Apr 2023 19:33:36 +0200 Subject: [PATCH 26/28] Additional cleanups --- .../src/Binding/CascadingModelBinder.cs | 5 ++++- .../src/RenderTree/RenderTreeDiffBuilder.cs | 11 ++--------- .../Components/src/RenderTree/Renderer.cs | 9 +++------ src/Components/Components/src/RouteView.cs | 9 --------- .../src/RazorComponentEndpointInvoker.cs | 5 +++++ .../src/Rendering/EndpointHtmlRenderer.cs | 2 ++ .../ServerRenderingTests/FormHandlingTest.cs | 12 ++++++++++++ .../FormOutsideBindingContextNoOps.razor | 2 +- .../Pages/Forms/ModifyHttpContextForm.razor | 2 +- .../NamedFormContextNoFormContextLayout.razor | 19 +++++++++++++++++++ .../Forms/NonStreamingRenderingForm.razor | 2 +- .../Pages/Forms/StreamingRenderingForm.razor | 2 +- ...chingDispatchedComponentsDoesNotBind.razor | 2 +- 13 files changed, 52 insertions(+), 30 deletions(-) create mode 100644 src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/NamedFormContextNoFormContextLayout.razor diff --git a/src/Components/Components/src/Binding/CascadingModelBinder.cs b/src/Components/Components/src/Binding/CascadingModelBinder.cs index 24f9b4764fe2..58e63b33052e 100644 --- a/src/Components/Components/src/Binding/CascadingModelBinder.cs +++ b/src/Components/Components/src/Binding/CascadingModelBinder.cs @@ -22,7 +22,10 @@ public sealed class CascadingModelBinder : IComponent, IDisposable [Parameter] public string Name { get; set; } = ""; /// - /// The binding context name. + /// If true, indicates that will not change. + /// This is a performance optimization that allows the framework to skip setting up + /// change notifications. Set this flag only if you will not change + /// of this context or its parents' context during the component's lifetime. /// [Parameter] public bool IsFixed { get; set; } diff --git a/src/Components/Components/src/RenderTree/RenderTreeDiffBuilder.cs b/src/Components/Components/src/RenderTree/RenderTreeDiffBuilder.cs index 492b1eb6292b..77800e8d0bb8 100644 --- a/src/Components/Components/src/RenderTree/RenderTreeDiffBuilder.cs +++ b/src/Components/Components/src/RenderTree/RenderTreeDiffBuilder.cs @@ -40,19 +40,12 @@ enum DiffAction { Match, Insert, Delete } // Once a component has defined a named event handler with a concrete name, no other component instance can // define a named event handler with that name. // - // This is only enforced when we are trying to dispatch an event to a named event handler, since in any other - // case we don't actually track the named event handlers. - // - // We don't enforce the global uniqueness of the event handler name inside the base renderer either. That's - // handled by the EndpointRenderer that takes care of ensuring that only one component defined the expected - // named event handler before dispatching the named event. - // // At this stage, we only ensure that the named event handler is unique per component instance, as that, // combined with the check that the EndpointRenderer does, is enough to ensure the uniqueness and the stability // of the named event handler over time **globally**. // - // Note that we only enforce this condition at the time we are going to dispatch a named event to a specific - // named event handler. In any other case, even though is an error, we don't care, as: + // Tracking and uniqueness are enforced when we are trying to dispatch an event to a named event handler, since in + // any other case we don't actually track the named event handlers. We do this because: // 1) We don't want to break the user's app if we don't have to. // 2) We don't have to pay the cost of continously tracking all events all the time to throw. // That's why raising the error is delayed until we are forced to make a decission. diff --git a/src/Components/Components/src/RenderTree/Renderer.cs b/src/Components/Components/src/RenderTree/Renderer.cs index 9b792ad2999c..6e2b01561860 100644 --- a/src/Components/Components/src/RenderTree/Renderer.cs +++ b/src/Components/Components/src/RenderTree/Renderer.cs @@ -419,13 +419,10 @@ public virtual Task DispatchEventAsync(ulong eventHandlerId, EventFieldInfo? fie if (quiesce) { _pendingTasks ??= new(); - task = callback.InvokeAsync(eventArgs); - AddToPendingTasksWithErrorHandling(task, receiverComponentState); - } - else - { - task = callback.InvokeAsync(eventArgs); } + + task = callback.InvokeAsync(eventArgs); + AddToPendingTasksWithErrorHandling(task, receiverComponentState); } catch (Exception e) { diff --git a/src/Components/Components/src/RouteView.cs b/src/Components/Components/src/RouteView.cs index ac489e821c26..72c2550b1720 100644 --- a/src/Components/Components/src/RouteView.cs +++ b/src/Components/Components/src/RouteView.cs @@ -77,15 +77,6 @@ protected virtual void Render(RenderTreeBuilder builder) private void RenderPageWithParameters(RenderTreeBuilder builder) { - var pathStart = NavigationManager.BaseUri.Length - 1; - var bindingId = NavigationManager.Uri.Substring( - pathStart, - NavigationManager.Uri.AsSpan().IndexOfAny("?#") switch - { - -1 => NavigationManager.Uri.Length - pathStart, - var index => index - pathStart - }); - builder.OpenComponent(0); builder.AddComponentParameter(1, nameof(CascadingModelBinder.ChildContent), (RenderFragment)RenderPageWithContext); builder.CloseComponent(); diff --git a/src/Components/Endpoints/src/RazorComponentEndpointInvoker.cs b/src/Components/Endpoints/src/RazorComponentEndpointInvoker.cs index a06a3e69639d..fa3e2b301708 100644 --- a/src/Components/Endpoints/src/RazorComponentEndpointInvoker.cs +++ b/src/Components/Endpoints/src/RazorComponentEndpointInvoker.cs @@ -59,6 +59,11 @@ private async Task RenderComponentCore() hostParameters, waitForQuiescence: isPost); + if (isPost && !_renderer.HasCapturedEvent()) + { + _context.Response.StatusCode = StatusCodes.Status404NotFound; + } + var quiesceTask = isPost ? _renderer.DispatchCapturedEvent() : htmlContent.QuiescenceTask; if (isPost) diff --git a/src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.cs b/src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.cs index 546ab88efa37..12df342931b2 100644 --- a/src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.cs +++ b/src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.cs @@ -119,6 +119,8 @@ protected override void TrackNamedEventId(ulong eventHandlerId, int componentId, } } + internal bool HasCapturedEvent() => _capturedNamedEvent != default; + internal Task DispatchCapturedEvent() { if (_capturedNamedEvent == default) diff --git a/src/Components/test/E2ETest/ServerRenderingTests/FormHandlingTest.cs b/src/Components/test/E2ETest/ServerRenderingTests/FormHandlingTest.cs index 24b1ccb6667f..28df349f43c4 100644 --- a/src/Components/test/E2ETest/ServerRenderingTests/FormHandlingTest.cs +++ b/src/Components/test/E2ETest/ServerRenderingTests/FormHandlingTest.cs @@ -49,6 +49,18 @@ public void CanDispatchToNamedForm() DispatchToFormCore(dispatchToForm); } + [Fact] + public void CanDispatchToNamedFormNoParentBindingContext() + { + var dispatchToForm = new DispatchToForm(this) + { + Url = "forms/named-form-no-form-context", + FormCssSelector = "form[name=named-form-handler]", + ExpectedActionValue = "forms/named-form-no-form-context?handler=named-form-handler", + }; + DispatchToFormCore(dispatchToForm); + } + [Fact] public void CanDispatchToNamedFormInNestedContext() { diff --git a/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/FormOutsideBindingContextNoOps.razor b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/FormOutsideBindingContextNoOps.razor index c8c47bd310d7..a9d0e2172246 100644 --- a/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/FormOutsideBindingContextNoOps.razor +++ b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/FormOutsideBindingContextNoOps.razor @@ -2,7 +2,7 @@ @using Microsoft.AspNetCore.Components.Forms @layout NoFormContextLayout -

Default form

+

Form outside a cascading binding context no-ops

diff --git a/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/ModifyHttpContextForm.razor b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/ModifyHttpContextForm.razor index 6459bae175d7..8eec5c286f26 100644 --- a/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/ModifyHttpContextForm.razor +++ b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/ModifyHttpContextForm.razor @@ -8,7 +8,7 @@ @using Microsoft.AspNetCore.Components.Forms @using Microsoft.AspNetCore.Mvc; -

Default form

+

Event handler sets cookie during form POST

diff --git a/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/NamedFormContextNoFormContextLayout.razor b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/NamedFormContextNoFormContextLayout.razor new file mode 100644 index 000000000000..e57419cd7fe3 --- /dev/null +++ b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/NamedFormContextNoFormContextLayout.razor @@ -0,0 +1,19 @@ +@page "/forms/named-form-no-form-context" +@layout NoFormContextLayout +@using Microsoft.AspNetCore.Components.Forms + +

Named form

+ + + + + +@if (_submitted) +{ +

Form submitted!

+} + +@code{ + bool _submitted = false; + EditContext _editContext = new EditContext(new object()); +} diff --git a/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/NonStreamingRenderingForm.razor b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/NonStreamingRenderingForm.razor index 4754399ce3b6..b9ac41bf57c0 100644 --- a/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/NonStreamingRenderingForm.razor +++ b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/NonStreamingRenderingForm.razor @@ -5,7 +5,7 @@ @using Microsoft.AspNetCore.Components.Forms @using Microsoft.AspNetCore.Mvc; -

Default form

+

Non streaming async form

diff --git a/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/StreamingRenderingForm.razor b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/StreamingRenderingForm.razor index 531ff8aedfea..879335fb9e53 100644 --- a/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/StreamingRenderingForm.razor +++ b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/StreamingRenderingForm.razor @@ -6,7 +6,7 @@ @using Microsoft.AspNetCore.Components.Forms @using Microsoft.AspNetCore.Mvc; -

Default form

+

Streaming rendering async form

diff --git a/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/SwitchingDispatchedComponentsDoesNotBind.razor b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/SwitchingDispatchedComponentsDoesNotBind.razor index 5fd1ec73dd92..2964245a154f 100644 --- a/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/SwitchingDispatchedComponentsDoesNotBind.razor +++ b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/SwitchingDispatchedComponentsDoesNotBind.razor @@ -2,7 +2,7 @@ @using Components.TestServer.RazorComponents @using Microsoft.AspNetCore.Components.Authorization -

Form disappears before dispatching

+

Form component instance changes before dispatching

@if (!_ready) { From 5415283f4c052fff14fa803367cce48f96c1f48f Mon Sep 17 00:00:00 2001 From: jacalvar Date: Thu, 20 Apr 2023 20:52:16 +0200 Subject: [PATCH 27/28] Fix DispatchEvent --- .../Components/src/RenderTree/Renderer.cs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/Components/Components/src/RenderTree/Renderer.cs b/src/Components/Components/src/RenderTree/Renderer.cs index 6e2b01561860..0fc9a3266ff7 100644 --- a/src/Components/Components/src/RenderTree/Renderer.cs +++ b/src/Components/Components/src/RenderTree/Renderer.cs @@ -416,16 +416,24 @@ public virtual Task DispatchEventAsync(ulong eventHandlerId, EventFieldInfo? fie // The event handler might request multiple renders in sequence. Capture them // all in a single batch. _isBatchInProgress = true; + + task = callback.InvokeAsync(eventArgs); if (quiesce) { + // If we are waiting for quiescence, the quiescence task will capture any async exception. + // If the exception is thrown synchronously, we just want it to flow to the callers, and + // not go through the ErrorBoundary. _pendingTasks ??= new(); + AddPendingTask(receiverComponentState, task); } - - task = callback.InvokeAsync(eventArgs); - AddToPendingTasksWithErrorHandling(task, receiverComponentState); } catch (Exception e) { + if (quiesce) + { + // Exception filters are not AoT friendly. + throw; + } HandleExceptionViaErrorBoundary(e, receiverComponentState); return Task.CompletedTask; } From ed05d7e8ed1c7fb9536f472c6a48ebfb515384b6 Mon Sep 17 00:00:00 2001 From: jacalvar Date: Fri, 21 Apr 2023 01:12:21 +0200 Subject: [PATCH 28/28] Addressed feedback from Mackinnon --- .../src/Binding/ModelBindingContext.cs | 2 +- .../src/Rendering/EndpointHtmlRenderer.cs | 2 +- ...soft.AspNetCore.Components.E2ETests.csproj | 38 +++++++++++++------ 3 files changed, 29 insertions(+), 13 deletions(-) diff --git a/src/Components/Components/src/Binding/ModelBindingContext.cs b/src/Components/Components/src/Binding/ModelBindingContext.cs index eca1934eed3b..0a4d0a378386 100644 --- a/src/Components/Components/src/Binding/ModelBindingContext.cs +++ b/src/Components/Components/src/Binding/ModelBindingContext.cs @@ -6,7 +6,7 @@ namespace Microsoft.AspNetCore.Components.Binding; /// /// The binding context associated with a given model binding operation. /// -public class ModelBindingContext +public sealed class ModelBindingContext { internal ModelBindingContext(string name, string bindingContextId) { diff --git a/src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.cs b/src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.cs index 12df342931b2..9a7c2a4d9a12 100644 --- a/src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.cs +++ b/src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.cs @@ -133,7 +133,7 @@ internal Task DispatchCapturedEvent() private static string GenerateComponentPath(ComponentState state) { - // We are generating a path from the root component with te component type names like: + // We are generating a path from the root component with component type names like: // App > Router > RouteView > LayoutView > Index > PartA // App > Router > RouteView > LayoutView > MainLayout > NavigationMenu // To help developers identify when they have multiple forms with the same handler. diff --git a/src/Components/test/E2ETest/Microsoft.AspNetCore.Components.E2ETests.csproj b/src/Components/test/E2ETest/Microsoft.AspNetCore.Components.E2ETests.csproj index 4b38dfe2f957..880e4375bec2 100644 --- a/src/Components/test/E2ETest/Microsoft.AspNetCore.Components.E2ETests.csproj +++ b/src/Components/test/E2ETest/Microsoft.AspNetCore.Components.E2ETests.csproj @@ -5,8 +5,10 @@ EXECUTE_COMPONENTS_E2E_TESTS. At least build the Components E2E tests locally unless SkipTestBuild is set. --> <_BuildAndTest>false - <_BuildAndTest Condition=" '$(ContinuousIntegrationBuild)' == 'true' AND '$(EXECUTE_COMPONENTS_E2E_TESTS)' == 'true' ">true - <_BuildAndTest Condition=" '$(ContinuousIntegrationBuild)' != 'true' AND '$(SkipTestBuild)' != 'true' ">true + <_BuildAndTest + Condition=" '$(ContinuousIntegrationBuild)' == 'true' AND '$(EXECUTE_COMPONENTS_E2E_TESTS)' == 'true' ">true + <_BuildAndTest + Condition=" '$(ContinuousIntegrationBuild)' != 'true' AND '$(SkipTestBuild)' != 'true' ">true true true @@ -61,15 +63,29 @@ - - - - - - - - - + + + + + + + + +