diff --git a/Oqtane.Client/UI/PageState.cs b/Oqtane.Client/UI/PageState.cs index 91cf158c..8aac9627 100644 --- a/Oqtane.Client/UI/PageState.cs +++ b/Oqtane.Client/UI/PageState.cs @@ -29,6 +29,8 @@ namespace Oqtane.UI public bool Refresh { get; set; } public bool AllowCookies { get; set; } + public int? StatusCode { get; set; } + public List Pages { get { return Site?.Pages; } @@ -63,7 +65,8 @@ namespace Oqtane.UI IsInternalNavigation = IsInternalNavigation, RenderId = RenderId, Refresh = Refresh, - AllowCookies = AllowCookies + AllowCookies = AllowCookies, + StatusCode = StatusCode }; } } diff --git a/Oqtane.Client/UI/SiteRouter.razor b/Oqtane.Client/UI/SiteRouter.razor index a345a7f0..e7b11256 100644 --- a/Oqtane.Client/UI/SiteRouter.razor +++ b/Oqtane.Client/UI/SiteRouter.razor @@ -158,7 +158,9 @@ // verify user is authenticated for current site var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync(); - if (authState.User.Identity.IsAuthenticated && authState.User.Claims.Any(item => item.Type == Constants.SiteKeyClaimType && item.Value == SiteState.Alias.SiteKey)) + if (authState.User.Identity.IsAuthenticated + && authState.User.Claims.Any(item => item.Type == Constants.SiteKeyClaimType && item.Value == SiteState.Alias.SiteKey) + && PageState.StatusCode != (int)HttpStatusCode.NotFound) { // get user var userid = int.Parse(authState.User.Claims.First(item => item.Type == ClaimTypes.NameIdentifier).Value); @@ -337,6 +339,7 @@ IsInternalNavigation = _isInternalNavigation, RenderId = renderid, Refresh = false, + StatusCode = PageState?.StatusCode, AllowCookies = _allowCookies.GetValueOrDefault(true) }; OnStateChange?.Invoke(_pagestate); diff --git a/Oqtane.Server/Components/App.razor b/Oqtane.Server/Components/App.razor index 7382976b..333ed42f 100644 --- a/Oqtane.Server/Components/App.razor +++ b/Oqtane.Server/Components/App.razor @@ -170,6 +170,7 @@ if (page == null || page.IsDeleted) { HandlePageNotFound(site, page, route); + return; } else { @@ -248,6 +249,7 @@ IsInternalNavigation = false, RenderId = Guid.NewGuid(), Refresh = true, + StatusCode = Context.Response.StatusCode, AllowCookies = _allowCookies }; } @@ -300,8 +302,16 @@ { if (route.PagePath != "404") { - // redirect to 404 page - NavigationManager.NavigateTo(route.SiteUrl + "/404", true); + // handle not found request in static mode + if(_renderMode == RenderModes.Static) + { + NavigationManager.NotFound(); + } + else + { + // redirect to 404 page + NavigationManager.NavigateTo(route.SiteUrl + "/404", true); + } } } } diff --git a/Oqtane.Server/Extensions/ApplicationBuilderExtensions.cs b/Oqtane.Server/Extensions/ApplicationBuilderExtensions.cs index fa3835ea..f3d11dc1 100644 --- a/Oqtane.Server/Extensions/ApplicationBuilderExtensions.cs +++ b/Oqtane.Server/Extensions/ApplicationBuilderExtensions.cs @@ -1,8 +1,11 @@ using System; +using System.IO; using System.Linq; +using System.Net; using System.Reflection; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Cors.Infrastructure; +using Microsoft.AspNetCore.Diagnostics; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; @@ -65,6 +68,7 @@ namespace Oqtane.Extensions app.UseAuthentication(); app.UseAuthorization(); app.UseAntiforgery(); + app.UseNotFoundResponse(); // execute any IServerStartup logic app.ConfigureOqtaneAssemblies(environment); @@ -146,5 +150,76 @@ namespace Oqtane.Extensions public static IApplicationBuilder UseExceptionMiddleWare(this IApplicationBuilder builder) => builder.UseMiddleware(); + + public static IApplicationBuilder UseNotFoundResponse(this IApplicationBuilder app) + { + const string notFoundRoute = "/404"; + app.UseStatusCodePagesWithReExecute(notFoundRoute, createScopeForStatusCodePages: true); + + app.Use(async (context, next) => + { + var path = context.Request.Path.Value ?? string.Empty; + if (ShouldSkipStatusCodeReExecution(path)) + { + var feature = context.Features.Get(); + feature?.Enabled = false; + } + + await next(); + }); + + app.Use(async (context, next) => + { + var feature = context.Features.Get(); + var handled = false; + if (feature != null + && context.Response.StatusCode == (int)HttpStatusCode.NotFound + && notFoundRoute.Equals(context.Request.Path.Value, StringComparison.OrdinalIgnoreCase)) + { + var alias = context.GetAlias(); + if (!string.IsNullOrEmpty(alias?.Path)) + { + var originalPath = context.Request.Path; + context.Request.Path = new PathString($"/{alias.Path}{notFoundRoute}"); + try + { + handled = true; + await next(); + } + finally + { + context.Request.Path = originalPath; + } + } + } + + if (!handled) + { + await next(); + } + }); + + return app; + } + + static bool ShouldSkipStatusCodeReExecution(string path) + { + return path.Contains("/api/", StringComparison.OrdinalIgnoreCase) || + path.StartsWith("/_framework/", StringComparison.OrdinalIgnoreCase) || + path.StartsWith("/_content/", StringComparison.OrdinalIgnoreCase) || + HasStaticFileExtension(path); + } + + static bool HasStaticFileExtension(string path) + { + var extension = Path.GetExtension(path); + if (string.IsNullOrEmpty(extension)) + { + return false; + } + + var staticExtensions = new[] { ".js", ".css", ".map", ".json", ".jpg", ".jpeg", ".png", ".gif", ".svg", ".webp", ".ico", ".woff", ".woff2", ".ttf", ".eot", ".otf", ".mp4", ".webm", ".ogg", ".mp3", ".wav", ".pdf", ".txt", ".xml" }; + return staticExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase); + } } }