A .NET 8 ASP.NET Core Web API that records check-in and check-out events triggered when users scan QR codes with their mobile phones.
Point A (Check-In) Point B (Check-Out)
┌──────────────┐ ┌──────────────┐
│ QR Code │ │ QR Code │
│ locationId= │ │ locationId= │
│ loc123 │ │ loc123 │
│ checkType= │ │ checkType= │
│ checkin │ │ checkout │
└──────┬───────┘ └──────┬───────┘
│ user scans │ user scans
▼ ▼
┌──────────────────────────────────────────────────────┐
│ Browser (Mobile Phone) │
│ │
│ 1. Opens scanner.html?locationId=loc123& │
│ checkType=checkin │
│ │
│ 2. Reads UUID from localStorage │
│ • If missing or >2 h old → generate new UUID │
│ • Same phone = same UUID for up to 2 hours │
│ • Different phone = different UUID │
│ │
│ 3. User taps "Confirm & Submit" │
│ POST /api/checkin { deviceUuid, locationId, │
│ checkType, timestamp } │
└──────────────────────────────────────────────────────┘
│ │
▼ ▼
┌──────────────────────────────────────────────────────┐
│ ASP.NET Core Web API │
│ • Validates payload │
│ • Stamps ServerTimestamp = DateTime.UtcNow │
│ • Saves to MS SQL via Entity Framework │
└──────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────┐
│ MS SQL – CheckInRecords table │
│ Id | DeviceUuid | LocationId | CheckType | … │
│ 1 | a1b2-… | loc123 | checkin | … │
│ 2 | a1b2-… | loc123 | checkout | … │
└──────────────────────────────────────────────────────┘
QrCheckInSystem/
├── QrCheckInSystem.sln
├── database_setup.sql ← Manual SQL alternative to EF migrations
└── QrCheckInSystem.Api/
├── Controllers/
│ ├── CheckInController.cs ← POST /api/checkin, GET /api/checkin/…
│ └── QrCodeController.cs ← GET /api/qrcode (returns PNG)
├── Data/
│ ├── AppDbContext.cs
│ └── Migrations/
│ ├── 20240101000000_InitialCreate.cs
│ └── AppDbContextModelSnapshot.cs
├── Models/
│ ├── CheckInRecord.cs ← EF entity
│ └── Dtos.cs ← Request/response DTOs
├── Services/
│ └── CheckInService.cs ← Business logic
├── wwwroot/
│ └── scanner.html ← Mobile browser scanner page
├── Program.cs
├── appsettings.json
└── appsettings.Development.json
| Tool | Version |
|---|---|
| .NET SDK | 8.0+ |
| MS SQL Server | 2019+ |
| dotnet-ef (CLI) | 8.0+ |
Install the EF CLI tool once:
dotnet tool install --global dotnet-efEdit appsettings.Development.json (or set environment variable):
{
"ConnectionStrings": {
"DefaultConnection": "Server=localhost,1433;Database=QrCheckInDb_Dev;User Id=sa;Password=YourStrong@Password;TrustServerCertificate=True;"
},
"App": {
"PublicBaseUrl": "http://localhost:5000"
}
}Tip: For Windows Auth use
Trusted_Connection=True;instead ofUser Id/Password.
cd QrCheckInSystem.Api
dotnet ef database updateOr use the manual SQL script:
sqlcmd -S localhost -i ../database_setup.sqldotnet run --project QrCheckInSystem.ApiThe API starts on http://localhost:5000 (HTTP) and https://localhost:5001 (HTTPS).
Open in browser or use curl:
# Check-in QR for Point A
GET http://localhost:5000/api/qrcode?locationId=loc123&checkType=checkin
# Check-out QR for Point B
GET http://localhost:5000/api/qrcode?locationId=loc123&checkType=checkout
Print these PNG images and place them at Points A and B.
curl -X POST http://localhost:5000/api/checkin \
-H "Content-Type: application/json" \
-d '{
"deviceUuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"locationId": "loc123",
"checkType": "checkin",
"timestamp": "2024-06-01T09:00:00Z"
}'Records a check-in or check-out event.
Request body:
{
"deviceUuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"locationId": "loc123",
"checkType": "checkin",
"timestamp": "2024-06-01T09:00:00Z"
}Response 201:
{
"success": true,
"message": "Successfully recorded checkin for location loc123.",
"data": {
"recordId": 1,
"deviceUuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"locationId": "loc123",
"checkType": "checkin",
"serverTimestamp": "2024-06-01T09:00:01.234Z"
}
}Returns all events for a specific device UUID.
Returns all events at a location. Optional UTC datetime filters:
from– include records at or after this timeto– include records at or before this time
Returns a PNG image of the QR code to print. The QR encodes:
https://<host>/scanner?locationId=loc123&checkType=checkin
Returns the scanner URL as JSON (useful if you want to render the QR client-side).
| Scenario | Behaviour |
|---|---|
| First visit on Phone A | New UUID generated, stored in localStorage, expires in 2 h |
| Phone A re-scans within 2 h | Same UUID returned — check-in and check-out are correlated |
| Phone A re-scans after 2 h | New UUID generated (new logical session) |
| Phone B scans | Different UUID (each device has isolated localStorage) |
| User clears browser data | New UUID on next visit |
| User opens incognito tab | New UUID (incognito storage is isolated) |
The key logic lives in scanner.html → getOrCreateDeviceUuid():
const UUID_TTL_MS = 2 * 60 * 60 * 1000; // 2 hours
function getOrCreateDeviceUuid() {
const stored = localStorage.getItem('qr_device_uuid');
const expiry = parseInt(localStorage.getItem('qr_device_uuid_expiry') || '0');
if (stored && expiry && Date.now() < expiry) return stored; // still valid
const uuid = crypto.randomUUID();
localStorage.setItem('qr_device_uuid', uuid);
localStorage.setItem('qr_device_uuid_expiry', Date.now() + UUID_TTL_MS);
return uuid;
}Available at: http://localhost:5000/swagger
- Set
App:PublicBaseUrlto your HTTPS domain so QR codes encode the correct URL - Store the connection string in an environment variable or Azure Key Vault
- Tighten CORS policy in
Program.csto your specific frontend domain - Add authentication (API key / JWT) to the
/api/checkinendpoint - Switch to HTTPS and redirect HTTP → HTTPS
- Consider rate-limiting per IP to prevent spam
- Remove
db.Database.Migrate()fromProgram.csand run migrations as a deploy step