Skip to content

Commit 31fc9ba

Browse files
authored
Cache uncached AppContext.TryGetSwitch calls (#66513)
1 parent 66093cf commit 31fc9ba

15 files changed

Lines changed: 167 additions & 101 deletions

File tree

src/Components/Components/src/Routing/RegexConstraintSupport.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,6 @@ internal static class RegexConstraintSupport
1010
// until the context switch flows to the runtime.
1111
// This value gets updated by the linker when the app is trimmed, so the code will always be removed from
1212
// webassembly unless the switch is enabled.
13-
public static bool IsEnabled =>
13+
public static bool IsEnabled { get; } =
1414
AppContext.TryGetSwitch("Microsoft.AspNetCore.Components.Routing.RegexConstraintSupport", out var enabled) && enabled;
1515
}

src/Components/Components/test/Microsoft.AspNetCore.Components.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
<Reference Include="Microsoft.AspNetCore.Components" />
1616
<Reference Include="Microsoft.Extensions.DependencyInjection" />
1717
<Reference Include="Microsoft.Extensions.Diagnostics.Testing" />
18+
<Reference Include="Microsoft.DotNet.RemoteExecutor" />
1819
</ItemGroup>
1920

2021
<ItemGroup>

src/Components/Components/test/RendererTest.cs

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
using Microsoft.AspNetCore.Components.RenderTree;
1313
using Microsoft.AspNetCore.Components.Test.Helpers;
1414
using Microsoft.AspNetCore.InternalTesting;
15+
using Microsoft.DotNet.RemoteExecutor;
1516
using Microsoft.Extensions.Logging.Abstractions;
1617

1718
namespace Microsoft.AspNetCore.Components.Test;
@@ -4981,13 +4982,18 @@ public async Task DisposeAsyncCallsComponentDisposeAsyncOnSyncContext()
49814982
Assert.True(wasOnSyncContext);
49824983
}
49834984

4984-
[Fact]
4985-
public async Task NoHotReloadListenersAreRegistered_WhenHotReloadIsDisabled()
4985+
[ConditionalFact]
4986+
[RemoteExecutionSupported]
4987+
public void NoHotReloadListenersAreRegistered_WhenHotReloadIsDisabled()
49864988
{
4987-
// Arrange
4988-
try
4989+
var options = new RemoteInvokeOptions();
4990+
options.RuntimeConfigurationOptions.Add("System.Reflection.Metadata.MetadataUpdater.IsSupported", "false");
4991+
4992+
using var remoteHandle = RemoteExecutor.Invoke(static async () =>
49894993
{
4994+
// Set the switch before any code triggers HotReloadManager type initialization.
49904995
AppContext.SetSwitch("System.Reflection.Metadata.MetadataUpdater.IsSupported", false);
4996+
49914997
await using var renderer = new TestRenderer();
49924998
var hotReloadManager = new HotReloadManager();
49934999
renderer.HotReloadManager = hotReloadManager;
@@ -4998,17 +5004,12 @@ public async Task NoHotReloadListenersAreRegistered_WhenHotReloadIsDisabled()
49985004
builder.CloseElement();
49995005
});
50005006

5001-
// Act
50025007
var componentId = renderer.AssignRootComponentId(component);
50035008
component.TriggerRender();
50045009
Assert.False(hotReloadManager.IsSubscribedTo);
50055010

50065011
await renderer.DisposeAsync();
5007-
}
5008-
finally
5009-
{
5010-
AppContext.SetSwitch("System.Reflection.Metadata.MetadataUpdater.IsSupported", true);
5011-
}
5012+
}, options);
50125013
}
50135014

50145015
[Fact]

