Building a Console-Based Vehicle Rental Management System Using N-Layer Architecture in C# and EF Core

Introduction

As software applications grow, maintaining clean code, separation of concerns, and scalability becomes increasingly important. One of the most widely adopted architectural patterns in enterprise .NET applications is N-Layer Architecture, which separates an application into distinct layers, each responsible for a specific set of tasks.

In this article, we will build a Vehicle Rental Management System using C#, Entity Framework Core, Repository Pattern, and Service Layer Architecture. The project follows a structured N-Layer approach that improves maintainability, testability, and code organization.

The implementation includes:

  • Entity Framework Core with SQL Server

  • Repository Pattern

  • Service Layer

  • Session Management

  • Authentication and Registration

  • Email Notifications

  • Vehicle Rental Operations

  • Console-Based User Interface

  • Result Wrapper Pattern

The project demonstrates how enterprise-level architecture can be implemented even in a console application.

What Is N-Layer Architecture?

N-Layer Architecture divides an application into multiple logical layers.

Presentation Layer

Responsible for user interaction.

Examples:

  • Console UI

  • ASP.NET MVC

  • Blazor

  • WinForms

Business Logic Layer (BLL)

Contains business rules and validations.

Examples:

  • Authentication rules

  • Vehicle rental policies

  • Customer validations

Data Access Layer (DAL)

Handles database operations.

Examples:

  • Entity Framework Core

  • Repository classes

  • Database Context

Shared Layer

Contains reusable components.

Examples:

  • Enums

  • DTOs

  • Utility classes

  • Result wrappers

Project Structure

Solution
│
├── Presentation
│   ├── Views
│   └── Program.cs
│
├── BLL
│   ├── Services
│   └── Interfaces
│
├── DAL
│   ├── Context
│   ├── Entities
│   ├── Repository
│   └── Interfaces
│
├── Utilities
│   ├── Validators
│   ├── SessionManager
│   └── EmailSender
│
└── Shared
    ├── Enums
    └── ResultWrapper

Required NuGet Packages

Install the following packages before starting the project:

Install-Package Microsoft.EntityFrameworkCore
Install-Package Microsoft.EntityFrameworkCore.SqlServer
Install-Package Microsoft.EntityFrameworkCore.Tools
Install-Package Microsoft.EntityFrameworkCore.Design

For reading configuration from appsettings.json:

Install-Package Microsoft.Extensions.Configuration
Install-Package Microsoft.Extensions.Configuration.Json
Install-Package Microsoft.Extensions.Configuration.FileExtensions
Install-Package Microsoft.Extensions.FileProviders.Physical

Additional console UI packages:

Install-Package Spectre.Console
Install-Package ConsoleTables

These packages are used throughout the application setup.

Configuring Entity Framework Core

After installing packages, create migrations and update the database.

Add-Migration InitialCreate
Update-Database

Creating the Database Context

The AppDbContext acts as the bridge between Entity Framework Core and SQL Server.

DbSets

The application contains the following entities:

public DbSet<User> Users { get; set; }
public DbSet<Customer> Customers { get; set; }
public DbSet<Vehicle> Vehicles { get; set; }
public DbSet<Rental> Rentals { get; set; }

The context loads the connection string from appsettings.json and configures SQL Server using Entity Framework Core. It also defines relationships between:

  • User and Customer

  • Customer and Rental

  • Vehicle and Rental

The project additionally uses seed data to insert default users and vehicles during database creation.

Database Configuration

The SQL Server connection string is stored in appsettings.json.

{
  "ConnectionStrings": {
    "DefaultConnection": "Your SQL Server Connection String"
  }
}

This approach keeps database configuration separate from application code.

Implementing the Repository Pattern

The Repository Pattern acts as an abstraction layer between the Business Layer and the database.

Benefits include:

  • Cleaner code

  • Easier testing

  • Better maintainability

  • Reduced database coupling

Instead of accessing Entity Framework directly inside services, repositories handle all CRUD operations.

