CarRental is a full-stack car rental platform that aggregates rental offers from multiple third-party API providers π. It features a price comparison tool, allowing users to find the best deals.
The project was developed as part of the Web Application Development with .NET course during the winter semester of the 2024/2025 academic year.
- π Overview
- π― Features
- π Technologies Used
- ποΈ Solution Architecture
- βοΈ Cloud Services Architecture
- ποΈ Database Schema
- π§ Key Patterns & Technologies
- π§ͺ Unit Testing
- π API Documentation
- π₯ Authors
The goal of this project is to build a comprehensive π Car Rental System composed of two main components:
-
π Web Application
Allows users to browse, filter, and compare rental prices from multiple car providers in one place. -
π Car Provider API
Integrates with external vehicleβrental services to manage available cars, reservations, and rental details.
The system is designed for flexibility and extensibility:
- β New car providers can be added at any time via dedicated APIs, ensuring smooth scalability.
- π€ Supports collaboration with other teams offering their own provider APIs, opening the door to future feature enhancements.
π Click on any feature below to see it in action
- Blazor WebAssembly, MudBlazor, Microsoft Entra Id, GoogleMaps.
- ASP.NET Core, C#, Entity Framework Core, SQL Server, Azure Cache for Redis, SendGrid, Azure Blob Storage, Azure Key Vault, Application Insights.
The solution consists of 11 modular projects, organized to ensure:
- βοΈ Clear code structure
- π§ Easy maintenance
- π Expandability without breaking existing functionality
Below is an overview of the logical layers and their responsibilities:
| π¨ Layer | π Description |
|---|---|
| Core | π¦ Domain models and enums for car providers and the rental price comparer. |
| Persistence | πΎ Data access via Entity Framework Core and the repository pattern. |
| Infrastructure | π Business logic and integrations with external services (e.g. Azure Cache for Redis, Azure Blob Storage, Twilio SendGrid). |
| API | π Exposes REST endpoints, handles authentication & authorization, and validates requests. |
| Web | π¨ Frontend built with Blazor WebAssembly, hosted as a static app on Azure Static Web Apps. |
| Tests | π§ͺ Unit tests to verify correctness of business logic and data access layers. |
The diagrams illustrates the structure of how the projects are organized within the solution:
graph TD
A[CarRental.Comparer.Web]
B[CarRental.Provider.Persistence] --> C[CarRental.Common.Core]
D[CarRental.Comparer.Persistence] --> C
E[CarRental.Provider.Infrastructure] --> B
E --> F[CarRental.Common.Infrastructure]
G[CarRental.Comparer.Infrastructure] --> F
G --> D
H[CarRental.Provider.API] --> E
I[CarRental.Comparer.API] --> G
J[CarRental.Provider.Tests] --> H
K[CarRental.Comparer.Tests] --> I
graph TD
subgraph CarRental.Common.Core
A1[ComparerEntities]
A2[Enums]
A3[ProviderEntities]
end
subgraph CarRental.Common.Infrastructure
B1[Configurations]
B2[PipelineBehaviours]
B3[Middlewares]
B4[Providers]
B5[Storages]
end
subgraph CarRental.Comparer.API
C1[DTOs]
C2[Authorization]
C3[Controllers]
C4[Profiles]
C5[Validators]
C6[Requests]
C7[Pagination]
C8[BackgroundJobs]
end
subgraph CarRental.Provider.API
D1[DTOs]
D2[Authorization]
D3[Controllers]
D4[Profiles]
D5[Validators]
D6[Requests]
end
subgraph CarRental.Provider.Infrastructure
E1[Calculators]
E2[EmailServices]
E3[BackgroundJobs]
end
subgraph CarRental.Comparer.Infrastructure
G1[HttpClients]
G2[Providers]
G3[Cache]
G4[Reports]
G5[CarProviders]
G6[CarComparisons]
end
subgraph CarRental.Provider.Persistence
H1[Specifications]
H2[Configurations]
H3[Migrations]
H4[Options]
H5[Data]
H6[Repositories]
end
subgraph CarRental.Comparer.Persistence
I1[Specifications]
I2[Configurations]
I3[Migrations]
I4[Options]
I5[Data]
I6[Repositories]
end
%% --- ZALEΕ»NOΕCI LOGICZNE ---
%% API β Infrastructure
C3 --> C1
C3 --> C6
C3 --> C5
C3 --> C4
C3 --> C2
C6 --> G5
C6 --> G6
C8 --> G4
C7 --> G3
D3 --> D1
D3 --> D6
D3 --> D5
D3 --> D4
D3 --> D2
D6 --> E1
E3 --> E2
%% Infrastructure β Persistence
G5 --> I6
G6 --> I6
G2 --> I6
G4 --> I5
G3 --> I4
E1 --> H6
E2 --> H5
%% Persistence β Core
H6 --> A3
I6 --> A1
H1 --> A2
I1 --> A2
%% Common Infrastructure β Core
B4 --> A1
B4 --> A3
B2 --> A2
B3 --> A2
B5 --> A2
%% API β Common.Infrastructure
C3 --> B3
D3 --> B3
C8 --> B5
D3 --> B1
π Click to see the diagram
The systemβs resources are organized into three Resource Groups for clarity and separation of responsibilities:
| π Resource Group | π οΈ Service Name | π¦ Type | π Description |
|---|---|---|---|
carrental-provider-prod-rg |
carrental-provider |
App Service | Hosts the Car Provider API (CRUD operations for vehicles, offers & reservations). |
carrental-provider-kv |
Key Vault | Secure storage for keys, passwords and other secrets. | |
carrental-provider-ai |
Application Insights | Realβtime monitoring and diagnostics for the provider API. | |
CarRentalProviderDb |
SQL Database | Relational database storing car provider data. | |
carrental-comparer-prod-rg |
carrental-comparer |
App Service | Hosts the Price Comparer API (aggregates and compares rental offers). |
carrental-comparer-kv |
Key Vault | Secure storage for keys, passwords and other secrets. | |
carrental-comparer-ai |
Application Insights | Monitoring and telemetry for the comparer API. | |
CarRentalComparerDb |
SQL Database | Relational database powering the price comparison engine. | |
carrental-comparer-web |
Static Web App | Frontend hosting for the price comparison UI. | |
carrental-common-prod-rg |
carrentalminisa |
Blob Storage | Static file storage (e.g., vehicle images, brand logos). |
carrental-cache |
Azure Cache for Redis | Caching layer to accelerate read operations (e.g., search results). |
The system uses Entity Framework Core to manage the database with a code-first approach π§©. This means the database schema is automatically generated from your C# model classes, ensuring consistency between the application and the database.
- In the development environment, we use Microsoft SQL Server running locally (
localhost) π₯οΈ. - In production, the solution is deployed on two Azure SQL Database instances βοΈβone for the Car Provider API and one for the Price Comparer.
Below are the diagrams illustrating the database structures, table relationships, and key attributes:
erDiagram
Insurance {
int Id PK
string Name
string Description
decimal PricePerDay
}
Segment {
int Id PK
int InsuranceId FK "Insurance"
string Name
string Description
decimal PricePerDay
}
Model {
int Id PK
int MakeId FK "Make"
int SegmentId FK "Segment"
string Name
int NumberOfDoors
int NumberOfSeats
EngineType EngineType
WheelDriveType WheelDriveType
}
Make {
int Id PK
string Name
}
Car {
int Id PK
int ModelId FK "Model"
int ProductionYear
FuelType FuelType
TransmissionType TransmissionType
decimal Longitude
decimal Latitude
CarStatus Status
}
Offer {
int Id PK
int CarId FK "Car"
DateTime GeneratedAt
DateTime ExpiresAt
decimal RentalPricePerDay
decimal InsurancePricePerDay
string Key
string GeneratedBy
}
Rental {
int Id PK
int OfferId FK "Offer"
int CustomerId FK "Customer"
RenatlStatus Status
}
Customer {
int Id PK
string EmailAddress
string FirstName
string LastName
}
RentalReturn {
int Id PK
int RentalId FK "Rental"
DateTime ReturnedAt
string Description
string Image
decimal Longitude
decimal Latitude
}
Insurance ||--o{ Segment : has
Segment ||--o{ Model : categorizes
Make ||--o{ Model : builds
Model ||--o{ Car : includes
Car ||--o{ Offer : offers
Offer ||--o{ Rental : basedOn
Customer ||--o{ Rental : rents
Rental ||--o| RentalReturn : mightHave
erDiagram
User {
int Id PK
string Name
string LastName
string Email
DateTime Birthday
DateTime DrivingLicenseDate
decimal Longitude
decimal Latitude
}
RentalTransaction {
int Id PK
int UserId FK "User"
int ProviderId FK "Provider"
int CarDetailsId FK "CarDetails"
string RentalOuterId
decimal RentalPricePerDay
decimal InsurancePricePerDay
DateTime RentedAt
DateTime ReturnedAt
decimal PricePerDay
RentalTransactionStatus Status
string Description
string Image
}
Provider {
int Id PK
string Name
}
Employee {
int Id PK
int ProviderId FK "Provider"
string FirstName
string LastName
string Email
}
CarDetails {
int Id PK
int OuterId
string Make
string Model
string Segment
string FuelType
string TransmissionType
int YearOfProduction
int NumberOfDoors
int NumberOfSeats
}
User ||--o{ RentalTransaction : rents
Provider ||--o{ RentalTransaction : offers
Provider ||--o{ Employee : employs
CarDetails ||--o{ RentalTransaction : describes
To ensure modularity and scalability, the system leverages the following design patterns and libraries:
Using MediatR, we implemented a mediator to handle communication between components, which:
- π Reduces direct dependencies between modules
- π― Centralizes request/command handling logic
We separate operations into:
- Commands: Modify system state (e.g. generate offers, create/return rentals, update user data)
- Queries: Read-only operations (e.g. search available vehicles, fetch rental details)
This separation simplifies logic and improves code clarity.
With Ardalis.Specification, we introduced:
- Repository Pattern: Abstracts data operations, decoupling business logic from data access
- Specifications: Encapsulate query criteria in reusable classes for consistent querying
Using Ardalis.Result for standardized operation results:
- β Indicates success or failure
- π Provides error details (codes/messages)
- π« Minimizes exception usage in business logic for cleaner, more testable code
Integrated Microsoft Entra ID for secure, single-signβon access:
- π§βπΌ Users log in via SSO
- π External service integration for robust access control
Two main roles are defined:
Employee: Manages vehicles, reservations, and system administrationUser: Browses available cars, compares offers, and rents vehicles
Each role has distinct permissions to enforce proper access control.
Handled with Hangfire for asynchronous tasks such as:
- β° Marking offers as expired based on business rules
- π Checking rental status updates
Hangfire offers easy scheduling, a web dashboard for monitoring, and seamless integration with Azure.
Employed FluentValidation to declare advanced validation rules:
- π Ensures every business operation starts with validated input
- π Improves readability and maintainability of validation logic
Used AutoMapper for rapid object-to-object mapping:
- βοΈ Eliminates boilerplate conversion code
- π Maintains consistent data structures across application layers
Utilized ClosedXML to generate periodic Excel reports:
- ποΈ Quickly create and format complex spreadsheets
- π Build dynamic reports from database or runtime data
- β Support advanced formulas and multiple data types for detailed analysis
The solution includes two dedicated test projects to ensure code quality and reliability:
-
π CarRental.Comparer.Tests
Contains unit tests for the price comparison logic. -
βοΈ CarRental.Provider.Tests
Contains unit tests for the Car Provider API logic.
The following libraries and frameworks are used for testing:
- π§°
xUnit: A streamlined testing framework for creating, running, and reporting unit tests. - β
FluentValidation: Automatically validates input models in tests, enabling precise validation checks. - π€
Moq: A mocking library for creating fake objects, allowing isolated testing of business logic without external dependencies.
All tests follow the Arrange-Act-Assert pattern to maintain clarity and ease of debugging.
You can access the API docs for both the Car Provider and Price Comparer in your development environment:
- π CarRental.Provider.API:
https://localhost:7173/swagger/index.html - π CarRental.Comparer.API:
https://localhost:7016/swagger/index.html
π Click to see the Swagger UI examples
This project was created by:
The course was taught by π Marcin Sulecki.
