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
|
.\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?
|
## 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
|
# 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'
|
$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) {
|
if ($changedFiles) {
|
||||||
foreach ($file in $changedFiles) {
|
foreach ($file in $changedFiles) {
|
||||||
|
if ($file -eq $changedFilesExclusion) {continue}
|
||||||
|
|
||||||
$filePath = Resolve-Path "${repoRoot}/${file}"
|
$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
|
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
|
& git --no-pager diff --ignore-space-at-eol $filePath
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@
|
||||||
<HelixAvailablePlatform Include="OSX" />
|
<HelixAvailablePlatform Include="OSX" />
|
||||||
<HelixAvailablePlatform Include="Linux" />
|
<HelixAvailablePlatform Include="Linux" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<!-- x64 queues -->
|
<!-- x64 queues -->
|
||||||
<ItemGroup Condition="'$(IsWindowsOnlyTest)' != 'true' AND '$(TargetArchitecture)' == 'x64'">
|
<ItemGroup Condition="'$(IsWindowsOnlyTest)' != 'true' AND '$(TargetArchitecture)' == 'x64'">
|
||||||
<HelixAvailableTargetQueue Include="Windows.10.Amd64.ClientRS4.VS2017.Open" Platform="Windows" />
|
<HelixAvailableTargetQueue Include="Windows.10.Amd64.ClientRS4.VS2017.Open" Platform="Windows" />
|
||||||
|
|
@ -27,11 +27,11 @@
|
||||||
<HelixAvailableTargetQueue Include="Redhat.7.Amd64.Open" Platform="Linux" />
|
<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" />
|
<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>
|
||||||
|
|
||||||
<ItemGroup Condition="'$(IsWindowsOnlyTest)' != 'true' AND '$(TargetArchitecture)' == 'arm64'">
|
<ItemGroup Condition="'$(IsWindowsOnlyTest)' != 'true' AND '$(TargetArchitecture)' == 'arm64'">
|
||||||
<!-- arm64 queues -->
|
<!-- 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
|
<!-- 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="(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" />
|
<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>
|
<NpmTestArgs>test</NpmTestArgs>
|
||||||
<Configuration Condition="'$(Configuration)' == '' AND '$(ContinuousIntegrationBuild)' == 'true'">Release</Configuration>
|
<Configuration Condition="'$(Configuration)' == '' AND '$(ContinuousIntegrationBuild)' == 'true'">Release</Configuration>
|
||||||
<Configuration Condition="'$(Configuration)' == ''">Debug</Configuration>
|
<Configuration Condition="'$(Configuration)' == ''">Debug</Configuration>
|
||||||
|
<PackOnBuild>false</PackOnBuild>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,9 @@
|
||||||
BuildInParallel="true" />
|
BuildInParallel="true" />
|
||||||
</Target>
|
</Target>
|
||||||
|
|
||||||
<Target Name="Build" DependsOnTargets="$(BuildDependsOn)" />
|
<Target Name="Build" DependsOnTargets="$(BuildDependsOn)">
|
||||||
|
<CallTarget Targets="_Pack" Condition="'$(PackOnBuild)' == 'true'" />
|
||||||
|
</Target>
|
||||||
|
|
||||||
<Target Name="_Build"
|
<Target Name="_Build"
|
||||||
Condition="'$(IsBuildable)' != 'false'"
|
Condition="'$(IsBuildable)' != 'false'"
|
||||||
|
|
@ -66,11 +68,12 @@
|
||||||
</PackDependsOn>
|
</PackDependsOn>
|
||||||
<PackDependsOn Condition="'$(NoBuild)' != 'true'">
|
<PackDependsOn Condition="'$(NoBuild)' != 'true'">
|
||||||
$(PackDependsOn);
|
$(PackDependsOn);
|
||||||
Build
|
Build;
|
||||||
|
_Pack
|
||||||
</PackDependsOn>
|
</PackDependsOn>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<Target Name="Pack" Condition="'$(IsPackable)' == 'true'" DependsOnTargets="$(PackDependsOn)">
|
<Target Name="_Pack" Condition="'$(IsPackable)' == 'true'" >
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<_PackageTargetPath>$(MSBuildProjectDirectory)\$(PackageFileName)</_PackageTargetPath>
|
<_PackageTargetPath>$(MSBuildProjectDirectory)\$(PackageFileName)</_PackageTargetPath>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
@ -88,6 +91,8 @@
|
||||||
<OnError ExecuteTargets="_RestoreBackupPackageJsonFile" />
|
<OnError ExecuteTargets="_RestoreBackupPackageJsonFile" />
|
||||||
</Target>
|
</Target>
|
||||||
|
|
||||||
|
<Target Name="Pack" Condition="'$(IsPackable)' == 'true'" DependsOnTargets="$(PackDependsOn)" />
|
||||||
|
|
||||||
<Target Name="_RestoreBackupPackageJsonFile">
|
<Target Name="_RestoreBackupPackageJsonFile">
|
||||||
<Move SourceFiles="$(_BackupPackageJson)" DestinationFiles="$(PackageJson)" />
|
<Move SourceFiles="$(_BackupPackageJson)" DestinationFiles="$(PackageJson)" />
|
||||||
</Target>
|
</Target>
|
||||||
|
|
|
||||||
|
|
@ -91,7 +91,6 @@ namespace Microsoft.AspNetCore.Blazor.Hosting
|
||||||
services.AddSingleton(_BrowserHostBuilderContext);
|
services.AddSingleton(_BrowserHostBuilderContext);
|
||||||
services.AddSingleton<IWebAssemblyHost, WebAssemblyHost>();
|
services.AddSingleton<IWebAssemblyHost, WebAssemblyHost>();
|
||||||
services.AddSingleton<IJSRuntime>(WebAssemblyJSRuntime.Instance);
|
services.AddSingleton<IJSRuntime>(WebAssemblyJSRuntime.Instance);
|
||||||
services.AddSingleton<IComponentContext, WebAssemblyComponentContext>();
|
|
||||||
services.AddSingleton<NavigationManager>(WebAssemblyNavigationManager.Instance);
|
services.AddSingleton<NavigationManager>(WebAssemblyNavigationManager.Instance);
|
||||||
services.AddSingleton<INavigationInterception>(WebAssemblyNavigationInterception.Instance);
|
services.AddSingleton<INavigationInterception>(WebAssemblyNavigationInterception.Instance);
|
||||||
services.AddSingleton<ILoggerFactory, WebAssemblyLoggerFactory>();
|
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
|
public abstract partial class Dispatcher
|
||||||
{
|
{
|
||||||
protected Dispatcher() { }
|
protected Dispatcher() { }
|
||||||
|
public void AssertAccess() { }
|
||||||
public abstract bool CheckAccess();
|
public abstract bool CheckAccess();
|
||||||
public static Microsoft.AspNetCore.Components.Dispatcher CreateDefault() { throw null; }
|
public static Microsoft.AspNetCore.Components.Dispatcher CreateDefault() { throw null; }
|
||||||
public abstract System.Threading.Tasks.Task InvokeAsync(System.Action workItem);
|
public abstract System.Threading.Tasks.Task InvokeAsync(System.Action workItem);
|
||||||
|
|
@ -196,10 +197,6 @@ namespace Microsoft.AspNetCore.Components
|
||||||
void Attach(Microsoft.AspNetCore.Components.RenderHandle renderHandle);
|
void Attach(Microsoft.AspNetCore.Components.RenderHandle renderHandle);
|
||||||
System.Threading.Tasks.Task SetParametersAsync(Microsoft.AspNetCore.Components.ParameterView parameters);
|
System.Threading.Tasks.Task SetParametersAsync(Microsoft.AspNetCore.Components.ParameterView parameters);
|
||||||
}
|
}
|
||||||
public partial interface IComponentContext
|
|
||||||
{
|
|
||||||
bool IsConnected { get; }
|
|
||||||
}
|
|
||||||
public partial interface IHandleAfterRender
|
public partial interface IHandleAfterRender
|
||||||
{
|
{
|
||||||
System.Threading.Tasks.Task OnAfterRenderAsync();
|
System.Threading.Tasks.Task OnAfterRenderAsync();
|
||||||
|
|
|
||||||
|
|
@ -97,6 +97,7 @@ namespace Microsoft.AspNetCore.Components
|
||||||
public abstract partial class Dispatcher
|
public abstract partial class Dispatcher
|
||||||
{
|
{
|
||||||
protected Dispatcher() { }
|
protected Dispatcher() { }
|
||||||
|
public void AssertAccess() { }
|
||||||
public abstract bool CheckAccess();
|
public abstract bool CheckAccess();
|
||||||
public static Microsoft.AspNetCore.Components.Dispatcher CreateDefault() { throw null; }
|
public static Microsoft.AspNetCore.Components.Dispatcher CreateDefault() { throw null; }
|
||||||
public abstract System.Threading.Tasks.Task InvokeAsync(System.Action workItem);
|
public abstract System.Threading.Tasks.Task InvokeAsync(System.Action workItem);
|
||||||
|
|
@ -196,10 +197,6 @@ namespace Microsoft.AspNetCore.Components
|
||||||
void Attach(Microsoft.AspNetCore.Components.RenderHandle renderHandle);
|
void Attach(Microsoft.AspNetCore.Components.RenderHandle renderHandle);
|
||||||
System.Threading.Tasks.Task SetParametersAsync(Microsoft.AspNetCore.Components.ParameterView parameters);
|
System.Threading.Tasks.Task SetParametersAsync(Microsoft.AspNetCore.Components.ParameterView parameters);
|
||||||
}
|
}
|
||||||
public partial interface IComponentContext
|
|
||||||
{
|
|
||||||
bool IsConnected { get; }
|
|
||||||
}
|
|
||||||
public partial interface IHandleAfterRender
|
public partial interface IHandleAfterRender
|
||||||
{
|
{
|
||||||
System.Threading.Tasks.Task OnAfterRenderAsync();
|
System.Threading.Tasks.Task OnAfterRenderAsync();
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,20 @@ namespace Microsoft.AspNetCore.Components
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal event UnhandledExceptionEventHandler UnhandledException;
|
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>
|
/// <summary>
|
||||||
/// Returns a value that determines whether using the dispatcher to invoke a work item is required
|
/// Returns a value that determines whether using the dispatcher to invoke a work item is required
|
||||||
/// from the current context.
|
/// 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>
|
/// </returns>
|
||||||
public virtual Task DispatchEventAsync(ulong eventHandlerId, EventFieldInfo fieldInfo, EventArgs eventArgs)
|
public virtual Task DispatchEventAsync(ulong eventHandlerId, EventFieldInfo fieldInfo, EventArgs eventArgs)
|
||||||
{
|
{
|
||||||
EnsureSynchronizationContext();
|
Dispatcher.AssertAccess();
|
||||||
|
|
||||||
if (!_eventBindings.TryGetValue(eventHandlerId, out var callback))
|
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);
|
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>
|
/// <param name="renderFragment">A <see cref="RenderFragment"/> that will supply the updated UI contents.</param>
|
||||||
internal void AddToRenderQueue(int componentId, RenderFragment renderFragment)
|
internal void AddToRenderQueue(int componentId, RenderFragment renderFragment)
|
||||||
{
|
{
|
||||||
EnsureSynchronizationContext();
|
Dispatcher.AssertAccess();
|
||||||
|
|
||||||
var componentState = GetOptionalComponentState(componentId);
|
var componentState = GetOptionalComponentState(componentId);
|
||||||
if (componentState == null)
|
if (componentState == null)
|
||||||
|
|
@ -374,21 +374,6 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
return eventHandlerId;
|
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)
|
private ComponentState GetRequiredComponentState(int componentId)
|
||||||
=> _componentStateById.TryGetValue(componentId, out var componentState)
|
=> _componentStateById.TryGetValue(componentId, out var componentState)
|
||||||
? componentState
|
? componentState
|
||||||
|
|
@ -414,7 +399,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
|
|
||||||
private void ProcessRenderQueue()
|
private void ProcessRenderQueue()
|
||||||
{
|
{
|
||||||
EnsureSynchronizationContext();
|
Dispatcher.AssertAccess();
|
||||||
|
|
||||||
if (_isBatchInProgress)
|
if (_isBatchInProgress)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -30,8 +30,6 @@ namespace Microsoft.AspNetCore.Components.Routing
|
||||||
|
|
||||||
[Inject] private INavigationInterception NavigationInterception { get; set; }
|
[Inject] private INavigationInterception NavigationInterception { get; set; }
|
||||||
|
|
||||||
[Inject] private IComponentContext ComponentContext { get; set; }
|
|
||||||
|
|
||||||
[Inject] private ILoggerFactory LoggerFactory { get; set; }
|
[Inject] private ILoggerFactory LoggerFactory { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -152,7 +150,7 @@ namespace Microsoft.AspNetCore.Components.Routing
|
||||||
|
|
||||||
Task IHandleAfterRender.OnAfterRenderAsync()
|
Task IHandleAfterRender.OnAfterRenderAsync()
|
||||||
{
|
{
|
||||||
if (!_navigationInterceptionEnabled && ComponentContext.IsConnected)
|
if (!_navigationInterceptionEnabled)
|
||||||
{
|
{
|
||||||
_navigationInterceptionEnabled = true;
|
_navigationInterceptionEnabled = true;
|
||||||
return NavigationInterception.EnableNavigationInterceptionAsync();
|
return NavigationInterception.EnableNavigationInterceptionAsync();
|
||||||
|
|
|
||||||
|
|
@ -2883,7 +2883,7 @@ namespace Microsoft.AspNetCore.Components.Test
|
||||||
{
|
{
|
||||||
return renderer.DispatchEventAsync(eventHandlerId, new EventArgs());
|
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);
|
Assert.Equal(2, numEventsFired);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,6 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
||||||
{
|
{
|
||||||
internal class CircuitHost : IAsyncDisposable
|
internal class CircuitHost : IAsyncDisposable
|
||||||
{
|
{
|
||||||
private readonly SemaphoreSlim HandlerLock = new SemaphoreSlim(1);
|
|
||||||
private readonly IServiceScope _scope;
|
private readonly IServiceScope _scope;
|
||||||
private readonly CircuitOptions _options;
|
private readonly CircuitOptions _options;
|
||||||
private readonly CircuitHandler[] _circuitHandlers;
|
private readonly CircuitHandler[] _circuitHandlers;
|
||||||
|
|
@ -27,24 +26,9 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
||||||
private bool _disposed;
|
private bool _disposed;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets the current <see cref="Circuits.Circuit"/>.
|
/// Sets the <see cref="IJSRuntime"/> for the current excution context.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="circuitHost">The <see cref="Circuits.Circuit"/>.</param>
|
public void SetCurrentJSRuntime() => JSInterop.JSRuntime.SetCurrentJSRuntime(JSRuntime);
|
||||||
/// <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);
|
|
||||||
}
|
|
||||||
|
|
||||||
// This event is fired when there's an unrecoverable exception coming from the circuit, and
|
// 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
|
// 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
|
try
|
||||||
{
|
{
|
||||||
SetCurrentCircuitHost(this);
|
SetCurrentJSRuntime();
|
||||||
_initialized = true; // We're ready to accept incoming JSInterop calls from here on
|
_initialized = true; // We're ready to accept incoming JSInterop calls from here on
|
||||||
|
|
||||||
await OnCircuitOpenedAsync(cancellationToken);
|
await OnCircuitOpenedAsync(cancellationToken);
|
||||||
|
|
@ -186,7 +170,18 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Renderer.Dispose();
|
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);
|
Log.DisposeSucceeded(_logger, CircuitId);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|
@ -202,35 +197,28 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
||||||
{
|
{
|
||||||
Log.CircuitOpened(_logger, Circuit.Id);
|
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;
|
var circuitHandler = _circuitHandlers[i];
|
||||||
|
try
|
||||||
for (var i = 0; i < _circuitHandlers.Length; i++)
|
|
||||||
{
|
{
|
||||||
var circuitHandler = _circuitHandlers[i];
|
await circuitHandler.OnCircuitOpenedAsync(Circuit, cancellationToken);
|
||||||
try
|
|
||||||
{
|
|
||||||
await circuitHandler.OnCircuitOpenedAsync(Circuit, cancellationToken);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Log.CircuitHandlerFailed(_logger, circuitHandler, nameof(CircuitHandler.OnCircuitOpenedAsync), ex);
|
|
||||||
exceptions ??= new List<Exception>();
|
|
||||||
exceptions.Add(ex);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
if (exceptions != null)
|
|
||||||
{
|
{
|
||||||
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);
|
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;
|
var circuitHandler = _circuitHandlers[i];
|
||||||
|
try
|
||||||
for (var i = 0; i < _circuitHandlers.Length; i++)
|
|
||||||
{
|
{
|
||||||
var circuitHandler = _circuitHandlers[i];
|
await circuitHandler.OnConnectionUpAsync(Circuit, cancellationToken);
|
||||||
try
|
|
||||||
{
|
|
||||||
await circuitHandler.OnConnectionUpAsync(Circuit, cancellationToken);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Log.CircuitHandlerFailed(_logger, circuitHandler, nameof(CircuitHandler.OnConnectionUpAsync), ex);
|
|
||||||
exceptions ??= new List<Exception>();
|
|
||||||
exceptions.Add(ex);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
if (exceptions != null)
|
|
||||||
{
|
{
|
||||||
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);
|
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;
|
var circuitHandler = _circuitHandlers[i];
|
||||||
|
try
|
||||||
for (var i = 0; i < _circuitHandlers.Length; i++)
|
|
||||||
{
|
{
|
||||||
var circuitHandler = _circuitHandlers[i];
|
await circuitHandler.OnConnectionDownAsync(Circuit, cancellationToken);
|
||||||
try
|
|
||||||
{
|
|
||||||
await circuitHandler.OnConnectionDownAsync(Circuit, cancellationToken);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Log.CircuitHandlerFailed(_logger, circuitHandler, nameof(CircuitHandler.OnConnectionDownAsync), ex);
|
|
||||||
exceptions ??= new List<Exception>();
|
|
||||||
exceptions.Add(ex);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
if (exceptions != null)
|
|
||||||
{
|
{
|
||||||
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);
|
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
|
try
|
||||||
{
|
{
|
||||||
List<Exception> exceptions = null;
|
_ = Renderer.OnRenderCompletedAsync(renderId, errorMessageOrNull);
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
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(() =>
|
await Renderer.Dispatcher.InvokeAsync(() =>
|
||||||
{
|
{
|
||||||
SetCurrentCircuitHost(this);
|
SetCurrentJSRuntime();
|
||||||
Log.BeginInvokeDotNet(_logger, callId, assemblyName, methodIdentifier, dotNetObjectId);
|
Log.BeginInvokeDotNet(_logger, callId, assemblyName, methodIdentifier, dotNetObjectId);
|
||||||
DotNetDispatcher.BeginInvoke(callId, assemblyName, methodIdentifier, dotNetObjectId, argsJson);
|
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
|
// 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.
|
// however, we still want this to get logged if we do.
|
||||||
Log.BeginInvokeDotNetFailed(_logger, callId, assemblyName, methodIdentifier, dotNetObjectId, ex);
|
Log.BeginInvokeDotNetFailed(_logger, callId, assemblyName, methodIdentifier, dotNetObjectId, ex);
|
||||||
if (Client.Connected)
|
await TryNotifyClientErrorAsync(Client, GetClientErrorMessage(ex, "Interop call failed."));
|
||||||
{
|
|
||||||
await NotifyClientError(Client, "Interop call failed.");
|
|
||||||
}
|
|
||||||
UnhandledException?.Invoke(this, new UnhandledExceptionEventArgs(ex, isTerminating: false));
|
UnhandledException?.Invoke(this, new UnhandledExceptionEventArgs(ex, isTerminating: false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -382,7 +366,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
||||||
{
|
{
|
||||||
await Renderer.Dispatcher.InvokeAsync(() =>
|
await Renderer.Dispatcher.InvokeAsync(() =>
|
||||||
{
|
{
|
||||||
SetCurrentCircuitHost(this);
|
SetCurrentJSRuntime();
|
||||||
if (!succeded)
|
if (!succeded)
|
||||||
{
|
{
|
||||||
// We can log the arguments here because it is simply the JS error with the call stack.
|
// 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
|
// An error completing JS interop means that the user sent invalid data, a well-behaved
|
||||||
// client won't do this.
|
// client won't do this.
|
||||||
Log.EndInvokeDispatchException(_logger, ex);
|
Log.EndInvokeDispatchException(_logger, ex);
|
||||||
if (Client.Connected)
|
await TryNotifyClientErrorAsync(Client, GetClientErrorMessage(ex, "Invalid interop arguments."));
|
||||||
{
|
|
||||||
await NotifyClientError(Client, "Invalid interop arguments.");
|
|
||||||
}
|
|
||||||
UnhandledException?.Invoke(this, new UnhandledExceptionEventArgs(ex, isTerminating: false));
|
UnhandledException?.Invoke(this, new UnhandledExceptionEventArgs(ex, isTerminating: false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -419,17 +400,13 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
||||||
WebEventData webEventData;
|
WebEventData webEventData;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
AssertInitialized();
|
|
||||||
webEventData = WebEventData.Parse(eventDescriptorJson, eventArgsJson);
|
webEventData = WebEventData.Parse(eventDescriptorJson, eventArgsJson);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
// Invalid event data is fatal. We expect a well-behaved client to send valid JSON.
|
// Invalid event data is fatal. We expect a well-behaved client to send valid JSON.
|
||||||
Log.DispatchEventFailedToParseEventData(_logger, ex);
|
Log.DispatchEventFailedToParseEventData(_logger, ex);
|
||||||
if (Client.Connected)
|
await TryNotifyClientErrorAsync(Client, GetClientErrorMessage(ex, "Bad input data."));
|
||||||
{
|
|
||||||
await NotifyClientError(Client, "Invalid event data.");
|
|
||||||
}
|
|
||||||
UnhandledException?.Invoke(this, new UnhandledExceptionEventArgs(ex, isTerminating: false));
|
UnhandledException?.Invoke(this, new UnhandledExceptionEventArgs(ex, isTerminating: false));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -438,7 +415,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
||||||
{
|
{
|
||||||
await Renderer.Dispatcher.InvokeAsync(() =>
|
await Renderer.Dispatcher.InvokeAsync(() =>
|
||||||
{
|
{
|
||||||
SetCurrentCircuitHost(this);
|
SetCurrentJSRuntime();
|
||||||
return Renderer.DispatchEventAsync(
|
return Renderer.DispatchEventAsync(
|
||||||
webEventData.EventHandlerId,
|
webEventData.EventHandlerId,
|
||||||
webEventData.EventFieldInfo,
|
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 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.
|
// A well-behaved client won't do this.
|
||||||
Log.DispatchEventFailedToDispatchEvent(_logger, webEventData.EventHandlerId.ToString(), ex);
|
Log.DispatchEventFailedToDispatchEvent(_logger, webEventData.EventHandlerId.ToString(), ex);
|
||||||
if (Client.Connected)
|
await TryNotifyClientErrorAsync(Client, GetClientErrorMessage(ex, "Failed to dispatch event."));
|
||||||
{
|
|
||||||
await NotifyClientError(Client, "Failed to dispatch event.");
|
|
||||||
}
|
|
||||||
UnhandledException?.Invoke(this, new UnhandledExceptionEventArgs(ex, isTerminating: false));
|
UnhandledException?.Invoke(this, new UnhandledExceptionEventArgs(ex, isTerminating: false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -469,7 +443,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
||||||
{
|
{
|
||||||
await Renderer.Dispatcher.InvokeAsync(() =>
|
await Renderer.Dispatcher.InvokeAsync(() =>
|
||||||
{
|
{
|
||||||
SetCurrentCircuitHost(this);
|
SetCurrentJSRuntime();
|
||||||
Log.LocationChange(_logger, uri, CircuitId);
|
Log.LocationChange(_logger, uri, CircuitId);
|
||||||
var navigationManager = (RemoteNavigationManager)Services.GetRequiredService<NavigationManager>();
|
var navigationManager = (RemoteNavigationManager)Services.GetRequiredService<NavigationManager>();
|
||||||
navigationManager.NotifyLocationChanged(uri, intercepted);
|
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
|
// LocationChangeException means that it failed in user-code. Treat this like an unhandled
|
||||||
// exception in user-code.
|
// exception in user-code.
|
||||||
Log.LocationChangeFailedInCircuit(_logger, uri, CircuitId, nex);
|
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));
|
UnhandledException?.Invoke(this, new UnhandledExceptionEventArgs(nex, isTerminating: false));
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
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
|
// Any other exception means that it failed validation, or inside the NavigationManager. Treat
|
||||||
// this like bad data.
|
// this like bad data.
|
||||||
Log.LocationChangeFailed(_logger, uri, CircuitId, ex);
|
Log.LocationChangeFailed(_logger, uri, CircuitId, ex);
|
||||||
if (Client.Connected)
|
await TryNotifyClientErrorAsync(Client, GetClientErrorMessage(ex, $"Location change to '{uri}' failed."));
|
||||||
{
|
|
||||||
await NotifyClientError(Client, $"Location change to {uri} failed.");
|
|
||||||
}
|
|
||||||
UnhandledException?.Invoke(this, new UnhandledExceptionEventArgs(ex, isTerminating: false));
|
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.
|
// 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
|
// 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());
|
_ = Renderer.Dispatcher.InvokeAsync(() => Renderer.ProcessBufferedRenderBatches());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -566,39 +537,50 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
||||||
private async Task ReportUnhandledException(Exception exception)
|
private async Task ReportUnhandledException(Exception exception)
|
||||||
{
|
{
|
||||||
Log.CircuitUnhandledException(_logger, CircuitId, 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)
|
if (!Client.Connected)
|
||||||
{
|
{
|
||||||
_logger.LogDebug("Client is disconnected YO.");
|
Log.UnhandledExceptionClientDisconnected(
|
||||||
|
_logger,
|
||||||
|
CircuitId,
|
||||||
|
exception);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (_options.DetailedErrors)
|
Log.CircuitTransmittingClientError(_logger, CircuitId);
|
||||||
{
|
await client.SendAsync("JS.Error", error);
|
||||||
await NotifyClientError(Client, exception.ToString());
|
Log.CircuitTransmittedClientErrorSuccess(_logger, CircuitId);
|
||||||
}
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
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 class Log
|
||||||
{
|
{
|
||||||
private static readonly Action<ILogger, Exception> _intializationStarted;
|
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, string, Exception> _onCircuitClosed;
|
||||||
private static readonly Action<ILogger, Type, string, string, Exception> _circuitHandlerFailed;
|
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> _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, string, string, Exception> _beginInvokeDotNetStatic;
|
||||||
private static readonly Action<ILogger, string, long, string, Exception> _beginInvokeDotNetInstance;
|
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> _locationChangeSucceeded;
|
||||||
private static readonly Action<ILogger, string, string, Exception> _locationChangeFailed;
|
private static readonly Action<ILogger, string, string, Exception> _locationChangeFailed;
|
||||||
private static readonly Action<ILogger, string, string, Exception> _locationChangeFailedInCircuit;
|
private static readonly Action<ILogger, string, string, Exception> _locationChangeFailedInCircuit;
|
||||||
|
private static readonly Action<ILogger, long, string, Exception> _onRenderCompletedFailed;
|
||||||
|
|
||||||
private static class EventIds
|
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 OnCircuitClosed = new EventId(109, "OnCircuitClosed");
|
||||||
public static readonly EventId CircuitHandlerFailed = new EventId(110, "CircuitHandlerFailed");
|
public static readonly EventId CircuitHandlerFailed = new EventId(110, "CircuitHandlerFailed");
|
||||||
public static readonly EventId CircuitUnhandledException = new EventId(111, "CircuitUnhandledException");
|
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
|
// 200s used for interactive stuff
|
||||||
public static readonly EventId DispatchEventFailedToParseEventData = new EventId(200, "DispatchEventFailedToParseEventData");
|
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 LocationChangeSucceded = new EventId(209, "LocationChangeSucceeded");
|
||||||
public static readonly EventId LocationChangeFailed = new EventId(210, "LocationChangeFailed");
|
public static readonly EventId LocationChangeFailed = new EventId(210, "LocationChangeFailed");
|
||||||
public static readonly EventId LocationChangeFailedInCircuit = new EventId(211, "LocationChangeFailedInCircuit");
|
public static readonly EventId LocationChangeFailedInCircuit = new EventId(211, "LocationChangeFailedInCircuit");
|
||||||
|
public static readonly EventId OnRenderCompletedFailed = new EventId(212, " OnRenderCompletedFailed");
|
||||||
}
|
}
|
||||||
|
|
||||||
static Log()
|
static Log()
|
||||||
|
|
@ -666,87 +656,102 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
||||||
_intializationStarted = LoggerMessage.Define(
|
_intializationStarted = LoggerMessage.Define(
|
||||||
LogLevel.Debug,
|
LogLevel.Debug,
|
||||||
EventIds.InitializationFailed,
|
EventIds.InitializationFailed,
|
||||||
"Circuit initialization started");
|
"Circuit initialization started.");
|
||||||
|
|
||||||
_intializationSucceded = LoggerMessage.Define(
|
_intializationSucceded = LoggerMessage.Define(
|
||||||
LogLevel.Debug,
|
LogLevel.Debug,
|
||||||
EventIds.InitializationFailed,
|
EventIds.InitializationFailed,
|
||||||
"Circuit initialization succeeded");
|
"Circuit initialization succeeded.");
|
||||||
|
|
||||||
_intializationFailed = LoggerMessage.Define(
|
_intializationFailed = LoggerMessage.Define(
|
||||||
LogLevel.Debug,
|
LogLevel.Debug,
|
||||||
EventIds.InitializationFailed,
|
EventIds.InitializationFailed,
|
||||||
"Circuit initialization failed");
|
"Circuit initialization failed.");
|
||||||
|
|
||||||
_disposeStarted = LoggerMessage.Define<string>(
|
_disposeStarted = LoggerMessage.Define<string>(
|
||||||
LogLevel.Debug,
|
LogLevel.Debug,
|
||||||
EventIds.DisposeStarted,
|
EventIds.DisposeStarted,
|
||||||
"Disposing circuit {CircuitId} started");
|
"Disposing circuit '{CircuitId}' started.");
|
||||||
|
|
||||||
_disposeSucceded = LoggerMessage.Define<string>(
|
_disposeSucceded = LoggerMessage.Define<string>(
|
||||||
LogLevel.Debug,
|
LogLevel.Debug,
|
||||||
EventIds.DisposeSucceeded,
|
EventIds.DisposeSucceeded,
|
||||||
"Disposing circuit {CircuitId} succeded");
|
"Disposing circuit '{CircuitId}' succeded.");
|
||||||
|
|
||||||
_disposeFailed = LoggerMessage.Define<string>(
|
_disposeFailed = LoggerMessage.Define<string>(
|
||||||
LogLevel.Debug,
|
LogLevel.Debug,
|
||||||
EventIds.DisposeFailed,
|
EventIds.DisposeFailed,
|
||||||
"Disposing circuit {CircuitId} failed");
|
"Disposing circuit '{CircuitId}' failed.");
|
||||||
|
|
||||||
_onCircuitOpened = LoggerMessage.Define<string>(
|
_onCircuitOpened = LoggerMessage.Define<string>(
|
||||||
LogLevel.Debug,
|
LogLevel.Debug,
|
||||||
EventIds.OnCircuitOpened,
|
EventIds.OnCircuitOpened,
|
||||||
"Opening circuit with id {CircuitId}.");
|
"Opening circuit with id '{CircuitId}'.");
|
||||||
|
|
||||||
_onConnectionUp = LoggerMessage.Define<string, string>(
|
_onConnectionUp = LoggerMessage.Define<string, string>(
|
||||||
LogLevel.Debug,
|
LogLevel.Debug,
|
||||||
EventIds.OnConnectionUp,
|
EventIds.OnConnectionUp,
|
||||||
"Circuit id {CircuitId} connected using connection {ConnectionId}.");
|
"Circuit id '{CircuitId}' connected using connection '{ConnectionId}'.");
|
||||||
|
|
||||||
_onConnectionDown = LoggerMessage.Define<string, string>(
|
_onConnectionDown = LoggerMessage.Define<string, string>(
|
||||||
LogLevel.Debug,
|
LogLevel.Debug,
|
||||||
EventIds.OnConnectionDown,
|
EventIds.OnConnectionDown,
|
||||||
"Circuit id {CircuitId} disconnected from connection {ConnectionId}.");
|
"Circuit id '{CircuitId}' disconnected from connection '{ConnectionId}'.");
|
||||||
|
|
||||||
_onCircuitClosed = LoggerMessage.Define<string>(
|
_onCircuitClosed = LoggerMessage.Define<string>(
|
||||||
LogLevel.Debug,
|
LogLevel.Debug,
|
||||||
EventIds.OnCircuitClosed,
|
EventIds.OnCircuitClosed,
|
||||||
"Closing circuit with id {CircuitId}.");
|
"Closing circuit with id '{CircuitId}'.");
|
||||||
|
|
||||||
_circuitHandlerFailed = LoggerMessage.Define<Type, string, string>(
|
_circuitHandlerFailed = LoggerMessage.Define<Type, string, string>(
|
||||||
LogLevel.Error,
|
LogLevel.Error,
|
||||||
EventIds.CircuitHandlerFailed,
|
EventIds.CircuitHandlerFailed,
|
||||||
"Unhandled error invoking circuit handler type {handlerType}.{handlerMethod}: {Message}");
|
"Unhandled error invoking circuit handler type {handlerType}.{handlerMethod}: {Message}");
|
||||||
|
|
||||||
_circuitUnhandledException = LoggerMessage.Define<string>(
|
_circuitUnhandledException = LoggerMessage.Define<string>(
|
||||||
LogLevel.Error,
|
LogLevel.Error,
|
||||||
EventIds.CircuitUnhandledException,
|
EventIds.CircuitUnhandledException,
|
||||||
"Unhandled exception in circuit {CircuitId}");
|
"Unhandled exception in circuit '{CircuitId}'.");
|
||||||
|
|
||||||
_circuitUnhandledExceptionFailed = LoggerMessage.Define<string>(
|
_circuitTransmittingClientError = LoggerMessage.Define<string>(
|
||||||
LogLevel.Debug,
|
LogLevel.Debug,
|
||||||
EventIds.CircuitUnhandledExceptionFailed,
|
EventIds.CircuitTransmittingClientError,
|
||||||
"Failed to transmit exception to client in circuit {CircuitId}");
|
"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>(
|
_beginInvokeDotNetStatic = LoggerMessage.Define<string, string, string>(
|
||||||
LogLevel.Debug,
|
LogLevel.Debug,
|
||||||
EventIds.BeginInvokeDotNet,
|
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>(
|
_beginInvokeDotNetInstance = LoggerMessage.Define<string, long, string>(
|
||||||
LogLevel.Debug,
|
LogLevel.Debug,
|
||||||
EventIds.BeginInvokeDotNet,
|
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>(
|
_beginInvokeDotNetStaticFailed = LoggerMessage.Define<string, string, string>(
|
||||||
LogLevel.Debug,
|
LogLevel.Debug,
|
||||||
EventIds.BeginInvokeDotNetFailed,
|
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>(
|
_beginInvokeDotNetInstanceFailed = LoggerMessage.Define<string, long, string>(
|
||||||
LogLevel.Debug,
|
LogLevel.Debug,
|
||||||
EventIds.BeginInvokeDotNetFailed,
|
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(
|
_endInvokeDispatchException = LoggerMessage.Define(
|
||||||
LogLevel.Debug,
|
LogLevel.Debug,
|
||||||
|
|
@ -776,25 +781,30 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
||||||
_locationChange = LoggerMessage.Define<string, string>(
|
_locationChange = LoggerMessage.Define<string, string>(
|
||||||
LogLevel.Debug,
|
LogLevel.Debug,
|
||||||
EventIds.LocationChange,
|
EventIds.LocationChange,
|
||||||
"Location changing to {URI} in circuit {CircuitId}.");
|
"Location changing to {URI} in circuit '{CircuitId}'.");
|
||||||
|
|
||||||
_locationChangeSucceeded = LoggerMessage.Define<string, string>(
|
_locationChangeSucceeded = LoggerMessage.Define<string, string>(
|
||||||
LogLevel.Debug,
|
LogLevel.Debug,
|
||||||
EventIds.LocationChangeSucceded,
|
EventIds.LocationChangeSucceded,
|
||||||
"Location change to {URI} in circuit {CircuitId} succeded.");
|
"Location change to '{URI}' in circuit '{CircuitId}' succeded.");
|
||||||
|
|
||||||
_locationChangeFailed = LoggerMessage.Define<string, string>(
|
_locationChangeFailed = LoggerMessage.Define<string, string>(
|
||||||
LogLevel.Debug,
|
LogLevel.Debug,
|
||||||
EventIds.LocationChangeFailed,
|
EventIds.LocationChangeFailed,
|
||||||
"Location change to {URI} in circuit {CircuitId} failed.");
|
"Location change to '{URI}' in circuit '{CircuitId}' failed.");
|
||||||
|
|
||||||
_locationChangeFailedInCircuit = LoggerMessage.Define<string, string>(
|
_locationChangeFailedInCircuit = LoggerMessage.Define<string, string>(
|
||||||
LogLevel.Error,
|
LogLevel.Error,
|
||||||
EventIds.LocationChangeFailed,
|
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 InitializationSucceeded(ILogger logger) => _intializationSucceded(logger, null);
|
||||||
public static void InitializationFailed(ILogger logger, Exception exception) => _intializationFailed(logger, exception);
|
public static void InitializationFailed(ILogger logger, Exception exception) => _intializationFailed(logger, exception);
|
||||||
public static void DisposeStarted(ILogger logger, string circuitId) => _disposeStarted(logger, circuitId, null);
|
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 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 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 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 CircuitClosed(ILogger logger, string circuitId) => _onCircuitClosed(logger, circuitId, null);
|
||||||
|
|
||||||
public static void CircuitHandlerFailed(ILogger logger, CircuitHandler handler, string handlerMethod, Exception exception)
|
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 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 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 EndInvokeJSFailed(ILogger logger, long asyncHandle, string arguments) => _endInvokeJSFailed(logger, asyncHandle, arguments, null);
|
||||||
public static void EndInvokeJSSucceeded(ILogger logger, long asyncCall) => _endInvokeJSSucceeded(logger, asyncCall, 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 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 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 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 scope = _scopeFactory.CreateScope();
|
||||||
var jsRuntime = (RemoteJSRuntime)scope.ServiceProvider.GetRequiredService<IJSRuntime>();
|
var jsRuntime = (RemoteJSRuntime)scope.ServiceProvider.GetRequiredService<IJSRuntime>();
|
||||||
var componentContext = (RemoteComponentContext)scope.ServiceProvider.GetRequiredService<IComponentContext>();
|
|
||||||
jsRuntime.Initialize(client);
|
jsRuntime.Initialize(client);
|
||||||
componentContext.Initialize(client);
|
|
||||||
|
|
||||||
var navigationManager = (RemoteNavigationManager)scope.ServiceProvider.GetRequiredService<NavigationManager>();
|
var navigationManager = (RemoteNavigationManager)scope.ServiceProvider.GetRequiredService<NavigationManager>();
|
||||||
var navigationInterception = (RemoteNavigationInterception)scope.ServiceProvider.GetRequiredService<INavigationInterception>();
|
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.
|
// disposed.
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task OnRenderCompleted(long incomingBatchId, string errorMessageOrNull)
|
public Task OnRenderCompletedAsync(long incomingBatchId, string errorMessageOrNull)
|
||||||
{
|
{
|
||||||
if (_disposing)
|
if (_disposing)
|
||||||
{
|
{
|
||||||
|
|
@ -273,9 +273,9 @@ namespace Microsoft.AspNetCore.Components.Web.Rendering
|
||||||
|
|
||||||
if (lastBatchId < incomingBatchId)
|
if (lastBatchId < incomingBatchId)
|
||||||
{
|
{
|
||||||
HandleException(
|
// This exception is due to a bad client input, so we mark it as such to prevent loging it as a warning and
|
||||||
new InvalidOperationException($"Received an acknowledgement for batch with id '{incomingBatchId}' when the last batch produced was '{lastBatchId}'."));
|
// flooding the logs with warnings.
|
||||||
return Task.CompletedTask;
|
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
|
// 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
|
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 System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Components.Server.Circuits;
|
using Microsoft.AspNetCore.Components.Server.Circuits;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Http.Features;
|
|
||||||
using Microsoft.AspNetCore.Routing;
|
|
||||||
using Microsoft.AspNetCore.SignalR;
|
using Microsoft.AspNetCore.SignalR;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
|
@ -203,7 +201,7 @@ namespace Microsoft.AspNetCore.Components.Server
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.ReceivedConfirmationForBatch(_logger, renderId);
|
Log.ReceivedConfirmationForBatch(_logger, renderId);
|
||||||
_ = circuitHost.Renderer.OnRenderCompleted(renderId, errorMessageOrNull);
|
_ = circuitHost.OnRenderCompletedAsync(renderId, errorMessageOrNull);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async ValueTask OnLocationChanged(string uri, bool intercepted)
|
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 =
|
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");
|
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 =
|
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 =
|
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)
|
public static void NoComponentsRegisteredInEndpoint(ILogger logger, string endpointDisplayName) => _noComponentsRegisteredInEndpoint(logger, endpointDisplayName, null);
|
||||||
{
|
|
||||||
_noComponentsRegisteredInEndpoint(logger, endpointDisplayName, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void ReceivedConfirmationForBatch(ILogger logger, long batchId)
|
public static void ReceivedConfirmationForBatch(ILogger logger, long batchId) => _receivedConfirmationForBatch(logger, batchId, null);
|
||||||
{
|
|
||||||
_receivedConfirmationForBatch(logger, batchId, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void UnhandledExceptionInCircuit(ILogger logger, string circuitId, Exception exception)
|
|
||||||
{
|
|
||||||
_unhandledExceptionInCircuit(logger, circuitId, exception);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void CircuitAlreadyInitialized(ILogger logger, string circuitId) => _circuitAlreadyInitialized(logger, circuitId, 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 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 InvalidInputData(ILogger logger, [CallerMemberName] string callSite = "") => _invalidInputData(logger, callSite, null);
|
||||||
|
|
||||||
public static void CircuitInitializationFailed(ILogger logger, Exception exception) => _circuitInitializationFailed(logger, exception);
|
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<NavigationManager, RemoteNavigationManager>();
|
||||||
services.AddScoped<IJSRuntime, RemoteJSRuntime>();
|
services.AddScoped<IJSRuntime, RemoteJSRuntime>();
|
||||||
services.AddScoped<INavigationInterception, RemoteNavigationInterception>();
|
services.AddScoped<INavigationInterception, RemoteNavigationInterception>();
|
||||||
services.AddScoped<IComponentContext, RemoteComponentContext>();
|
|
||||||
services.AddScoped<AuthenticationStateProvider, ServerAuthenticationStateProvider>();
|
services.AddScoped<AuthenticationStateProvider, ServerAuthenticationStateProvider>();
|
||||||
|
|
||||||
services.TryAddEnumerable(ServiceDescriptor.Singleton<IConfigureOptions<CircuitOptions>, CircuitOptionsJSInteropDetailedErrorsConfiguration>());
|
services.TryAddEnumerable(ServiceDescriptor.Singleton<IConfigureOptions<CircuitOptions>, CircuitOptionsJSInteropDetailedErrorsConfiguration>());
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,33 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
||||||
Assert.Null(circuitHost.Handle.CircuitHost);
|
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]
|
[Fact]
|
||||||
public async Task DisposeAsync_DisposesResourcesAndSilencesException()
|
public async Task DisposeAsync_DisposesResourcesAndSilencesException()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -270,46 +270,6 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
||||||
Assert.False(registry.DisconnectedCircuits.TryGetValue(circuitHost.CircuitId, out _));
|
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]
|
[Fact]
|
||||||
public async Task DisconnectWhenAConnectIsInProgress()
|
public async Task DisconnectWhenAConnectIsInProgress()
|
||||||
{
|
{
|
||||||
|
|
@ -444,37 +404,5 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
||||||
NullLogger<CircuitRegistry>.Instance,
|
NullLogger<CircuitRegistry>.Instance,
|
||||||
factory ?? TestCircuitIdFactory.CreateTestFactory());
|
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();
|
component.TriggerRender();
|
||||||
}
|
}
|
||||||
|
|
||||||
await renderer.OnRenderCompleted(2, null);
|
await renderer.OnRenderCompletedAsync(2, null);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(9, renderer._unacknowledgedRenderBatches.Count);
|
Assert.Equal(9, renderer._unacknowledgedRenderBatches.Count);
|
||||||
|
|
@ -115,7 +115,7 @@ namespace Microsoft.AspNetCore.Components.Web.Rendering
|
||||||
}
|
}
|
||||||
Assert.Equal(10, renderer._unacknowledgedRenderBatches.Count);
|
Assert.Equal(10, renderer._unacknowledgedRenderBatches.Count);
|
||||||
|
|
||||||
await renderer.OnRenderCompleted(2, null);
|
await renderer.OnRenderCompletedAsync(2, null);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(10, renderer._unacknowledgedRenderBatches.Count);
|
Assert.Equal(10, renderer._unacknowledgedRenderBatches.Count);
|
||||||
|
|
@ -153,7 +153,7 @@ namespace Microsoft.AspNetCore.Components.Web.Rendering
|
||||||
|
|
||||||
var componentId = renderer.AssignRootComponentId(component);
|
var componentId = renderer.AssignRootComponentId(component);
|
||||||
component.TriggerRender();
|
component.TriggerRender();
|
||||||
_ = renderer.OnRenderCompleted(2, null);
|
_ = renderer.OnRenderCompletedAsync(2, null);
|
||||||
|
|
||||||
@event.Reset();
|
@event.Reset();
|
||||||
firstBatchTCS.SetResult(null);
|
firstBatchTCS.SetResult(null);
|
||||||
|
|
@ -171,7 +171,7 @@ namespace Microsoft.AspNetCore.Components.Web.Rendering
|
||||||
|
|
||||||
foreach (var id in renderIds.ToArray())
|
foreach (var id in renderIds.ToArray())
|
||||||
{
|
{
|
||||||
_ = renderer.OnRenderCompleted(id, null);
|
_ = renderer.OnRenderCompletedAsync(id, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
secondBatchTCS.SetResult(null);
|
secondBatchTCS.SetResult(null);
|
||||||
|
|
@ -234,14 +234,14 @@ namespace Microsoft.AspNetCore.Components.Web.Rendering
|
||||||
};
|
};
|
||||||
|
|
||||||
// Receive the ack for the intial batch
|
// Receive the ack for the intial batch
|
||||||
_ = renderer.OnRenderCompleted(2, null);
|
_ = renderer.OnRenderCompletedAsync(2, null);
|
||||||
// Receive the ack for the second batch
|
// Receive the ack for the second batch
|
||||||
_ = renderer.OnRenderCompleted(3, null);
|
_ = renderer.OnRenderCompletedAsync(3, null);
|
||||||
|
|
||||||
firstBatchTCS.SetResult(null);
|
firstBatchTCS.SetResult(null);
|
||||||
secondBatchTCS.SetResult(null);
|
secondBatchTCS.SetResult(null);
|
||||||
// Repeat the ack for the third batch
|
// Repeat the ack for the third batch
|
||||||
_ = renderer.OnRenderCompleted(3, null);
|
_ = renderer.OnRenderCompletedAsync(3, null);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Empty(exceptions);
|
Assert.Empty(exceptions);
|
||||||
|
|
@ -297,14 +297,14 @@ namespace Microsoft.AspNetCore.Components.Web.Rendering
|
||||||
};
|
};
|
||||||
|
|
||||||
// Receive the ack for the intial batch
|
// Receive the ack for the intial batch
|
||||||
_ = renderer.OnRenderCompleted(2, null);
|
_ = renderer.OnRenderCompletedAsync(2, null);
|
||||||
// Receive the ack for the second batch
|
// Receive the ack for the second batch
|
||||||
_ = renderer.OnRenderCompleted(2, null);
|
_ = renderer.OnRenderCompletedAsync(2, null);
|
||||||
|
|
||||||
firstBatchTCS.SetResult(null);
|
firstBatchTCS.SetResult(null);
|
||||||
secondBatchTCS.SetResult(null);
|
secondBatchTCS.SetResult(null);
|
||||||
// Repeat the ack for the third batch
|
// Repeat the ack for the third batch
|
||||||
_ = renderer.OnRenderCompleted(3, null);
|
_ = renderer.OnRenderCompletedAsync(3, null);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Empty(exceptions);
|
Assert.Empty(exceptions);
|
||||||
|
|
@ -358,7 +358,7 @@ namespace Microsoft.AspNetCore.Components.Web.Rendering
|
||||||
};
|
};
|
||||||
|
|
||||||
// Pretend that we missed the ack for the initial batch
|
// Pretend that we missed the ack for the initial batch
|
||||||
_ = renderer.OnRenderCompleted(3, null);
|
_ = renderer.OnRenderCompletedAsync(3, null);
|
||||||
firstBatchTCS.SetResult(null);
|
firstBatchTCS.SetResult(null);
|
||||||
secondBatchTCS.SetResult(null);
|
secondBatchTCS.SetResult(null);
|
||||||
|
|
||||||
|
|
@ -414,12 +414,11 @@ namespace Microsoft.AspNetCore.Components.Web.Rendering
|
||||||
exceptions.Add(e);
|
exceptions.Add(e);
|
||||||
};
|
};
|
||||||
|
|
||||||
_ = renderer.OnRenderCompleted(4, null);
|
var exception = await Assert.ThrowsAsync<InvalidOperationException>(() => renderer.OnRenderCompletedAsync(4, null));
|
||||||
firstBatchTCS.SetResult(null);
|
firstBatchTCS.SetResult(null);
|
||||||
secondBatchTCS.SetResult(null);
|
secondBatchTCS.SetResult(null);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
var exception = Assert.Single(exceptions);
|
|
||||||
Assert.Equal(
|
Assert.Equal(
|
||||||
"Received an acknowledgement for batch with id '4' when the last batch produced was '3'.",
|
"Received an acknowledgement for batch with id '4' when the last batch produced was '3'.",
|
||||||
exception.Message);
|
exception.Message);
|
||||||
|
|
|
||||||
|
|
@ -11,11 +11,20 @@ namespace Microsoft.AspNetCore.Components.Web
|
||||||
{
|
{
|
||||||
// This class represents the second half of parsing incoming event data,
|
// This class represents the second half of parsing incoming event data,
|
||||||
// once the type of the eventArgs becomes known.
|
// once the type of the eventArgs becomes known.
|
||||||
|
|
||||||
public static WebEventData Parse(string eventDescriptorJson, string eventArgsJson)
|
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(
|
return Parse(
|
||||||
Deserialize<WebEventDescriptor>(eventDescriptorJson),
|
eventDescriptor,
|
||||||
eventArgsJson);
|
eventArgsJson);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -25,7 +34,7 @@ namespace Microsoft.AspNetCore.Components.Web
|
||||||
eventDescriptor.BrowserRendererId,
|
eventDescriptor.BrowserRendererId,
|
||||||
eventDescriptor.EventHandlerId,
|
eventDescriptor.EventHandlerId,
|
||||||
InterpretEventFieldInfo(eventDescriptor.EventFieldInfo),
|
InterpretEventFieldInfo(eventDescriptor.EventFieldInfo),
|
||||||
ParseEventArgsJson(eventDescriptor.EventArgsType, eventArgsJson));
|
ParseEventArgsJson(eventDescriptor.EventHandlerId, eventDescriptor.EventArgsType, eventArgsJson));
|
||||||
}
|
}
|
||||||
|
|
||||||
private WebEventData(int browserRendererId, ulong eventHandlerId, EventFieldInfo eventFieldInfo, EventArgs eventArgs)
|
private WebEventData(int browserRendererId, ulong eventHandlerId, EventFieldInfo eventFieldInfo, EventArgs eventArgs)
|
||||||
|
|
@ -44,43 +53,34 @@ namespace Microsoft.AspNetCore.Components.Web
|
||||||
|
|
||||||
public EventArgs EventArgs { get; }
|
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 eventArgsType switch
|
||||||
return DeserializeChangeEventArgs(eventArgsJson);
|
{
|
||||||
case "clipboard":
|
"change" => DeserializeChangeEventArgs(eventArgsJson),
|
||||||
return Deserialize<UIClipboardEventArgs>(eventArgsJson);
|
"clipboard" => Deserialize<UIClipboardEventArgs>(eventArgsJson),
|
||||||
case "drag":
|
"drag" => Deserialize<UIDragEventArgs>(eventArgsJson),
|
||||||
return Deserialize<UIDragEventArgs>(eventArgsJson);
|
"error" => Deserialize<UIErrorEventArgs>(eventArgsJson),
|
||||||
case "error":
|
"focus" => Deserialize<UIFocusEventArgs>(eventArgsJson),
|
||||||
return Deserialize<UIErrorEventArgs>(eventArgsJson);
|
"keyboard" => Deserialize<UIKeyboardEventArgs>(eventArgsJson),
|
||||||
case "focus":
|
"mouse" => Deserialize<UIMouseEventArgs>(eventArgsJson),
|
||||||
return Deserialize<UIFocusEventArgs>(eventArgsJson);
|
"pointer" => Deserialize<UIPointerEventArgs>(eventArgsJson),
|
||||||
case "keyboard":
|
"progress" => Deserialize<UIProgressEventArgs>(eventArgsJson),
|
||||||
return Deserialize<UIKeyboardEventArgs>(eventArgsJson);
|
"touch" => Deserialize<UITouchEventArgs>(eventArgsJson),
|
||||||
case "mouse":
|
"unknown" => EventArgs.Empty,
|
||||||
return Deserialize<UIMouseEventArgs>(eventArgsJson);
|
"wheel" => Deserialize<UIWheelEventArgs>(eventArgsJson),
|
||||||
case "pointer":
|
_ => throw new InvalidOperationException($"Unsupported event type '{eventArgsType}'. EventId: '{eventHandlerId}'."),
|
||||||
return Deserialize<UIPointerEventArgs>(eventArgsJson);
|
};
|
||||||
case "progress":
|
}
|
||||||
return Deserialize<UIProgressEventArgs>(eventArgsJson);
|
catch (Exception e)
|
||||||
case "touch":
|
{
|
||||||
return Deserialize<UITouchEventArgs>(eventArgsJson);
|
throw new InvalidOperationException($"There was an error parsing the event arguments. EventId: '{eventHandlerId}'.", e);
|
||||||
case "unknown":
|
|
||||||
return EventArgs.Empty;
|
|
||||||
case "wheel":
|
|
||||||
return Deserialize<UIWheelEventArgs>(eventArgsJson);
|
|
||||||
default:
|
|
||||||
throw new ArgumentException($"Unsupported value '{eventArgsType}'.", nameof(eventArgsType));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static T Deserialize<T>(string json)
|
private static T Deserialize<T>(string json) => JsonSerializer.Deserialize<T>(json, JsonSerializerOptionsProvider.Options);
|
||||||
{
|
|
||||||
return JsonSerializer.Deserialize<T>(json, JsonSerializerOptionsProvider.Options);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static EventFieldInfo InterpretEventFieldInfo(EventFieldInfo fieldInfo)
|
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);
|
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))) {
|
if (!(await circuit.reconnect(reconnection))) {
|
||||||
logger.log(LogLevel.Information, 'Reconnection attempt failed.');
|
logger.log(LogLevel.Information, 'Reconnection attempt to the circuit failed.');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -31,12 +31,18 @@ export class DefaultReconnectDisplay implements ReconnectDisplay {
|
||||||
];
|
];
|
||||||
|
|
||||||
this.modal.style.cssText = modalStyles.join(';');
|
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.message = this.modal.querySelector('h5')!;
|
||||||
this.button = this.modal.querySelector('button')!;
|
this.button = this.modal.querySelector('button')!;
|
||||||
this.reloadParagraph = this.modal.querySelector('p')!;
|
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());
|
this.reloadParagraph.querySelector('a')!.addEventListener('click', () => location.reload());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -57,7 +63,7 @@ export class DefaultReconnectDisplay implements ReconnectDisplay {
|
||||||
|
|
||||||
failed(): void {
|
failed(): void {
|
||||||
this.button.style.display = 'block';
|
this.button.style.display = 'block';
|
||||||
this.reloadParagraph.style.display = 'block';
|
this.reloadParagraph.style.display = 'none';
|
||||||
this.message.textContent = 'Failed to reconnect to the server.';
|
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();
|
display.failed();
|
||||||
|
|
||||||
expect(display.modal.style.display).toBe('block');
|
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');
|
expect(display.button.style.display).toBe('block');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -58,24 +58,23 @@ describe('DefaultReconnectionHandler', () => {
|
||||||
expect(reconnect).toHaveBeenCalledTimes(1);
|
expect(reconnect).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Skipped while under investigation: https://github.com/aspnet/AspNetCore/issues/12578
|
it('invokes failed if reconnect fails', async () => {
|
||||||
// it('invokes failed if reconnect fails', async () => {
|
const testDisplay = createTestDisplay();
|
||||||
// const testDisplay = createTestDisplay();
|
const reconnect = jest.fn().mockRejectedValue(null);
|
||||||
// const reconnect = jest.fn().mockRejectedValue(null);
|
const handler = new DefaultReconnectionHandler(NullLogger.instance, testDisplay, reconnect);
|
||||||
// const handler = new DefaultReconnectionHandler(NullLogger.instance, testDisplay, reconnect);
|
window.console.error = jest.fn();
|
||||||
// window.console.error = jest.fn();
|
|
||||||
|
|
||||||
// handler.onConnectionDown({
|
handler.onConnectionDown({
|
||||||
// maxRetries: 3,
|
maxRetries: 2,
|
||||||
// retryIntervalMilliseconds: 20,
|
retryIntervalMilliseconds: 5,
|
||||||
// dialogId: 'ignored'
|
dialogId: 'ignored'
|
||||||
// });
|
});
|
||||||
|
|
||||||
// await delay(500);
|
await delay(500);
|
||||||
// expect(testDisplay.show).toHaveBeenCalled();
|
expect(testDisplay.show).toHaveBeenCalled();
|
||||||
// expect(testDisplay.failed).toHaveBeenCalled();
|
expect(testDisplay.failed).toHaveBeenCalled();
|
||||||
// expect(reconnect).toHaveBeenCalledTimes(3);
|
expect(reconnect).toHaveBeenCalledTimes(2);
|
||||||
// });
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function attachUserSpecifiedUI(options: ReconnectionOptions): Element {
|
function attachUserSpecifiedUI(options: ReconnectionOptions): Element {
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,12 @@ using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Data;
|
using System.Data;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Text.Json;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Ignitor;
|
using Ignitor;
|
||||||
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures;
|
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures;
|
||||||
|
using Microsoft.AspNetCore.Components.Web;
|
||||||
using Microsoft.AspNetCore.SignalR.Client;
|
using Microsoft.AspNetCore.SignalR.Client;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
@ -20,7 +23,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
||||||
{
|
{
|
||||||
public class ComponentHubReliabilityTest : IClassFixture<AspNetSiteServerFixture>, IDisposable
|
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;
|
private readonly AspNetSiteServerFixture _serverFixture;
|
||||||
|
|
||||||
public ComponentHubReliabilityTest(AspNetSiteServerFixture serverFixture, ITestOutputHelper output)
|
public ComponentHubReliabilityTest(AspNetSiteServerFixture serverFixture, ITestOutputHelper output)
|
||||||
|
|
@ -52,7 +55,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
||||||
return new Exception(error + Environment.NewLine + logs);
|
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 = _serverFixture.Host.Services.GetRequiredService<TestSink>();
|
||||||
TestSink.MessageLogged += LogMessages;
|
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"));
|
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]
|
[Fact]
|
||||||
public async Task CannotInvokeOnRenderCompletedBeforeInitialization()
|
public async Task CannotInvokeOnRenderCompletedBeforeInitialization()
|
||||||
{
|
{
|
||||||
|
|
@ -249,7 +364,10 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
||||||
public async Task OnLocationChanged_ReportsDebugForExceptionInValidation()
|
public async Task OnLocationChanged_ReportsDebugForExceptionInValidation()
|
||||||
{
|
{
|
||||||
// Arrange
|
// 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 rootUri = _serverFixture.RootUri;
|
||||||
var baseUri = new Uri(rootUri, "/subdir");
|
var baseUri = new Uri(rootUri, "/subdir");
|
||||||
Assert.True(await Client.ConnectAsync(baseUri, prerendered: false), "Couldn't connect to the app");
|
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.DoesNotContain(Logs, l => l.LogLevel > LogLevel.Information);
|
||||||
Assert.Contains(Logs, l =>
|
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()
|
public async Task OnLocationChanged_ReportsErrorForExceptionInUserCode()
|
||||||
{
|
{
|
||||||
// Arrange
|
// 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 rootUri = _serverFixture.RootUri;
|
||||||
var baseUri = new Uri(rootUri, "/subdir");
|
var baseUri = new Uri(rootUri, "/subdir");
|
||||||
Assert.True(await Client.ConnectAsync(baseUri, prerendered: false), "Couldn't connect to the app");
|
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(
|
await Client.ExpectCircuitError(() => Client.HubConnection.SendAsync(
|
||||||
"OnLocationChanged",
|
"OnLocationChanged",
|
||||||
new Uri(baseUri, "/test").AbsoluteUri,
|
new Uri(baseUri, "/test").AbsoluteUri,
|
||||||
false));
|
false));
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
var actualError = Assert.Single(Errors);
|
var actualError = Assert.Single(Errors);
|
||||||
Assert.Matches(expectedError, actualError);
|
Assert.Equal(expectedError, actualError);
|
||||||
Assert.Contains(Logs, l =>
|
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;
|
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
|
private class LogMessage
|
||||||
{
|
{
|
||||||
public LogMessage(LogLevel logLevel, string message, Exception exception)
|
public LogMessage(LogLevel logLevel, string message, Exception exception)
|
||||||
|
|
@ -394,7 +519,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
||||||
|
|
||||||
private class Batch
|
private class Batch
|
||||||
{
|
{
|
||||||
public Batch(int id, byte [] data)
|
public Batch(int id, byte[] data)
|
||||||
{
|
{
|
||||||
Id = id;
|
Id = id;
|
||||||
Data = data;
|
Data = data;
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,11 @@ using System.Text.Json;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Ignitor;
|
using Ignitor;
|
||||||
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures;
|
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures;
|
||||||
|
using Microsoft.AspNetCore.Components.Rendering;
|
||||||
using Microsoft.AspNetCore.Components.Web;
|
using Microsoft.AspNetCore.Components.Web;
|
||||||
using Microsoft.AspNetCore.SignalR.Client;
|
using Microsoft.AspNetCore.SignalR.Client;
|
||||||
|
using Microsoft.AspNetCore.Testing;
|
||||||
|
using Microsoft.AspNetCore.Testing.xunit;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Logging.Testing;
|
using Microsoft.Extensions.Logging.Testing;
|
||||||
|
|
@ -19,6 +22,7 @@ using Xunit.Abstractions;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
||||||
{
|
{
|
||||||
|
[Flaky("https://github.com/aspnet/AspNetCore/issues/13086", FlakyOn.All)]
|
||||||
public class InteropReliabilityTests : IClassFixture<AspNetSiteServerFixture>, IDisposable
|
public class InteropReliabilityTests : IClassFixture<AspNetSiteServerFixture>, IDisposable
|
||||||
{
|
{
|
||||||
private static readonly TimeSpan DefaultLatencyTimeout = TimeSpan.FromSeconds(30);
|
private static readonly TimeSpan DefaultLatencyTimeout = TimeSpan.FromSeconds(30);
|
||||||
|
|
@ -260,6 +264,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
[Flaky("https://github.com/aspnet/AspNetCore/issues/13086", FlakyOn.AzP.Windows)]
|
||||||
public async Task ContinuesWorkingAfterInvalidAsyncReturnCallback()
|
public async Task ContinuesWorkingAfterInvalidAsyncReturnCallback()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
|
|
@ -500,8 +505,8 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
||||||
// Arrange
|
// Arrange
|
||||||
await GoToTestComponent(Batches);
|
await GoToTestComponent(Batches);
|
||||||
var sink = _serverFixture.Host.Services.GetRequiredService<TestSink>();
|
var sink = _serverFixture.Host.Services.GetRequiredService<TestSink>();
|
||||||
var logEvents = new List<(LogLevel logLevel, string)>();
|
var logEvents = new List<(LogLevel logLevel, string eventIdName, Exception exception)>();
|
||||||
sink.MessageLogged += (wc) => logEvents.Add((wc.LogLevel, wc.EventId.Name));
|
sink.MessageLogged += (wc) => logEvents.Add((wc.LogLevel, wc.EventId.Name, wc.Exception));
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var browserDescriptor = new WebEventDescriptor()
|
var browserDescriptor = new WebEventDescriptor()
|
||||||
|
|
@ -520,8 +525,9 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
||||||
});
|
});
|
||||||
|
|
||||||
Assert.Contains(
|
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.
|
// Taking any other action will fail because the circuit is disposed.
|
||||||
await Client.ExpectCircuitErrorAndDisconnect(async () =>
|
await Client.ExpectCircuitErrorAndDisconnect(async () =>
|
||||||
|
|
@ -563,7 +569,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
||||||
Assert.Contains(
|
Assert.Contains(
|
||||||
logEvents,
|
logEvents,
|
||||||
e => e.eventIdName == "DispatchEventFailedToDispatchEvent" && e.logLevel == LogLevel.Debug &&
|
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.
|
// Taking any other action will fail because the circuit is disposed.
|
||||||
await Client.ExpectCircuitErrorAndDisconnect(async () =>
|
await Client.ExpectCircuitErrorAndDisconnect(async () =>
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
||||||
appElement.FindElement(By.Id("run-without-dispatch")).Click();
|
appElement.FindElement(By.Id("run-without-dispatch")).Click();
|
||||||
|
|
||||||
Browser.Contains(
|
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);
|
() => result.Text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<h1>Counter</h1>
|
<h1>Counter</h1>
|
||||||
<p>Current count: @currentCount</p>
|
<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>
|
<label>
|
||||||
<input type="checkbox" @bind="handleClicks" />
|
<input type="checkbox" @bind="handleClicks" />
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
@page "/prerendered-interop"
|
@page "/prerendered-interop"
|
||||||
@using Microsoft.AspNetCore.Components
|
@using Microsoft.AspNetCore.Components
|
||||||
@using Microsoft.JSInterop
|
@using Microsoft.JSInterop
|
||||||
@inject IComponentContext ComponentContext
|
|
||||||
@inject IJSRuntime JSRuntime
|
@inject IJSRuntime JSRuntime
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
@page "/prerendered-transition"
|
@page "/prerendered-transition"
|
||||||
@using Microsoft.AspNetCore.Components
|
@using Microsoft.AspNetCore.Components
|
||||||
@using Microsoft.AspNetCore.Components.Authorization
|
@using Microsoft.AspNetCore.Components.Authorization
|
||||||
@inject IComponentContext ComponentContext
|
|
||||||
|
|
||||||
<CascadingAuthenticationState>
|
<CascadingAuthenticationState>
|
||||||
<AuthorizeView>
|
<AuthorizeView>
|
||||||
|
|
@ -11,7 +10,7 @@
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
Current state:
|
Current state:
|
||||||
<strong id="connected-state">@(ComponentContext.IsConnected ? "connected" : "not connected")</strong>
|
<strong id="connected-state">@(hasRenderedInConnectedMode ? "connected" : "not connected")</strong>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
|
|
@ -22,11 +21,13 @@
|
||||||
</CascadingAuthenticationState>
|
</CascadingAuthenticationState>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
|
bool hasRenderedInConnectedMode;
|
||||||
int count;
|
int count;
|
||||||
protected override Task OnAfterRenderAsync(bool firstRender)
|
protected override Task OnAfterRenderAsync(bool firstRender)
|
||||||
{
|
{
|
||||||
if (firstRender)
|
if (firstRender)
|
||||||
{
|
{
|
||||||
|
hasRenderedInConnectedMode = true;
|
||||||
// We need to queue another render when we connect, otherwise the
|
// We need to queue another render when we connect, otherwise the
|
||||||
// browser won't see anything.
|
// browser won't see anything.
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ using Microsoft.AspNetCore.Hosting;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
|
|
||||||
|
|
||||||
namespace ComponentsApp.Server
|
namespace ComponentsApp.Server
|
||||||
{
|
{
|
||||||
public class Startup
|
public class Startup
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,10 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Http.Features;
|
|
||||||
using Microsoft.AspNetCore.Routing.Matching;
|
using Microsoft.AspNetCore.Routing.Matching;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
|
@ -51,8 +51,6 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
if (endpoint != null)
|
if (endpoint != null)
|
||||||
{
|
{
|
||||||
Log.MatchSkipped(_logger, endpoint);
|
Log.MatchSkipped(_logger, endpoint);
|
||||||
|
|
||||||
// Someone else set the endpoint, we'll let them handle the clearing of the endpoint.
|
|
||||||
return _next(httpContext);
|
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
|
// If there was no mutation of the endpoint then log failure
|
||||||
var endpoint = httpContext.GetEndpoint();
|
var endpoint = httpContext.GetEndpoint();
|
||||||
|
|
@ -108,18 +107,7 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
Log.MatchSuccess(_logger, endpoint);
|
Log.MatchSuccess(_logger, endpoint);
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
return _next(httpContext);
|
||||||
{
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialization is async to avoid blocking threads while reflection and things
|
// Initialization is async to avoid blocking threads while reflection and things
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,7 @@ namespace Microsoft.AspNetCore.Builder
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task UseRouting_ServicesRegistered_Match_DoesNotSetFeature()
|
public async Task UseRouting_ServicesRegistered_Match_DoesNotSetsFeature()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var endpoint = new RouteEndpoint(
|
var endpoint = new RouteEndpoint(
|
||||||
|
|
@ -104,6 +104,7 @@ namespace Microsoft.AspNetCore.Builder
|
||||||
// Assert
|
// Assert
|
||||||
var feature = httpContext.Features.Get<IEndpointFeature>();
|
var feature = httpContext.Features.Get<IEndpointFeature>();
|
||||||
Assert.NotNull(feature);
|
Assert.NotNull(feature);
|
||||||
|
Assert.Same(endpoint, httpContext.GetEndpoint());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
|
||||||
|
|
@ -21,72 +21,7 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
public class EndpointRoutingMiddlewareTest
|
public class EndpointRoutingMiddlewareTest
|
||||||
{
|
{
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task Invoke_ChangedPath_ResultsInDifferentResult()
|
public async Task Invoke_OnCall_SetsEndpointFeature()
|
||||||
{
|
|
||||||
// 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()
|
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var httpContext = CreateHttpContext();
|
var httpContext = CreateHttpContext();
|
||||||
|
|
@ -99,36 +34,14 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
// Assert
|
// Assert
|
||||||
var endpointFeature = httpContext.Features.Get<IEndpointFeature>();
|
var endpointFeature = httpContext.Features.Get<IEndpointFeature>();
|
||||||
Assert.NotNull(endpointFeature);
|
Assert.NotNull(endpointFeature);
|
||||||
Assert.Null(endpointFeature.Endpoint);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task Invoke_OnCall_SetsEndpointFeatureAndResetsRouteValues()
|
public async Task Invoke_SkipsRouting_IfEndpointSet()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var httpContext = CreateHttpContext();
|
var httpContext = CreateHttpContext();
|
||||||
var initialRouteData = new RouteData();
|
httpContext.SetEndpoint(new Endpoint(c => Task.CompletedTask, new EndpointMetadataCollection(), "myapp"));
|
||||||
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);
|
|
||||||
|
|
||||||
var middleware = CreateMiddleware();
|
var middleware = CreateMiddleware();
|
||||||
|
|
||||||
|
|
@ -137,7 +50,7 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
var endpoint = httpContext.GetEndpoint();
|
var endpoint = httpContext.GetEndpoint();
|
||||||
Assert.Same(expectedEndpoint, endpoint);
|
Assert.NotNull(endpoint);
|
||||||
Assert.Equal("myapp", endpoint.DisplayName);
|
Assert.Equal("myapp", endpoint.DisplayName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -182,29 +95,22 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var httpContext = CreateHttpContext();
|
var httpContext = CreateHttpContext();
|
||||||
var nextCalled = false;
|
|
||||||
|
|
||||||
var middleware = CreateMiddleware(next: context =>
|
var middleware = CreateMiddleware();
|
||||||
{
|
|
||||||
var routeData = httpContext.GetRouteData();
|
|
||||||
var routeValue = httpContext.GetRouteValue("controller");
|
|
||||||
var routeValuesFeature = httpContext.Features.Get<IRouteValuesFeature>();
|
|
||||||
nextCalled = true;
|
|
||||||
|
|
||||||
// Assert
|
// Act
|
||||||
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
|
|
||||||
await middleware.Invoke(httpContext);
|
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]
|
[Fact]
|
||||||
|
|
@ -212,29 +118,22 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var httpContext = CreateHttpContext();
|
var httpContext = CreateHttpContext();
|
||||||
var called = false;
|
|
||||||
|
|
||||||
var middleware = CreateMiddleware(next: context =>
|
var middleware = CreateMiddleware();
|
||||||
{
|
|
||||||
var routeData = httpContext.GetRouteData();
|
|
||||||
var routeValue = httpContext.GetRouteValue("controller");
|
|
||||||
var routeValuesFeature = httpContext.Features.Get<IRouteValuesFeature>();
|
|
||||||
called = true;
|
|
||||||
|
|
||||||
// Assert
|
// Act
|
||||||
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
|
|
||||||
await middleware.Invoke(httpContext);
|
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]
|
[Fact]
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ using System.Runtime.ExceptionServices;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using Microsoft.Net.Http.Headers;
|
using Microsoft.Net.Http.Headers;
|
||||||
|
|
@ -103,7 +104,8 @@ namespace Microsoft.AspNetCore.Diagnostics
|
||||||
}
|
}
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
context.Response.Clear();
|
ClearHttpContext(context);
|
||||||
|
|
||||||
var exceptionHandlerFeature = new ExceptionHandlerFeature()
|
var exceptionHandlerFeature = new ExceptionHandlerFeature()
|
||||||
{
|
{
|
||||||
Error = edi.SourceException,
|
Error = edi.SourceException,
|
||||||
|
|
@ -137,6 +139,17 @@ namespace Microsoft.AspNetCore.Diagnostics
|
||||||
edi.Throw(); // Re-throw the original if we couldn't handle it
|
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)
|
private static Task ClearCacheHeaders(object state)
|
||||||
{
|
{
|
||||||
var headers = ((HttpResponse)state).Headers;
|
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<StaticComponentRenderer>();
|
||||||
services.TryAddScoped<NavigationManager, HttpNavigationManager>();
|
services.TryAddScoped<NavigationManager, HttpNavigationManager>();
|
||||||
services.TryAddScoped<IJSRuntime, UnsupportedJavaScriptRuntime>();
|
services.TryAddScoped<IJSRuntime, UnsupportedJavaScriptRuntime>();
|
||||||
services.TryAddScoped<IComponentContext, UnsupportedComponentContext>();
|
|
||||||
services.TryAddScoped<INavigationInterception, UnsupportedNavigationInterception>();
|
services.TryAddScoped<INavigationInterception, UnsupportedNavigationInterception>();
|
||||||
|
|
||||||
services.TryAddTransient<ControllerSaveTempDataPropertyFilter>();
|
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>
|
<IsPackable>true</IsPackable>
|
||||||
<IsTestProject>false</IsTestProject>
|
<IsTestProject>false</IsTestProject>
|
||||||
<IsShippingPackage>true</IsShippingPackage>
|
<IsShippingPackage>true</IsShippingPackage>
|
||||||
|
<PackOnBuild>true</PackOnBuild>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
||||||
|
|
@ -9,58 +9,60 @@ namespace SignalRSamples.Hubs
|
||||||
{
|
{
|
||||||
public class Chat : Hub
|
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 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);
|
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 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 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);
|
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 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 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);
|
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>
|
<option value="hubT">Hub<T></option>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
<input type="text" id="displayname" placeholder="Enter User Name" />
|
||||||
<input type="button" id="connect" value="Connect" />
|
<input type="button" id="connect" value="Connect" />
|
||||||
<input type="button" id="disconnect" value="Disconnect" />
|
<input type="button" id="disconnect" value="Disconnect" />
|
||||||
|
|
||||||
|
|
@ -58,6 +59,7 @@
|
||||||
<div class="input-append">
|
<div class="input-append">
|
||||||
<input type="text" id="me-message-text" placeholder="Type a message" />
|
<input type="text" id="me-message-text" placeholder="Type a message" />
|
||||||
<input type="button" id="send" class="btn" value="Send to Me" />
|
<input type="button" id="send" class="btn" value="Send to Me" />
|
||||||
|
<input type="hidden" id="displayname" />
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
|
@ -138,6 +140,13 @@
|
||||||
var connection;
|
var connection;
|
||||||
|
|
||||||
click('connect', function (event) {
|
click('connect', function (event) {
|
||||||
|
|
||||||
|
name = document.getElementById("displayname").value
|
||||||
|
if (name === "") {
|
||||||
|
alert("Please enter a valid name");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let hubRoute = hubTypeDropdown.value || "default";
|
let hubRoute = hubTypeDropdown.value || "default";
|
||||||
let protocol = protocolDropdown.value === "msgpack" ?
|
let protocol = protocolDropdown.value === "msgpack" ?
|
||||||
new signalR.protocols.msgpack.MessagePackHubProtocol() :
|
new signalR.protocols.msgpack.MessagePackHubProtocol() :
|
||||||
|
|
@ -148,6 +157,7 @@
|
||||||
options.transport = signalR.HttpTransportType[transportDropdown.value];
|
options.transport = signalR.HttpTransportType[transportDropdown.value];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hubRoute = hubRoute + "?name=" + name;
|
||||||
console.log('http://' + document.location.host + '/' + hubRoute);
|
console.log('http://' + document.location.host + '/' + hubRoute);
|
||||||
|
|
||||||
var connectionBuilder = new signalR.HubConnectionBuilder()
|
var connectionBuilder = new signalR.HubConnectionBuilder()
|
||||||
|
|
@ -205,45 +215,45 @@
|
||||||
|
|
||||||
click('broadcast', function (event) {
|
click('broadcast', function (event) {
|
||||||
let data = getText('message-text');
|
let data = getText('message-text');
|
||||||
invoke(connection, 'Send', data);
|
invoke(connection, 'Send', name, data);
|
||||||
});
|
});
|
||||||
|
|
||||||
click('join-group', function (event) {
|
click('join-group', function (event) {
|
||||||
let groupName = getText('group-name');
|
let groupName = getText('group-name');
|
||||||
invoke(connection, 'JoinGroup', groupName);
|
invoke(connection, 'JoinGroup', groupName, name);
|
||||||
});
|
});
|
||||||
|
|
||||||
click('leave-group', function (event) {
|
click('leave-group', function (event) {
|
||||||
let groupName = getText('group-name');
|
let groupName = getText('group-name');
|
||||||
invoke(connection, 'LeaveGroup', groupName);
|
invoke(connection, 'LeaveGroup', groupName, name);
|
||||||
});
|
});
|
||||||
|
|
||||||
click('groupmsg', function (event) {
|
click('groupmsg', function (event) {
|
||||||
let groupName = getText('group-name');
|
let groupName = getText('group-name');
|
||||||
let message = getText('group-message-text');
|
let message = getText('group-message-text');
|
||||||
invoke(connection, 'SendToGroup', groupName, message);
|
invoke(connection, 'SendToGroup', groupName, name, message);
|
||||||
});
|
});
|
||||||
|
|
||||||
click('others-groupmsg', function (event) {
|
click('others-groupmsg', function (event) {
|
||||||
let groupName = getText('group-name');
|
let groupName = getText('group-name');
|
||||||
let message = getText('group-message-text');
|
let message = getText('group-message-text');
|
||||||
invoke(connection, 'SendToOthersInGroup', groupName, message);
|
invoke(connection, 'SendToOthersInGroup', groupName, name, message);
|
||||||
});
|
});
|
||||||
|
|
||||||
click('send', function (event) {
|
click('send', function (event) {
|
||||||
let data = getText('me-message-text');
|
let data = getText('me-message-text');
|
||||||
invoke(connection, 'Echo', data);
|
invoke(connection, 'Echo', name, data);
|
||||||
});
|
});
|
||||||
|
|
||||||
click('broadcast-exceptme', function (event) {
|
click('broadcast-exceptme', function (event) {
|
||||||
let data = getText('message-text');
|
let data = getText('message-text');
|
||||||
invoke(connection, 'SendToOthers', data);
|
invoke(connection, 'SendToOthers', name, data);
|
||||||
});
|
});
|
||||||
|
|
||||||
click('connection-send', function (event) {
|
click('connection-send', function (event) {
|
||||||
let data = getText('connection-message-text');
|
let data = getText('connection-message-text');
|
||||||
let id = getText('connection-id');
|
let id = getText('connection-id');
|
||||||
invoke(connection, 'SendToConnection', id, data);
|
invoke(connection, 'SendToConnection', id, name, data);
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue