diff --git a/src/Components/Blazor/Blazor/ref/Microsoft.AspNetCore.Blazor.netstandard2.0.cs b/src/Components/Blazor/Blazor/ref/Microsoft.AspNetCore.Blazor.netstandard2.0.cs index b75ca7546e..7c6500f008 100644 --- a/src/Components/Blazor/Blazor/ref/Microsoft.AspNetCore.Blazor.netstandard2.0.cs +++ b/src/Components/Blazor/Blazor/ref/Microsoft.AspNetCore.Blazor.netstandard2.0.cs @@ -59,6 +59,7 @@ namespace Microsoft.AspNetCore.Blazor.Rendering public partial class WebAssemblyRenderer : Microsoft.AspNetCore.Components.Rendering.Renderer { public WebAssemblyRenderer(System.IServiceProvider serviceProvider, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) : base (default(System.IServiceProvider), default(Microsoft.Extensions.Logging.ILoggerFactory)) { } + public override Microsoft.AspNetCore.Components.Dispatcher Dispatcher { get { throw null; } } public System.Threading.Tasks.Task AddComponentAsync(System.Type componentType, string domElementSelector) { throw null; } public System.Threading.Tasks.Task AddComponentAsync(string domElementSelector) where TComponent : Microsoft.AspNetCore.Components.IComponent { throw null; } public override System.Threading.Tasks.Task DispatchEventAsync(int eventHandlerId, Microsoft.AspNetCore.Components.Rendering.EventFieldInfo eventFieldInfo, Microsoft.AspNetCore.Components.UIEventArgs eventArgs) { throw null; } diff --git a/src/Components/Blazor/Blazor/src/Rendering/NullDispatcher.cs b/src/Components/Blazor/Blazor/src/Rendering/NullDispatcher.cs new file mode 100644 index 0000000000..d39897b0a7 --- /dev/null +++ b/src/Components/Blazor/Blazor/src/Rendering/NullDispatcher.cs @@ -0,0 +1,61 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Components; + +namespace Microsoft.AspNetCore.Blazor.Rendering +{ + internal class NullDispatcher : Dispatcher + { + public static readonly Dispatcher Instance = new NullDispatcher(); + + private NullDispatcher() + { + } + + public override bool CheckAccess() => true; + + public override Task InvokeAsync(Action workItem) + { + if (workItem is null) + { + throw new ArgumentNullException(nameof(workItem)); + } + + workItem(); + return Task.CompletedTask; + } + + public override Task InvokeAsync(Func workItem) + { + if (workItem is null) + { + throw new ArgumentNullException(nameof(workItem)); + } + + return workItem(); + } + + public override Task InvokeAsync(Func workItem) + { + if (workItem is null) + { + throw new ArgumentNullException(nameof(workItem)); + } + + return Task.FromResult(workItem()); + } + + public override Task InvokeAsync(Func> workItem) + { + if (workItem is null) + { + throw new ArgumentNullException(nameof(workItem)); + } + + return workItem(); + } + } +} diff --git a/src/Components/Blazor/Blazor/src/Rendering/WebAssemblyRenderer.cs b/src/Components/Blazor/Blazor/src/Rendering/WebAssemblyRenderer.cs index 9bf7251ea1..84e84fb609 100644 --- a/src/Components/Blazor/Blazor/src/Rendering/WebAssemblyRenderer.cs +++ b/src/Components/Blazor/Blazor/src/Rendering/WebAssemblyRenderer.cs @@ -37,6 +37,8 @@ namespace Microsoft.AspNetCore.Blazor.Rendering _webAssemblyRendererId = RendererRegistry.Current.Add(this); } + public override Dispatcher Dispatcher => NullDispatcher.Instance; + /// /// Attaches a new root component to the renderer, /// causing it to be displayed in the specified DOM element. diff --git a/src/Components/Blazor/Build/test/RazorIntegrationTestBase.cs b/src/Components/Blazor/Build/test/RazorIntegrationTestBase.cs index 7e41359917..8eda2e6b35 100644 --- a/src/Components/Blazor/Build/test/RazorIntegrationTestBase.cs +++ b/src/Components/Blazor/Build/test/RazorIntegrationTestBase.cs @@ -385,7 +385,7 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test protected private RenderTreeFrame[] GetRenderTree(TestRenderer renderer, IComponent component) { renderer.AttachComponent(component); - var task = renderer.InvokeAsync(() => component.SetParametersAsync(ParameterCollection.Empty)); + var task = renderer.Dispatcher.InvokeAsync(() => component.SetParametersAsync(ParameterCollection.Empty)); // we will have to change this method if we add a test that does actual async work. Assert.True(task.Status.HasFlag(TaskStatus.RanToCompletion) || task.Status.HasFlag(TaskStatus.Faulted)); if (task.IsFaulted) @@ -442,10 +442,12 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test protected class TestRenderer : Renderer { - public TestRenderer() : base(new TestServiceProvider(), NullLoggerFactory.Instance, CreateDefaultDispatcher()) + public TestRenderer() : base(new TestServiceProvider(), NullLoggerFactory.Instance) { } + public override Dispatcher Dispatcher { get; } = Dispatcher.CreateDefault(); + public RenderTreeFrame[] LatestBatchReferenceFrames { get; private set; } public void AttachComponent(IComponent component) diff --git a/src/Components/Blazor/Build/test/RenderingRazorIntegrationTest.cs b/src/Components/Blazor/Build/test/RenderingRazorIntegrationTest.cs index 034e2b0c76..8b91c024b4 100644 --- a/src/Components/Blazor/Build/test/RenderingRazorIntegrationTest.cs +++ b/src/Components/Blazor/Build/test/RenderingRazorIntegrationTest.cs @@ -332,7 +332,7 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test // Trigger the change event to show it updates the property // // This should always complete synchronously. - var task = renderer.InvokeAsync(() => setter.InvokeAsync(new UIChangeEventArgs { Value = "Modified value", })); + var task = renderer.Dispatcher.InvokeAsync(() => setter.InvokeAsync(new UIChangeEventArgs { Value = "Modified value", })); Assert.Equal(TaskStatus.RanToCompletion, task.Status); await task; @@ -367,7 +367,7 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test // Trigger the change event to show it updates the property // // This should always complete synchronously. - var task = renderer.InvokeAsync(() => setter.InvokeAsync(new UIChangeEventArgs { Value = "Modified value", })); + var task = renderer.Dispatcher.InvokeAsync(() => setter.InvokeAsync(new UIChangeEventArgs { Value = "Modified value", })); Assert.Equal(TaskStatus.RanToCompletion, task.Status); await task; @@ -404,7 +404,7 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test // // This should always complete synchronously. var newDateValue = new DateTime(2018, 3, 5, 4, 5, 6); - var task = renderer.InvokeAsync(() => setter.InvokeAsync(new UIChangeEventArgs { Value = newDateValue.ToString(), })); + var task = renderer.Dispatcher.InvokeAsync(() => setter.InvokeAsync(new UIChangeEventArgs { Value = newDateValue.ToString(), })); Assert.Equal(TaskStatus.RanToCompletion, task.Status); await task; @@ -440,7 +440,7 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test // Trigger the change event to show it updates the property // // This should always complete synchronously. - var task = renderer.InvokeAsync(() => setter.InvokeAsync(new UIChangeEventArgs { Value = new DateTime(2018, 3, 5).ToString(testDateFormat), })); + var task = renderer.Dispatcher.InvokeAsync(() => setter.InvokeAsync(new UIChangeEventArgs { Value = new DateTime(2018, 3, 5).ToString(testDateFormat), })); Assert.Equal(TaskStatus.RanToCompletion, task.Status); await task; @@ -559,7 +559,7 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test // Trigger the change event to show it updates the property // // This should always complete synchronously. - var task = renderer.InvokeAsync(() => setter.InvokeAsync(new UIChangeEventArgs() { Value = false, })); + var task = renderer.Dispatcher.InvokeAsync(() => setter.InvokeAsync(new UIChangeEventArgs() { Value = false, })); Assert.Equal(TaskStatus.RanToCompletion, task.Status); await task; @@ -595,7 +595,7 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test // Trigger the change event to show it updates the property // // This should always complete synchronously. - var task = renderer.InvokeAsync(() => setter.InvokeAsync(new UIChangeEventArgs { Value = MyEnum.SecondValue.ToString(), })); + var task = renderer.Dispatcher.InvokeAsync(() => setter.InvokeAsync(new UIChangeEventArgs { Value = MyEnum.SecondValue.ToString(), })); Assert.Equal(TaskStatus.RanToCompletion, task.Status); await task; diff --git a/src/Components/Components/perf/RenderTreeDiffBuilderBenchmark.cs b/src/Components/Components/perf/RenderTreeDiffBuilderBenchmark.cs index 12560fe9c8..d48b4026ab 100644 --- a/src/Components/Components/perf/RenderTreeDiffBuilderBenchmark.cs +++ b/src/Components/Components/perf/RenderTreeDiffBuilderBenchmark.cs @@ -88,10 +88,12 @@ namespace Microsoft.AspNetCore.Components.Performance private class FakeRenderer : Renderer { public FakeRenderer() - : base(new TestServiceProvider(), NullLoggerFactory.Instance, new RendererSynchronizationContext()) + : base(new TestServiceProvider(), NullLoggerFactory.Instance) { } + public override Dispatcher Dispatcher { get; } = Dispatcher.CreateDefault(); + protected override void HandleException(Exception exception) { throw new NotImplementedException(); diff --git a/src/Components/Components/ref/Microsoft.AspNetCore.Components.netstandard2.0.cs b/src/Components/Components/ref/Microsoft.AspNetCore.Components.netstandard2.0.cs index a7593aa8a7..9405897b79 100644 --- a/src/Components/Components/ref/Microsoft.AspNetCore.Components.netstandard2.0.cs +++ b/src/Components/Components/ref/Microsoft.AspNetCore.Components.netstandard2.0.cs @@ -85,6 +85,17 @@ namespace Microsoft.AspNetCore.Components public Microsoft.AspNetCore.Components.UIDataTransferItem[] Items { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public string[] Types { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } } + public abstract partial class Dispatcher + { + protected Dispatcher() { } + public abstract bool CheckAccess(); + public static Microsoft.AspNetCore.Components.Dispatcher CreateDefault() { throw null; } + public abstract System.Threading.Tasks.Task InvokeAsync(System.Action workItem); + public abstract System.Threading.Tasks.Task InvokeAsync(System.Func workItem); + public abstract System.Threading.Tasks.Task InvokeAsync(System.Func> workItem); + public abstract System.Threading.Tasks.Task InvokeAsync(System.Func workItem); + protected void OnUnhandledException(System.UnhandledExceptionEventArgs e) { } + } [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] public readonly partial struct ElementRef { @@ -303,13 +314,6 @@ namespace Microsoft.AspNetCore.Components { bool IsConnected { get; } } - public partial interface IDispatcher - { - System.Threading.Tasks.Task InvokeAsync(System.Action workItem); - System.Threading.Tasks.Task InvokeAsync(System.Func workItem); - System.Threading.Tasks.Task InvokeAsync(System.Func> workItem); - System.Threading.Tasks.Task InvokeAsync(System.Func workItem); - } public partial interface IHandleAfterRender { System.Threading.Tasks.Task OnAfterRenderAsync(); @@ -406,9 +410,8 @@ namespace Microsoft.AspNetCore.Components { private readonly object _dummy; private readonly int _dummyPrimitive; + public Microsoft.AspNetCore.Components.Dispatcher Dispatcher { get { throw null; } } public bool IsInitialized { get { throw null; } } - public System.Threading.Tasks.Task InvokeAsync(System.Action workItem) { throw null; } - public System.Threading.Tasks.Task InvokeAsync(System.Func workItem) { throw null; } public void Render(Microsoft.AspNetCore.Components.RenderFragment renderFragment) { } } [System.AttributeUsageAttribute(System.AttributeTargets.Class, AllowMultiple=true, Inherited=false)] @@ -656,7 +659,8 @@ namespace Microsoft.AspNetCore.Components.Rendering } public partial class HtmlRenderer : Microsoft.AspNetCore.Components.Rendering.Renderer { - public HtmlRenderer(System.IServiceProvider serviceProvider, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory, Microsoft.AspNetCore.Components.IDispatcher dispatcher, System.Func htmlEncoder) : base (default(System.IServiceProvider), default(Microsoft.Extensions.Logging.ILoggerFactory)) { } + public HtmlRenderer(System.IServiceProvider serviceProvider, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory, System.Func htmlEncoder) : base (default(System.IServiceProvider), default(Microsoft.Extensions.Logging.ILoggerFactory)) { } + public override Microsoft.AspNetCore.Components.Dispatcher Dispatcher { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } protected override void HandleException(System.Exception exception) { } [System.Diagnostics.DebuggerStepThroughAttribute] public System.Threading.Tasks.Task RenderComponentAsync(System.Type componentType, Microsoft.AspNetCore.Components.ParameterCollection initialParameters) { throw null; } @@ -675,18 +679,15 @@ namespace Microsoft.AspNetCore.Components.Rendering public abstract partial class Renderer : System.IDisposable { public Renderer(System.IServiceProvider serviceProvider, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) { } - public Renderer(System.IServiceProvider serviceProvider, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory, Microsoft.AspNetCore.Components.IDispatcher dispatcher) { } + public abstract Microsoft.AspNetCore.Components.Dispatcher Dispatcher { get; } public event System.UnhandledExceptionEventHandler UnhandledSynchronizationException { add { } remove { } } protected internal virtual void AddToRenderQueue(int componentId, Microsoft.AspNetCore.Components.RenderFragment renderFragment) { } protected internal int AssignRootComponentId(Microsoft.AspNetCore.Components.IComponent component) { throw null; } - public static Microsoft.AspNetCore.Components.IDispatcher CreateDefaultDispatcher() { throw null; } public virtual System.Threading.Tasks.Task DispatchEventAsync(int eventHandlerId, Microsoft.AspNetCore.Components.Rendering.EventFieldInfo fieldInfo, Microsoft.AspNetCore.Components.UIEventArgs eventArgs) { throw null; } public void Dispose() { } protected virtual void Dispose(bool disposing) { } protected abstract void HandleException(System.Exception exception); protected Microsoft.AspNetCore.Components.IComponent InstantiateComponent(System.Type componentType) { throw null; } - public virtual System.Threading.Tasks.Task InvokeAsync(System.Action workItem) { throw null; } - public virtual System.Threading.Tasks.Task InvokeAsync(System.Func workItem) { throw null; } protected System.Threading.Tasks.Task RenderRootComponentAsync(int componentId) { throw null; } [System.Diagnostics.DebuggerStepThroughAttribute] protected System.Threading.Tasks.Task RenderRootComponentAsync(int componentId, Microsoft.AspNetCore.Components.ParameterCollection initialParameters) { throw null; } diff --git a/src/Components/Components/src/ComponentBase.cs b/src/Components/Components/src/ComponentBase.cs index 61bc253eba..045216b722 100644 --- a/src/Components/Components/src/ComponentBase.cs +++ b/src/Components/Components/src/ComponentBase.cs @@ -148,7 +148,7 @@ namespace Microsoft.AspNetCore.Components /// /// The work item to execute. protected Task InvokeAsync(Action workItem) - => _renderHandle.InvokeAsync(workItem); + => _renderHandle.Dispatcher.InvokeAsync(workItem); /// /// Executes the supplied work item on the associated renderer's @@ -156,7 +156,7 @@ namespace Microsoft.AspNetCore.Components /// /// The work item to execute. protected Task InvokeAsync(Func workItem) - => _renderHandle.InvokeAsync(workItem); + => _renderHandle.Dispatcher.InvokeAsync(workItem); void IComponent.Configure(RenderHandle renderHandle) { diff --git a/src/Components/Components/src/IDispatcher.cs b/src/Components/Components/src/Dispatcher.cs similarity index 53% rename from src/Components/Components/src/IDispatcher.cs rename to src/Components/Components/src/Dispatcher.cs index 263f5e08ac..c08275af7d 100644 --- a/src/Components/Components/src/IDispatcher.cs +++ b/src/Components/Components/src/Dispatcher.cs @@ -10,34 +10,66 @@ namespace Microsoft.AspNetCore.Components /// /// Dispatches external actions to be executed on the context of a . /// - public interface IDispatcher + public abstract class Dispatcher { + /// + /// Creates a default instance of . + /// + /// A instance. + public static Dispatcher CreateDefault() => new RendererSynchronizationContextDispatcher(); + + /// + /// Provides notifications of unhandled exceptions that occur within the dispatcher. + /// + internal event UnhandledExceptionEventHandler UnhandledException; + + /// + /// Returns a value that determines whether using the dispatcher to invoke a work item is required + /// from the current context. + /// + /// true if invoking is required, otherwise false. + public abstract bool CheckAccess(); + /// /// Invokes the given in the context of the associated . /// /// The action to execute. /// A that will be completed when the action has finished executing. - Task InvokeAsync(Action workItem); + public abstract Task InvokeAsync(Action workItem); /// /// Invokes the given in the context of the associated . /// /// The asynchronous action to execute. /// A that will be completed when the action has finished executing. - Task InvokeAsync(Func workItem); + public abstract Task InvokeAsync(Func workItem); /// /// Invokes the given in the context of the associated . /// /// The function to execute. /// A that will be completed when the function has finished executing. - Task InvokeAsync(Func workItem); + public abstract Task InvokeAsync(Func workItem); /// /// Invokes the given in the context of the associated . /// /// The asynchronous function to execute. /// A that will be completed when the function has finished executing. - Task InvokeAsync(Func> workItem); + public abstract Task InvokeAsync(Func> workItem); + + /// + /// Called to notify listeners of an unhandled exception. + /// + /// The . + protected void OnUnhandledException(UnhandledExceptionEventArgs e) + { + if (e is null) + { + throw new ArgumentNullException(nameof(e)); + } + + UnhandledException?.Invoke(this, e); + } } } diff --git a/src/Components/Components/src/RenderHandle.cs b/src/Components/Components/src/RenderHandle.cs index 29a6f55834..dc383a38f2 100644 --- a/src/Components/Components/src/RenderHandle.cs +++ b/src/Components/Components/src/RenderHandle.cs @@ -2,13 +2,12 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Threading.Tasks; using Microsoft.AspNetCore.Components.Rendering; namespace Microsoft.AspNetCore.Components { /// - /// Allows a component to notify the renderer that it should be rendered. + /// Allows a component to interact with its renderer. /// public readonly struct RenderHandle { @@ -21,9 +20,25 @@ namespace Microsoft.AspNetCore.Components _componentId = componentId; } + /// + /// Gets the associated with the component. + /// + public Dispatcher Dispatcher + { + get + { + if (_renderer == null) + { + ThrowNotInitialized(); + } + + return _renderer.Dispatcher; + } + } + /// /// Gets a value that indicates whether the has been - /// initialised and is ready to use. + /// initialized and is ready to use. /// public bool IsInitialized => _renderer != null; @@ -36,38 +51,15 @@ namespace Microsoft.AspNetCore.Components { if (_renderer == null) { - throw new InvalidOperationException("The render handle is not yet assigned."); + ThrowNotInitialized(); } _renderer.AddToRenderQueue(_componentId, renderFragment); } - /// - /// Executes the supplied work item on the renderer's - /// synchronization context. - /// - /// The work item to execute. - public Task InvokeAsync(Action workItem) + private static void ThrowNotInitialized() { - if (_renderer == null) - { - throw new InvalidOperationException("The render handle is not yet assigned."); - } - return _renderer.InvokeAsync(workItem); - } - - /// - /// Executes the supplied work item on the renderer's - /// synchronization context. - /// - /// The work item to execute. - public Task InvokeAsync(Func workItem) - { - if (_renderer == null) - { - throw new InvalidOperationException("The render handle is not yet assigned."); - } - return _renderer.InvokeAsync(workItem); + throw new InvalidOperationException("The render handle is not yet assigned."); } } } diff --git a/src/Components/Components/src/Rendering/HtmlRenderer.cs b/src/Components/Components/src/Rendering/HtmlRenderer.cs index d844f7497b..13015c0ffe 100644 --- a/src/Components/Components/src/Rendering/HtmlRenderer.cs +++ b/src/Components/Components/src/Rendering/HtmlRenderer.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Runtime.CompilerServices; using System.Runtime.ExceptionServices; using System.Threading.Tasks; using Microsoft.AspNetCore.Components.RenderTree; @@ -28,14 +29,15 @@ namespace Microsoft.AspNetCore.Components.Rendering /// /// The to use to instantiate components. /// The . - /// The to be for invoking user actions into the context. /// A that will HTML encode the given string. - public HtmlRenderer(IServiceProvider serviceProvider, ILoggerFactory loggerFactory, IDispatcher dispatcher, Func htmlEncoder) - : base(serviceProvider, loggerFactory, dispatcher) + public HtmlRenderer(IServiceProvider serviceProvider, ILoggerFactory loggerFactory, Func htmlEncoder) + : base(serviceProvider, loggerFactory) { _htmlEncoder = htmlEncoder; } + public override Dispatcher Dispatcher { get; } = Dispatcher.CreateDefault(); + /// protected override Task UpdateDisplayAsync(in RenderBatch renderBatch) { diff --git a/src/Components/Components/src/Rendering/Renderer.cs b/src/Components/Components/src/Rendering/Renderer.cs index 0ab6ad5b66..cce99aca88 100644 --- a/src/Components/Components/src/Rendering/Renderer.cs +++ b/src/Components/Components/src/Rendering/Renderer.cs @@ -22,7 +22,6 @@ namespace Microsoft.AspNetCore.Components.Rendering private readonly RenderBatchBuilder _batchBuilder = new RenderBatchBuilder(); private readonly Dictionary _eventBindings = new Dictionary(); private readonly Dictionary _eventHandlerIdReplacements = new Dictionary(); - private readonly IDispatcher _dispatcher; private readonly ILogger _logger; private int _nextComponentId = 0; // TODO: change to 'long' when Mono .NET->JS interop supports it @@ -37,19 +36,11 @@ namespace Microsoft.AspNetCore.Components.Rendering { add { - if (!(_dispatcher is RendererSynchronizationContext rendererSynchronizationContext)) - { - return; - } - rendererSynchronizationContext.UnhandledException += value; + Dispatcher.UnhandledException += value; } remove { - if (!(_dispatcher is RendererSynchronizationContext rendererSynchronizationContext)) - { - return; - } - rendererSynchronizationContext.UnhandledException -= value; + Dispatcher.UnhandledException -= value; } } @@ -72,25 +63,13 @@ namespace Microsoft.AspNetCore.Components.Rendering _componentFactory = new ComponentFactory(serviceProvider); _logger = loggerFactory.CreateLogger(); + _componentFactory = new ComponentFactory(serviceProvider); } /// - /// Constructs an instance of . + /// Gets the associated with this . /// - /// The to be used when initializing components. - /// The . - /// The to be for invoking user actions into the context. - public Renderer(IServiceProvider serviceProvider, ILoggerFactory loggerFactory, IDispatcher dispatcher) - : this(serviceProvider, loggerFactory) - { - _dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher)); - } - - /// - /// Creates an that can be used with one or more . - /// - /// The . - public static IDispatcher CreateDefaultDispatcher() => new RendererSynchronizationContext(); + public abstract Dispatcher Dispatcher { get; } /// /// Constructs a new component of the specified type. @@ -272,58 +251,6 @@ namespace Microsoft.AspNetCore.Components.Rendering return GetErrorHandledTask(task); } - /// - /// Executes the supplied work item on the renderer's - /// synchronization context. - /// - /// The work item to execute. - public virtual Task InvokeAsync(Action workItem) - { - // This is for example when we run on a system with a single thread, like WebAssembly. - if (_dispatcher == null) - { - workItem(); - return Task.CompletedTask; - } - - if (SynchronizationContext.Current == _dispatcher) - { - // This is an optimization for when the dispatcher is also a syncronization context, like in the default case. - // No need to dispatch. Avoid deadlock by invoking directly. - workItem(); - return Task.CompletedTask; - } - else - { - return _dispatcher.InvokeAsync(workItem); - } - } - - /// - /// Executes the supplied work item on the renderer's - /// synchronization context. - /// - /// The work item to execute. - public virtual Task InvokeAsync(Func workItem) - { - // This is for example when we run on a system with a single thread, like WebAssembly. - if (_dispatcher == null) - { - return workItem(); - } - - if (SynchronizationContext.Current == _dispatcher) - { - // This is an optimization for when the dispatcher is also a syncronization context, like in the default case. - // No need to dispatch. Avoid deadlock by invoking directly. - return workItem(); - } - else - { - return _dispatcher.InvokeAsync(workItem); - } - } - internal void InstantiateChildComponentOnFrame(ref RenderTreeFrame frame, int parentComponentId) { if (frame.FrameType != RenderTreeFrameType.Component) @@ -448,17 +375,16 @@ namespace Microsoft.AspNetCore.Components.Rendering private void EnsureSynchronizationContext() { - // When the IDispatcher is a synchronization context - // Render operations are not thread-safe, so they need to be serialized. + // Render operations are not thread-safe, so they need to be serialized by the dispatcher. // Plus, any other logic that mutates state accessed during rendering also // needs not to run concurrently with rendering so should be dispatched to // the renderer's sync context. - if (_dispatcher is SynchronizationContext synchronizationContext && SynchronizationContext.Current != synchronizationContext) + if (!Dispatcher.CheckAccess()) { throw new InvalidOperationException( - "The current thread is not associated with the renderer's synchronization context. " + - "Use Invoke() or InvokeAsync() to switch execution to the renderer's synchronization " + - "context when triggering rendering or modifying any state accessed during rendering."); + "The current thread is not associated with the Dispatcher. " + + "Use Invoke() or InvokeAsync() to switch execution to the Dispatcher when " + + "triggering rendering or modifying any state accessed during rendering."); } } diff --git a/src/Components/Components/src/Rendering/RendererSynchronizationContext.cs b/src/Components/Components/src/Rendering/RendererSynchronizationContext.cs index a01a948ddd..176e4d83e4 100644 --- a/src/Components/Components/src/Rendering/RendererSynchronizationContext.cs +++ b/src/Components/Components/src/Rendering/RendererSynchronizationContext.cs @@ -9,7 +9,7 @@ using System.Threading.Tasks; namespace Microsoft.AspNetCore.Components.Rendering { [DebuggerDisplay("{_state,nq}")] - internal class RendererSynchronizationContext : SynchronizationContext, IDispatcher + internal class RendererSynchronizationContext : SynchronizationContext { private static readonly ContextCallback ExecutionContextThunk = (object state) => { diff --git a/src/Components/Components/src/Rendering/RendererSynchronizationContextDispatcher.cs b/src/Components/Components/src/Rendering/RendererSynchronizationContextDispatcher.cs new file mode 100644 index 0000000000..e09488431c --- /dev/null +++ b/src/Components/Components/src/Rendering/RendererSynchronizationContextDispatcher.cs @@ -0,0 +1,66 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.AspNetCore.Components.Rendering +{ + internal class RendererSynchronizationContextDispatcher : Dispatcher + { + private readonly RendererSynchronizationContext _context; + + public RendererSynchronizationContextDispatcher() + { + _context = new RendererSynchronizationContext(); + _context.UnhandledException += (sender, e) => + { + OnUnhandledException(e); + }; + } + + public override bool CheckAccess() => SynchronizationContext.Current == _context; + + public override Task InvokeAsync(Action workItem) + { + if (CheckAccess()) + { + workItem(); + return Task.CompletedTask; + } + + return _context.InvokeAsync(workItem); + } + + public override Task InvokeAsync(Func workItem) + { + if (CheckAccess()) + { + return workItem(); + } + + return _context.InvokeAsync(workItem); + } + + public override Task InvokeAsync(Func workItem) + { + if (CheckAccess()) + { + return Task.FromResult(workItem()); + } + + return _context.InvokeAsync(workItem); + } + + public override Task InvokeAsync(Func> workItem) + { + if (CheckAccess()) + { + return workItem(); + } + + return _context.InvokeAsync(workItem); + } + } +} diff --git a/src/Components/Components/test/CascadingParameterStateTest.cs b/src/Components/Components/test/CascadingParameterStateTest.cs index 5e31b7b0dc..95d42dc24f 100644 --- a/src/Components/Components/test/CascadingParameterStateTest.cs +++ b/src/Components/Components/test/CascadingParameterStateTest.cs @@ -386,7 +386,7 @@ namespace Microsoft.AspNetCore.Components.Test supplierParams.Add("Name", name); } - renderer.InvokeAsync((Action)(() => supplier.SetParametersAsync(ParameterCollection.FromDictionary(supplierParams)))); + renderer.Dispatcher.InvokeAsync((Action)(() => supplier.SetParametersAsync(ParameterCollection.FromDictionary(supplierParams)))); return supplier; } diff --git a/src/Components/Components/test/PageDisplayTest.cs b/src/Components/Components/test/PageDisplayTest.cs index 28a4d22b4a..8e834a7e4f 100644 --- a/src/Components/Components/test/PageDisplayTest.cs +++ b/src/Components/Components/test/PageDisplayTest.cs @@ -27,7 +27,7 @@ namespace Microsoft.AspNetCore.Components.Test public void DisplaysComponentInsideLayout() { // Arrange/Act - _renderer.InvokeAsync(() => _pageDisplayComponent.SetParametersAsync(ParameterCollection.FromDictionary(new Dictionary + _renderer.Dispatcher.InvokeAsync(() => _pageDisplayComponent.SetParametersAsync(ParameterCollection.FromDictionary(new Dictionary { { nameof(PageDisplay.Page), typeof(ComponentWithLayout) } }))); @@ -84,7 +84,7 @@ namespace Microsoft.AspNetCore.Components.Test public void DisplaysComponentInsideNestedLayout() { // Arrange/Act - _renderer.InvokeAsync(() => _pageDisplayComponent.SetParametersAsync(ParameterCollection.FromDictionary(new Dictionary + _renderer.Dispatcher.InvokeAsync(() => _pageDisplayComponent.SetParametersAsync(ParameterCollection.FromDictionary(new Dictionary { { nameof(PageDisplay.Page), typeof(ComponentWithNestedLayout) } }))); @@ -111,13 +111,13 @@ namespace Microsoft.AspNetCore.Components.Test public void CanChangeDisplayedPageWithSameLayout() { // Arrange - _renderer.InvokeAsync(() => _pageDisplayComponent.SetParametersAsync(ParameterCollection.FromDictionary(new Dictionary + _renderer.Dispatcher.InvokeAsync(() => _pageDisplayComponent.SetParametersAsync(ParameterCollection.FromDictionary(new Dictionary { { nameof(PageDisplay.Page), typeof(ComponentWithLayout) } }))); // Act - _renderer.InvokeAsync(() => _pageDisplayComponent.SetParametersAsync(ParameterCollection.FromDictionary(new Dictionary + _renderer.Dispatcher.InvokeAsync(() => _pageDisplayComponent.SetParametersAsync(ParameterCollection.FromDictionary(new Dictionary { { nameof(PageDisplay.Page), typeof(DifferentComponentWithLayout) } }))); @@ -162,13 +162,13 @@ namespace Microsoft.AspNetCore.Components.Test public void CanChangeDisplayedPageWithDifferentLayout() { // Arrange - _renderer.InvokeAsync(() => _pageDisplayComponent.SetParametersAsync(ParameterCollection.FromDictionary(new Dictionary + _renderer.Dispatcher.InvokeAsync(() => _pageDisplayComponent.SetParametersAsync(ParameterCollection.FromDictionary(new Dictionary { { nameof(PageDisplay.Page), typeof(ComponentWithLayout) } }))); // Act - _renderer.InvokeAsync(() => _pageDisplayComponent.SetParametersAsync(ParameterCollection.FromDictionary(new Dictionary + _renderer.Dispatcher.InvokeAsync(() => _pageDisplayComponent.SetParametersAsync(ParameterCollection.FromDictionary(new Dictionary { { nameof(PageDisplay.Page), typeof(ComponentWithNestedLayout) } }))); diff --git a/src/Components/Components/test/RenderTreeBuilderTest.cs b/src/Components/Components/test/RenderTreeBuilderTest.cs index 2060532f8e..6d03c1261c 100644 --- a/src/Components/Components/test/RenderTreeBuilderTest.cs +++ b/src/Components/Components/test/RenderTreeBuilderTest.cs @@ -1795,10 +1795,12 @@ namespace Microsoft.AspNetCore.Components.Test private class TestRenderer : Renderer { - public TestRenderer() : base(new TestServiceProvider(), NullLoggerFactory.Instance, new RendererSynchronizationContext()) + public TestRenderer() : base(new TestServiceProvider(), NullLoggerFactory.Instance) { } + public override Dispatcher Dispatcher { get; } = Dispatcher.CreateDefault(); + protected override void HandleException(Exception exception) => throw new NotImplementedException(); diff --git a/src/Components/Components/test/RenderTreeDiffBuilderTest.cs b/src/Components/Components/test/RenderTreeDiffBuilderTest.cs index b640247e4d..5054d574b4 100644 --- a/src/Components/Components/test/RenderTreeDiffBuilderTest.cs +++ b/src/Components/Components/test/RenderTreeDiffBuilderTest.cs @@ -2208,10 +2208,12 @@ namespace Microsoft.AspNetCore.Components.Test private class FakeRenderer : Renderer { - public FakeRenderer() : base(new TestServiceProvider(), NullLoggerFactory.Instance, new RendererSynchronizationContext()) + public FakeRenderer() : base(new TestServiceProvider(), NullLoggerFactory.Instance) { } + public override Dispatcher Dispatcher { get; } = Dispatcher.CreateDefault(); + protected override void HandleException(Exception exception) => throw new NotImplementedException(); diff --git a/src/Components/Components/test/RendererTest.cs b/src/Components/Components/test/RendererTest.cs index 508236c5ad..5b4baef88c 100644 --- a/src/Components/Components/test/RendererTest.cs +++ b/src/Components/Components/test/RendererTest.cs @@ -186,7 +186,7 @@ namespace Microsoft.AspNetCore.Components.Test // Act var componentId = renderer.AssignRootComponentId(component); - var renderTask = renderer.InvokeAsync(() => renderer.RenderRootComponentAsync(componentId)); + var renderTask = renderer.Dispatcher.InvokeAsync(() => renderer.RenderRootComponentAsync(componentId)); // Assert Assert.False(renderTask.IsCompleted); @@ -239,7 +239,7 @@ namespace Microsoft.AspNetCore.Components.Test // Act/Assert var componentId = renderer.AssignRootComponentId(component); var log = new ConcurrentQueue<(int id, NestedAsyncComponent.EventType @event)>(); - await renderer.InvokeAsync(() => renderer.RenderRootComponentAsync(componentId, ParameterCollection.FromDictionary(new Dictionary + await renderer.Dispatcher.InvokeAsync(() => renderer.RenderRootComponentAsync(componentId, ParameterCollection.FromDictionary(new Dictionary { [EventActionsName] = new Dictionary> { @@ -283,7 +283,7 @@ namespace Microsoft.AspNetCore.Components.Test // Act/Assert var componentId = renderer.AssignRootComponentId(component); var log = new ConcurrentQueue<(int id, NestedAsyncComponent.EventType @event)>(); - await renderer.InvokeAsync(() => renderer.RenderRootComponentAsync(componentId, ParameterCollection.FromDictionary(new Dictionary + await renderer.Dispatcher.InvokeAsync(() => renderer.RenderRootComponentAsync(componentId, ParameterCollection.FromDictionary(new Dictionary { [EventActionsName] = new Dictionary> { @@ -327,7 +327,7 @@ namespace Microsoft.AspNetCore.Components.Test // Act/Assert var componentId = renderer.AssignRootComponentId(component); var log = new ConcurrentQueue<(int id, NestedAsyncComponent.EventType @event)>(); - await renderer.InvokeAsync(() => renderer.RenderRootComponentAsync(componentId, ParameterCollection.FromDictionary(new Dictionary + await renderer.Dispatcher.InvokeAsync(() => renderer.RenderRootComponentAsync(componentId, ParameterCollection.FromDictionary(new Dictionary { [EventActionsName] = new Dictionary> { @@ -371,7 +371,7 @@ namespace Microsoft.AspNetCore.Components.Test // Act/Assert var componentId = renderer.AssignRootComponentId(component); var log = new ConcurrentQueue<(int id, NestedAsyncComponent.EventType @event)>(); - await renderer.InvokeAsync(() => renderer.RenderRootComponentAsync(componentId, ParameterCollection.FromDictionary(new Dictionary + await renderer.Dispatcher.InvokeAsync(() => renderer.RenderRootComponentAsync(componentId, ParameterCollection.FromDictionary(new Dictionary { [EventActionsName] = new Dictionary> { @@ -2017,7 +2017,7 @@ namespace Microsoft.AspNetCore.Components.Test // Act/Assert: If a disposed component requests a render, it's a no-op var renderHandle = ((FakeComponent)childComponent3).RenderHandle; - renderHandle.InvokeAsync(() => renderHandle.Render(builder + renderHandle.Dispatcher.InvokeAsync(() => renderHandle.Render(builder => throw new NotImplementedException("Should not be invoked"))); Assert.Equal(2, renderer.Batches.Count); } @@ -2906,7 +2906,7 @@ namespace Microsoft.AspNetCore.Components.Test var asyncExceptionTcs = new TaskCompletionSource(); taskToAwait = asyncExceptionTcs.Task; - await renderer.InvokeAsync(component.TriggerRender); + await renderer.Dispatcher.InvokeAsync(component.TriggerRender); // Act var exception = new InvalidOperationException(); @@ -3373,10 +3373,12 @@ namespace Microsoft.AspNetCore.Components.Test private class NoOpRenderer : Renderer { - public NoOpRenderer() : base(new TestServiceProvider(), NullLoggerFactory.Instance, new RendererSynchronizationContext()) + public NoOpRenderer() : base(new TestServiceProvider(), NullLoggerFactory.Instance) { } + public override Dispatcher Dispatcher { get; } = Dispatcher.CreateDefault(); + public new int AssignRootComponentId(IComponent component) => base.AssignRootComponentId(component); @@ -3410,7 +3412,7 @@ namespace Microsoft.AspNetCore.Components.Test public void TriggerRender() { - var t = _renderHandle.InvokeAsync(() => _renderHandle.Render(_renderFragment)); + var t = _renderHandle.Dispatcher.InvokeAsync(() => _renderHandle.Render(_renderFragment)); // This should always be run synchronously Assert.True(t.IsCompleted); if (t.IsFaulted) @@ -3660,7 +3662,7 @@ namespace Microsoft.AspNetCore.Components.Test { foreach (var renderHandle in _renderHandles) { - renderHandle.InvokeAsync(() => renderHandle.Render(builder => + renderHandle.Dispatcher.InvokeAsync(() => renderHandle.Render(builder => { builder.AddContent(0, $"Hello from {nameof(MultiRendererComponent)}"); })); @@ -3821,7 +3823,7 @@ namespace Microsoft.AspNetCore.Components.Test return TriggerRenderAsync(); } - public Task TriggerRenderAsync() => _renderHandle.InvokeAsync(() => _renderHandle.Render(RenderFragment)); + public Task TriggerRenderAsync() => _renderHandle.Dispatcher.InvokeAsync(() => _renderHandle.Render(RenderFragment)); } private void AssertStream(int expectedId, (int id, NestedAsyncComponent.EventType @event)[] logStream) diff --git a/src/Components/Components/test/Rendering/HtmlRendererTestBase.cs b/src/Components/Components/test/Rendering/HtmlRendererTestBase.cs index e65159b0c7..84f02e61b0 100644 --- a/src/Components/Components/test/Rendering/HtmlRendererTestBase.cs +++ b/src/Components/Components/test/Rendering/HtmlRendererTestBase.cs @@ -15,7 +15,6 @@ namespace Microsoft.AspNetCore.Components.Rendering public abstract class HtmlRendererTestBase { protected readonly Func _encoder = (string t) => HtmlEncoder.Default.Encode(t); - protected readonly IDispatcher Dispatcher = Renderer.CreateDefaultDispatcher(); protected abstract HtmlRenderer GetHtmlRenderer(IServiceProvider serviceProvider); @@ -33,7 +32,7 @@ namespace Microsoft.AspNetCore.Components.Rendering var htmlRenderer = GetHtmlRenderer(serviceProvider); // Act - var result = GetResult(Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync(ParameterCollection.Empty))); + var result = GetResult(htmlRenderer.Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync(ParameterCollection.Empty))); // Assert Assert.Equal(expectedHtml, result); @@ -43,7 +42,6 @@ namespace Microsoft.AspNetCore.Components.Rendering public void RenderComponentAsync_CanRenderSimpleComponent() { // Arrange - var dispatcher = Renderer.CreateDefaultDispatcher(); var expectedHtml = new[] { "<", "p", ">", "Hello world!", "" }; var serviceProvider = new ServiceCollection().AddSingleton(new RenderFragment(rtb => { @@ -54,7 +52,7 @@ namespace Microsoft.AspNetCore.Components.Rendering var htmlRenderer = GetHtmlRenderer(serviceProvider); // Act - var result = GetResult(Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync(ParameterCollection.Empty))); + var result = GetResult(htmlRenderer.Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync(ParameterCollection.Empty))); // Assert Assert.Equal(expectedHtml, result); @@ -64,7 +62,6 @@ namespace Microsoft.AspNetCore.Components.Rendering public void RenderComponentAsync_HtmlEncodesContent() { // Arrange - var dispatcher = Renderer.CreateDefaultDispatcher(); var expectedHtml = new[] { "<", "p", ">", "<Hello world!>", "" }; var serviceProvider = new ServiceCollection().AddSingleton(new RenderFragment(rtb => { @@ -75,7 +72,7 @@ namespace Microsoft.AspNetCore.Components.Rendering var htmlRenderer = GetHtmlRenderer(serviceProvider); // Act - var result = GetResult(Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync(ParameterCollection.Empty))); + var result = GetResult(htmlRenderer.Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync(ParameterCollection.Empty))); // Assert Assert.Equal(expectedHtml, result); @@ -96,7 +93,7 @@ namespace Microsoft.AspNetCore.Components.Rendering var htmlRenderer = GetHtmlRenderer(serviceProvider); // Act - var result = GetResult(Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync(ParameterCollection.Empty))); + var result = GetResult(htmlRenderer.Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync(ParameterCollection.Empty))); // Assert Assert.Equal(expectedHtml, result); @@ -119,7 +116,7 @@ namespace Microsoft.AspNetCore.Components.Rendering var htmlRenderer = GetHtmlRenderer(serviceProvider); // Act - var result = GetResult(Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync(ParameterCollection.Empty))); + var result = GetResult(htmlRenderer.Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync(ParameterCollection.Empty))); // Assert Assert.Equal(expectedHtml, result); @@ -153,7 +150,7 @@ namespace Microsoft.AspNetCore.Components.Rendering var htmlRenderer = GetHtmlRenderer(serviceProvider); // Act - var result = GetResult(Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync(ParameterCollection.Empty))); + var result = GetResult(htmlRenderer.Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync(ParameterCollection.Empty))); // Assert Assert.Equal(expectedHtml, result); @@ -175,7 +172,7 @@ namespace Microsoft.AspNetCore.Components.Rendering var htmlRenderer = GetHtmlRenderer(serviceProvider); // Act - var result = GetResult(Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync(ParameterCollection.Empty))); + var result = GetResult(htmlRenderer.Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync(ParameterCollection.Empty))); // Assert Assert.Equal(expectedHtml, result); @@ -196,7 +193,7 @@ namespace Microsoft.AspNetCore.Components.Rendering var htmlRenderer = GetHtmlRenderer(serviceProvider); // Act - var result = GetResult(Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync(ParameterCollection.Empty))); + var result = GetResult(htmlRenderer.Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync(ParameterCollection.Empty))); // Assert Assert.Equal(expectedHtml, result); @@ -217,7 +214,7 @@ namespace Microsoft.AspNetCore.Components.Rendering var htmlRenderer = GetHtmlRenderer(serviceProvider); // Act - var result = GetResult(Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync(ParameterCollection.Empty))); + var result = GetResult(htmlRenderer.Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync(ParameterCollection.Empty))); // Assert Assert.Equal(expectedHtml, result); @@ -240,7 +237,7 @@ namespace Microsoft.AspNetCore.Components.Rendering var htmlRenderer = GetHtmlRenderer(serviceProvider); // Act - var result = GetResult(Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync(ParameterCollection.Empty))); + var result = GetResult(htmlRenderer.Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync(ParameterCollection.Empty))); // Assert Assert.Equal(expectedHtml, result); @@ -270,7 +267,7 @@ namespace Microsoft.AspNetCore.Components.Rendering var htmlRenderer = GetHtmlRenderer(serviceProvider); // Act - var result = GetResult(Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync(ParameterCollection.Empty))); + var result = GetResult(htmlRenderer.Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync(ParameterCollection.Empty))); // Assert Assert.Equal(expectedHtml, result); @@ -299,7 +296,7 @@ namespace Microsoft.AspNetCore.Components.Rendering var htmlRenderer = GetHtmlRenderer(serviceProvider); // Act - var result = GetResult(Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync(ParameterCollection.Empty))); + var result = GetResult(htmlRenderer.Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync(ParameterCollection.Empty))); // Assert Assert.Equal(expectedHtml, result); @@ -329,7 +326,7 @@ namespace Microsoft.AspNetCore.Components.Rendering var htmlRenderer = GetHtmlRenderer(serviceProvider); // Act - var result = GetResult(Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync(ParameterCollection.Empty))); + var result = GetResult(htmlRenderer.Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync(ParameterCollection.Empty))); // Assert Assert.Equal(expectedHtml, result); @@ -360,7 +357,7 @@ namespace Microsoft.AspNetCore.Components.Rendering Action change = (UIChangeEventArgs changeArgs) => throw new InvalidOperationException(); // Act - var result = GetResult(Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync( + var result = GetResult(htmlRenderer.Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync( new ParameterCollection(new[] { RenderTreeFrame.Element(0,string.Empty), RenderTreeFrame.Attribute(1,"update",change), @@ -391,7 +388,7 @@ namespace Microsoft.AspNetCore.Components.Rendering var htmlRenderer = GetHtmlRenderer(serviceProvider); // Act - var result = GetResult(Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync(ParameterCollection.Empty))); + var result = GetResult(htmlRenderer.Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync(ParameterCollection.Empty))); // Assert Assert.Equal(expectedHtml, result); @@ -418,7 +415,7 @@ namespace Microsoft.AspNetCore.Components.Rendering var htmlRenderer = GetHtmlRenderer(serviceProvider); // Act - var result = GetResult(Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync(ParameterCollection.Empty))); + var result = GetResult(htmlRenderer.Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync(ParameterCollection.Empty))); // Assert Assert.Equal(expectedHtml, result); @@ -468,7 +465,7 @@ namespace Microsoft.AspNetCore.Components.Rendering var htmlRenderer = GetHtmlRenderer(serviceProvider); // Act - var result = await Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync(ParameterCollection.FromDictionary(new Dictionary + var result = await htmlRenderer.Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync(ParameterCollection.FromDictionary(new Dictionary { ["Value"] = 10 }))); @@ -481,8 +478,8 @@ namespace Microsoft.AspNetCore.Components.Rendering public async Task CanRender_NestedAsyncComponents() { // Arrange - var dispatcher = Renderer.CreateDefaultDispatcher(); - var expectedHtml = new[] { + var expectedHtml = new[] + { "<", "p", ">", "20", "", "<", "p", ">", "80", "" }; @@ -492,7 +489,7 @@ namespace Microsoft.AspNetCore.Components.Rendering var htmlRenderer = GetHtmlRenderer(serviceProvider); // Act - var result = await Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync(ParameterCollection.FromDictionary(new Dictionary + var result = await htmlRenderer.Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync(ParameterCollection.FromDictionary(new Dictionary { ["Nested"] = false, ["Value"] = 10 diff --git a/src/Components/Components/test/Rendering/HtmlRendererTests.cs b/src/Components/Components/test/Rendering/HtmlRendererTests.cs index 072ee563d5..c02e835751 100644 --- a/src/Components/Components/test/Rendering/HtmlRendererTests.cs +++ b/src/Components/Components/test/Rendering/HtmlRendererTests.cs @@ -10,7 +10,7 @@ namespace Microsoft.AspNetCore.Components.Rendering { protected override HtmlRenderer GetHtmlRenderer(IServiceProvider serviceProvider) { - return new HtmlRenderer(serviceProvider, NullLoggerFactory.Instance, Dispatcher, _encoder); + return new HtmlRenderer(serviceProvider, NullLoggerFactory.Instance, _encoder); } } } diff --git a/src/Components/Server/src/Circuits/CircuitHost.cs b/src/Components/Server/src/Circuits/CircuitHost.cs index 5ee9520582..65ea20fc6b 100644 --- a/src/Components/Server/src/Circuits/CircuitHost.cs +++ b/src/Components/Server/src/Circuits/CircuitHost.cs @@ -56,14 +56,12 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits RendererRegistry rendererRegistry, RemoteRenderer renderer, IList descriptors, - IDispatcher dispatcher, RemoteJSRuntime jsRuntime, CircuitHandler[] circuitHandlers, ILogger logger) { CircuitId = circuitId; _scope = scope ?? throw new ArgumentNullException(nameof(scope)); - Dispatcher = dispatcher; Client = client; RendererRegistry = rendererRegistry ?? throw new ArgumentNullException(nameof(rendererRegistry)); Descriptors = descriptors ?? throw new ArgumentNullException(nameof(descriptors)); @@ -96,11 +94,9 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits public IServiceProvider Services { get; } - public IDispatcher Dispatcher { get; } - public Task PrerenderComponentAsync(Type componentType, ParameterCollection parameters) { - return Dispatcher.InvokeAsync(async () => + return Renderer.Dispatcher.InvokeAsync(async () => { var result = await Renderer.RenderComponentAsync(componentType, parameters); @@ -137,12 +133,12 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits // Dispatch any buffered renders we accumulated during a disconnect. // Note that while the rendering is async, we cannot await it here. The Task returned by ProcessBufferedRenderBatches relies on // OnRenderCompleted to be invoked to complete, and SignalR does not allow concurrent hub method invocations. - var _ = Renderer.InvokeAsync(() => Renderer.ProcessBufferedRenderBatches()); + var _ = Renderer.Dispatcher.InvokeAsync(() => Renderer.ProcessBufferedRenderBatches()); } public async Task InitializeAsync(CancellationToken cancellationToken) { - await Renderer.InvokeAsync(async () => + await Renderer.Dispatcher.InvokeAsync(async () => { try { @@ -180,7 +176,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits try { - await Renderer.InvokeAsync(() => + await Renderer.Dispatcher.InvokeAsync(() => { SetCurrentCircuitHost(this); DotNetDispatcher.BeginInvoke(callId, assemblyName, methodIdentifier, dotNetObjectId, argsJson); @@ -291,7 +287,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits { Log.DisposingCircuit(_logger, CircuitId); - await Renderer.InvokeAsync(async () => + await Renderer.Dispatcher.InvokeAsync(async () => { try { diff --git a/src/Components/Server/src/Circuits/CircuitRegistry.cs b/src/Components/Server/src/Circuits/CircuitRegistry.cs index d1bac2c6d7..2c57ce63f5 100644 --- a/src/Components/Server/src/Circuits/CircuitRegistry.cs +++ b/src/Components/Server/src/Circuits/CircuitRegistry.cs @@ -90,7 +90,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits { if (DisconnectCore(circuitHost, connectionId)) { - circuitHandlerTask = circuitHost.Dispatcher.InvokeAsync(() => circuitHost.OnConnectionDownAsync(default)); + circuitHandlerTask = circuitHost.Renderer.Dispatcher.InvokeAsync(() => circuitHost.OnConnectionDownAsync(default)); } else { @@ -189,7 +189,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits // Dispatch the circuit handlers inside the sync context to ensure the order of execution. CircuitHost executes circuit handlers inside of // - circuitHandlerTask = circuitHost.Dispatcher.InvokeAsync(async () => + circuitHandlerTask = circuitHost.Renderer.Dispatcher.InvokeAsync(async () => { if (previouslyConnected) { diff --git a/src/Components/Server/src/Circuits/DefaultCircuitFactory.cs b/src/Components/Server/src/Circuits/DefaultCircuitFactory.cs index 343a191326..c08db124bc 100644 --- a/src/Components/Server/src/Circuits/DefaultCircuitFactory.cs +++ b/src/Components/Server/src/Circuits/DefaultCircuitFactory.cs @@ -71,14 +71,12 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits } var rendererRegistry = new RendererRegistry(); - var dispatcher = Renderer.CreateDefaultDispatcher(); var renderer = new RemoteRenderer( scope.ServiceProvider, _loggerFactory, rendererRegistry, jsRuntime, client, - dispatcher, encoder, _loggerFactory.CreateLogger()); @@ -93,7 +91,6 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits rendererRegistry, renderer, components, - dispatcher, jsRuntime, circuitHandlers, _loggerFactory.CreateLogger()); diff --git a/src/Components/Server/src/Circuits/RemoteRenderer.cs b/src/Components/Server/src/Circuits/RemoteRenderer.cs index 6003340eec..8065ca4afd 100644 --- a/src/Components/Server/src/Circuits/RemoteRenderer.cs +++ b/src/Components/Server/src/Circuits/RemoteRenderer.cs @@ -41,10 +41,9 @@ namespace Microsoft.AspNetCore.Components.Web.Rendering RendererRegistry rendererRegistry, IJSRuntime jsRuntime, CircuitClientProxy client, - IDispatcher dispatcher, HtmlEncoder encoder, ILogger logger) - : base(serviceProvider, loggerFactory, dispatcher, encoder.Encode) + : base(serviceProvider, loggerFactory, encoder.Encode) { _rendererRegistry = rendererRegistry; _jsRuntime = jsRuntime; @@ -56,6 +55,8 @@ namespace Microsoft.AspNetCore.Components.Web.Rendering internal ConcurrentQueue PendingRenderBatches = new ConcurrentQueue(); + public override Dispatcher Dispatcher { get; } = Dispatcher.CreateDefault(); + public int Id { get; } /// diff --git a/src/Components/Server/test/Circuits/CircuitHostTest.cs b/src/Components/Server/test/Circuits/CircuitHostTest.cs index 36096524f6..e5606f179a 100644 --- a/src/Components/Server/test/Circuits/CircuitHostTest.cs +++ b/src/Components/Server/test/Circuits/CircuitHostTest.cs @@ -10,7 +10,6 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Components.Web; using Microsoft.AspNetCore.Components.Web.Rendering; -using Microsoft.AspNetCore.Components.Rendering; using Microsoft.AspNetCore.SignalR; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging.Abstractions; @@ -27,7 +26,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits { // Arrange var serviceScope = new Mock(); - var remoteRenderer = GetRemoteRenderer(Renderer.CreateDefaultDispatcher()); + var remoteRenderer = GetRemoteRenderer(); var circuitHost = TestCircuitHost.Create( Guid.NewGuid().ToString(), serviceScope.Object, @@ -50,7 +49,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits handler .Setup(h => h.OnCircuitClosedAsync(It.IsAny(), It.IsAny())) .Throws(); - var remoteRenderer = GetRemoteRenderer(Renderer.CreateDefaultDispatcher()); + var remoteRenderer = GetRemoteRenderer(); var circuitHost = TestCircuitHost.Create( Guid.NewGuid().ToString(), serviceScope.Object, @@ -77,13 +76,13 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits { // Arrange var serviceScope = new Mock(); - var remoteRenderer = GetRemoteRenderer(Renderer.CreateDefaultDispatcher()); + var remoteRenderer = GetRemoteRenderer(); var circuitHost = TestCircuitHost.Create( Guid.NewGuid().ToString(), serviceScope.Object, remoteRenderer); - var component = new DispatcherComponent(circuitHost.Dispatcher); + var component = new DispatcherComponent(circuitHost.Renderer.Dispatcher); circuitHost.Renderer.AssignRootComponentId(component); var original = SynchronizationContext.Current; SynchronizationContext.SetSynchronizationContext(null); @@ -229,20 +228,19 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits handler2.VerifyAll(); } - private static TestRemoteRenderer GetRemoteRenderer(IDispatcher dispatcher) + private static TestRemoteRenderer GetRemoteRenderer() { return new TestRemoteRenderer( Mock.Of(), new RendererRegistry(), - dispatcher, Mock.Of(), Mock.Of()); } private class TestRemoteRenderer : RemoteRenderer { - public TestRemoteRenderer(IServiceProvider serviceProvider, RendererRegistry rendererRegistry, IDispatcher dispatcher, IJSRuntime jsRuntime, IClientProxy client) - : base(serviceProvider, NullLoggerFactory.Instance, rendererRegistry, jsRuntime, new CircuitClientProxy(client, "connection"), dispatcher, HtmlEncoder.Default, NullLogger.Instance) + public TestRemoteRenderer(IServiceProvider serviceProvider, RendererRegistry rendererRegistry, IJSRuntime jsRuntime, IClientProxy client) + : base(serviceProvider, NullLoggerFactory.Instance, rendererRegistry, jsRuntime, new CircuitClientProxy(client, "connection"), HtmlEncoder.Default, NullLogger.Instance) { } @@ -257,12 +255,12 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits private class DispatcherComponent : ComponentBase, IDisposable { - public DispatcherComponent(IDispatcher dispatcher) + public DispatcherComponent(Dispatcher dispatcher) { Dispatcher = dispatcher; } - public IDispatcher Dispatcher { get; } + public Dispatcher Dispatcher { get; } public bool Called { get; private set; } public void Dispose() diff --git a/src/Components/Server/test/Circuits/RemoteRendererTest.cs b/src/Components/Server/test/Circuits/RemoteRendererTest.cs index 966419f420..a940902735 100644 --- a/src/Components/Server/test/Circuits/RemoteRendererTest.cs +++ b/src/Components/Server/test/Circuits/RemoteRendererTest.cs @@ -268,7 +268,6 @@ namespace Microsoft.AspNetCore.Components.Web.Rendering new RendererRegistry(), jsRuntime.Object, circuitClientProxy, - Dispatcher, HtmlEncoder.Default, NullLogger.Instance); } @@ -313,7 +312,7 @@ namespace Microsoft.AspNetCore.Components.Web.Rendering public void TriggerRender() { - var task = _renderHandle.InvokeAsync(() => _renderHandle.Render(_renderFragment)); + var task = _renderHandle.Dispatcher.InvokeAsync(() => _renderHandle.Render(_renderFragment)); Assert.True(task.IsCompletedSuccessfully); } } @@ -342,7 +341,7 @@ namespace Microsoft.AspNetCore.Components.Web.Rendering public void TriggerRender() { - var task = _renderHandle.InvokeAsync(() => _renderHandle.Render(Content)); + var task = _renderHandle.Dispatcher.InvokeAsync(() => _renderHandle.Render(Content)); Assert.True(task.IsCompletedSuccessfully); } } diff --git a/src/Components/Server/test/Circuits/RenderBatchWriterTest.cs b/src/Components/Server/test/Circuits/RenderBatchWriterTest.cs index 4c187b02fa..fba9b4de0d 100644 --- a/src/Components/Server/test/Circuits/RenderBatchWriterTest.cs +++ b/src/Components/Server/test/Circuits/RenderBatchWriterTest.cs @@ -55,7 +55,7 @@ namespace Microsoft.AspNetCore.Components.Server // Assert AssertBinaryContents(bytes, /* startIndex */ 0, 0, // Length of UpdatedComponents - 0, // Length of ReferenceFrames + 0, // Length of ReferenceFrames 3, 123, int.MaxValue, int.MinValue, // DisposedComponentIds as length-prefixed array 0, // Length of DisposedEventHandlerIds @@ -122,7 +122,7 @@ namespace Microsoft.AspNetCore.Components.Server 2, // Length of UpdatedComponents 0, // Index of UpdatedComponents[0] 8, // Index of UpdatedComponents[1] - + 0, // Length of ReferenceFrames 0, // Length of DisposedComponentIds 0, // Length of DisposedEventHandlerIds @@ -159,7 +159,7 @@ namespace Microsoft.AspNetCore.Components.Server var bytes = Serialize(new RenderBatch( new ArrayRange(new[] { - new RenderTreeDiff(123, editsSegment) + new RenderTreeDiff(123, editsSegment) }, 1), default, default, @@ -360,7 +360,7 @@ namespace Microsoft.AspNetCore.Components.Server shift += 7; numBytesRead++; } - + return result; } @@ -376,10 +376,12 @@ namespace Microsoft.AspNetCore.Components.Server class FakeRenderer : Renderer { public FakeRenderer() - : base(new ServiceCollection().BuildServiceProvider(), NullLoggerFactory.Instance, new RendererSynchronizationContext()) + : base(new ServiceCollection().BuildServiceProvider(), NullLoggerFactory.Instance) { } + public override Dispatcher Dispatcher { get; } = Dispatcher.CreateDefault(); + protected override void HandleException(Exception exception) { throw new NotImplementedException(); diff --git a/src/Components/Server/test/Circuits/TestCircuitHost.cs b/src/Components/Server/test/Circuits/TestCircuitHost.cs index 89ab0c2679..326675cfe0 100644 --- a/src/Components/Server/test/Circuits/TestCircuitHost.cs +++ b/src/Components/Server/test/Circuits/TestCircuitHost.cs @@ -7,20 +7,19 @@ using System.Runtime.ExceptionServices; using System.Text.Encodings.Web; using Microsoft.AspNetCore.Components.Web; using Microsoft.AspNetCore.Components.Web.Rendering; -using Microsoft.AspNetCore.Components.Rendering; using Microsoft.AspNetCore.SignalR; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; -using Moq; using Microsoft.Extensions.Options; +using Moq; namespace Microsoft.AspNetCore.Components.Server.Circuits { internal class TestCircuitHost : CircuitHost { - private TestCircuitHost(string circuitId, IServiceScope scope, CircuitClientProxy client, RendererRegistry rendererRegistry, RemoteRenderer renderer, IList descriptors, IDispatcher dispatcher, RemoteJSRuntime jsRuntime, CircuitHandler[] circuitHandlers, ILogger logger) - : base(circuitId, scope, client, rendererRegistry, renderer, descriptors, dispatcher, jsRuntime, circuitHandlers, logger) + private TestCircuitHost(string circuitId, IServiceScope scope, CircuitClientProxy client, RendererRegistry rendererRegistry, RemoteRenderer renderer, IList descriptors, RemoteJSRuntime jsRuntime, CircuitHandler[] circuitHandlers, ILogger logger) + : base(circuitId, scope, client, rendererRegistry, renderer, descriptors, jsRuntime, circuitHandlers, logger) { } @@ -40,7 +39,6 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits clientProxy = clientProxy ?? new CircuitClientProxy(Mock.Of(), Guid.NewGuid().ToString()); var renderRegistry = new RendererRegistry(); var jsRuntime = new RemoteJSRuntime(Options.Create(new CircuitOptions())); - var dispatcher = Rendering.Renderer.CreateDefaultDispatcher(); if (remoteRenderer == null) { @@ -50,7 +48,6 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits new RendererRegistry(), jsRuntime, clientProxy, - dispatcher, HtmlEncoder.Default, NullLogger.Instance); } @@ -63,7 +60,6 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits renderRegistry, remoteRenderer, new List(), - dispatcher, jsRuntime, handlers, NullLogger.Instance); diff --git a/src/Components/Shared/test/AutoRenderComponent.cs b/src/Components/Shared/test/AutoRenderComponent.cs index b64cbc0c1f..4c426d0c2a 100644 --- a/src/Components/Shared/test/AutoRenderComponent.cs +++ b/src/Components/Shared/test/AutoRenderComponent.cs @@ -4,7 +4,6 @@ using System; using System.Runtime.ExceptionServices; using System.Threading.Tasks; -using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.RenderTree; using Xunit; @@ -29,7 +28,7 @@ namespace Microsoft.AspNetCore.Components.Test.Helpers // We do it this way so that we don't have to be doing renderer.Invoke on each and every test. public void TriggerRender() { - var t = _renderHandle.InvokeAsync(() => _renderHandle.Render(BuildRenderTree)); + var t = _renderHandle.Dispatcher.InvokeAsync(() => _renderHandle.Render(BuildRenderTree)); // This should always be run synchronously Assert.True(t.IsCompleted); if (t.IsFaulted) diff --git a/src/Components/Shared/test/TestRenderer.cs b/src/Components/Shared/test/TestRenderer.cs index d2832e7473..85828b0b23 100644 --- a/src/Components/Shared/test/TestRenderer.cs +++ b/src/Components/Shared/test/TestRenderer.cs @@ -18,14 +18,18 @@ namespace Microsoft.AspNetCore.Components.Test.Helpers { } - public TestRenderer(IDispatcher dispatcher) : base(new TestServiceProvider(), NullLoggerFactory.Instance, dispatcher) + public TestRenderer(Dispatcher dispatcher) : base(new TestServiceProvider(), NullLoggerFactory.Instance) { + Dispatcher = dispatcher; } - public TestRenderer(IServiceProvider serviceProvider) : base(serviceProvider, NullLoggerFactory.Instance, new RendererSynchronizationContext()) + public TestRenderer(IServiceProvider serviceProvider) : base(serviceProvider, NullLoggerFactory.Instance) { + Dispatcher = Dispatcher.CreateDefault(); } + public override Dispatcher Dispatcher { get; } + public Action OnExceptionHandled { get; set; } public Action OnUpdateDisplay { get; set; } @@ -46,21 +50,21 @@ namespace Microsoft.AspNetCore.Components.Test.Helpers public void RenderRootComponent(int componentId, ParameterCollection? parameters = default) { - var task = InvokeAsync(() => base.RenderRootComponentAsync(componentId, parameters ?? ParameterCollection.Empty)); + var task = Dispatcher.InvokeAsync(() => base.RenderRootComponentAsync(componentId, parameters ?? ParameterCollection.Empty)); UnwrapTask(task); } public new Task RenderRootComponentAsync(int componentId) - => InvokeAsync(() => base.RenderRootComponentAsync(componentId)); + => Dispatcher.InvokeAsync(() => base.RenderRootComponentAsync(componentId)); public new Task RenderRootComponentAsync(int componentId, ParameterCollection parameters) - => InvokeAsync(() => base.RenderRootComponentAsync(componentId, parameters)); + => Dispatcher.InvokeAsync(() => base.RenderRootComponentAsync(componentId, parameters)); public Task DispatchEventAsync(int eventHandlerId, UIEventArgs args) - => InvokeAsync(() => base.DispatchEventAsync(eventHandlerId, null, args)); + => Dispatcher.InvokeAsync(() => base.DispatchEventAsync(eventHandlerId, null, args)); public new Task DispatchEventAsync(int eventHandlerId, EventFieldInfo eventFieldInfo, UIEventArgs args) - => InvokeAsync(() => base.DispatchEventAsync(eventHandlerId, eventFieldInfo, args)); + => Dispatcher.InvokeAsync(() => base.DispatchEventAsync(eventHandlerId, eventFieldInfo, args)); private static Task UnwrapTask(Task task) { diff --git a/src/Components/test/E2ETest/ServerExecutionTests/ServerComponentRenderingTest.cs b/src/Components/test/E2ETest/ServerExecutionTests/ServerComponentRenderingTest.cs index 2fefa59d02..abf1ef5a15 100644 --- a/src/Components/test/E2ETest/ServerExecutionTests/ServerComponentRenderingTest.cs +++ b/src/Components/test/E2ETest/ServerExecutionTests/ServerComponentRenderingTest.cs @@ -33,7 +33,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests appElement.FindElement(By.Id("run-without-dispatch")).Click(); Browser.Contains( - $"{typeof(InvalidOperationException).FullName}: The current thread is not associated with the renderer's synchronization context", + $"{typeof(InvalidOperationException).FullName}: The current thread is not associated with the Dispatcher. Use Invoke() or InvokeAsync() to switch execution to the Dispatcher when triggering rendering or modifying any state accessed during rendering.", () => result.Text); } } diff --git a/src/Components/test/Ignitor.Test/RenderBatchReaderTest.cs b/src/Components/test/Ignitor.Test/RenderBatchReaderTest.cs index a4f0dafa56..703318ea68 100644 --- a/src/Components/test/Ignitor.Test/RenderBatchReaderTest.cs +++ b/src/Components/test/Ignitor.Test/RenderBatchReaderTest.cs @@ -335,10 +335,12 @@ namespace Ignitor class FakeRenderer : Renderer { public FakeRenderer() - : base(new ServiceCollection().BuildServiceProvider(), NullLoggerFactory.Instance, new RendererSynchronizationContext()) + : base(new ServiceCollection().BuildServiceProvider(), NullLoggerFactory.Instance) { } + public override Dispatcher Dispatcher { get; } = Dispatcher.CreateDefault(); + protected override void HandleException(Exception exception) { throw new NotImplementedException(); diff --git a/src/Components/test/testassets/Ignitor/RenderBatchReader.cs b/src/Components/test/testassets/Ignitor/RenderBatchReader.cs index bfe28e74cb..7286b98316 100644 --- a/src/Components/test/testassets/Ignitor/RenderBatchReader.cs +++ b/src/Components/test/testassets/Ignitor/RenderBatchReader.cs @@ -289,10 +289,12 @@ namespace Ignitor public class FakeRenderer : Renderer { public FakeRenderer() - : base(new ServiceCollection().BuildServiceProvider(), NullLoggerFactory.Instance, new RendererSynchronizationContext()) + : base(new ServiceCollection().BuildServiceProvider(), NullLoggerFactory.Instance) { } + public override Dispatcher Dispatcher { get; } = Dispatcher.CreateDefault(); + protected override void HandleException(Exception exception) { throw new NotImplementedException(); diff --git a/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/StaticComponentRenderer.cs b/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/StaticComponentRenderer.cs index 2b415d05fc..9fa07a3a5c 100644 --- a/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/StaticComponentRenderer.cs +++ b/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/StaticComponentRenderer.cs @@ -30,16 +30,14 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponents HttpContext httpContext, Type componentType) { - var dispatcher = Renderer.CreateDefaultDispatcher(); - InitializeUriHelper(httpContext); var loggerFactory = (ILoggerFactory)httpContext.RequestServices.GetService(typeof (ILoggerFactory)); - using (var htmlRenderer = new HtmlRenderer(httpContext.RequestServices, loggerFactory, dispatcher, _encoder.Encode)) + using (var htmlRenderer = new HtmlRenderer(httpContext.RequestServices, loggerFactory, _encoder.Encode)) { ComponentRenderedText result = default; try { - result = await dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync( + result = await htmlRenderer.Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync( componentType, parameters)); }