Phases 3 & 4: Complete data access architecture migration
Phase 3 — eliminated ApplicationDbContext from all non-exempt controllers, routing all data access through IUnitOfWork. Added IPlainRepository<T> for the four platform entities (Announcement, BannedIp, DashboardTip, ReleaseNote) that intentionally don't extend BaseEntity and therefore can't use the constrained IRepository<T>. Added permanent-exception comments to the 18 controllers that legitimately retain direct DbContext access (Identity infra, cross-tenant platform ops, bulk streaming exports). Phase 4 — added EnforceDataAccessArchitecture() to Program.cs, a startup gate that reflects over every Controller subclass and throws at boot if any non-exempt controller injects ApplicationDbContext. The app cannot start with a violation. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -10,7 +10,6 @@ using PowderCoating.Application.Interfaces;
|
||||
using PowderCoating.Core.Entities;
|
||||
using PowderCoating.Core.Enums;
|
||||
using PowderCoating.Core.Interfaces;
|
||||
using PowderCoating.Infrastructure.Data;
|
||||
using PowderCoating.Shared.Constants;
|
||||
|
||||
namespace PowderCoating.Web.Controllers;
|
||||
@@ -22,7 +21,6 @@ public class BugReportController : Controller
|
||||
private readonly IMapper _mapper;
|
||||
private readonly ITenantContext _tenantContext;
|
||||
private readonly UserManager<ApplicationUser> _userManager;
|
||||
private readonly ApplicationDbContext _context;
|
||||
private readonly IEmailService _emailService;
|
||||
private readonly IAdminNotificationService _adminNotification;
|
||||
private readonly IAzureBlobStorageService _blobService;
|
||||
@@ -40,7 +38,6 @@ public class BugReportController : Controller
|
||||
IMapper mapper,
|
||||
ITenantContext tenantContext,
|
||||
UserManager<ApplicationUser> userManager,
|
||||
ApplicationDbContext context,
|
||||
IEmailService emailService,
|
||||
IAdminNotificationService adminNotification,
|
||||
IAzureBlobStorageService blobService,
|
||||
@@ -51,7 +48,6 @@ public class BugReportController : Controller
|
||||
_mapper = mapper;
|
||||
_tenantContext = tenantContext;
|
||||
_userManager = userManager;
|
||||
_context = context;
|
||||
_emailService = emailService;
|
||||
_adminNotification = adminNotification;
|
||||
_blobService = blobService;
|
||||
@@ -153,7 +149,7 @@ public class BugReportController : Controller
|
||||
ContentType = file.ContentType,
|
||||
FileSizeBytes = file.Length
|
||||
};
|
||||
_context.BugReportAttachments.Add(attachment);
|
||||
await _unitOfWork.BugReportAttachments.AddAsync(attachment);
|
||||
uploadedCount++;
|
||||
}
|
||||
else
|
||||
@@ -164,7 +160,7 @@ public class BugReportController : Controller
|
||||
}
|
||||
|
||||
if (uploadedCount > 0)
|
||||
await _context.SaveChangesAsync();
|
||||
await _unitOfWork.CompleteAsync();
|
||||
}
|
||||
|
||||
_logger.LogInformation("Bug report #{Id} submitted by {UserName} ({Company}): {Title} with {AttachmentCount} attachment(s)",
|
||||
@@ -211,16 +207,13 @@ public class BugReportController : Controller
|
||||
pageNumber = Math.Max(1, pageNumber);
|
||||
pageSize = pageSize is 10 or 25 or 50 or 100 ? pageSize : 25;
|
||||
|
||||
var query = _context.BugReports
|
||||
.AsNoTracking()
|
||||
.IgnoreQueryFilters()
|
||||
.Where(r => !r.IsDeleted)
|
||||
.AsQueryable();
|
||||
var allReports = (await _unitOfWork.BugReports.GetAllAsync(ignoreQueryFilters: true))
|
||||
.AsEnumerable();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(searchTerm))
|
||||
{
|
||||
var search = searchTerm.ToLower();
|
||||
query = query.Where(r =>
|
||||
allReports = allReports.Where(r =>
|
||||
r.Title.ToLower().Contains(search) ||
|
||||
r.Description.ToLower().Contains(search) ||
|
||||
r.SubmittedByUserName.ToLower().Contains(search));
|
||||
@@ -228,31 +221,31 @@ public class BugReportController : Controller
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(statusFilter) &&
|
||||
Enum.TryParse<BugReportStatus>(statusFilter, out var status))
|
||||
query = query.Where(r => r.Status == status);
|
||||
allReports = allReports.Where(r => r.Status == status);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(priorityFilter) &&
|
||||
Enum.TryParse<BugReportPriority>(priorityFilter, out var priority))
|
||||
query = query.Where(r => r.Priority == priority);
|
||||
allReports = allReports.Where(r => r.Priority == priority);
|
||||
|
||||
query = (sortColumn, sortDirection == "asc") switch
|
||||
allReports = (sortColumn, sortDirection == "asc") switch
|
||||
{
|
||||
("Title", true) => query.OrderBy(r => r.Title),
|
||||
("Title", false) => query.OrderByDescending(r => r.Title),
|
||||
("Status", true) => query.OrderBy(r => r.Status),
|
||||
("Status", false) => query.OrderByDescending(r => r.Status),
|
||||
("Priority", true) => query.OrderBy(r => r.Priority),
|
||||
("Priority", false) => query.OrderByDescending(r => r.Priority),
|
||||
("Submitted", true) => query.OrderBy(r => r.SubmittedByUserName),
|
||||
("Submitted", false) => query.OrderByDescending(r => r.SubmittedByUserName),
|
||||
(_, true) => query.OrderBy(r => r.CreatedAt),
|
||||
_ => query.OrderByDescending(r => r.CreatedAt)
|
||||
("Title", true) => allReports.OrderBy(r => r.Title),
|
||||
("Title", false) => allReports.OrderByDescending(r => r.Title),
|
||||
("Status", true) => allReports.OrderBy(r => r.Status),
|
||||
("Status", false) => allReports.OrderByDescending(r => r.Status),
|
||||
("Priority", true) => allReports.OrderBy(r => r.Priority),
|
||||
("Priority", false) => allReports.OrderByDescending(r => r.Priority),
|
||||
("Submitted", true) => allReports.OrderBy(r => r.SubmittedByUserName),
|
||||
("Submitted", false) => allReports.OrderByDescending(r => r.SubmittedByUserName),
|
||||
(_, true) => allReports.OrderBy(r => r.CreatedAt),
|
||||
_ => allReports.OrderByDescending(r => r.CreatedAt)
|
||||
};
|
||||
|
||||
var totalCount = await query.CountAsync();
|
||||
var items = await query
|
||||
var totalCount = allReports.Count();
|
||||
var items = allReports
|
||||
.Skip((pageNumber - 1) * pageSize)
|
||||
.Take(pageSize)
|
||||
.ToListAsync();
|
||||
.ToList();
|
||||
|
||||
var dtos = _mapper.Map<List<BugReportDto>>(items);
|
||||
|
||||
@@ -291,12 +284,10 @@ public class BugReportController : Controller
|
||||
|
||||
var dto = _mapper.Map<EditBugReportDto>(bugReport);
|
||||
|
||||
var attachments = await _context.BugReportAttachments
|
||||
.AsNoTracking()
|
||||
.IgnoreQueryFilters()
|
||||
.Where(a => a.BugReportId == id && !a.IsDeleted)
|
||||
var attachments = (await _unitOfWork.BugReportAttachments.FindAsync(
|
||||
a => a.BugReportId == id && !a.IsDeleted, ignoreQueryFilters: true))
|
||||
.OrderBy(a => a.CreatedAt)
|
||||
.ToListAsync();
|
||||
.ToList();
|
||||
|
||||
dto.Attachments = _mapper.Map<List<BugReportAttachmentDto>>(attachments);
|
||||
|
||||
@@ -319,10 +310,7 @@ public class BugReportController : Controller
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> Attachment(int id)
|
||||
{
|
||||
var attachment = await _context.BugReportAttachments
|
||||
.AsNoTracking()
|
||||
.IgnoreQueryFilters()
|
||||
.FirstOrDefaultAsync(a => a.Id == id && !a.IsDeleted);
|
||||
var attachment = await _unitOfWork.BugReportAttachments.GetByIdAsync(id, ignoreQueryFilters: true);
|
||||
|
||||
if (attachment == null)
|
||||
return NotFound();
|
||||
|
||||
Reference in New Issue
Block a user