From e54bca365a2a948c0f6dd950bb8200f3d179b5a5 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Wed, 23 Apr 2025 18:10:30 +0200 Subject: [PATCH 01/15] Remove the hard stop - the whole batch has to finish its rendering. --- .../Components/src/PublicAPI.Unshipped.txt | 3 +-- .../Components/src/RenderTree/Renderer.cs | 19 ++----------------- .../EndpointHtmlRenderer.EventDispatch.cs | 2 +- .../src/Rendering/EndpointHtmlRenderer.cs | 9 +-------- 4 files changed, 5 insertions(+), 28 deletions(-) diff --git a/src/Components/Components/src/PublicAPI.Unshipped.txt b/src/Components/Components/src/PublicAPI.Unshipped.txt index b99e9fd7f216..1ef94910be78 100644 --- a/src/Components/Components/src/PublicAPI.Unshipped.txt +++ b/src/Components/Components/src/PublicAPI.Unshipped.txt @@ -14,5 +14,4 @@ Microsoft.AspNetCore.Components.SupplyParameterFromPersistentComponentStateAttri Microsoft.AspNetCore.Components.SupplyParameterFromPersistentComponentStateAttribute.SupplyParameterFromPersistentComponentStateAttribute() -> void Microsoft.Extensions.DependencyInjection.SupplyParameterFromPersistentComponentStateProviderServiceCollectionExtensions static Microsoft.AspNetCore.Components.Infrastructure.RegisterPersistentComponentStateServiceCollectionExtensions.AddPersistentServiceRegistration(Microsoft.Extensions.DependencyInjection.IServiceCollection! services, Microsoft.AspNetCore.Components.IComponentRenderMode! componentRenderMode) -> Microsoft.Extensions.DependencyInjection.IServiceCollection! -static Microsoft.Extensions.DependencyInjection.SupplyParameterFromPersistentComponentStateProviderServiceCollectionExtensions.AddSupplyValueFromPersistentComponentStateProvider(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services) -> Microsoft.Extensions.DependencyInjection.IServiceCollection! -virtual Microsoft.AspNetCore.Components.RenderTree.Renderer.SignalRendererToFinishRendering() -> void \ No newline at end of file +static Microsoft.Extensions.DependencyInjection.SupplyParameterFromPersistentComponentStateProviderServiceCollectionExtensions.AddSupplyValueFromPersistentComponentStateProvider(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services) -> Microsoft.Extensions.DependencyInjection.IServiceCollection! \ No newline at end of file diff --git a/src/Components/Components/src/RenderTree/Renderer.cs b/src/Components/Components/src/RenderTree/Renderer.cs index 5ba977930a46..62357b9ac34e 100644 --- a/src/Components/Components/src/RenderTree/Renderer.cs +++ b/src/Components/Components/src/RenderTree/Renderer.cs @@ -46,7 +46,6 @@ public abstract partial class Renderer : IDisposable, IAsyncDisposable private bool _rendererIsDisposed; private bool _hotReloadInitialized; - private bool _rendererIsStopped; /// /// Allows the caller to handle exceptions from the SynchronizationContext when one is available. @@ -665,12 +664,6 @@ internal void AddToRenderQueue(int componentId, RenderFragment renderFragment) { Dispatcher.AssertAccess(); - if (_rendererIsStopped) - { - // Once we're stopped, we'll disregard further attempts to queue anything - return; - } - var componentState = GetOptionalComponentState(componentId); if (componentState == null) { @@ -737,22 +730,14 @@ private ComponentState GetRequiredRootComponentState(int componentId) return componentState; } - /// - /// Stop adding render requests to the render queue. - /// - protected virtual void SignalRendererToFinishRendering() - { - _rendererIsStopped = true; - } - /// /// Processes pending renders requests from components if there are any. /// protected virtual void ProcessPendingRender() { - if (_rendererIsDisposed || _rendererIsStopped) + if (_rendererIsDisposed) { - // Once we're disposed or stopped, we'll disregard further attempts to render anything + // Once we're disposed, we'll disregard further attempts to render anything return; } diff --git a/src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.EventDispatch.cs b/src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.EventDispatch.cs index 8a1062a58d76..6af020fc847a 100644 --- a/src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.EventDispatch.cs +++ b/src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.EventDispatch.cs @@ -97,7 +97,7 @@ private async Task SetNotFoundResponseAsync(string baseUri) // When the application triggers a NotFound event, we continue rendering the current batch. // However, after completing this batch, we do not want to process any further UI updates, // as we are going to return a 404 status and discard the UI updates generated so far. - SignalRendererToFinishRenderingAfterCurrentBatch(); + SignalRendererToFinishRendering(); } private async Task OnNavigateTo(string uri) diff --git a/src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.cs b/src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.cs index e99574aa881e..1b2d4d9c94d0 100644 --- a/src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.cs +++ b/src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.cs @@ -183,19 +183,12 @@ protected override void AddPendingTask(ComponentState? componentState, Task task base.AddPendingTask(componentState, task); } - private void SignalRendererToFinishRenderingAfterCurrentBatch() + private void SignalRendererToFinishRendering() { // sets a deferred stop on the renderer, which will have an effect after the current batch is completed _rendererIsStopped = true; } - protected override void SignalRendererToFinishRendering() - { - SignalRendererToFinishRenderingAfterCurrentBatch(); - // sets a hard stop on the renderer, which will have an effect immediately - base.SignalRendererToFinishRendering(); - } - protected override void ProcessPendingRender() { if (_rendererIsStopped) From 112ba26b7fca1d874ab2a4f521eb61e903d94d87 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Wed, 23 Apr 2025 18:11:42 +0200 Subject: [PATCH 02/15] Change the test expectation - prerendering redirection behaves same as server or wasm redirection. --- .../ServerExecutionTests/PrerenderingTest.cs | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/Components/test/E2ETest/ServerExecutionTests/PrerenderingTest.cs b/src/Components/test/E2ETest/ServerExecutionTests/PrerenderingTest.cs index 9ab8248ec73f..9382aac606f4 100644 --- a/src/Components/test/E2ETest/ServerExecutionTests/PrerenderingTest.cs +++ b/src/Components/test/E2ETest/ServerExecutionTests/PrerenderingTest.cs @@ -97,22 +97,17 @@ public void CanReadUrlHashOnlyOnceConnected() () => Browser.Exists(By.TagName("strong")).Text); } - [Theory] - [InlineData("base/relative", "prerendered/base/relative")] - [InlineData("/root/relative", "/root/relative")] - [InlineData("http://absolute/url", "http://absolute/url")] - public async Task CanRedirectDuringPrerendering(string destinationParam, string expectedRedirectionLocation) + [Fact] + public async Task CannotRedirectWhenExceptionIsThrownDuringPrerendering() { var requestUri = new Uri( _serverFixture.RootUri, - "prerendered/prerendered-redirection?destination=" + destinationParam); + "prerendered/prerendered-redirection?destination=base/relative"); var httpClient = new HttpClient(new HttpClientHandler { AllowAutoRedirect = false }); var response = await httpClient.GetAsync(requestUri); - var expectedUri = new Uri(_serverFixture.RootUri, expectedRedirectionLocation); - Assert.Equal(HttpStatusCode.Redirect, response.StatusCode); - Assert.Equal(expectedUri, response.Headers.Location); + Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode); } [Theory] From 129d180b82d5775aaae1d9bc5b120ebec2fac2bc Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Thu, 24 Apr 2025 15:39:05 +0200 Subject: [PATCH 03/15] Add unit tests. --- .../Components/test/NavigationManagerTest.cs | 19 ++++++++++++ .../test/EndpointHtmlRendererTest.cs | 30 +++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/src/Components/Components/test/NavigationManagerTest.cs b/src/Components/Components/test/NavigationManagerTest.cs index 9b3763b45945..3f6e680d0c8b 100644 --- a/src/Components/Components/test/NavigationManagerTest.cs +++ b/src/Components/Components/test/NavigationManagerTest.cs @@ -1,7 +1,10 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Buffers; using System.Diagnostics; +using System.Net.Http; +using System.Text; using Microsoft.AspNetCore.Components.Routing; using Microsoft.AspNetCore.InternalTesting; @@ -868,6 +871,22 @@ async ValueTask HandleLocationChanging(LocationChangingContext context) } } + [Fact] + public void OnNotFoundSubscriptionIsTriggeredWhenNotFoundCalled() + { + // Arrange + var baseUri = "scheme://host/"; + var testNavManager = new TestNavigationManager(baseUri); + bool notFoundTriggered = false; + testNavManager.OnNotFound += (sender, args) => notFoundTriggered = true; + + // Simulate a component triggered NotFound + testNavManager.NotFound(); + + // Assert + Assert.True(notFoundTriggered, "The OnNotFound event was not triggered as expected."); + } + private class TestNavigationManager : NavigationManager { public TestNavigationManager() diff --git a/src/Components/Endpoints/test/EndpointHtmlRendererTest.cs b/src/Components/Endpoints/test/EndpointHtmlRendererTest.cs index 7bd648c783c1..bfb82e50d284 100644 --- a/src/Components/Endpoints/test/EndpointHtmlRendererTest.cs +++ b/src/Components/Endpoints/test/EndpointHtmlRendererTest.cs @@ -46,6 +46,21 @@ public EndpointHtmlRendererTest() renderer = GetEndpointHtmlRenderer(); } + [Fact] + public async Task DoesNotRenderChildAfterRendererStopped() + { + renderer.SignalRendererToFinishRendering(); + + var httpContext = GetHttpContext(); + var writer = new StringWriter(); + + var result = await renderer.PrerenderComponentAsync(httpContext, typeof(SimpleComponent), null, ParameterView.Empty); + await renderer.Dispatcher.InvokeAsync(() => result.WriteTo(writer, HtmlEncoder.Default)); + var content = writer.ToString(); + + Assert.DoesNotContain("Hello from SimpleComponent", content); + } + [Fact] public async Task CanRender_ParameterlessComponent_ClientMode() { @@ -1756,6 +1771,7 @@ private TestEndpointHtmlRenderer GetEndpointHtmlRenderer(IServiceProvider servic private class TestEndpointHtmlRenderer : EndpointHtmlRenderer { + private bool _rendererIsStopped = false; public TestEndpointHtmlRenderer(IServiceProvider serviceProvider, ILoggerFactory loggerFactory) : base(serviceProvider, loggerFactory) { } @@ -1764,6 +1780,20 @@ internal int TestAssignRootComponentId(IComponent component) { return base.AssignRootComponentId(component); } + public void SignalRendererToFinishRendering() + { + // sets a deferred stop on the renderer, which will have an effect after the current batch is completed + _rendererIsStopped = true; + } + + protected override void ProcessPendingRender() + { + if (_rendererIsStopped) + { + return; + } + base.ProcessPendingRender(); + } } private HttpContext GetHttpContext(HttpContext context = null) From 96f1e6e2085e522ba0796d4d178f5bf87ae4374d Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Thu, 24 Apr 2025 16:49:53 +0200 Subject: [PATCH 04/15] Feedback - improve trimming. --- .../Endpoints/src/DependencyInjection/HttpNavigationManager.cs | 2 ++ src/Components/Server/src/Circuits/RemoteNavigationManager.cs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/Components/Endpoints/src/DependencyInjection/HttpNavigationManager.cs b/src/Components/Endpoints/src/DependencyInjection/HttpNavigationManager.cs index becfe7feeaa9..2864d8965433 100644 --- a/src/Components/Endpoints/src/DependencyInjection/HttpNavigationManager.cs +++ b/src/Components/Endpoints/src/DependencyInjection/HttpNavigationManager.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.AspNetCore.Components.Routing; +using System.Diagnostics.CodeAnalysis; namespace Microsoft.AspNetCore.Components.Endpoints; @@ -9,6 +10,7 @@ internal sealed class HttpNavigationManager : NavigationManager, IHostEnvironmen { private const string _enableThrowNavigationException = "Microsoft.AspNetCore.Components.Endpoints.NavigationManager.EnableThrowNavigationException"; + [FeatureSwitchDefinition("Microsoft.AspNetCore.Components.Endpoints.NavigationManager.EnableThrowNavigationException")] private static bool _throwNavigationException => AppContext.TryGetSwitch(_enableThrowNavigationException, out var switchValue) && switchValue; diff --git a/src/Components/Server/src/Circuits/RemoteNavigationManager.cs b/src/Components/Server/src/Circuits/RemoteNavigationManager.cs index 0c38fcc8437b..b2d166f960a7 100644 --- a/src/Components/Server/src/Circuits/RemoteNavigationManager.cs +++ b/src/Components/Server/src/Circuits/RemoteNavigationManager.cs @@ -18,6 +18,8 @@ internal sealed partial class RemoteNavigationManager : NavigationManager, IHost private IJSRuntime _jsRuntime; private bool? _navigationLockStateBeforeJsRuntimeAttached; private const string _enableThrowNavigationException = "Microsoft.AspNetCore.Components.Endpoints.NavigationManager.EnableThrowNavigationException"; + + [FeatureSwitchDefinition("Microsoft.AspNetCore.Components.Endpoints.NavigationManager.EnableThrowNavigationException")] private static bool _throwNavigationException => AppContext.TryGetSwitch(_enableThrowNavigationException, out var switchValue) && switchValue; private Func? _onNavigateTo; From 70dd508d774b272509e45906907757b3e67d79a4 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Fri, 25 Apr 2025 16:31:40 +0200 Subject: [PATCH 05/15] Add stopping renderer tests. --- .../ServerExecutionTests/PrerenderingTest.cs | 13 ----- .../ServerRenderingTests/RenderingTest.cs | 49 +++++++++++++++++++ ...teractiveStreamingRenderingComponent.razor | 26 +--------- .../Pages/Rendering/AsyncComponent.razor | 28 +++++++++++ .../Pages/Rendering/StoppingRenderer.razor | 27 ++++++++++ .../Components.TestServer/RenderModeHelper.cs | 46 +++++++++++++++++ 6 files changed, 151 insertions(+), 38 deletions(-) create mode 100644 src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Rendering/AsyncComponent.razor create mode 100644 src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Rendering/StoppingRenderer.razor create mode 100644 src/Components/test/testassets/Components.TestServer/RenderModeHelper.cs diff --git a/src/Components/test/E2ETest/ServerExecutionTests/PrerenderingTest.cs b/src/Components/test/E2ETest/ServerExecutionTests/PrerenderingTest.cs index 9382aac606f4..3de6df8da5db 100644 --- a/src/Components/test/E2ETest/ServerExecutionTests/PrerenderingTest.cs +++ b/src/Components/test/E2ETest/ServerExecutionTests/PrerenderingTest.cs @@ -97,19 +97,6 @@ public void CanReadUrlHashOnlyOnceConnected() () => Browser.Exists(By.TagName("strong")).Text); } - [Fact] - public async Task CannotRedirectWhenExceptionIsThrownDuringPrerendering() - { - var requestUri = new Uri( - _serverFixture.RootUri, - "prerendered/prerendered-redirection?destination=base/relative"); - - var httpClient = new HttpClient(new HttpClientHandler { AllowAutoRedirect = false }); - var response = await httpClient.GetAsync(requestUri); - - Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode); - } - [Theory] [InlineData(null, null)] [InlineData(null, "Bert")] diff --git a/src/Components/test/E2ETest/ServerRenderingTests/RenderingTest.cs b/src/Components/test/E2ETest/ServerRenderingTests/RenderingTest.cs index 16a1387feb89..8232098d8a78 100644 --- a/src/Components/test/E2ETest/ServerRenderingTests/RenderingTest.cs +++ b/src/Components/test/E2ETest/ServerRenderingTests/RenderingTest.cs @@ -66,4 +66,53 @@ public void PostRequestRendersEndStateOfComponentsOnSSRPage() Browser.Equal("loaded child", () => Browser.Exists(By.Id("child")).Text); } + + [Theory] + [InlineData(false, "ServerPrerendered", true)] + [InlineData(false, "ServerPrerendered", false)] + [InlineData(true, "ServerPrerendered", false)] + [InlineData(true, "ServerNonPrerendered", false)] + [InlineData(true, "WebAssemblyPrerendered", false)] + [InlineData(true, "WebAssemblyNonPrerendered", false)] + public async Task RenderBatchQueuedAfterRedirectionIsNotProcessed(bool redirect, string renderMode, bool throwSync) + { + string relativeUri = $"subdir/stopping-renderer?renderMode={renderMode}"; + if (redirect) + { + relativeUri += $"&destination=redirect"; + } + + // async operation forces the next render batch + if (throwSync) + { + relativeUri += $"&delay=0"; + } + else + { + relativeUri += $"&delay=1"; + } + + var requestUri = new Uri(_serverFixture.RootUri, relativeUri); + var httpClient = new HttpClient(new HttpClientHandler { AllowAutoRedirect = false }); + var response = await httpClient.GetAsync(requestUri); + + if (redirect) + { + var expectedUri = new Uri(_serverFixture.RootUri, "subdir/redirect"); + Assert.Equal(HttpStatusCode.Redirect, response.StatusCode); + Assert.Equal(expectedUri, response.Headers.Location); + } + else + { + // the status code cannot be changed after it got set, so async throwing returns OK + if (throwSync) + { + Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode); + } + else + { + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + } + } + } } diff --git a/src/Components/test/testassets/Components.TestServer/RazorComponents/Components/InteractiveStreamingRenderingComponent.razor b/src/Components/test/testassets/Components.TestServer/RazorComponents/Components/InteractiveStreamingRenderingComponent.razor index de9d5114a204..605941be0f38 100644 --- a/src/Components/test/testassets/Components.TestServer/RazorComponents/Components/InteractiveStreamingRenderingComponent.razor +++ b/src/Components/test/testassets/Components.TestServer/RazorComponents/Components/InteractiveStreamingRenderingComponent.razor @@ -59,7 +59,7 @@ else object key = DisableKeys ? null : counter.Id; RenderMode.InteractiveServer, - RenderModeId.ServerNonPrerendered => new InteractiveServerRenderMode(false), - RenderModeId.WebAssemblyPrerendered => RenderMode.InteractiveWebAssembly, - RenderModeId.WebAssemblyNonPrerendered => new InteractiveWebAssemblyRenderMode(false), - RenderModeId.AutoPrerendered => RenderMode.InteractiveAuto, - RenderModeId.AutoNonPrerendered => new InteractiveAutoRenderMode(false), - _ => throw new InvalidOperationException($"Unknown render mode: {renderMode}"), - }; - } - - private enum RenderModeId - { - ServerPrerendered = 0, - ServerNonPrerendered = 1, - WebAssemblyPrerendered = 2, - WebAssemblyNonPrerendered = 3, - AutoPrerendered = 4, - AutoNonPrerendered = 5, - } - private record struct CounterInfo(int Id, int IncrementAmount, RenderModeId RenderModeId); private record ComponentState(ImmutableArray Counters, int NextCounterId) diff --git a/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Rendering/AsyncComponent.razor b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Rendering/AsyncComponent.razor new file mode 100644 index 000000000000..ead21ed17d84 --- /dev/null +++ b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Rendering/AsyncComponent.razor @@ -0,0 +1,28 @@ +@if(throwException) +{ + throw new InvalidOperationException("Child component UI exception: redirection should have stopped renderer."); +} + +@code { + [Parameter] + public int Delay { get; set; } + + private bool throwException { get; set; } + + private string message = string.Empty; + + protected override async Task OnInitializedAsync() + { + await Task.Yield(); + _ = ScheduleRenderingExceptionAfterDelay(); + } + + private async Task ScheduleRenderingExceptionAfterDelay() + { + // This update should not happen if the renderer is stopped + await Task.Delay(Delay); + throwException = true; + StateHasChanged(); + } +} + diff --git a/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Rendering/StoppingRenderer.razor b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Rendering/StoppingRenderer.razor new file mode 100644 index 000000000000..e60fc672ed6b --- /dev/null +++ b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Rendering/StoppingRenderer.razor @@ -0,0 +1,27 @@ +@page "/stopping-renderer" +@inject NavigationManager NavigationManager + +

