ASP.NET Core 9 MVC B2B e-commerce platform with SignalR, Identity auth, and iOS companion app
# From the GuyStore\ folder
dotnet run --launch-profile http
# Starts at http://localhost:5090
Launch profiles (Properties/launchSettings.json):
| Profile | URL |
|---|---|
http | http://localhost:5090 |
https | https://localhost:7091 |
IIS Express | http://localhost:5099 |
Runs in-memory by default ("UseInMemoryDatabase": true in appsettings.json). Set to false and supply a SQL Server connection string to persist data across restarts.
| Password | Roles | |
|---|---|---|
| [email protected] | Admin@123 | Administrator, Registered |
| [email protected] | Acct@123 | AccountManagement, Registered |
/Theme/Customize)/Cart/Invoice/{id}, admin order detail)/Admin/Discounts): code, percent off, max uses, expiry; users apply a code on the cart page; discount is deducted from order total/Address CRUD pages; default address pre-fills the checkout billing form/Account/TwoFactor); 2FA code entry page enforced on next login; enable/disable toggle on profile/Admin/Reports) — 30-day revenue trend chart, top 5 products by revenue, order status breakdown, month-to-date revenue vs previous month/Admin/Import) — admin uploads a CSV file to create multiple orders in one operation/VoiceFeature/Translate) — speech input via Web Speech API in the user's chosen language, translated via MyMemory API (12 languages: en, zh-TW, zh-CN, ja, ko, fr, de, es, it, pt, vi, th), translated text read back via speech synthesis; translation history (last 10 entries)getDisplayMedia() or upload an image, add a message, submit to adminScreenFeedback model (message, image binary, status, admin reply) + EF migrationScreenFeedbackHub at /screenFeedbackHub — real-time admin notification + user reply delivery/Admin/Feedback) — list, filter, view screenshot, replyMyFeedback page shows submission history and admin replies#mainCanvas) — image, strokes, and crop overlay all rendered together, eliminating dual-canvas alignment bugs/api for the VoiceSmart iOS companion app (cookie-based auth, no AntiForgery)/Admin/PaymentMethods): name, code, PO# required flag, sort order, enable/disable, set default/Admin/Settings); EmailOrderConfirmation toggle controls whether order emails are sentEmailOrderConfirmation is disabledGET /api/products, GET /api/payment-methods, POST /api/ordersPOST /api/orders now accepts couponCode?, validates against DiscountCodes table, deducts discount from totalPaginatedList<T> gained a Create() overload for pre-built lists; four controllers newly paginated:_Pagination.cshtml's query-string passthroughGET /api/products and GET /api/feedback/mine now accept ?page=&pageSize= and return {page, pageSize, total, totalPages, items:[…]}CollectionView.Footer shows a "Load More" button when HasMore is trueDataSeeder.cs expanded to 30 products (15 Electronics, 8 Office, 7 Accessories) and 21 discount codes20260517041218_SeedPaginationTestData — idempotent IF NOT EXISTS … INSERT statements apply the same 22 new products and 21 discount codes to GuyStoreDbPaginatedListTests.cs extended with 4 tests for the synchronous Create() overload (93 unit tests total)AdminPaginationTests.cs (12 tests: Accounts, Discounts, Feedback, PaymentMethods pagination); ApiControllerTests.cs updated to assert paginated-envelope shape + pageSize clamping (139 integration tests total)PaginationTests.cs (6 browser tests: Shop + Admin Products + Admin Discounts page navigation)net9.0 xUnit project at iPhoneApp\VoiceSmart.Tests\; links source files from the iOS MAUI project directly; MainThread stub satisfies the one MAUI platform API; 19 ViewModel tests covering ShopViewModel and MyFeedbackViewModel pagination + SignalR reply logicGET /Passkey/Manage): give each passkey a friendly name, view creation and last-used dates, remove individual passkeysPasskeyCredentials table stores credential ID (base64url string), COSE public key, user handle, and rolling signature counter per credential; uses existing PasskeyCredential EF entityPasskeyService wraps IFido2 from the Fido2.AspNet v4.0.1 NuGet package; registration and authentication challenges are stored in ASP.NET Core session between Begin and Complete callsPasskeyController exposes 5 endpoints: BeginRegistration, CompleteRegistration, BeginAuthentication, CompleteAuthentication, Removewwwroot/js/passkey.js — registerPasskey() and signInWithPasskey() with base64url encode/decode helpers; no external JS library neededappsettings.json under the "Fido2" section (defaults to localhost); update ServerDomain and Origins when deploying to a real domain"UseInMemoryDatabase": false in appsettings.json switches EF Core from InMemory to SQL Server; run dotnet ef database update to apply the AddPasskeyCredentials migration
Route prefix: /api — cookie-based Identity auth, no AntiForgery tokens. Client logs in via POST /api/auth/login, receives a session cookie, and includes it in all subsequent requests via CookieContainer.
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| POST | /api/auth/login |
— | {email, password} → {success, userName, …} + sets session cookie |
| POST | /api/auth/logout |
Required | Signs out |
| GET | /api/auth/me |
— | {authenticated, userName} |
| POST | /api/feedback |
Required | Multipart: message, pageUrl, image? (≤8 MB, JPEG/PNG/GIF/WebP) — pushes SignalR on receipt |
| GET | /api/feedback/mine?page=1&pageSize=10 |
Required | User's own feedback as paginated envelope {page, pageSize, total, totalPages, items:[…]} (pageSize clamped 1–50) |
| GET | /api/products?page=1&pageSize=20 |
Required | Published products as paginated envelope {page, pageSize, total, totalPages, items:[…]} (pageSize clamped 1–100) |
| GET | /api/payment-methods |
Required | All active payment methods as JSON array |
| POST | /api/orders |
Required | {paymentMethodId, poNumber?, couponCode?, billingCompany, billingAddress, billingCity, billingState, billingZip, contactName, contactPhone, notes?, items:[{productId,quantity}]} → {orderId, orderNumber, total, discountAmount, status} |
All voice pages are under /VoiceFeature/ and are accessible via the Voice nav dropdown (authenticated users only).
| Page | Route | Description |
|---|---|---|
| Voice Camera | /VoiceFeature/Camera |
Say a trigger word (language-aware) to capture the webcam. Supports English, Traditional Chinese, Simplified Chinese, Taiwanese Hokkien. |
| Voice Translation | /VoiceFeature/Translate |
Speak in one language, see and hear the translation. 12 languages: en, zh-TW, zh-CN, ja, ko, fr, de, es, it, pt, vi, th. Powered by MyMemory API (free tier). Translation history (last 10). |
| Screen Capture | /VoiceFeature/ScreenCapture |
Capture a screenshot or upload an image, crop or annotate with freehand drawing, add a message, and submit as feedback to the admin. |
| My Feedback | /VoiceFeature/MyFeedback |
View all feedback submissions and admin replies. Replies delivered in real time via SignalR. |
Voice camera trigger words: voice-camera.js — TRIGGERS map keyed by BCP-47 language code. Taiwanese Hokkien uses zh-TW recognition with a reduced word list (only words with Mandarin-compatible phonetics).
| Hub | Route | Groups | Events |
|---|---|---|---|
| NotificationHub | /notificationHub |
all clients | ReceiveNotification |
| SupportHub | /supportHub |
admins, user_{userId} |
ReceiveMessage, ReceiveAdminReply |
| ScreenFeedbackHub | /screenFeedbackHub |
sfb_admins, sfb_{userId} |
NewFeedback, ReceiveFeedbackReply |
ImageCapture.grabFrame() returns black frames for display-media streams. Tracks were also stopped in a shared finally before the video rendered a frame.ImageCapture. Stream piped into a <video>; requestVideoFrameCallback guarantees first real frame before drawImage(). Tracks stopped only after drawing.
ScreenCapture.cshtml. Redirect to MyFeedback destroyed the page and its socket.wwwroot/js/screen-feedback.js — persistent hub loaded globally in _Layout.cshtml. On reply, dispatches CustomEvent("sfbReply") that any page can listen for.
Request.Host was localhost:5090 so RedirectToAction produced unreachable URLs.UseForwardedHeaders added as first middleware — rewrites Request.Host from X-Forwarded-Host.web.config with hostingModel="inprocess" — IIS loads app directly; no separate Kestrel port.
displayCanvas centered by flex, overlayCanvas centered by position:absolute. Browser rounding causes sub-pixel misalignment; drag events land in the wrong position.<video> not in the DOM; Chrome does not reliably deliver display-media frames. videoWidth can be 0 on the first requestVideoFrameCallback.#mainCanvas; image and overlay drawn together in drawAll(). Video appended to document.body (hidden) during capture; polls videoWidth > 0 up to 3 s before drawing.
web.config sets hostingModel="inprocess" — IIS loads the app into w3wp.exe directly; no Kestrel port conflict.UseForwardedHeaders is the first middleware in Program.cs — rewrites Request.Host from X-Forwarded-Host for correct redirect URL generation behind a reverse proxy. No-op under plain Kestrel.VoiceSmart iOS MAUI app is located at:
C:\Guy\Notes\Claud Projects\iPhoneApp\VoiceSmart\
See VoiceSmart\README.html for setup, features, and API details.
GuyStore Project Documentation — Last updated 2026-06-12 (Stage 7: Passkeys + UseInMemoryDatabase toggle)