# Phase 1: Architecture Foundation - Research **Researched:** 2026-03-27 **Domain:** .NET 9 Blazor WASM + ASP.NET Core Web API solution scaffolding **Confidence:** HIGH ## Summary Phase 1 creates the three-project solution structure (`ChatAgent.Client`, `ChatAgent.Api`, `ChatAgent.Shared`) with working CORS communication between WASM client and API server, and establishes the tutorial commenting convention that applies to all subsequent phases. No feature code is written -- this phase locks in the architectural boundaries (no API key in WASM, no file I/O in WASM, no direct OpenAI calls from WASM) so that later phases build on a verified foundation. The .NET 9 SDK is available on this machine (9.0.312), alongside .NET 10 (10.0.201). Per CLAUDE.md the project targets .NET 9. The `blazorwasm`, `webapi`, and `classlib` templates are all available. The `webapi` template supports `--use-controllers` for the MVC Controller pattern the user chose (D-05). The hosted Blazor WASM template was removed in .NET 8, so the three projects must be created separately and added to a solution manually. **Primary recommendation:** Create the solution using `dotnet new` templates targeting `net9.0`, add a shared class library for DTOs, wire up a minimal health-check endpoint, confirm CORS works from WASM to API, and verify `dotnet publish` completes cleanly. ## User Constraints (from CONTEXT.md) ### Locked Decisions - **D-01:** Solution named `ChatAgent.sln` at repo root with three projects: `ChatAgent.Client` (Blazor WASM), `ChatAgent.Api` (ASP.NET Core backend), `ChatAgent.Shared` (shared models/DTOs) - **D-02:** Projects live in `src/` subfolders: `src/ChatAgent.Client/`, `src/ChatAgent.Api/`, `src/ChatAgent.Shared/` - **D-03:** Solution file at repo root for easy `dotnet build` from project root - **D-04:** Typed HttpClient pattern -- a `ChatApiClient` class in the Client project wraps all backend API calls, registered via DI - **D-05:** Backend uses traditional MVC Controllers (not Minimal API) -- more structure, familiar pattern for tutorial - **D-06:** CORS configured on the API to allow the WASM client origin during development - **D-07:** Full tutorial-style inline comments -- explain everything including basic patterns, treat every file as a teaching moment - **D-08:** Comments go inline (XML doc comments and `//` comments right next to the code), no separate companion docs - **D-09:** Every Blazor concept introduced must have a comment explaining WHAT it is and WHY it's used - **D-10:** Start with plain HTML/CSS in Phase 1 -- no MudBlazor yet. Learn raw Blazor rendering first, add component library later - **D-11:** Light theme as default look and feel ### Claude's Discretion - CORS configuration details (origins, headers, methods) - Base URL configuration approach (appsettings.json vs environment variables) - Project file (.csproj) configuration details - Test project structure (if any placeholder tests needed) - .NET version targeting (9 vs 10 based on current stability) ### Deferred Ideas (OUT OF SCOPE) None -- discussion stayed within phase scope ## Phase Requirements | ID | Description | Research Support | |----|-------------|------------------| | CODE-01 | Every Blazor concept introduced has inline comments explaining what and why | Tutorial commenting convention (D-07/D-08/D-09); every `.cs`, `.razor`, and `.csproj` file gets comments explaining the Blazor/ASP.NET Core concept it demonstrates | | CODE-02 | Each phase introduces one concept incrementally (tutorial-style progression) | Phase 1's concept is "solution structure and project boundaries" -- no feature code, no OpenAI calls, no persistence, no streaming. Just the scaffold + one health-check round-trip to prove communication works | ## Standard Stack ### Core (Phase 1 Only) | Library | Version | Purpose | Why Standard | |---------|---------|---------|--------------| | .NET 9 SDK | 9.0.312 | Runtime and tooling | Installed on machine; stable; CLAUDE.md specifies .NET 9 | | Blazor WebAssembly Standalone | net9.0 | Client SPA | Non-negotiable per constraints | | ASP.NET Core Web API | net9.0 | Backend API server | Required for API key isolation | | Class Library | net9.0 | Shared models/DTOs | Referenced by both Client and Api projects | ### Not Needed in Phase 1 | Library | Why Deferred | |---------|-------------| | `OpenAI` NuGet | No AI calls in Phase 1 -- introduced in Phase 3 | | `Markdig` | No markdown rendering in Phase 1 -- introduced in Phase 4 | | `MudBlazor` | D-10 explicitly defers this; plain HTML/CSS first | | Any test framework | No business logic to test yet; placeholder tests are optional (Claude's discretion -- recommend skipping to keep Phase 1 focused) | **Installation:** ```bash # From repo root dotnet new sln -n ChatAgent # Create projects targeting .NET 9 dotnet new blazorwasm -n ChatAgent.Client --framework net9.0 -o src/ChatAgent.Client dotnet new webapi -n ChatAgent.Api --framework net9.0 --use-controllers -o src/ChatAgent.Api dotnet new classlib -n ChatAgent.Shared --framework net9.0 -o src/ChatAgent.Shared # Add projects to solution dotnet sln ChatAgent.sln add src/ChatAgent.Client/ChatAgent.Client.csproj dotnet sln ChatAgent.sln add src/ChatAgent.Api/ChatAgent.Api.csproj dotnet sln ChatAgent.sln add src/ChatAgent.Shared/ChatAgent.Shared.csproj # Add Shared reference to both projects dotnet add src/ChatAgent.Client/ChatAgent.Client.csproj reference src/ChatAgent.Shared/ChatAgent.Shared.csproj dotnet add src/ChatAgent.Api/ChatAgent.Api.csproj reference src/ChatAgent.Shared/ChatAgent.Shared.csproj ``` ## Architecture Patterns ### Recommended Project Structure (Phase 1) ``` ChatAgent.sln # Solution file at repo root (D-03) src/ ChatAgent.Client/ # Blazor WASM standalone app ChatAgent.Client.csproj Program.cs # DI registration, HttpClient base URL config App.razor # Root component _Imports.razor # Global using directives Layout/ MainLayout.razor # App shell layout Pages/ Home.razor # Landing page (placeholder in Phase 1) Services/ ChatApiClient.cs # Typed HttpClient wrapper (D-04) wwwroot/ index.html # HTML host page css/app.css # Basic styling (plain CSS, D-10) ChatAgent.Api/ # ASP.NET Core Web API ChatAgent.Api.csproj Program.cs # DI, CORS, controller mapping Controllers/ HealthController.cs # GET /api/health -- proves CORS works appsettings.json # API config (base URLs, future API key ref) appsettings.Development.json # Dev-specific config ChatAgent.Shared/ # Shared class library ChatAgent.Shared.csproj Models/ HealthResponse.cs # Simple DTO for health-check response ``` ### Pattern 1: Typed HttpClient in Blazor WASM (D-04) **What:** A `ChatApiClient` class wraps `HttpClient` for all API calls. Registered in DI as a typed client. **When to use:** Always -- components never use `HttpClient` directly. **Example:** ```csharp // Source: ASP.NET Core docs + CONTEXT.md D-04 // ChatApiClient.cs -- Typed HttpClient wrapper // In Blazor WASM, HttpClient is backed by the browser's Fetch API. // We wrap it in a typed client so components don't depend on HttpClient directly. // This makes testing easier and centralizes API URL management. public class ChatApiClient { private readonly HttpClient _httpClient; public ChatApiClient(HttpClient httpClient) { _httpClient = httpClient; } /// /// Calls the health endpoint to verify the API server is reachable. /// This is the first HTTP call from WASM to the backend -- proves CORS works. /// public async Task GetHealthAsync() { return await _httpClient.GetFromJsonAsync("api/health"); } } // Registration in Program.cs: // AddHttpClient registers a typed HttpClient with its own configuration. // The base address points to the API server (different port during local dev). builder.Services.AddHttpClient(client => { client.BaseAddress = new Uri("https://localhost:7100"); }); ``` ### Pattern 2: MVC Controller on API (D-05) **What:** Traditional `[ApiController]` with `[Route("api/[controller]")]` attributes instead of Minimal API `app.MapGet()`. **When to use:** All API endpoints in this project. **Example:** ```csharp // HealthController.cs -- ASP.NET Core MVC Controller // We use Controllers (not Minimal API) for more explicit structure (D-05). // Each controller is a class with methods for each HTTP verb. // [ApiController] enables automatic model validation and 400 responses. [ApiController] [Route("api/[controller]")] public class HealthController : ControllerBase { /// /// Health check endpoint. Returns server status and timestamp. /// Used by the WASM client to verify the API is reachable and CORS is working. /// [HttpGet] public IActionResult Get() { return Ok(new HealthResponse { Status = "healthy", Timestamp = DateTime.UtcNow }); } } ``` ### Pattern 3: CORS Configuration (D-06) **What:** Explicit CORS policy on the API allowing the WASM client origin. **When to use:** Required for any cross-origin HTTP call from WASM to API during development. **Example:** ```csharp // Program.cs (API project) -- CORS setup // CORS (Cross-Origin Resource Sharing) is required because the Blazor WASM client // runs on a different port (e.g., localhost:5200) than the API (e.g., localhost:7100). // Browsers block cross-origin requests by default for security. // We define a named policy that allows only our client origin. builder.Services.AddCors(options => { options.AddPolicy("AllowBlazorClient", policy => { policy.WithOrigins("https://localhost:5200") // WASM client origin .AllowAnyHeader() .AllowAnyMethod(); }); }); // ... after builder.Build() app.UseCors("AllowBlazorClient"); ``` ### Pattern 4: Base URL Configuration (Claude's Discretion) **Recommendation:** Use `appsettings.json` in the WASM client for the API base URL. This is standard for Blazor WASM and the file is in `wwwroot/` (public, no secrets). Environment variables are harder to configure for WASM since it runs in the browser. ```csharp // wwwroot/appsettings.json (Client project) -- PUBLIC config only // In Blazor WASM, appsettings.json is a static file served to the browser. // NEVER put secrets here. Only public configuration like API URLs. { "ApiBaseUrl": "https://localhost:7100" } // Program.cs -- reading the config var apiBaseUrl = builder.Configuration["ApiBaseUrl"] ?? throw new InvalidOperationException("ApiBaseUrl not configured"); builder.Services.AddHttpClient(client => { client.BaseAddress = new Uri(apiBaseUrl); }); ``` ### Anti-Patterns to Avoid - **Any `System.IO.File` usage in the Client project:** WASM runs in a browser sandbox; file I/O does not persist. All persistence is server-side. - **Any API key or secret in `wwwroot/appsettings.json`:** This file is publicly accessible. Secrets belong in the API project only. - **OpenAI SDK registration in the Client `Program.cs`:** All OpenAI calls go through the backend API. The Client never talks to OpenAI directly. - **CORS wildcard (`AllowAnyOrigin`):** Even for local dev, scope to the actual client origin. Prevents accidental exposure. - **Logic in `.razor` code blocks:** Components call services; services own logic (D-04 enforces this via typed client pattern). ## Don't Hand-Roll | Problem | Don't Build | Use Instead | Why | |---------|-------------|-------------|-----| | HTTP client management | Manual `HttpClient` instantiation in components | `IHttpClientFactory` via `AddHttpClient` | Proper socket management, DI lifecycle, testability | | JSON serialization | Manual string building | `System.Text.Json` (built-in) with `GetFromJsonAsync`/`PostAsJsonAsync` | Type-safe, trim-compatible, zero dependencies | | CORS handling | Custom middleware or response headers | `builder.Services.AddCors()` + `app.UseCors()` | Handles preflight OPTIONS requests, header management automatically | | Project references | Copy-paste DTOs between projects | `dotnet add reference` to Shared project | Single source of truth; compile-time verification | ## Common Pitfalls ### Pitfall 1: Wrong Port in CORS or Client Config **What goes wrong:** WASM client gets CORS errors because the API CORS policy specifies a different port than the client actually runs on. **Why it happens:** `dotnet new blazorwasm` and `dotnet new webapi` assign random ports in `launchSettings.json`. Developer hardcodes one set of ports but the templates use different ones. **How to avoid:** After project creation, check `Properties/launchSettings.json` in BOTH projects. Align the CORS origin in the API with the actual client URL. Or set explicit ports in `launchSettings.json`. **Warning signs:** Browser console shows `Access-Control-Allow-Origin` errors. ### Pitfall 2: Missing `UseCors()` Middleware Order **What goes wrong:** CORS policy is registered in DI but `app.UseCors()` is called after `app.MapControllers()`, causing CORS headers to not be applied. **Why it happens:** ASP.NET Core middleware order matters. CORS must run before routing/endpoints. **How to avoid:** Call `app.UseCors("PolicyName")` before `app.MapControllers()` and before `app.UseAuthorization()`. **Warning signs:** CORS errors despite correct policy configuration. ### Pitfall 3: IL Trimming Warnings on Publish **What goes wrong:** `dotnet publish` for the WASM project produces IL trimmer warnings about types that may be removed. **Why it happens:** Blazor WASM uses IL trimming in Release mode. Types used only via reflection (JSON serialization) can be trimmed away. **How to avoid:** For Phase 1, the surface area is tiny (one DTO). Verify `dotnet publish` completes without warnings. In later phases, use `[JsonSerializable]` source generators. **Warning signs:** `dotnet publish` output contains lines with `IL2xxx` warning codes. ### Pitfall 4: Solution File Path Issues **What goes wrong:** `dotnet build` from repo root fails because `ChatAgent.sln` references projects with wrong relative paths. **Why it happens:** Solution was created in a subdirectory, or projects were moved after being added. **How to avoid:** Create the solution at repo root, add projects using their paths relative to the sln file location. **Warning signs:** `The project file could not be found` errors. ### Pitfall 5: WASM Project Serves on HTTPS But Certificate Not Trusted **What goes wrong:** Browser shows security warning when accessing WASM app, blocking API calls. **Why it happens:** Dev HTTPS certificate not installed or not trusted. **How to avoid:** Run `dotnet dev-certs https --trust` once before starting development. Alternatively, use `--no-https` flag during project creation for Phase 1 simplicity and add HTTPS later. **Warning signs:** Browser shows "Your connection is not private" page. ## Code Examples ### Shared DTO (ChatAgent.Shared) ```csharp // Models/HealthResponse.cs // This is a shared model (DTO - Data Transfer Object). // It lives in the Shared project so both the Client and Api can use it // without duplicating the class definition. When the API returns this object, // the Client can deserialize it into the same type. namespace ChatAgent.Shared.Models; public class HealthResponse { /// /// Server health status (e.g., "healthy"). /// public string Status { get; set; } = string.Empty; /// /// Server timestamp -- proves we're getting a live response, not a cached one. /// public DateTime Timestamp { get; set; } } ``` ### WASM Program.cs (Client) ```csharp // Program.cs -- Blazor WASM application entry point // This is where the WebAssembly application starts. // WebAssemblyHostBuilder configures: // 1. Root components (what Razor components to render) // 2. Services (dependency injection container) // 3. Configuration (reads from wwwroot/appsettings.json) using Microsoft.AspNetCore.Components.Web; using Microsoft.AspNetCore.Components.WebAssembly.Hosting; using ChatAgent.Client; using ChatAgent.Client.Services; var builder = WebAssemblyHostBuilder.CreateDefault(args); // Add("#app") tells Blazor to render the App component // inside the
element in wwwroot/index.html. builder.RootComponents.Add("#app"); // HeadOutlet manages elements (title, meta tags) from Razor components. builder.RootComponents.Add("head::after"); // Read the API base URL from configuration (wwwroot/appsettings.json). // This file is PUBLIC -- never put secrets here. var apiBaseUrl = builder.Configuration["ApiBaseUrl"] ?? "https://localhost:7100"; // Register ChatApiClient as a typed HttpClient. // AddHttpClient uses IHttpClientFactory internally, which: // - Manages HttpClient lifetimes properly (avoids socket exhaustion) // - Allows per-client configuration (base address, headers) // - Makes the service injectable via constructor DI builder.Services.AddHttpClient(client => { client.BaseAddress = new Uri(apiBaseUrl); }); await builder.Build().RunAsync(); ``` ### API Program.cs (Server) ```csharp // Program.cs -- ASP.NET Core Web API entry point // This is the backend server that the Blazor WASM client talks to. // It will eventually proxy OpenAI calls and manage JSON file storage. // In Phase 1, it only serves a health check endpoint. var builder = WebApplication.CreateBuilder(args); // AddControllers() registers MVC controller services. // We use Controllers (not Minimal API) for explicit structure (D-05). builder.Services.AddControllers(); // CORS (Cross-Origin Resource Sharing) configuration. // The WASM client runs on a different origin (different port), // so the browser blocks requests unless the server explicitly allows them. builder.Services.AddCors(options => { options.AddPolicy("AllowBlazorClient", policy => { // TODO: Read from configuration instead of hardcoding policy.WithOrigins("https://localhost:5200") .AllowAnyHeader() .AllowAnyMethod(); }); }); var app = builder.Build(); // Middleware order matters in ASP.NET Core. // CORS must be applied before routing and authorization. app.UseCors("AllowBlazorClient"); app.UseAuthorization(); // MapControllers() discovers all [ApiController] classes // and maps their [HttpGet], [HttpPost], etc. attributes to routes. app.MapControllers(); app.Run(); ``` ## State of the Art | Old Approach | Current Approach | When Changed | Impact | |--------------|------------------|--------------|--------| | Hosted Blazor WASM template (Client+Server+Shared in one template) | Separate projects added to a manual solution | .NET 8 (Nov 2023) | Must create three projects manually; no `--hosted` flag | | `Newtonsoft.Json` for serialization | `System.Text.Json` built into .NET | .NET Core 3.0+ | No extra dependency; source generators for trim safety | | `HttpClient` registered directly as Singleton/Scoped | `IHttpClientFactory` via `AddHttpClient` | .NET Core 2.1+ | Proper socket management; typed clients for clean architecture | ## .NET Version Decision (Claude's Discretion) **Recommendation: Target .NET 9 (`net9.0`)** Rationale: - CLAUDE.md explicitly specifies .NET 9 as the target - .NET 9.0.312 SDK is installed on this machine - .NET 10 SDK (10.0.201) is also available but is not yet at LTS; using it in a tutorial project risks encountering preview-stage breaking changes - All recommended packages (OpenAI 2.9.1, Markdig 1.1.1, MudBlazor 9.2.0) are confirmed compatible with .NET 9 - The `blazorwasm` template with `--framework net9.0` is verified working ## Validation Architecture ### Test Framework | Property | Value | |----------|-------| | Framework | None yet (greenfield) | | Config file | None -- see Wave 0 | | Quick run command | `dotnet build ChatAgent.sln` | | Full suite command | `dotnet build ChatAgent.sln && dotnet publish src/ChatAgent.Client/ChatAgent.Client.csproj -c Release --nologo` | ### Phase Requirements -> Test Map | Req ID | Behavior | Test Type | Automated Command | File Exists? | |--------|----------|-----------|-------------------|-------------| | CODE-01 | Every file has inline comments explaining Blazor concepts | manual | Manual review of all `.cs` and `.razor` files | N/A | | CODE-02 | Phase introduces one concept incrementally | manual | Manual review -- Phase 1 concept = "solution structure and project boundaries" | N/A | | (SC-1) | `dotnet run` starts both projects without errors | smoke | `dotnet build ChatAgent.sln` (build verification) | N/A -- Wave 0 | | (SC-2) | WASM client reaches API server (CORS working) | integration | Manual -- start both projects, verify health endpoint from client UI | N/A | | (SC-3) | Shared models referenced by both projects | build | `dotnet build ChatAgent.sln` (compile-time check) | N/A -- Wave 0 | | (SC-4) | `dotnet publish` completes with no IL trim warnings | smoke | `dotnet publish src/ChatAgent.Client/ChatAgent.Client.csproj -c Release --nologo 2>&1 \| grep -c "warning IL"` | N/A -- Wave 0 | | (SC-5) | Every file contains inline comments | manual | Manual review | N/A | ### Sampling Rate - **Per task commit:** `dotnet build ChatAgent.sln` - **Per wave merge:** `dotnet build ChatAgent.sln && dotnet publish src/ChatAgent.Client/ChatAgent.Client.csproj -c Release --nologo` - **Phase gate:** Full build + publish clean + manual CORS verification ### Wave 0 Gaps - No formal test project needed in Phase 1 -- the verification is build success + publish success + manual CORS check - The `dotnet build` and `dotnet publish` commands serve as the automated validation - CODE-01 and CODE-02 are inherently manual-review requirements ## Environment Availability | Dependency | Required By | Available | Version | Fallback | |------------|------------|-----------|---------|----------| | .NET 9 SDK | All projects | Yes | 9.0.312 | -- | | .NET 10 SDK | Not required | Yes | 10.0.201 | -- | | `dotnet new blazorwasm` template | Client project | Yes | Included in SDK | -- | | `dotnet new webapi` template | API project | Yes | Included in SDK | -- | | `dotnet new classlib` template | Shared project | Yes | Included in SDK | -- | | HTTPS dev certificate | Local HTTPS | Unknown | -- | Use `--no-https` flag or run `dotnet dev-certs https --trust` | **Missing dependencies with no fallback:** None. **Missing dependencies with fallback:** - HTTPS dev certificate status is unknown -- if untrusted, can use HTTP for Phase 1 local dev or run the trust command. ## Open Questions 1. **Port numbers for local development** - What we know: Templates assign ports in `launchSettings.json` which vary per creation - What's unclear: Exact ports until projects are actually created - Recommendation: After project creation, inspect both `launchSettings.json` files and align CORS config with actual client URL. Consider setting explicit predictable ports (e.g., Client: 5200, API: 7100). 2. **HTTPS vs HTTP for Phase 1 local dev** - What we know: HTTPS requires a trusted dev certificate - What's unclear: Whether the dev cert is already trusted on this machine - Recommendation: Attempt HTTPS first. If certificate issues arise, fall back to HTTP for Phase 1 (add `--no-https` and use `http://` URLs). HTTPS can be re-enabled in later phases. ## Project Constraints (from CLAUDE.md) - **Tech stack**: .NET / C# / Blazor WebAssembly -- non-negotiable - **LLM provider**: OpenAI GPT API (not used in Phase 1, but architecture must support it) - **Storage**: JSON files on local disk (not used in Phase 1, but architecture must support it) - **Architecture**: WASM client + backend API (API key stays server-side) - **Code style**: Every Blazor concept introduced must have inline comments explaining what it does and why - **GSD Workflow**: Use GSD entry points for all file changes ## Sources ### Primary (HIGH confidence) - .NET 9 SDK verified installed: `dotnet --list-sdks` shows 9.0.312 - `blazorwasm` template verified: `dotnet new list blazorwasm` confirms availability - `webapi --use-controllers` flag verified: `dotnet new webapi --help` confirms option exists - Template output verified: Created temporary projects to confirm `Program.cs` structure, `.csproj` contents, and default file layout ### Secondary (MEDIUM confidence) - `.planning/research/ARCHITECTURE.md` -- pre-existing project research on WASM/API split patterns - `.planning/research/STACK.md` -- pre-existing stack research with NuGet-verified versions - `.planning/research/PITFALLS.md` -- pre-existing pitfalls research from official GitHub issues ### Tertiary (LOW confidence) - None -- all findings verified against installed SDK and official templates ## Metadata **Confidence breakdown:** - Standard stack: HIGH -- verified against installed SDK and templates - Architecture: HIGH -- three-project structure is well-documented Microsoft pattern; template output confirmed - Pitfalls: HIGH -- CORS ordering, port mismatch, and IL trimming are well-known ASP.NET Core issues documented in official sources **Research date:** 2026-03-27 **Valid until:** 2026-04-27 (stable -- .NET 9 is mature, templates are not changing)