src/Components/Endpoints/src/DependencyInjection/HttpNavigationManager.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,12 @@ internal sealed class HttpNavigationManager : NavigationManager, IHostEnvironmen
1010
{
1111
private const string _disableThrowNavigationException = "Microsoft.AspNetCore.Components.Endpoints.NavigationManager.DisableThrowNavigationException";
1212

13-
[FeatureSwitchDefinition(_disableThrowNavigationException)]
14-
private static bool _throwNavigationException =>
13+
private static readonly bool s_throwNavigationException =
1514
!AppContext.TryGetSwitch(_disableThrowNavigationException, out var switchValue) || !switchValue;
1615

16+
[FeatureSwitchDefinition(_disableThrowNavigationException)]
17+
private static bool _throwNavigationException => s_throwNavigationException;
18+
1719
private Func<string, Task>? _onNavigateTo;
1820

1921
void IHostEnvironmentNavigationManager.Initialize(string baseUri, string uri) => Initialize(baseUri, uri);

src/Components/Endpoints/test/EndpointHtmlRendererTest.cs

Lines changed: 63 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
using Microsoft.AspNetCore.Http;
2323
using Microsoft.AspNetCore.Http.Features;
2424
using Microsoft.AspNetCore.InternalTesting;
25+
using Microsoft.DotNet.RemoteExecutor;
2526
using Microsoft.Extensions.DependencyInjection;
2627
using Microsoft.Extensions.DependencyInjection.Extensions;
2728
using Microsoft.Extensions.FileProviders;
@@ -982,62 +983,72 @@ public async Task Rendering_ComponentWithJsInteropThrows()
982983
exception.Message);
983984
}
984985

985-
[Theory]
986+
[ConditionalTheory]
987+
[RemoteExecutionSupported]
986988
[InlineData(true)]
987989
[InlineData(false)]
988-
public async Task UriHelperRedirect_ThrowsInvalidOperationException_WhenResponseHasAlreadyStarted(bool allowException)
990+
public void UriHelperRedirect_ThrowsInvalidOperationException_WhenResponseHasAlreadyStarted(bool allowException)
989991
{
990-
AppContext.SetSwitch("Microsoft.AspNetCore.Components.Endpoints.NavigationManager.DisableThrowNavigationException", isEnabled: !allowException);
991-
// Arrange
992-
var ctx = new DefaultHttpContext();
993-
ctx.Request.Scheme = "http";
994-
ctx.Request.Host = new HostString("localhost");
995-
ctx.Request.PathBase = "/base";
996-
ctx.Request.Path = "/path";
997-
ctx.Request.QueryString = new QueryString("?query=value");
998-
ctx.Response.Body = new MemoryStream();
999-
var responseMock = new Mock<IHttpResponseFeature>();
1000-
responseMock.Setup(r => r.HasStarted).Returns(true);
1001-
ctx.Features.Set(responseMock.Object);
1002-
var httpContext = GetHttpContext(ctx);
1003-
string redirectUri = "http://localhost/redirect";
992+
var options = new RemoteInvokeOptions();
993+
options.RuntimeConfigurationOptions.Add(
994+
"Microsoft.AspNetCore.Components.Endpoints.NavigationManager.DisableThrowNavigationException",
995+
(!allowException).ToString().ToLowerInvariant());
1004996

1005-
// Act
1006-
if (allowException)
997+
using var remoteHandle = RemoteExecutor.Invoke(static async (allowExceptionStr) =>
1007998
{
1008-
var exception = await Assert.ThrowsAsync<InvalidOperationException>(async () => await renderer.PrerenderComponentAsync(
1009-
httpContext,
1010-
typeof(RedirectComponent),
1011-
null,
1012-
ParameterView.FromDictionary(new Dictionary<string, object>
1013-
{
1014-
{ "RedirectUri", redirectUri }
1015-
})));
1016-
1017-
Assert.Equal("A navigation command was attempted during prerendering after the server already started sending the response. " +
1018-
"Navigation commands can not be issued during server-side prerendering after the response from the server has started. Applications must buffer the" +
1019-
"response and avoid using features like FlushAsync() before all components on the page have been rendered to prevent failed navigation commands.",
1020-
exception.Message);
1021-
}
1022-
else
1023-
{
1024-
await renderer.PrerenderComponentAsync(
1025-
httpContext,
1026-
typeof(RedirectComponent),
1027-
null,
1028-
ParameterView.FromDictionary(new Dictionary<string, object>
1029-
{
1030-
{ "RedirectUri", redirectUri }
1031-
}));
1032-
// read the custom element from the response body
1033-
httpContext.Response.Body.Position = 0;
1034-
var reader = new StreamReader(httpContext.Response.Body);
1035-
var output = await reader.ReadToEndAsync();
1036-
1037-
// Assert that the output contains expected navigation instructions.
1038-
var pattern = "^<blazor-ssr><template type=\"redirection\".*>.*<\\/template><blazor-ssr-end><\\/blazor-ssr-end><\\/blazor-ssr>$";
1039-
Assert.Matches(pattern, output);
1040-
}
999+
var allowException = bool.Parse(allowExceptionStr);
1000+
var services = CreateDefaultServiceCollection().BuildServiceProvider();
1001+
var renderer = new TestEndpointHtmlRenderer(services, NullLoggerFactory.Instance);
1002+
1003+
var ctx = new DefaultHttpContext();
1004+
ctx.Request.Scheme = "http";
1005+
ctx.Request.Host = new HostString("localhost");
1006+
ctx.Request.PathBase = "/base";
1007+
ctx.Request.Path = "/path";
1008+
ctx.Request.QueryString = new QueryString("?query=value");
1009+
ctx.Response.Body = new MemoryStream();
1010+
ctx.RequestServices = services;
1011+
var responseMock = new Mock<IHttpResponseFeature>();
1012+
responseMock.Setup(r => r.HasStarted).Returns(true);
1013+
ctx.Features.Set(responseMock.Object);
1014+
string redirectUri = "http://localhost/redirect";
1015+
1016+
if (allowException)
1017+
{
1018+
var exception = await Assert.ThrowsAsync<InvalidOperationException>(async () => await renderer.PrerenderComponentAsync(
1019+
ctx,
1020+
typeof(RedirectComponent),
1021+
null,
1022+
ParameterView.FromDictionary(new Dictionary<string, object>
1023+
{
1024+
{ "RedirectUri", redirectUri }
1025+
})));
1026+
1027+
Assert.Equal("A navigation command was attempted during prerendering after the server already started sending the response. " +
1028+
"Navigation commands can not be issued during server-side prerendering after the response from the server has started. Applications must buffer the" +
1029+
"response and avoid using features like FlushAsync() before all components on the page have been rendered to prevent failed navigation commands.",
1030+
exception.Message);
1031+
}
1032+
else
1033+
{
1034+
await renderer.PrerenderComponentAsync(
1035+
ctx,
1036+
typeof(RedirectComponent),
1037+
null,
1038+
ParameterView.FromDictionary(new Dictionary<string, object>
1039+
{
1040+
{ "RedirectUri", redirectUri }
1041+
}));
1042+
// read the custom element from the response body
1043+
ctx.Response.Body.Position = 0;
1044+
var reader = new StreamReader(ctx.Response.Body);
1045+
var output = await reader.ReadToEndAsync();
1046+
1047+
// Assert that the output contains expected navigation instructions.
1048+
var pattern = "^<blazor-ssr><template type=\"redirection\".*>.*<\\/template><blazor-ssr-end><\\/blazor-ssr-end><\\/blazor-ssr>$";
1049+
Assert.Matches(pattern, output);
1050+
}
1051+
}, allowException.ToString(), options);
10411052
}
10421053

