From e8c400a20a31ed4ea07c73e508c09690eb2b0083 Mon Sep 17 00:00:00 2001 From: Adam Moussa <166072409+amoussa1229@users.noreply.github.com> Date: Thu, 16 Apr 2026 16:33:42 -0400 Subject: [PATCH] Add null safety checks across all API controllers - Add null checks after FirstOrDefault() before property access - Guard navigation property access (Locations, AssignToUser, ApplicationUser, POC, Contacts) - Null-coalesce string properties in search/filter LINQ queries - Protect Sum() on nullable LineItems collections - Change FirstAsync to FirstOrDefaultAsync with null guard in UserController - Add null check on model.Attachments iteration in EditWorkorder --- .../Controllers/CommonController.cs | 2 +- .../Controllers/ContactController.cs | 4 +- .../Controllers/QuotesController.cs | 12 +-- .../Controllers/SettingsController.cs | 4 +- .../Controllers/UserController.cs | 6 +- .../Controllers/WorkOrderController.cs | 78 ++++++++++--------- 6 files changed, 59 insertions(+), 47 deletions(-) diff --git a/Api.SeaHavenIndustries/Controllers/CommonController.cs b/Api.SeaHavenIndustries/Controllers/CommonController.cs index b891be3..2a44899 100644 --- a/Api.SeaHavenIndustries/Controllers/CommonController.cs +++ b/Api.SeaHavenIndustries/Controllers/CommonController.cs @@ -30,7 +30,7 @@ namespace Api.SeaHavenIndustries.Controllers try { var allUsersQuery = _db.Users - .Where(w => w.FirstName.ToLower().Contains(search.ToLower())) + .Where(w => (w.FirstName ?? "").ToLower().Contains((search ?? "").ToLower())) .OrderByDescending(d => d.Id); var totalCount = allUsersQuery.Count(); diff --git a/Api.SeaHavenIndustries/Controllers/ContactController.cs b/Api.SeaHavenIndustries/Controllers/ContactController.cs index c15a1e9..091b5e9 100644 --- a/Api.SeaHavenIndustries/Controllers/ContactController.cs +++ b/Api.SeaHavenIndustries/Controllers/ContactController.cs @@ -62,6 +62,8 @@ namespace Api.SeaHavenIndustries.Controllers try { var exist = _db.Contacts.Where(w => w.Id == contacts.Id).FirstOrDefault(); + if (exist == null) + return BadRequest(new Response { Status = "Error", Message = "Contact not found" }); exist.Title = contacts.Title; exist.Owner = contacts.Owner; @@ -260,7 +262,7 @@ namespace Api.SeaHavenIndustries.Controllers public IActionResult GetAddressbook(string? search = "", int page = 1, int pageSize = 10) { var totalCount = _db.Locations.Count(); - var data = _db.Locations.Where(w=> w.Name.ToLower().Contains(search.ToLower()) || w.Title.ToLower().Contains(search.ToLower())).Select(n => new + var data = _db.Locations.Where(w=> (w.Name ?? "").ToLower().Contains(search.ToLower()) || (w.Title ?? "").ToLower().Contains(search.ToLower())).Select(n => new { n.Id, n.Name, diff --git a/Api.SeaHavenIndustries/Controllers/QuotesController.cs b/Api.SeaHavenIndustries/Controllers/QuotesController.cs index 8108301..fd3e09e 100644 --- a/Api.SeaHavenIndustries/Controllers/QuotesController.cs +++ b/Api.SeaHavenIndustries/Controllers/QuotesController.cs @@ -158,13 +158,13 @@ namespace Api.SeaHavenIndustries.Controllers public IActionResult GetQuotes(string search = "", int page = 1, int pageSize = 10) { var totalCount = _db.Quotes.Count(); - var data = _db.Quotes.Include(s => s.WorkOrder).AsNoTracking().Where(w => w.QuotId.ToLower().Contains(search.ToLower()) || w.SiteName.ToLower().Contains(search.ToLower()) || w.WorkOrder.WorkerOrderTitle.ToLower().Contains(search.ToLower())).Select(s => new + var data = _db.Quotes.Include(s => s.WorkOrder).AsNoTracking().Where(w => (w.QuotId ?? "").ToLower().Contains(search.ToLower()) || (w.SiteName ?? "").ToLower().Contains(search.ToLower()) || (w.WorkOrder != null && (w.WorkOrder.WorkerOrderTitle ?? "").ToLower().Contains(search.ToLower()))).Select(s => new { s.Id, QuoteId = "QO-" + s.QuotId, WorkOrder = s.WorkOrder != null ? s.WorkOrder.WorkerOrderNumber : "", s.ProjectName, - TotalAmount = "$" + s.LineItems.Sum(s => s.Lineitem.Value).ToString(), + TotalAmount = "$" + (s.LineItems != null ? s.LineItems.Sum(l => l.Lineitem ?? 0) : 0).ToString(), s.SiteName, s.SiteId, s.PO, @@ -201,8 +201,8 @@ namespace Api.SeaHavenIndustries.Controllers s.Id, s.PO, s.TT, - s.Locations.Title, - s.Locations.Name + Title = s.Locations != null ? s.Locations.Title : "", + Name = s.Locations != null ? s.Locations.Name : "" }).ToListAsync(); return Ok(data); } @@ -216,7 +216,7 @@ namespace Api.SeaHavenIndustries.Controllers s.Id, s.QuotId, s.Date, - Name = (s.Contacts.FirstName + " " + s.Contacts.LastName).Trim(), + Name = s.Contacts != null ? (s.Contacts.FirstName + " " + s.Contacts.LastName).Trim() : "", s.PO, s.TT, s.SiteName, @@ -225,7 +225,7 @@ namespace Api.SeaHavenIndustries.Controllers s.BillingAddress, s.ShippingAddress, lineitems = s.LineItems.Select(q => new { q.Descriptions, q.Lineitem }), - TotalAmount = "$" + s.LineItems.Sum(s => s.Lineitem.Value).ToString() + TotalAmount = "$" + (s.LineItems != null ? s.LineItems.Sum(l => l.Lineitem ?? 0) : 0).ToString() }).FirstOrDefaultAsync(); return Ok(data); } diff --git a/Api.SeaHavenIndustries/Controllers/SettingsController.cs b/Api.SeaHavenIndustries/Controllers/SettingsController.cs index 6183727..78e4189 100644 --- a/Api.SeaHavenIndustries/Controllers/SettingsController.cs +++ b/Api.SeaHavenIndustries/Controllers/SettingsController.cs @@ -47,6 +47,8 @@ namespace Api.SeaHavenIndustries.Controllers try { var exist = _db.Categories.Where(w => w.Id == model.Id).FirstOrDefault(); + if (exist == null) + return BadRequest(new Response { Status = "Error", Message = "Category not found" }); exist.Name = model.Name; _db.Categories.Update(exist); await _db.SaveChangesAsync(); @@ -157,7 +159,7 @@ namespace Api.SeaHavenIndustries.Controllers try { var totalCount = _db.Users.Count(); - var allUsers = _db.Users.Where(w => w.FirstName.ToLower().Contains(search.ToLower())).OrderByDescending(d => d.Id).ToList().Skip((page - 1) * pageSize) + var allUsers = _db.Users.Where(w => (w.FirstName ?? "").ToLower().Contains((search ?? "").ToLower())).OrderByDescending(d => d.Id).ToList().Skip((page - 1) * pageSize) .Take(pageSize); // Calculate the number of pages diff --git a/Api.SeaHavenIndustries/Controllers/UserController.cs b/Api.SeaHavenIndustries/Controllers/UserController.cs index f37f652..375649c 100644 --- a/Api.SeaHavenIndustries/Controllers/UserController.cs +++ b/Api.SeaHavenIndustries/Controllers/UserController.cs @@ -94,7 +94,9 @@ namespace Api.SeaHavenIndustries.Controllers else { - var exist1 = await _userManager.Users.FirstAsync(w => w.Id == model.Id); + var exist1 = await _userManager.Users.FirstOrDefaultAsync(w => w.Id == model.Id); + if (exist1 == null) + return BadRequest(new Response { Status = "Error", Message = "User not found" }); var existingRole = await _userManager.GetRolesAsync(exist1); @@ -134,6 +136,8 @@ namespace Api.SeaHavenIndustries.Controllers try { var exist = _db.Users.Where(w => w.Id == user.Id).FirstOrDefault(); + if (exist == null) + return BadRequest(new Response { Status = "Error", Message = "User not found" }); exist.FirstName = user.Name; exist.EmailConfirmed = true; exist.UserName = user.Email; diff --git a/Api.SeaHavenIndustries/Controllers/WorkOrderController.cs b/Api.SeaHavenIndustries/Controllers/WorkOrderController.cs index abd0ceb..c9f62fa 100644 --- a/Api.SeaHavenIndustries/Controllers/WorkOrderController.cs +++ b/Api.SeaHavenIndustries/Controllers/WorkOrderController.cs @@ -180,6 +180,8 @@ namespace Api.SeaHavenIndustries.Controllers try { var workOrder = _db.workOrders.Where(w => w.Id == model.Id).FirstOrDefault(); + if (workOrder == null) + return BadRequest(new Response { Status = "Error", Message = "Work order not found" }); workOrder.WorkerOrderNumber = model.WorkerOrderNumber; workOrder.WorkerOrderTitle = model.WorkerOrderTitle; workOrder.Description = model.Description; @@ -227,27 +229,28 @@ namespace Api.SeaHavenIndustries.Controllers #region Images of workorder workOrder.workOrderAttachments = workOrder.workOrderAttachments == null ? new List() : workOrder.workOrderAttachments; - foreach (var attachment in model.Attachments) + if (model.Attachments != null) { - string fileName = ContentDispositionHeaderValue.Parse(attachment.ContentDisposition).FileName.Trim('"'); - string uniqueFileName = $"{Guid.NewGuid()}_{fileName}"; - - string uploadProfilePath = Path.Combine("Assets", "Documents"); - string fullPath = Path.Combine(_webHostEnvironment.WebRootPath, uploadProfilePath, uniqueFileName); - - Directory.CreateDirectory(Path.GetDirectoryName(fullPath)); - - // Copy the file to the server - using (var ms = System.IO.File.Create(fullPath)) + foreach (var attachment in model.Attachments) { - await attachment.CopyToAsync(ms); - } + string fileName = ContentDispositionHeaderValue.Parse(attachment.ContentDisposition).FileName.Trim('"'); + string uniqueFileName = $"{Guid.NewGuid()}_{fileName}"; - // Update the user's image URL - var request = _httpContext.HttpContext.Request; - var domain = $"{request.Scheme}://{request.Host}"; - var imagesPath = domain + "/" + Path.Combine(uploadProfilePath, uniqueFileName); - workOrder.workOrderAttachments.Add(new WorkOrderAttachments { Attachments = imagesPath }); + string uploadProfilePath = Path.Combine("Assets", "Documents"); + string fullPath = Path.Combine(_webHostEnvironment.WebRootPath, uploadProfilePath, uniqueFileName); + + Directory.CreateDirectory(Path.GetDirectoryName(fullPath)); + + using (var ms = System.IO.File.Create(fullPath)) + { + await attachment.CopyToAsync(ms); + } + + var request = _httpContext.HttpContext.Request; + var domain = $"{request.Scheme}://{request.Host}"; + var imagesPath = domain + "/" + Path.Combine(uploadProfilePath, uniqueFileName); + workOrder.workOrderAttachments.Add(new WorkOrderAttachments { Attachments = imagesPath }); + } } if (model.BeforPhotoAttachment != null) @@ -455,10 +458,11 @@ namespace Api.SeaHavenIndustries.Controllers if (!string.IsNullOrEmpty(search)) { + var s = search.ToLower(); query = query.Where(w => - w.WorkerOrderTitle.ToLower().Contains(search.ToLower()) || - w.WorkerOrderNumber.ToLower().Contains(search.ToLower()) || - w.Locations.Title.ToLower().Contains(search.ToLower())); + (w.WorkerOrderTitle ?? "").ToLower().Contains(s) || + (w.WorkerOrderNumber ?? "").ToLower().Contains(s) || + (w.Locations != null && (w.Locations.Title ?? "").ToLower().Contains(s))); } if (assignto != null && assignto.Length > 0) @@ -515,7 +519,7 @@ namespace Api.SeaHavenIndustries.Controllers var pagedData = data .Skip((page - 1) * pageSize) .Take(pageSize) - .Select(s => new { s.Id, s.WorkerOrderTitle, s.DueDate, s.Status, s.Priority, s.Locations.Name }) + .Select(s => new { s.Id, s.WorkerOrderTitle, s.DueDate, s.Status, s.Priority, Name = s.Locations != null ? s.Locations.Name : "" }) .OrderByDescending(d => d.Name) .ToList(); @@ -551,16 +555,14 @@ namespace Api.SeaHavenIndustries.Controllers title = s.WorkerOrderTitle, s.DueDate, s.Status, - assignee = s.AssignToUser.FirstName, - location = s.Locations.Name, + assignee = s.AssignToUser != null ? s.AssignToUser.FirstName : "", + location = s.Locations != null ? s.Locations.Name : "", priority = s.Priority, s.PO, s.TT, - poc = s.WorkOrderContacts.Select(s => new { s.Id, name = (s.POC.FirstName + " " + s.POC.LastName).Trim(), email = s.POC.Email, phone = s.POC.PhoneNumber }), - category = s.WorkOrderCategories.Select(s => new { s.Id, name = s.Category.Name }), - Attachments = s.workOrderAttachments.Select(s => new { s.Id, attachment = s.Attachments }), - //category = s.Category.Name, - //s.Attachments, + poc = s.WorkOrderContacts.Select(c => new { c.Id, name = c.POC != null ? (c.POC.FirstName + " " + c.POC.LastName).Trim() : "", email = c.POC != null ? c.POC.Email : "", phone = c.POC != null ? c.POC.PhoneNumber : "" }), + category = s.WorkOrderCategories.Select(c => new { c.Id, name = c.Category != null ? c.Category.Name : "" }), + Attachments = s.workOrderAttachments.Select(a => new { a.Id, attachment = a.Attachments }), s.BeforPhotoIssue, s.BeforPhotoAttachment, s.AfterPhotoIssue, @@ -568,7 +570,7 @@ namespace Api.SeaHavenIndustries.Controllers s.SignOffName, s.SignOffAttachment, s.SignOffSignature, - Comments = s.Comments.Select(s => new { s.Id, s.CreatedDate, s.Commenttext, s.Documents, userName = s.ApplicationUser.FirstName }), + Comments = s.Comments.Select(c => new { c.Id, c.CreatedDate, c.Commenttext, c.Documents, userName = c.ApplicationUser != null ? c.ApplicationUser.FirstName : "" }), quotes = s.Quotes.Select(q => new { code = q.QuotId, q.Id }) }).FirstOrDefaultAsync(); if (exist == null) @@ -615,7 +617,7 @@ namespace Api.SeaHavenIndustries.Controllers var currentDate = DateTime.UtcNow.Date; var data = _db.workOrders.Include(w => w.Locations) - .Where(w => w.istemplate != true && ((w.WorkerOrderTitle.ToLower().Contains(search.ToLower()) || w.WorkerOrderNumber.ToLower().Contains(search.ToLower()) || w.Locations.Title.ToLower().Contains(search.ToLower())) && + .Where(w => w.istemplate != true && (((w.WorkerOrderTitle ?? "").ToLower().Contains(search.ToLower()) || (w.WorkerOrderNumber ?? "").ToLower().Contains(search.ToLower()) || (w.Locations != null && (w.Locations.Title ?? "").ToLower().Contains(search.ToLower()))) && (assignto.Count() == 0 || assignto.Contains(w.AssignTo)) && (location.Count() == 0 || (w.LocationId.HasValue && location.Contains(w.LocationId.Value))) && (priority.Count() == 0 || priority.Contains(w.Priority)) && @@ -647,7 +649,7 @@ namespace Api.SeaHavenIndustries.Controllers break; } } - var newdata = data.Select(s => new { s.Id, s.WorkerOrderTitle, s.DueDate, s.Status, s.Priority, s.Locations.Name }).OrderByDescending(d => d.Name).ToList().Skip((page - 1) * pageSize) + var newdata = data.Select(s => new { s.Id, s.WorkerOrderTitle, s.DueDate, s.Status, s.Priority, Name = s.Locations != null ? s.Locations.Name : "" }).OrderByDescending(d => d.Name).ToList().Skip((page - 1) * pageSize) .Take(pageSize); var totalPages1 = (int)Math.Ceiling(totalCount / (double)pageSize); @@ -672,7 +674,7 @@ namespace Api.SeaHavenIndustries.Controllers s.DueDate, s.Status, s.Priority, - s.Locations.Name + Name = s.Locations != null ? s.Locations.Name : "" }).OrderByDescending(d => d.Id).ToList().Skip((page - 1) * pageSize) .Take(pageSize); @@ -711,6 +713,8 @@ namespace Api.SeaHavenIndustries.Controllers try { var data = await _db.workOrders.Where(w => w.Id == id).FirstOrDefaultAsync(); + if (data == null) + return BadRequest(new Response { Status = "Error", Message = "Work order not found" }); data.Status = status; await _db.SaveChangesAsync(); return Ok(new DataResponse { Message = "Updated Successfully", Status = "200", Data = data }); @@ -770,7 +774,7 @@ namespace Api.SeaHavenIndustries.Controllers { s.Id, s.Commenttext, - s.ApplicationUser.FirstName, + FirstName = s.ApplicationUser != null ? s.ApplicationUser.FirstName : "", s.Documents }).ToListAsync(); return Ok(data); @@ -784,7 +788,7 @@ namespace Api.SeaHavenIndustries.Controllers { s.Id, s.Commenttext, - s.ApplicationUser.FirstName, + FirstName = s.ApplicationUser != null ? s.ApplicationUser.FirstName : "", s.Documents }).ToListAsync(); return Ok(data); @@ -815,8 +819,8 @@ namespace Api.SeaHavenIndustries.Controllers WorkerOrderNumber = s.WorkerOrderNumber, WorkerOrderTitle = s.WorkerOrderTitle, DueDate = s.DueDate, - AssignToUserName = s.AssignToUser.FirstName, - LocationName = s.Locations.Name, + AssignToUserName = s.AssignToUser != null ? s.AssignToUser.FirstName : "", + LocationName = s.Locations != null ? s.Locations.Name : "", LocationId = s.LocationId, PO = s.PO, TT = s.TT,