Psyter Web Portal - Detailed Structure (Part 1 of 2)¶
Project: Web Portal (.NET Framework MVC)
Technology: ASP.NET MVC 5, .NET Framework 4.7.2, jQuery, Bootstrap
Purpose: Web-based portal for patients, providers, and administrators
Last Updated: November 5, 2025
📁 Root Structure¶
Web/
├── Psyter/ # Main web application
│ ├── Controllers/ # MVC controllers
│ │ ├── Admin/ # Admin controllers
│ │ ├── Accountant/ # Accountant controllers
│ │ ├── Patient/ # Patient controllers
│ │ ├── Physician/ # Provider controllers
│ │ ├── WebController.cs # Public website
│ │ ├── CommonController.cs # Shared functionality
│ │ ├── UserManagerController.cs
│ │ ├── OrganizationController.cs
│ │ ├── EventController.cs
│ │ └── ...
│ ├── Views/ # Razor views
│ │ ├── Web/ # Public pages
│ │ ├── Patient/ # Patient views
│ │ ├── Physician/ # Provider views
│ │ ├── Admin/ # Admin views
│ │ ├── Shared/ # Shared layouts
│ │ └── ...
│ ├── Models/ # View models
│ ├── DataAccess/ # Data layer
│ ├── Common/ # Utilities
│ ├── Filters/ # Action filters
│ ├── Content/ # CSS, images
│ ├── Scripts/ # JavaScript
│ ├── App_Start/ # Config files
│ ├── Global.asax.cs # Application entry
│ └── Web.config # Configuration
├── Psyter.sln # Solution file
├── azure-pipelines.yml # CI/CD pipeline
├── packages/ # NuGet packages
└── .git/ # Git repository
🎯 Project Overview¶
Purpose¶
Multi-tenant web portal providing:
- Public Website: Marketing pages, therapist listing, psychological screening
- Patient Portal: Book appointments, manage profile, attend sessions
- Provider Portal: Manage schedule, view appointments, access patient records
- Admin Portal: Manage users, content, analytics, payments
- Content Management: SEO, blogs, events, educational content
- Organization Management: Corporate accounts, charity bookings
User Roles¶
| Role | Access Level | Key Features |
|---|---|---|
| Guest | Public | Home, therapist search, screening test |
| Patient | Authenticated | Book sessions, manage appointments, profile |
| Provider | Authenticated | Schedule, appointments, patient history, earnings |
| Admin | Super User | Full system access, user management, analytics |
| Accountant | Finance | Payment reports, refunds, financial analytics |
| Content Manager | Content | Blog posts, SEO, events, educational content |
| SEO Manager | Marketing | Page optimization, keywords, meta tags |
| Marketing Manager | Marketing | Campaigns, analytics, promotions |
| Organization | Corporate | Bulk bookings, employee access, reporting |
Architecture¶
ASP.NET MVC 5 Pattern:
- Model: Data models and view models
- View: Razor templates (Arabic + English)
- Controller: Business logic and API orchestration
- Filter: Authentication and authorization
Backend API Integration:
- Web portal is a frontend only (no direct database access)
- All data operations via REST API calls to PsyterAPI
- API base URL: https://dvx.innotech-sa.com/Psyter/Master/APIs/
- Token-based authentication (OAuth 2.0)
- Scheduling API integration for availability
📦 Dependencies & Packages¶
Core Framework¶
ASP.NET MVC 5:
- .NET Framework 4.7.2
- Razor view engine
- Bundling and minification
- JSON.NET for serialization
Key NuGet Packages (Psyter.csproj)¶
<!-- Google APIs -->
<PackageReference Include="Google.Analytics.Data.V1Beta" Version="2.0.0-beta01" />
<PackageReference Include="Google.Api.Gax" Version="4.0.0" />
<PackageReference Include="Google.Apis.Auth" Version="1.56.0" />
<!-- gRPC (for Google services) -->
<PackageReference Include="Grpc.Core" Version="2.46.3" />
<PackageReference Include="Grpc.Net.Client" Version="2.46.0" />
<!-- Application Insights -->
<PackageReference Include="Microsoft.ApplicationInsights" Version="2.20.0" />
<PackageReference Include="Microsoft.ApplicationInsights.Web" Version="2.9.0" />
<!-- JWT Tokens -->
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="6.25.1" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.25.1" />
<!-- JSON -->
<PackageReference Include="Newtonsoft.Json" Version="13.0.0" />
<!-- Office Documents -->
<PackageReference Include="DocumentFormat.OpenXml" Version="2.11.3" />
<!-- Security -->
<PackageReference Include="BouncyCastle" Version="1.8.9" />
<!-- Frontend -->
<PackageReference Include="jQuery" Version="3.6.0" />
<PackageReference Include="Bootstrap" Version="4.6.0" />
Purpose of Key Packages¶
Google Analytics Data:
- Track user behavior
- Generate analytics reports
- Monitor page performance
gRPC:
- Google service communication
- Protobuf serialization
Application Insights:
- Performance monitoring
- Error tracking
- Usage analytics
JWT Tokens:
- Parse API authentication tokens
- Validate user sessions
OpenXML:
- Generate Excel reports
- Export appointment data
- Financial reports
⚙️ Configuration¶
Web.config¶
Key Settings:
<appSettings>
<!-- API Endpoints -->
<add key="PsyterApiBasePath" value="https://dvx.innotech-sa.com/Psyter/Master/APIs/" />
<!-- Application Token (for scheduling API) -->
<add key="PsyterApplicationToken" value="f97f3496-a2c8-4c20-84ef-b5a8e6388038" />
<!-- Session Timeout -->
<add key="SessionTimeoutCountDown" value="180" />
<!-- JSON Max Size -->
<add key="aspnet:MaxJsonDeserializerMembers" value="10000000"/>
<!-- Web Pages -->
<add key="webpages:Version" value="3.0.0.0" />
<add key="UnobtrusiveJavaScriptEnabled" value="true" />
</appSettings>
<system.web>
<compilation debug="true" targetFramework="4.7.2" />
<httpRuntime targetFramework="4.7.2" enableVersionHeader="false" />
<!-- Security -->
<httpCookies httpOnlyCookies="true" requireSSL="true" />
<!-- Error Handling -->
<customErrors mode="Off">
<error statusCode="404" redirect="~/Error/NotFound" />
</customErrors>
</system.web>
Application Settings¶
PsyterApiBasePath:
- Base URL for all API calls
- Points to PsyterAPI backend
- All CRUD operations proxied through API
SessionTimeoutCountDown:
- Session timeout in seconds (180 = 3 minutes)
- User warned before auto-logout
- JavaScript countdown timer
PsyterApplicationToken:
- Application-level authentication
- Used for scheduling API
- Required for availability queries
🏗️ Application Architecture¶
Data Flow¶
User Browser
↓
Web Portal (MVC)
↓
Controllers
↓
DALManager / ApiDataAccess
↓
HTTP Client → PsyterAPI (REST)
↓
Database (SQL Server)
Key Differences from Direct DB Access¶
Traditional MVC:
Controller → Entity Framework → Database
Psyter Web Portal:
Controller → HttpClient → PsyterAPI → Database
Benefits:
- Shared business logic across platforms (Web, Android, iOS)
- Centralized authentication
- API versioning support
- Easier to scale and maintain
- Consistent validation rules
📂 Data Access Layer¶
DALManager.cs¶
Purpose: Centralized API communication manager
Key Methods:
public class DALManager
{
private HttpClient mObjHttpClient;
// Constructor - Initialize HTTP client
public DALManager(ApiType apiType)
{
string apiBaseUri = GetApiBaseUri(apiType);
mObjHttpClient = new HttpClient();
mObjHttpClient.BaseAddress = new Uri(apiBaseUri);
mObjHttpClient.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json")
);
}
// GET request with authentication
public async Task<JObject> GetData(
string requestObject,
string methodName,
string token
)
{
mObjHttpClient.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", token);
HttpResponseMessage response =
await mObjHttpClient.GetAsync(methodName);
if (response.IsSuccessStatusCode)
{
var responseJson = await response.Content.ReadAsStringAsync();
return JObject.Parse(responseJson);
}
return HandleError(response);
}
// POST request with authentication
public async Task<JObject> PostData(
string requestObject,
string methodName,
string token
)
{
var content = new StringContent(
requestObject,
Encoding.UTF8,
"application/json"
);
mObjHttpClient.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", token);
HttpResponseMessage response =
await mObjHttpClient.PostAsync(methodName, content);
var responseJson = await response.Content.ReadAsStringAsync();
return JObject.Parse(responseJson);
}
}
ApiDataAccess.cs¶
Purpose: Specific API method implementations
Total Methods: 40+ API integration methods
Categories:
- Authentication & Registration
- User Profile Management
- Appointment Booking
- Payment Processing
- Screening & Assessment
- Provider Management
- Admin Operations
Sample Methods:
GetUserDetails()¶
public async Task<JObject> GetUserDetails(string userName, string password)
{
// OAuth-style login
var formContent = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("grant_type", "password"),
new KeyValuePair<string, string>("username", userName),
new KeyValuePair<string, string>("password", password),
});
HttpResponseMessage response =
await mObjHttpClient.PostAsync("authenticate", formContent);
if (response.IsSuccessStatusCode)
{
var responseJson = await response.Content.ReadAsStringAsync();
var jObject = JObject.Parse(responseJson);
return jObject;
}
return CreateErrorResponse("Error while loading user details");
}
RegisterUser()¶
public async Task<UserRegistrationResponse> RegisterUser(
UserRegistrationRequest userRegistration
)
{
var json = JsonConvert.SerializeObject(userRegistration);
var content = new StringContent(json, Encoding.UTF8, "application/json");
HttpResponseMessage response =
await mObjHttpClient.PostAsync("user/RegUser", content);
var responseJson = await response.Content.ReadAsStringAsync();
var jObject = JObject.Parse(responseJson);
return jObject.ToObject<UserRegistrationResponse>();
}
BookAppointment()¶
public async Task<JObject> BookAppointment(
string requestObject,
string token
)
{
var content = new StringContent(
requestObject,
Encoding.UTF8,
"application/json"
);
mObjHttpClient.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", token);
HttpResponseMessage response =
await mObjHttpClient.PostAsync("booking/bookslot", content);
var responseJson = await response.Content.ReadAsStringAsync();
return JObject.Parse(responseJson);
}
API Method Categories¶
Authentication (5 methods):
- GetUserDetails() - Login
- RegisterUser() - Registration
- VerifyUser() - Email verification
- ForgotPassword() - Password reset
- ChangePassword() - Update password
Profile Management (8 methods):
- GetUserProfile() - Load profile
- UpdatePersonalInfo() - Update personal data
- UpdateContactInfo() - Update contact
- UploadProfileImage() - Upload photo
- GetEducationHistory() - Provider education
- SaveEducationHistory() - Save education
- GetWorkExperience() - Provider experience
- SaveWorkExperience() - Save experience
Booking (10 methods):
- GetAvailableProviders() - Search therapists
- GetProviderSchedule() - Check availability
- BookAppointment() - Create booking
- GetMyBookings() - List appointments
- CancelBooking() - Cancel appointment
- RescheduleBooking() - Change appointment
- GetBookingDetails() - Appointment info
- JoinSession() - Get video link
- RateProvider() - Submit rating
- GetBookingHistory() - Past appointments
Payment (7 methods):
- InitiatePayment() - Start payment
- VerifyPayment() - Check status
- GetPaymentHistory() - Transaction list
- RequestRefund() - Refund request
- GetWalletBalance() - Check balance
- AddToWallet() - Top up wallet
- GetTransactionDetails() - Payment info
Screening (4 methods):
- GetScreeningQuestions() - Assessment questions
- SubmitScreeningAnswers() - Save answers
- GetScreeningResult() - Get recommendation
- GetScreeningHistory() - Past assessments
Admin (6+ methods):
- GetAllUsers() - User list
- GetUserDetails() - User details
- ActivateUser() - Enable account
- DeactivateUser() - Disable account
- GetPlatformStatistics() - Analytics
- GetFinancialReports() - Revenue data
🔐 Authentication & Session¶
Session Variables (SessionVariables.cs)¶
User Session:
public static class SessionVariables
{
// User Information
public const string UserInfo = "UserInfo";
public const string UserType = "UserType";
public const string UserProfileStatus = "UserProfileStatus";
// Authentication
public const string APIAuthTokenList = "APIAuthTokenList";
public const string ScheduleAPIAccessToken = "ScheduleAPIAccessToken";
// Application State
public const string CurrentLanguage = "CurrentLanguage";
public const string CurrentPage = "CurrentPage";
// Configuration
public const string PsyterApplicationToken = "PsyterApplicationToken";
}
Authentication Flow¶
1. Initial Session (Global.asax.cs):
protected void Session_Start(Object sender, EventArgs e)
{
// Initialize common data
Common.Common.initCommonData();
// Get API authentication token
Common.APIAuthenticationToken.GetAPIAuthenticationTokens();
}
2. Get API Token (APIAuthenticationToken.cs):
public static async void GetAPIAuthenticationTokens()
{
// Request app-level token from API
var formContent = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("grant_type", "password"),
new KeyValuePair<string, string>("applicationtoken", appToken)
});
HttpResponseMessage response =
await httpClient.PostAsync("authenticate", formContent);
if (response.IsSuccessStatusCode)
{
var responseJson = await response.Content.ReadAsStringAsync();
var authResponse = JsonConvert.DeserializeObject<APIAuthTokensResponse>(responseJson);
// Store in session
HttpContext.Current.Session[SessionVariables.APIAuthTokenList] = authResponse;
}
}
3. User Login (WebController.cs):
[HttpPost]
public async Task<ActionResult> Login(string username, string password)
{
ApiDataAccess api = new ApiDataAccess();
JObject userDetails = await api.GetUserDetails(username, password);
if (userDetails["access_token"] != null)
{
// Store user info in session
Session[SessionVariables.UserInfo] = userDetails;
// Redirect based on user type
int userType = (int)userDetails["UserTypeId"];
if (userType == 1) // Patient
return RedirectToAction("Index", "Client");
else if (userType == 2) // Provider
return RedirectToAction("Index", "ServiceProvider");
else if (userType == 3) // Admin
return RedirectToAction("Index", "Admin");
}
return View("Login");
}
🛡️ Authorization Filters¶
Filter Hierarchy¶
Total Filters: 10 authorization filters
- AuthenticateUser - Base authentication
- IsClient - Patient access only
- IsServiceProvider - Provider access only
- IsAdmin - Admin access only
- IsAccountant - Accountant access only
- IsContentManager - Content manager access
- IsSeoManager - SEO manager access
- IsMarkeetingManager - Marketing manager access
- IsAuthorized - Generic authorization
- exceptionFilter - Global error handler
AuthenticateUser.cs (Base Filter)¶
Purpose: Verify user is logged in
public class AuthenticateUser : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
// Check if user session exists
if (HttpContext.Current.Session[SessionVariables.UserInfo] == null)
{
// Not logged in - redirect to login
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary(new
{
controller = "Web",
action = "Login"
})
);
}
base.OnActionExecuting(filterContext);
}
}
IsClient.cs (Patient Filter)¶
Purpose: Ensure user is a patient
public class IsClient : AuthenticateUser
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
// First check authentication
base.OnActionExecuting(filterContext);
// Check user type
if (HttpContext.Current.Session[SessionVariables.UserInfo] != null)
{
var userInfo = HttpContext.Current.Session[SessionVariables.UserInfo];
int userType = GetUserType(userInfo);
if (userType != 1) // Not a patient
{
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary(new
{
controller = "Error",
action = "Unauthorized"
})
);
}
}
}
}
IsServiceProvider.cs (Provider Filter)¶
Purpose: Ensure user is a care provider
[IsServiceProvider]
public class ServiceProviderController : Controller
{
// Only care providers can access these actions
}
IsAdmin.cs (Admin Filter)¶
Purpose: Ensure user is administrator
[IsAdmin]
public class AdminController : Controller
{
// Only admins can access these actions
}
exceptionFilter.cs (Global Error Handler)¶
Purpose: Catch and log all exceptions
public class exceptionFilter : HandleErrorAttribute
{
public override void OnException(ExceptionContext filterContext)
{
// Log error
LogError(filterContext.Exception);
// Check if AJAX request
if (filterContext.HttpContext.Request.IsAjaxRequest())
{
// Return JSON error
filterContext.Result = new JsonResult
{
Data = new
{
success = false,
error = filterContext.Exception.Message
},
JsonRequestBehavior = JsonRequestBehavior.AllowGet
};
filterContext.ExceptionHandled = true;
}
else
{
// Redirect to error page
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary(new
{
controller = "Error",
action = "Index"
})
);
}
base.OnException(filterContext);
}
}
🧩 Common Utilities¶
Common.cs¶
Purpose: Shared utility functions
Total Methods: 30+ utility methods
Key Categories:
1. Initialization¶
public static void initCommonData()
{
// Initialize language (default Arabic)
if (HttpContext.Current.Session[SessionVariables.CurrentLanguage] == null)
{
HttpContext.Current.Session[SessionVariables.CurrentLanguage] =
AppConstants.Languages.Arabic;
}
// Initialize HTTP clients for APIs
if (mObjHttpClient == null)
{
string apiBaseUri = ConfigurationManager.AppSettings["PsyterApiBasePath"];
mObjHttpClient = new HttpClient();
mObjHttpClient.BaseAddress = new Uri(apiBaseUri);
mObjHttpClient.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json")
);
}
// Initialize scheduling API client
if (mObjAvailabilityHttpClient == null)
{
var authTokens = (APIAuthTokensResponse)
HttpContext.Current.Session[SessionVariables.APIAuthTokenList];
string schedulingApiBaseUri = authTokens.APIAuthToken.SchedulingAPIBaseURL;
mObjAvailabilityHttpClient = new HttpClient();
mObjAvailabilityHttpClient.BaseAddress = new Uri(schedulingApiBaseUri);
}
}
2. Encryption/Decryption¶
// Encrypt query string parameters
public static string EncryptQS(string plainText, bool toLower = false)
{
string encrypted = Encrypt(plainText, "&%#@?,:*");
if (toLower)
encrypted = encrypted.ToLower();
return encrypted;
}
// Decrypt query string parameters
public static string DecryptQS(string encryptedText)
{
return Decrypt(encryptedText, "&%#@?,:*");
}
// Base encryption (Rijndael)
private static string Encrypt(string plainText, string passPhrase)
{
byte[] plainTextBytes = Encoding.UTF8.GetBytes(plainText);
using (RijndaelManaged rijndael = new RijndaelManaged())
{
rijndael.Key = DeriveKeyFromPassword(passPhrase);
rijndael.IV = new byte[16]; // Initialization vector
ICryptoTransform encryptor = rijndael.CreateEncryptor();
byte[] encryptedBytes = encryptor.TransformFinalBlock(
plainTextBytes, 0, plainTextBytes.Length
);
return Convert.ToBase64String(encryptedBytes);
}
}
3. SEO Page Management¶
public static async Task<ApplicationPageDetail> GetPageDetailForSEOFromDB(
string pageName
)
{
DALManager dalManager = new DALManager(ApiType.psyter);
APIAuthTokensResponse authTokens =
(APIAuthTokensResponse)HttpContext.Current.Session[SessionVariables.APIAuthTokenList];
JObject response = await dalManager.GetData(
"",
$"Common/GetPageDetail/{pageName}",
authTokens.APIAuthToken.access_token
);
ApplicationPageDetailResponse pageDetailResponse =
response.ToObject<ApplicationPageDetailResponse>();
if (pageDetailResponse.Status == 1)
{
return pageDetailResponse.Data;
}
return new ApplicationPageDetail();
}
4. Image Upload¶
public static async Task<string> UploadImage(
HttpPostedFileBase file,
string userId,
int imageType
)
{
if (file != null && file.ContentLength > 0)
{
// Convert to base64
byte[] fileBytes = new byte[file.ContentLength];
file.InputStream.Read(fileBytes, 0, file.ContentLength);
string base64Image = Convert.ToBase64String(fileBytes);
// Upload to media API
var uploadRequest = new
{
UserId = userId,
ImageType = imageType,
ImageData = base64Image,
FileName = file.FileName
};
string json = JsonConvert.SerializeObject(uploadRequest);
var content = new StringContent(json, Encoding.UTF8, "application/json");
HttpResponseMessage response =
await mObjHttpClient.PostAsync("media/upload", content);
if (response.IsSuccessStatusCode)
{
var responseJson = await response.Content.ReadAsStringAsync();
var jObject = JObject.Parse(responseJson);
return jObject["ImageUrl"].ToString();
}
}
return null;
}
5. Date/Time Utilities¶
// Convert to user timezone
public static DateTime ConvertToUserTime(DateTime utcTime, int gmtOffset)
{
return utcTime.AddHours(gmtOffset);
}
// Format date for display
public static string FormatDate(DateTime date, string language)
{
if (language == AppConstants.Languages.Arabic)
{
return date.ToString("dd/MM/yyyy", new CultureInfo("ar-SA"));
}
else
{
return date.ToString("dd/MM/yyyy", new CultureInfo("en-US"));
}
}
// Calculate age
public static int CalculateAge(DateTime birthDate)
{
DateTime today = DateTime.Today;
int age = today.Year - birthDate.Year;
if (birthDate.Date > today.AddYears(-age))
age--;
return age;
}
📋 Models¶
Total Model Files: 40+ classes¶
Base Models¶
UserLoginInfo.cs¶
public class UserLoginInfo
{
public long UserLoginInfoId { get; set; }
public string UserName { get; set; }
public string Email { get; set; }
public int UserTypeId { get; set; }
public string UserType { get; set; }
public bool IsActive { get; set; }
public bool IsVerified { get; set; }
public DateTime CreatedDate { get; set; }
public string AccessToken { get; set; }
public string RefreshToken { get; set; }
public int TokenExpiresIn { get; set; }
}
PersonalInformation.cs¶
public class PersonalInformation
{
public long UserLoginInfoId { get; set; }
public string FirstNamePLang { get; set; }
public string FirstNameSLang { get; set; }
public string LastNamePLang { get; set; }
public string LastNameSLang { get; set; }
public DateTime? DateOfBirth { get; set; }
public int? Age { get; set; }
public int? GenderType { get; set; }
public int? CatSalutationId { get; set; }
public string ProfileImagePath { get; set; }
public string IdentificationNumber { get; set; }
public string AboutMePLang { get; set; }
public string AboutMeSLang { get; set; }
}
ContactInformation.cs¶
public class ContactInformation
{
public long UserLoginInfoId { get; set; }
public string MobileNumber { get; set; }
public string PhoneNumber { get; set; }
public string EmergencyContactName { get; set; }
public string EmergencyContactNumber { get; set; }
public string AddressLine1 { get; set; }
public string AddressLine2 { get; set; }
public long? CityId { get; set; }
public string CityName { get; set; }
public long? CountryId { get; set; }
public string CountryName { get; set; }
public string ZipCode { get; set; }
}
Provider-Specific Models¶
EducationHistory.cs¶
public class EducationHistory
{
public long EducationHistoryId { get; set; }
public long UserLoginInfoId { get; set; }
public string DegreePLang { get; set; }
public string DegreeSLang { get; set; }
public string UniversityPLang { get; set; }
public string UniversitySLang { get; set; }
public int? CatEducationTypeId { get; set; }
public string EducationType { get; set; }
public int? GraduationYear { get; set; }
public bool IsDeleted { get; set; }
}
WorkExperience.cs¶
public class WorkExperience
{
public long WorkExperienceId { get; set; }
public long UserLoginInfoId { get; set; }
public string OrganizationNamePLang { get; set; }
public string OrganizationNameSLang { get; set; }
public string PositionPLang { get; set; }
public string PositionSLang { get; set; }
public DateTime? StartDate { get; set; }
public DateTime? EndDate { get; set; }
public bool IsCurrentlyWorking { get; set; }
public string DescriptionPLang { get; set; }
public string DescriptionSLang { get; set; }
public bool IsDeleted { get; set; }
}
CvDetails.cs¶
public class CvDetails
{
public long UserLoginInfoId { get; set; }
public string CVFilePath { get; set; }
public DateTime? CVUploadDate { get; set; }
public string LicenseCertificateFilePath { get; set; }
public DateTime? LicenseUploadDate { get; set; }
public string CertificateFilePath { get; set; }
public DateTime? CertificateUploadDate { get; set; }
}
Booking Models¶
Booking.cs¶
public class Booking
{
public long SlotBookingId { get; set; }
public long ServiceProviderId { get; set; }
public string ServiceProviderName { get; set; }
public long ClientId { get; set; }
public string ClientName { get; set; }
public DateTime BookingDate { get; set; }
public string SlotDate { get; set; }
public string SlotStartTime { get; set; }
public string SlotEndTime { get; set; }
public int CatBookingStatusId { get; set; }
public string BookingStatus { get; set; }
public int CatCommunicationTypeId { get; set; }
public string CommunicationType { get; set; }
public decimal ConsultationFee { get; set; }
public string VideoSDKMeetingId { get; set; }
public bool IsRated { get; set; }
public decimal? Rating { get; set; }
public string RatingComments { get; set; }
}
BookingOrder.cs¶
public class BookingOrder
{
public long SlotBookingPayForId { get; set; }
public long SlotBookingId { get; set; }
public decimal TotalAmount { get; set; }
public decimal PlatformFee { get; set; }
public decimal VATAmount { get; set; }
public decimal NetAmount { get; set; }
public int CatPaymentStatusId { get; set; }
public string PaymentStatus { get; set; }
public string TransactionId { get; set; }
public DateTime? PaymentDate { get; set; }
public int CatPaymentMethodId { get; set; }
public string PaymentMethod { get; set; }
}
Screening Models¶
CatScreeningQuestion.cs¶
public class CatScreeningQuestion
{
public int ScreeningQuestionId { get; set; }
public string QuestionPLang { get; set; }
public string QuestionSLang { get; set; }
public int QuestionType { get; set; } // 1=Single Choice, 2=Multiple Choice, 3=Text
public int DisplayOrder { get; set; }
public bool IsActive { get; set; }
public List<CatScreeningQuestinOption> Options { get; set; }
}
CatScreeningQuestinOption.cs¶
public class CatScreeningQuestinOption
{
public int CatScreeningQuetionOptionId { get; set; }
public int ScreeningQuestionId { get; set; }
public string OptionTextPLang { get; set; }
public string OptionTextSLang { get; set; }
public int OptionValue { get; set; }
public int DisplayOrder { get; set; }
}
Catalogue Models¶
CatSpeciality.cs¶
public class CatSpeciality
{
public long CatSpecialityId { get; set; }
public string SpecialityNamePLang { get; set; }
public string SpecialityNameSLang { get; set; }
public string SpecialityDescriptionPLang { get; set; }
public string SpecialityDescriptionSLang { get; set; }
public string IconPath { get; set; }
public bool IsActive { get; set; }
public int DisplayOrder { get; set; }
}
Language.cs¶
public class Language
{
public long LanguageId { get; set; }
public string LanguageNamePLang { get; set; }
public string LanguageNameSLang { get; set; }
public string LanguageCode { get; set; } // ar, en, fr, etc.
public bool IsActive { get; set; }
}
Country.cs¶
public class Country
{
public long CountryId { get; set; }
public string CountryNamePLang { get; set; }
public string CountryNameSLang { get; set; }
public string CountryCode { get; set; } // SA, AE, EG, etc.
public string PhoneCode { get; set; } // +966, +971, +20, etc.
public bool IsActive { get; set; }
}
City.cs¶
public class City
{
public long CityId { get; set; }
public long CountryId { get; set; }
public string CityNamePLang { get; set; }
public string CityNameSLang { get; set; }
public bool IsActive { get; set; }
}
📊 Enumerations¶
Enumeration.cs¶
Purpose: Define all system enumerations
public class Enumeration
{
// User Types
public enum UserTypes
{
Patient = 1,
CareProvider = 2,
Admin = 3,
Accountant = 4,
ContentManager = 5,
SEOManager = 6,
MarketingManager = 7,
Organization = 8
}
// Gender Types
public enum GenderTypes
{
Male = 1,
Female = 2,
Other = 3
}
// Booking Status
public enum BookingStatus
{
Pending = 1,
Confirmed = 2,
Cancelled = 3,
Completed = 4,
NoShow = 5,
Rescheduled = 6
}
// Payment Status
public enum PaymentStatus
{
Pending = 1,
Completed = 2,
Failed = 3,
Refunded = 4,
PartiallyRefunded = 5
}
// Communication Types
public enum CommunicationTypes
{
InPerson = 1,
VideoCall = 2,
AudioCall = 3,
Chat = 4
}
// Profile Components
public enum ProfileComponents
{
PersonalInformation = 1,
ContactInformation = 2,
Education = 3,
Experience = 4,
Documents = 5,
Specialties = 6,
Languages = 7,
Availability = 8
}
// Document Types
public enum DocumentTypes
{
ProfileImage = 1,
CV = 2,
License = 3,
Certificate = 4,
IdentityDocument = 5
}
// Notification Types
public enum NotificationTypes
{
BookingConfirmed = 1,
BookingCancelled = 2,
BookingReminder = 3,
PaymentReceived = 4,
MessageReceived = 5,
ProfileUpdated = 6
}
}
🌐 Request/Response Models¶
Request Models (Models/Request/)¶
UserRegistrationRequest.cs¶
public class UserRegistrationRequest
{
public string Email { get; set; }
public string Password { get; set; }
public string ConfirmPassword { get; set; }
public int UserTypeId { get; set; }
public string FirstNamePLang { get; set; }
public string LastNamePLang { get; set; }
public string MobileNumber { get; set; }
public int? GenderType { get; set; }
public DateTime? DateOfBirth { get; set; }
public long? CountryId { get; set; }
public long? CityId { get; set; }
public List<int> SpecialityIds { get; set; } // For providers
public List<int> LanguageIds { get; set; } // For providers
}
BookingRequest.cs¶
public class BookingRequest
{
public long ServiceProviderId { get; set; }
public string SlotDate { get; set; }
public string SlotStartTime { get; set; }
public string SlotEndTime { get; set; }
public long SlotAvailabilityId { get; set; }
public int CatCommunicationTypeId { get; set; }
public string BookingNotes { get; set; }
public int PaymentMethodId { get; set; }
public string PromoCode { get; set; }
}
Response Models (Models/Response/)¶
BaseResponse.cs¶
public class BaseResponse
{
public int Status { get; set; } // 1=Success, 0=Error
public string Reason { get; set; }
public string Message { get; set; }
public object Data { get; set; }
}
UserRegistrationResponse.cs¶
public class UserRegistrationResponse : BaseResponse
{
public new UserRegistrationData Data { get; set; }
}
public class UserRegistrationData
{
public long UserLoginInfoId { get; set; }
public string Email { get; set; }
public string VerificationCode { get; set; }
public bool RequiresVerification { get; set; }
}
ScreenigQuestionOptionResponse.cs¶
public class ScreenigQuestionOptionResponse : BaseResponse
{
public new List<CatScreeningQuestion> ScreeningQuestions { get; set; }
}
🔧 Constants¶
AppConstants.cs¶
public class AppConstants
{
// Languages
public class Languages
{
public const string Arabic = "ar";
public const string English = "en";
}
// Date Formats
public class DateFormats
{
public const string DisplayDate = "dd/MM/yyyy";
public const string DisplayDateTime = "dd/MM/yyyy hh:mm tt";
public const string ApiDate = "yyyy-MM-dd";
public const string ApiDateTime = "yyyy-MM-dd HH:mm:ss";
}
// Session Timeout
public const int SessionTimeoutMinutes = 30;
public const int SessionWarningSeconds = 180;
// Image Settings
public class ImageSettings
{
public const int MaxFileSizeKB = 5120; // 5MB
public const string AllowedExtensions = ".jpg,.jpeg,.png,.gif";
public const int ProfileImageWidth = 300;
public const int ProfileImageHeight = 300;
}
// Pagination
public class Pagination
{
public const int DefaultPageSize = 20;
public const int MaxPageSize = 100;
}
// Payment
public class PaymentSettings
{
public const decimal PlatformFeePercentage = 15; // 15%
public const decimal VATPercentage = 15; // 15%
public const decimal MinBookingAmount = 50;
public const decimal MaxBookingAmount = 5000;
}
}
Pages Enumeration¶
public enum Pages
{
Home = 1,
Login = 2,
Register = 3,
ScreeningTest = 4,
ClientIndex = 5,
ServiceProviderIndex = 6,
AdminIndex = 7,
TherapistList = 8,
BookingConfirmation = 9,
MyAppointments = 10
}
🚀 Application Lifecycle¶
Global.asax.cs¶
Application Events:
public class MvcApplication : System.Web.HttpApplication
{
// Application startup
protected void Application_Start()
{
// Register areas
AreaRegistration.RegisterAllAreas();
// Register global filters
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
// Register routes
RouteConfig.RegisterRoutes(RouteTable.Routes);
// Register bundles (CSS/JS)
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
// New session started
protected void Session_Start(Object sender, EventArgs e)
{
// Initialize common data
Common.Common.initCommonData();
// Get API authentication tokens
Common.APIAuthenticationToken.GetAPIAuthenticationTokens();
}
// Request started
protected void Application_BeginRequest()
{
// Disable caching
Response.Cache.SetCacheability(HttpCacheability.NoCache);
Response.Cache.SetExpires(DateTime.UtcNow.AddHours(-1));
Response.Cache.SetNoStore();
// Fix double slash issue
string requestUrl = Request.ServerVariables["REQUEST_URI"];
string rewriteUrl = Request.ServerVariables["UNENCODED_URL"];
if (rewriteUrl.Contains("//") && !requestUrl.Contains("//"))
{
Response.RedirectPermanent(requestUrl);
}
}
// Before response sent
protected void Application_PreSendRequestHeaders(Object sender, EventArgs e)
{
// Ensure no caching
Response.Cache.SetCacheability(HttpCacheability.NoCache);
}
}
📦 Bundling & Minification¶
BundleConfig.cs¶
CSS Bundles:
public class BundleConfig
{
public static void RegisterBundles(BundleCollection bundles)
{
// jQuery
bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
"~/Scripts/jquery-{version}.js"
));
// Bootstrap
bundles.Add(new ScriptBundle("~/bundles/bootstrap").Include(
"~/Scripts/bootstrap.js"
));
bundles.Add(new StyleBundle("~/Content/bootstrap").Include(
"~/Content/bootstrap.css"
));
// Custom styles
bundles.Add(new StyleBundle("~/Content/css").Include(
"~/Content/site.css",
"~/Content/custom.css"
));
// Custom scripts
bundles.Add(new ScriptBundle("~/bundles/custom").Include(
"~/Scripts/common.js",
"~/Scripts/validation.js",
"~/Scripts/api-client.js"
));
// Enable optimization in production
#if !DEBUG
BundleTable.EnableOptimizations = true;
#endif
}
}
🛣️ Routing¶
RouteConfig.cs¶
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
// Default route
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new
{
controller = "Web",
action = "Home",
id = UrlParameter.Optional
}
);
// SEO-friendly routes
routes.MapRoute(
name: "TherapistProfile",
url: "therapist/{id}/{name}",
defaults: new
{
controller = "Web",
action = "TherapistProfile"
}
);
routes.MapRoute(
name: "BlogPost",
url: "blog/{id}/{slug}",
defaults: new
{
controller = "Web",
action = "BlogPost"
}
);
}
}
END OF PART 1
What’s in Part 1:
✅ Project overview & architecture
✅ Configuration & settings
✅ Data access layer (API integration)
✅ Models (user, booking, screening, catalogues)
✅ Common utilities & helpers
✅ Authentication & session management
✅ Authorization filters
✅ Request/response models
✅ Constants & enumerations
✅ Application lifecycle
✅ Bundling & routing
Coming in Part 2:
- WebController (public website)
- Patient/ClientController (patient portal)
- Physician/ServiceProviderController (provider portal)
- Admin controllers (admin, accountant, content, SEO, marketing)
- Views & Razor templates
- JavaScript & frontend
- Deployment pipeline