10431054
[Fact]
@@ -1343,7 +1354,7 @@ public async Task RenderMode_CanRenderInteractiveComponents()
13431354
lines[0] = AssertAndStripBrowserConfiguration(lines[0]);
13441355
var serverMarkerMatch = Regex.Match(lines[0], PrerenderedComponentPattern);
13451356
var serverNonPrerenderedMarkerMatch = Regex.Match(lines[1], ComponentPattern);
1346-
1357+
13471358
var webAssemblyMarkerMatch = Regex.Match(lines[2], PrerenderedComponentPattern);
13481359
var webAssemblyNonPrerenderedMarkerMatch = Regex.Match(lines[3], ComponentPattern);
13491360

src/Components/Endpoints/test/Microsoft.AspNetCore.Components.Endpoints.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
<Reference Include="Microsoft.AspNetCore.Routing" />
2929
<Reference Include="Microsoft.AspNetCore.Routing.Abstractions" />
3030
<Reference Include="Microsoft.Extensions.Hosting" />
31+
<Reference Include="Microsoft.DotNet.RemoteExecutor" />
3132
</ItemGroup>
3233

3334
<ItemGroup>
Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,20 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System.Diagnostics.CodeAnalysis;
5+
46
namespace Microsoft.AspNetCore.Components.QuickGrid;
57

