From e35b8e438c7e34ae2669a8c1e94736851a5991e0 Mon Sep 17 00:00:00 2001 From: Leonid Pershin Date: Thu, 20 Aug 2026 13:22:28 +0300 Subject: [PATCH 1/2] Skip UseFileServer when wwwroot is missing. Local Aspire has no published client folder; the middleware warned on every boot. Co-authored-by: Cursor --- docs/bugs/04-wwwroot-not-found.md | 37 +++++++++++++++++++ docs/bugs/README.md | 1 + src/HSchool.Server/ClientStaticFiles.cs | 10 +++++ src/HSchool.Server/Program.cs | 6 ++- .../ClientStaticFilesTests.cs | 28 ++++++++++++++ 5 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 docs/bugs/04-wwwroot-not-found.md create mode 100644 src/HSchool.Server/ClientStaticFiles.cs create mode 100644 tests/HSchool.Server.Tests/ClientStaticFilesTests.cs diff --git a/docs/bugs/04-wwwroot-not-found.md b/docs/bugs/04-wwwroot-not-found.md new file mode 100644 index 0000000..dcb6961 --- /dev/null +++ b/docs/bugs/04-wwwroot-not-found.md @@ -0,0 +1,37 @@ +# Баг 4. UseFileServer без каталога wwwroot + +## Симптом + +При локальном старте сервера (Aspire, без publish): + +``` +warn: Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware[16] + The WebRootPath was not found: …\src\HSchool.Server\wwwroot. Static files may be unavailable. +``` + +Клиент в деве идёт с Vite на 5173, не с сервера. Ожидали отсутствие этого предупреждения. + +## Причина + +`Program` всегда зовёт `UseFileServer()`. ASP.NET подставляет `ContentRoot/wwwroot` даже если папки нет и пишет warning 16. Каталог появляется только в опубликованном контейнере (`PublishWithContainerFiles(client, "wwwroot")`). + +## Путь + +`UseFileServer` только если `wwwroot` есть на диске. Отвергнуто: пустой каталог с `.gitkeep`; флаг `IsDevelopment` (publish с Development тогда не раздаст клиент). + +## Задачи + +- [x] Не вызывать `UseFileServer`, пока `WebRootPath` не существует +- [x] Тест предиката на существующий и отсутствующий каталог + +## Тест, без которого не закрыт + +`HSchool.Server.Tests`: `ClientStaticFiles.CanServe` ложь на несуществующем пути, истина на живом каталоге. + +```bash +dotnet test tests/HSchool.Server.Tests --filter FullyQualifiedName~ClientStaticFiles +``` + +## Стоп + +Протокол, сейв, Vite, `PublishWithContainerFiles` не трогать. diff --git a/docs/bugs/README.md b/docs/bugs/README.md index 87b42d3..2411cbd 100644 --- a/docs/bugs/README.md +++ b/docs/bugs/README.md @@ -12,3 +12,4 @@ | [1. Окно входа](01-login-layout.md) | ✅ | Форма входа прижата к углу, поле пароля на всю ширину | | [2. Запуск AppHost](02-client-endpoint-proxy.md) | ✅ | `run-aspire` падает: proxy + Port=TargetPort=5173 у client | | [3. users.json как сейв](03-users-json-as-school.md) | ✅ | При старте: `Save …/users.json has id 0; ids start at 1` | +| [4. Нет wwwroot](04-wwwroot-not-found.md) | 🔄 | При старте: `The WebRootPath was not found: …/wwwroot` | diff --git a/src/HSchool.Server/ClientStaticFiles.cs b/src/HSchool.Server/ClientStaticFiles.cs new file mode 100644 index 0000000..12f5581 --- /dev/null +++ b/src/HSchool.Server/ClientStaticFiles.cs @@ -0,0 +1,10 @@ +namespace HSchool.Server; + +/// +/// Published images copy the Vite build into wwwroot. Local Aspire has no such folder. +/// +internal static class ClientStaticFiles +{ + public static bool CanServe(string? webRootPath) => + !string.IsNullOrEmpty(webRootPath) && Directory.Exists(webRootPath); +} diff --git a/src/HSchool.Server/Program.cs b/src/HSchool.Server/Program.cs index 50f1027..39b67c8 100644 --- a/src/HSchool.Server/Program.cs +++ b/src/HSchool.Server/Program.cs @@ -159,7 +159,11 @@ app.Map("/ws/game", async (HttpContext context, GameSocketHandler handler, Sessi app.MapDefaultEndpoints(); // In a published container the built client lands in wwwroot next to the server. -app.UseFileServer(); +// Local Aspire has no such folder — Vite serves the UI, and UseFileServer would warn. +if (ClientStaticFiles.CanServe(app.Environment.WebRootPath)) +{ + app.UseFileServer(); +} app.Run(); diff --git a/tests/HSchool.Server.Tests/ClientStaticFilesTests.cs b/tests/HSchool.Server.Tests/ClientStaticFilesTests.cs new file mode 100644 index 0000000..ffa60f3 --- /dev/null +++ b/tests/HSchool.Server.Tests/ClientStaticFilesTests.cs @@ -0,0 +1,28 @@ +namespace HSchool.Server.Tests; + +public class ClientStaticFilesTests +{ + [Fact] + public void CanServe_IsFalseWhenTheDirectoryIsMissing() + { + var missing = Path.Combine(Path.GetTempPath(), "h-school-no-wwwroot-" + Guid.NewGuid().ToString("N")); + + Assert.False(ClientStaticFiles.CanServe(missing)); + Assert.False(ClientStaticFiles.CanServe(null)); + Assert.False(ClientStaticFiles.CanServe("")); + } + + [Fact] + public void CanServe_IsTrueWhenTheDirectoryExists() + { + var directory = Directory.CreateTempSubdirectory("h-school-wwwroot-"); + try + { + Assert.True(ClientStaticFiles.CanServe(directory.FullName)); + } + finally + { + directory.Delete(recursive: true); + } + } +} From f7d5fa1d6b44efad3b00c02ce0b5209a74e601ed Mon Sep 17 00:00:00 2001 From: Leonid Pershin Date: Thu, 20 Aug 2026 13:22:59 +0300 Subject: [PATCH 2/2] Mark bug 4 wwwroot skip as complete. Co-authored-by: Cursor --- docs/bugs/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/bugs/README.md b/docs/bugs/README.md index 2411cbd..c9c7546 100644 --- a/docs/bugs/README.md +++ b/docs/bugs/README.md @@ -12,4 +12,4 @@ | [1. Окно входа](01-login-layout.md) | ✅ | Форма входа прижата к углу, поле пароля на всю ширину | | [2. Запуск AppHost](02-client-endpoint-proxy.md) | ✅ | `run-aspire` падает: proxy + Port=TargetPort=5173 у client | | [3. users.json как сейв](03-users-json-as-school.md) | ✅ | При старте: `Save …/users.json has id 0; ids start at 1` | -| [4. Нет wwwroot](04-wwwroot-not-found.md) | 🔄 | При старте: `The WebRootPath was not found: …/wwwroot` | +| [4. Нет wwwroot](04-wwwroot-not-found.md) | ✅ | При старте: `The WebRootPath was not found: …/wwwroot` |