Typical repositories include:

  • UserRepository

  • CustomerRepository

  • VehicleRepository

  • RentalRepository

Implementing the Service Layer

The Service Layer contains all business logic.

Examples include:

  • Login validation

  • User registration

  • Vehicle rental rules

  • Rental return logic

  • Email notification handling

Services interact with repositories rather than directly communicating with the database.

Authentication Service

The AuthServices class handles:

  • User login

  • User registration

  • Email validation

  • Password validation

  • Session creation

The service uses dependency-based communication with repositories to perform user-related operations.

Session Management

The application maintains the logged-in user using a static Session Manager.

Features

  • Login

  • Logout

  • Current User Tracking

  • Role Checking

public static User? CurrentUser { get; private set; }

public static bool IsLoggedIn()
{
    return CurrentUser != null;
}

This allows role-based navigation throughout the application.

Input Validation

Validation is centralized inside a utility class.

Email Validation

The application validates email addresses using regular expressions.

Password Validation

Passwords are checked before registration and login.

Benefits:

  • Reusable validation logic

  • Cleaner service classes

  • Consistent validation rules

Implementing Email Notifications

The system includes an Email Sender service using SMTP.

Features

  • Registration notifications

  • OTP notifications

  • Rental confirmations

  • Future email integrations

public interface IEmailSender
{
    Task SendEmailAsync(string toEmail, string subject, string body);
}

The implementation uses Gmail SMTP for sending emails.

Result Wrapper Pattern

Instead of returning primitive values, the application uses a generic Result Wrapper.

public class Result<T>
{
    public bool Success { get; set; }
    public string Message { get; set; } = string.Empty;
    public T? Data { get; set; }
}

Benefits:

  • Consistent API responses

  • Better error handling

  • Easier debugging

  • Strongly typed responses

Vehicle Rental Business Rules

The Vehicle Rental Service contains business validations before creating rentals.

Rules Implemented

  • Rental period must be between 1 and 30 days

  • Customer must exist

  • Vehicle must exist

  • Vehicle must be available

  • Maximum 2 active rentals per customer

  • Same vehicle cannot be rented twice simultaneously

These validations ensure business consistency before a rental is created.

Vehicle Return Process

When a customer returns a vehicle:

  1. Rental status is verified.

  2. Return date is recorded.

  3. Rental status changes to Returned.

  4. Vehicle availability is increased.

  5. Database records are updated.

This logic is encapsulated inside the User Service layer.

Console User Interface

The application uses Spectre.Console to create an interactive console experience.

Public Menu

Available before login:

  • Login

  • Register

  • Quit

Administrator Menu

Available after admin login:

  • View Active Subscriptions

  • View Paused Subscriptions

  • Logout

Customer Menu

Available after customer login:

  • View Available Plans

  • Subscribe to a Plan

  • Pause Subscription

  • Resume Subscription

  • Cancel Subscription

  • Logout

The menu-driven structure provides a clean user experience inside the console environment.

Advantages of Repository and Service Pattern

Separation of Concerns

Each layer handles a specific responsibility.

Maintainability

Business rules remain isolated from database logic.

Scalability

Additional features can be added without modifying existing layers.

Testability

Repositories and services can be mocked during unit testing.

Reusability

Business logic can be reused across multiple UI layers.

Best Practices Followed

  • N-Layer Architecture

  • Repository Pattern

  • Service Layer Pattern

  • Entity Framework Core

  • Configuration Management

  • Centralized Validation

  • Session Management

  • Generic Result Wrapper

  • Dependency-Based Design

  • Database Seeding

Conclusion

N-Layer Architecture remains one of the most effective ways to organize medium and large-scale C# applications. By combining Entity Framework Core, Repository Pattern, Service Layer, and Utility Components, developers can create applications that are easier to maintain, extend, and test.

The Vehicle Rental Management System demonstrated in this article shows how these architectural principles work together in a real-world scenario. Although the application is console-based, the same architecture can easily be extended to ASP.NET Core Web APIs, MVC applications, Blazor projects, or enterprise solutions.