Complete record of the Stage 1 build session · Created 2026-04-28
I need a workable ASP.NET Core MVC project with these features in stage 1:
Building the full project in 8 tracked tasks:
dotnet new mvc -n GuyStore --framework net9.0
dotnet add package Microsoft.AspNetCore.Identity.EntityFrameworkCore --version 9.0.4
dotnet add package Microsoft.EntityFrameworkCore.SqlServer --version 9.0.4
dotnet add package Microsoft.EntityFrameworkCore.Design --version 9.0.4
dotnet add package Microsoft.AspNetCore.SignalR --version 1.1.0
dotnet add package Newtonsoft.Json --version 13.0.3
libman install @microsoft/signalr@latest # JS client
# Set ConnectionStrings:DefaultConnection in appsettings.json to your SQL Server instance
dotnet ef migrations add InitialCreate
dotnet ef database update
| File | Purpose |
|---|---|
Models/ApplicationUser.cs | Extends IdentityUser - adds FirstName, LastName, IsApproved, RegisteredAt, ApprovedAt, ApprovedBy |
Models/Product.cs | Name, Description, Price, StockQuantity, Category, IsPublished, CreatedBy |
Models/Order.cs | OrderStatus enum + full Order model with PO fields |
Models/OrderItem.cs | Order line items (ProductId, ProductName snapshot, UnitPrice, Quantity) |
Models/CartItem.cs | Session-only cart line item |
Models/ViewModels/* | Login, Register, ChangePassword, UserManagement, Product, Shop, Checkout, OrderSummary |
Data/ApplicationDbContext.cs | IdentityDbContext<ApplicationUser> + Products, Orders, OrderItems DbSets |
Data/DataSeeder.cs | Seeds 3 roles, 2 demo accounts, 8 products at startup |
| Role | What they can do |
|---|---|
| Administrator | Full access to all admin pages (products, orders, accounts, notifications) |
| AccountManagement | Admin panel - accounts section only (approve/deny/unlock/change password) |
| Registered | Assigned to every user. Required to have IsApproved=true to log in. Shop + cart access. |
New user registrations create accounts with IsApproved = false. Login is blocked with a clear message until an AccountManagement user approves the account.
Server: Hubs/NotificationHub.cs - empty hub (no client→server calls in Stage 1). Admin broadcasts via IHubContext<NotificationHub> injected into NotificationsController.
Client: wwwroot/js/notifications.js - auto-connects with reconnect policy on every page (both public and admin layouts). Renders Bootstrap toast overlays in the bottom-right corner.
Admin UI: Admin → Send Notification page with message field, type selector (info/warning/danger/success), and pre-filled quick templates ("System closing in 5 minutes", etc.).
// How admin broadcasts:
await _hub.Clients.All.SendAsync("ReceiveNotification", new {
message, type, timestamp
});
| Theme | File | Primary Color | Feel |
|---|---|---|---|
| Corporate Blue | themes/corporate.css | #1a56db | Professional, light background |
| Dark Slate | themes/dark.css | #6366f1 | Modern dark, indigo accents |
Theme is stored in a cookie (SiteTheme) that lasts 1 year. Switch via the 🎨 palette dropdown in the navbar. Works on both the public site and the admin panel - both layouts read the same cookie.
Adding a third theme in Stage 2: create the CSS file, add the option to both layout dropdowns, and add the theme name to ThemeController.validThemes.
| Feature | Location |
|---|---|
| Product list (all including drafts) | Admin → Products → Index |
| Create / Edit product | Admin → Products → Create / Edit |
| Publish / Unpublish toggle | One-click button on product list row |
| Delete product | With confirmation dialog |
| Public product listing | Shop → Index (published only, search + category filter) |
| Product detail page | Shop → Detail (add-to-cart if logged in) |
8 products seeded at startup (7 published, 1 draft "Webcam 1080p").
Cart: CartService stores items in ASP.NET session as JSON. Quantity update and remove on cart page. Cart count badge shown in navbar.
Checkout: Only available to logged-in users. Requires PO Number, billing company/address, contact name/phone. 10% tax applied. Order placed as OrderStatus.Pending.
Order lifecycle: Pending → Confirmed → Processing → Shipped → Delivered (or Cancelled at any point).
Admin orders: List with status filter tabs. Detail view shows items, billing info, PO number, contact. Status can be updated with a dropdown + save.
My Orders: Logged-in users can view their own order history at Cart → My Orders.
All admin controllers are in Areas/Admin/ with [Area("Admin")] attribute. The admin layout (_AdminLayout.cshtml) uses a fixed left sidebar on desktop that collapses to a button row on mobile.
Sidebar sections are conditionally rendered with @if (User.IsInRole("Administrator")). AccountManagement users only see the Users and Pending sections.
| Item | Value |
|---|---|
| Project location | C:\Guy\Notes\Claud Projects\Guy\GuyStore\ |
| Run command | dotnet run --launch-profile http |
| Admin account | [email protected] / Admin@123 |
| AccountMgr account | [email protected] / Acct@123 |
| Build result | 0 errors, 0 warnings |
| HTTP 200 verified | Yes - app starts and home page loads |
| Demo products | 8 seeded (7 published, 1 draft) |
| Themes | Corporate Blue, Dark Slate - switchable via navbar 🎨 |
See CLAUDE.md in the project root for the full extension guide.
Three separate test projects cover the full application stack: pure logic unit tests, server-side HTTP integration tests, and real-browser E2E tests driven by Playwright. All 97 tests pass against an in-memory database - no SQL Server or external services required.
| Project | Type | Tests | What it covers |
|---|---|---|---|
GuyStore.Tests.Unit |
Unit | 33 | CartService, CartItem model, ApplicationUser model, NotificationsController |
GuyStore.Tests.Integration |
Integration (HTTP) | 43 | AccountController, ShopController, ThemeController, CartController, Admin: Products / Orders / Accounts |
GuyStore.Tests.E2E |
E2E (Playwright) | 21 | Login/logout flows, shop browsing & search, full checkout, admin dashboard, theme switching |
GuyStore\ ← main app
GuyStore.Tests.Unit\ ← xUnit + Moq
GuyStore.Tests.Integration\ ← xUnit + WebApplicationFactory
GuyStore.Tests.E2E\ ← xUnit + Microsoft.Playwright
GuyStore.sln ← all four projects
Playwright needs a local Chromium binary. Run this once after the first build:
cd "C:\Guy\Notes\Claud Projects\Guy"
dotnet build GuyStore.Tests.E2E\GuyStore.Tests.E2E.csproj
pwsh GuyStore.Tests.E2E\bin\Debug\net9.0\playwright.ps1 install chromium
On Windows PowerShell (not pwsh) use powershell instead of pwsh.
This step is only needed once per machine.
From C:\Guy\Notes\Claud Projects\Guy:
# Run everything at once (all 97 tests)
dotnet test GuyStore.sln
# Run just one project
dotnet test GuyStore.Tests.Unit\GuyStore.Tests.Unit.csproj
dotnet test GuyStore.Tests.Integration\GuyStore.Tests.Integration.csproj
dotnet test GuyStore.Tests.E2E\GuyStore.Tests.E2E.csproj
# Run a single test by name (works for any project)
dotnet test GuyStore.Tests.Unit\GuyStore.Tests.Unit.csproj --filter "CartService_AddItem_SameProduct_IncrementsQuantity"
dotnet test GuyStore.Tests.Integration\GuyStore.Tests.Integration.csproj --filter "Login_WithValidCredentials_RedirectsToHome"
dotnet test GuyStore.Tests.E2E\GuyStore.Tests.E2E.csproj --filter "AdminLogin_SucceedsAndLandsOnHome"
InMemory database for all test types
Program.cs checks builder.Environment.IsEnvironment("Testing")
and registers EF Core's InMemory provider when true - no SQL Server needed.
Integration tests set this via WebApplicationFactory.UseEnvironment("Testing");
E2E tests pass it as WebApplicationOptions.EnvironmentName = "Testing".
The DB name includes a Guid so each test run gets a fresh store.
Dynamic port for E2E
The PlaywrightFixture binds Kestrel to
http://127.0.0.1:0 (port zero = OS chooses a free port),
then reads the actual address back via IServerAddressesFeature.
This avoids "port already in use" conflicts between test runs.
ApplicationName must match GuyStore
The E2E fixture creates a new WebApplication in the test process.
Without ApplicationName = "GuyStore" in
WebApplicationOptions, ASP.NET Core's MVC view engine uses the test
assembly name and can't locate the precompiled Razor views from GuyStore.dll,
causing every page to render a blank/error response.
Anti-forgery token helper (integration tests)
AuthHelper.GetAntiForgeryTokenAsync issues a GET to a login/form page,
extracts the __RequestVerificationToken hidden field via regex, and
adds it to every POST. Without this, all POST endpoints with
[ValidateAntiForgeryToken] return 400.
Bootstrap dropdown buttons are hidden by default
The theme switcher and logout button live inside Bootstrap
dropdown-menu elements (display: none).
E2E tests avoid clicking them directly; instead they call
form.submit() via page.EvaluateAsync
and use page.RunAndWaitForNavigationAsync to properly wait for the
resulting redirect before reading cookies.