ASP.NET Core Web APIs · Capstone
Capstone: A Booking API in ASP.NET Core
Design an ASP.NET Core API for clinic appointment booking: structure, validation, auth, data consistency, testing and operations.
Scenario
A network of clinics needs an appointment booking API. Patients book, reschedule and cancel appointments through a mobile app; staff manage clinicians' schedules from a web app; a partner insurance service queries appointment status. Peak load is Monday mornings, when thousands of patients try to book the same few slots.
Requirements
- A slot can never be double-booked.
- Patients see only their own appointments; staff only their clinic's.
- Errors are consistent and documented for the mobile and partner teams.
- The API keeps working when the SMS reminder provider is down.
Constraints
- .NET 10, ASP.NET Core minimal APIs, EF Core with PostgreSQL, Redis available.
- 4 instances behind a load balancer on Kubernetes; an OpenID Connect identity provider issues JWTs.
Deliverables
- API structure: endpoints, route groups and project layout.
- Request pipeline: validation, authentication, authorization, errors.
- Data and consistency: preventing double booking, concurrency, side effects.
- Testing.
- Operations: configuration, caching, rate limiting, health, observability.
Hints
- Two patients clicking the same slot at the same moment is the core case.
- SMS reminders are a side effect, not part of the booking transaction.
Draft each deliverable below before revealing the model answer and rubric.
Further reading: ASP.NET Core fundamentals · Handling concurrency conflicts
Deliverables
Describe the API structure.
Model answer
- Route groups per area:
/api/appointments(patients),/api/clinics/{clinicId}/schedules(staff),/api/partner/appointments(insurance), each in its ownMap...Endpoints()extension method. - Feature folders (Appointments, Schedules, Partners) with endpoints, request/response records and services; EF Core
DbContextand migrations in an infrastructure folder. - TypedResults everywhere and OpenAPI generated from them for the mobile and partner teams; versioned routes or headers for breaking changes.
Rubric
Look for route groups by audience, feature organisation, typed results feeding OpenAPI and a versioning plan.
Describe the request pipeline.
Model answer
AddValidation()with public request records and data annotations; business rules (slot in the future, within opening hours) in services.AddJwtBearerfor the identity provider; a fallback policy requiring authentication; policiesPatient,ClinicStaff(clinic claim) andPartner(scope); resource-based checks so patients only touch their own appointments and staff only their clinic's.AddProblemDetails+ anIExceptionHandlermapping domain exceptions (slot taken 409, not found 404, too late to cancel 422); generic 500s without details.
Rubric
Strong answers combine automatic validation, secure-by-default authorization with resource checks, and one ProblemDetails error format.
Describe data and consistency.
Model answer
- A unique index on (clinician_id, slot_start) for active appointments makes double booking impossible at the database level; the losing insert is caught and returned as 409 "slot taken".
- Rescheduling uses a concurrency token on the appointment, returning 409 on conflicts.
- The booking and an outbox row for the SMS reminder are saved in one transaction; a background worker (with its own DI scope) sends reminders with retries, so SMS outages don't affect booking.
Rubric
Look for a database constraint (not only an application check), concurrency tokens, and an outbox for side effects.
Describe the testing approach.
Model answer
WebApplicationFactory<Program>integration tests against PostgreSQL in a container (Testcontainers), with a fakeTimeProviderand a fake SMS sender.- Cases: two concurrent bookings of one slot (exactly one 201), cross-patient and cross-clinic access (403/404), cancellation deadlines, validation errors in ProblemDetails format.
- Contract tests or OpenAPI diff checks for the partner API.
Rubric
Strong answers test the full pipeline against the real database engine and cover concurrency and authorization boundaries.
Describe operations.
Model answer
- Options validated with
ValidateOnStart; secrets from the platform's secret store. - Output caching (Redis store) for public clinic and slot listings with short expiry, evicted by tag when schedules change; booking endpoints not cached.
- Partitioned rate limiting per patient for booking attempts and per partner for the partner API; 429 with Retry-After.
- Liveness without dependencies, readiness checking PostgreSQL; OpenTelemetry traces and metrics, structured logs with trace IDs; graceful shutdown within the pod's grace period.
Rubric
Look for validated configuration, careful caching, per-caller rate limits, correct health checks and observability.
Open in the app to mark lessons complete, take notes and track your progress.