Merge pull request #13136 from dotnet-maestro-bot/merge/release/3.0-to-master
[automated] Merge branch 'release/3.0' => 'master'
This commit is contained in:
commit
5e575a3e64
|
|
@ -22,7 +22,7 @@ To run tests for the entire repo, run:
|
|||
.\eng\scripts\TestHelix.ps1
|
||||
```
|
||||
|
||||
This will restore, and then publish all of the test projects including some bootstrapping scripts that will install the correct dotnet runtime/sdk before running the test assemblies on the helix machine, and upload the job to helix, it won't wait for the jobs to complete, but you can go to https://mc.dot.net/#/user/$(your user name)/builds.
|
||||
This will restore, and then publish all of the test projects including some bootstrapping scripts that will install the correct dotnet runtime/sdk before running the test assemblies on the helix machine, and upload the job to helix.
|
||||
|
||||
|
||||
## How do I look at the results of a helix run on Azure Pipelines?
|
||||
|
|
|
|||
|
|
@ -171,8 +171,13 @@ try {
|
|||
# Redirect stderr to stdout because PowerShell does not consistently handle output to stderr
|
||||
$changedFiles = & cmd /c 'git --no-pager diff --ignore-space-at-eol --name-only 2>nul'
|
||||
|
||||
# Temporary: Disable check for blazor js file
|
||||
$changedFilesExclusion = "src/Components/Web.JS/dist/Release/blazor.server.js"
|
||||
|
||||
if ($changedFiles) {
|
||||
foreach ($file in $changedFiles) {
|
||||
if ($file -eq $changedFilesExclusion) {continue}
|
||||
|
||||
$filePath = Resolve-Path "${repoRoot}/${file}"
|
||||
LogError "Generated code is not up to date in $file. You might need to regenerate the reference assemblies or project list (see docs/ReferenceAssemblies.md and docs/ReferenceResolution.md)" -filepath $filePath
|
||||
& git --no-pager diff --ignore-space-at-eol $filePath
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
<HelixAvailablePlatform Include="OSX" />
|
||||
<HelixAvailablePlatform Include="Linux" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<!-- x64 queues -->
|
||||
<ItemGroup Condition="'$(IsWindowsOnlyTest)' != 'true' AND '$(TargetArchitecture)' == 'x64'">
|
||||
<HelixAvailableTargetQueue Include="Windows.10.Amd64.ClientRS4.VS2017.Open" Platform="Windows" />
|
||||
|
|
@ -27,11 +27,11 @@
|
|||
<HelixAvailableTargetQueue Include="Redhat.7.Amd64.Open" Platform="Linux" />
|
||||
<HelixAvailableTargetQueue Include="(Fedora.28.Amd64.Open)Ubuntu.1604.Amd64.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:fedora-28-helix-09ca40b-20190508143249" Platform="Linux" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<ItemGroup Condition="'$(IsWindowsOnlyTest)' != 'true' AND '$(TargetArchitecture)' == 'arm64'">
|
||||
<!-- arm64 queues -->
|
||||
<HelixAvailableTargetQueue Include="(Debian.9.Arm64.Open)Ubuntu.1604.Arm64.Docker.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:debian-9-helix-arm64v8-0a0ebdd-20190312215438" Platform="Linux" />
|
||||
|
||||
<HelixAvailableTargetQueue Include="(Debian.9.Arm64.Open)Ubuntu.1604.Arm64.Docker.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:debian-9-helix-arm64v8-a12566d-20190807161036" Platform="Linux" />
|
||||
|
||||
<!-- Need to resolve permission issues on this docker queue
|
||||
<HelixAvailableTargetQueue Include="(Alpine.38.Arm64)Ubuntu.1604.Arm64.Docker@mcr.microsoft.com/dotnet-buildtools/prereqs:alpine-3.8-helix-arm64v8-46e69dd-20190327215724" Platform="Linux" />
|
||||
<HelixAvailableTargetQueue Include="(Ubuntu-1804.Arm64.Open)Ubuntu.1604.Arm64.Docker.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:ubuntu-18.04-helix-arm64v8-6f28fa9-20190606004102" Platform="Linux" />
|
||||
|
|
|
|||
|
|
@ -3,5 +3,6 @@
|
|||
<NpmTestArgs>test</NpmTestArgs>
|
||||
<Configuration Condition="'$(Configuration)' == '' AND '$(ContinuousIntegrationBuild)' == 'true'">Release</Configuration>
|
||||
<Configuration Condition="'$(Configuration)' == ''">Debug</Configuration>
|
||||
<PackOnBuild>false</PackOnBuild>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -49,7 +49,9 @@
|
|||
BuildInParallel="true" />
|
||||
</Target>
|
||||
|
||||
<Target Name="Build" DependsOnTargets="$(BuildDependsOn)" />
|
||||
<Target Name="Build" DependsOnTargets="$(BuildDependsOn)">
|
||||
<CallTarget Targets="_Pack" Condition="'$(PackOnBuild)' == 'true'" />
|
||||
</Target>
|
||||
|
||||
<Target Name="_Build"
|
||||
Condition="'$(IsBuildable)' != 'false'"
|
||||
|
|
@ -66,11 +68,12 @@
|
|||
</PackDependsOn>
|
||||
<PackDependsOn Condition="'$(NoBuild)' != 'true'">
|
||||
$(PackDependsOn);
|
||||
Build
|
||||
Build;
|
||||
_Pack
|
||||
</PackDependsOn>
|
||||
</PropertyGroup>
|
||||
|
||||
<Target Name="Pack" Condition="'$(IsPackable)' == 'true'" DependsOnTargets="$(PackDependsOn)">
|
||||
<Target Name="_Pack" Condition="'$(IsPackable)' == 'true'" >
|
||||
<PropertyGroup>
|
||||
<_PackageTargetPath>$(MSBuildProjectDirectory)\$(PackageFileName)</_PackageTargetPath>
|
||||
</PropertyGroup>
|
||||
|
|
@ -88,6 +91,8 @@
|
|||
<OnError ExecuteTargets="_RestoreBackupPackageJsonFile" />
|
||||
</Target>
|
||||
|
||||
<Target Name="Pack" Condition="'$(IsPackable)' == 'true'" DependsOnTargets="$(PackDependsOn)" />
|
||||
|
||||
<Target Name="_RestoreBackupPackageJsonFile">
|
||||
<Move SourceFiles="$(_BackupPackageJson)" DestinationFiles="$(PackageJson)" />
|
||||
</Target>
|
||||
|
|
|
|||
|
|
@ -91,7 +91,6 @@ namespace Microsoft.AspNetCore.Blazor.Hosting
|
|||
services.AddSingleton(_BrowserHostBuilderContext);
|
||||
services.AddSingleton<IWebAssemblyHost, WebAssemblyHost>();
|
||||
services.AddSingleton<IJSRuntime>(WebAssemblyJSRuntime.Instance);
|
||||
services.AddSingleton<IComponentContext, WebAssemblyComponentContext>();
|
||||
services.AddSingleton<NavigationManager>(WebAssemblyNavigationManager.Instance);
|
||||
services.AddSingleton<INavigationInterception>(WebAssemblyNavigationInterception.Instance);
|
||||
services.AddSingleton<ILoggerFactory, WebAssemblyLoggerFactory>();
|
||||
|
|
|
|||
|
|
@ -1,12 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Services
|
||||
{
|
||||
internal class WebAssemblyComponentContext : IComponentContext
|
||||
{
|
||||
public bool IsConnected => true;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Services.Test
|
||||
{
|
||||
public class WebAssemblyComponentContextTest
|
||||
{
|
||||
[Fact]
|
||||
public void IsConnected()
|
||||
{
|
||||
Assert.True(new WebAssemblyComponentContext().IsConnected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -97,6 +97,7 @@ namespace Microsoft.AspNetCore.Components
|
|||
public abstract partial class Dispatcher
|
||||
{
|
||||
protected Dispatcher() { }
|
||||
public void AssertAccess() { }
|
||||
public abstract bool CheckAccess();
|
||||
public static Microsoft.AspNetCore.Components.Dispatcher CreateDefault() { throw null; }
|
||||
public abstract System.Threading.Tasks.Task InvokeAsync(System.Action workItem);
|
||||
|
|
@ -196,10 +197,6 @@ namespace Microsoft.AspNetCore.Components
|
|||
void Attach(Microsoft.AspNetCore.Components.RenderHandle renderHandle);
|
||||
System.Threading.Tasks.Task SetParametersAsync(Microsoft.AspNetCore.Components.ParameterView parameters);
|
||||
}
|
||||
public partial interface IComponentContext
|
||||
{
|
||||
bool IsConnected { get; }
|
||||
}
|
||||
public partial interface IHandleAfterRender
|
||||
{
|
||||
System.Threading.Tasks.Task OnAfterRenderAsync();
|
||||
|
|
|
|||
|
|
@ -97,6 +97,7 @@ namespace Microsoft.AspNetCore.Components
|
|||
public abstract partial class Dispatcher
|
||||
{
|
||||
protected Dispatcher() { }
|
||||
public void AssertAccess() { }
|
||||
public abstract bool CheckAccess();
|
||||
public static Microsoft.AspNetCore.Components.Dispatcher CreateDefault() { throw null; }
|
||||
public abstract System.Threading.Tasks.Task InvokeAsync(System.Action workItem);
|
||||
|
|
@ -196,10 +197,6 @@ namespace Microsoft.AspNetCore.Components
|
|||
void Attach(Microsoft.AspNetCore.Components.RenderHandle renderHandle);
|
||||
System.Threading.Tasks.Task SetParametersAsync(Microsoft.AspNetCore.Components.ParameterView parameters);
|
||||
}
|
||||
public partial interface IComponentContext
|
||||
{
|
||||
bool IsConnected { get; }
|
||||
}
|
||||
public partial interface IHandleAfterRender
|
||||
{
|
||||
System.Threading.Tasks.Task OnAfterRenderAsync();
|
||||
|
|
|
|||
|
|
@ -23,6 +23,20 @@ namespace Microsoft.AspNetCore.Components
|
|||
/// </summary>
|
||||
internal event UnhandledExceptionEventHandler UnhandledException;
|
||||
|
||||
/// <summary>
|
||||
/// Validates that the currently executing code is running inside the dispatcher.
|
||||
/// </summary>
|
||||
public void AssertAccess()
|
||||
{
|
||||
if (!CheckAccess())
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"The current thread is not associated with the Dispatcher. " +
|
||||
"Use InvokeAsync() to switch execution to the Dispatcher when " +
|
||||
"triggering rendering or component state.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a value that determines whether using the dispatcher to invoke a work item is required
|
||||
/// from the current context.
|
||||
|
|
|
|||
|
|
@ -1,19 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNetCore.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides information about the environment in which components are executing.
|
||||
/// </summary>
|
||||
public interface IComponentContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a flag to indicate whether there is an active connection to the user's display.
|
||||
/// </summary>
|
||||
/// <example>During prerendering, the value will always be false.</example>
|
||||
/// <example>During server-side execution, the value can be true or false depending on whether there is an active SignalR connection.</example>
|
||||
/// <example>During client-side execution, the value will always be true.</example>
|
||||
bool IsConnected { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -209,11 +209,11 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
|||
/// </returns>
|
||||
public virtual Task DispatchEventAsync(ulong eventHandlerId, EventFieldInfo fieldInfo, EventArgs eventArgs)
|
||||
{
|
||||
EnsureSynchronizationContext();
|
||||
Dispatcher.AssertAccess();
|
||||
|
||||
if (!_eventBindings.TryGetValue(eventHandlerId, out var callback))
|
||||
{
|
||||
throw new ArgumentException($"There is no event handler with ID {eventHandlerId}");
|
||||
throw new ArgumentException($"There is no event handler associated with this event. EventId: '{eventHandlerId}'.", nameof(eventHandlerId));
|
||||
}
|
||||
|
||||
Log.HandlingEvent(_logger, eventHandlerId, eventArgs);
|
||||
|
|
@ -337,7 +337,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
|||
/// <param name="renderFragment">A <see cref="RenderFragment"/> that will supply the updated UI contents.</param>
|
||||
internal void AddToRenderQueue(int componentId, RenderFragment renderFragment)
|
||||
{
|
||||
EnsureSynchronizationContext();
|
||||
Dispatcher.AssertAccess();
|
||||
|
||||
var componentState = GetOptionalComponentState(componentId);
|
||||
if (componentState == null)
|
||||
|
|
@ -374,21 +374,6 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
|||
return eventHandlerId;
|
||||
}
|
||||
|
||||
private void EnsureSynchronizationContext()
|
||||
{
|
||||
// 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.CheckAccess())
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"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.");
|
||||
}
|
||||
}
|
||||
|
||||
private ComponentState GetRequiredComponentState(int componentId)
|
||||
=> _componentStateById.TryGetValue(componentId, out var componentState)
|
||||
? componentState
|
||||
|
|
@ -414,7 +399,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
|||
|
||||
private void ProcessRenderQueue()
|
||||
{
|
||||
EnsureSynchronizationContext();
|
||||
Dispatcher.AssertAccess();
|
||||
|
||||
if (_isBatchInProgress)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -30,8 +30,6 @@ namespace Microsoft.AspNetCore.Components.Routing
|
|||
|
||||
[Inject] private INavigationInterception NavigationInterception { get; set; }
|
||||
|
||||
[Inject] private IComponentContext ComponentContext { get; set; }
|
||||
|
||||
[Inject] private ILoggerFactory LoggerFactory { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -152,7 +150,7 @@ namespace Microsoft.AspNetCore.Components.Routing
|
|||
|
||||
Task IHandleAfterRender.OnAfterRenderAsync()
|
||||
{
|
||||
if (!_navigationInterceptionEnabled && ComponentContext.IsConnected)
|
||||
if (!_navigationInterceptionEnabled)
|
||||
{
|
||||
_navigationInterceptionEnabled = true;
|
||||
return NavigationInterception.EnableNavigationInterceptionAsync();
|
||||
|
|
|
|||
|
|
@ -2883,7 +2883,7 @@ namespace Microsoft.AspNetCore.Components.Test
|
|||
{
|
||||
return renderer.DispatchEventAsync(eventHandlerId, new EventArgs());
|
||||
});
|
||||
Assert.Equal($"There is no event handler with ID {eventHandlerId}", ex.Message);
|
||||
Assert.Contains($"There is no event handler associated with this event. EventId: '{eventHandlerId}'.", ex.Message);
|
||||
Assert.Equal(2, numEventsFired);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
|||
{
|
||||
internal class CircuitHost : IAsyncDisposable
|
||||
{
|
||||
private readonly SemaphoreSlim HandlerLock = new SemaphoreSlim(1);
|
||||
private readonly IServiceScope _scope;
|
||||
private readonly CircuitOptions _options;
|
||||
private readonly CircuitHandler[] _circuitHandlers;
|
||||
|
|
@ -27,24 +26,9 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
|||
private bool _disposed;
|
||||
|
||||
/// <summary>
|
||||
/// Sets the current <see cref="Circuits.Circuit"/>.
|
||||
/// Sets the <see cref="IJSRuntime"/> for the current excution context.
|
||||
/// </summary>
|
||||
/// <param name="circuitHost">The <see cref="Circuits.Circuit"/>.</param>
|
||||
/// <remarks>
|
||||
/// Calling <see cref="SetCurrentCircuitHost(CircuitHost)"/> will store 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,
|
||||
/// it is primarily used by the Server-Side Components infrastructure.
|
||||
/// </remarks>
|
||||
public static void SetCurrentCircuitHost(CircuitHost circuitHost)
|
||||
{
|
||||
if (circuitHost is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(circuitHost));
|
||||
}
|
||||
|
||||
JSInterop.JSRuntime.SetCurrentJSRuntime(circuitHost.JSRuntime);
|
||||
}
|
||||
public void SetCurrentJSRuntime() => JSInterop.JSRuntime.SetCurrentJSRuntime(JSRuntime);
|
||||
|
||||
// This event is fired when there's an unrecoverable exception coming from the circuit, and
|
||||
// it need so be torn down. The registry listens to this even so that the circuit can
|
||||
|
|
@ -116,7 +100,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
|||
|
||||
try
|
||||
{
|
||||
SetCurrentCircuitHost(this);
|
||||
SetCurrentJSRuntime();
|
||||
_initialized = true; // We're ready to accept incoming JSInterop calls from here on
|
||||
|
||||
await OnCircuitOpenedAsync(cancellationToken);
|
||||
|
|
@ -186,7 +170,18 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
|||
try
|
||||
{
|
||||
Renderer.Dispose();
|
||||
_scope.Dispose();
|
||||
|
||||
// This cast is needed because it's possible the scope may not support async dispose.
|
||||
// Our DI container does, but other DI systems may not.
|
||||
if (_scope is IAsyncDisposable asyncDisposable)
|
||||
{
|
||||
await asyncDisposable.DisposeAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
_scope.Dispose();
|
||||
}
|
||||
|
||||
Log.DisposeSucceeded(_logger, CircuitId);
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
|
@ -202,35 +197,28 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
|||
{
|
||||
Log.CircuitOpened(_logger, Circuit.Id);
|
||||
|
||||
await HandlerLock.WaitAsync(cancellationToken);
|
||||
Renderer.Dispatcher.AssertAccess();
|
||||
|
||||
try
|
||||
List<Exception> exceptions = null;
|
||||
|
||||
for (var i = 0; i < _circuitHandlers.Length; i++)
|
||||
{
|
||||
List<Exception> exceptions = null;
|
||||
|
||||
for (var i = 0; i < _circuitHandlers.Length; i++)
|
||||
var circuitHandler = _circuitHandlers[i];
|
||||
try
|
||||
{
|
||||
var circuitHandler = _circuitHandlers[i];
|
||||
try
|
||||
{
|
||||
await circuitHandler.OnCircuitOpenedAsync(Circuit, cancellationToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.CircuitHandlerFailed(_logger, circuitHandler, nameof(CircuitHandler.OnCircuitOpenedAsync), ex);
|
||||
exceptions ??= new List<Exception>();
|
||||
exceptions.Add(ex);
|
||||
}
|
||||
await circuitHandler.OnCircuitOpenedAsync(Circuit, cancellationToken);
|
||||
}
|
||||
|
||||
if (exceptions != null)
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new AggregateException("Encountered exceptions while executing circuit handlers.", exceptions);
|
||||
Log.CircuitHandlerFailed(_logger, circuitHandler, nameof(CircuitHandler.OnCircuitOpenedAsync), ex);
|
||||
exceptions ??= new List<Exception>();
|
||||
exceptions.Add(ex);
|
||||
}
|
||||
}
|
||||
finally
|
||||
|
||||
if (exceptions != null)
|
||||
{
|
||||
HandlerLock.Release();
|
||||
throw new AggregateException("Encountered exceptions while executing circuit handlers.", exceptions);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -238,35 +226,28 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
|||
{
|
||||
Log.ConnectionUp(_logger, Circuit.Id, Client.ConnectionId);
|
||||
|
||||
await HandlerLock.WaitAsync(cancellationToken);
|
||||
Renderer.Dispatcher.AssertAccess();
|
||||
|
||||
List<Exception> exceptions = null;
|
||||
|
||||
try
|
||||
for (var i = 0; i < _circuitHandlers.Length; i++)
|
||||
{
|
||||
List<Exception> exceptions = null;
|
||||
|
||||
for (var i = 0; i < _circuitHandlers.Length; i++)
|
||||
var circuitHandler = _circuitHandlers[i];
|
||||
try
|
||||
{
|
||||
var circuitHandler = _circuitHandlers[i];
|
||||
try
|
||||
{
|
||||
await circuitHandler.OnConnectionUpAsync(Circuit, cancellationToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.CircuitHandlerFailed(_logger, circuitHandler, nameof(CircuitHandler.OnConnectionUpAsync), ex);
|
||||
exceptions ??= new List<Exception>();
|
||||
exceptions.Add(ex);
|
||||
}
|
||||
await circuitHandler.OnConnectionUpAsync(Circuit, cancellationToken);
|
||||
}
|
||||
|
||||
if (exceptions != null)
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new AggregateException("Encountered exceptions while executing circuit handlers.", exceptions);
|
||||
Log.CircuitHandlerFailed(_logger, circuitHandler, nameof(CircuitHandler.OnConnectionUpAsync), ex);
|
||||
exceptions ??= new List<Exception>();
|
||||
exceptions.Add(ex);
|
||||
}
|
||||
}
|
||||
finally
|
||||
|
||||
if (exceptions != null)
|
||||
{
|
||||
HandlerLock.Release();
|
||||
throw new AggregateException("Encountered exceptions while executing circuit handlers.", exceptions);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -274,35 +255,28 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
|||
{
|
||||
Log.ConnectionDown(_logger, Circuit.Id, Client.ConnectionId);
|
||||
|
||||
await HandlerLock.WaitAsync(cancellationToken);
|
||||
Renderer.Dispatcher.AssertAccess();
|
||||
|
||||
List<Exception> exceptions = null;
|
||||
|
||||
try
|
||||
for (var i = 0; i < _circuitHandlers.Length; i++)
|
||||
{
|
||||
List<Exception> exceptions = null;
|
||||
|
||||
for (var i = 0; i < _circuitHandlers.Length; i++)
|
||||
var circuitHandler = _circuitHandlers[i];
|
||||
try
|
||||
{
|
||||
var circuitHandler = _circuitHandlers[i];
|
||||
try
|
||||
{
|
||||
await circuitHandler.OnConnectionDownAsync(Circuit, cancellationToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.CircuitHandlerFailed(_logger, circuitHandler, nameof(CircuitHandler.OnConnectionDownAsync), ex);
|
||||
exceptions ??= new List<Exception>();
|
||||
exceptions.Add(ex);
|
||||
}
|
||||
await circuitHandler.OnConnectionDownAsync(Circuit, cancellationToken);
|
||||
}
|
||||
|
||||
if (exceptions != null)
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new AggregateException("Encountered exceptions while executing circuit handlers.", exceptions);
|
||||
Log.CircuitHandlerFailed(_logger, circuitHandler, nameof(CircuitHandler.OnConnectionDownAsync), ex);
|
||||
exceptions ??= new List<Exception>();
|
||||
exceptions.Add(ex);
|
||||
}
|
||||
}
|
||||
finally
|
||||
|
||||
if (exceptions != null)
|
||||
{
|
||||
HandlerLock.Release();
|
||||
throw new AggregateException("Encountered exceptions while executing circuit handlers.", exceptions);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -310,35 +284,48 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
|||
{
|
||||
Log.CircuitClosed(_logger, Circuit.Id);
|
||||
|
||||
await HandlerLock.WaitAsync(cancellationToken);
|
||||
List<Exception> exceptions = null;
|
||||
|
||||
for (var i = 0; i < _circuitHandlers.Length; i++)
|
||||
{
|
||||
var circuitHandler = _circuitHandlers[i];
|
||||
try
|
||||
{
|
||||
await circuitHandler.OnCircuitClosedAsync(Circuit, cancellationToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.CircuitHandlerFailed(_logger, circuitHandler, nameof(CircuitHandler.OnCircuitClosedAsync), ex);
|
||||
exceptions ??= new List<Exception>();
|
||||
exceptions.Add(ex);
|
||||
}
|
||||
}
|
||||
|
||||
if (exceptions != null)
|
||||
{
|
||||
throw new AggregateException("Encountered exceptions while executing circuit handlers.", exceptions);
|
||||
}
|
||||
}
|
||||
|
||||
// Called by the client when it completes rendering a batch.
|
||||
// OnRenderCompletedAsync is used in a fire-and-forget context, so it's responsible for its own
|
||||
// error handling.
|
||||
public async Task OnRenderCompletedAsync(long renderId, string errorMessageOrNull)
|
||||
{
|
||||
AssertInitialized();
|
||||
AssertNotDisposed();
|
||||
|
||||
try
|
||||
{
|
||||
List<Exception> exceptions = null;
|
||||
|
||||
for (var i = 0; i < _circuitHandlers.Length; i++)
|
||||
{
|
||||
var circuitHandler = _circuitHandlers[i];
|
||||
try
|
||||
{
|
||||
await circuitHandler.OnCircuitClosedAsync(Circuit, cancellationToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.CircuitHandlerFailed(_logger, circuitHandler, nameof(CircuitHandler.OnCircuitClosedAsync), ex);
|
||||
exceptions ??= new List<Exception>();
|
||||
exceptions.Add(ex);
|
||||
}
|
||||
}
|
||||
|
||||
if (exceptions != null)
|
||||
{
|
||||
throw new AggregateException("Encountered exceptions while executing circuit handlers.", exceptions);
|
||||
}
|
||||
_ = Renderer.OnRenderCompletedAsync(renderId, errorMessageOrNull);
|
||||
}
|
||||
finally
|
||||
catch (Exception e)
|
||||
{
|
||||
HandlerLock.Release();
|
||||
// Captures sync exceptions when invoking OnRenderCompletedAsync.
|
||||
// An exception might be throw synchronously when we receive an ack for a batch we never produced.
|
||||
Log.OnRenderCompletedFailed(_logger, renderId, CircuitId, e);
|
||||
await TryNotifyClientErrorAsync(Client, GetClientErrorMessage(e, $"Failed to complete render batch '{renderId}'."));
|
||||
UnhandledException(this, new UnhandledExceptionEventArgs(e, isTerminating: false));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -353,7 +340,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
|||
{
|
||||
await Renderer.Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
SetCurrentCircuitHost(this);
|
||||
SetCurrentJSRuntime();
|
||||
Log.BeginInvokeDotNet(_logger, callId, assemblyName, methodIdentifier, dotNetObjectId);
|
||||
DotNetDispatcher.BeginInvoke(callId, assemblyName, methodIdentifier, dotNetObjectId, argsJson);
|
||||
});
|
||||
|
|
@ -363,10 +350,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
|||
// We don't expect any of this code to actually throw, because DotNetDispatcher.BeginInvoke doesn't throw
|
||||
// however, we still want this to get logged if we do.
|
||||
Log.BeginInvokeDotNetFailed(_logger, callId, assemblyName, methodIdentifier, dotNetObjectId, ex);
|
||||
if (Client.Connected)
|
||||
{
|
||||
await NotifyClientError(Client, "Interop call failed.");
|
||||
}
|
||||
await TryNotifyClientErrorAsync(Client, GetClientErrorMessage(ex, "Interop call failed."));
|
||||
UnhandledException?.Invoke(this, new UnhandledExceptionEventArgs(ex, isTerminating: false));
|
||||
}
|
||||
}
|
||||
|
|
@ -382,7 +366,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
|||
{
|
||||
await Renderer.Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
SetCurrentCircuitHost(this);
|
||||
SetCurrentJSRuntime();
|
||||
if (!succeded)
|
||||
{
|
||||
// We can log the arguments here because it is simply the JS error with the call stack.
|
||||
|
|
@ -401,10 +385,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
|||
// An error completing JS interop means that the user sent invalid data, a well-behaved
|
||||
// client won't do this.
|
||||
Log.EndInvokeDispatchException(_logger, ex);
|
||||
if (Client.Connected)
|
||||
{
|
||||
await NotifyClientError(Client, "Invalid interop arguments.");
|
||||
}
|
||||
await TryNotifyClientErrorAsync(Client, GetClientErrorMessage(ex, "Invalid interop arguments."));
|
||||
UnhandledException?.Invoke(this, new UnhandledExceptionEventArgs(ex, isTerminating: false));
|
||||
}
|
||||
}
|
||||
|
|
@ -419,17 +400,13 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
|||
WebEventData webEventData;
|
||||
try
|
||||
{
|
||||
AssertInitialized();
|
||||
webEventData = WebEventData.Parse(eventDescriptorJson, eventArgsJson);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Invalid event data is fatal. We expect a well-behaved client to send valid JSON.
|
||||
Log.DispatchEventFailedToParseEventData(_logger, ex);
|
||||
if (Client.Connected)
|
||||
{
|
||||
await NotifyClientError(Client, "Invalid event data.");
|
||||
}
|
||||
await TryNotifyClientErrorAsync(Client, GetClientErrorMessage(ex, "Bad input data."));
|
||||
UnhandledException?.Invoke(this, new UnhandledExceptionEventArgs(ex, isTerminating: false));
|
||||
return;
|
||||
}
|
||||
|
|
@ -438,7 +415,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
|||
{
|
||||
await Renderer.Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
SetCurrentCircuitHost(this);
|
||||
SetCurrentJSRuntime();
|
||||
return Renderer.DispatchEventAsync(
|
||||
webEventData.EventHandlerId,
|
||||
webEventData.EventFieldInfo,
|
||||
|
|
@ -450,10 +427,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
|||
// A failure in dispatching an event means that it was an attempt to use an invalid event id.
|
||||
// A well-behaved client won't do this.
|
||||
Log.DispatchEventFailedToDispatchEvent(_logger, webEventData.EventHandlerId.ToString(), ex);
|
||||
if (Client.Connected)
|
||||
{
|
||||
await NotifyClientError(Client, "Failed to dispatch event.");
|
||||
}
|
||||
await TryNotifyClientErrorAsync(Client, GetClientErrorMessage(ex, "Failed to dispatch event."));
|
||||
UnhandledException?.Invoke(this, new UnhandledExceptionEventArgs(ex, isTerminating: false));
|
||||
}
|
||||
}
|
||||
|
|
@ -469,7 +443,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
|||
{
|
||||
await Renderer.Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
SetCurrentCircuitHost(this);
|
||||
SetCurrentJSRuntime();
|
||||
Log.LocationChange(_logger, uri, CircuitId);
|
||||
var navigationManager = (RemoteNavigationManager)Services.GetRequiredService<NavigationManager>();
|
||||
navigationManager.NotifyLocationChanged(uri, intercepted);
|
||||
|
|
@ -491,7 +465,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
|||
// LocationChangeException means that it failed in user-code. Treat this like an unhandled
|
||||
// exception in user-code.
|
||||
Log.LocationChangeFailedInCircuit(_logger, uri, CircuitId, nex);
|
||||
await ReportUnhandledException(nex);
|
||||
await TryNotifyClientErrorAsync(Client, GetClientErrorMessage(nex, "Location change failed."));
|
||||
UnhandledException?.Invoke(this, new UnhandledExceptionEventArgs(nex, isTerminating: false));
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
|
@ -499,10 +473,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
|||
// Any other exception means that it failed validation, or inside the NavigationManager. Treat
|
||||
// this like bad data.
|
||||
Log.LocationChangeFailed(_logger, uri, CircuitId, ex);
|
||||
if (Client.Connected)
|
||||
{
|
||||
await NotifyClientError(Client, $"Location change to {uri} failed.");
|
||||
}
|
||||
await TryNotifyClientErrorAsync(Client, GetClientErrorMessage(ex, $"Location change to '{uri}' failed."));
|
||||
UnhandledException?.Invoke(this, new UnhandledExceptionEventArgs(ex, isTerminating: false));
|
||||
}
|
||||
}
|
||||
|
|
@ -527,7 +498,7 @@ 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.
|
||||
// OnRenderCompletedAsync to be invoked to complete, and SignalR does not allow concurrent hub method invocations.
|
||||
_ = Renderer.Dispatcher.InvokeAsync(() => Renderer.ProcessBufferedRenderBatches());
|
||||
}
|
||||
|
||||
|
|
@ -566,39 +537,50 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
|||
private async Task ReportUnhandledException(Exception exception)
|
||||
{
|
||||
Log.CircuitUnhandledException(_logger, CircuitId, exception);
|
||||
|
||||
await TryNotifyClientErrorAsync(Client, GetClientErrorMessage(exception), exception);
|
||||
}
|
||||
|
||||
private string GetClientErrorMessage(Exception exception, string additionalInformation = null)
|
||||
{
|
||||
if (_options.DetailedErrors)
|
||||
{
|
||||
return exception.ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
return
|
||||
$"There was an unhandled exception on the current circuit, so this circuit will be terminated. For more details turn on " +
|
||||
$"detailed exceptions in '{typeof(CircuitOptions).Name}.{nameof(CircuitOptions.DetailedErrors)}'. {additionalInformation}";
|
||||
}
|
||||
}
|
||||
|
||||
// exception is only populated when either the renderer or the synchronization context signal exceptions.
|
||||
// In other cases it is null and should never be sent to the client.
|
||||
// error contains the information to send to the client.
|
||||
private async Task TryNotifyClientErrorAsync(IClientProxy client, string error, Exception exception = null)
|
||||
{
|
||||
if (!Client.Connected)
|
||||
{
|
||||
_logger.LogDebug("Client is disconnected YO.");
|
||||
Log.UnhandledExceptionClientDisconnected(
|
||||
_logger,
|
||||
CircuitId,
|
||||
exception);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (_options.DetailedErrors)
|
||||
{
|
||||
await NotifyClientError(Client, exception.ToString());
|
||||
}
|
||||
else
|
||||
{
|
||||
var message =
|
||||
$"There was an unhandled exception on the current circuit, so this circuit will be terminated. For more details turn on " +
|
||||
$"detailed exceptions in '{typeof(CircuitOptions).Name}.{nameof(CircuitOptions.DetailedErrors)}'";
|
||||
await NotifyClientError(Client, message);
|
||||
}
|
||||
Log.CircuitTransmittingClientError(_logger, CircuitId);
|
||||
await client.SendAsync("JS.Error", error);
|
||||
Log.CircuitTransmittedClientErrorSuccess(_logger, CircuitId);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.CircuitUnhandledExceptionFailed(_logger, CircuitId, ex);
|
||||
Log.CircuitTransmitErrorFailed(_logger, CircuitId, ex);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task NotifyClientError(IClientProxy client, string error)
|
||||
{
|
||||
_logger.LogDebug("About to notify of an error");
|
||||
await client.SendAsync("JS.Error", error);
|
||||
_logger.LogDebug("Completed notify of an error");
|
||||
}
|
||||
|
||||
private static class Log
|
||||
{
|
||||
private static readonly Action<ILogger, Exception> _intializationStarted;
|
||||
|
|
@ -613,7 +595,10 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
|||
private static readonly Action<ILogger, string, Exception> _onCircuitClosed;
|
||||
private static readonly Action<ILogger, Type, string, string, Exception> _circuitHandlerFailed;
|
||||
private static readonly Action<ILogger, string, Exception> _circuitUnhandledException;
|
||||
private static readonly Action<ILogger, string, Exception> _circuitUnhandledExceptionFailed;
|
||||
private static readonly Action<ILogger, string, Exception> _circuitTransmittingClientError;
|
||||
private static readonly Action<ILogger, string, Exception> _circuitTransmittedClientErrorSuccess;
|
||||
private static readonly Action<ILogger, string, Exception> _circuitTransmitErrorFailed;
|
||||
private static readonly Action<ILogger, string, Exception> _unhandledExceptionClientDisconnected;
|
||||
|
||||
private static readonly Action<ILogger, string, string, string, Exception> _beginInvokeDotNetStatic;
|
||||
private static readonly Action<ILogger, string, long, string, Exception> _beginInvokeDotNetInstance;
|
||||
|
|
@ -628,6 +613,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
|||
private static readonly Action<ILogger, string, string, Exception> _locationChangeSucceeded;
|
||||
private static readonly Action<ILogger, string, string, Exception> _locationChangeFailed;
|
||||
private static readonly Action<ILogger, string, string, Exception> _locationChangeFailedInCircuit;
|
||||
private static readonly Action<ILogger, long, string, Exception> _onRenderCompletedFailed;
|
||||
|
||||
private static class EventIds
|
||||
{
|
||||
|
|
@ -644,7 +630,10 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
|||
public static readonly EventId OnCircuitClosed = new EventId(109, "OnCircuitClosed");
|
||||
public static readonly EventId CircuitHandlerFailed = new EventId(110, "CircuitHandlerFailed");
|
||||
public static readonly EventId CircuitUnhandledException = new EventId(111, "CircuitUnhandledException");
|
||||
public static readonly EventId CircuitUnhandledExceptionFailed = new EventId(112, "CircuitUnhandledExceptionFailed");
|
||||
public static readonly EventId CircuitTransmittingClientError = new EventId(112, "CircuitTransmittingClientError");
|
||||
public static readonly EventId CircuitTransmittedClientErrorSuccess = new EventId(113, "CircuitTransmittedClientErrorSuccess");
|
||||
public static readonly EventId CircuitTransmitErrorFailed = new EventId(114, "CircuitTransmitErrorFailed");
|
||||
public static readonly EventId UnhandledExceptionClientDisconnected = new EventId(115, "UnhandledExceptionClientDisconnected");
|
||||
|
||||
// 200s used for interactive stuff
|
||||
public static readonly EventId DispatchEventFailedToParseEventData = new EventId(200, "DispatchEventFailedToParseEventData");
|
||||
|
|
@ -659,6 +648,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
|||
public static readonly EventId LocationChangeSucceded = new EventId(209, "LocationChangeSucceeded");
|
||||
public static readonly EventId LocationChangeFailed = new EventId(210, "LocationChangeFailed");
|
||||
public static readonly EventId LocationChangeFailedInCircuit = new EventId(211, "LocationChangeFailedInCircuit");
|
||||
public static readonly EventId OnRenderCompletedFailed = new EventId(212, " OnRenderCompletedFailed");
|
||||
}
|
||||
|
||||
static Log()
|
||||
|
|
@ -666,87 +656,102 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
|||
_intializationStarted = LoggerMessage.Define(
|
||||
LogLevel.Debug,
|
||||
EventIds.InitializationFailed,
|
||||
"Circuit initialization started");
|
||||
"Circuit initialization started.");
|
||||
|
||||
_intializationSucceded = LoggerMessage.Define(
|
||||
LogLevel.Debug,
|
||||
EventIds.InitializationFailed,
|
||||
"Circuit initialization succeeded");
|
||||
"Circuit initialization succeeded.");
|
||||
|
||||
_intializationFailed = LoggerMessage.Define(
|
||||
LogLevel.Debug,
|
||||
EventIds.InitializationFailed,
|
||||
"Circuit initialization failed");
|
||||
"Circuit initialization failed.");
|
||||
|
||||
_disposeStarted = LoggerMessage.Define<string>(
|
||||
LogLevel.Debug,
|
||||
EventIds.DisposeStarted,
|
||||
"Disposing circuit {CircuitId} started");
|
||||
"Disposing circuit '{CircuitId}' started.");
|
||||
|
||||
_disposeSucceded = LoggerMessage.Define<string>(
|
||||
LogLevel.Debug,
|
||||
EventIds.DisposeSucceeded,
|
||||
"Disposing circuit {CircuitId} succeded");
|
||||
"Disposing circuit '{CircuitId}' succeded.");
|
||||
|
||||
_disposeFailed = LoggerMessage.Define<string>(
|
||||
LogLevel.Debug,
|
||||
EventIds.DisposeFailed,
|
||||
"Disposing circuit {CircuitId} failed");
|
||||
"Disposing circuit '{CircuitId}' failed.");
|
||||
|
||||
_onCircuitOpened = LoggerMessage.Define<string>(
|
||||
LogLevel.Debug,
|
||||
EventIds.OnCircuitOpened,
|
||||
"Opening circuit with id {CircuitId}.");
|
||||
"Opening circuit with id '{CircuitId}'.");
|
||||
|
||||
_onConnectionUp = LoggerMessage.Define<string, string>(
|
||||
LogLevel.Debug,
|
||||
EventIds.OnConnectionUp,
|
||||
"Circuit id {CircuitId} connected using connection {ConnectionId}.");
|
||||
"Circuit id '{CircuitId}' connected using connection '{ConnectionId}'.");
|
||||
|
||||
_onConnectionDown = LoggerMessage.Define<string, string>(
|
||||
LogLevel.Debug,
|
||||
EventIds.OnConnectionDown,
|
||||
"Circuit id {CircuitId} disconnected from connection {ConnectionId}.");
|
||||
"Circuit id '{CircuitId}' disconnected from connection '{ConnectionId}'.");
|
||||
|
||||
_onCircuitClosed = LoggerMessage.Define<string>(
|
||||
LogLevel.Debug,
|
||||
EventIds.OnCircuitClosed,
|
||||
"Closing circuit with id {CircuitId}.");
|
||||
"Closing circuit with id '{CircuitId}'.");
|
||||
|
||||
_circuitHandlerFailed = LoggerMessage.Define<Type, string, string>(
|
||||
LogLevel.Error,
|
||||
EventIds.CircuitHandlerFailed,
|
||||
"Unhandled error invoking circuit handler type {handlerType}.{handlerMethod}: {Message}");
|
||||
|
||||
_circuitUnhandledException = LoggerMessage.Define<string>(
|
||||
LogLevel.Error,
|
||||
EventIds.CircuitUnhandledException,
|
||||
"Unhandled exception in circuit {CircuitId}");
|
||||
_circuitUnhandledException = LoggerMessage.Define<string>(
|
||||
LogLevel.Error,
|
||||
EventIds.CircuitUnhandledException,
|
||||
"Unhandled exception in circuit '{CircuitId}'.");
|
||||
|
||||
_circuitUnhandledExceptionFailed = LoggerMessage.Define<string>(
|
||||
LogLevel.Debug,
|
||||
EventIds.CircuitUnhandledExceptionFailed,
|
||||
"Failed to transmit exception to client in circuit {CircuitId}");
|
||||
_circuitTransmittingClientError = LoggerMessage.Define<string>(
|
||||
LogLevel.Debug,
|
||||
EventIds.CircuitTransmittingClientError,
|
||||
"About to notify client of an error in circuit '{CircuitId}'.");
|
||||
|
||||
_circuitTransmittedClientErrorSuccess = LoggerMessage.Define<string>(
|
||||
LogLevel.Debug,
|
||||
EventIds.CircuitTransmittedClientErrorSuccess,
|
||||
"Successfully transmitted error to client in circuit '{CircuitId}'.");
|
||||
|
||||
_circuitTransmitErrorFailed = LoggerMessage.Define<string>(
|
||||
LogLevel.Debug,
|
||||
EventIds.CircuitTransmitErrorFailed,
|
||||
"Failed to transmit exception to client in circuit '{CircuitId}'.");
|
||||
|
||||
_unhandledExceptionClientDisconnected = LoggerMessage.Define<string>(
|
||||
LogLevel.Debug,
|
||||
EventIds.UnhandledExceptionClientDisconnected,
|
||||
"An exception ocurred on the circuit host '{CircuitId}' while the client is disconnected.");
|
||||
|
||||
_beginInvokeDotNetStatic = LoggerMessage.Define<string, string, string>(
|
||||
LogLevel.Debug,
|
||||
EventIds.BeginInvokeDotNet,
|
||||
"Invoking static method with identifier '{MethodIdentifier}' on assembly '{Assembly}' with callback id '{CallId}'");
|
||||
"Invoking static method with identifier '{MethodIdentifier}' on assembly '{Assembly}' with callback id '{CallId}'.");
|
||||
|
||||
_beginInvokeDotNetInstance = LoggerMessage.Define<string, long, string>(
|
||||
LogLevel.Debug,
|
||||
EventIds.BeginInvokeDotNet,
|
||||
"Invoking instance method '{MethodIdentifier}' on instance '{DotNetObjectId}' with callback id '{CallId}'");
|
||||
"Invoking instance method '{MethodIdentifier}' on instance '{DotNetObjectId}' with callback id '{CallId}'.");
|
||||
|
||||
_beginInvokeDotNetStaticFailed = LoggerMessage.Define<string, string, string>(
|
||||
LogLevel.Debug,
|
||||
EventIds.BeginInvokeDotNetFailed,
|
||||
"Failed to invoke static method with identifier '{MethodIdentifier}' on assembly '{Assembly}' with callback id '{CallId}'");
|
||||
"Failed to invoke static method with identifier '{MethodIdentifier}' on assembly '{Assembly}' with callback id '{CallId}'.");
|
||||
|
||||
_beginInvokeDotNetInstanceFailed = LoggerMessage.Define<string, long, string>(
|
||||
LogLevel.Debug,
|
||||
EventIds.BeginInvokeDotNetFailed,
|
||||
"Failed to invoke instance method '{MethodIdentifier}' on instance '{DotNetObjectId}' with callback id '{CallId}'");
|
||||
"Failed to invoke instance method '{MethodIdentifier}' on instance '{DotNetObjectId}' with callback id '{CallId}'.");
|
||||
|
||||
_endInvokeDispatchException = LoggerMessage.Define(
|
||||
LogLevel.Debug,
|
||||
|
|
@ -776,25 +781,30 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
|||
_locationChange = LoggerMessage.Define<string, string>(
|
||||
LogLevel.Debug,
|
||||
EventIds.LocationChange,
|
||||
"Location changing to {URI} in circuit {CircuitId}.");
|
||||
"Location changing to {URI} in circuit '{CircuitId}'.");
|
||||
|
||||
_locationChangeSucceeded = LoggerMessage.Define<string, string>(
|
||||
LogLevel.Debug,
|
||||
EventIds.LocationChangeSucceded,
|
||||
"Location change to {URI} in circuit {CircuitId} succeded.");
|
||||
"Location change to '{URI}' in circuit '{CircuitId}' succeded.");
|
||||
|
||||
_locationChangeFailed = LoggerMessage.Define<string, string>(
|
||||
LogLevel.Debug,
|
||||
EventIds.LocationChangeFailed,
|
||||
"Location change to {URI} in circuit {CircuitId} failed.");
|
||||
"Location change to '{URI}' in circuit '{CircuitId}' failed.");
|
||||
|
||||
_locationChangeFailedInCircuit = LoggerMessage.Define<string, string>(
|
||||
LogLevel.Error,
|
||||
EventIds.LocationChangeFailed,
|
||||
"Location change to {URI} in circuit {CircuitId} failed.");
|
||||
"Location change to '{URI}' in circuit '{CircuitId}' failed.");
|
||||
|
||||
_onRenderCompletedFailed = LoggerMessage.Define<long, string>(
|
||||
LogLevel.Debug,
|
||||
EventIds.OnRenderCompletedFailed,
|
||||
"Failed to complete render batch '{RenderId}' in circuit host '{CircuitId}'.");
|
||||
}
|
||||
|
||||
public static void InitializationStarted(ILogger logger) =>_intializationStarted(logger, null);
|
||||
public static void InitializationStarted(ILogger logger) => _intializationStarted(logger, null);
|
||||
public static void InitializationSucceeded(ILogger logger) => _intializationSucceded(logger, null);
|
||||
public static void InitializationFailed(ILogger logger, Exception exception) => _intializationFailed(logger, exception);
|
||||
public static void DisposeStarted(ILogger logger, string circuitId) => _disposeStarted(logger, circuitId, null);
|
||||
|
|
@ -802,7 +812,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
|||
public static void DisposeFailed(ILogger logger, string circuitId, Exception exception) => _disposeFailed(logger, circuitId, exception);
|
||||
public static void CircuitOpened(ILogger logger, string circuitId) => _onCircuitOpened(logger, circuitId, null);
|
||||
public static void ConnectionUp(ILogger logger, string circuitId, string connectionId) => _onConnectionUp(logger, circuitId, connectionId, null);
|
||||
public static void ConnectionDown(ILogger logger, string circuitId, string connectionId) => _onConnectionDown(logger, circuitId, connectionId, null);
|
||||
public static void ConnectionDown(ILogger logger, string circuitId, string connectionId) => _onConnectionDown(logger, circuitId, connectionId, null);
|
||||
public static void CircuitClosed(ILogger logger, string circuitId) => _onCircuitClosed(logger, circuitId, null);
|
||||
|
||||
public static void CircuitHandlerFailed(ILogger logger, CircuitHandler handler, string handlerMethod, Exception exception)
|
||||
|
|
@ -816,7 +826,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
|||
}
|
||||
|
||||
public static void CircuitUnhandledException(ILogger logger, string circuitId, Exception exception) => _circuitUnhandledException(logger, circuitId, exception);
|
||||
public static void CircuitUnhandledExceptionFailed(ILogger logger, string circuitId, Exception exception) => _circuitUnhandledExceptionFailed(logger, circuitId, exception);
|
||||
public static void CircuitTransmitErrorFailed(ILogger logger, string circuitId, Exception exception) => _circuitTransmitErrorFailed(logger, circuitId, exception);
|
||||
public static void EndInvokeDispatchException(ILogger logger, Exception ex) => _endInvokeDispatchException(logger, ex);
|
||||
public static void EndInvokeJSFailed(ILogger logger, long asyncHandle, string arguments) => _endInvokeJSFailed(logger, asyncHandle, arguments, null);
|
||||
public static void EndInvokeJSSucceeded(ILogger logger, long asyncCall) => _endInvokeJSSucceeded(logger, asyncCall, null);
|
||||
|
|
@ -851,6 +861,10 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
|||
public static void LocationChangeSucceeded(ILogger logger, string uri, string circuitId) => _locationChangeSucceeded(logger, uri, circuitId, null);
|
||||
public static void LocationChangeFailed(ILogger logger, string uri, string circuitId, Exception exception) => _locationChangeFailed(logger, uri, circuitId, exception);
|
||||
public static void LocationChangeFailedInCircuit(ILogger logger, string uri, string circuitId, Exception exception) => _locationChangeFailedInCircuit(logger, uri, circuitId, exception);
|
||||
public static void UnhandledExceptionClientDisconnected(ILogger logger, string circuitId, Exception exception) => _unhandledExceptionClientDisconnected(logger, circuitId, exception);
|
||||
public static void CircuitTransmittingClientError(ILogger logger, string circuitId) => _circuitTransmittingClientError(logger, circuitId, null);
|
||||
public static void CircuitTransmittedClientErrorSuccess(ILogger logger, string circuitId) => _circuitTransmittedClientErrorSuccess(logger, circuitId, null);
|
||||
public static void OnRenderCompletedFailed(ILogger logger, long renderId, string circuitId, Exception e) => _onRenderCompletedFailed(logger, renderId, circuitId, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,9 +51,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
|||
|
||||
var scope = _scopeFactory.CreateScope();
|
||||
var jsRuntime = (RemoteJSRuntime)scope.ServiceProvider.GetRequiredService<IJSRuntime>();
|
||||
var componentContext = (RemoteComponentContext)scope.ServiceProvider.GetRequiredService<IComponentContext>();
|
||||
jsRuntime.Initialize(client);
|
||||
componentContext.Initialize(client);
|
||||
|
||||
var navigationManager = (RemoteNavigationManager)scope.ServiceProvider.GetRequiredService<NavigationManager>();
|
||||
var navigationInterception = (RemoteNavigationInterception)scope.ServiceProvider.GetRequiredService<INavigationInterception>();
|
||||
|
|
|
|||
|
|
@ -1,19 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.Server.Circuits
|
||||
{
|
||||
internal class RemoteComponentContext : IComponentContext
|
||||
{
|
||||
private CircuitClientProxy _clientProxy;
|
||||
|
||||
public bool IsConnected => _clientProxy != null && _clientProxy.Connected;
|
||||
|
||||
internal void Initialize(CircuitClientProxy clientProxy)
|
||||
{
|
||||
_clientProxy = clientProxy ?? throw new ArgumentNullException(nameof(clientProxy));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -222,7 +222,7 @@ namespace Microsoft.AspNetCore.Components.Web.Rendering
|
|||
// disposed.
|
||||
}
|
||||
|
||||
public Task OnRenderCompleted(long incomingBatchId, string errorMessageOrNull)
|
||||
public Task OnRenderCompletedAsync(long incomingBatchId, string errorMessageOrNull)
|
||||
{
|
||||
if (_disposing)
|
||||
{
|
||||
|
|
@ -273,9 +273,9 @@ namespace Microsoft.AspNetCore.Components.Web.Rendering
|
|||
|
||||
if (lastBatchId < incomingBatchId)
|
||||
{
|
||||
HandleException(
|
||||
new InvalidOperationException($"Received an acknowledgement for batch with id '{incomingBatchId}' when the last batch produced was '{lastBatchId}'."));
|
||||
return Task.CompletedTask;
|
||||
// This exception is due to a bad client input, so we mark it as such to prevent loging it as a warning and
|
||||
// flooding the logs with warnings.
|
||||
throw new InvalidOperationException($"Received an acknowledgement for batch with id '{incomingBatchId}' when the last batch produced was '{lastBatchId}'.");
|
||||
}
|
||||
|
||||
// Normally we will not have pending renders, but it might happen that we reached the limit of
|
||||
|
|
@ -320,7 +320,7 @@ namespace Microsoft.AspNetCore.Components.Web.Rendering
|
|||
}
|
||||
else
|
||||
{
|
||||
pendingRenderInfo.TrySetException(new RemoteRendererException(errorMessageOrNull));
|
||||
pendingRenderInfo.TrySetException(new InvalidOperationException(errorMessageOrNull));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,21 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.Web.Rendering
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an exception related to remote rendering.
|
||||
/// </summary>
|
||||
internal class RemoteRendererException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructs an instance of <see cref="RemoteRendererException"/>.
|
||||
/// </summary>
|
||||
/// <param name="message">The exception message.</param>
|
||||
public RemoteRendererException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -6,8 +6,6 @@ using System.Runtime.CompilerServices;
|
|||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Components.Server.Circuits;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
|
@ -203,7 +201,7 @@ namespace Microsoft.AspNetCore.Components.Server
|
|||
}
|
||||
|
||||
Log.ReceivedConfirmationForBatch(_logger, renderId);
|
||||
_ = circuitHost.Renderer.OnRenderCompleted(renderId, errorMessageOrNull);
|
||||
_ = circuitHost.OnRenderCompletedAsync(renderId, errorMessageOrNull);
|
||||
}
|
||||
|
||||
public async ValueTask OnLocationChanged(string uri, bool intercepted)
|
||||
|
|
@ -281,29 +279,15 @@ namespace Microsoft.AspNetCore.Components.Server
|
|||
private static readonly Action<ILogger, string, Exception> _circuitHostShutdown =
|
||||
LoggerMessage.Define<string>(LogLevel.Debug, new EventId(6, "CircuitHostShutdown"), "Call to '{CallSite}' received after the circuit was shut down");
|
||||
|
||||
private static readonly Action<ILogger, string, Exception> _circuitTerminatedGracefully =
|
||||
LoggerMessage.Define<string>(LogLevel.Debug, new EventId(7, "CircuitTerminatedGracefully"), "Circuit '{CircuitId}' terminated gracefully");
|
||||
|
||||
private static readonly Action<ILogger, string, Exception> _invalidInputData =
|
||||
LoggerMessage.Define<string>(LogLevel.Debug, new EventId(8, "InvalidInputData"), "Call to '{CallSite}' received invalid input data");
|
||||
LoggerMessage.Define<string>(LogLevel.Debug, new EventId(7, "InvalidInputData"), "Call to '{CallSite}' received invalid input data");
|
||||
|
||||
private static readonly Action<ILogger, Exception> _circuitInitializationFailed =
|
||||
LoggerMessage.Define(LogLevel.Debug, new EventId(9, "CircuitInitializationFailed"), "Circuit initialization failed");
|
||||
LoggerMessage.Define(LogLevel.Debug, new EventId(8, "CircuitInitializationFailed"), "Circuit initialization failed");
|
||||
|
||||
public static void NoComponentsRegisteredInEndpoint(ILogger logger, string endpointDisplayName)
|
||||
{
|
||||
_noComponentsRegisteredInEndpoint(logger, endpointDisplayName, null);
|
||||
}
|
||||
public static void NoComponentsRegisteredInEndpoint(ILogger logger, string endpointDisplayName) => _noComponentsRegisteredInEndpoint(logger, endpointDisplayName, null);
|
||||
|
||||
public static void ReceivedConfirmationForBatch(ILogger logger, long batchId)
|
||||
{
|
||||
_receivedConfirmationForBatch(logger, batchId, null);
|
||||
}
|
||||
|
||||
public static void UnhandledExceptionInCircuit(ILogger logger, string circuitId, Exception exception)
|
||||
{
|
||||
_unhandledExceptionInCircuit(logger, circuitId, exception);
|
||||
}
|
||||
public static void ReceivedConfirmationForBatch(ILogger logger, long batchId) => _receivedConfirmationForBatch(logger, batchId, null);
|
||||
|
||||
public static void CircuitAlreadyInitialized(ILogger logger, string circuitId) => _circuitAlreadyInitialized(logger, circuitId, null);
|
||||
|
||||
|
|
@ -311,8 +295,6 @@ namespace Microsoft.AspNetCore.Components.Server
|
|||
|
||||
public static void CircuitHostShutdown(ILogger logger, [CallerMemberName] string callSite = "") => _circuitHostShutdown(logger, callSite, null);
|
||||
|
||||
public static void CircuitTerminatedGracefully(ILogger logger, string circuitId) => _circuitTerminatedGracefully(logger, circuitId, null);
|
||||
|
||||
public static void InvalidInputData(ILogger logger, [CallerMemberName] string callSite = "") => _invalidInputData(logger, callSite, null);
|
||||
|
||||
public static void CircuitInitializationFailed(ILogger logger, Exception exception) => _circuitInitializationFailed(logger, exception);
|
||||
|
|
|
|||
|
|
@ -70,7 +70,6 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
services.AddScoped<NavigationManager, RemoteNavigationManager>();
|
||||
services.AddScoped<IJSRuntime, RemoteJSRuntime>();
|
||||
services.AddScoped<INavigationInterception, RemoteNavigationInterception>();
|
||||
services.AddScoped<IComponentContext, RemoteComponentContext>();
|
||||
services.AddScoped<AuthenticationStateProvider, ServerAuthenticationStateProvider>();
|
||||
|
||||
services.TryAddEnumerable(ServiceDescriptor.Singleton<IConfigureOptions<CircuitOptions>, CircuitOptionsJSInteropDetailedErrorsConfiguration>());
|
||||
|
|
|
|||
|
|
@ -39,6 +39,33 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
|||
Assert.Null(circuitHost.Handle.CircuitHost);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DisposeAsync_DisposesScopeAsynchronouslyIfPossible()
|
||||
{
|
||||
// Arrange
|
||||
var serviceScope = new Mock<IServiceScope>();
|
||||
serviceScope
|
||||
.As<IAsyncDisposable>()
|
||||
.Setup(f => f.DisposeAsync())
|
||||
.Returns(new ValueTask(Task.CompletedTask))
|
||||
.Verifiable();
|
||||
|
||||
var remoteRenderer = GetRemoteRenderer();
|
||||
var circuitHost = TestCircuitHost.Create(
|
||||
Guid.NewGuid().ToString(),
|
||||
serviceScope.Object,
|
||||
remoteRenderer);
|
||||
|
||||
// Act
|
||||
await circuitHost.DisposeAsync();
|
||||
|
||||
// Assert
|
||||
serviceScope.Verify(s => s.Dispose(), Times.Never());
|
||||
serviceScope.As<IAsyncDisposable>().Verify(s => s.DisposeAsync(), Times.Once());
|
||||
Assert.True(remoteRenderer.Disposed);
|
||||
Assert.Null(circuitHost.Handle.CircuitHost);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DisposeAsync_DisposesResourcesAndSilencesException()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -270,46 +270,6 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
|||
Assert.False(registry.DisconnectedCircuits.TryGetValue(circuitHost.CircuitId, out _));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Connect_WhileDisconnectIsInProgress_SeriallyExecutesCircuitHandlers()
|
||||
{
|
||||
// Arrange
|
||||
var circuitIdFactory = TestCircuitIdFactory.CreateTestFactory();
|
||||
|
||||
var registry = new TestCircuitRegistry(circuitIdFactory);
|
||||
registry.BeforeDisconnect = new ManualResetEventSlim();
|
||||
// This verifies that connection up \ down events on a circuit handler are always invoked serially.
|
||||
var circuitHandler = new SerialCircuitHandler();
|
||||
var tcs = new TaskCompletionSource<int>();
|
||||
|
||||
var circuitHost = TestCircuitHost.Create(circuitIdFactory.CreateCircuitId(), handlers: new[] { circuitHandler });
|
||||
registry.Register(circuitHost);
|
||||
var client = Mock.Of<IClientProxy>();
|
||||
var newId = "new-connection";
|
||||
|
||||
// Act
|
||||
var disconnect = Task.Run(() =>
|
||||
{
|
||||
var task = registry.DisconnectAsync(circuitHost, circuitHost.Client.ConnectionId);
|
||||
tcs.SetResult(0);
|
||||
return task;
|
||||
});
|
||||
var connect = Task.Run(async () =>
|
||||
{
|
||||
registry.BeforeDisconnect.Set();
|
||||
await tcs.Task;
|
||||
await registry.ConnectAsync(circuitHost.CircuitId, client, newId, default);
|
||||
});
|
||||
await Task.WhenAll(disconnect, connect);
|
||||
|
||||
// Assert
|
||||
Assert.Single(registry.ConnectedCircuits.Values);
|
||||
Assert.False(registry.DisconnectedCircuits.TryGetValue(circuitHost.CircuitId, out _));
|
||||
|
||||
Assert.True(circuitHandler.OnConnectionDownExecuted, "OnConnectionDownAsync should have been executed.");
|
||||
Assert.True(circuitHandler.OnConnectionUpExecuted, "OnConnectionUpAsync should have been executed.");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DisconnectWhenAConnectIsInProgress()
|
||||
{
|
||||
|
|
@ -444,37 +404,5 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
|||
NullLogger<CircuitRegistry>.Instance,
|
||||
factory ?? TestCircuitIdFactory.CreateTestFactory());
|
||||
}
|
||||
|
||||
private class SerialCircuitHandler : CircuitHandler
|
||||
{
|
||||
private readonly SemaphoreSlim _sempahore = new SemaphoreSlim(1);
|
||||
|
||||
public bool OnConnectionUpExecuted { get; private set; }
|
||||
public bool OnConnectionDownExecuted { get; private set; }
|
||||
|
||||
public override async Task OnConnectionUpAsync(Circuit circuit, CancellationToken cancellationToken)
|
||||
{
|
||||
Assert.True(await _sempahore.WaitAsync(0), "This should be serialized and consequently without contention");
|
||||
await Task.Delay(10);
|
||||
|
||||
Assert.False(OnConnectionUpExecuted);
|
||||
Assert.True(OnConnectionDownExecuted);
|
||||
OnConnectionUpExecuted = true;
|
||||
|
||||
_sempahore.Release();
|
||||
}
|
||||
|
||||
public override async Task OnConnectionDownAsync(Circuit circuit, CancellationToken cancellationToken)
|
||||
{
|
||||
Assert.True(await _sempahore.WaitAsync(0), "This should be serialized and consequently without contention");
|
||||
await Task.Delay(10);
|
||||
|
||||
Assert.False(OnConnectionUpExecuted);
|
||||
Assert.False(OnConnectionDownExecuted);
|
||||
OnConnectionDownExecuted = true;
|
||||
|
||||
_sempahore.Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,46 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Components.Server.Circuits;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.Web.Rendering
|
||||
{
|
||||
public class RemoteComponentContextTest
|
||||
{
|
||||
[Fact]
|
||||
public void IfNotInitialized_IsConnectedReturnsFalse()
|
||||
{
|
||||
Assert.False(new RemoteComponentContext().IsConnected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IfInitialized_IsConnectedValueDeterminedByCircuitProxy()
|
||||
{
|
||||
// Arrange
|
||||
var clientProxy = new FakeClientProxy();
|
||||
var circuitProxy = new CircuitClientProxy(clientProxy, "test connection");
|
||||
var remoteComponentContext = new RemoteComponentContext();
|
||||
|
||||
// Act/Assert: Can observe connected state
|
||||
remoteComponentContext.Initialize(circuitProxy);
|
||||
Assert.True(remoteComponentContext.IsConnected);
|
||||
|
||||
// Act/Assert: Can observe disconnected state
|
||||
circuitProxy.SetDisconnected();
|
||||
Assert.False(remoteComponentContext.IsConnected);
|
||||
}
|
||||
|
||||
private class FakeClientProxy : IClientProxy
|
||||
{
|
||||
public Task SendCoreAsync(string method, object[] args, CancellationToken cancellationToken = default)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -89,7 +89,7 @@ namespace Microsoft.AspNetCore.Components.Web.Rendering
|
|||
component.TriggerRender();
|
||||
}
|
||||
|
||||
await renderer.OnRenderCompleted(2, null);
|
||||
await renderer.OnRenderCompletedAsync(2, null);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(9, renderer._unacknowledgedRenderBatches.Count);
|
||||
|
|
@ -115,7 +115,7 @@ namespace Microsoft.AspNetCore.Components.Web.Rendering
|
|||
}
|
||||
Assert.Equal(10, renderer._unacknowledgedRenderBatches.Count);
|
||||
|
||||
await renderer.OnRenderCompleted(2, null);
|
||||
await renderer.OnRenderCompletedAsync(2, null);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(10, renderer._unacknowledgedRenderBatches.Count);
|
||||
|
|
@ -153,7 +153,7 @@ namespace Microsoft.AspNetCore.Components.Web.Rendering
|
|||
|
||||
var componentId = renderer.AssignRootComponentId(component);
|
||||
component.TriggerRender();
|
||||
_ = renderer.OnRenderCompleted(2, null);
|
||||
_ = renderer.OnRenderCompletedAsync(2, null);
|
||||
|
||||
@event.Reset();
|
||||
firstBatchTCS.SetResult(null);
|
||||
|
|
@ -171,7 +171,7 @@ namespace Microsoft.AspNetCore.Components.Web.Rendering
|
|||
|
||||
foreach (var id in renderIds.ToArray())
|
||||
{
|
||||
_ = renderer.OnRenderCompleted(id, null);
|
||||
_ = renderer.OnRenderCompletedAsync(id, null);
|
||||
}
|
||||
|
||||
secondBatchTCS.SetResult(null);
|
||||
|
|
@ -234,14 +234,14 @@ namespace Microsoft.AspNetCore.Components.Web.Rendering
|
|||
};
|
||||
|
||||
// Receive the ack for the intial batch
|
||||
_ = renderer.OnRenderCompleted(2, null);
|
||||
_ = renderer.OnRenderCompletedAsync(2, null);
|
||||
// Receive the ack for the second batch
|
||||
_ = renderer.OnRenderCompleted(3, null);
|
||||
_ = renderer.OnRenderCompletedAsync(3, null);
|
||||
|
||||
firstBatchTCS.SetResult(null);
|
||||
secondBatchTCS.SetResult(null);
|
||||
// Repeat the ack for the third batch
|
||||
_ = renderer.OnRenderCompleted(3, null);
|
||||
_ = renderer.OnRenderCompletedAsync(3, null);
|
||||
|
||||
// Assert
|
||||
Assert.Empty(exceptions);
|
||||
|
|
@ -297,14 +297,14 @@ namespace Microsoft.AspNetCore.Components.Web.Rendering
|
|||
};
|
||||
|
||||
// Receive the ack for the intial batch
|
||||
_ = renderer.OnRenderCompleted(2, null);
|
||||
_ = renderer.OnRenderCompletedAsync(2, null);
|
||||
// Receive the ack for the second batch
|
||||
_ = renderer.OnRenderCompleted(2, null);
|
||||
_ = renderer.OnRenderCompletedAsync(2, null);
|
||||
|
||||
firstBatchTCS.SetResult(null);
|
||||
secondBatchTCS.SetResult(null);
|
||||
// Repeat the ack for the third batch
|
||||
_ = renderer.OnRenderCompleted(3, null);
|
||||
_ = renderer.OnRenderCompletedAsync(3, null);
|
||||
|
||||
// Assert
|
||||
Assert.Empty(exceptions);
|
||||
|
|
@ -358,7 +358,7 @@ namespace Microsoft.AspNetCore.Components.Web.Rendering
|
|||
};
|
||||
|
||||
// Pretend that we missed the ack for the initial batch
|
||||
_ = renderer.OnRenderCompleted(3, null);
|
||||
_ = renderer.OnRenderCompletedAsync(3, null);
|
||||
firstBatchTCS.SetResult(null);
|
||||
secondBatchTCS.SetResult(null);
|
||||
|
||||
|
|
@ -414,12 +414,11 @@ namespace Microsoft.AspNetCore.Components.Web.Rendering
|
|||
exceptions.Add(e);
|
||||
};
|
||||
|
||||
_ = renderer.OnRenderCompleted(4, null);
|
||||
var exception = await Assert.ThrowsAsync<InvalidOperationException>(() => renderer.OnRenderCompletedAsync(4, null));
|
||||
firstBatchTCS.SetResult(null);
|
||||
secondBatchTCS.SetResult(null);
|
||||
|
||||
// Assert
|
||||
var exception = Assert.Single(exceptions);
|
||||
Assert.Equal(
|
||||
"Received an acknowledgement for batch with id '4' when the last batch produced was '3'.",
|
||||
exception.Message);
|
||||
|
|
|
|||
|
|
@ -11,11 +11,20 @@ namespace Microsoft.AspNetCore.Components.Web
|
|||
{
|
||||
// This class represents the second half of parsing incoming event data,
|
||||
// once the type of the eventArgs becomes known.
|
||||
|
||||
public static WebEventData Parse(string eventDescriptorJson, string eventArgsJson)
|
||||
{
|
||||
WebEventDescriptor eventDescriptor;
|
||||
try
|
||||
{
|
||||
eventDescriptor = Deserialize<WebEventDescriptor>(eventDescriptorJson);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new InvalidOperationException("Error parsing the event descriptor", e);
|
||||
}
|
||||
|
||||
return Parse(
|
||||
Deserialize<WebEventDescriptor>(eventDescriptorJson),
|
||||
eventDescriptor,
|
||||
eventArgsJson);
|
||||
}
|
||||
|
||||
|
|
@ -25,7 +34,7 @@ namespace Microsoft.AspNetCore.Components.Web
|
|||
eventDescriptor.BrowserRendererId,
|
||||
eventDescriptor.EventHandlerId,
|
||||
InterpretEventFieldInfo(eventDescriptor.EventFieldInfo),
|
||||
ParseEventArgsJson(eventDescriptor.EventArgsType, eventArgsJson));
|
||||
ParseEventArgsJson(eventDescriptor.EventHandlerId, eventDescriptor.EventArgsType, eventArgsJson));
|
||||
}
|
||||
|
||||
private WebEventData(int browserRendererId, ulong eventHandlerId, EventFieldInfo eventFieldInfo, EventArgs eventArgs)
|
||||
|
|
@ -44,43 +53,34 @@ namespace Microsoft.AspNetCore.Components.Web
|
|||
|
||||
public EventArgs EventArgs { get; }
|
||||
|
||||
private static EventArgs ParseEventArgsJson(string eventArgsType, string eventArgsJson)
|
||||
private static EventArgs ParseEventArgsJson(ulong eventHandlerId, string eventArgsType, string eventArgsJson)
|
||||
{
|
||||
switch (eventArgsType)
|
||||
try
|
||||
{
|
||||
case "change":
|
||||
return DeserializeChangeEventArgs(eventArgsJson);
|
||||
case "clipboard":
|
||||
return Deserialize<UIClipboardEventArgs>(eventArgsJson);
|
||||
case "drag":
|
||||
return Deserialize<UIDragEventArgs>(eventArgsJson);
|
||||
case "error":
|
||||
return Deserialize<UIErrorEventArgs>(eventArgsJson);
|
||||
case "focus":
|
||||
return Deserialize<UIFocusEventArgs>(eventArgsJson);
|
||||
case "keyboard":
|
||||
return Deserialize<UIKeyboardEventArgs>(eventArgsJson);
|
||||
case "mouse":
|
||||
return Deserialize<UIMouseEventArgs>(eventArgsJson);
|
||||
case "pointer":
|
||||
return Deserialize<UIPointerEventArgs>(eventArgsJson);
|
||||
case "progress":
|
||||
return Deserialize<UIProgressEventArgs>(eventArgsJson);
|
||||
case "touch":
|
||||
return Deserialize<UITouchEventArgs>(eventArgsJson);
|
||||
case "unknown":
|
||||
return EventArgs.Empty;
|
||||
case "wheel":
|
||||
return Deserialize<UIWheelEventArgs>(eventArgsJson);
|
||||
default:
|
||||
throw new ArgumentException($"Unsupported value '{eventArgsType}'.", nameof(eventArgsType));
|
||||
return eventArgsType switch
|
||||
{
|
||||
"change" => DeserializeChangeEventArgs(eventArgsJson),
|
||||
"clipboard" => Deserialize<UIClipboardEventArgs>(eventArgsJson),
|
||||
"drag" => Deserialize<UIDragEventArgs>(eventArgsJson),
|
||||
"error" => Deserialize<UIErrorEventArgs>(eventArgsJson),
|
||||
"focus" => Deserialize<UIFocusEventArgs>(eventArgsJson),
|
||||
"keyboard" => Deserialize<UIKeyboardEventArgs>(eventArgsJson),
|
||||
"mouse" => Deserialize<UIMouseEventArgs>(eventArgsJson),
|
||||
"pointer" => Deserialize<UIPointerEventArgs>(eventArgsJson),
|
||||
"progress" => Deserialize<UIProgressEventArgs>(eventArgsJson),
|
||||
"touch" => Deserialize<UITouchEventArgs>(eventArgsJson),
|
||||
"unknown" => EventArgs.Empty,
|
||||
"wheel" => Deserialize<UIWheelEventArgs>(eventArgsJson),
|
||||
_ => throw new InvalidOperationException($"Unsupported event type '{eventArgsType}'. EventId: '{eventHandlerId}'."),
|
||||
};
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new InvalidOperationException($"There was an error parsing the event arguments. EventId: '{eventHandlerId}'.", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static T Deserialize<T>(string json)
|
||||
{
|
||||
return JsonSerializer.Deserialize<T>(json, JsonSerializerOptionsProvider.Options);
|
||||
}
|
||||
private static T Deserialize<T>(string json) => JsonSerializer.Deserialize<T>(json, JsonSerializerOptionsProvider.Options);
|
||||
|
||||
private static EventFieldInfo InterpretEventFieldInfo(EventFieldInfo fieldInfo)
|
||||
{
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -37,8 +37,13 @@ async function boot(userOptions?: Partial<BlazorOptions>): Promise<void> {
|
|||
}
|
||||
|
||||
const reconnection = existingConnection || await initializeConnection(options, logger);
|
||||
if (reconnection.state !== signalR.HubConnectionState.Connected) {
|
||||
logger.log(LogLevel.Information, 'Reconnection attempt failed. Unable to connect to the server.');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(await circuit.reconnect(reconnection))) {
|
||||
logger.log(LogLevel.Information, 'Reconnection attempt failed.');
|
||||
logger.log(LogLevel.Information, 'Reconnection attempt to the circuit failed.');
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -31,12 +31,18 @@ export class DefaultReconnectDisplay implements ReconnectDisplay {
|
|||
];
|
||||
|
||||
this.modal.style.cssText = modalStyles.join(';');
|
||||
this.modal.innerHTML = '<h5 style="margin-top: 20px"></h5><button style="margin:5px auto 5px">Retry?</button><p>Alternatively, <a href>reload</a></p>';
|
||||
this.modal.innerHTML = '<h5 style="margin-top: 20px"></h5><button style="margin:5px auto 5px">Retry</button><p>Alternatively, <a href>reload</a></p>';
|
||||
this.message = this.modal.querySelector('h5')!;
|
||||
this.button = this.modal.querySelector('button')!;
|
||||
this.reloadParagraph = this.modal.querySelector('p')!;
|
||||
|
||||
this.button.addEventListener('click', () => window['Blazor'].reconnect());
|
||||
this.button.addEventListener('click', async () => {
|
||||
this.show();
|
||||
const successful = await window['Blazor'].reconnect();
|
||||
if (!successful) {
|
||||
this.failed();
|
||||
}
|
||||
});
|
||||
this.reloadParagraph.querySelector('a')!.addEventListener('click', () => location.reload());
|
||||
}
|
||||
|
||||
|
|
@ -57,7 +63,7 @@ export class DefaultReconnectDisplay implements ReconnectDisplay {
|
|||
|
||||
failed(): void {
|
||||
this.button.style.display = 'block';
|
||||
this.reloadParagraph.style.display = 'block';
|
||||
this.message.textContent = 'Failed to reconnect to the server.';
|
||||
this.reloadParagraph.style.display = 'none';
|
||||
this.message.innerHTML = 'Reconnection failed. Try <a href>reloading</a> the page if you\'re unable to reconnect.';
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ describe('DefaultReconnectDisplay', () => {
|
|||
display.failed();
|
||||
|
||||
expect(display.modal.style.display).toBe('block');
|
||||
expect(display.message.textContent).toBe('Failed to reconnect to the server.');
|
||||
expect(display.message.innerHTML).toBe('Reconnection failed. Try <a href=\"\">reloading</a> the page if you\'re unable to reconnect.');
|
||||
expect(display.button.style.display).toBe('block');
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -58,24 +58,23 @@ describe('DefaultReconnectionHandler', () => {
|
|||
expect(reconnect).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
// Skipped while under investigation: https://github.com/aspnet/AspNetCore/issues/12578
|
||||
// it('invokes failed if reconnect fails', async () => {
|
||||
// const testDisplay = createTestDisplay();
|
||||
// const reconnect = jest.fn().mockRejectedValue(null);
|
||||
// const handler = new DefaultReconnectionHandler(NullLogger.instance, testDisplay, reconnect);
|
||||
// window.console.error = jest.fn();
|
||||
it('invokes failed if reconnect fails', async () => {
|
||||
const testDisplay = createTestDisplay();
|
||||
const reconnect = jest.fn().mockRejectedValue(null);
|
||||
const handler = new DefaultReconnectionHandler(NullLogger.instance, testDisplay, reconnect);
|
||||
window.console.error = jest.fn();
|
||||
|
||||
// handler.onConnectionDown({
|
||||
// maxRetries: 3,
|
||||
// retryIntervalMilliseconds: 20,
|
||||
// dialogId: 'ignored'
|
||||
// });
|
||||
handler.onConnectionDown({
|
||||
maxRetries: 2,
|
||||
retryIntervalMilliseconds: 5,
|
||||
dialogId: 'ignored'
|
||||
});
|
||||
|
||||
// await delay(500);
|
||||
// expect(testDisplay.show).toHaveBeenCalled();
|
||||
// expect(testDisplay.failed).toHaveBeenCalled();
|
||||
// expect(reconnect).toHaveBeenCalledTimes(3);
|
||||
// });
|
||||
await delay(500);
|
||||
expect(testDisplay.show).toHaveBeenCalled();
|
||||
expect(testDisplay.failed).toHaveBeenCalled();
|
||||
expect(reconnect).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
|
||||
function attachUserSpecifiedUI(options: ReconnectionOptions): Element {
|
||||
|
|
|
|||
|
|
@ -6,9 +6,12 @@ using System.Collections.Concurrent;
|
|||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Diagnostics;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using Ignitor;
|
||||
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures;
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
using Microsoft.AspNetCore.SignalR.Client;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
|
@ -20,7 +23,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
|||
{
|
||||
public class ComponentHubReliabilityTest : IClassFixture<AspNetSiteServerFixture>, IDisposable
|
||||
{
|
||||
private static readonly TimeSpan DefaultLatencyTimeout = TimeSpan.FromSeconds(10);
|
||||
private static readonly TimeSpan DefaultLatencyTimeout = TimeSpan.FromSeconds(Debugger.IsAttached ? 60 : 10);
|
||||
private readonly AspNetSiteServerFixture _serverFixture;
|
||||
|
||||
public ComponentHubReliabilityTest(AspNetSiteServerFixture serverFixture, ITestOutputHelper output)
|
||||
|
|
@ -52,7 +55,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
|||
return new Exception(error + Environment.NewLine + logs);
|
||||
};
|
||||
|
||||
_ = _serverFixture.RootUri; // this is needed for the side-effects of getting the URI.
|
||||
_ = _serverFixture.RootUri; // this is needed for the side-effects of getting the URI.
|
||||
TestSink = _serverFixture.Host.Services.GetRequiredService<TestSink>();
|
||||
TestSink.MessageLogged += LogMessages;
|
||||
}
|
||||
|
|
@ -199,6 +202,118 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
|||
Assert.Contains(Logs, l => (l.LogLevel, l.Message) == (LogLevel.Debug, "Call to 'DispatchBrowserEvent' received before the circuit host initialization"));
|
||||
}
|
||||
|
||||
private async Task GoToTestComponent(IList<Batch> batches)
|
||||
{
|
||||
var rootUri = _serverFixture.RootUri;
|
||||
Assert.True(await Client.ConnectAsync(new Uri(rootUri, "/subdir"), prerendered: false), "Couldn't connect to the app");
|
||||
Assert.Single(batches);
|
||||
|
||||
await Client.SelectAsync("test-selector-select", "BasicTestApp.CounterComponent");
|
||||
Assert.Equal(2, batches.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DispatchingAnInvalidEventArgument_DoesNotProduceWarnings()
|
||||
{
|
||||
// Arrange
|
||||
var expectedError = $"There was an unhandled exception on the current circuit, so this circuit will be terminated. For more details turn on " +
|
||||
$"detailed exceptions in 'CircuitOptions.DetailedErrors'. Bad input data.";
|
||||
|
||||
var eventDescriptor = Serialize(new WebEventDescriptor()
|
||||
{
|
||||
BrowserRendererId = 0,
|
||||
EventHandlerId = 3,
|
||||
EventArgsType = "mouse",
|
||||
});
|
||||
|
||||
await GoToTestComponent(Batches);
|
||||
Assert.Equal(2, Batches.Count);
|
||||
|
||||
// Act
|
||||
await Client.ExpectCircuitError(() => Client.HubConnection.SendAsync(
|
||||
"DispatchBrowserEvent",
|
||||
eventDescriptor,
|
||||
"{sadfadsf]"));
|
||||
|
||||
// Assert
|
||||
var actualError = Assert.Single(Errors);
|
||||
Assert.Equal(expectedError, actualError);
|
||||
Assert.DoesNotContain(Logs, l => l.LogLevel > LogLevel.Information);
|
||||
Assert.Contains(Logs, l => (l.LogLevel, l.Exception?.Message) == (LogLevel.Debug, "There was an error parsing the event arguments. EventId: '3'."));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DispatchingAnInvalidEvent_DoesNotTriggerWarnings()
|
||||
{
|
||||
// Arrange
|
||||
var expectedError = $"There was an unhandled exception on the current circuit, so this circuit will be terminated. For more details turn on " +
|
||||
$"detailed exceptions in 'CircuitOptions.DetailedErrors'. Failed to dispatch event.";
|
||||
|
||||
var eventDescriptor = Serialize(new WebEventDescriptor()
|
||||
{
|
||||
BrowserRendererId = 0,
|
||||
EventHandlerId = 1990,
|
||||
EventArgsType = "mouse",
|
||||
});
|
||||
|
||||
var eventArgs = new UIMouseEventArgs
|
||||
{
|
||||
Type = "click",
|
||||
Detail = 1,
|
||||
ScreenX = 47,
|
||||
ScreenY = 258,
|
||||
ClientX = 47,
|
||||
ClientY = 155,
|
||||
};
|
||||
|
||||
await GoToTestComponent(Batches);
|
||||
Assert.Equal(2, Batches.Count);
|
||||
|
||||
// Act
|
||||
await Client.ExpectCircuitError(() => Client.HubConnection.SendAsync(
|
||||
"DispatchBrowserEvent",
|
||||
eventDescriptor,
|
||||
Serialize(eventArgs)));
|
||||
|
||||
// Assert
|
||||
var actualError = Assert.Single(Errors);
|
||||
Assert.Equal(expectedError, actualError);
|
||||
Assert.DoesNotContain(Logs, l => l.LogLevel > LogLevel.Information);
|
||||
Assert.Contains(Logs, l => (l.LogLevel, l.Message, l.Exception?.Message) ==
|
||||
(LogLevel.Debug,
|
||||
"There was an error dispatching the event '1990' to the application.",
|
||||
"There is no event handler associated with this event. EventId: '1990'. (Parameter 'eventHandlerId')"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DispatchingAnInvalidRenderAcknowledgement_DoesNotTriggerWarnings()
|
||||
{
|
||||
// Arrange
|
||||
var expectedError = $"There was an unhandled exception on the current circuit, so this circuit will be terminated. For more details turn on " +
|
||||
$"detailed exceptions in 'CircuitOptions.DetailedErrors'. Failed to complete render batch '1846'.";
|
||||
|
||||
await GoToTestComponent(Batches);
|
||||
Assert.Equal(2, Batches.Count);
|
||||
|
||||
Client.ConfirmRenderBatch = false;
|
||||
await Client.ClickAsync("counter");
|
||||
|
||||
// Act
|
||||
await Client.ExpectCircuitError(() => Client.HubConnection.SendAsync(
|
||||
"OnRenderCompleted",
|
||||
1846,
|
||||
null));
|
||||
|
||||
// Assert
|
||||
var actualError = Assert.Single(Errors);
|
||||
Assert.Equal(expectedError, actualError);
|
||||
Assert.DoesNotContain(Logs, l => l.LogLevel > LogLevel.Information);
|
||||
Assert.Contains(Logs, l => (l.LogLevel, l.Message, l.Exception?.Message) ==
|
||||
(LogLevel.Debug,
|
||||
$"Failed to complete render batch '1846' in circuit host '{Client.CircuitId}'.",
|
||||
"Received an acknowledgement for batch with id '1846' when the last batch produced was '4'."));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CannotInvokeOnRenderCompletedBeforeInitialization()
|
||||
{
|
||||
|
|
@ -249,7 +364,10 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
|||
public async Task OnLocationChanged_ReportsDebugForExceptionInValidation()
|
||||
{
|
||||
// Arrange
|
||||
var expectedError = "Location change to http://example.com failed.";
|
||||
var expectedError = "There was an unhandled exception on the current circuit, so this circuit will be terminated. " +
|
||||
"For more details turn on detailed exceptions in 'CircuitOptions.DetailedErrors'. " +
|
||||
"Location change to 'http://example.com' failed.";
|
||||
|
||||
var rootUri = _serverFixture.RootUri;
|
||||
var baseUri = new Uri(rootUri, "/subdir");
|
||||
Assert.True(await Client.ConnectAsync(baseUri, prerendered: false), "Couldn't connect to the app");
|
||||
|
|
@ -267,7 +385,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
|||
Assert.DoesNotContain(Logs, l => l.LogLevel > LogLevel.Information);
|
||||
Assert.Contains(Logs, l =>
|
||||
{
|
||||
return l.LogLevel == LogLevel.Debug && Regex.IsMatch(l.Message, "Location change to http://example.com in circuit .* failed.");
|
||||
return (l.LogLevel, l.Message) == (LogLevel.Debug, $"Location change to 'http://example.com' in circuit '{Client.CircuitId}' failed.");
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -275,7 +393,10 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
|||
public async Task OnLocationChanged_ReportsErrorForExceptionInUserCode()
|
||||
{
|
||||
// Arrange
|
||||
var expectedError = "There was an unhandled exception .?";
|
||||
var expectedError = "There was an unhandled exception on the current circuit, so this circuit will be terminated. " +
|
||||
"For more details turn on detailed exceptions in 'CircuitOptions.DetailedErrors'. " +
|
||||
"Location change failed.";
|
||||
|
||||
var rootUri = _serverFixture.RootUri;
|
||||
var baseUri = new Uri(rootUri, "/subdir");
|
||||
Assert.True(await Client.ConnectAsync(baseUri, prerendered: false), "Couldn't connect to the app");
|
||||
|
|
@ -287,14 +408,14 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
|||
await Client.ExpectCircuitError(() => Client.HubConnection.SendAsync(
|
||||
"OnLocationChanged",
|
||||
new Uri(baseUri, "/test").AbsoluteUri,
|
||||
false));
|
||||
false));
|
||||
|
||||
// Assert
|
||||
var actualError = Assert.Single(Errors);
|
||||
Assert.Matches(expectedError, actualError);
|
||||
Assert.Equal(expectedError, actualError);
|
||||
Assert.Contains(Logs, l =>
|
||||
{
|
||||
return l.LogLevel == LogLevel.Error && Regex.IsMatch(l.Message, "Unhandled exception in circuit .*");
|
||||
return (l.LogLevel, l.Message) == (LogLevel.Error, $"Location change to '{new Uri(_serverFixture.RootUri,"/test")}' in circuit '{Client.CircuitId}' failed.");
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -373,6 +494,10 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
|||
TestSink.MessageLogged -= LogMessages;
|
||||
}
|
||||
|
||||
private string Serialize<T>(T browserEventDescriptor) =>
|
||||
JsonSerializer.Serialize(browserEventDescriptor, TestJsonSerializerOptionsProvider.Options);
|
||||
|
||||
[DebuggerDisplay("{LogLevel.ToString(),nq} - {Message ?? \"null\",nq} - {Exception?.Message,nq}")]
|
||||
private class LogMessage
|
||||
{
|
||||
public LogMessage(LogLevel logLevel, string message, Exception exception)
|
||||
|
|
@ -394,7 +519,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
|||
|
||||
private class Batch
|
||||
{
|
||||
public Batch(int id, byte [] data)
|
||||
public Batch(int id, byte[] data)
|
||||
{
|
||||
Id = id;
|
||||
Data = data;
|
||||
|
|
|
|||
|
|
@ -9,8 +9,11 @@ using System.Text.Json;
|
|||
using System.Threading.Tasks;
|
||||
using Ignitor;
|
||||
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures;
|
||||
using Microsoft.AspNetCore.Components.Rendering;
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
using Microsoft.AspNetCore.SignalR.Client;
|
||||
using Microsoft.AspNetCore.Testing;
|
||||
using Microsoft.AspNetCore.Testing.xunit;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Testing;
|
||||
|
|
@ -19,6 +22,7 @@ using Xunit.Abstractions;
|
|||
|
||||
namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
||||
{
|
||||
[Flaky("https://github.com/aspnet/AspNetCore/issues/13086", FlakyOn.All)]
|
||||
public class InteropReliabilityTests : IClassFixture<AspNetSiteServerFixture>, IDisposable
|
||||
{
|
||||
private static readonly TimeSpan DefaultLatencyTimeout = TimeSpan.FromSeconds(30);
|
||||
|
|
@ -260,6 +264,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
[Flaky("https://github.com/aspnet/AspNetCore/issues/13086", FlakyOn.AzP.Windows)]
|
||||
public async Task ContinuesWorkingAfterInvalidAsyncReturnCallback()
|
||||
{
|
||||
// Arrange
|
||||
|
|
@ -500,8 +505,8 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
|||
// Arrange
|
||||
await GoToTestComponent(Batches);
|
||||
var sink = _serverFixture.Host.Services.GetRequiredService<TestSink>();
|
||||
var logEvents = new List<(LogLevel logLevel, string)>();
|
||||
sink.MessageLogged += (wc) => logEvents.Add((wc.LogLevel, wc.EventId.Name));
|
||||
var logEvents = new List<(LogLevel logLevel, string eventIdName, Exception exception)>();
|
||||
sink.MessageLogged += (wc) => logEvents.Add((wc.LogLevel, wc.EventId.Name, wc.Exception));
|
||||
|
||||
// Act
|
||||
var browserDescriptor = new WebEventDescriptor()
|
||||
|
|
@ -520,8 +525,9 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
|||
});
|
||||
|
||||
Assert.Contains(
|
||||
(LogLevel.Debug, "DispatchEventFailedToParseEventData"),
|
||||
logEvents);
|
||||
logEvents,
|
||||
e => e.eventIdName == "DispatchEventFailedToParseEventData" && e.logLevel == LogLevel.Debug &&
|
||||
e.exception.Message == "There was an error parsing the event arguments. EventId: '6'.");
|
||||
|
||||
// Taking any other action will fail because the circuit is disposed.
|
||||
await Client.ExpectCircuitErrorAndDisconnect(async () =>
|
||||
|
|
@ -563,7 +569,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
|||
Assert.Contains(
|
||||
logEvents,
|
||||
e => e.eventIdName == "DispatchEventFailedToDispatchEvent" && e.logLevel == LogLevel.Debug &&
|
||||
e.exception is ArgumentException ae && ae.Message.Contains("There is no event handler with ID 1"));
|
||||
e.exception is ArgumentException ae && ae.Message.Contains("There is no event handler associated with this event. EventId: '1'."));
|
||||
|
||||
// Taking any other action will fail because the circuit is disposed.
|
||||
await Client.ExpectCircuitErrorAndDisconnect(async () =>
|
||||
|
|
|
|||
|
|
@ -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 Dispatcher. Use Invoke() or InvokeAsync() to switch execution to the Dispatcher when triggering rendering or modifying any state accessed during rendering.",
|
||||
$"{typeof(InvalidOperationException).FullName}: The current thread is not associated with the Dispatcher. Use InvokeAsync() to switch execution to the Dispatcher when triggering rendering or component state.",
|
||||
() => result.Text);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<h1>Counter</h1>
|
||||
<p>Current count: @currentCount</p>
|
||||
<p><button @onclick="@(handleClicks ? (Action)IncrementCount : null)">Click me</button></p>
|
||||
<p><button id="counter" @onclick="@(handleClicks ? (Action)IncrementCount : null)">Click me</button></p>
|
||||
|
||||
<label>
|
||||
<input type="checkbox" @bind="handleClicks" />
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
@page "/prerendered-interop"
|
||||
@using Microsoft.AspNetCore.Components
|
||||
@using Microsoft.JSInterop
|
||||
@inject IComponentContext ComponentContext
|
||||
@inject IJSRuntime JSRuntime
|
||||
|
||||
<p>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
@page "/prerendered-transition"
|
||||
@using Microsoft.AspNetCore.Components
|
||||
@using Microsoft.AspNetCore.Components.Authorization
|
||||
@inject IComponentContext ComponentContext
|
||||
|
||||
<CascadingAuthenticationState>
|
||||
<AuthorizeView>
|
||||
|
|
@ -11,7 +10,7 @@
|
|||
|
||||
<p>
|
||||
Current state:
|
||||
<strong id="connected-state">@(ComponentContext.IsConnected ? "connected" : "not connected")</strong>
|
||||
<strong id="connected-state">@(hasRenderedInConnectedMode ? "connected" : "not connected")</strong>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
|
|
@ -22,11 +21,13 @@
|
|||
</CascadingAuthenticationState>
|
||||
|
||||
@code {
|
||||
bool hasRenderedInConnectedMode;
|
||||
int count;
|
||||
protected override Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
hasRenderedInConnectedMode = true;
|
||||
// We need to queue another render when we connect, otherwise the
|
||||
// browser won't see anything.
|
||||
StateHasChanged();
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ using Microsoft.AspNetCore.Hosting;
|
|||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
|
||||
namespace ComponentsApp.Server
|
||||
{
|
||||
public class Startup
|
||||
|
|
|
|||
|
|
@ -3,10 +3,10 @@
|
|||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.AspNetCore.Routing.Matching;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
|
|
@ -51,8 +51,6 @@ namespace Microsoft.AspNetCore.Routing
|
|||
if (endpoint != null)
|
||||
{
|
||||
Log.MatchSkipped(_logger, endpoint);
|
||||
|
||||
// Someone else set the endpoint, we'll let them handle the clearing of the endpoint.
|
||||
return _next(httpContext);
|
||||
}
|
||||
|
||||
|
|
@ -88,7 +86,8 @@ namespace Microsoft.AspNetCore.Routing
|
|||
|
||||
}
|
||||
|
||||
private async Task SetRoutingAndContinue(HttpContext httpContext)
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private Task SetRoutingAndContinue(HttpContext httpContext)
|
||||
{
|
||||
// If there was no mutation of the endpoint then log failure
|
||||
var endpoint = httpContext.GetEndpoint();
|
||||
|
|
@ -108,18 +107,7 @@ namespace Microsoft.AspNetCore.Routing
|
|||
Log.MatchSuccess(_logger, endpoint);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await _next(httpContext);
|
||||
}
|
||||
finally
|
||||
{
|
||||
// This allows a second call in a single request (such as from the ErrorHandlerMiddleware) to perform routing again.
|
||||
httpContext.SetEndpoint(endpoint: null);
|
||||
|
||||
var routeValuesFeature = httpContext.Features.Get<IRouteValuesFeature>();
|
||||
routeValuesFeature?.RouteValues?.Clear();
|
||||
}
|
||||
return _next(httpContext);
|
||||
}
|
||||
|
||||
// Initialization is async to avoid blocking threads while reflection and things
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ namespace Microsoft.AspNetCore.Builder
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UseRouting_ServicesRegistered_Match_DoesNotSetFeature()
|
||||
public async Task UseRouting_ServicesRegistered_Match_DoesNotSetsFeature()
|
||||
{
|
||||
// Arrange
|
||||
var endpoint = new RouteEndpoint(
|
||||
|
|
@ -104,6 +104,7 @@ namespace Microsoft.AspNetCore.Builder
|
|||
// Assert
|
||||
var feature = httpContext.Features.Get<IEndpointFeature>();
|
||||
Assert.NotNull(feature);
|
||||
Assert.Same(endpoint, httpContext.GetEndpoint());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
|
|
@ -21,72 +21,7 @@ namespace Microsoft.AspNetCore.Routing
|
|||
public class EndpointRoutingMiddlewareTest
|
||||
{
|
||||
[Fact]
|
||||
public async Task Invoke_ChangedPath_ResultsInDifferentResult()
|
||||
{
|
||||
// Arrange
|
||||
var httpContext = CreateHttpContext();
|
||||
var matcher = new Mock<Matcher>();
|
||||
var pathToEndpoints = new Dictionary<string, Endpoint>()
|
||||
{
|
||||
["/initial"] = new Endpoint(c => Task.CompletedTask, new EndpointMetadataCollection(), "initialEndpoint"),
|
||||
["/changed"] = new Endpoint(c => Task.CompletedTask, new EndpointMetadataCollection(), "changedEndpoint")
|
||||
};
|
||||
matcher.Setup(m => m.MatchAsync(httpContext))
|
||||
.Callback<HttpContext>(context =>
|
||||
{
|
||||
var endpointToSet = pathToEndpoints[context.Request.Path];
|
||||
context.SetEndpoint(endpointToSet);
|
||||
})
|
||||
.Returns(Task.CompletedTask)
|
||||
.Verifiable();
|
||||
var matcherFactory = Mock.Of<MatcherFactory>(factory => factory.CreateMatcher(It.IsAny<EndpointDataSource>()) == matcher.Object);
|
||||
var middleware = CreateMiddleware(
|
||||
matcherFactory: matcherFactory,
|
||||
next: context =>
|
||||
{
|
||||
Assert.True(pathToEndpoints.TryGetValue(context.Request.Path, out var expectedEndpoint));
|
||||
|
||||
var currentEndpoint = context.GetEndpoint();
|
||||
Assert.Equal(expectedEndpoint, currentEndpoint);
|
||||
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
|
||||
// Act
|
||||
httpContext.Request.Path = "/initial";
|
||||
await middleware.Invoke(httpContext);
|
||||
httpContext.Request.Path = "/changed";
|
||||
await middleware.Invoke(httpContext);
|
||||
|
||||
// Assert
|
||||
matcher.Verify();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Invoke_OnException_ResetsEndpoint()
|
||||
{
|
||||
// Arrange
|
||||
var httpContext = CreateHttpContext();
|
||||
|
||||
var middleware = CreateMiddleware(next: context => throw new Exception());
|
||||
|
||||
// Act
|
||||
try
|
||||
{
|
||||
await middleware.Invoke(httpContext);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Do nothing, we expect the test to throw.
|
||||
}
|
||||
|
||||
// Assert
|
||||
var endpoint = httpContext.GetEndpoint();
|
||||
Assert.Null(endpoint);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Invoke_OnCall_SetsEndpointFeatureAndResetsEndpoint()
|
||||
public async Task Invoke_OnCall_SetsEndpointFeature()
|
||||
{
|
||||
// Arrange
|
||||
var httpContext = CreateHttpContext();
|
||||
|
|
@ -99,36 +34,14 @@ namespace Microsoft.AspNetCore.Routing
|
|||
// Assert
|
||||
var endpointFeature = httpContext.Features.Get<IEndpointFeature>();
|
||||
Assert.NotNull(endpointFeature);
|
||||
Assert.Null(endpointFeature.Endpoint);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Invoke_OnCall_SetsEndpointFeatureAndResetsRouteValues()
|
||||
public async Task Invoke_SkipsRouting_IfEndpointSet()
|
||||
{
|
||||
// Arrange
|
||||
var httpContext = CreateHttpContext();
|
||||
var initialRouteData = new RouteData();
|
||||
initialRouteData.Values["test"] = true;
|
||||
httpContext.Features.Set<IRoutingFeature>(new RoutingFeature()
|
||||
{
|
||||
RouteData = initialRouteData,
|
||||
});
|
||||
var middleware = CreateMiddleware();
|
||||
|
||||
// Act
|
||||
await middleware.Invoke(httpContext);
|
||||
|
||||
// Assert
|
||||
Assert.Null(httpContext.GetRouteValue("test"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Invoke_SkipsRoutingAndMaintainsEndpoint_IfEndpointSet()
|
||||
{
|
||||
// Arrange
|
||||
var httpContext = CreateHttpContext();
|
||||
var expectedEndpoint = new Endpoint(c => Task.CompletedTask, new EndpointMetadataCollection(), "myapp");
|
||||
httpContext.SetEndpoint(expectedEndpoint);
|
||||
httpContext.SetEndpoint(new Endpoint(c => Task.CompletedTask, new EndpointMetadataCollection(), "myapp"));
|
||||
|
||||
var middleware = CreateMiddleware();
|
||||
|
||||
|
|
@ -137,7 +50,7 @@ namespace Microsoft.AspNetCore.Routing
|
|||
|
||||
// Assert
|
||||
var endpoint = httpContext.GetEndpoint();
|
||||
Assert.Same(expectedEndpoint, endpoint);
|
||||
Assert.NotNull(endpoint);
|
||||
Assert.Equal("myapp", endpoint.DisplayName);
|
||||
}
|
||||
|
||||
|
|
@ -182,29 +95,22 @@ namespace Microsoft.AspNetCore.Routing
|
|||
{
|
||||
// Arrange
|
||||
var httpContext = CreateHttpContext();
|
||||
var nextCalled = false;
|
||||
|
||||
var middleware = CreateMiddleware(next: context =>
|
||||
{
|
||||
var routeData = httpContext.GetRouteData();
|
||||
var routeValue = httpContext.GetRouteValue("controller");
|
||||
var routeValuesFeature = httpContext.Features.Get<IRouteValuesFeature>();
|
||||
nextCalled = true;
|
||||
var middleware = CreateMiddleware();
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(routeData);
|
||||
Assert.Equal("Home", (string)routeValue);
|
||||
|
||||
// changing route data value is reflected in endpoint feature values
|
||||
routeData.Values["testKey"] = "testValue";
|
||||
Assert.Equal("testValue", routeValuesFeature.RouteValues["testKey"]);
|
||||
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
|
||||
// Act & Assert
|
||||
// Act
|
||||
await middleware.Invoke(httpContext);
|
||||
Assert.True(nextCalled);
|
||||
var routeData = httpContext.GetRouteData();
|
||||
var routeValue = httpContext.GetRouteValue("controller");
|
||||
var routeValuesFeature = httpContext.Features.Get<IRouteValuesFeature>();
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(routeData);
|
||||
Assert.Equal("Home", (string)routeValue);
|
||||
|
||||
// changing route data value is reflected in endpoint feature values
|
||||
routeData.Values["testKey"] = "testValue";
|
||||
Assert.Equal("testValue", routeValuesFeature.RouteValues["testKey"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -212,29 +118,22 @@ namespace Microsoft.AspNetCore.Routing
|
|||
{
|
||||
// Arrange
|
||||
var httpContext = CreateHttpContext();
|
||||
var called = false;
|
||||
|
||||
var middleware = CreateMiddleware(next: context =>
|
||||
{
|
||||
var routeData = httpContext.GetRouteData();
|
||||
var routeValue = httpContext.GetRouteValue("controller");
|
||||
var routeValuesFeature = httpContext.Features.Get<IRouteValuesFeature>();
|
||||
called = true;
|
||||
var middleware = CreateMiddleware();
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(routeData);
|
||||
Assert.Equal("Home", (string)routeValue);
|
||||
|
||||
// changing route data value is reflected in endpoint feature values
|
||||
routeData.Values["testKey"] = "testValue";
|
||||
Assert.Equal("testValue", routeValuesFeature.RouteValues["testKey"]);
|
||||
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
|
||||
// Act & Assert
|
||||
// Act
|
||||
await middleware.Invoke(httpContext);
|
||||
Assert.True(called);
|
||||
var routeData = httpContext.GetRouteData();
|
||||
var routeValue = httpContext.GetRouteValue("controller");
|
||||
var routeValuesFeature = httpContext.Features.Get<IRouteValuesFeature>();
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(routeData);
|
||||
Assert.Equal("Home", (string)routeValue);
|
||||
|
||||
// changing route data value is reflected in endpoint feature values
|
||||
routeData.Values["testKey"] = "testValue";
|
||||
Assert.Equal("testValue", routeValuesFeature.RouteValues["testKey"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ using System.Runtime.ExceptionServices;
|
|||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
|
|
@ -103,7 +104,8 @@ namespace Microsoft.AspNetCore.Diagnostics
|
|||
}
|
||||
try
|
||||
{
|
||||
context.Response.Clear();
|
||||
ClearHttpContext(context);
|
||||
|
||||
var exceptionHandlerFeature = new ExceptionHandlerFeature()
|
||||
{
|
||||
Error = edi.SourceException,
|
||||
|
|
@ -137,6 +139,17 @@ namespace Microsoft.AspNetCore.Diagnostics
|
|||
edi.Throw(); // Re-throw the original if we couldn't handle it
|
||||
}
|
||||
|
||||
private static void ClearHttpContext(HttpContext context)
|
||||
{
|
||||
context.Response.Clear();
|
||||
|
||||
// An endpoint may have already been set. Since we're going to re-invoke the middleware pipeline we need to reset
|
||||
// the endpoint and route values to ensure things are re-calculated.
|
||||
context.SetEndpoint(endpoint: null);
|
||||
var routeValuesFeature = context.Features.Get<IRouteValuesFeature>();
|
||||
routeValuesFeature?.RouteValues?.Clear();
|
||||
}
|
||||
|
||||
private static Task ClearCacheHeaders(object state)
|
||||
{
|
||||
var headers = ((HttpResponse)state).Headers;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,87 @@
|
|||
// 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.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Diagnostics
|
||||
{
|
||||
public class ExceptionHandlerMiddlewareTest
|
||||
{
|
||||
[Fact]
|
||||
public async Task Invoke_ExceptionThrownResultsInClearedRouteValuesAndEndpoint()
|
||||
{
|
||||
// Arrange
|
||||
var httpContext = CreateHttpContext();
|
||||
httpContext.SetEndpoint(new Endpoint((_) => Task.CompletedTask, new EndpointMetadataCollection(), "Test"));
|
||||
httpContext.Request.RouteValues["John"] = "Doe";
|
||||
|
||||
var optionsAccessor = CreateOptionsAccessor(
|
||||
exceptionHandler: context =>
|
||||
{
|
||||
Assert.Empty(context.Request.RouteValues);
|
||||
Assert.Null(context.GetEndpoint());
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
var middleware = CreateMiddleware(_ => throw new InvalidOperationException(), optionsAccessor);
|
||||
|
||||
// Act & Assert
|
||||
await middleware.Invoke(httpContext);
|
||||
}
|
||||
|
||||
private HttpContext CreateHttpContext()
|
||||
{
|
||||
var httpContext = new DefaultHttpContext
|
||||
{
|
||||
RequestServices = new TestServiceProvider()
|
||||
};
|
||||
|
||||
return httpContext;
|
||||
}
|
||||
|
||||
private IOptions<ExceptionHandlerOptions> CreateOptionsAccessor(
|
||||
RequestDelegate exceptionHandler = null,
|
||||
string exceptionHandlingPath = null)
|
||||
{
|
||||
exceptionHandler ??= c => Task.CompletedTask;
|
||||
var options = new ExceptionHandlerOptions()
|
||||
{
|
||||
ExceptionHandler = exceptionHandler,
|
||||
ExceptionHandlingPath = exceptionHandlingPath,
|
||||
};
|
||||
var optionsAccessor = Mock.Of<IOptions<ExceptionHandlerOptions>>(o => o.Value == options);
|
||||
return optionsAccessor;
|
||||
}
|
||||
|
||||
private ExceptionHandlerMiddleware CreateMiddleware(
|
||||
RequestDelegate next,
|
||||
IOptions<ExceptionHandlerOptions> options)
|
||||
{
|
||||
next ??= c => Task.CompletedTask;
|
||||
var listener = new DiagnosticListener("Microsoft.AspNetCore");
|
||||
|
||||
var middleware = new ExceptionHandlerMiddleware(
|
||||
next,
|
||||
NullLoggerFactory.Instance,
|
||||
options,
|
||||
listener);
|
||||
|
||||
return middleware;
|
||||
}
|
||||
|
||||
private class TestServiceProvider : IServiceProvider
|
||||
{
|
||||
public object GetService(Type serviceType)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -208,7 +208,6 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
services.TryAddScoped<StaticComponentRenderer>();
|
||||
services.TryAddScoped<NavigationManager, HttpNavigationManager>();
|
||||
services.TryAddScoped<IJSRuntime, UnsupportedJavaScriptRuntime>();
|
||||
services.TryAddScoped<IComponentContext, UnsupportedComponentContext>();
|
||||
services.TryAddScoped<INavigationInterception, UnsupportedNavigationInterception>();
|
||||
|
||||
services.TryAddTransient<ControllerSaveTempDataPropertyFilter>();
|
||||
|
|
|
|||
|
|
@ -1,12 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
||||
{
|
||||
internal class UnsupportedComponentContext : IComponentContext
|
||||
{
|
||||
public bool IsConnected => false;
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@
|
|||
<IsPackable>true</IsPackable>
|
||||
<IsTestProject>false</IsTestProject>
|
||||
<IsShippingPackage>true</IsShippingPackage>
|
||||
<PackOnBuild>true</PackOnBuild>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -9,58 +9,60 @@ namespace SignalRSamples.Hubs
|
|||
{
|
||||
public class Chat : Hub
|
||||
{
|
||||
public override async Task OnConnectedAsync()
|
||||
public override Task OnConnectedAsync()
|
||||
{
|
||||
await Clients.All.SendAsync("Send", $"{Context.ConnectionId} joined");
|
||||
var name = Context.GetHttpContext().Request.Query["name"];
|
||||
return Clients.All.SendAsync("Send", $"{name} joined the chat");
|
||||
}
|
||||
|
||||
public override async Task OnDisconnectedAsync(Exception ex)
|
||||
public override Task OnDisconnectedAsync(Exception exception)
|
||||
{
|
||||
await Clients.Others.SendAsync("Send", $"{Context.ConnectionId} left");
|
||||
var name = Context.GetHttpContext().Request.Query["name"];
|
||||
return Clients.All.SendAsync("Send", $"{name} left the chat");
|
||||
}
|
||||
|
||||
public Task Send(string message)
|
||||
public Task Send(string name, string message)
|
||||
{
|
||||
return Clients.All.SendAsync("Send", $"{Context.ConnectionId}: {message}");
|
||||
return Clients.All.SendAsync("Send", $"{name}: {message}");
|
||||
}
|
||||
|
||||
public Task SendToOthers(string message)
|
||||
public Task SendToOthers(string name, string message)
|
||||
{
|
||||
return Clients.Others.SendAsync("Send", $"{Context.ConnectionId}: {message}");
|
||||
return Clients.Others.SendAsync("Send", $"{name}: {message}");
|
||||
}
|
||||
|
||||
public Task SendToConnection(string connectionId, string message)
|
||||
public Task SendToConnection(string connectionId, string name, string message)
|
||||
{
|
||||
return Clients.Client(connectionId).SendAsync("Send", $"Private message from {Context.ConnectionId}: {message}");
|
||||
return Clients.Client(connectionId).SendAsync("Send", $"Private message from {name}: {message}");
|
||||
}
|
||||
|
||||
public Task SendToGroup(string groupName, string message)
|
||||
public Task SendToGroup(string groupName, string name ,string message)
|
||||
{
|
||||
return Clients.Group(groupName).SendAsync("Send", $"{Context.ConnectionId}@{groupName}: {message}");
|
||||
return Clients.Group(groupName).SendAsync("Send", $"{name}@{groupName}: {message}");
|
||||
}
|
||||
|
||||
public Task SendToOthersInGroup(string groupName, string message)
|
||||
public Task SendToOthersInGroup(string groupName, string name,string message)
|
||||
{
|
||||
return Clients.OthersInGroup(groupName).SendAsync("Send", $"{Context.ConnectionId}@{groupName}: {message}");
|
||||
return Clients.OthersInGroup(groupName).SendAsync("Send", $"{name}@{groupName}: {message}");
|
||||
}
|
||||
|
||||
public async Task JoinGroup(string groupName)
|
||||
public async Task JoinGroup(string groupName, string name)
|
||||
{
|
||||
await Groups.AddToGroupAsync(Context.ConnectionId, groupName);
|
||||
|
||||
await Clients.Group(groupName).SendAsync("Send", $"{Context.ConnectionId} joined {groupName}");
|
||||
await Clients.Group(groupName).SendAsync("Send", $"{name} joined {groupName}");
|
||||
}
|
||||
|
||||
public async Task LeaveGroup(string groupName)
|
||||
public async Task LeaveGroup(string groupName, string name)
|
||||
{
|
||||
await Clients.Group(groupName).SendAsync("Send", $"{Context.ConnectionId} left {groupName}");
|
||||
await Clients.Group(groupName).SendAsync("Send", $"{name} left {groupName}");
|
||||
|
||||
await Groups.RemoveFromGroupAsync(Context.ConnectionId, groupName);
|
||||
}
|
||||
|
||||
public Task Echo(string message)
|
||||
public Task Echo(string name, string message)
|
||||
{
|
||||
return Clients.Caller.SendAsync("Send", $"{Context.ConnectionId}: {message}");
|
||||
return Clients.Caller.SendAsync("Send", $"{name}: {message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,53 +9,55 @@ namespace SignalRSamples.Hubs
|
|||
{
|
||||
public class DynamicChat : DynamicHub
|
||||
{
|
||||
public override async Task OnConnectedAsync()
|
||||
public override Task OnConnectedAsync()
|
||||
{
|
||||
await Clients.All.Send($"{Context.ConnectionId} joined");
|
||||
var name = Context.GetHttpContext().Request.Query["name"];
|
||||
return Clients.All.Send($"{name} joined the chat");
|
||||
}
|
||||
|
||||
public override async Task OnDisconnectedAsync(Exception ex)
|
||||
public override Task OnDisconnectedAsync(Exception exception)
|
||||
{
|
||||
await Clients.Others.Send($"{Context.ConnectionId} left");
|
||||
var name = Context.GetHttpContext().Request.Query["name"];
|
||||
return Clients.All.Send($"{name} left the chat");
|
||||
}
|
||||
|
||||
public Task Send(string message)
|
||||
public Task Send(string name, string message)
|
||||
{
|
||||
return Clients.All.Send($"{Context.ConnectionId}: {message}");
|
||||
return Clients.All.Send($"{name}: {message}");
|
||||
}
|
||||
|
||||
public Task SendToOthers(string message)
|
||||
public Task SendToOthers(string name, string message)
|
||||
{
|
||||
return Clients.Others.Send($"{Context.ConnectionId}: {message}");
|
||||
return Clients.Others.Send($"{name}: {message}");
|
||||
}
|
||||
|
||||
public Task SendToGroup(string groupName, string message)
|
||||
public Task SendToGroup(string groupName, string name, string message)
|
||||
{
|
||||
return Clients.Group(groupName).Send($"{Context.ConnectionId}@{groupName}: {message}");
|
||||
return Clients.Group(groupName).Send($"{name}@{groupName}: {message}");
|
||||
}
|
||||
|
||||
public Task SendToOthersInGroup(string groupName, string message)
|
||||
public Task SendToOthersInGroup(string groupName, string name, string message)
|
||||
{
|
||||
return Clients.OthersInGroup(groupName).Send($"{Context.ConnectionId}@{groupName}: {message}");
|
||||
return Clients.OthersInGroup(groupName).Send($"{name}@{groupName}: {message}");
|
||||
}
|
||||
|
||||
public async Task JoinGroup(string groupName)
|
||||
public async Task JoinGroup(string groupName, string name)
|
||||
{
|
||||
await Groups.AddToGroupAsync(Context.ConnectionId, groupName);
|
||||
|
||||
await Clients.Group(groupName).Send($"{Context.ConnectionId} joined {groupName}");
|
||||
await Clients.Group(groupName).Send($"{name} joined {groupName}");
|
||||
}
|
||||
|
||||
public async Task LeaveGroup(string groupName)
|
||||
public async Task LeaveGroup(string groupName, string name)
|
||||
{
|
||||
await Clients.Group(groupName).Send($"{Context.ConnectionId} left {groupName}");
|
||||
await Clients.Group(groupName).Send($"{name} left {groupName}");
|
||||
|
||||
await Groups.RemoveFromGroupAsync(Context.ConnectionId, groupName);
|
||||
}
|
||||
|
||||
public Task Echo(string message)
|
||||
public Task Echo(string name, string message)
|
||||
{
|
||||
return Clients.Caller.Send($"{Context.ConnectionId}: {message}");
|
||||
return Clients.Caller.Send($"{name}: {message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,53 +9,55 @@ namespace SignalRSamples.Hubs
|
|||
{
|
||||
public class HubTChat : Hub<IChatClient>
|
||||
{
|
||||
public override async Task OnConnectedAsync()
|
||||
public override Task OnConnectedAsync()
|
||||
{
|
||||
await Clients.All.Send($"{Context.ConnectionId} joined");
|
||||
var name = Context.GetHttpContext().Request.Query["name"];
|
||||
return Clients.All.Send($"{name} joined the chat");
|
||||
}
|
||||
|
||||
public override async Task OnDisconnectedAsync(Exception ex)
|
||||
public override Task OnDisconnectedAsync(Exception exception)
|
||||
{
|
||||
await Clients.Others.Send($"{Context.ConnectionId} left");
|
||||
var name = Context.GetHttpContext().Request.Query["name"];
|
||||
return Clients.All.Send($"{name} left the chat");
|
||||
}
|
||||
|
||||
public Task Send(string message)
|
||||
public Task Send(string name, string message)
|
||||
{
|
||||
return Clients.All.Send($"{Context.ConnectionId}: {message}");
|
||||
return Clients.All.Send($"{name}: {message}");
|
||||
}
|
||||
|
||||
public Task SendToOthers(string message)
|
||||
public Task SendToOthers(string name, string message)
|
||||
{
|
||||
return Clients.Others.Send($"{Context.ConnectionId}: {message}");
|
||||
return Clients.Others.Send($"{name}: {message}");
|
||||
}
|
||||
|
||||
public Task SendToGroup(string groupName, string message)
|
||||
public Task SendToGroup(string groupName, string name, string message)
|
||||
{
|
||||
return Clients.Group(groupName).Send($"{Context.ConnectionId}@{groupName}: {message}");
|
||||
return Clients.Group(groupName).Send($"{name}@{groupName}: {message}");
|
||||
}
|
||||
|
||||
public Task SendToOthersInGroup(string groupName, string message)
|
||||
public Task SendToOthersInGroup(string groupName, string name, string message)
|
||||
{
|
||||
return Clients.OthersInGroup(groupName).Send($"{Context.ConnectionId}@{groupName}: {message}");
|
||||
return Clients.OthersInGroup(groupName).Send($"{name}@{groupName}: {message}");
|
||||
}
|
||||
|
||||
public async Task JoinGroup(string groupName)
|
||||
public async Task JoinGroup(string groupName, string name)
|
||||
{
|
||||
await Groups.AddToGroupAsync(Context.ConnectionId, groupName);
|
||||
|
||||
await Clients.Group(groupName).Send($"{Context.ConnectionId} joined {groupName}");
|
||||
await Clients.Group(groupName).Send($"{name} joined {groupName}");
|
||||
}
|
||||
|
||||
public async Task LeaveGroup(string groupName)
|
||||
public async Task LeaveGroup(string groupName, string name)
|
||||
{
|
||||
await Clients.Group(groupName).Send($"{Context.ConnectionId} left {groupName}");
|
||||
await Clients.Group(groupName).Send($"{name} left {groupName}");
|
||||
|
||||
await Groups.RemoveFromGroupAsync(Context.ConnectionId, groupName);
|
||||
}
|
||||
|
||||
public Task Echo(string message)
|
||||
public Task Echo(string name, string message)
|
||||
{
|
||||
return Clients.Caller.Send($"{Context.ConnectionId}: {message}");
|
||||
return Clients.Caller.Send($"{name}: {message}");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@
|
|||
<option value="hubT">Hub<T></option>
|
||||
</select>
|
||||
|
||||
<input type="text" id="displayname" placeholder="Enter User Name" />
|
||||
<input type="button" id="connect" value="Connect" />
|
||||
<input type="button" id="disconnect" value="Disconnect" />
|
||||
|
||||
|
|
@ -58,6 +59,7 @@
|
|||
<div class="input-append">
|
||||
<input type="text" id="me-message-text" placeholder="Type a message" />
|
||||
<input type="button" id="send" class="btn" value="Send to Me" />
|
||||
<input type="hidden" id="displayname" />
|
||||
</div>
|
||||
</form>
|
||||
|
||||
|
|
@ -138,6 +140,13 @@
|
|||
var connection;
|
||||
|
||||
click('connect', function (event) {
|
||||
|
||||
name = document.getElementById("displayname").value
|
||||
if (name === "") {
|
||||
alert("Please enter a valid name");
|
||||
return;
|
||||
}
|
||||
|
||||
let hubRoute = hubTypeDropdown.value || "default";
|
||||
let protocol = protocolDropdown.value === "msgpack" ?
|
||||
new signalR.protocols.msgpack.MessagePackHubProtocol() :
|
||||
|
|
@ -148,6 +157,7 @@
|
|||
options.transport = signalR.HttpTransportType[transportDropdown.value];
|
||||
}
|
||||
|
||||
hubRoute = hubRoute + "?name=" + name;
|
||||
console.log('http://' + document.location.host + '/' + hubRoute);
|
||||
|
||||
var connectionBuilder = new signalR.HubConnectionBuilder()
|
||||
|
|
@ -205,45 +215,45 @@
|
|||
|
||||
click('broadcast', function (event) {
|
||||
let data = getText('message-text');
|
||||
invoke(connection, 'Send', data);
|
||||
invoke(connection, 'Send', name, data);
|
||||
});
|
||||
|
||||
click('join-group', function (event) {
|
||||
let groupName = getText('group-name');
|
||||
invoke(connection, 'JoinGroup', groupName);
|
||||
invoke(connection, 'JoinGroup', groupName, name);
|
||||
});
|
||||
|
||||
click('leave-group', function (event) {
|
||||
let groupName = getText('group-name');
|
||||
invoke(connection, 'LeaveGroup', groupName);
|
||||
invoke(connection, 'LeaveGroup', groupName, name);
|
||||
});
|
||||
|
||||
click('groupmsg', function (event) {
|
||||
let groupName = getText('group-name');
|
||||
let message = getText('group-message-text');
|
||||
invoke(connection, 'SendToGroup', groupName, message);
|
||||
invoke(connection, 'SendToGroup', groupName, name, message);
|
||||
});
|
||||
|
||||
click('others-groupmsg', function (event) {
|
||||
let groupName = getText('group-name');
|
||||
let message = getText('group-message-text');
|
||||
invoke(connection, 'SendToOthersInGroup', groupName, message);
|
||||
invoke(connection, 'SendToOthersInGroup', groupName, name, message);
|
||||
});
|
||||
|
||||
click('send', function (event) {
|
||||
let data = getText('me-message-text');
|
||||
invoke(connection, 'Echo', data);
|
||||
invoke(connection, 'Echo', name, data);
|
||||
});
|
||||
|
||||
click('broadcast-exceptme', function (event) {
|
||||
let data = getText('message-text');
|
||||
invoke(connection, 'SendToOthers', data);
|
||||
invoke(connection, 'SendToOthers', name, data);
|
||||
});
|
||||
|
||||
click('connection-send', function (event) {
|
||||
let data = getText('connection-message-text');
|
||||
let id = getText('connection-id');
|
||||
invoke(connection, 'SendToConnection', id, data);
|
||||
invoke(connection, 'SendToConnection', id, name, data);
|
||||
});
|
||||
|
||||
</script>
|
||||
|
|
|
|||
Loading…
Reference in New Issue