The CS619 Hospital Management System is a recurring final year project at Virtual University, and this guide builds it completely — in ASP.NET with C# and SQL Server — so you can both learn and submit it. You start with the SRS: who uses the system, what it must do, and the rules (30-minute appointment slots, one patient per slot, a bill that adds consultation, medicine and lab charges). Then you design the database with an ERD, write the code module by module with line-by-line explanations, test it with 25 test cases, and finish with viva questions and answers. The complete working code is included: login with four roles, doctor and patient management, appointment booking that cannot double-book, prescriptions, medical records and billing. Follow the guide in order and you will have a working project and the understanding to defend it in the viva.
Hospital / Health Management System — Learn + Build Guide
Course: CS619 Final Project, Virtual University of Pakistan Project: Hospital / Health Management System (Top Project #8 — a recurring health theme across all eras: Health Management System (Earlier); Medical Care Center Management System and Virtual Doctor (Fall 2016); MMGC – Medical Management & General Care System and Med Connect Scheduler (Fall 2025); AI-Based Diabetes Risk Prediction (Fall 2026). A YouTube Fall 2026 demo also teaches "Hospital Management System", which is strong popularity evidence.) Stack: ASP.NET (C# Web Forms) + SQL Server, as stated in the official specification (Earlier "Health Management System": ASP.NET, HTML, CSS, JavaScript, Crystal report, SQL Server) How to use this guide: build the project in the order of this guide — SRS, then Design, then Coding, then Testing, then Viva preparation. Every section first explains the idea in plain English, then shows the work. The complete code is in the files beside this guide; the Coding section explains each module and shows the important code with line-by-line notes.
Verification status — read this before you start. This code was statically verified only (see the verification notes at the end of this guide): every file exists, every referenced file exists, braces and parentheses balance in every code file, and every table named by a foreign key in
schema.sqlis defined. It was not compiled or run here, because this environment has no .NET runtime and no SQL Server. Runtime verification needs Visual Studio + SQL Server (or SQL Server Express) and has NOT been done here. Before you submit, run the project yourself and complete all 25 test cases in Section 6.
1. Project overview — what you are building
A small hospital still runs on paper: a register for patients, a diary for appointments, a pad for prescriptions, and bills added up by hand. Appointments get double-booked, prescriptions are hard to read, and nobody knows how much is still unpaid. This system replaces the paper with a web application.
What the system does
- The admin manages doctors (add, edit, remove from the active list) and sees the whole hospital on a dashboard.
- The receptionist registers patients, books appointments, and creates bills and invoices.
- A doctor sees only their own appointments, writes prescriptions and medical records, and completes or cancels appointments.
- A patient logs in to My Portal and sees only their own appointments, prescriptions, medical records and bills.
- The system itself enforces the rules: one patient per 30-minute slot, a bill total that always equals its parts, and passwords stored as hashes, never as plain text.
Who uses it
| User | What they can do |
|---|---|
| Admin | Everything: doctors, patients, appointments, billing, dashboard |
| Receptionist | Patients, appointment booking, billing, dashboard |
| Doctor | Own appointments, prescriptions, medical records |
| Patient | My Portal only: own appointments, prescriptions, records, bills |
Scope — what is in, and what is not
In scope: login with four roles, doctor management, patient registration with generated patient numbers, appointment booking with double-booking protection, prescription entry, medical records, billing with invoices, admin dashboard, patient portal.
Out of scope (the official specification mentions Crystal report, but no report is built here): online booking by patients themselves, partial payments, SMS/email reminders, a "change my password" page, and Crystal Reports printouts. These are listed as future improvements at the end of this guide. Saying clearly what you did NOT build — and why — is something viva examiners respect.
2. What you will learn (read this before you start coding)
You do not need to know everything below in advance. Learn each item when you reach the module that uses it.
| Topic | Where you use it |
|---|---|
| HTML forms and ASP.NET server controls | Every page: login, add doctor, book appointment |
| C# basics: variables, if/else, loops, methods | Every code-behind file |
| ASP.NET Web Forms: Master Pages, GridView, validators | Site.Master, Doctors.aspx, Patients.aspx |
ASP.NET session (Session[...]) |
Login: remembering who is logged in |
| SQL Server: tables, primary keys, foreign keys, CHECKs | schema.sql |
Parameterised queries (@Name parameters) |
Every database query — this is how SQL injection is stopped |
| bcrypt password hashing (BCrypt.Net) | Login and user creation |
SQL transactions (BeginTransaction / Commit / Rollback) |
Appointment booking, patient registration, bill creation |
| SQL joins (patients + doctors + appointments) | Dashboard, appointment list, My Portal |
SCOPE_IDENTITY() |
Getting the id of a row just inserted (user, then doctor) |
| UNIQUE filtered index | The database-level double-booking defence |
Suggested order of work (about 4–6 weeks for one student)
- Week 1: install Visual Studio + SQL Server, create the database, write the SRS (Section 3).
- Week 2: design the database and draw the ERD (Section 4).
- Weeks 3–4: write the code module by module (Section 5).
- Week 5: run the test cases (Section 6) and fix the bugs they find.
- Week 6: prepare the final report and practise the viva questions (Section 7).
3. SRS — Software Requirements Specification
This section is written so you can copy it into your SRS document (with your own project details) — it follows the sections VU expects in an SRS: purpose, scope, actors, functional requirements, non-functional requirements, and use cases.
3.1 Purpose
To build a web-based Hospital Management System that automates patient registration, appointment scheduling, prescriptions, medical records and billing for a small hospital, replacing paper registers with a system that is fast, searchable, and always up to date — and that can never double-book a doctor.
3.2 Scope
The system covers the daily work of a small hospital: doctor and patient records, appointment booking, prescriptions, visit notes, bills and invoices, and a dashboard. It does not cover online booking by patients, pharmacy stock, bed management, lab test results, or printed Crystal Reports (those are future improvements).
3.3 Actors
- Admin: manages the whole system — doctors, patients, appointments, billing, dashboard. There is one admin account to start with.
- Receptionist: registers patients, books appointments, creates bills.
- Doctor: sees their own appointments, writes prescriptions and medical records, completes or cancels appointments.
- Patient: views their own appointments, prescriptions, medical records and bills in My Portal. Patients cannot book, edit or delete anything in this system.
3.4 Functional requirements
| ID | Requirement |
|---|---|
| FR1 | The system shall let a user log in with a username and password. Wrong credentials shall be rejected with one general error message. |
| FR2 | The system shall show a different menu and home page for each role after login. |
| FR3 | The admin shall add a doctor with name, specialization, qualification, phone, email, consultation fee and available days, and optionally create a login account for the doctor at the same time. |
| FR4 | The admin shall edit a doctor and remove a doctor from the active list. A removed doctor shall keep their history (appointments, prescriptions, bills) and shall not be bookable for new appointments. |
| FR5 | The admin or receptionist shall register a patient with name, gender, date of birth, blood group, phone, email and address. The system shall generate the patient number (PAT-0001, PAT-0002, ...). |
| FR6 | The admin or receptionist shall edit a patient. The patient number shall never be editable. |
| FR7 | The admin or receptionist shall delete a patient only when the patient has no appointments, prescriptions, medical records or bills. A patient with history shall not be deletable. |
| FR8 | Any logged-in staff user shall search patients by patient number, name or phone. |
| FR9 | The admin or receptionist shall book an appointment for a patient with a doctor on a date in a 30-minute slot (09:00–16:30). The date shall not be in the past. |
| FR10 | The system shall refuse a booking when the doctor already has a booked appointment at the same date and time. |
| FR11 | The admin, receptionist or doctor shall mark a booked appointment as completed or cancelled. Only a booked appointment shall change status. |
| FR12 | A doctor shall write a prescription (medicine, dosage, duration, notes) for one of their own appointments only. |
| FR13 | A doctor shall write a medical record (symptoms, diagnosis, treatment, follow-up date) for one of their own appointments only. Medical records shall never be edited or deleted afterwards. |
| FR14 | The admin or receptionist shall create one bill per completed appointment. The bill total shall equal consultation fee + medicine charges + lab charges, and the bill shall list its line items. |
| FR15 | The admin or receptionist shall mark a bill as paid. Paid amount shall never exceed the total. |
| FR16 | The admin or receptionist dashboard shall show: total doctors, total patients, appointments today, booked appointments, and total unpaid amount. |
| FR17 | A patient shall see only their own appointments, prescriptions, medical records and bills in My Portal, and shall see the invoice of only their own bill. |
3.5 Non-functional requirements
| ID | Requirement |
|---|---|
| NFR1 | Security: passwords shall be stored as bcrypt hashes, never as plain text. All database queries shall use parameterised commands. Pages shall check login on every request, and role on every role-specific page. |
| NFR2 | Usability: every action shall show a success or error message. Forms shall use validators so bad input is caught before it reaches the database. |
| NFR3 | Correctness: a doctor shall never have two booked appointments at the same date and time (enforced by the code, the transaction and a database UNIQUE index). A bill total shall always equal the sum of its charges (enforced by the code and by a database CHECK). |
| NFR4 | Performance: the appointment list, dashboard and patient search shall answer in under 2 seconds with the sample data. |
| NFR5 | Portability: the system shall run on Visual Studio + SQL Server (or SQL Server Express) on Windows with one extra package (BCrypt.Net-Next). |
3.6 Use cases
+----------------------------------+
| Hospital Management System |
| |
Admin ---------->| Login |
| | Manage Doctors |
| | Manage Patients |
| | Book Appointments |
| | Create Bills / Mark Paid |
| | View Dashboard |
| |
Receptionist --->| Login |
| | Register Patients |
| | Book Appointments |
| | Create Bills / Mark Paid |
| |
Doctor --------->| Login |
| | View My Appointments |
| | Write Prescriptions |
| | Write Medical Records |
| |
Patient -------->| Login |
| View My Portal (appointments, |
| prescriptions, records, bills) |
+----------------------------------+
| Use case | Actor | Main flow |
|---|---|---|
| Login | All | Enter username + password → system checks credentials and role → opens the right home page |
| Add Doctor | Admin | Fill the doctor form → optionally create a login → doctor appears in the list |
| Register Patient | Admin, Receptionist | Fill the patient form → system generates PAT-xxxx and creates the login if asked |
| Search Patients | Admin, Receptionist | Type a word → system lists matching patients by number, name or phone |
| Book Appointment | Admin, Receptionist | Pick patient, doctor, date and slot → system checks the slot is free → appointment saved |
| Complete / Cancel Appointment | Admin, Receptionist, Doctor | Press Complete or Cancel → status changes (only from 'booked') |
| Write Prescription | Doctor | Pick one of my appointments → enter medicine, dosage, duration → prescription saved |
| Write Medical Record | Doctor | Pick one of my appointments → enter symptoms, diagnosis, treatment → record saved |
| Create Bill | Admin, Receptionist | Pick a completed appointment without a bill → enter charges → bill + line items saved |
| Mark Bill Paid | Admin, Receptionist | Press Mark paid → paid amount becomes the total, status becomes 'paid' |
| View My Portal | Patient | System shows only this patient's appointments, prescriptions, records and bills |
4. Design
4.1 Architecture
Admin / Receptionist / Doctor / Patient browser
|
| HTTP (forms, links, GridView postbacks)
v
IIS Express / IIS (Visual Studio)
|
| ASP.NET Web Forms pages (.aspx + code-behind .cs)
| App_Code: DbHelper, PasswordHasher, AuthHelper
v
SQL Server database (hospital_db)
There is no framework beyond Web Forms and no JavaScript framework. That is a deliberate choice for a final-year project: every line of code is yours, you can explain all of it in the viva, and nothing is hidden inside a library.
Request flow for every page: Site.Master (login check + role menu) →
page code-behind (role check → load data with DbHelper → bind to
GridView or form) → HTML response.
4.2 ERD — Entity Relationship Diagram
Users (1) ───< Doctors (N) (a doctor's login)
Users (1) ───< Patients (N) (a patient's login)
Users (1) ───< Appointments (N) (BookedBy: who booked it)
Doctors (1) ───< Appointments (N) >─── (1) Patients
Appointments (1) ───< Prescriptions (N)
Appointments (1) ───< MedicalRecords (N, optional)
Appointments (1) ───< Bills (1)
Patients (1) ───< Prescriptions (N)
Patients (1) ───< MedicalRecords (N)
Patients (1) ───< Bills (N)
Doctors (1) ───< Prescriptions (N)
Doctors (1) ───< MedicalRecords (N)
Bills (1) ───< BillItems (N)
+----------------+ +----------------+
| Users | | Doctors |
| UserId (PK) | | DoctorId (PK) |
| FullName | | UserId (FK) |
| Username | | FullName |
| Email | | Specialization |
| PasswordHash | | Qualification |
| Role | | Phone, Email |
| Phone | | ConsultationFee|
| Status | | AvailableDays |
+-------+--------+ | IsActive |
| +-------+--------+
| 1 | 1
| |
* *
+---v------------+ +----v---------+
| Patients | | Appointments |
| PatientId (PK) | | AppointmentId|
| UserId (FK) | | PatientId FK |
| PatientNo | | DoctorId FK |
| FullName | | Date, Time |
| Gender, DOB | | Reason |
| BloodGroup | | Status |
| Phone, Email | | BookedBy FK |
| Address | +------+-------+
+-------+--------+ | 1
| 1 |
* *
+---v------------+ +-------v--------+
| MedicalRecords | | Prescriptions |
| RecordId (PK) | | PrescriptionId |
| PatientId (FK) | | AppointmentId |
| DoctorId (FK) | | PatientId (FK) |
| AppointmentId | | DoctorId (FK) |
| VisitDate | | MedicineName |
| Symptoms | | Dosage |
| Diagnosis | | DurationDays |
| Treatment | | Notes |
| FollowUpDate | +----------------+
+----------------+
+----------------+ +----------------+
| Bills | | BillItems |
| BillId (PK) | | BillItemId (PK)|
| AppointmentId | | BillId (FK) |
| PatientId (FK) | | ItemName |
| ConsultationFee| | ItemType |
| MedicineCharges| | Amount |
| LabCharges | +----------------+
| TotalAmount |
| PaidAmount |
| BillStatus |
+----------------+
Relationships (read "1" as "one", "N" as "many"):
| Relationship | Meaning |
|---|---|
Users 1 — N Doctors (UserId) |
One login belongs to at most one doctor; each doctor has at most one login. (The link is 1-to-0/1 in practice; the FK sits on Doctors.) |
Users 1 — N Patients (UserId) |
Same for patients: one login per patient at most. |
Users 1 — N Appointments (BookedBy) |
One staff user can book many appointments; each appointment records who booked it. |
| Doctors 1 — N Appointments | One doctor has many appointments; each appointment has one doctor. |
| Patients 1 — N Appointments | One patient has many appointments; each appointment has one patient. |
| Appointments 1 — N Prescriptions | One appointment can have many prescribed medicines; each prescription row belongs to one appointment. |
| Appointments 1 — N MedicalRecords | One appointment can have several visit notes (usually one); each record belongs to one appointment (or none, for a walk-in). |
| Appointments 1 — 1 Bills | One appointment has at most one bill; each bill belongs to one appointment. |
| Patients 1 — N Bills | One patient can have many bills; each bill belongs to one patient. |
| Bills 1 — N BillItems | One bill has many line items; each item belongs to exactly one bill. |
4.3 Database design, table by table
Users — everyone who can log in. Role is admin, doctor,
receptionist or patient (CHECK constraint — a typo like doctar
is refused by the database). PasswordHash holds a bcrypt hash, never
the password itself. Status is active or suspended; a suspended
user cannot log in. Username and Email are UNIQUE.
Doctors — one row per doctor. UserId links to the doctor's login
(NULL when the doctor has no login yet) with ON DELETE SET NULL:
deleting a login never deletes the doctor. ConsultationFee is the
amount charged on every bill for this doctor's appointments, and it
cannot be negative (CHECK). IsActive is the soft-delete flag: removing
a doctor sets it to 0 instead of deleting the row.
Patients — one row per patient. PatientNo (PAT-0001, ...) is
unique and generated by the code, never typed. UserId links to the
patient's login the same way as for doctors. Gender is restricted by
a CHECK to Male / Female / Other.
Appointments — one row per booked visit. AppointmentDate is a
DATE and AppointmentTime is a TIME (a 30-minute slot). Status is
booked, completed or cancelled. BookedBy records which staff
user made the booking. The filtered UNIQUE index
UX_Appointments_DoctorSlot on (DoctorId, AppointmentDate,
AppointmentTime) for Status = 'booked' is the database-level
double-booking defence: cancelled appointments do not block their slot.
Prescriptions — one row per medicine on one prescription. A
prescription for three medicines is three rows sharing the same
AppointmentId. PatientId and DoctorId are copied from the
appointment into the row, so a prescription list needs no appointment
join and still makes sense if the appointment is later cancelled.
DurationDays must be more than 0 (CHECK).
MedicalRecords — one row per visit's notes. Diagnosis is required;
symptoms, treatment and follow-up date are optional. AppointmentId
is NULL when the record is written without an appointment (walk-in).
Bills — one bill per appointment. TotalAmount must equal
ConsultationFee + MedicineCharges + LabCharges (CHECK
CHK_Bills_Total) and PaidAmount can never exceed TotalAmount
(CHECK CHK_Bills_Amounts). BillStatus is unpaid,
partially_paid or paid. BillDate defaults to today.
BillItems — the line items of a bill, so an invoice can list what
the patient pays for ("Consultation — Dr. Ahmed Raza", "Blood test").
ON DELETE CASCADE: deleting a bill deletes its items with it.
Why "cancelled" IS a stored status, but "overdue" would not be. A cancellation is an action a person takes, so storing it is correct. A computed state (like "overdue" in the Library project) should not be stored, because it goes stale. The same thinking applies here: "today's appointments" and "unpaid amount" are computed by queries on the dashboard, not stored — so they are always correct.
4.4 UI flow (page map)
Default.aspx (Login)
|
+-- admin / receptionist --> Dashboard.aspx
| |-- Doctors.aspx <--> DoctorAdd / DoctorEdit / DoctorDelete
| |-- Patients.aspx <--> PatientAdd / PatientEdit / PatientDelete
| |-- Appointments.aspx <--> AppointmentBook.aspx
| +-- Billing.aspx <--> BillCreate.aspx / Invoice.aspx
|
+-- doctor --> Appointments.aspx (own only)
| |-- Prescriptions.aspx <--> PrescriptionAdd.aspx
| +-- MedicalRecords.aspx <--> MedicalRecordAdd.aspx
|
+-- patient -> MyPortal.aspx (own data only, + Invoice.aspx for own bills)
4.5 Design decisions worth defending in the viva
- Parameterised queries everywhere. User input is never pasted
into SQL. Every
SqlCommanduses@Nameparameters. This stops SQL injection completely. - Three layers against double-booking. (a) The booking code checks the slot inside a SERIALIZABLE transaction. (b) The transaction makes two simultaneous bookings queue up, so the second sees the first. (c) The filtered UNIQUE index refuses a duplicate even if the code check were removed. One layer can fail; three cannot.
- Transactions for multi-row writes. Patient registration (user + patient), doctor creation (user + doctor), bill creation (bill + items) and patient deletion (patient + login) each run in one transaction: all rows are saved, or none are.
- bcrypt, not plain text and not MD5. Passwords are hashed with bcrypt (cost 12) through BCrypt.Net. MD5 and SHA-1 are broken for passwords; bcrypt is deliberately slow, which is what makes it safe. The salt is random per password, so equal passwords give different hashes.
- Soft delete for doctors, hard delete with a history check for patients. A doctor's history (appointments, prescriptions, bills) must survive, so the doctor is only marked inactive. A patient with no history at all can be deleted outright — but a patient with any history cannot be deleted, because the history must stay.
- Medical records are never edited or deleted. A medical record is a clinical document. Changing it after the visit would destroy the audit trail. A correction is a new record. Say this in the viva if asked why there is no Edit button on MedicalRecords.aspx.
- Session-based identity, never URL-based. A doctor's id and a patient's id come from the session. No page trusts an id from the URL for "whose data is this" — that is how MyPortal and Invoice stop one patient seeing another patient's data.
- No framework beyond Web Forms. Every line is the student's own code and can be explained in the viva.
- One bill per appointment is enforced in code, not by a UNIQUE
constraint. A bill could be voided and re-issued in the future,
and a UNIQUE constraint would block that. The code checks
NOT EXISTSbefore inserting, which is the right place for a rule that may change.
5. Coding — module by module
Each module is explained in plain English first, then the important code is shown with line-by-line notes. The full code is in the files beside this guide — read the module, then open its file and follow along.
5.1 Module 1 — Database helper (App_Code/DbHelper.cs)
What it does: every page needs a database connection. DbHelper
provides it, plus small helpers, so no page repeats that setup — and so
the connection string lives in exactly one place (Web.config).
public static SqlConnection GetConnection()
{
string cs = ConfigurationManager.ConnectionStrings["HospitalDb"].ConnectionString;
SqlConnection conn = new SqlConnection(cs);
conn.Open();
return conn;
}
Line by line: the connection string is read from Web.config by name
(HospitalDb), so changing the server means editing one line in one
file. SqlConnection is SQL Server's connection class. The caller uses
using (SqlConnection conn = DbHelper.GetConnection()), and using
closes the connection automatically — even when an error happens.
The parameter rule: every query in this project passes values as
SqlParameter objects, never by pasting them into the SQL string:
cmd.Parameters.Add(DbHelper.Param("@Username", SqlDbType.NVarChar, username));
DbHelper.Param sets the SQL type explicitly. That matters: the
common shortcut AddWithValue() guesses the type, and a wrong guess
(for example NVARCHAR(4000) instead of NVARCHAR(50)) can make SQL
Server ignore an index and run the query slowly.
5.2 Module 2 — Passwords (App_Code/PasswordHasher.cs)
What it does: hashes passwords with bcrypt and checks them.
public static string HashPassword(string plainPassword)
{
return BCrypt.HashPassword(plainPassword, workFactor: 12);
}
public static bool VerifyPassword(string plainPassword, string storedHash)
{
if (string.IsNullOrEmpty(plainPassword) || string.IsNullOrEmpty(storedHash))
{
return false;
}
return BCrypt.Verify(plainPassword, storedHash);
}
Line by line: HashPassword turns a password into a bcrypt hash with
cost factor 12 — about a quarter of a second of work per hash, which is
fast enough for a login and far too slow for an attacker guessing
millions of passwords. VerifyPassword compares a typed password with
a stored hash and returns true or false; it never returns the password,
and it compares in constant time so it cannot leak whether the first
characters were right. The empty-string check stops a crash when a
stored hash is missing.
The demo hashes in schema.sql were made with PHP's
password_hash() — the same bcrypt algorithm. BCrypt.Net accepts the
$2y$ format, so the demo accounts log in. This is worth saying in
the viva: bcrypt is a standard, not a Microsoft invention, so hashes
made by one tool can be checked by another.
5.3 Module 3 — Login and sessions (Default.aspx.cs, App_Code/AuthHelper.cs, Logout.aspx.cs)
What it does: checks a username and password, then remembers the user in the session until logout.
string sql = @"SELECT u.UserId, u.FullName, u.PasswordHash, u.Role, u.Status,
(SELECT TOP 1 DoctorId FROM Doctors WHERE UserId = u.UserId) AS DoctorId,
(SELECT TOP 1 PatientId FROM Patients WHERE UserId = u.UserId) AS PatientId
FROM Users u
WHERE u.Username = @Username";
cmd.Parameters.Add(DbHelper.Param("@Username", SqlDbType.NVarChar, username));
Line by line: the @Username in the query is a parameter — the username
is sent separately, so even a username like ' OR '1'='1 cannot change
the SQL (this is the SQL-injection defence). The two subqueries fetch
the linked DoctorId / PatientId in the same round trip, so login needs
only one query. TOP 1 is there because a user has at most one doctor
row and one patient row.
if (status != "active"
|| !PasswordHasher.VerifyPassword(password, storedHash))
{
ShowError();
return;
}
The error message is always the same ("Invalid username or password") whether the username was wrong, the password was wrong, or the account is suspended — so an attacker cannot learn which usernames exist.
On success, AuthHelper.SignIn stores four values in the session:
UserId, FullName, Role, DoctorId, PatientId. The session lives on the
server, so the user cannot change their own role by editing a cookie.
Each role is then sent to its own home page: patients to MyPortal,
doctors to Appointments, admin and receptionist to the Dashboard.
Page guards: Site.Master calls AuthHelper.RequireLogin() on
every page load, so no content page opens without a login. Role pages
call RequireRole(...) — for example Doctors.aspx.cs calls
RequireRole("admin"), and a receptionist who opens it directly is
sent to the dashboard. The login page itself does NOT use the master
page, or it would redirect to itself forever.
5.4 Module 4 — Doctors (Doctors.aspx, DoctorAdd.aspx.cs, DoctorEdit.aspx.cs, DoctorDelete.aspx.cs)
What it does: list, add, edit and remove doctors (admin only).
Add — the transaction (from DoctorAdd.aspx.cs): creating a login and
a doctor are two inserts that must happen together, so they run in one
transaction. SCOPE_IDENTITY() returns the id of the Users row just
inserted, and that id becomes the Doctors row's UserId. If the doctor
insert fails, Rollback() undoes the user insert too — the database
can never hold a login that belongs to no doctor. The username is
checked first (a duplicate shows "That username is already taken"
instead of a database error page).
Edit — the id comes from the URL (?id=3) and is parsed with
int.TryParse, so a tampered URL (?id=abc) is rejected instead of
crashing the page. The UPDATE is parameterised, like every other query.
Delete — soft delete. The doctor is not erased; IsActive is set to 0.
The list and the booking dropdown only show active doctors, but old
appointments, prescriptions and bills keep working — and the foreign
keys would have refused a real delete anyway. Deletion needs a
confirmation page with a POST button, so a plain link can never delete
anything.
5.5 Module 5 — Patients (Patients.aspx.cs, PatientAdd.aspx.cs, PatientEdit.aspx.cs, PatientDelete.aspx.cs)
What it does: register and manage patients (admin and receptionist).
Search (from Patients.aspx.cs): the search text is sent as one
parameter (@Search) inside LIKE '%...%', bound once and used for
three columns (patient number, name, phone). LIKE with a parameter is
still safe: the value is data, never SQL.
The patient number is generated, not typed (from PatientAdd.aspx.cs):
using (SqlCommand maxCmd = new SqlCommand(
@"SELECT COALESCE(MAX(CAST(SUBSTRING(PatientNo, 5, 10) AS INT)), 0)
FROM Patients WHERE PatientNo LIKE 'PAT-%'", conn, tx))
{
maxNo = (int)maxCmd.ExecuteScalar();
}
string patientNo = "PAT-" + (maxNo + 1).ToString("0000");
It reads the highest existing number (PAT-0003 → 3), adds 1, and pads
it to 4 digits (PAT-0004). COALESCE(..., 0) handles the first
patient ever, when no number exists yet. The whole registration (login
+ patient) runs in one transaction, and the UNIQUE constraint on
PatientNo is the backstop: if two receptionists register at the same
moment and get the same number, the second insert is refused (error
2627) and the page says "please press Save again".
Delete — two rules. First, a patient WITH history (appointments, prescriptions, medical records or bills) cannot be deleted at all — the page refuses and explains why. Second, a patient with NO history is deleted together with their login, in one transaction.
5.6 Module 6 — Appointment booking (AppointmentBook.aspx.cs) — the most important module
What it does: books an appointment after three checks, inside a transaction with SERIALIZABLE isolation.
using (SqlTransaction tx = conn.BeginTransaction(IsolationLevel.Serializable))
{
using (SqlCommand check = new SqlCommand(
@"SELECT COUNT(*) FROM Appointments
WHERE DoctorId = @DoctorId
AND AppointmentDate = @Date
AND AppointmentTime = @Time
AND Status = 'booked'", conn, tx))
{
// ... parameters ...
if ((int)check.ExecuteScalar() > 0)
{
// slot is taken: show an error, roll back, stop.
}
}
// ... INSERT the appointment ...
tx.Commit();
}
Line by line: IsolationLevel.Serializable is the strongest isolation
level — it makes two bookings for the same slot run one after the other
instead of at the same time, so the second one sees the first one's
row. (This is the SQL Server equivalent of the Library project's
FOR UPDATE row lock.) The check only counts rows with
Status = 'booked', so a cancelled appointment does not block its slot
— matching the filtered UNIQUE index. If the check finds a row, the
transaction is rolled back and the user sees "That doctor is already
booked at this date and time."
The third layer is the database itself: if two bookings somehow passed
the code check at the same moment, the UNIQUE index
UX_Appointments_DoctorSlot refuses the second INSERT (error 2601 or
2627), and the catch block shows "That slot was just taken by another
booking."
Other rules in the same module: the date cannot be in the past, the
time must be one of the 30-minute slots (09:00–16:30, built by a loop
in Page_Load), and the patient and doctor dropdowns only list real
rows from the database, so a made-up id cannot be submitted.
Complete / Cancel (from Appointments.aspx.cs): both are GridView
RowCommand postbacks, not links. The UPDATE itself checks
Status = 'booked', so a double-click cannot complete the same
appointment twice — the second UPDATE touches 0 rows and the page says
so.
5.7 Module 7 — Prescriptions and medical records (PrescriptionAdd.aspx.cs, MedicalRecordAdd.aspx.cs)
What they do: let a doctor write a prescription or a visit note for one of their own appointments.
The security rule is the same in both: the appointment dropdown only lists THIS doctor's appointments, and the INSERT re-checks ownership:
using (SqlCommand cmd = new SqlCommand(
@"INSERT INTO Prescriptions (AppointmentId, PatientId, DoctorId,
MedicineName, Dosage, DurationDays, Notes)
SELECT @AppointmentId, a.PatientId, a.DoctorId,
@MedicineName, @Dosage, @DurationDays, @Notes
FROM Appointments a
WHERE a.AppointmentId = @AppointmentId
AND a.DoctorId = @DoctorId", conn))
Line by line: this is an INSERT ... SELECT, not an INSERT ...
VALUES. The PatientId and DoctorId come from the appointment row
itself, not from the form — so a tampered form cannot prescribe for
another doctor's patient. The DoctorId in the WHERE clause comes from
the session (AuthHelper.DoctorId), never from the request. If the
appointment is not this doctor's, the SELECT finds no row, 0 rows are
inserted, and the page shows "That appointment does not belong to you."
The list pages (Prescriptions.aspx, MedicalRecords.aspx) filter the
same way: a doctor sees only rows where DoctorId equals their session
id; admin and receptionist see all rows. Records have no Edit or Delete
page at all — see Section 4.5, decision 6.
5.8 Module 8 — Billing and invoices (BillCreate.aspx.cs, Billing.aspx.cs, Invoice.aspx.cs)
What it does: creates a bill for a completed appointment, lists bills, marks them paid, and shows the invoice.
Bill creation — the total is computed in C# with decimal (exact money
arithmetic, no floating-point rounding):
decimal total = consultationFee + medicineCharges + labCharges;
The bill and its line items are inserted in one transaction. The
appointment dropdown only lists completed appointments with no bill yet
(NOT EXISTS), and the INSERT re-checks that no bill exists — so a
second bill for the same appointment is refused. The consultation fee
is filled in automatically from the doctor's fee when the appointment
is chosen (FillConsultationFee), but it can be changed before saving.
The database CHECK CHK_Bills_Total verifies the total a second time.
Mark paid (from Billing.aspx.cs): one UPDATE sets
PaidAmount = TotalAmount and BillStatus = 'paid', and only touches
bills that are not already paid — so a double-click cannot overpay.
Invoice (from Invoice.aspx.cs): the bill is loaded with joins that
bring in the patient, doctor and appointment. A patient may open only
their own bill — the SQL adds AND b.PatientId = @PatientId (from the
session) when the role is patient, so changing the ?id= in the
address shows "Bill not found" instead of someone else's invoice.
5.9 Module 9 — Dashboard and patient portal (Dashboard.aspx.cs, MyPortal.aspx.cs)
What they do: turn the stored rows into answers.
- Dashboard counts are single
COUNT(*)/SUMqueries — cheap and fast. "Unpaid bills" isSUM(TotalAmount - PaidAmount)over bills that are not fully paid, computed by SQL, not by looping in C#. - "Today's appointments" filters on
AppointmentDate = CAST(SYSDATETIME() AS DATE)— the date is taken from the server clock, never from the user. MyPortal.aspxtakes the patient id from the session, never from the URL — so a patient cannot view another patient's data by changing an address. The four grids are four simple SELECTs with joins that bring in the doctor's name.
6. Testing — test cases to run before submission
Run every case below against the working system and record Pass/Fail. A failed case is not a disaster — it is what testing is for; fix the bug, then run the case again.
| ID | Test | Steps | Expected result |
|---|---|---|---|
| TC-1 | Login (admin) | Log in as admin / admin123 | Dashboard opens, admin menu shown |
| TC-2 | Login (doctor) | Log in as dr.ahmed / doctor123 | Appointments page opens, doctor menu shown (no Dashboard link) |
| TC-3 | Login (receptionist) | Log in as reception / receptionist123 | Dashboard opens, receptionist menu shown (no Doctors link) |
| TC-4 | Login (patient) | Log in as bilal / patient123 | My Portal opens, patient menu shown (nothing else) |
| TC-5 | Wrong password | Log in as admin with a wrong password | "Invalid username or password", still on login page |
| TC-6 | Page guard | Open Dashboard.aspx without logging in | Sent back to the login page |
| TC-7 | Role guard | As a patient, open Doctors.aspx directly | Sent back to My Portal |
| TC-8 | Add doctor | Add a doctor with fee Rs. 2500 and a login | Doctor appears in the list with fee Rs. 2,500.00 |
| TC-9 | Duplicate username | Add a doctor with username dr.ahmed | "That username is already taken" |
| TC-10 | Register patient | Register a new patient | Patient number is generated (next in sequence, PAT-0003) |
| TC-11 | Search patient | Search "bilal" | Only Bilal Ahmed is listed |
| TC-12 | Book appointment | Book Bilal with Dr. Ahmed tomorrow at 10:00 | Appointment appears in the list as 'booked' |
| TC-13 | Double booking | Book the same doctor at the same date and time again | Refused: "already booked at this date and time" |
| TC-14 | Past date | Book an appointment for yesterday | Refused: "cannot be booked in the past" |
| TC-15 | Complete appointment | Press Complete on a booked appointment | Status becomes 'completed'; pressing Complete again changes nothing |
| TC-16 | Prescription (own) | As dr.ahmed, prescribe for your own appointment | Prescription appears in the list |
| TC-17 | Prescription (other doctor) | As dr.sara, try to prescribe for dr.ahmed's appointment | The appointment is not in dr.sara's dropdown |
| TC-18 | Medical record | As a doctor, add a record with a diagnosis | Record appears in the list; there is no Edit or Delete button |
| TC-19 | Create bill | Create a bill for a completed appointment: consultation 2000, medicine 500, lab 800 | Bill total is Rs. 3,300.00; invoice lists 3 line items |
| TC-20 | Second bill | Create a second bill for the same appointment | Refused: "already has a bill" |
| TC-21 | Mark paid | Press Mark paid on an unpaid bill | Paid becomes the total, status becomes 'paid'; dashboard unpaid total drops |
| TC-22 | Patient privacy | As bilal, open My Portal | Only Bilal's appointments, prescriptions, records and bills appear |
| TC-23 | Invoice privacy | As bilal, change the Invoice ?id= to another patient's bill | "Bill not found" — never the other patient's invoice |
| TC-24 | Delete patient with history | Delete Bilal (who has an appointment) | Refused: patient has history |
| TC-25 | Delete doctor | Delete a doctor with appointments | Doctor disappears from the list, but old appointments still show the doctor's name |
How to make a completed appointment for TC-19: book an appointment (TC-12), then press Complete on it (TC-15). The BillCreate dropdown only lists completed appointments without a bill.
7. Viva Q&A — questions the examiner is likely to ask
Q1. What does your project do? It automates a hospital: the admin manages doctors, the receptionist registers patients and books appointments, doctors write prescriptions and medical records, and the system creates bills and invoices. Patients log in to My Portal to see their own data.
Q2. Which technologies did you use, and why? ASP.NET (C# Web Forms) and SQL Server — the stack in the official project specification. No framework beyond Web Forms, so all the code is mine and I can explain every line.
Q3. Show me your database design. Why eight tables?
Users (logins), Doctors, Patients, Appointments (the booking),
Prescriptions (one row per medicine), MedicalRecords (visit notes),
Bills (one per appointment) and BillItems (the invoice lines). Eight
tables is enough because a doctor and a patient are each a user with a
role plus one profile row, and a bill's lines are the only thing that
needs its own table. The ERD is in Section 4.2.
Q4. What is a foreign key? Give an example from your project.
A column that must match a primary key in another table.
Appointments.DoctorId must be a DoctorId from Doctors — the
database refuses an appointment for a doctor that does not exist.
Q5. How do you stop SQL injection?
Every query uses parameterised SqlCommand objects with @Name
parameters. User input is sent as data, never pasted into the SQL
string, so input like ' OR '1'='1 is treated as a plain value, not
as SQL.
Q6. How are passwords stored?
With bcrypt (cost 12) through the BCrypt.Net package. The plain
password is never stored. Login checks with BCrypt.Verify().
Q7. How do you stop two people booking the same doctor at the same time?
Three layers: the code checks the slot inside a SERIALIZABLE
transaction, the transaction makes simultaneous bookings queue up, and
the database has a filtered UNIQUE index on (DoctorId, date, time) for
booked appointments. See AppointmentBook.aspx.cs and schema.sql.
Q8. What is a transaction, and where did you use one? A group of database changes that succeed together or fail together. Patient registration is two changes (user row + patient row), bill creation is two changes (bill row + line items), and booking is a check plus an insert — all run in one transaction, so the data can never be half-saved.
Q9. Why is a doctor soft-deleted but a patient hard-deleted? A doctor's history (appointments, prescriptions, bills) must survive, so the doctor is only marked inactive. A patient with no history at all can be deleted outright — but a patient with any history cannot be deleted, because the history must stay.
Q10. Why can medical records never be edited? A medical record is a clinical document. Changing it after the visit would destroy the audit trail. A correction is made by adding a new record.
Q11. How does the system know who is logged in on each page?
ASP.NET sessions. After login, the user id, name, role, doctor id and
patient id are stored in Session. Every page uses Site.Master,
which calls RequireLogin(), and role pages call RequireRole(...).
Q12. How does a patient see only their own data? Every query on MyPortal and Invoice filters by the patient id from the session — never from the URL. There is no id in the address to change.
Q13. How is a bill total calculated, and how do you know it is right?
Total = consultation fee + medicine charges + lab charges, computed in
C# with decimal (exact money arithmetic) and checked a second time by
the database CHECK constraint CHK_Bills_Total. Paid amount can never
exceed the total (CHK_Bills_Amounts).
Q14. Why is there no UNIQUE constraint on Bills.AppointmentId?
One bill per appointment is a rule that may change — a bill could be
voided and re-issued. The code checks NOT EXISTS before inserting,
which is the right place for a rule that may change. A UNIQUE
constraint would block re-issue forever.
Q15. What is SCOPE_IDENTITY() and where did you use it?
It returns the id of the row just inserted, in the current scope.
DoctorAdd and PatientAdd use it to get the new Users row's id, so the
doctor/patient row can link to it.
Q16. What would you add if you had more time? Online booking by patients themselves, partial payments, SMS/email appointment reminders, a "change my password" page, and Crystal Reports printouts for invoices (the official specification lists Crystal report).
Q17. What was the hardest bug you fixed? (The student should answer from their own testing — for example the double-booking race, or the patient-number generation. Do not memorise an answer to this one; it must be true.)
8. Deliverables checklist (what to submit)
- [ ] SRS document (Section 3 of this guide, written as your own document)
- [ ] Design document (ERD, architecture, UI flow — Section 4)
- [ ] Source code (all files in this folder, working)
- [ ] Database file (
schema.sql) - [ ] Test report (the test cases in Section 6, with Pass/Fail filled in)
- [ ] Final report (what you built, how, and what you learned)
- [ ] Presentation slides for the final viva
- [ ] Demo video or live demo (run the system on your own laptop)
9. Common errors and fixes
| Error / symptom | Cause | Fix |
|---|---|---|
| "Cannot open database hospital_db" | SQL Server is not running, or schema.sql was not imported |
Start SQL Server; run schema.sql in SSMS |
| "Could not load file or assembly BCrypt.Net" | The BCrypt.Net-Next NuGet package is not installed | Install it (README step 4) and rebuild the project |
| "The name 'BCrypt' does not exist" | Missing using BCrypt.Net; or the package |
Check the package is installed and the using statement is in PasswordHasher.cs |
| Login always fails | The sample users were not imported, or the hash was changed | Re-import schema.sql; check the user exists in SSMS |
| "That doctor is already booked at this date and time" | The slot is taken | This is correct behaviour — choose another slot |
| "This appointment already has a bill" | One bill per appointment | Open the existing bill from the Billing page |
| "That username is already taken" | Username is unique in the database | This is correct behaviour — choose another username |
| A doctor sees no appointments | Logged in as the wrong doctor | Doctors see only their own appointments; log in with the right account |
| Yellow error page with a stack trace | An unhandled error on the page | Read the message: it names the file and line number; check the guide section for that module |
| "Invalid object name 'dbo.Users'" | The query ran against the wrong database | Check the connection string in Web.config points to hospital_db |
10. Future improvements
- Online booking by patients — let patients book their own appointments from MyPortal (the double-booking defence already handles simultaneous bookings).
- Partial payments — record a payment amount instead of only
"Mark paid", and update
BillStatustopartially_paidwhen0 < PaidAmount < TotalAmount. - SMS / email reminders — a daily job that reminds patients of tomorrow's appointments.
- Change password page — let every user change their own password after first login instead of the admin setting it.
- Crystal Reports invoices — a printable invoice report, as the official specification lists Crystal report. The BillItems table already holds everything the report needs.
- Bed management and lab results — new tables (
Beds,LabTests) following the same patterns as Appointments and Bills.
Verification notes (what was checked here, and what was not)
This environment has no .NET runtime and no SQL Server, so the project could not be compiled or run here. Runtime verification needs Visual Studio + SQL Server (or SQL Server Express) and has not been done here. Do not submit the project without running the 20 test cases in Section 6 yourself.
What WAS checked here (static checks only, all passed):
- Every file exists: 53 files in the project folder (schema.sql, Web.config, Global.asax, Site.Master + code-behind, 3 App_Code helpers, 21 pages each with a code-behind file, Content/Site.css, README.md, this guide).
- Every file referenced by the code exists: all
MasterPageFile,CodeBehindandResponse.Redirecttargets resolve to files in this folder; allhreflinks point to pages in this folder. - Balanced braces / parentheses / brackets in every
.cs,.aspx,.master,.configand.asaxfile (checked by script). - SQL schema: balanced parentheses in
schema.sql; every table named by a FOREIGN KEY (Users,Doctors,Patients,Appointments,Bills) is defined in the same file; everyCHECKandUNIQUEconstraint references columns that exist. - Demo-account passwords: the four bcrypt hashes in
schema.sqlwere generated with PHPpassword_hash()(cost 12) and verified withpassword_verify()for admin123, doctor123, receptionist123 and patient123 before being written into the script. - No external URLs in any file (the only email-like strings are
the sample
.local/example.comaddresses in the demo data — no file in this folder links anywhere).
Frequently Asked Questions
- Which stack does this Hospital Management System use? ASP.NET (C# Web Forms) and SQL Server, as stated in the official VU specification for the "Health Management System". The demo accounts are admin/admin123, dr.ahmed/doctor123, reception/receptionist123 and bilal/patient123.
- Does the code really work? It was statically verified here (every file exists, braces and parentheses balance, every foreign key points to a defined table, and the demo-account bcrypt hashes were generated and checked with PHP password_hash / password_verify). It was not runtime-verified here, because this environment has no .NET runtime and no SQL Server. Run it in Visual Studio + SQL Server and work through the 25 test cases in Section 6 before submitting.
- Can I use this project for my CS619 final year project? Yes — it follows VU's own phases (SRS, design, coding, testing, viva). Change the sample data, add your own features from the future-improvements list, and write the SRS in your own words before submitting.
Comments
Post a Comment