Parent content

+ + +@code { + [Parameter, SupplyParameterFromQuery(Name = "destination")] + public string Destination { get; set; } = string.Empty; + + [Parameter, SupplyParameterFromQuery(Name = "renderMode")] + public string? RenderModeStr { get; set; } + + [Parameter, SupplyParameterFromQuery(Name = "delay")] + public int Delay { get; set; } + + private RenderModeId CurrentRenderMode => RenderModeHelper.ParseRenderMode(RenderModeStr); + + protected override Task OnInitializedAsync() + { + if (!string.IsNullOrEmpty(Destination)) + { + NavigationManager.NavigateTo(Destination); + } + return Task.CompletedTask; + } +} diff --git a/src/Components/test/testassets/Components.TestServer/RenderModeHelper.cs b/src/Components/test/testassets/Components.TestServer/RenderModeHelper.cs new file mode 100644 index 000000000000..ab1285699691 --- /dev/null +++ b/src/Components/test/testassets/Components.TestServer/RenderModeHelper.cs @@ -0,0 +1,46 @@ + +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Web; + +namespace TestServer; + +public static class RenderModeHelper +{ + public static IComponentRenderMode GetRenderMode(RenderModeId renderMode) + { + return renderMode switch + { + RenderModeId.ServerPrerendered => RenderMode.InteractiveServer, + RenderModeId.ServerNonPrerendered => new InteractiveServerRenderMode(false), + RenderModeId.WebAssemblyPrerendered => RenderMode.InteractiveWebAssembly, + RenderModeId.WebAssemblyNonPrerendered => new InteractiveWebAssemblyRenderMode(false), + RenderModeId.AutoPrerendered => RenderMode.InteractiveAuto, + RenderModeId.AutoNonPrerendered => new InteractiveAutoRenderMode(false), + _ => throw new InvalidOperationException($"Unknown render mode: {renderMode}"), + }; + } + + public static RenderModeId ParseRenderMode(string? renderModeStr) + { + if (!string.IsNullOrEmpty(renderModeStr) && + Enum.TryParse(renderModeStr, ignoreCase: true, out var result)) + { + return result; + } + return RenderModeId.AutoNonPrerendered; + } + +} + +public enum RenderModeId +{ + ServerPrerendered = 0, + ServerNonPrerendered = 1, + WebAssemblyPrerendered = 2, + WebAssemblyNonPrerendered = 3, + AutoPrerendered = 4, + AutoNonPrerendered = 5, +} From d33515b424bc50c66f92cf8b75737b42ab9c3197 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Mon, 28 Apr 2025 12:53:50 +0200 Subject: [PATCH 06/15] Do not throw on new way of navigation. --- .../Account/IdentityRedirectManager.cs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/IdentityRedirectManager.cs b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/IdentityRedirectManager.cs index da85e6efd46d..4306b43f7e28 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/IdentityRedirectManager.cs +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/IdentityRedirectManager.cs @@ -7,6 +7,10 @@ internal sealed class IdentityRedirectManager(NavigationManager navigationManage { public const string StatusCookieName = "Identity.StatusMessage"; + [FeatureSwitchDefinition("Microsoft.AspNetCore.Components.Endpoints.NavigationManager.EnableThrowNavigationException")] + private static bool _throwNavigationException => + AppContext.TryGetSwitch(_enableThrowNavigationException, out var switchValue) && switchValue; + private static readonly CookieBuilder StatusCookieBuilder = new() { SameSite = SameSiteMode.Strict, @@ -15,7 +19,6 @@ internal sealed class IdentityRedirectManager(NavigationManager navigationManage MaxAge = TimeSpan.FromSeconds(5), }; - [DoesNotReturn] public void RedirectTo(string? uri) { uri ??= ""; @@ -26,13 +29,15 @@ public void RedirectTo(string? uri) uri = navigationManager.ToBaseRelativePath(uri); } - // During static rendering, NavigateTo throws a NavigationException which is handled by the framework as a redirect. - // So as long as this is called from a statically rendered Identity component, the InvalidOperationException is never thrown. navigationManager.NavigateTo(uri); - throw new InvalidOperationException($"{nameof(IdentityRedirectManager)} can only be used during static rendering."); + if (_throwNavigationException) + { + // During static rendering, NavigateTo throws a NavigationException which is handled by the framework as a redirect. + // So as long as this is called from a statically rendered Identity component, the InvalidOperationException is never thrown. + throw new InvalidOperationException($"{nameof(IdentityRedirectManager)} can only be used during static rendering."); + } } - [DoesNotReturn] public void RedirectTo(string uri, Dictionary queryParameters) { var uriWithoutQuery = navigationManager.ToAbsoluteUri(uri).GetLeftPart(UriPartial.Path); @@ -40,7 +45,6 @@ public void RedirectTo(string uri, Dictionary queryParameters) RedirectTo(newUri); } - [DoesNotReturn] public void RedirectToWithStatus(string uri, string message, HttpContext context) { context.Response.Cookies.Append(StatusCookieName, message, StatusCookieBuilder.Build(context)); @@ -49,10 +53,8 @@ public void RedirectToWithStatus(string uri, string message, HttpContext context private string CurrentPath => navigationManager.ToAbsoluteUri(navigationManager.Uri).GetLeftPart(UriPartial.Path); - [DoesNotReturn] public void RedirectToCurrentPage() => RedirectTo(CurrentPath); - [DoesNotReturn] public void RedirectToCurrentPageWithStatus(string message, HttpContext context) => RedirectToWithStatus(CurrentPath, message, context); } From ae715c96dd4c94fd9debf191082a4f004530a736 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz <32700855+ilonatommy@users.noreply.github.com> Date: Mon, 28 Apr 2025 16:15:24 +0200 Subject: [PATCH 07/15] Missing change. --- .../Components/Account/IdentityRedirectManager.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/IdentityRedirectManager.cs b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/IdentityRedirectManager.cs index 4306b43f7e28..692f51a57386 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/IdentityRedirectManager.cs +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/IdentityRedirectManager.cs @@ -7,6 +7,8 @@ internal sealed class IdentityRedirectManager(NavigationManager navigationManage { public const string StatusCookieName = "Identity.StatusMessage"; + private const string _enableThrowNavigationException = "Microsoft.AspNetCore.Components.Endpoints.NavigationManager.EnableThrowNavigationException"; + [FeatureSwitchDefinition("Microsoft.AspNetCore.Components.Endpoints.NavigationManager.EnableThrowNavigationException")] private static bool _throwNavigationException => AppContext.TryGetSwitch(_enableThrowNavigationException, out var switchValue) && switchValue; From 0a6088d838efed49212f29faadae2f26a9b7c1ca Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Tue, 29 Apr 2025 12:24:28 +0200 Subject: [PATCH 08/15] Fix: possible null reference errors and consequences of user being nullable. --- .../Components/Account/IdentityUserAccessor.cs | 2 +- .../Account/Pages/ConfirmEmail.razor | 1 + .../Account/Pages/ConfirmEmailChange.razor | 1 + .../Account/Pages/ExternalLogin.razor | 3 +++ .../Account/Pages/ForgotPassword.razor | 1 + .../Account/Pages/Manage/ChangePassword.razor | 12 +++++++++++- .../Pages/Manage/DeletePersonalData.razor | 12 ++++++++++-- .../Account/Pages/Manage/Disable2fa.razor | 9 +++++++-- .../Account/Pages/Manage/Email.razor | 17 ++++++++++++++++- .../Pages/Manage/EnableAuthenticator.razor | 13 ++++++++++--- .../Account/Pages/Manage/ExternalLogins.razor | 18 +++++++++++++++++- .../Pages/Manage/GenerateRecoveryCodes.razor | 11 ++++++++++- .../Account/Pages/Manage/Index.razor | 12 +++++++++++- .../Pages/Manage/ResetAuthenticator.razor | 5 +++++ .../Account/Pages/Manage/SetPassword.razor | 11 ++++++++++- .../Pages/Manage/TwoFactorAuthentication.razor | 5 +++++ .../Account/Pages/RegisterConfirmation.razor | 1 + .../Account/Pages/ResetPassword.razor | 2 ++ 18 files changed, 122 insertions(+), 14 deletions(-) diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/IdentityUserAccessor.cs b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/IdentityUserAccessor.cs index 86e027c0b6ee..077ef1a57fd0 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/IdentityUserAccessor.cs +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/IdentityUserAccessor.cs @@ -5,7 +5,7 @@ namespace BlazorWeb_CSharp.Components.Account; internal sealed class IdentityUserAccessor(UserManager userManager, IdentityRedirectManager redirectManager) { - public async Task GetRequiredUserAsync(HttpContext context) + public async Task GetRequiredUserAsync(HttpContext context) { var user = await userManager.GetUserAsync(context.User); diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/ConfirmEmail.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/ConfirmEmail.razor index 830ee204c824..4254c0e65bae 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/ConfirmEmail.razor +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/ConfirmEmail.razor @@ -30,6 +30,7 @@ if (UserId is null || Code is null) { RedirectManager.RedirectTo(""); + return; } var user = await UserManager.FindByIdAsync(UserId); diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/ConfirmEmailChange.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/ConfirmEmailChange.razor index 478a810defc9..a1547b6fa41c 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/ConfirmEmailChange.razor +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/ConfirmEmailChange.razor @@ -36,6 +36,7 @@ { RedirectManager.RedirectToWithStatus( "Account/Login", "Error: Invalid email change confirmation link.", HttpContext); + return; } var user = await UserManager.FindByIdAsync(UserId); diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/ExternalLogin.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/ExternalLogin.razor index 0e5200ecb4e5..611b3448d7f6 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/ExternalLogin.razor +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/ExternalLogin.razor @@ -101,6 +101,7 @@ if (externalLoginInfo is null) { RedirectManager.RedirectToWithStatus("Account/Login", "Error loading external login information.", HttpContext); + return; } // Sign in the user with this external login provider if the user already has a login. @@ -121,6 +122,7 @@ else if (result.IsLockedOut) { RedirectManager.RedirectTo("Account/Lockout"); + return; } // If the user does not have an account, then ask the user to create an account. @@ -135,6 +137,7 @@ if (externalLoginInfo is null) { RedirectManager.RedirectToWithStatus("Account/Login", "Error loading external login information during confirmation.", HttpContext); + return; } var emailStore = GetEmailStore(); diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/ForgotPassword.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/ForgotPassword.razor index efe20b0f5639..1ab1bfa5fcb0 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/ForgotPassword.razor +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/ForgotPassword.razor @@ -44,6 +44,7 @@ { // Don't reveal that the user does not exist or is not confirmed RedirectManager.RedirectTo("Account/ForgotPasswordConfirmation"); + return; } // For more information on how to enable account confirmation and password reset please diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/ChangePassword.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/ChangePassword.razor index dc21755a47b0..6278bd9c5e31 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/ChangePassword.razor +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/ChangePassword.razor @@ -41,7 +41,7 @@ @code { private string? message; - private ApplicationUser user = default!; + private ApplicationUser? user; private bool hasPassword; [CascadingParameter] @@ -53,6 +53,11 @@ protected override async Task OnInitializedAsync() { user = await UserAccessor.GetRequiredUserAsync(HttpContext); + if (user is null) + { + return; + } + hasPassword = await UserManager.HasPasswordAsync(user); if (!hasPassword) { @@ -62,6 +67,11 @@ private async Task OnValidSubmitAsync() { + if (user is null) + { + throw new InvalidOperationException("User is not loaded; cannot change password."); + } + var changePasswordResult = await UserManager.ChangePasswordAsync(user, Input.OldPassword, Input.NewPassword); if (!changePasswordResult.Succeeded) { diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/DeletePersonalData.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/DeletePersonalData.razor index d7bfb84d491b..b766b66a5c0e 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/DeletePersonalData.razor +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/DeletePersonalData.razor @@ -40,7 +40,7 @@ @code { private string? message; - private ApplicationUser user = default!; + private ApplicationUser? user; private bool requirePassword; [CascadingParameter] @@ -53,11 +53,19 @@ { Input ??= new(); user = await UserAccessor.GetRequiredUserAsync(HttpContext); - requirePassword = await UserManager.HasPasswordAsync(user); + if (user is not null) + { + requirePassword = await UserManager.HasPasswordAsync(user); + } } private async Task OnValidSubmitAsync() { + if (user is null) + { + throw new InvalidOperationException("User is not loaded; failed to delete personal data."); + } + if (requirePassword && !await UserManager.CheckPasswordAsync(user, Input.Password)) { message = "Error: Incorrect password."; diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/Disable2fa.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/Disable2fa.razor index d3969f457c9c..c64ab96d629c 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/Disable2fa.razor +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/Disable2fa.razor @@ -31,7 +31,7 @@ @code { - private ApplicationUser user = default!; + private ApplicationUser? user; [CascadingParameter] private HttpContext HttpContext { get; set; } = default!; @@ -40,7 +40,7 @@ { user = await UserAccessor.GetRequiredUserAsync(HttpContext); - if (HttpMethods.IsGet(HttpContext.Request.Method) && !await UserManager.GetTwoFactorEnabledAsync(user)) + if (user is not null && HttpMethods.IsGet(HttpContext.Request.Method) && !await UserManager.GetTwoFactorEnabledAsync(user)) { throw new InvalidOperationException("Cannot disable 2FA for user as it's not currently enabled."); } @@ -48,6 +48,11 @@ private async Task OnSubmitAsync() { + if (user is null) + { + throw new InvalidOperationException("User is not loaded; failed to disable 2FA."); + } + var disable2faResult = await UserManager.SetTwoFactorEnabledAsync(user, false); if (!disable2faResult.Succeeded) { diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/Email.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/Email.razor index c8b1518061ae..08eb4589b86a 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/Email.razor +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/Email.razor @@ -55,7 +55,7 @@ @code { private string? message; - private ApplicationUser user = default!; + private ApplicationUser? user; private string? email; private bool isEmailConfirmed; @@ -68,6 +68,11 @@ protected override async Task OnInitializedAsync() { user = await UserAccessor.GetRequiredUserAsync(HttpContext); + if (user is null) + { + return; + } + email = await UserManager.GetEmailAsync(user); isEmailConfirmed = await UserManager.IsEmailConfirmedAsync(user); @@ -82,6 +87,11 @@ return; } + if (user is null) + { + throw new InvalidOperationException("User is not loaded; failed to manage email."); + } + var userId = await UserManager.GetUserIdAsync(user); var code = await UserManager.GenerateChangeEmailTokenAsync(user, Input.NewEmail); code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code)); @@ -101,6 +111,11 @@ return; } + if (user is null) + { + throw new InvalidOperationException("User is not loaded; failed to send verification email."); + } + var userId = await UserManager.GetUserIdAsync(user); var code = await UserManager.GenerateEmailConfirmationTokenAsync(user); code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code)); diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/EnableAuthenticator.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/EnableAuthenticator.razor index 1872e9e6c83b..d2e7cdd24813 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/EnableAuthenticator.razor +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/EnableAuthenticator.razor @@ -70,7 +70,7 @@ else private const string AuthenticatorUriFormat = "otpauth://totp/{0}:{1}?secret={2}&issuer={0}&digits=6"; private string? message; - private ApplicationUser user = default!; + private ApplicationUser? user; private string? sharedKey; private string? authenticatorUri; private IEnumerable? recoveryCodes; @@ -84,12 +84,19 @@ else protected override async Task OnInitializedAsync() { user = await UserAccessor.GetRequiredUserAsync(HttpContext); - - await LoadSharedKeyAndQrCodeUriAsync(user); + if (user is not null) + { + await LoadSharedKeyAndQrCodeUriAsync(user); + } } private async Task OnValidSubmitAsync() { + if (user is null) + { + throw new InvalidOperationException("User is not loaded; failed to configure authenticator app."); + } + // Strip spaces and hyphens var verificationCode = Input.Code.Replace(" ", string.Empty).Replace("-", string.Empty); diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/ExternalLogins.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/ExternalLogins.razor index 25a6b27926a0..ca95de6402dc 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/ExternalLogins.razor +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/ExternalLogins.razor @@ -66,7 +66,7 @@ @code { public const string LinkLoginCallbackAction = "LinkLoginCallback"; - private ApplicationUser user = default!; + private ApplicationUser? user; private IList? currentLogins; private IList? otherLogins; private bool showRemoveButton; @@ -86,6 +86,11 @@ protected override async Task OnInitializedAsync() { user = await UserAccessor.GetRequiredUserAsync(HttpContext); + if (user is null) + { + return; + } + currentLogins = await UserManager.GetLoginsAsync(user); otherLogins = (await SignInManager.GetExternalAuthenticationSchemesAsync()) .Where(auth => currentLogins.All(ul => auth.Name != ul.LoginProvider)) @@ -107,6 +112,11 @@ private async Task OnSubmitAsync() { + if (user is null) + { + throw new InvalidOperationException("User is not loaded; failed to remove external login."); + } + var result = await UserManager.RemoveLoginAsync(user, LoginProvider!, ProviderKey!); if (!result.Succeeded) { @@ -119,11 +129,17 @@ private async Task OnGetLinkLoginCallbackAsync() { + if (user is null) + { + throw new InvalidOperationException("User is not loaded; failed to add external login."); + } + var userId = await UserManager.GetUserIdAsync(user); var info = await SignInManager.GetExternalLoginInfoAsync(userId); if (info is null) { RedirectManager.RedirectToCurrentPageWithStatus("Error: Could not load external login info.", HttpContext); + return; } var result = await UserManager.AddLoginAsync(user, info); diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/GenerateRecoveryCodes.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/GenerateRecoveryCodes.razor index c63c765ab887..b03a2905200b 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/GenerateRecoveryCodes.razor +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/GenerateRecoveryCodes.razor @@ -40,7 +40,7 @@ else @code { private string? message; - private ApplicationUser user = default!; + private ApplicationUser? user; private IEnumerable? recoveryCodes; [CascadingParameter] @@ -49,6 +49,10 @@ else protected override async Task OnInitializedAsync() { user = await UserAccessor.GetRequiredUserAsync(HttpContext); + if (user is null) + { + return; + } var isTwoFactorEnabled = await UserManager.GetTwoFactorEnabledAsync(user); if (!isTwoFactorEnabled) @@ -59,6 +63,11 @@ else private async Task OnSubmitAsync() { + if (user is null) + { + throw new InvalidOperationException("User is not loaded; failed to generate recovery codes."); + } + var userId = await UserManager.GetUserIdAsync(user); recoveryCodes = await UserManager.GenerateNewTwoFactorRecoveryCodesAsync(user, 10); message = "You have generated new recovery codes."; diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/Index.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/Index.razor index bff831a1d852..a5c47d666791 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/Index.razor +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/Index.razor @@ -34,7 +34,7 @@ @code { - private ApplicationUser user = default!; + private ApplicationUser? user; private string? username; private string? phoneNumber; @@ -47,6 +47,11 @@ protected override async Task OnInitializedAsync() { user = await UserAccessor.GetRequiredUserAsync(HttpContext); + if (user is null) + { + return; + } + username = await UserManager.GetUserNameAsync(user); phoneNumber = await UserManager.GetPhoneNumberAsync(user); @@ -55,6 +60,11 @@ private async Task OnValidSubmitAsync() { + if (user is null) + { + throw new InvalidOperationException("User is not loaded; failed to set phone number."); + } + if (Input.PhoneNumber != phoneNumber) { var setPhoneResult = await UserManager.SetPhoneNumberAsync(user, Input.PhoneNumber); diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/ResetAuthenticator.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/ResetAuthenticator.razor index c12e38094f76..1ae1e884a9a5 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/ResetAuthenticator.razor +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/ResetAuthenticator.razor @@ -37,6 +37,11 @@ private async Task OnSubmitAsync() { var user = await UserAccessor.GetRequiredUserAsync(HttpContext); + if (user is null) + { + return; + } + await UserManager.SetTwoFactorEnabledAsync(user, false); await UserManager.ResetAuthenticatorKeyAsync(user); var userId = await UserManager.GetUserIdAsync(user); diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/SetPassword.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/SetPassword.razor index 79eabe780fff..e56f1980af56 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/SetPassword.razor +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/SetPassword.razor @@ -39,7 +39,7 @@ @code { private string? message; - private ApplicationUser user = default!; + private ApplicationUser? user; [CascadingParameter] private HttpContext HttpContext { get; set; } = default!; @@ -50,6 +50,10 @@ protected override async Task OnInitializedAsync() { user = await UserAccessor.GetRequiredUserAsync(HttpContext); + if (user is null) + { + return; + } var hasPassword = await UserManager.HasPasswordAsync(user); if (hasPassword) @@ -60,6 +64,11 @@ private async Task OnValidSubmitAsync() { + if (user is null) + { + throw new InvalidOperationException("User is not loaded; failed to set password."); + } + var addPasswordResult = await UserManager.AddPasswordAsync(user, Input.NewPassword!); if (!addPasswordResult.Succeeded) { diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/TwoFactorAuthentication.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/TwoFactorAuthentication.razor index d15097a9ed16..dab6b95429ec 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/TwoFactorAuthentication.razor +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/TwoFactorAuthentication.razor @@ -83,6 +83,11 @@ else protected override async Task OnInitializedAsync() { var user = await UserAccessor.GetRequiredUserAsync(HttpContext); + if (user is null) + { + return; + } + canTrack = HttpContext.Features.Get()?.CanTrack ?? true; hasAuthenticator = await UserManager.GetAuthenticatorKeyAsync(user) is not null; is2faEnabled = await UserManager.GetTwoFactorEnabledAsync(user); diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/RegisterConfirmation.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/RegisterConfirmation.razor index ec74ea584ef8..52ee9594920b 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/RegisterConfirmation.razor +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/RegisterConfirmation.razor @@ -46,6 +46,7 @@ else if (Email is null) { RedirectManager.RedirectTo(""); + return; } var user = await UserManager.FindByEmailAsync(Email); diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/ResetPassword.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/ResetPassword.razor index 61e07c0b8de0..4c88c56665ae 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/ResetPassword.razor +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/ResetPassword.razor @@ -58,6 +58,7 @@ if (Code is null) { RedirectManager.RedirectTo("Account/InvalidPasswordReset"); + return; } Input.Code = Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(Code)); @@ -70,6 +71,7 @@ { // Don't reveal that the user does not exist RedirectManager.RedirectTo("Account/ResetPasswordConfirmation"); + return; } var result = await UserManager.ResetPasswordAsync(user, Input.Code, Input.Password); From 085a3bd75477afe6105b5710aa10da5a56ae03ed Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Tue, 6 May 2025 14:24:32 +0200 Subject: [PATCH 09/15] Apply feedback from the backporting PR. --- .../Components/Account/IdentityRedirectManager.cs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/IdentityRedirectManager.cs b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/IdentityRedirectManager.cs index 692f51a57386..a7de94ca41e2 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/IdentityRedirectManager.cs +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/IdentityRedirectManager.cs @@ -7,12 +7,6 @@ internal sealed class IdentityRedirectManager(NavigationManager navigationManage { public const string StatusCookieName = "Identity.StatusMessage"; - private const string _enableThrowNavigationException = "Microsoft.AspNetCore.Components.Endpoints.NavigationManager.EnableThrowNavigationException"; - - [FeatureSwitchDefinition("Microsoft.AspNetCore.Components.Endpoints.NavigationManager.EnableThrowNavigationException")] - private static bool _throwNavigationException => - AppContext.TryGetSwitch(_enableThrowNavigationException, out var switchValue) && switchValue; - private static readonly CookieBuilder StatusCookieBuilder = new() { SameSite = SameSiteMode.Strict, @@ -32,12 +26,6 @@ public void RedirectTo(string? uri) } navigationManager.NavigateTo(uri); - if (_throwNavigationException) - { - // During static rendering, NavigateTo throws a NavigationException which is handled by the framework as a redirect. - // So as long as this is called from a statically rendered Identity component, the InvalidOperationException is never thrown. - throw new InvalidOperationException($"{nameof(IdentityRedirectManager)} can only be used during static rendering."); - } } public void RedirectTo(string uri, Dictionary queryParameters) From 80925acc8a2ff222aef6915c3981cd2252f8ae52 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Wed, 14 May 2025 15:54:53 +0200 Subject: [PATCH 10/15] Feedback. --- .../HttpNavigationManager.cs | 2 +- .../Account/IdentityRedirectManager.cs | 3 +++ .../Account/IdentityUserAccessor.cs | 19 ------------------- .../Account/Pages/Manage/ChangePassword.razor | 5 +++-- .../Pages/Manage/DeletePersonalData.razor | 12 +++++++----- .../Account/Pages/Manage/Disable2fa.razor | 10 +++++++--- .../Account/Pages/Manage/Email.razor | 10 ++++++---- .../Pages/Manage/EnableAuthenticator.razor | 11 ++++++----- .../Account/Pages/Manage/ExternalLogins.razor | 10 ++++++---- .../Pages/Manage/GenerateRecoveryCodes.razor | 7 ++++--- .../Account/Pages/Manage/Index.razor | 7 ++++--- .../Account/Pages/Manage/PersonalData.razor | 9 ++++++--- .../Pages/Manage/ResetAuthenticator.razor | 4 ++-- .../Account/Pages/Manage/SetPassword.razor | 7 ++++--- .../Manage/TwoFactorAuthentication.razor | 4 ++-- .../BlazorWeb-CSharp/Program.Main.cs | 1 - .../BlazorWeb-CSharp/Program.cs | 1 - 17 files changed, 61 insertions(+), 61 deletions(-) delete mode 100644 src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/IdentityUserAccessor.cs diff --git a/src/Components/Endpoints/src/DependencyInjection/HttpNavigationManager.cs b/src/Components/Endpoints/src/DependencyInjection/HttpNavigationManager.cs index 2864d8965433..4585368b830a 100644 --- a/src/Components/Endpoints/src/DependencyInjection/HttpNavigationManager.cs +++ b/src/Components/Endpoints/src/DependencyInjection/HttpNavigationManager.cs @@ -10,7 +10,7 @@ internal sealed class HttpNavigationManager : NavigationManager, IHostEnvironmen { private const string _enableThrowNavigationException = "Microsoft.AspNetCore.Components.Endpoints.NavigationManager.EnableThrowNavigationException"; - [FeatureSwitchDefinition("Microsoft.AspNetCore.Components.Endpoints.NavigationManager.EnableThrowNavigationException")] + [FeatureSwitchDefinition(_enableThrowNavigationException)] private static bool _throwNavigationException => AppContext.TryGetSwitch(_enableThrowNavigationException, out var switchValue) && switchValue; diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/IdentityRedirectManager.cs b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/IdentityRedirectManager.cs index a7de94ca41e2..15cfd7b0405f 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/IdentityRedirectManager.cs +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/IdentityRedirectManager.cs @@ -47,4 +47,7 @@ public void RedirectToWithStatus(string uri, string message, HttpContext context public void RedirectToCurrentPageWithStatus(string message, HttpContext context) => RedirectToWithStatus(CurrentPath, message, context); + + public void RedirectToInvalidUser(UserManager userManager, HttpContext context) + => redirectManager.RedirectToWithStatus("Account/InvalidUser", $"Error: Unable to load user with ID '{userManager.GetUserId(context.User)}'.", context); } diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/IdentityUserAccessor.cs b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/IdentityUserAccessor.cs deleted file mode 100644 index 077ef1a57fd0..000000000000 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/IdentityUserAccessor.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Microsoft.AspNetCore.Identity; -using BlazorWeb_CSharp.Data; - -namespace BlazorWeb_CSharp.Components.Account; - -internal sealed class IdentityUserAccessor(UserManager userManager, IdentityRedirectManager redirectManager) -{ - public async Task GetRequiredUserAsync(HttpContext context) - { - var user = await userManager.GetUserAsync(context.User); - - if (user is null) - { - redirectManager.RedirectToWithStatus("Account/InvalidUser", $"Error: Unable to load user with ID '{userManager.GetUserId(context.User)}'.", context); - } - - return user; - } -} diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/ChangePassword.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/ChangePassword.razor index 6278bd9c5e31..d4c6236a115a 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/ChangePassword.razor +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/ChangePassword.razor @@ -6,7 +6,7 @@ @inject UserManager UserManager @inject SignInManager SignInManager -@inject IdentityUserAccessor UserAccessor +@inject RedirectManager RedirectManager @inject IdentityRedirectManager RedirectManager @inject ILogger Logger @@ -52,9 +52,10 @@ protected override async Task OnInitializedAsync() { - user = await UserAccessor.GetRequiredUserAsync(HttpContext); + user = await UserManager.GetUserAsync(HttpContext.User); if (user is null) { + RedirectManager.RedirectToInvalidUser(UserManager, HttpContext); return; } diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/DeletePersonalData.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/DeletePersonalData.razor index b766b66a5c0e..12dcd63bd78a 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/DeletePersonalData.razor +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/DeletePersonalData.razor @@ -6,7 +6,6 @@ @inject UserManager UserManager @inject SignInManager SignInManager -@inject IdentityUserAccessor UserAccessor @inject IdentityRedirectManager RedirectManager @inject ILogger Logger @@ -52,18 +51,21 @@ protected override async Task OnInitializedAsync() { Input ??= new(); - user = await UserAccessor.GetRequiredUserAsync(HttpContext); - if (user is not null) + user = await UserManager.GetUserAsync(HttpContext.User); + if (user is null) { - requirePassword = await UserManager.HasPasswordAsync(user); + RedirectManager.RedirectToInvalidUser(UserManager, HttpContext); + return; } + requirePassword = await UserManager.HasPasswordAsync(user); } private async Task OnValidSubmitAsync() { if (user is null) { - throw new InvalidOperationException("User is not loaded; failed to delete personal data."); + RedirectManager.RedirectToInvalidUser(UserManager, HttpContext); + return; } if (requirePassword && !await UserManager.CheckPasswordAsync(user, Input.Password)) diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/Disable2fa.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/Disable2fa.razor index c64ab96d629c..edf406428ffd 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/Disable2fa.razor +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/Disable2fa.razor @@ -4,7 +4,6 @@ @using BlazorWeb_CSharp.Data @inject UserManager UserManager -@inject IdentityUserAccessor UserAccessor @inject IdentityRedirectManager RedirectManager @inject ILogger Logger @@ -38,9 +37,14 @@ protected override async Task OnInitializedAsync() { - user = await UserAccessor.GetRequiredUserAsync(HttpContext); + user = await UserManager.GetUserAsync(HttpContext.User); + if (user is null) + { + RedirectManager.RedirectToInvalidUser(UserManager, HttpContext); + return; + } - if (user is not null && HttpMethods.IsGet(HttpContext.Request.Method) && !await UserManager.GetTwoFactorEnabledAsync(user)) + if (HttpMethods.IsGet(HttpContext.Request.Method) && !await UserManager.GetTwoFactorEnabledAsync(user)) { throw new InvalidOperationException("Cannot disable 2FA for user as it's not currently enabled."); } diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/Email.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/Email.razor index 08eb4589b86a..54ca704882ed 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/Email.razor +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/Email.razor @@ -9,7 +9,6 @@ @inject UserManager UserManager @inject IEmailSender EmailSender -@inject IdentityUserAccessor UserAccessor @inject NavigationManager NavigationManager Manage email @@ -67,9 +66,10 @@ protected override async Task OnInitializedAsync() { - user = await UserAccessor.GetRequiredUserAsync(HttpContext); + user = await UserManager.GetUserAsync(HttpContext.User); if (user is null) { + RedirectManager.RedirectToInvalidUser(UserManager, HttpContext); return; } @@ -89,7 +89,8 @@ if (user is null) { - throw new InvalidOperationException("User is not loaded; failed to manage email."); + RedirectManager.RedirectToInvalidUser(UserManager, HttpContext); + return; } var userId = await UserManager.GetUserIdAsync(user); @@ -113,7 +114,8 @@ if (user is null) { - throw new InvalidOperationException("User is not loaded; failed to send verification email."); + RedirectManager.RedirectToInvalidUser(UserManager, HttpContext); + return; } var userId = await UserManager.GetUserIdAsync(user); diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/EnableAuthenticator.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/EnableAuthenticator.razor index d2e7cdd24813..be8c1429e18b 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/EnableAuthenticator.razor +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/EnableAuthenticator.razor @@ -8,7 +8,6 @@ @using BlazorWeb_CSharp.Data @inject UserManager UserManager -@inject IdentityUserAccessor UserAccessor @inject UrlEncoder UrlEncoder @inject IdentityRedirectManager RedirectManager @inject ILogger Logger @@ -83,10 +82,11 @@ else protected override async Task OnInitializedAsync() { - user = await UserAccessor.GetRequiredUserAsync(HttpContext); - if (user is not null) + user = await UserManager.GetUserAsync(HttpContext.User); + if (user is null) { - await LoadSharedKeyAndQrCodeUriAsync(user); + RedirectManager.RedirectToInvalidUser(UserManager, HttpContext); + return; } } @@ -94,7 +94,8 @@ else { if (user is null) { - throw new InvalidOperationException("User is not loaded; failed to configure authenticator app."); + RedirectManager.RedirectToInvalidUser(UserManager, HttpContext); + return; } // Strip spaces and hyphens diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/ExternalLogins.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/ExternalLogins.razor index ca95de6402dc..00794aa25f93 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/ExternalLogins.razor +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/ExternalLogins.razor @@ -6,7 +6,6 @@ @inject UserManager UserManager @inject SignInManager SignInManager -@inject IdentityUserAccessor UserAccessor @inject IUserStore UserStore @inject IdentityRedirectManager RedirectManager @@ -85,9 +84,10 @@ protected override async Task OnInitializedAsync() { - user = await UserAccessor.GetRequiredUserAsync(HttpContext); + user = await UserManager.GetUserAsync(HttpContext.User); if (user is null) { + RedirectManager.RedirectToInvalidUser(UserManager, HttpContext); return; } @@ -114,7 +114,8 @@ { if (user is null) { - throw new InvalidOperationException("User is not loaded; failed to remove external login."); + RedirectManager.RedirectToInvalidUser(UserManager, HttpContext); + return; } var result = await UserManager.RemoveLoginAsync(user, LoginProvider!, ProviderKey!); @@ -131,7 +132,8 @@ { if (user is null) { - throw new InvalidOperationException("User is not loaded; failed to add external login."); + RedirectManager.RedirectToInvalidUser(UserManager, HttpContext); + return; } var userId = await UserManager.GetUserIdAsync(user); diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/GenerateRecoveryCodes.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/GenerateRecoveryCodes.razor index b03a2905200b..3e62899c0a14 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/GenerateRecoveryCodes.razor +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/GenerateRecoveryCodes.razor @@ -4,7 +4,6 @@ @using BlazorWeb_CSharp.Data @inject UserManager UserManager -@inject IdentityUserAccessor UserAccessor @inject IdentityRedirectManager RedirectManager @inject ILogger Logger @@ -48,9 +47,10 @@ else protected override async Task OnInitializedAsync() { - user = await UserAccessor.GetRequiredUserAsync(HttpContext); + user = await UserManager.GetUserAsync(HttpContext.User); if (user is null) { + RedirectManager.RedirectToInvalidUser(UserManager, HttpContext); return; } @@ -65,7 +65,8 @@ else { if (user is null) { - throw new InvalidOperationException("User is not loaded; failed to generate recovery codes."); + RedirectManager.RedirectToInvalidUser(UserManager, HttpContext); + return; } var userId = await UserManager.GetUserIdAsync(user); diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/Index.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/Index.razor index a5c47d666791..b5ca1361af4e 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/Index.razor +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/Index.razor @@ -6,7 +6,6 @@ @inject UserManager UserManager @inject SignInManager SignInManager -@inject IdentityUserAccessor UserAccessor @inject IdentityRedirectManager RedirectManager Profile @@ -46,9 +45,10 @@ protected override async Task OnInitializedAsync() { - user = await UserAccessor.GetRequiredUserAsync(HttpContext); + user = await UserManager.GetUserAsync(HttpContext.User); if (user is null) { + RedirectManager.RedirectToInvalidUser(UserManager, HttpContext); return; } @@ -62,7 +62,8 @@ { if (user is null) { - throw new InvalidOperationException("User is not loaded; failed to set phone number."); + RedirectManager.RedirectToInvalidUser(UserManager, HttpContext); + return; } if (Input.PhoneNumber != phoneNumber) diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/PersonalData.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/PersonalData.razor index 851eb54c8820..d56b785bb21f 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/PersonalData.razor +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/PersonalData.razor @@ -1,7 +1,5 @@ @page "/Account/Manage/PersonalData" -@inject IdentityUserAccessor UserAccessor - Personal Data @@ -29,6 +27,11 @@ protected override async Task OnInitializedAsync() { - _ = await UserAccessor.GetRequiredUserAsync(HttpContext); + var user = await UserManager.GetUserAsync(HttpContext.User); + if (user is null) + { + RedirectManager.RedirectToInvalidUser(UserManager, HttpContext); + return; + } } } diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/ResetAuthenticator.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/ResetAuthenticator.razor index 1ae1e884a9a5..c087a5eb6797 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/ResetAuthenticator.razor +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/ResetAuthenticator.razor @@ -5,7 +5,6 @@ @inject UserManager UserManager @inject SignInManager SignInManager -@inject IdentityUserAccessor UserAccessor @inject IdentityRedirectManager RedirectManager @inject ILogger Logger @@ -36,9 +35,10 @@ private async Task OnSubmitAsync() { - var user = await UserAccessor.GetRequiredUserAsync(HttpContext); + var user = await UserManager.GetUserAsync(HttpContext.User); if (user is null) { + RedirectManager.RedirectToInvalidUser(UserManager, HttpContext); return; } diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/SetPassword.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/SetPassword.razor index e56f1980af56..c9c7b26317cf 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/SetPassword.razor +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/SetPassword.razor @@ -6,7 +6,6 @@ @inject UserManager UserManager @inject SignInManager SignInManager -@inject IdentityUserAccessor UserAccessor @inject IdentityRedirectManager RedirectManager Set password @@ -49,9 +48,10 @@ protected override async Task OnInitializedAsync() { - user = await UserAccessor.GetRequiredUserAsync(HttpContext); + user = await UserManager.GetUserAsync(HttpContext.User); if (user is null) { + RedirectManager.RedirectToInvalidUser(UserManager, HttpContext); return; } @@ -66,7 +66,8 @@ { if (user is null) { - throw new InvalidOperationException("User is not loaded; failed to set password."); + RedirectManager.RedirectToInvalidUser(UserManager, HttpContext); + return; } var addPasswordResult = await UserManager.AddPasswordAsync(user, Input.NewPassword!); diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/TwoFactorAuthentication.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/TwoFactorAuthentication.razor index dab6b95429ec..08fae2319374 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/TwoFactorAuthentication.razor +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/TwoFactorAuthentication.razor @@ -6,7 +6,6 @@ @inject UserManager UserManager @inject SignInManager SignInManager -@inject IdentityUserAccessor UserAccessor @inject IdentityRedirectManager RedirectManager Two-factor authentication (2FA) @@ -82,9 +81,10 @@ else protected override async Task OnInitializedAsync() { - var user = await UserAccessor.GetRequiredUserAsync(HttpContext); + var user = await UserManager.GetUserAsync(HttpContext.User); if (user is null) { + RedirectManager.RedirectToInvalidUser(UserManager, HttpContext); return; } diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Program.Main.cs b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Program.Main.cs index 6a2069105227..d37d24553867 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Program.Main.cs +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Program.Main.cs @@ -46,7 +46,6 @@ public static void Main(string[] args) #if (IndividualLocalAuth) builder.Services.AddCascadingAuthenticationState(); - builder.Services.AddScoped(); builder.Services.AddScoped(); #if (UseServer) builder.Services.AddScoped(); diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Program.cs b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Program.cs index b0186c948ca4..7ea8e5a50033 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Program.cs +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Program.cs @@ -40,7 +40,6 @@ #if (IndividualLocalAuth) builder.Services.AddCascadingAuthenticationState(); -builder.Services.AddScoped(); builder.Services.AddScoped(); #if (UseServer) builder.Services.AddScoped(); From 3c28ae7a4e4bd3fe8643c5fdd75b3eba57f87a28 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Wed, 14 May 2025 16:19:09 +0200 Subject: [PATCH 11/15] Missing changes. --- .../Components/Account/IdentityRedirectManager.cs | 5 +++-- .../Components/Account/Pages/Manage/Email.razor | 1 + .../Components/Account/Pages/Manage/PersonalData.razor | 6 ++++++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/IdentityRedirectManager.cs b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/IdentityRedirectManager.cs index 15cfd7b0405f..00734c65c714 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/IdentityRedirectManager.cs +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/IdentityRedirectManager.cs @@ -1,5 +1,6 @@ -using System.Diagnostics.CodeAnalysis; using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Identity; +using BlazorWeb_CSharp.Data; namespace BlazorWeb_CSharp.Components.Account; @@ -49,5 +50,5 @@ public void RedirectToCurrentPageWithStatus(string message, HttpContext context) => RedirectToWithStatus(CurrentPath, message, context); public void RedirectToInvalidUser(UserManager userManager, HttpContext context) - => redirectManager.RedirectToWithStatus("Account/InvalidUser", $"Error: Unable to load user with ID '{userManager.GetUserId(context.User)}'.", context); + => RedirectToWithStatus("Account/InvalidUser", $"Error: Unable to load user with ID '{userManager.GetUserId(context.User)}'.", context); } diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/Email.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/Email.razor index 54ca704882ed..5cbfdcb75024 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/Email.razor +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/Email.razor @@ -10,6 +10,7 @@ @inject UserManager UserManager @inject IEmailSender EmailSender @inject NavigationManager NavigationManager +@inject IdentityRedirectManager RedirectManager Manage email diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/PersonalData.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/PersonalData.razor index d56b785bb21f..42dd6d52ada3 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/PersonalData.razor +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/PersonalData.razor @@ -1,5 +1,11 @@ @page "/Account/Manage/PersonalData" +@using Microsoft.AspNetCore.Identity +@using BlazorWeb_CSharp.Data + +@inject UserManager UserManager +@inject IdentityRedirectManager RedirectManager + Personal Data From a2c439ffce878c82eea0560237e3e911fb6e9841 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Thu, 15 May 2025 10:27:59 +0200 Subject: [PATCH 12/15] Fix expectations for files present in templates. --- .../test/Templates.Tests/template-baselines.json | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/ProjectTemplates/test/Templates.Tests/template-baselines.json b/src/ProjectTemplates/test/Templates.Tests/template-baselines.json index 5142ac24928e..d8d269531f22 100644 --- a/src/ProjectTemplates/test/Templates.Tests/template-baselines.json +++ b/src/ProjectTemplates/test/Templates.Tests/template-baselines.json @@ -600,7 +600,6 @@ "Components/Account/IdentityComponentsEndpointRouteBuilderExtensions.cs", "Components/Account/IdentityNoOpEmailSender.cs", "Components/Account/IdentityRedirectManager.cs", - "Components/Account/IdentityUserAccessor.cs", "Components/Account/Pages/AccessDenied.razor", "Components/Account/Pages/ConfirmEmail.razor", "Components/Account/Pages/ConfirmEmailChange.razor", @@ -791,7 +790,6 @@ "Components/Account/IdentityNoOpEmailSender.cs", "Components/Account/IdentityRedirectManager.cs", "Components/Account/IdentityRevalidatingAuthenticationStateProvider.cs", - "Components/Account/IdentityUserAccessor.cs", "Components/Account/Pages/AccessDenied.razor", "Components/Account/Pages/ConfirmEmail.razor", "Components/Account/Pages/ConfirmEmailChange.razor", @@ -913,7 +911,6 @@ "Components/Account/IdentityNoOpEmailSender.cs", "Components/Account/IdentityRedirectManager.cs", "Components/Account/IdentityRevalidatingAuthenticationStateProvider.cs", - "Components/Account/IdentityUserAccessor.cs", "Components/Account/Pages/AccessDenied.razor", "Components/Account/Pages/ConfirmEmail.razor", "Components/Account/Pages/ConfirmEmailChange.razor", @@ -1118,7 +1115,6 @@ "{ProjectName}/Components/Account/IdentityComponentsEndpointRouteBuilderExtensions.cs", "{ProjectName}/Components/Account/IdentityNoOpEmailSender.cs", "{ProjectName}/Components/Account/IdentityRedirectManager.cs", - "{ProjectName}/Components/Account/IdentityUserAccessor.cs", "{ProjectName}/Components/Account/Pages/AccessDenied.razor", "{ProjectName}/Components/Account/Pages/ConfirmEmail.razor", "{ProjectName}/Components/Account/Pages/ConfirmEmailChange.razor", @@ -1322,7 +1318,6 @@ "{ProjectName}/Components/Account/IdentityNoOpEmailSender.cs", "{ProjectName}/Components/Account/IdentityRedirectManager.cs", "{ProjectName}/Components/Account/IdentityRevalidatingAuthenticationStateProvider.cs", - "{ProjectName}/Components/Account/IdentityUserAccessor.cs", "{ProjectName}/Components/Account/Pages/AccessDenied.razor", "{ProjectName}/Components/Account/Pages/ConfirmEmail.razor", "{ProjectName}/Components/Account/Pages/ConfirmEmailChange.razor", @@ -1772,7 +1767,6 @@ "{ProjectName}/Components/Account/IdentityNoOpEmailSender.cs", "{ProjectName}/Components/Account/IdentityRedirectManager.cs", "{ProjectName}/Components/Account/IdentityRevalidatingAuthenticationStateProvider.cs", - "{ProjectName}/Components/Account/IdentityUserAccessor.cs", "{ProjectName}/Components/Account/Pages/AccessDenied.razor", "{ProjectName}/Components/Account/Pages/ConfirmEmail.razor", "{ProjectName}/Components/Account/Pages/ConfirmEmailChange.razor", @@ -1843,7 +1837,6 @@ "Components/Account/IdentityNoOpEmailSender.cs", "Components/Account/IdentityRedirectManager.cs", "Components/Account/IdentityRevalidatingAuthenticationStateProvider.cs", - "Components/Account/IdentityUserAccessor.cs", "Components/Account/Pages/AccessDenied.razor", "Components/Account/Pages/ConfirmEmail.razor", "Components/Account/Pages/ConfirmEmailChange.razor", @@ -1981,7 +1974,6 @@ "{ProjectName}/Components/Account/IdentityComponentsEndpointRouteBuilderExtensions.cs", "{ProjectName}/Components/Account/IdentityNoOpEmailSender.cs", "{ProjectName}/Components/Account/IdentityRedirectManager.cs", - "{ProjectName}/Components/Account/IdentityUserAccessor.cs", "{ProjectName}/Components/Account/Pages/AccessDenied.razor", "{ProjectName}/Components/Account/Pages/ConfirmEmail.razor", "{ProjectName}/Components/Account/Pages/ConfirmEmailChange.razor", @@ -2110,7 +2102,6 @@ "{ProjectName}/Components/Account/IdentityNoOpEmailSender.cs", "{ProjectName}/Components/Account/IdentityRedirectManager.cs", "{ProjectName}/Components/Account/IdentityRevalidatingAuthenticationStateProvider.cs", - "{ProjectName}/Components/Account/IdentityUserAccessor.cs", "{ProjectName}/Components/Account/Pages/AccessDenied.razor", "{ProjectName}/Components/Account/Pages/ConfirmEmail.razor", "{ProjectName}/Components/Account/Pages/ConfirmEmailChange.razor", From 2b13780b51524eaf93fb77746336174183b857c1 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Mon, 19 May 2025 13:24:44 +0200 Subject: [PATCH 13/15] Fix merge conflict. --- src/Components/Components/src/PublicAPI.Unshipped.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Components/Components/src/PublicAPI.Unshipped.txt b/src/Components/Components/src/PublicAPI.Unshipped.txt index 891e4b9ed77c..056848376079 100644 --- a/src/Components/Components/src/PublicAPI.Unshipped.txt +++ b/src/Components/Components/src/PublicAPI.Unshipped.txt @@ -17,5 +17,4 @@ static Microsoft.AspNetCore.Components.Infrastructure.RegisterPersistentComponen static Microsoft.Extensions.DependencyInjection.SupplyParameterFromPersistentComponentStateProviderServiceCollectionExtensions.AddSupplyValueFromPersistentComponentStateProvider(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services) -> Microsoft.Extensions.DependencyInjection.IServiceCollection! static Microsoft.AspNetCore.Components.Infrastructure.ComponentsMetricsServiceCollectionExtensions.AddComponentsMetrics(Microsoft.Extensions.DependencyInjection.IServiceCollection! services) -> Microsoft.Extensions.DependencyInjection.IServiceCollection! static Microsoft.AspNetCore.Components.Infrastructure.ComponentsMetricsServiceCollectionExtensions.AddComponentsTracing(Microsoft.Extensions.DependencyInjection.IServiceCollection! services) -> Microsoft.Extensions.DependencyInjection.IServiceCollection! -static Microsoft.Extensions.DependencyInjection.SupplyParameterFromPersistentComponentStateProviderServiceCollectionExtensions.AddSupplyValueFromPersistentComponentStateProvider(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services) -> Microsoft.Extensions.DependencyInjection.IServiceCollection! virtual Microsoft.AspNetCore.Components.RenderTree.Renderer.SignalRendererToFinishRendering() -> void From 4f9e9ffaad6a94abb9daaf83c4b6723304bb70bf Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Mon, 19 May 2025 13:33:53 +0200 Subject: [PATCH 14/15] Cleanup. --- src/Components/Components/src/PublicAPI.Unshipped.txt | 2 +- .../Components/Account/Pages/Manage/ChangePassword.razor | 3 ++- .../Components/Account/Pages/Manage/Disable2fa.razor | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Components/Components/src/PublicAPI.Unshipped.txt b/src/Components/Components/src/PublicAPI.Unshipped.txt index 056848376079..774edb023c79 100644 --- a/src/Components/Components/src/PublicAPI.Unshipped.txt +++ b/src/Components/Components/src/PublicAPI.Unshipped.txt @@ -14,7 +14,7 @@ Microsoft.AspNetCore.Components.SupplyParameterFromPersistentComponentStateAttri Microsoft.AspNetCore.Components.SupplyParameterFromPersistentComponentStateAttribute.SupplyParameterFromPersistentComponentStateAttribute() -> void Microsoft.Extensions.DependencyInjection.SupplyParameterFromPersistentComponentStateProviderServiceCollectionExtensions static Microsoft.AspNetCore.Components.Infrastructure.RegisterPersistentComponentStateServiceCollectionExtensions.AddPersistentServiceRegistration(Microsoft.Extensions.DependencyInjection.IServiceCollection! services, Microsoft.AspNetCore.Components.IComponentRenderMode! componentRenderMode) -> Microsoft.Extensions.DependencyInjection.IServiceCollection! -static Microsoft.Extensions.DependencyInjection.SupplyParameterFromPersistentComponentStateProviderServiceCollectionExtensions.AddSupplyValueFromPersistentComponentStateProvider(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services) -> Microsoft.Extensions.DependencyInjection.IServiceCollection! static Microsoft.AspNetCore.Components.Infrastructure.ComponentsMetricsServiceCollectionExtensions.AddComponentsMetrics(Microsoft.Extensions.DependencyInjection.IServiceCollection! services) -> Microsoft.Extensions.DependencyInjection.IServiceCollection! static Microsoft.AspNetCore.Components.Infrastructure.ComponentsMetricsServiceCollectionExtensions.AddComponentsTracing(Microsoft.Extensions.DependencyInjection.IServiceCollection! services) -> Microsoft.Extensions.DependencyInjection.IServiceCollection! +static Microsoft.Extensions.DependencyInjection.SupplyParameterFromPersistentComponentStateProviderServiceCollectionExtensions.AddSupplyValueFromPersistentComponentStateProvider(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services) -> Microsoft.Extensions.DependencyInjection.IServiceCollection! virtual Microsoft.AspNetCore.Components.RenderTree.Renderer.SignalRendererToFinishRendering() -> void diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/ChangePassword.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/ChangePassword.razor index d4c6236a115a..1764bde9919f 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/ChangePassword.razor +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/ChangePassword.razor @@ -70,7 +70,8 @@ { if (user is null) { - throw new InvalidOperationException("User is not loaded; cannot change password."); + RedirectManager.RedirectToInvalidUser(UserManager, HttpContext); + return; } var changePasswordResult = await UserManager.ChangePasswordAsync(user, Input.OldPassword, Input.NewPassword); diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/Disable2fa.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/Disable2fa.razor index edf406428ffd..a539c533fa19 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/Disable2fa.razor +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Account/Pages/Manage/Disable2fa.razor @@ -54,7 +54,8 @@ { if (user is null) { - throw new InvalidOperationException("User is not loaded; failed to disable 2FA."); + RedirectManager.RedirectToInvalidUser(UserManager, HttpContext); + return; } var disable2faResult = await UserManager.SetTwoFactorEnabledAsync(user, false); From 7a21ae8f19c3c77524573be669ebca418f66c593 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Mon, 19 May 2025 13:59:28 +0200 Subject: [PATCH 15/15] Fix public APIs. --- src/Components/Components/src/PublicAPI.Unshipped.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Components/Components/src/PublicAPI.Unshipped.txt b/src/Components/Components/src/PublicAPI.Unshipped.txt index 774edb023c79..5eb52d2c330e 100644 --- a/src/Components/Components/src/PublicAPI.Unshipped.txt +++ b/src/Components/Components/src/PublicAPI.Unshipped.txt @@ -17,4 +17,3 @@ static Microsoft.AspNetCore.Components.Infrastructure.RegisterPersistentComponen static Microsoft.AspNetCore.Components.Infrastructure.ComponentsMetricsServiceCollectionExtensions.AddComponentsMetrics(Microsoft.Extensions.DependencyInjection.IServiceCollection! services) -> Microsoft.Extensions.DependencyInjection.IServiceCollection! static Microsoft.AspNetCore.Components.Infrastructure.ComponentsMetricsServiceCollectionExtensions.AddComponentsTracing(Microsoft.Extensions.DependencyInjection.IServiceCollection! services) -> Microsoft.Extensions.DependencyInjection.IServiceCollection! static Microsoft.Extensions.DependencyInjection.SupplyParameterFromPersistentComponentStateProviderServiceCollectionExtensions.AddSupplyValueFromPersistentComponentStateProvider(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services) -> Microsoft.Extensions.DependencyInjection.IServiceCollection! -virtual Microsoft.AspNetCore.Components.RenderTree.Renderer.SignalRendererToFinishRendering() -> void