Dispose components on client disconnects (#6693)
* Dispose components on client disconnects Fixes https://github.com/aspnet/AspNetCore/issues/4047
This commit is contained in:
parent
4c956d4767
commit
b56c589773
|
|
@ -25,3 +25,31 @@ indent_size = 2
|
||||||
|
|
||||||
[*.{xml,csproj,config,*proj,targets,props}]
|
[*.{xml,csproj,config,*proj,targets,props}]
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
|
|
||||||
|
# Dotnet code style settings:
|
||||||
|
[*.cs]
|
||||||
|
# Sort using and Import directives with System.* appearing first
|
||||||
|
dotnet_sort_system_directives_first = true
|
||||||
|
|
||||||
|
# Don't use this. qualifier
|
||||||
|
dotnet_style_qualification_for_field = false:suggestion
|
||||||
|
dotnet_style_qualification_for_property = false:suggestion
|
||||||
|
|
||||||
|
# use int x = .. over Int32
|
||||||
|
dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
|
||||||
|
|
||||||
|
# use int.MaxValue over Int32.MaxValue
|
||||||
|
dotnet_style_predefined_type_for_member_access = true:suggestion
|
||||||
|
|
||||||
|
# Require var all the time.
|
||||||
|
csharp_style_var_for_built_in_types = true:suggestion
|
||||||
|
csharp_style_var_when_type_is_apparent = true:suggestion
|
||||||
|
csharp_style_var_elsewhere = true:suggestion
|
||||||
|
|
||||||
|
# Newline settings
|
||||||
|
csharp_new_line_before_open_brace = all
|
||||||
|
csharp_new_line_before_else = true
|
||||||
|
csharp_new_line_before_catch = true
|
||||||
|
csharp_new_line_before_finally = true
|
||||||
|
csharp_new_line_before_members_in_object_initializers = true
|
||||||
|
csharp_new_line_before_members_in_anonymous_types = true
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ namespace Microsoft.AspNetCore.Blazor.Rendering
|
||||||
/// Provides mechanisms for rendering <see cref="IComponent"/> instances in a
|
/// Provides mechanisms for rendering <see cref="IComponent"/> instances in a
|
||||||
/// web browser, dispatching events to them, and refreshing the UI as required.
|
/// web browser, dispatching events to them, and refreshing the UI as required.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class WebAssemblyRenderer : Renderer, IDisposable
|
public class WebAssemblyRenderer : Renderer
|
||||||
{
|
{
|
||||||
private readonly int _webAssemblyRendererId;
|
private readonly int _webAssemblyRendererId;
|
||||||
|
|
||||||
|
|
@ -71,11 +71,10 @@ namespace Microsoft.AspNetCore.Blazor.Rendering
|
||||||
RenderRootComponent(componentId);
|
RenderRootComponent(componentId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc />
|
||||||
/// Disposes the instance.
|
protected override void Dispose(bool disposing)
|
||||||
/// </summary>
|
|
||||||
public void Dispose()
|
|
||||||
{
|
{
|
||||||
|
base.Dispose(disposing);
|
||||||
RendererRegistry.Current.TryRemove(_webAssemblyRendererId);
|
RendererRegistry.Current.TryRemove(_webAssemblyRendererId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,3 +2,5 @@ using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Blazor, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Blazor, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||||
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Components.Server, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Components.Server, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||||
|
|
||||||
|
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Components.Server.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
||||||
{
|
{
|
||||||
internal class CircuitHost : IDisposable
|
internal class CircuitHost : IDisposable
|
||||||
{
|
{
|
||||||
private static AsyncLocal<CircuitHost> _current = new AsyncLocal<CircuitHost>();
|
private static readonly AsyncLocal<CircuitHost> _current = new AsyncLocal<CircuitHost>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the current <see cref="Circuit"/>, if any.
|
/// Gets the current <see cref="Circuit"/>, if any.
|
||||||
|
|
@ -24,23 +24,18 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
||||||
public static CircuitHost Current => _current.Value;
|
public static CircuitHost Current => _current.Value;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets the current <see cref="Circuit"/>.
|
/// Sets the current <see cref="Circuits.Circuit"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="circuitHost">The <see cref="Circuit"/>.</param>
|
/// <param name="circuitHost">The <see cref="Circuits.Circuit"/>.</param>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// Calling <see cref="SetCurrentCircuitHost(CircuitHost)"/> will store the circuit
|
/// Calling <see cref="SetCurrentCircuitHost(CircuitHost)"/> will store the circuit
|
||||||
/// and other related values such as the <see cref="IJSRuntime"/> and <see cref="Renderer"/>
|
/// and other related values such as the <see cref="IJSRuntime"/> and <see cref="Renderer"/>
|
||||||
/// in the local execution context. Application code should not need to call this method,
|
/// in the local execution context. Application code should not need to call this method,
|
||||||
/// it is primarily used by the Server-Side Blazor infrastructure.
|
/// it is primarily used by the Server-Side Components infrastructure.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public static void SetCurrentCircuitHost(CircuitHost circuitHost)
|
public static void SetCurrentCircuitHost(CircuitHost circuitHost)
|
||||||
{
|
{
|
||||||
if (circuitHost == null)
|
_current.Value = circuitHost ?? throw new ArgumentNullException(nameof(circuitHost));
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(circuitHost));
|
|
||||||
}
|
|
||||||
|
|
||||||
_current.Value = circuitHost;
|
|
||||||
|
|
||||||
Microsoft.JSInterop.JSRuntime.SetCurrentJSRuntime(circuitHost.JSRuntime);
|
Microsoft.JSInterop.JSRuntime.SetCurrentJSRuntime(circuitHost.JSRuntime);
|
||||||
RendererRegistry.SetCurrentRendererRegistry(circuitHost.RendererRegistry);
|
RendererRegistry.SetCurrentRendererRegistry(circuitHost.RendererRegistry);
|
||||||
|
|
@ -134,6 +129,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
Scope.Dispose();
|
Scope.Dispose();
|
||||||
|
Renderer.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AssertInitialized()
|
private void AssertInitialized()
|
||||||
|
|
|
||||||
|
|
@ -120,11 +120,10 @@ namespace Microsoft.AspNetCore.Components.Browser.Rendering
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc />
|
||||||
/// Disposes the instance.
|
protected override void Dispose(bool disposing)
|
||||||
/// </summary>
|
|
||||||
public void Dispose()
|
|
||||||
{
|
{
|
||||||
|
base.Dispose(true);
|
||||||
_rendererRegistry.TryRemove(_id);
|
_rendererRegistry.TryRemove(_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,19 +12,17 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
/// Provides mechanisms for rendering hierarchies of <see cref="IComponent"/> instances,
|
/// Provides mechanisms for rendering hierarchies of <see cref="IComponent"/> instances,
|
||||||
/// dispatching events to them, and notifying when the user interface is being updated.
|
/// dispatching events to them, and notifying when the user interface is being updated.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class Renderer
|
public abstract class Renderer : IDisposable
|
||||||
{
|
{
|
||||||
private readonly ComponentFactory _componentFactory;
|
private readonly ComponentFactory _componentFactory;
|
||||||
private int _nextComponentId = 0; // TODO: change to 'long' when Mono .NET->JS interop supports it
|
private readonly Dictionary<int, ComponentState> _componentStateById = new Dictionary<int, ComponentState>();
|
||||||
private readonly Dictionary<int, ComponentState> _componentStateById
|
|
||||||
= new Dictionary<int, ComponentState>();
|
|
||||||
|
|
||||||
private readonly RenderBatchBuilder _batchBuilder = new RenderBatchBuilder();
|
private readonly RenderBatchBuilder _batchBuilder = new RenderBatchBuilder();
|
||||||
private bool _isBatchInProgress;
|
|
||||||
|
|
||||||
private int _lastEventHandlerId = 0;
|
|
||||||
private readonly Dictionary<int, EventHandlerInvoker> _eventBindings = new Dictionary<int, EventHandlerInvoker>();
|
private readonly Dictionary<int, EventHandlerInvoker> _eventBindings = new Dictionary<int, EventHandlerInvoker>();
|
||||||
|
|
||||||
|
private int _nextComponentId = 0; // TODO: change to 'long' when Mono .NET->JS interop supports it
|
||||||
|
private bool _isBatchInProgress;
|
||||||
|
private int _lastEventHandlerId = 0;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Constructs an instance of <see cref="Renderer"/>.
|
/// Constructs an instance of <see cref="Renderer"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -175,7 +173,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
|
|
||||||
if (frame.AttributeValue is MulticastDelegate @delegate)
|
if (frame.AttributeValue is MulticastDelegate @delegate)
|
||||||
{
|
{
|
||||||
_eventBindings.Add(id, new EventHandlerInvoker(@delegate));
|
_eventBindings.Add(id, new EventHandlerInvoker(@delegate));
|
||||||
}
|
}
|
||||||
|
|
||||||
frame = frame.WithAttributeEventHandlerId(id);
|
frame = frame.WithAttributeEventHandlerId(id);
|
||||||
|
|
@ -295,5 +293,44 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
RemoveEventHandlerIds(eventHandlerIdsClone, Task.CompletedTask));
|
RemoveEventHandlerIds(eventHandlerIdsClone, Task.CompletedTask));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Releases all resources currently used by this <see cref="Renderer"/> instance.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="disposing"><see langword="true"/> if this method is being invoked by <see cref="IDisposable.Dispose"/>, otherwise <see langword="false"/>.</param>
|
||||||
|
protected virtual void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
List<Exception> exceptions = null;
|
||||||
|
|
||||||
|
foreach (var componentState in _componentStateById.Values)
|
||||||
|
{
|
||||||
|
if (componentState.Component is IDisposable disposable)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
disposable.Dispose();
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
// Capture exceptions thrown by individual components and rethrow as an aggregate.
|
||||||
|
exceptions = exceptions ?? new List<Exception>();
|
||||||
|
exceptions.Add(exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exceptions != null)
|
||||||
|
{
|
||||||
|
throw new AggregateException(exceptions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Releases all resources currently used by this <see cref="Renderer"/> instance.
|
||||||
|
/// </summary>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(disposing: true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
using Microsoft.AspNetCore.Components.Browser;
|
||||||
|
using Microsoft.AspNetCore.Components.Browser.Rendering;
|
||||||
|
using Microsoft.AspNetCore.SignalR;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.JSInterop;
|
||||||
|
using Moq;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Components.Server.Circuits
|
||||||
|
{
|
||||||
|
public class CircuitHostTest
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Dispose_DisposesResources()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var serviceScope = new Mock<IServiceScope>();
|
||||||
|
var clientProxy = Mock.Of<IClientProxy>();
|
||||||
|
var renderRegistry = new RendererRegistry();
|
||||||
|
var jsRuntime = Mock.Of<IJSRuntime>();
|
||||||
|
var syncContext = new CircuitSynchronizationContext();
|
||||||
|
|
||||||
|
var remoteRenderer = new TestRemoteRenderer(
|
||||||
|
Mock.Of<IServiceProvider>(),
|
||||||
|
renderRegistry,
|
||||||
|
jsRuntime,
|
||||||
|
clientProxy,
|
||||||
|
syncContext);
|
||||||
|
|
||||||
|
var circuitHost = new CircuitHost(serviceScope.Object, clientProxy, renderRegistry, remoteRenderer, configure: _ => { }, jsRuntime: jsRuntime, synchronizationContext: syncContext);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
circuitHost.Dispose();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
serviceScope.Verify(s => s.Dispose(), Times.Once());
|
||||||
|
Assert.True(remoteRenderer.Disposed);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestRemoteRenderer : RemoteRenderer
|
||||||
|
{
|
||||||
|
public TestRemoteRenderer(IServiceProvider serviceProvider, RendererRegistry rendererRegistry, IJSRuntime jsRuntime, IClientProxy client, SynchronizationContext syncContext)
|
||||||
|
: base(serviceProvider, rendererRegistry, jsRuntime, client, syncContext)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Disposed { get; set; }
|
||||||
|
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
base.Dispose(disposing);
|
||||||
|
Disposed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netcoreapp3.0</TargetFramework>
|
<TargetFramework>netcoreapp3.0</TargetFramework>
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.5.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.5.0" />
|
||||||
|
<PackageReference Include="Moq" Version="4.10.0" />
|
||||||
<PackageReference Include="xunit" Version="2.3.1" />
|
<PackageReference Include="xunit" Version="2.3.1" />
|
||||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.1" />
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Components;
|
|
||||||
using Microsoft.AspNetCore.Components.Rendering;
|
using Microsoft.AspNetCore.Components.Rendering;
|
||||||
using Microsoft.AspNetCore.Components.RenderTree;
|
using Microsoft.AspNetCore.Components.RenderTree;
|
||||||
using Microsoft.AspNetCore.Components.Test.Helpers;
|
using Microsoft.AspNetCore.Components.Test.Helpers;
|
||||||
|
|
@ -1139,6 +1138,78 @@ namespace Microsoft.AspNetCore.Components.Test
|
||||||
Assert.Equal(2, numEventsFired);
|
Assert.Equal(2, numEventsFired);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void DisposingRenderer_DisposesTopLevelComponents()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var renderer = new TestRenderer();
|
||||||
|
var component = new DisposableComponent();
|
||||||
|
renderer.AssignRootComponentId(component);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
renderer.Dispose();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(component.Disposed);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void DisposingRenderer_DisposesNestedComponents()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var renderer = new TestRenderer();
|
||||||
|
var component = new TestComponent(builder =>
|
||||||
|
{
|
||||||
|
builder.AddContent(0, "Hello");
|
||||||
|
builder.OpenComponent<DisposableComponent>(1);
|
||||||
|
builder.CloseComponent();
|
||||||
|
});
|
||||||
|
var componentId = renderer.AssignRootComponentId(component);
|
||||||
|
component.TriggerRender();
|
||||||
|
var batch = renderer.Batches.Single();
|
||||||
|
var componentFrame = batch.ReferenceFrames
|
||||||
|
.Single(frame => frame.FrameType == RenderTreeFrameType.Component);
|
||||||
|
var nestedComponent = Assert.IsType<DisposableComponent>(componentFrame.Component);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
renderer.Dispose();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(component.Disposed);
|
||||||
|
Assert.True(nestedComponent.Disposed);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void DisposingRenderer_CapturesExceptionsFromAllRegisteredComponents()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var renderer = new TestRenderer();
|
||||||
|
var exception1 = new Exception();
|
||||||
|
var exception2 = new Exception();
|
||||||
|
var component = new TestComponent(builder =>
|
||||||
|
{
|
||||||
|
builder.AddContent(0, "Hello");
|
||||||
|
builder.OpenComponent<DisposableComponent>(1);
|
||||||
|
builder.AddAttribute(1, nameof(DisposableComponent.DisposeAction), (Action)(() => throw exception1));
|
||||||
|
builder.CloseComponent();
|
||||||
|
|
||||||
|
builder.OpenComponent<DisposableComponent>(2);
|
||||||
|
builder.AddAttribute(1, nameof(DisposableComponent.DisposeAction), (Action)(() => throw exception2));
|
||||||
|
builder.CloseComponent();
|
||||||
|
});
|
||||||
|
var componentId = renderer.AssignRootComponentId(component);
|
||||||
|
component.TriggerRender();
|
||||||
|
|
||||||
|
// Act &A Assert
|
||||||
|
var aggregate = Assert.Throws<AggregateException>(renderer.Dispose);
|
||||||
|
|
||||||
|
// All components must be disposed even if some throw as part of being diposed.
|
||||||
|
Assert.True(component.Disposed);
|
||||||
|
Assert.Equal(2, aggregate.InnerExceptions.Count);
|
||||||
|
Assert.Contains(exception1, aggregate.InnerExceptions);
|
||||||
|
Assert.Contains(exception2, aggregate.InnerExceptions);
|
||||||
|
}
|
||||||
|
|
||||||
private class NoOpRenderer : Renderer
|
private class NoOpRenderer : Renderer
|
||||||
{
|
{
|
||||||
public NoOpRenderer() : base(new TestServiceProvider())
|
public NoOpRenderer() : base(new TestServiceProvider())
|
||||||
|
|
@ -1152,7 +1223,7 @@ namespace Microsoft.AspNetCore.Components.Test
|
||||||
=> Task.CompletedTask;
|
=> Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TestComponent : IComponent
|
private class TestComponent : IComponent, IDisposable
|
||||||
{
|
{
|
||||||
private RenderHandle _renderHandle;
|
private RenderHandle _renderHandle;
|
||||||
private RenderFragment _renderFragment;
|
private RenderFragment _renderFragment;
|
||||||
|
|
@ -1172,6 +1243,10 @@ namespace Microsoft.AspNetCore.Components.Test
|
||||||
|
|
||||||
public void TriggerRender()
|
public void TriggerRender()
|
||||||
=> _renderHandle.Render(_renderFragment);
|
=> _renderHandle.Render(_renderFragment);
|
||||||
|
|
||||||
|
public bool Disposed { get; private set; }
|
||||||
|
|
||||||
|
void IDisposable.Dispose() => Disposed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private class MessageComponent : AutoRenderComponent
|
private class MessageComponent : AutoRenderComponent
|
||||||
|
|
@ -1398,6 +1473,24 @@ namespace Microsoft.AspNetCore.Components.Test
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class DisposableComponent : AutoRenderComponent, IDisposable
|
||||||
|
{
|
||||||
|
public bool Disposed { get; private set; }
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public Action DisposeAction { get; private set; }
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Disposed = true;
|
||||||
|
DisposeAction?.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void BuildRenderTree(RenderTreeBuilder builder)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class TestAsyncRenderer : TestRenderer
|
class TestAsyncRenderer : TestRenderer
|
||||||
{
|
{
|
||||||
public Task NextUpdateDisplayReturnTask { get; set; }
|
public Task NextUpdateDisplayReturnTask { get; set; }
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue