diff --git a/src/Components/test/E2ETest/ServerRenderingTests/InteractivityTest.cs b/src/Components/test/E2ETest/ServerRenderingTests/InteractivityTest.cs index 286a6de9e12d..dd9f2a3a47e8 100644 --- a/src/Components/test/E2ETest/ServerRenderingTests/InteractivityTest.cs +++ b/src/Components/test/E2ETest/ServerRenderingTests/InteractivityTest.cs @@ -1411,4 +1411,41 @@ public void NavigatesWithInteractivityByRequestRedirection(bool controlFlowByExc Browser.Click(By.Id("redirectButton")); Browser.Equal("Routing test cases", () => Browser.Exists(By.Id("test-info")).Text); } + + [Theory] + // prerendering (SSR) is tested in NoInteractivityTest + [InlineData("ServerNonPrerendered")] + [InlineData("WebAssemblyNonPrerendered")] + public void ProgrammaticNavigationToNotExistingPathReExecutesTo404(string renderMode) + { + Navigate($"{ServerPathBase}/reexecution/redirection-not-found?renderMode={renderMode}&navigate-programmatically=true"); + Assert404ReExecuted(); + } + + [Theory] + // prerendering (SSR) is tested in NoInteractivityTest + [InlineData("ServerNonPrerendered")] + [InlineData("WebAssemblyNonPrerendered")] + public void LinkNavigationToNotExistingPathReExecutesTo404(string renderMode) + { + Navigate($"{ServerPathBase}/reexecution/redirection-not-found?renderMode={renderMode}"); + Browser.Click(By.Id("link-to-not-existing-page")); + Assert404ReExecuted(); + } + + [Theory] + // prerendering (SSR) is tested in NoInteractivityTest + [InlineData("ServerNonPrerendered")] + [InlineData("WebAssemblyNonPrerendered")] + public void BrowserNavigationToNotExistingPathReExecutesTo404(string renderMode) + { + // non-existing path has to have re-execution middleware set up + // so it has to have "reexecution" prefix. Otherwise middleware mapping + // will not be activated, see configuration in Startup + Navigate($"{ServerPathBase}/reexecution/not-existing-page?renderMode={renderMode}"); + Assert404ReExecuted(); + } + + private void Assert404ReExecuted() => + Browser.Equal("Welcome On Page Re-executed After Not Found Event", () => Browser.Exists(By.Id("test-info")).Text); } diff --git a/src/Components/test/E2ETest/ServerRenderingTests/NoInteractivityTest.cs b/src/Components/test/E2ETest/ServerRenderingTests/NoInteractivityTest.cs index 139f3db4726e..c7af49fc7b3f 100644 --- a/src/Components/test/E2ETest/ServerRenderingTests/NoInteractivityTest.cs +++ b/src/Components/test/E2ETest/ServerRenderingTests/NoInteractivityTest.cs @@ -87,6 +87,43 @@ public void CanRenderNotFoundPageAfterStreamingStarted() Browser.Equal("Default Not Found Page", () => Browser.Title); } + [Theory] + [InlineData(true)] + [InlineData(false)] + public void ProgrammaticNavigationToNotExistingPathReExecutesTo404(bool streaming) + { + string streamingPath = streaming ? "-streaming" : ""; + Navigate($"{ServerPathBase}/reexecution/redirection-not-found-ssr{streamingPath}?navigate-programmatically=true"); + Assert404ReExecuted(); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void LinkNavigationToNotExistingPathReExecutesTo404(bool streaming) + { + string streamingPath = streaming ? "-streaming" : ""; + Navigate($"{ServerPathBase}/reexecution/redirection-not-found-ssr{streamingPath}"); + Browser.Click(By.Id("link-to-not-existing-page")); + Assert404ReExecuted(); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void BrowserNavigationToNotExistingPathReExecutesTo404(bool streaming) + { + // non-existing path has to have re-execution middleware set up + // so it has to have "reexecution" prefix. Otherwise middleware mapping + // will not be activated, see configuration in Startup + string streamingPath = streaming ? "-streaming" : ""; + Navigate($"{ServerPathBase}/reexecution/not-existing-page-ssr{streamingPath}"); + Assert404ReExecuted(); + } + + private void Assert404ReExecuted() => + Browser.Equal("Welcome On Page Re-executed After Not Found Event", () => Browser.Exists(By.Id("test-info")).Text); + [Theory] [InlineData(true)] [InlineData(false)] diff --git a/src/Components/test/testassets/Components.TestServer/RazorComponentEndpointsNoInteractivityStartup.cs b/src/Components/test/testassets/Components.TestServer/RazorComponentEndpointsNoInteractivityStartup.cs index c4af233c40fe..7257fafa898d 100644 --- a/src/Components/test/testassets/Components.TestServer/RazorComponentEndpointsNoInteractivityStartup.cs +++ b/src/Components/test/testassets/Components.TestServer/RazorComponentEndpointsNoInteractivityStartup.cs @@ -48,19 +48,37 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) app.Map("/subdir", app => { - if (!env.IsDevelopment()) + app.Map("/reexecution", reexecutionApp => { - app.UseExceptionHandler("/Error", createScopeForErrors: true); - } - - app.UseStaticFiles(); - app.UseRouting(); - RazorComponentEndpointsStartup.UseFakeAuthState(app); - app.UseAntiforgery(); - app.UseEndpoints(endpoints => - { - endpoints.MapRazorComponents(); + reexecutionApp.UseStaticFiles(); + reexecutionApp.UseStatusCodePagesWithReExecute("/not-found-reexecute", createScopeForErrors: true); + reexecutionApp.UseRouting(); + RazorComponentEndpointsStartup.UseFakeAuthState(reexecutionApp); + reexecutionApp.UseAntiforgery(); + reexecutionApp.UseEndpoints(endpoints => + { + endpoints.MapRazorComponents(); + }); }); + + ConfigureSubdirPipeline(app, env); + }); + } + + private void ConfigureSubdirPipeline(IApplicationBuilder app, IWebHostEnvironment env) + { + if (!env.IsDevelopment()) + { + app.UseExceptionHandler("/Error", createScopeForErrors: true); + } + + app.UseStaticFiles(); + app.UseRouting(); + RazorComponentEndpointsStartup.UseFakeAuthState(app); + app.UseAntiforgery(); + app.UseEndpoints(endpoints => + { + endpoints.MapRazorComponents(); }); } } diff --git a/src/Components/test/testassets/Components.TestServer/RazorComponentEndpointsStartup.cs b/src/Components/test/testassets/Components.TestServer/RazorComponentEndpointsStartup.cs index f91db9aa4ee3..ea4f7f7ad220 100644 --- a/src/Components/test/testassets/Components.TestServer/RazorComponentEndpointsStartup.cs +++ b/src/Components/test/testassets/Components.TestServer/RazorComponentEndpointsStartup.cs @@ -76,20 +76,17 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) app.Map("/reexecution", reexecutionApp => { reexecutionApp.UseStatusCodePagesWithReExecute("/not-found-reexecute", createScopeForErrors: true); - reexecutionApp.UseRouting(); + reexecutionApp.UseAntiforgery(); - reexecutionApp.UseEndpoints(endpoints => - { - endpoints.MapRazorComponents(); - }); + ConfigureEndpoints(reexecutionApp, env); }); ConfigureSubdirPipeline(app, env); }); } - protected virtual void ConfigureSubdirPipeline(IApplicationBuilder app, IWebHostEnvironment env) + private void ConfigureSubdirPipeline(IApplicationBuilder app, IWebHostEnvironment env) { WebAssemblyTestHelper.ServeCoopHeadersIfWebAssemblyThreadingEnabled(app); @@ -106,11 +103,15 @@ protected virtual void ConfigureSubdirPipeline(IApplicationBuilder app, IWebHost { if (ctx.Request.Query.ContainsKey("add-csp")) { - ctx.Response.Headers.Add("Content-Security-Policy", "script-src 'self' 'unsafe-inline'"); + ctx.Response.Headers.Add("Content-Security-Policy", "script-src 'self' 'unsafe-inline'"); } return nxt(); }); + ConfigureEndpoints(app, env); + } + private void ConfigureEndpoints(IApplicationBuilder app, IWebHostEnvironment env) + { _ = app.UseEndpoints(endpoints => { var contentRootStaticAssetsPath = Path.Combine(env.ContentRootPath, "Components.TestServer.staticwebassets.endpoints.json"); diff --git a/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/NotFoundPage.razor b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/NotFound/NotFoundPage.razor similarity index 100% rename from src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/NotFoundPage.razor rename to src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/NotFound/NotFoundPage.razor diff --git a/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/PageThatSetsNotFound.razor b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/NotFound/PageThatSetsNotFound.razor similarity index 100% rename from src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/PageThatSetsNotFound.razor rename to src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/NotFound/PageThatSetsNotFound.razor diff --git a/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/NotFound/RedirectionNotFound-SSR-streaming.razor b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/NotFound/RedirectionNotFound-SSR-streaming.razor new file mode 100644 index 000000000000..74374799ce04 --- /dev/null +++ b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/NotFound/RedirectionNotFound-SSR-streaming.razor @@ -0,0 +1,5 @@ +@page "/redirection-not-found-ssr-streaming" +@page "/reexecution/redirection-not-found-ssr-streaming" +@attribute [StreamRendering(true)] + + diff --git a/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/NotFound/RedirectionNotFound-SSR.razor b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/NotFound/RedirectionNotFound-SSR.razor new file mode 100644 index 000000000000..80a579a2e456 --- /dev/null +++ b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/NotFound/RedirectionNotFound-SSR.razor @@ -0,0 +1,5 @@ +@page "/redirection-not-found-ssr" +@page "/reexecution/redirection-not-found-ssr" +@attribute [StreamRendering(false)] + + \ No newline at end of file diff --git a/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/ReexecutedPage.razor b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/NotFound/ReexecutedPage.razor similarity index 100% rename from src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/ReexecutedPage.razor rename to src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/NotFound/ReexecutedPage.razor diff --git a/src/Components/test/testassets/Components.WasmMinimal/Pages/NotFoundInteractiveServer.razor b/src/Components/test/testassets/Components.WasmMinimal/Pages/NotFound/NotFoundInteractiveServer.razor similarity index 100% rename from src/Components/test/testassets/Components.WasmMinimal/Pages/NotFoundInteractiveServer.razor rename to src/Components/test/testassets/Components.WasmMinimal/Pages/NotFound/NotFoundInteractiveServer.razor diff --git a/src/Components/test/testassets/Components.WasmMinimal/Pages/NotFoundInteractiveWebassembly.razor b/src/Components/test/testassets/Components.WasmMinimal/Pages/NotFound/NotFoundInteractiveWebassembly.razor similarity index 100% rename from src/Components/test/testassets/Components.WasmMinimal/Pages/NotFoundInteractiveWebassembly.razor rename to src/Components/test/testassets/Components.WasmMinimal/Pages/NotFound/NotFoundInteractiveWebassembly.razor diff --git a/src/Components/test/testassets/Components.WasmMinimal/Pages/NotFoundSSR.razor b/src/Components/test/testassets/Components.WasmMinimal/Pages/NotFound/NotFoundSSR.razor similarity index 100% rename from src/Components/test/testassets/Components.WasmMinimal/Pages/NotFoundSSR.razor rename to src/Components/test/testassets/Components.WasmMinimal/Pages/NotFound/NotFoundSSR.razor diff --git a/src/Components/test/testassets/Components.WasmMinimal/Pages/NotFound/RedirectionNotFound-Interactive.razor b/src/Components/test/testassets/Components.WasmMinimal/Pages/NotFound/RedirectionNotFound-Interactive.razor new file mode 100644 index 000000000000..461d8b6f5263 --- /dev/null +++ b/src/Components/test/testassets/Components.WasmMinimal/Pages/NotFound/RedirectionNotFound-Interactive.razor @@ -0,0 +1,19 @@ +@page "/redirection-not-found" +@page "/reexecution/redirection-not-found" + + + +@code{ + [Parameter, SupplyParameterFromQuery(Name = "renderMode")] + public string? RenderModeStr { get; set; } + + private RenderModeId _renderMode; + + protected override void OnInitialized() + { + if (!string.IsNullOrEmpty(RenderModeStr)) + { + _renderMode = RenderModeHelper.ParseRenderMode(RenderModeStr); + } + } +} \ No newline at end of file diff --git a/src/Components/test/testassets/Components.WasmMinimal/Pages/NotFound/RedirectionNotFoundComponent.razor b/src/Components/test/testassets/Components.WasmMinimal/Pages/NotFound/RedirectionNotFoundComponent.razor new file mode 100644 index 000000000000..241052e41ed3 --- /dev/null +++ b/src/Components/test/testassets/Components.WasmMinimal/Pages/NotFound/RedirectionNotFoundComponent.razor @@ -0,0 +1,33 @@ + +@inject NavigationManager NavigationManager + +

Original page

+ +

Any content

+ + Go to not-existing-page + + +@code{ + [Parameter] + [SupplyParameterFromQuery(Name = "navigate-programmatically")] + public bool? NavigateProgrammatically { get; set; } + + [Parameter] + public bool StartStreaming { get; set; } = false; + + private string _nonExistingPath = string.Empty; + + protected override async Task OnInitializedAsync() + { + if (StartStreaming) + { + await Task.Yield(); + } + _nonExistingPath = $"{NavigationManager.BaseUri}reexecution/not-existing-page"; + if (NavigateProgrammatically == true) + { + NavigationManager.NavigateTo(_nonExistingPath); + } + } +} diff --git a/src/Components/test/testassets/Components.TestServer/RenderModeHelper.cs b/src/Components/test/testassets/Components.WasmMinimal/RenderModeHelper.cs similarity index 98% rename from src/Components/test/testassets/Components.TestServer/RenderModeHelper.cs rename to src/Components/test/testassets/Components.WasmMinimal/RenderModeHelper.cs index ab1285699691..1845bddfbe22 100644 --- a/src/Components/test/testassets/Components.TestServer/RenderModeHelper.cs +++ b/src/Components/test/testassets/Components.WasmMinimal/RenderModeHelper.cs @@ -5,8 +5,6 @@ using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Web; -namespace TestServer; - public static class RenderModeHelper { public static IComponentRenderMode GetRenderMode(RenderModeId renderMode) 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 d37d24553867..b0b8d955cc6d 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 @@ -103,10 +103,12 @@ public static void Main(string[] args) #endif } + app.UseStatusCodePagesWithReExecute("/not-found", createScopeForErrors: true); + #if (HasHttpsProfile) app.UseHttpsRedirection(); - #endif +#endif app.UseAntiforgery(); app.MapStaticAssets(); 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 7ea8e5a50033..8eb1deacb1fa 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 @@ -96,6 +96,7 @@ app.UseHsts(); #endif } +app.UseStatusCodePagesWithReExecute("/not-found", createScopeForErrors: true); #if (HasHttpsProfile) app.UseHttpsRedirection();