using BoardApp.Config; using BoardApp.Middleware; using BoardApp.Services; using Microsoft.AspNetCore.Authentication.Negotiate; using Microsoft.AspNetCore.HttpOverrides; using Microsoft.AspNetCore.Server.IISIntegration; var builder = WebApplication.CreateBuilder(args); // Load app.config.json early so we can configure session cookie flags. var earlyCfgLoader = new BoardConfigLoader(); var appCfg = earlyCfgLoader.LoadAppConfig(); builder.Services.AddControllersWithViews(); builder.Services.AddHttpContextAccessor(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); var isIisHost = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("ASPNETCORE_IIS_PHYSICAL_PATH")); if (isIisHost) { builder.Services.AddAuthentication(IISDefaults.AuthenticationScheme); } else { builder.Services.AddAuthentication(NegotiateDefaults.AuthenticationScheme).AddNegotiate(); } static SameSiteMode ParseSameSite(object? v) { var s = (v as string)?.Trim().ToLowerInvariant(); return s switch { "strict" => SameSiteMode.Strict, "none" => SameSiteMode.None, _ => SameSiteMode.Lax }; } // Session cookie must match Node version for behavior parity. builder.Services.AddDistributedMemoryCache(); builder.Services.AddSession(options => { options.Cookie.Name = "board.sid"; options.Cookie.Path = "/"; options.Cookie.HttpOnly = true; options.Cookie.IsEssential = true; options.Cookie.SecurePolicy = appCfg.CookieSecure ? CookieSecurePolicy.Always : CookieSecurePolicy.SameAsRequest; options.Cookie.SameSite = ParseSameSite(appCfg.CookieSameSite); options.Cookie.MaxAge = TimeSpan.FromMilliseconds(appCfg.SessionMaxAgeMs); options.IdleTimeout = TimeSpan.FromMilliseconds(appCfg.SessionMaxAgeMs); }); var app = builder.Build(); if (appCfg.TrustProxy) { app.UseForwardedHeaders(new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto }); } // Keep external URL shape: http://host:3000/board/... var pathBase = Environment.GetEnvironmentVariable("BOARD_PATH_BASE"); if (string.IsNullOrWhiteSpace(pathBase)) { pathBase = "/board"; } if (!pathBase.StartsWith("/")) { pathBase = "/" + pathBase; } app.UsePathBase(pathBase); app.Use(async (ctx, next) => { ctx.Items["BoardAppConfig"] = appCfg; await next(); }); // /board/static/x → wwwroot/static/x(勿设 RequestPath=/static,否则会映射到 wwwroot/x 导致 404) app.UseStaticFiles(); app.UseRouting(); app.UseSession(); app.UseAuthentication(); app.UseMiddleware(); app.MapControllerRoute( name: "default", pattern: "{controller=Board}/{action=Home}/{id?}"); // IIS(含进程内托管)或容器常由宿主注入监听地址,勿再硬编码 Kestrel URL。 var urlsFromEnv = Environment.GetEnvironmentVariable("ASPNETCORE_URLS"); var iisPhysicalPath = Environment.GetEnvironmentVariable("ASPNETCORE_IIS_PHYSICAL_PATH"); if (!string.IsNullOrWhiteSpace(urlsFromEnv) || !string.IsNullOrWhiteSpace(iisPhysicalPath)) { app.Run(); } else { app.Run($"http://0.0.0.0:{appCfg.Port}"); }