Blazor API Review: Design concept for Dispatcher (#11930)
* Design concept for Dispatcher Part of: #11610 This change brings forward the Dispatcher as a more primary and more expandable concept. - Dispatcher shows up in more places - Dispatcher is an abstract class for horizontal scalability - Dispatcher has parallels with S.Windows.Threading.Dispatcher where possible Looking for feedback on this approach. I feel pretty strongly that making this an abstract class is the right choice, I want to see opinions on how much to push it into people's faces. * WIP * PR feedback
This commit is contained in:
parent
62f00a77e6
commit
a27b9fc335
|
|
@ -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<TComponent>(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; }
|
||||
|
|
|
|||
|
|
@ -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<Task> workItem)
|
||||
{
|
||||
if (workItem is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(workItem));
|
||||
}
|
||||
|
||||
return workItem();
|
||||
}
|
||||
|
||||
public override Task<TResult> InvokeAsync<TResult>(Func<TResult> workItem)
|
||||
{
|
||||
if (workItem is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(workItem));
|
||||
}
|
||||
|
||||
return Task.FromResult(workItem());
|
||||
}
|
||||
|
||||
public override Task<TResult> InvokeAsync<TResult>(Func<Task<TResult>> workItem)
|
||||
{
|
||||
if (workItem is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(workItem));
|
||||
}
|
||||
|
||||
return workItem();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -37,6 +37,8 @@ namespace Microsoft.AspNetCore.Blazor.Rendering
|
|||
_webAssemblyRendererId = RendererRegistry.Current.Add(this);
|
||||
}
|
||||
|
||||
public override Dispatcher Dispatcher => NullDispatcher.Instance;
|
||||
|
||||
/// <summary>
|
||||
/// Attaches a new root component to the renderer,
|
||||
/// causing it to be displayed in the specified DOM element.
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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<System.Threading.Tasks.Task> workItem);
|
||||
public abstract System.Threading.Tasks.Task<TResult> InvokeAsync<TResult>(System.Func<System.Threading.Tasks.Task<TResult>> workItem);
|
||||
public abstract System.Threading.Tasks.Task<TResult> InvokeAsync<TResult>(System.Func<TResult> 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<System.Threading.Tasks.Task> workItem);
|
||||
System.Threading.Tasks.Task<TResult> InvokeAsync<TResult>(System.Func<System.Threading.Tasks.Task<TResult>> workItem);
|
||||
System.Threading.Tasks.Task<TResult> InvokeAsync<TResult>(System.Func<TResult> 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<System.Threading.Tasks.Task> 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<string, string> htmlEncoder) : base (default(System.IServiceProvider), default(Microsoft.Extensions.Logging.ILoggerFactory)) { }
|
||||
public HtmlRenderer(System.IServiceProvider serviceProvider, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory, System.Func<string, string> 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<Microsoft.AspNetCore.Components.Rendering.ComponentRenderedText> 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<System.Threading.Tasks.Task> 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; }
|
||||
|
|
|
|||
|
|
@ -148,7 +148,7 @@ namespace Microsoft.AspNetCore.Components
|
|||
/// </summary>
|
||||
/// <param name="workItem">The work item to execute.</param>
|
||||
protected Task InvokeAsync(Action workItem)
|
||||
=> _renderHandle.InvokeAsync(workItem);
|
||||
=> _renderHandle.Dispatcher.InvokeAsync(workItem);
|
||||
|
||||
/// <summary>
|
||||
/// Executes the supplied work item on the associated renderer's
|
||||
|
|
@ -156,7 +156,7 @@ namespace Microsoft.AspNetCore.Components
|
|||
/// </summary>
|
||||
/// <param name="workItem">The work item to execute.</param>
|
||||
protected Task InvokeAsync(Func<Task> workItem)
|
||||
=> _renderHandle.InvokeAsync(workItem);
|
||||
=> _renderHandle.Dispatcher.InvokeAsync(workItem);
|
||||
|
||||
void IComponent.Configure(RenderHandle renderHandle)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -10,34 +10,66 @@ namespace Microsoft.AspNetCore.Components
|
|||
/// <summary>
|
||||
/// Dispatches external actions to be executed on the context of a <see cref="Renderer"/>.
|
||||
/// </summary>
|
||||
public interface IDispatcher
|
||||
public abstract class Dispatcher
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a default instance of <see cref="Dispatcher"/>.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="Dispatcher"/> instance.</returns>
|
||||
public static Dispatcher CreateDefault() => new RendererSynchronizationContextDispatcher();
|
||||
|
||||
/// <summary>
|
||||
/// Provides notifications of unhandled exceptions that occur within the dispatcher.
|
||||
/// </summary>
|
||||
internal event UnhandledExceptionEventHandler UnhandledException;
|
||||
|
||||
/// <summary>
|
||||
/// Returns a value that determines whether using the dispatcher to invoke a work item is required
|
||||
/// from the current context.
|
||||
/// </summary>
|
||||
/// <returns><c>true</c> if invoking is required, otherwise <c>false</c>.</returns>
|
||||
public abstract bool CheckAccess();
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the given <see cref="Action"/> in the context of the associated <see cref="Renderer"/>.
|
||||
/// </summary>
|
||||
/// <param name="workItem">The action to execute.</param>
|
||||
/// <returns>A <see cref="Task"/> that will be completed when the action has finished executing.</returns>
|
||||
Task InvokeAsync(Action workItem);
|
||||
public abstract Task InvokeAsync(Action workItem);
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the given <see cref="Func{TResult}"/> in the context of the associated <see cref="Renderer"/>.
|
||||
/// </summary>
|
||||
/// <param name="workItem">The asynchronous action to execute.</param>
|
||||
/// <returns>A <see cref="Task"/> that will be completed when the action has finished executing.</returns>
|
||||
Task InvokeAsync(Func<Task> workItem);
|
||||
public abstract Task InvokeAsync(Func<Task> workItem);
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the given <see cref="Func{TResult}"/> in the context of the associated <see cref="Renderer"/>.
|
||||
/// </summary>
|
||||
/// <param name="workItem">The function to execute.</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> that will be completed when the function has finished executing.</returns>
|
||||
Task<TResult> InvokeAsync<TResult>(Func<TResult> workItem);
|
||||
public abstract Task<TResult> InvokeAsync<TResult>(Func<TResult> workItem);
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the given <see cref="Func{TResult}"/> in the context of the associated <see cref="Renderer"/>.
|
||||
/// </summary>
|
||||
/// <param name="workItem">The asynchronous function to execute.</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> that will be completed when the function has finished executing.</returns>
|
||||
Task<TResult> InvokeAsync<TResult>(Func<Task<TResult>> workItem);
|
||||
public abstract Task<TResult> InvokeAsync<TResult>(Func<Task<TResult>> workItem);
|
||||
|
||||
/// <summary>
|
||||
/// Called to notify listeners of an unhandled exception.
|
||||
/// </summary>
|
||||
/// <param name="e">The <see cref="UnhandledExceptionEventArgs"/>.</param>
|
||||
protected void OnUnhandledException(UnhandledExceptionEventArgs e)
|
||||
{
|
||||
if (e is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(e));
|
||||
}
|
||||
|
||||
UnhandledException?.Invoke(this, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Allows a component to notify the renderer that it should be rendered.
|
||||
/// Allows a component to interact with its renderer.
|
||||
/// </summary>
|
||||
public readonly struct RenderHandle
|
||||
{
|
||||
|
|
@ -21,9 +20,25 @@ namespace Microsoft.AspNetCore.Components
|
|||
_componentId = componentId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="Microsoft.AspNetCore.Components.Dispatcher" /> associated with the component.
|
||||
/// </summary>
|
||||
public Dispatcher Dispatcher
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_renderer == null)
|
||||
{
|
||||
ThrowNotInitialized();
|
||||
}
|
||||
|
||||
return _renderer.Dispatcher;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value that indicates whether the <see cref="RenderHandle"/> has been
|
||||
/// initialised and is ready to use.
|
||||
/// initialized and is ready to use.
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes the supplied work item on the renderer's
|
||||
/// synchronization context.
|
||||
/// </summary>
|
||||
/// <param name="workItem">The work item to execute.</param>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes the supplied work item on the renderer's
|
||||
/// synchronization context.
|
||||
/// </summary>
|
||||
/// <param name="workItem">The work item to execute.</param>
|
||||
public Task InvokeAsync(Func<Task> 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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
|||
/// </summary>
|
||||
/// <param name="serviceProvider">The <see cref="IServiceProvider"/> to use to instantiate components.</param>
|
||||
/// <param name="loggerFactory">The <see cref="ILoggerFactory"/>.</param>
|
||||
/// <param name="dispatcher">The <see cref="IDispatcher"/> to be for invoking user actions into the <see cref="Renderer"/> context.</param>
|
||||
/// <param name="htmlEncoder">A <see cref="Func{T, TResult}"/> that will HTML encode the given string.</param>
|
||||
public HtmlRenderer(IServiceProvider serviceProvider, ILoggerFactory loggerFactory, IDispatcher dispatcher, Func<string, string> htmlEncoder)
|
||||
: base(serviceProvider, loggerFactory, dispatcher)
|
||||
public HtmlRenderer(IServiceProvider serviceProvider, ILoggerFactory loggerFactory, Func<string, string> htmlEncoder)
|
||||
: base(serviceProvider, loggerFactory)
|
||||
{
|
||||
_htmlEncoder = htmlEncoder;
|
||||
}
|
||||
|
||||
public override Dispatcher Dispatcher { get; } = Dispatcher.CreateDefault();
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Task UpdateDisplayAsync(in RenderBatch renderBatch)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
|||
private readonly RenderBatchBuilder _batchBuilder = new RenderBatchBuilder();
|
||||
private readonly Dictionary<int, EventCallback> _eventBindings = new Dictionary<int, EventCallback>();
|
||||
private readonly Dictionary<int, int> _eventHandlerIdReplacements = new Dictionary<int, int>();
|
||||
private readonly IDispatcher _dispatcher;
|
||||
private readonly ILogger<Renderer> _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<Renderer>();
|
||||
_componentFactory = new ComponentFactory(serviceProvider);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs an instance of <see cref="Renderer"/>.
|
||||
/// Gets the <see cref="Microsoft.AspNetCore.Components.Dispatcher" /> associated with this <see cref="Renderer" />.
|
||||
/// </summary>
|
||||
/// <param name="serviceProvider">The <see cref="IServiceProvider"/> to be used when initializing components.</param>
|
||||
/// <param name="loggerFactory">The <see cref="ILoggerFactory"/>.</param>
|
||||
/// <param name="dispatcher">The <see cref="IDispatcher"/> to be for invoking user actions into the <see cref="Renderer"/> context.</param>
|
||||
public Renderer(IServiceProvider serviceProvider, ILoggerFactory loggerFactory, IDispatcher dispatcher)
|
||||
: this(serviceProvider, loggerFactory)
|
||||
{
|
||||
_dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an <see cref="IDispatcher"/> that can be used with one or more <see cref="Renderer"/>.
|
||||
/// </summary>
|
||||
/// <returns>The <see cref="IDispatcher"/>.</returns>
|
||||
public static IDispatcher CreateDefaultDispatcher() => new RendererSynchronizationContext();
|
||||
public abstract Dispatcher Dispatcher { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new component of the specified type.
|
||||
|
|
@ -272,58 +251,6 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
|||
return GetErrorHandledTask(task);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes the supplied work item on the renderer's
|
||||
/// synchronization context.
|
||||
/// </summary>
|
||||
/// <param name="workItem">The work item to execute.</param>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes the supplied work item on the renderer's
|
||||
/// synchronization context.
|
||||
/// </summary>
|
||||
/// <param name="workItem">The work item to execute.</param>
|
||||
public virtual Task InvokeAsync(Func<Task> 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.");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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) =>
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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<Task> workItem)
|
||||
{
|
||||
if (CheckAccess())
|
||||
{
|
||||
return workItem();
|
||||
}
|
||||
|
||||
return _context.InvokeAsync(workItem);
|
||||
}
|
||||
|
||||
public override Task<TResult> InvokeAsync<TResult>(Func<TResult> workItem)
|
||||
{
|
||||
if (CheckAccess())
|
||||
{
|
||||
return Task.FromResult(workItem());
|
||||
}
|
||||
|
||||
return _context.InvokeAsync<TResult>(workItem);
|
||||
}
|
||||
|
||||
public override Task<TResult> InvokeAsync<TResult>(Func<Task<TResult>> workItem)
|
||||
{
|
||||
if (CheckAccess())
|
||||
{
|
||||
return workItem();
|
||||
}
|
||||
|
||||
return _context.InvokeAsync<TResult>(workItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ namespace Microsoft.AspNetCore.Components.Test
|
|||
public void DisplaysComponentInsideLayout()
|
||||
{
|
||||
// Arrange/Act
|
||||
_renderer.InvokeAsync(() => _pageDisplayComponent.SetParametersAsync(ParameterCollection.FromDictionary(new Dictionary<string, object>
|
||||
_renderer.Dispatcher.InvokeAsync(() => _pageDisplayComponent.SetParametersAsync(ParameterCollection.FromDictionary(new Dictionary<string, object>
|
||||
{
|
||||
{ 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<string, object>
|
||||
_renderer.Dispatcher.InvokeAsync(() => _pageDisplayComponent.SetParametersAsync(ParameterCollection.FromDictionary(new Dictionary<string, object>
|
||||
{
|
||||
{ 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<string, object>
|
||||
_renderer.Dispatcher.InvokeAsync(() => _pageDisplayComponent.SetParametersAsync(ParameterCollection.FromDictionary(new Dictionary<string, object>
|
||||
{
|
||||
{ nameof(PageDisplay.Page), typeof(ComponentWithLayout) }
|
||||
})));
|
||||
|
||||
// Act
|
||||
_renderer.InvokeAsync(() => _pageDisplayComponent.SetParametersAsync(ParameterCollection.FromDictionary(new Dictionary<string, object>
|
||||
_renderer.Dispatcher.InvokeAsync(() => _pageDisplayComponent.SetParametersAsync(ParameterCollection.FromDictionary(new Dictionary<string, object>
|
||||
{
|
||||
{ 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<string, object>
|
||||
_renderer.Dispatcher.InvokeAsync(() => _pageDisplayComponent.SetParametersAsync(ParameterCollection.FromDictionary(new Dictionary<string, object>
|
||||
{
|
||||
{ nameof(PageDisplay.Page), typeof(ComponentWithLayout) }
|
||||
})));
|
||||
|
||||
// Act
|
||||
_renderer.InvokeAsync(() => _pageDisplayComponent.SetParametersAsync(ParameterCollection.FromDictionary(new Dictionary<string, object>
|
||||
_renderer.Dispatcher.InvokeAsync(() => _pageDisplayComponent.SetParametersAsync(ParameterCollection.FromDictionary(new Dictionary<string, object>
|
||||
{
|
||||
{ nameof(PageDisplay.Page), typeof(ComponentWithNestedLayout) }
|
||||
})));
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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<string, object>
|
||||
await renderer.Dispatcher.InvokeAsync(() => renderer.RenderRootComponentAsync(componentId, ParameterCollection.FromDictionary(new Dictionary<string, object>
|
||||
{
|
||||
[EventActionsName] = new Dictionary<int, IList<NestedAsyncComponent.ExecutionAction>>
|
||||
{
|
||||
|
|
@ -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<string, object>
|
||||
await renderer.Dispatcher.InvokeAsync(() => renderer.RenderRootComponentAsync(componentId, ParameterCollection.FromDictionary(new Dictionary<string, object>
|
||||
{
|
||||
[EventActionsName] = new Dictionary<int, IList<NestedAsyncComponent.ExecutionAction>>
|
||||
{
|
||||
|
|
@ -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<string, object>
|
||||
await renderer.Dispatcher.InvokeAsync(() => renderer.RenderRootComponentAsync(componentId, ParameterCollection.FromDictionary(new Dictionary<string, object>
|
||||
{
|
||||
[EventActionsName] = new Dictionary<int, IList<NestedAsyncComponent.ExecutionAction>>
|
||||
{
|
||||
|
|
@ -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<string, object>
|
||||
await renderer.Dispatcher.InvokeAsync(() => renderer.RenderRootComponentAsync(componentId, ParameterCollection.FromDictionary(new Dictionary<string, object>
|
||||
{
|
||||
[EventActionsName] = new Dictionary<int, IList<NestedAsyncComponent.ExecutionAction>>
|
||||
{
|
||||
|
|
@ -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<object>();
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
|||
public abstract class HtmlRendererTestBase
|
||||
{
|
||||
protected readonly Func<string, string> _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<TestComponent>(ParameterCollection.Empty)));
|
||||
var result = GetResult(htmlRenderer.Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync<TestComponent>(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!", "</", "p", ">" };
|
||||
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<TestComponent>(ParameterCollection.Empty)));
|
||||
var result = GetResult(htmlRenderer.Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync<TestComponent>(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!>", "</", "p", ">" };
|
||||
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<TestComponent>(ParameterCollection.Empty)));
|
||||
var result = GetResult(htmlRenderer.Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync<TestComponent>(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<TestComponent>(ParameterCollection.Empty)));
|
||||
var result = GetResult(htmlRenderer.Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync<TestComponent>(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<TestComponent>(ParameterCollection.Empty)));
|
||||
var result = GetResult(htmlRenderer.Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync<TestComponent>(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<TestComponent>(ParameterCollection.Empty)));
|
||||
var result = GetResult(htmlRenderer.Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync<TestComponent>(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<TestComponent>(ParameterCollection.Empty)));
|
||||
var result = GetResult(htmlRenderer.Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync<TestComponent>(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<TestComponent>(ParameterCollection.Empty)));
|
||||
var result = GetResult(htmlRenderer.Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync<TestComponent>(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<TestComponent>(ParameterCollection.Empty)));
|
||||
var result = GetResult(htmlRenderer.Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync<TestComponent>(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<TestComponent>(ParameterCollection.Empty)));
|
||||
var result = GetResult(htmlRenderer.Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync<TestComponent>(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<TestComponent>(ParameterCollection.Empty)));
|
||||
var result = GetResult(htmlRenderer.Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync<TestComponent>(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<TestComponent>(ParameterCollection.Empty)));
|
||||
var result = GetResult(htmlRenderer.Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync<TestComponent>(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<TestComponent>(ParameterCollection.Empty)));
|
||||
var result = GetResult(htmlRenderer.Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync<TestComponent>(ParameterCollection.Empty)));
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedHtml, result);
|
||||
|
|
@ -360,7 +357,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
|||
Action<UIChangeEventArgs> change = (UIChangeEventArgs changeArgs) => throw new InvalidOperationException();
|
||||
|
||||
// Act
|
||||
var result = GetResult(Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync<ComponentWithParameters>(
|
||||
var result = GetResult(htmlRenderer.Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync<ComponentWithParameters>(
|
||||
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<TestComponent>(ParameterCollection.Empty)));
|
||||
var result = GetResult(htmlRenderer.Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync<TestComponent>(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<TestComponent>(ParameterCollection.Empty)));
|
||||
var result = GetResult(htmlRenderer.Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync<TestComponent>(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<AsyncComponent>(ParameterCollection.FromDictionary(new Dictionary<string, object>
|
||||
var result = await htmlRenderer.Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync<AsyncComponent>(ParameterCollection.FromDictionary(new Dictionary<string, object>
|
||||
{
|
||||
["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", ">",
|
||||
"<", "p", ">", "80", "</", "p", ">"
|
||||
};
|
||||
|
|
@ -492,7 +489,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
|||
var htmlRenderer = GetHtmlRenderer(serviceProvider);
|
||||
|
||||
// Act
|
||||
var result = await Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync<NestedAsyncComponent>(ParameterCollection.FromDictionary(new Dictionary<string, object>
|
||||
var result = await htmlRenderer.Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync<NestedAsyncComponent>(ParameterCollection.FromDictionary(new Dictionary<string, object>
|
||||
{
|
||||
["Nested"] = false,
|
||||
["Value"] = 10
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,14 +56,12 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
|||
RendererRegistry rendererRegistry,
|
||||
RemoteRenderer renderer,
|
||||
IList<ComponentDescriptor> 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<ComponentRenderedText> 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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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<RemoteRenderer>());
|
||||
|
||||
|
|
@ -93,7 +91,6 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
|||
rendererRegistry,
|
||||
renderer,
|
||||
components,
|
||||
dispatcher,
|
||||
jsRuntime,
|
||||
circuitHandlers,
|
||||
_loggerFactory.CreateLogger<CircuitHost>());
|
||||
|
|
|
|||
|
|
@ -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<PendingRender> PendingRenderBatches = new ConcurrentQueue<PendingRender>();
|
||||
|
||||
public override Dispatcher Dispatcher { get; } = Dispatcher.CreateDefault();
|
||||
|
||||
public int Id { get; }
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -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<IServiceScope>();
|
||||
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<Circuit>(), It.IsAny<CancellationToken>()))
|
||||
.Throws<InvalidTimeZoneException>();
|
||||
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<IServiceScope>();
|
||||
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<IServiceProvider>(),
|
||||
new RendererRegistry(),
|
||||
dispatcher,
|
||||
Mock.Of<IJSRuntime>(),
|
||||
Mock.Of<IClientProxy>());
|
||||
}
|
||||
|
||||
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()
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<RenderTreeDiff>(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();
|
||||
|
|
|
|||
|
|
@ -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<ComponentDescriptor> 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<ComponentDescriptor> 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<IClientProxy>(), 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<ComponentDescriptor>(),
|
||||
dispatcher,
|
||||
jsRuntime,
|
||||
handlers,
|
||||
NullLogger<CircuitHost>.Instance);
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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<RenderBatch> 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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue