using Microsoft.Extensions.Options; using Web.Settings; namespace Web.Middleware; public sealed class SiteModeMiddleware(RequestDelegate next, IOptions options) { private static readonly string[] StaticAssetPrefixes = [ "/css/", "/js/", "/img/", "/logo/", "/fonts/" ]; public async Task InvokeAsync(HttpContext context) { var mode = ResolveMode(options.Value.Mode); if (mode == SiteModes.Normal) { await next(context); return; } var path = context.Request.Path.Value ?? "/"; var statusPath = GetStatusPath(mode); if (IsAllowedPath(path, statusPath)) { if (mode == SiteModes.Unavailable && path.Equals("/site-unavailable.html", StringComparison.OrdinalIgnoreCase)) { context.Response.StatusCode = StatusCodes.Status503ServiceUnavailable; context.Response.Headers.RetryAfter = "3600"; } await next(context); return; } context.Response.Redirect(statusPath); } private static string ResolveMode(string? configuredMode) { if (string.Equals(configuredMode, SiteModes.UnderConstruction, StringComparison.OrdinalIgnoreCase)) return SiteModes.UnderConstruction; if (string.Equals(configuredMode, SiteModes.Unavailable, StringComparison.OrdinalIgnoreCase)) return SiteModes.Unavailable; return SiteModes.Normal; } private static string GetStatusPath(string mode) => mode == SiteModes.UnderConstruction ? "/under-construction.html" : "/site-unavailable.html"; private static bool IsAllowedPath(string path, string statusPath) { if (path.Equals(statusPath, StringComparison.OrdinalIgnoreCase)) return true; if (path.Equals("/favicon.ico", StringComparison.OrdinalIgnoreCase)) return true; return StaticAssetPrefixes.Any(prefix => path.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)); } }