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
This commit is contained in:
Adam Moussa 2026-04-16 16:33:42 -04:00
parent 128ae44a0d
commit e8c400a20a
6 changed files with 59 additions and 47 deletions

View file

@ -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();

View file

@ -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,

View file

@ -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);
}

View file

@ -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

View file

@ -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;

View file

@ -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<WorkOrderAttachments>() : 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,