A C# Windows Forms desktop application that simulates a real-world Driving & Vehicle License Department: managing people and drivers, processing license applications, scheduling and grading driving tests, and issuing, renewing, detaining, and releasing driving licenses — built on a 4-layer architecture (UI, Business Layer, Data Access Layer, DTO).
Built as a hands-on exercise in applying OOP (inheritance & polymorphism) and transactional data integrity to a multi-step, real-world workflow — not just CRUD screens.
- Overview
- Architecture
- Features
- Core Workflow: Issuing a License
- Tech Stack
- Project Structure
- Getting Started
- Application Preview
- Key Design Decisions
- Known Limitations
- Roadmap
- License
DVLD models the full lifecycle of a driving license: a person applies for a license class, gets scheduled for and takes a series of tests (vision, written, practical), and — only after passing all required tests — is issued a license. From there, licenses can be renewed, detained (e.g. for violations), released, replaced if lost/damaged, or issued internationally.
The system is deliberately modeled with object-oriented inheritance: ClsApplications is the base class for every application type, and ClsLocalDrivingLicenseApplication, along with the international license and detained-license-release flows, extend it and override its SaveAsync() / DeleteAsync() behavior — sharing common application fields (applicant, date, fees, status) while specializing the type-specific logic.
flowchart TD
A[DVLD — Windows Forms UI] --> B[BusinessLayer<br/>Domain Objects & Business Rules]
B --> C[DataAccessLayer<br/>ADO.NET, Parameterized SQL]
C --> D[(SQL Server)]
A -.-> E[DTO<br/>Shared Data Contracts]
B -.-> E
C -.-> E
| Layer | Responsibility |
|---|---|
| DVLD (UI) | Windows Forms screens grouped by domain (People, Drivers, Applications, License, Tests, User), plus reusable user controls (e.g. ctrlPersonCard, ctrlDriverLicenseInfo) shared across forms. |
| BusinessLayer | Domain objects modeled with an Active-Record-style pattern (AddNew / Update modes), business-rule enforcement, and multi-step workflows wrapped in TransactionScope for atomicity. |
| DataAccessLayer | ADO.NET data access with fully parameterized SQL commands (no string concatenation, no SQL injection surface), centralized command-execution helpers, and Windows Event Log error logging. |
| DTO | Plain data contracts passed between layers, decoupling the business objects from the raw database shape. |
- People & Drivers — register people, promote a person to a driver record, view full driver history.
- Applications — generic application lifecycle (
ClsApplications) shared by every application type, with status tracking (New, Cancelled, Completed). - Local Driving License Applications — apply for a license class, validate no duplicate active application or existing license exists for that class.
- Driving Tests — schedule test appointments, record pass/fail results, and track attempt counts per test type; a license can only be issued after passing all 3 required test types.
- License Issuance — atomically creates a driver record (if needed), issues the license with a class-based expiration date, and marks the application complete — all inside a single transaction.
- License Renewal, Replacement & Detention — renew an existing local license, replace a lost/damaged one, detain a license and later release it.
- International Licenses — issue an international license linked to an existing local one.
- User Management & Authentication — create/manage system users, login with hashed credentials, change password.
- Reusable filtering controls — generic filter-by components (e.g.
ctrlPersonCardWithFilter,ctrlDriverLicenseInfoWithFilter) reused across multiple screens instead of duplicating search logic per form.
This is the most business-critical flow in the system, and the clearest demonstration of transactional integrity in the project:
sequenceDiagram
participant App as LocalDrivingLicenseApplication
participant Driver as ClsDriver
participant License as ClsLicenses
App->>App: Check license not already issued
App->>App: Check all 3 tests passed
App->>Driver: Find or create driver record
Note over App,License: TransactionScope begins
App->>License: Create license (class, issue/expiry dates, fees)
App->>App: Mark application as Complete
Note over App,License: Scope.Complete() — commit all or nothing
If license creation succeeds but marking the application complete fails (or vice versa), the entire operation rolls back — the system never ends up with an issued license against an application that's still "in progress," or a completed application with no license.
- C# / .NET Framework, Windows Forms
- ADO.NET (
System.Data.SqlClient) — direct, parameterized data access - System.Transactions (
TransactionScope) — atomic multi-table operations - SHA-256 — credential hashing
- SQL Server
- Windows Event Log — centralized error logging via a dedicated logger class
DVLD Project/
├── DVLD/ # UI — Windows Forms
│ ├── People/ # Person management screens
│ ├── Drivers/ # Driver detail screens
│ ├── Applications/ # Application types, local/international flows
│ ├── License/ # Issue, renew, detain, release licenses
│ ├── Tests/ # Schedule & take tests, test types
│ ├── User/ # User management, login, change password
│ ├── Global classes/ # Shared validation, filtering, utility helpers
│ └── Login/ # LoginForm
├── BusinessLayer/ # Domain objects & business rules (Cls*.cs)
├── DataAccessLayer/ # ADO.NET data access (Cls*Data.cs)
│ └── LogErorr/ # Windows Event Log logger
└── DTO/ # Shared data contracts
- Visual Studio (2019 or later recommended)
- .NET Framework (matching the project's target)
- SQL Server (LocalDB, Express, or full)
-
Clone the repository
git clone https://github.com/Sherif-Osama/DVLD-Project.git
-
Restore the database using the backup included in the repository.
-
Configure the connection string in
App.configunderDVLDConnectionStringsto point to your SQL Server instance. -
Open
DVLD.slnin Visual Studio, build the solution, and run (F5). The application starts at the login screen.
- Active-Record-style business objects: each domain class (
ClsDriver,ClsUser,ClsLocalDrivingLicenseApplication, ...) tracks its ownAddNew/Updatemode and exposes a singleSaveAsync()entry point, so callers don't need to know whether an operation is an insert or an update. - Inheritance for shared application behavior: rather than duplicating "applicant, date, fees, status" logic across every application type,
ClsApplicationscentralizes it, and each concrete application type overrides only what's specific to it. - Transactions around multi-step writes: any operation that touches more than one table as a single logical unit (issuing a license, deleting an application) is wrapped in
TransactionScopeso partial failures can't leave the database in an inconsistent state. - Parameterized SQL everywhere: every DAL method builds
SqlCommandobjects with explicitParameters.Add(...)calls — there is no string concatenation into SQL text anywhere in the data layer.
Documented here deliberately, rather than hidden:
- Password hashing uses unsalted SHA-256. This is vulnerable to precomputed rainbow-table attacks and doesn't meet modern password-storage standards. It should be replaced with a salted, slow hashing algorithm (BCrypt, Argon2, or PBKDF2).
- DAL error handling collapses "not found" and "system failure" into the same result. Every data-access method catches all exceptions, logs them, and returns
null/-1/false— the exact same value it would return for a legitimate "no matching row." Callers in the business layer currently cannot distinguish "the record doesn't exist" from "the database connection failed," which can mask real outages as ordinary not-found cases. - Inline SQL rather than stored procedures. All data access uses ad-hoc parameterized
SqlCommandtext rather than stored procedures, so query logic isn't centralized or independently versioned in the database. - No automated tests. The domain layer's Active-Record pattern would benefit from unit tests around business-rule methods (
BusinessRulesAsync, license issuance eligibility, etc.).
- Replace SHA-256 with salted BCrypt/Argon2 for password storage.
- Rework DAL error handling to distinguish "not found" from "operation failed" (e.g. via nullable results plus exception propagation, or a result/outcome type) instead of silently collapsing both into the same return value.
- Add automated tests for business-rule methods in the Business Layer.
- Migrate remaining inline SQL to stored procedures for centralized, versionable query logic.
This project is open for educational and portfolio purposes.