68
internal static class QuickGridFeatureFlags
79
{
8-
internal static bool EnableUrlBasedQuickGridNavigationAndSorting =>
9-
!AppContext.TryGetSwitch("Microsoft.AspNetCore.Components.QuickGrid.EnableUrlBasedQuickGridNavigationAndSorting", out var isEnabled) || isEnabled;
10+
private const string EnableUrlBasedNavigationSwitchName =
11+
"Microsoft.AspNetCore.Components.QuickGrid.EnableUrlBasedQuickGridNavigationAndSorting";
12+
13+
#pragma warning disable IDE0044
14+
private static bool s_enableUrlBasedQuickGridNavigationAndSorting =
15+
!AppContext.TryGetSwitch(EnableUrlBasedNavigationSwitchName, out var isEnabled) || isEnabled;
16+
#pragma warning restore IDE0044
17+
18+
[FeatureSwitchDefinition(EnableUrlBasedNavigationSwitchName)]
19+
internal static bool EnableUrlBasedQuickGridNavigationAndSorting => s_enableUrlBasedQuickGridNavigationAndSorting;
1020
}

src/Components/Server/src/Circuits/RemoteNavigationManager.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ internal sealed partial class RemoteNavigationManager : NavigationManager, IHost
2020
private const string _disableThrowNavigationException = "Microsoft.AspNetCore.Components.Endpoints.NavigationManager.DisableThrowNavigationException";
2121

2222
[FeatureSwitchDefinition(_disableThrowNavigationException)]
23-
private static bool _throwNavigationException =>
23+
private static bool _throwNavigationException { get; } =
2424
!AppContext.TryGetSwitch(_disableThrowNavigationException, out var switchValue) || !switchValue;
2525
private Func<string, Task>? _onNavigateTo;
2626

src/Components/Shared/src/HotReloadManager.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,12 @@ internal sealed class HotReloadManager
1313
{
1414
public static readonly HotReloadManager Default = new();
1515

16-
[FeatureSwitchDefinition("System.Reflection.Metadata.MetadataUpdater.IsSupported")]
17-
internal static bool IsSupported =>
16+
private static readonly bool s_isSupported =
1817
AppContext.TryGetSwitch("System.Reflection.Metadata.MetadataUpdater.IsSupported", out bool isSupported) ? isSupported : true;
1918

19+
[FeatureSwitchDefinition("System.Reflection.Metadata.MetadataUpdater.IsSupported")]
20+
internal static bool IsSupported => s_isSupported;
21+
2022
/// <summary>
2123
/// Gets a value that determines if OnDeltaApplied is subscribed to.
2224
/// </summary>

src/Components/test/testassets/Components.TestServer/RazorComponentEndpointsStartup.cs

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,15 @@ public RazorComponentEndpointsStartup(IConfiguration configuration)
3131
// This method gets called by the runtime. Use this method to add services to the container.
3232
public void ConfigureServices(IServiceCollection services)
3333
{
34-
if (Configuration.GetValue<bool>("DisableUrlDrivenNavigation"))
35-
{
36-
AppContext.SetSwitch("Microsoft.AspNetCore.Components.QuickGrid.EnableUrlBasedQuickGridNavigationAndSorting", false);
37-
}
38-
else
39-
{
40-
AppContext.SetSwitch("Microsoft.AspNetCore.Components.QuickGrid.EnableUrlBasedQuickGridNavigationAndSorting", true);
41-
}
34+
var enableUrlNavigation = !Configuration.GetValue<bool>("DisableUrlDrivenNavigation");
35+
AppContext.SetSwitch("Microsoft.AspNetCore.Components.QuickGrid.EnableUrlBasedQuickGridNavigationAndSorting", enableUrlNavigation);
36+
37+
// Also update the cached field in QuickGridFeatureFlags, since it captures the AppContext
38+
// switch value once at static initialization and won't see subsequent AppContext changes.
39+
var featureFlagsType = typeof(Microsoft.AspNetCore.Components.QuickGrid.QuickGrid<>).Assembly
40+
.GetType("Microsoft.AspNetCore.Components.QuickGrid.QuickGridFeatureFlags");
41+
featureFlagsType?.GetField("s_enableUrlBasedQuickGridNavigationAndSorting", BindingFlags.Static | BindingFlags.NonPublic)
42+
?.SetValue(null, enableUrlNavigation);
4243

4344
services.AddValidation();
4445

0 commit comments

Comments
 (0)