A REST API for renting conference halls: search for available halls, book them, calculate the rental cost based on the time of day and selected services, and generate business reports.
- Business problem
- Tech stack
- Architecture
- Project structure
- Data model
- Pricing rules
- Getting started
- API overview
- Testing
- Possible improvements
A company rents conference halls to businesses. The API lets clients:
- manage halls (create, update, delete) and their additional services;
- search for halls that are free for a given period and capacity;
- book a hall and receive a full cost calculation;
- view business reports (revenue, service popularity, hall occupancy).
- .NET 10, ASP.NET Core Web API
- Entity Framework Core + SQLite (the provider is easily swappable)
- FluentValidation — input validation
- Swagger / OpenAPI (Swashbuckle) — interactive documentation
- xUnit + Moq — unit and integration tests
The solution follows Clean Architecture with four layers, so the business logic depends neither on the database nor on the web framework. Dependencies point inward (toward the Domain) — the Dependency Inversion Principle.
| Layer | Responsibility |
|---|---|
| Domain | Entities, domain rules, the pricing engine, domain exceptions. Depends on nothing. |
| Application | Use-case services, DTOs, validation, repository abstractions. Depends only on Domain. |
| Infrastructure | EF Core, repositories, database seeding. Implements Application's abstractions. |
| WebApi | Controllers, middleware, authentication, Swagger, composition root. |
ConferenceRooms/
├── src/
│ ├── ConferenceRooms.Domain/ # Entities, pricing engine, domain exceptions
│ │ ├── Common/ # Guard clauses
│ │ ├── Entities/ # Hall, AdditionalService, Booking, BookingServiceItem
│ │ ├── Enums/ # RatePeriod
│ │ ├── Exceptions/ # Domain exceptions (mapped to HTTP codes)
│ │ └── Pricing/ # IPricingRule, PricingEngine, Rules/
│ ├── ConferenceRooms.Application/ # Use cases, DTOs, validation, abstractions
│ │ ├── Abstractions/ # Service & repository interfaces
│ │ ├── Dtos/ # Request/response models
│ │ ├── Mapping/ # Entity <-> DTO mapping
│ │ ├── Services/ # HallService, BookingService, ReportService
│ │ └── Validation/ # FluentValidation validators
│ ├── ConferenceRooms.Infrastructure/ # EF Core, repositories, seeding
│ │ ├── Migrations/
│ │ └── Persistence/ # DbContext, Configurations/, Repositories/, Seeding/
│ └── ConferenceRooms.WebApi/ # Controllers, middleware, auth, Swagger
│ ├── Controllers/
│ ├── Middleware/ # Exception handling, API key
│ └── Security/ # ApiKeyOptions
└── tests/
└── ConferenceRooms.UnitTests/ # Unit, integration and end-to-end API tests
├── Api/ # End-to-end tests (WebApplicationFactory)
├── Infrastructure/ # Repository integration tests
├── Pricing/ # Pricing engine unit tests
└── Services/ # Booking service unit tests (Moq)
AdditionalService and BookingServiceItem are owned by Hall and Booking
respectively (cascade delete). A Booking references a hall by HallId (a plain
column, not an enforced foreign key), and its service items are snapshots
taken at booking time.
- Pricing engine (Strategy pattern). The rate depends on overlapping
time-of-day periods, so a booking is split into segments at rate boundaries and
each segment is charged at its own rate. Each rate is a separate strategy
(
IPricingRule), so new rates can be added without changing the engine. - Service price snapshots: a booking copies each service's name and price at booking time, so later price changes never affect historical bookings or reports.
- Soft delete for halls, enforced by a global EF query filter.
- Domain exceptions mapped to HTTP status codes (404 / 409 / 400) by a single error-handling middleware; unexpected errors return 500 without leaking details.
- Repository + Unit of Work over EF Core; Dependency Injection throughout.
- API key authentication with a fixed-time comparison.
The base hourly price is adjusted by the time of day. Overlapping periods are resolved by priority (Peak overrides Standard):
| Period | Time | Adjustment |
|---|---|---|
| Morning | 06:00–09:00 | −10% |
| Standard | 09:00–18:00 | base price |
| Peak | 12:00–14:00 | +15% |
| Evening | 18:00–23:00 | −20% |
Total cost = hall cost (summed per segment) + selected services. Bookings must fall within the operating hours 06:00–23:00.
- .NET 10 SDK
dotnet run --project src/ConferenceRooms.WebApiThe SQLite database is created and seeded (Halls A, B, C) automatically on first run. Open the URL shown in the console — the root redirects to Swagger.
All /api/* endpoints require an API key in the X-Api-Key header. The demo key
is set in appsettings.json:
X-Api-Key: dev-secret-key-change-me
In Swagger, click Authorize and paste the key.
For production the key should come from environment variables or a secret store, not from
appsettings.json.
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/halls |
List all halls |
| GET | /api/halls/{id} |
Get a hall |
| POST | /api/halls |
Create a hall |
| PUT | /api/halls/{id} |
Update a hall |
| DELETE | /api/halls/{id} |
Soft-delete a hall |
| POST | /api/halls/{id}/services |
Add a service to a hall |
| DELETE | /api/halls/{id}/services/{serviceId} |
Remove a service |
| GET | /api/bookings/available |
Search available halls |
| POST | /api/bookings |
Create a booking (returns full cost breakdown) |
| GET | /api/reports/revenue |
Revenue report |
| GET | /api/reports/popular-services |
Service popularity report |
| GET | /api/reports/occupancy |
Hall occupancy report |
dotnet testCoverage includes:
- Pricing engine — rate boundaries, overlapping periods, invalid times.
- Booking service — cost calculation and 404/409 branches (unit tests with Moq).
- Repositories — overlap detection, availability, soft-delete filter (integration tests against SQLite in-memory).
- API endpoints — every endpoint end-to-end.