Files
AgenticCode/.planning/phases/01-architecture-foundation/01-RESEARCH.md
2026-03-27 01:38:56 +00:00

25 KiB

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>

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 </user_constraints>

<phase_requirements>

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
</phase_requirements>

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:

# 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

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:

// 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;
    }

    /// <summary>
    /// 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.
    /// </summary>
    public async Task<HealthResponse?> GetHealthAsync()
    {
        return await _httpClient.GetFromJsonAsync<HealthResponse>("api/health");
    }
}

// Registration in Program.cs:
// AddHttpClient<T> registers a typed HttpClient with its own configuration.
// The base address points to the API server (different port during local dev).
builder.Services.AddHttpClient<ChatApiClient>(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:

// 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
{
    /// <summary>
    /// Health check endpoint. Returns server status and timestamp.
    /// Used by the WASM client to verify the API is reachable and CORS is working.
    /// </summary>
    [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:

// 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.

// 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<ChatApiClient>(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<T> 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)

// 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
{
    /// <summary>
    /// Server health status (e.g., "healthy").
    /// </summary>
    public string Status { get; set; } = string.Empty;

    /// <summary>
    /// Server timestamp -- proves we're getting a live response, not a cached one.
    /// </summary>
    public DateTime Timestamp { get; set; }
}

WASM Program.cs (Client)

// 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>("#app") tells Blazor to render the App component
// inside the <div id="app"> element in wwwroot/index.html.
builder.RootComponents.Add<App>("#app");

// HeadOutlet manages <head> elements (title, meta tags) from Razor components.
builder.RootComponents.Add<HeadOutlet>("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<T> 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<ChatApiClient>(client =>
{
    client.BaseAddress = new Uri(apiBaseUrl);
});

await builder.Build().RunAsync();

API Program.cs (Server)

// 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<T> .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)