Startup, Configuration, Session State & Lifetime
The per-user values the TicketOps Console
kept reaching for (operator, tenant, theme, client profile, the selected ticket) move out of static fields
into a session-scoped SessionContext that is created once per browser session by AppComposition
and constructor-injected into the Form and every service. A Session diagnostics page shows, side by
side, what is shared by the whole app (configuration read once per process, the shared ticket store, a
process counter) and what belongs to just this session. A static-state audit is applied to the project
(docs/StaticStateAudit.md).
Nothing here is deployed anywhere; it is a plain Wisej.NET 4 project that runs on your machine.
Run it
cd "LearnWisej-Samples/Production Architecture Deep Dive\Module 2\TicketOps"
dotnet run -f net10.0 --urls http://localhost:5102
Then open http://localhost:5102. (Visual Studio: open TicketOps.slnx, press F5 — the port is in
Properties/launchSettings.json.)
Requirements: .NET 10 SDK, the Wisej-4 4.1.0 NuGet package.
What to try in the Session diagnostics window
The Application panel (blue) is identical in every session; the This session panel (green) comes
from the injected SessionContext. Below them: the per-session selectors and the open tickets of this
session's tenant.
| Action | What you should see |
|---|---|
| change operator / tenant / theme, Apply to this session | the green panel and the window caption update; the grid reloads for the tenant; status ● Signed in as Bob Chen · Contoso. A theme restyles this tab only |
| pick a tenant the operator does not belong to (e.g. Bob Chen → Fabrikam), Apply | orange banner Bob Chen is not a member of Fabrikam.; the tenant does not change |
| select a ticket row | Ticket 1003 selected for this session.; green panel Selected ticket = #1003 |
| ↻ | both panels re-read (use it in the other tab during the two-tab test) |
The two-tab test (lab step 9: "run two sessions and confirm they don't leak state into each other")
Open http://localhost:5102 in a second browser tab. Each tab is its own Wisej.NET session — like a second launch of a desktop app. Call them A and B.
| Step | In tab A | Then in tab B | Expected |
|---|---|---|---|
| 1 | read Session id | read Session id | different ids; Active sessions = 2 in both after ↻ |
| 2 | operator → Bob Chen, Apply to this session | press ↻ | A: User = Bob Chen. B: User = Alice Rivera — no leak |
| 3 | operator Alice Rivera, tenant Fabrikam, Apply | press ↻ | A's grid shows the 3 Fabrikam tickets. B still shows Contoso — no leak |
| 4 | theme → Material-3, Apply | look | only A restyles — no leak (per-session Application.Theme, not the global LoadTheme) |
| 5 | click ticket 2001 | press ↻ | A: Selected ticket = #2001. B: (none) — no leak |
| 6 | compare the blue panels | — | identical: the shared, read-only settings both tabs must see |
| 7 | press F5 in A | — | Wisej.NET keeps the session: the same Session id |
Where things live
TicketOps/
├─ Views/SessionDiagnostics the screen: injected SessionContext; thin handlers; ShowResult / ReportFailure
├─ Controls/StatusBanner reusable "● state" + banner UserControl (display only)
├─ Services/
│ ├─ SessionContext.cs ONE PER SESSION: session id, user, tenant, theme, client profile, selected ticket
│ ├─ ISessionService / SessionService.cs sign-in, tenant switch (membership rule), theme choice, ticket selection
│ └─ ITicketService / TicketService.cs tenant-filtered ticket list
├─ Domain/ UserAccount (IsMemberOf rule), Ticket, OperationResult
├─ Data/
│ ├─ SharedTicketStore.cs PROCESS-WIDE, thread-safe ticket store (stands in for the database)
│ ├─ InMemoryTicketRepository.cs per-session door to the shared store
│ └─ IUserDirectory / InMemoryUserDirectory.cs fake user store
├─ Infrastructure/
│ ├─ AppComposition.cs who gets what: one object graph per session; where SessionContext is born
│ ├─ ProcessScope.cs the "once per process" moment: Lazy<AppSettings>
│ ├─ AppSettings.cs appsettings.json → immutable object (the deployable contract)
│ ├─ SharedCounters.cs the one mutable shared value (Interlocked)
│ ├─ WisejRuntimeInfo.cs IRuntimeInfo over Application.* (SessionCount, Configuration, Theme, ActiveProfile)
│ ├─ ThemeCatalog.cs builds a per-session ClientTheme from the framework's embedded theme JSON
│ └─ ILog.cs / ActivityLog.cs logging (one log per session, written to the server console)
├─ Diagnostics/ DiagnosticsService, DiagnosticsSnapshot, IDiagnosticsService, IRuntimeInfo
├─ Resources/Strings.cs safe user-facing messages
├─ docs/ StaticStateAudit.md · ProductionReadinessNote.md · ConfigurationContract.md
├─ appsettings.json the application-wide settings (read once per process)
├─ Default.json Wisej.NET config: startup, theme, sessionTimeout (1200 s)
├─ Program.cs Wisej.NET session entry point → AppComposition (once per session)
└─ Startup.cs Kestrel host (app.UseWisej()) — once per process