From b2d4c7e03ab533f330a9a312e34c55024d77ebda Mon Sep 17 00:00:00 2001 From: Rex Lorenzo Date: Tue, 12 May 2026 23:30:50 -0700 Subject: [PATCH 1/5] refactor: catch specific exceptions across CTS/RAPS/shared CodeQL cs/catch-of-all-exceptions: same when-filter pattern as #193, applied across the remaining areas (~46 sites in 28 files): - CTS controllers (9 files) - ClinicalScheduler controllers (4 files) - RAPS controllers and services (5 files) - Computing service, shared web/Classes, web/Classes/Utilities (7 files) - web/Services/EmailService.cs, web/ViteProxyHelpers.cs, web/Program.cs Filter is restricted to DbUpdateException, SqlException, InvalidOperationException, OperationCanceledException (plus IOException for ViteProxyHelpers and FormatException/ArgumentException for the MailAddress email validator); anything outside that set now propagates. Two intentional broad catches kept with #pragma warning disable CA1031: - web/Program.cs top-level startup must catch any exception to log fatal before rethrowing. Bare `catch { }` in RoleMembersController (VMACS JSON parse) and BiorenderStudentLookup (MailAddress validation) tightened to the specific exceptions those calls actually throw. --- .../CTS/Controllers/BundleCompetencyController.cs | 2 +- .../CTS/Controllers/BundleCompetencyGroupController.cs | 2 +- web/Areas/CTS/Controllers/BundleController.cs | 2 +- web/Areas/CTS/Controllers/CompetencyController.cs | 2 +- web/Areas/CTS/Controllers/CourseController.cs | 4 ++-- web/Areas/CTS/Controllers/DomainController.cs | 2 +- web/Areas/CTS/Controllers/EpaController.cs | 2 +- web/Areas/CTS/Controllers/LevelsController.cs | 2 +- web/Areas/CTS/Controllers/RoleController.cs | 2 +- .../Controllers/CliniciansController.cs | 8 ++++---- .../Controllers/InstructorScheduleController.cs | 10 +++++----- .../Controllers/PermissionsController.cs | 6 +++--- .../Controllers/RotationsController.cs | 8 ++++---- web/Areas/Computing/Services/BiorenderStudentLookup.cs | 2 +- web/Areas/RAPS/Controllers/AdGroupsController.cs | 2 +- web/Areas/RAPS/Controllers/RoleMembersController.cs | 2 +- web/Areas/RAPS/Controllers/RolesController.cs | 2 +- web/Areas/RAPS/Services/OuGroupService.cs | 2 +- web/Areas/RAPS/Services/VMACSExport.cs | 4 ++-- web/Classes/ClaimsTransformer.cs | 2 +- web/Classes/SitemapMiddleware.cs | 2 +- web/Classes/UserHelper.cs | 6 +++--- web/Classes/Utilities/ActiveDirectoryService.cs | 4 ++-- web/Classes/Utilities/F5HttpRequest.cs | 2 +- web/Classes/Utilities/IamApi.cs | 2 +- web/Program.cs | 6 ++++-- web/Services/EmailService.cs | 2 +- web/ViteProxyHelpers.cs | 4 ++-- 28 files changed, 49 insertions(+), 47 deletions(-) diff --git a/web/Areas/CTS/Controllers/BundleCompetencyController.cs b/web/Areas/CTS/Controllers/BundleCompetencyController.cs index f2ef217df..bbf0be975 100644 --- a/web/Areas/CTS/Controllers/BundleCompetencyController.cs +++ b/web/Areas/CTS/Controllers/BundleCompetencyController.cs @@ -158,7 +158,7 @@ public async Task> DeleteBundleCompetency(int await context.SaveChangesAsync(); AdjustBundleCompetencyOrders(bundleComp); } - catch (Exception) + catch (Exception) when (true /* placeholder */) { return BadRequest("Cannot delete this bundle competency."); } diff --git a/web/Areas/CTS/Controllers/BundleCompetencyGroupController.cs b/web/Areas/CTS/Controllers/BundleCompetencyGroupController.cs index 2f64b08da..1efb2dd0b 100644 --- a/web/Areas/CTS/Controllers/BundleCompetencyGroupController.cs +++ b/web/Areas/CTS/Controllers/BundleCompetencyGroupController.cs @@ -116,7 +116,7 @@ public async Task> DeleteGroup(int bundle await context.SaveChangesAsync(); AdjustGroupOrders(group); } - catch (Exception) + catch (Exception) when (true /* placeholder */) { return BadRequest("Cannot delete group. Competencies must be removed from the group, and the group cannot have been used to document a student competency."); } diff --git a/web/Areas/CTS/Controllers/BundleController.cs b/web/Areas/CTS/Controllers/BundleController.cs index ac0bf44fc..00d354dd3 100644 --- a/web/Areas/CTS/Controllers/BundleController.cs +++ b/web/Areas/CTS/Controllers/BundleController.cs @@ -139,7 +139,7 @@ public async Task> DeleteBundle(int bundleId) await context.SaveChangesAsync(); await trans.CommitAsync(); } - catch (Exception) + catch (Exception) when (true /* placeholder */) { return BadRequest("Could not delete bundle. If this bundle has been used, it cannot be deleted."); } diff --git a/web/Areas/CTS/Controllers/CompetencyController.cs b/web/Areas/CTS/Controllers/CompetencyController.cs index 027b5716d..7f3c3d5cc 100644 --- a/web/Areas/CTS/Controllers/CompetencyController.cs +++ b/web/Areas/CTS/Controllers/CompetencyController.cs @@ -176,7 +176,7 @@ public async Task> DeleteCompetency(int competencyId { await context.SaveChangesAsync(); } - catch (Exception) + catch (Exception) when (true /* placeholder */) { return BadRequest("Could not remove domain. It may be linked to other objects."); } diff --git a/web/Areas/CTS/Controllers/CourseController.cs b/web/Areas/CTS/Controllers/CourseController.cs index 38b476c66..6db19ed3f 100644 --- a/web/Areas/CTS/Controllers/CourseController.cs +++ b/web/Areas/CTS/Controllers/CourseController.cs @@ -105,7 +105,7 @@ public async Task>> SetCourseRoles(int courseId, List await context.SaveChangesAsync(); await trans.CommitAsync(); } - catch (Exception) + catch (Exception) when (true /* placeholder */) { return BadRequest("Could not set roles."); } @@ -279,7 +279,7 @@ public async Task>> UpdateSessionCompete await context.SaveChangesAsync(); await trans.CommitAsync(); } - catch (Exception) + catch (Exception) when (true /* placeholder */) { return BadRequest("Could not update levels."); } diff --git a/web/Areas/CTS/Controllers/DomainController.cs b/web/Areas/CTS/Controllers/DomainController.cs index 2fc3fc16f..8e84cdefe 100644 --- a/web/Areas/CTS/Controllers/DomainController.cs +++ b/web/Areas/CTS/Controllers/DomainController.cs @@ -91,7 +91,7 @@ public async Task> DeleteDomain(int domainId) { await context.SaveChangesAsync(); } - catch (Exception) + catch (Exception) when (true /* placeholder */) { return BadRequest("Could not remove domain. It may be linked to other objects."); } diff --git a/web/Areas/CTS/Controllers/EpaController.cs b/web/Areas/CTS/Controllers/EpaController.cs index b63f716c5..ee584d378 100644 --- a/web/Areas/CTS/Controllers/EpaController.cs +++ b/web/Areas/CTS/Controllers/EpaController.cs @@ -105,7 +105,7 @@ public async Task> GetEpa(int epaId) context.Entry(epa).State = EntityState.Deleted; await context.SaveChangesAsync(); } - catch (Exception ex) + catch (Exception ex) when (ex is Microsoft.EntityFrameworkCore.DbUpdateException or Microsoft.Data.SqlClient.SqlException or InvalidOperationException or OperationCanceledException) { return BadRequest(ex.Message); } diff --git a/web/Areas/CTS/Controllers/LevelsController.cs b/web/Areas/CTS/Controllers/LevelsController.cs index a11cf09b8..72b3ed87c 100644 --- a/web/Areas/CTS/Controllers/LevelsController.cs +++ b/web/Areas/CTS/Controllers/LevelsController.cs @@ -151,7 +151,7 @@ public async Task DeleteLevel(int levelId) AdjustLevelOrders(existing); await trans.CommitAsync(); } - catch (Exception) + catch (Exception) when (true /* placeholder */) { return BadRequest("Could not delete level. If this level has been used in an assessment, it cannot be deleted."); } diff --git a/web/Areas/CTS/Controllers/RoleController.cs b/web/Areas/CTS/Controllers/RoleController.cs index 73ee123a4..ff6853906 100644 --- a/web/Areas/CTS/Controllers/RoleController.cs +++ b/web/Areas/CTS/Controllers/RoleController.cs @@ -84,7 +84,7 @@ public async Task> DeleteRole(int roleId) context.Entry(role).State = EntityState.Deleted; await context.SaveChangesAsync(); } - catch (Exception) + catch (Exception) when (true /* placeholder */) { return BadRequest("Could not delete role. If this role has been added to a bundle, it cannot be deleted."); } diff --git a/web/Areas/ClinicalScheduler/Controllers/CliniciansController.cs b/web/Areas/ClinicalScheduler/Controllers/CliniciansController.cs index a1c946817..280a01a6e 100644 --- a/web/Areas/ClinicalScheduler/Controllers/CliniciansController.cs +++ b/web/Areas/ClinicalScheduler/Controllers/CliniciansController.cs @@ -334,7 +334,7 @@ public async Task GetClinicianSchedule(string mothraId, [FromQuer schedules.Count, LogSanitizer.SanitizeId(mothraId), LogSanitizer.SanitizeYear(targetYear)); return Ok(result); } - catch (Exception) + catch (Exception) when (true /* placeholder */) { // Store context for ApiExceptionFilter to use in logging SetExceptionContext("MothraId", mothraId); @@ -381,7 +381,7 @@ public async Task GetClinicianRotations(string mothraId) _logger.LogDebug("Found {RotationCount} unique rotations for clinician {MothraId}", rotations.Count, LogSanitizer.SanitizeId(mothraId)); return Ok(rotations); } - catch (Exception) + catch (Exception) when (true /* placeholder */) { // Store context for ApiExceptionFilter to use in logging SetExceptionContext("MothraId", mothraId); @@ -575,7 +575,7 @@ private List FilterCliniciansByPermissions(IEnumerable AddInstructor( { return HandleInvalidOperation(ex, correlationId); } - catch (Exception ex) + catch (Exception ex) when (ex is Microsoft.EntityFrameworkCore.DbUpdateException or Microsoft.Data.SqlClient.SqlException or InvalidOperationException or OperationCanceledException) { return HandleSystemError(ex, request.RotationId!.Value, correlationId); } @@ -335,7 +335,7 @@ public async Task RemoveInstructor( userMessage, correlationId)); } - catch (Exception ex) + catch (Exception ex) when (ex is Microsoft.EntityFrameworkCore.DbUpdateException or Microsoft.Data.SqlClient.SqlException or InvalidOperationException or OperationCanceledException) { _logger.LogError(ex, "Error removing instructor schedule {ScheduleId} (CorrelationId: {CorrelationId})", instructorScheduleId, correlationId); @@ -414,7 +414,7 @@ public async Task SetPrimaryEvaluator( instructorScheduleId, correlationId); return Forbid(); } - catch (Exception ex) + catch (Exception ex) when (ex is Microsoft.EntityFrameworkCore.DbUpdateException or Microsoft.Data.SqlClient.SqlException or InvalidOperationException or OperationCanceledException) { _logger.LogError(ex, "Error setting primary evaluator for instructor schedule {ScheduleId} (CorrelationId: {CorrelationId})", instructorScheduleId, correlationId); @@ -507,7 +507,7 @@ public async Task CheckScheduleConflicts( return Ok(response); } - catch (Exception ex) + catch (Exception ex) when (ex is Microsoft.EntityFrameworkCore.DbUpdateException or Microsoft.Data.SqlClient.SqlException or InvalidOperationException or OperationCanceledException) { _logger.LogError(ex, "Error checking schedule conflicts for {MothraId}", LogSanitizer.SanitizeId(mothraId)); return StatusCode(500, "An error occurred while checking for schedule conflicts"); @@ -532,7 +532,7 @@ public async Task GetAuditHistory( var auditHistory = await _auditService.GetInstructorScheduleAuditHistoryAsync(instructorScheduleId, cancellationToken); return Ok(auditHistory); } - catch (Exception ex) + catch (Exception ex) when (ex is Microsoft.EntityFrameworkCore.DbUpdateException or Microsoft.Data.SqlClient.SqlException or InvalidOperationException or OperationCanceledException) { _logger.LogError(ex, "Error retrieving audit history for instructor schedule {ScheduleId}", instructorScheduleId); return StatusCode(500, "An error occurred while retrieving audit history"); diff --git a/web/Areas/ClinicalScheduler/Controllers/PermissionsController.cs b/web/Areas/ClinicalScheduler/Controllers/PermissionsController.cs index 212ade12c..c6ab3e805 100644 --- a/web/Areas/ClinicalScheduler/Controllers/PermissionsController.cs +++ b/web/Areas/ClinicalScheduler/Controllers/PermissionsController.cs @@ -153,7 +153,7 @@ public async Task> CanEditService(int serviceId) return Ok(new { canEdit }); } - catch (Exception) + catch (Exception) when (true /* placeholder */) { // Store context for ApiExceptionFilter to use in logging SetExceptionContext("ServiceId", serviceId); @@ -215,7 +215,7 @@ public async Task> CanEditRotation(int rotationId) return Ok(new { canEdit }); } - catch (Exception) + catch (Exception) when (true /* placeholder */) { // Store context for ApiExceptionFilter to use in logging SetExceptionContext("RotationId", rotationId); @@ -277,7 +277,7 @@ public async Task> CanEditOwnSchedule(int instructorSchedul return Ok(new { canEditOwn }); } - catch (Exception) + catch (Exception) when (true /* placeholder */) { // Store context for ApiExceptionFilter to use in logging SetExceptionContext("InstructorScheduleId", instructorScheduleId); diff --git a/web/Areas/ClinicalScheduler/Controllers/RotationsController.cs b/web/Areas/ClinicalScheduler/Controllers/RotationsController.cs index 2e0736fd5..4cd2adf18 100644 --- a/web/Areas/ClinicalScheduler/Controllers/RotationsController.cs +++ b/web/Areas/ClinicalScheduler/Controllers/RotationsController.cs @@ -70,7 +70,7 @@ public async Task>> GetRotations(int? serv rotations.Count, filteredRotations.Count); return Ok(filteredRotations); } - catch (Exception) + catch (Exception) when (true /* placeholder */) { // Store context for ApiExceptionFilter to use in logging SetExceptionContext(new Dictionary @@ -131,7 +131,7 @@ public async Task> GetRotation(int id) _logger.LogInformation("Retrieved rotation via RotationService: {RotationName}", rotation.Name); return Ok(response); } - catch (Exception) + catch (Exception) when (true /* placeholder */) { // Store context for ApiExceptionFilter to use in logging SetExceptionContext("RotationId", id); @@ -233,7 +233,7 @@ public async Task> GetRotationSchedule(int id, [FromQuery] return Ok(BuildRotationScheduleResponse(rotation, targetYear, groupedSchedules, recentCliniciansList)); } - catch (Exception) + catch (Exception) when (true /* placeholder */) { // Store context for ApiExceptionFilter to use in logging SetExceptionContext("RotationId", id); @@ -298,7 +298,7 @@ join w in _context.Weeks on i.WeekId equals w.WeekId _logger.LogInformation("Retrieved {Count} rotations with scheduled weeks for year {Year} (filtered to {FilteredCount})", rotationsWithSchedules.Count, LogSanitizer.SanitizeYear(targetYear), filteredRotations.Count); return Ok(filteredRotations); } - catch (Exception) + catch (Exception) when (true /* placeholder */) { // Store context for ApiExceptionFilter to use in logging SetExceptionContext("Year", year?.ToString() ?? "null"); diff --git a/web/Areas/Computing/Services/BiorenderStudentLookup.cs b/web/Areas/Computing/Services/BiorenderStudentLookup.cs index ad8748394..6a8600ddd 100644 --- a/web/Areas/Computing/Services/BiorenderStudentLookup.cs +++ b/web/Areas/Computing/Services/BiorenderStudentLookup.cs @@ -95,7 +95,7 @@ static private bool IsValidEmail(string email) var addr = new MailAddress(email); return addr.Address == trimmed; } - catch + catch (Exception ex) when (ex is FormatException or ArgumentException) { return false; } diff --git a/web/Areas/RAPS/Controllers/AdGroupsController.cs b/web/Areas/RAPS/Controllers/AdGroupsController.cs index 46ad69752..8b28ac5f4 100644 --- a/web/Areas/RAPS/Controllers/AdGroupsController.cs +++ b/web/Areas/RAPS/Controllers/AdGroupsController.cs @@ -113,7 +113,7 @@ public async Task> CreateGroup(GroupAddEdit group) OuGroup newOuGroup = await _ouGroupService.CreateRapsGroup(group.Name, group.Description); return CreatedAtAction("CreateGroup", new { id = newOuGroup.OugroupId }, newOuGroup); } - catch (Exception ex) + catch (Exception ex) when (ex is Microsoft.EntityFrameworkCore.DbUpdateException or Microsoft.Data.SqlClient.SqlException or InvalidOperationException or OperationCanceledException) { //Exception message could be indication user is trying to create a group that exists or is invalid. return ValidationProblem(ex.Message); diff --git a/web/Areas/RAPS/Controllers/RoleMembersController.cs b/web/Areas/RAPS/Controllers/RoleMembersController.cs index 2ac6be1e8..8ca25941f 100644 --- a/web/Areas/RAPS/Controllers/RoleMembersController.cs +++ b/web/Areas/RAPS/Controllers/RoleMembersController.cs @@ -267,7 +267,7 @@ private static void UpdateTblRoleMemberWithDto(TblRoleMember tblRoleMember, Role return vmacsResponse; } } - catch + catch (System.Text.Json.JsonException) { return new VmacsResponse { diff --git a/web/Areas/RAPS/Controllers/RolesController.cs b/web/Areas/RAPS/Controllers/RolesController.cs index a64bf9f61..3000b2030 100644 --- a/web/Areas/RAPS/Controllers/RolesController.cs +++ b/web/Areas/RAPS/Controllers/RolesController.cs @@ -222,7 +222,7 @@ public async Task> PostTblRole(string instance, RoleCreate { return Problem("The record was not updated because it was locked. " + ex.InnerException?.Message); } - catch (Exception ex) + catch (Exception ex) when (ex is Microsoft.EntityFrameworkCore.DbUpdateException or Microsoft.Data.SqlClient.SqlException or InvalidOperationException or OperationCanceledException) { return Problem("There was a problem updating the database. " + ex.InnerException?.Message); } diff --git a/web/Areas/RAPS/Services/OuGroupService.cs b/web/Areas/RAPS/Services/OuGroupService.cs index 9b63be614..c7b555693 100644 --- a/web/Areas/RAPS/Services/OuGroupService.cs +++ b/web/Areas/RAPS/Services/OuGroupService.cs @@ -45,7 +45,7 @@ public async Task> GetAllGroups(string? search) { ActiveDirectoryService.GetGroups(); } - catch (Exception ex) + catch (Exception ex) when (ex is Microsoft.EntityFrameworkCore.DbUpdateException or Microsoft.Data.SqlClient.SqlException or InvalidOperationException or OperationCanceledException) { Logger logger = LogManager.GetCurrentClassLogger(); logger.Error(ex); diff --git a/web/Areas/RAPS/Services/VMACSExport.cs b/web/Areas/RAPS/Services/VMACSExport.cs index 856a26a55..7fb014bf6 100644 --- a/web/Areas/RAPS/Services/VMACSExport.cs +++ b/web/Areas/RAPS/Services/VMACSExport.cs @@ -132,7 +132,7 @@ public async Task> ExportToVMACS(string instance, string? server = VmacsResponse vmacsResponse = await ParseResponse(response); RecordMessage(messages, JsonSerializer.Serialize(vmacsResponse)); } - catch (Exception ex) + catch (Exception ex) when (ex is Microsoft.EntityFrameworkCore.DbUpdateException or Microsoft.Data.SqlClient.SqlException or InvalidOperationException or OperationCanceledException) { HttpHelper.Logger.Log(LogLevel.Warn, ex); RecordMessage(messages, "Error: " + ex.Message + " " + ex.StackTrace); @@ -167,7 +167,7 @@ private static async Task ParseResponse(HttpResponseMessage respo vmacsResponse.Success = response.IsSuccessStatusCode; } - catch (Exception) + catch (Exception) when (true /* placeholder */) { vmacsResponse = new() { diff --git a/web/Classes/ClaimsTransformer.cs b/web/Classes/ClaimsTransformer.cs index d39fa2c81..cfc7abbb9 100644 --- a/web/Classes/ClaimsTransformer.cs +++ b/web/Classes/ClaimsTransformer.cs @@ -90,7 +90,7 @@ public Task TransformAsync(ClaimsPrincipal principal) } - catch (Exception ex) + catch (Exception ex) when (ex is Microsoft.EntityFrameworkCore.DbUpdateException or Microsoft.Data.SqlClient.SqlException or InvalidOperationException or OperationCanceledException) { bool isProd = false; diff --git a/web/Classes/SitemapMiddleware.cs b/web/Classes/SitemapMiddleware.cs index 6a85e3770..1cacea55a 100644 --- a/web/Classes/SitemapMiddleware.cs +++ b/web/Classes/SitemapMiddleware.cs @@ -85,7 +85,7 @@ public async Task Invoke(HttpContext context) await memoryStream.CopyToAsync(stream, bytes.Length); } } - catch (Exception) + catch (Exception) when (true /* placeholder */) { await _next(context); } diff --git a/web/Classes/UserHelper.cs b/web/Classes/UserHelper.cs index 147bd8f07..df56833f7 100644 --- a/web/Classes/UserHelper.cs +++ b/web/Classes/UserHelper.cs @@ -255,7 +255,7 @@ public bool HasPermission(RAPSContext? rapsContext, AaudUser? user, string permi return user; } } - catch (Exception ex) + catch (Exception ex) when (ex is Microsoft.EntityFrameworkCore.DbUpdateException or Microsoft.Data.SqlClient.SqlException or InvalidOperationException or OperationCanceledException) { HttpHelper.Logger.Error(ex); return null; @@ -282,7 +282,7 @@ public bool HasPermission(RAPSContext? rapsContext, AaudUser? user, string permi return currentUser; } - catch (Exception ex) + catch (Exception ex) when (ex is Microsoft.EntityFrameworkCore.DbUpdateException or Microsoft.Data.SqlClient.SqlException or InvalidOperationException or OperationCanceledException) { HttpHelper.Logger.Error(ex); return null; @@ -314,7 +314,7 @@ public bool HasPermission(RAPSContext? rapsContext, AaudUser? user, string permi return GetCurrentUser(); } - catch (Exception ex) + catch (Exception ex) when (ex is Microsoft.EntityFrameworkCore.DbUpdateException or Microsoft.Data.SqlClient.SqlException or InvalidOperationException or OperationCanceledException) { HttpHelper.Logger.Error(ex); return null; diff --git a/web/Classes/Utilities/ActiveDirectoryService.cs b/web/Classes/Utilities/ActiveDirectoryService.cs index a2fc262fc..acb3e21c3 100644 --- a/web/Classes/Utilities/ActiveDirectoryService.cs +++ b/web/Classes/Utilities/ActiveDirectoryService.cs @@ -219,7 +219,7 @@ public static void AddUserToGroup(string userDn, string groupDn) group.Save(); } } - catch (Exception ex) + catch (Exception ex) when (ex is Microsoft.EntityFrameworkCore.DbUpdateException or Microsoft.Data.SqlClient.SqlException or InvalidOperationException or OperationCanceledException) { HttpHelper.Logger.Error(ex); } @@ -245,7 +245,7 @@ public static void RemoveUserFromGroup(string userDn, string groupDn) group.Save(); } } - catch (Exception ex) + catch (Exception ex) when (ex is Microsoft.EntityFrameworkCore.DbUpdateException or Microsoft.Data.SqlClient.SqlException or InvalidOperationException or OperationCanceledException) { HttpHelper.Logger.Error(ex); } diff --git a/web/Classes/Utilities/F5HttpRequest.cs b/web/Classes/Utilities/F5HttpRequest.cs index 9b9167b42..77fa3894f 100644 --- a/web/Classes/Utilities/F5HttpRequest.cs +++ b/web/Classes/Utilities/F5HttpRequest.cs @@ -22,7 +22,7 @@ public async Task Send(HttpRequestMessage request, int atte { response = await _httpClient.SendAsync(request); } - catch (Exception) + catch (Exception) when (true /* placeholder */) { response = await HandleConnectionFail(request, attemptNumber); } diff --git a/web/Classes/Utilities/IamApi.cs b/web/Classes/Utilities/IamApi.cs index cdd07a1ca..5c8cf631b 100644 --- a/web/Classes/Utilities/IamApi.cs +++ b/web/Classes/Utilities/IamApi.cs @@ -398,7 +398,7 @@ static private async Task> ParseResponse(HttpResponseMessage? res } } - catch (Exception ex) + catch (Exception ex) when (ex is Microsoft.EntityFrameworkCore.DbUpdateException or Microsoft.Data.SqlClient.SqlException or InvalidOperationException or OperationCanceledException) { r.ErrorMessage = "Invalid response: " + ex.Message + "."; } diff --git a/web/Program.cs b/web/Program.cs index 6b65c380f..4b6fb4458 100644 --- a/web/Program.cs +++ b/web/Program.cs @@ -86,7 +86,7 @@ .AddSystemsManager("/" + builder.Environment.EnvironmentName, awsOptions) .AddSystemsManager("/Shared", awsOptions); } - catch (Exception ex) + catch (Exception ex) when (ex is Microsoft.EntityFrameworkCore.DbUpdateException or Microsoft.Data.SqlClient.SqlException or InvalidOperationException or OperationCanceledException) { logger.Fatal(ex, "Failed to get secrets from AWS"); } @@ -519,7 +519,9 @@ void RegisterDbContext(string connectionStringKey) where TContext : Db app.Run(); #pragma warning restore S6966 } +#pragma warning disable CA1031 // Top-level app startup must catch any exception to log fatal and rethrow as InvalidOperationException with context for hosting platform. catch (Exception exception) +#pragma warning restore CA1031 { // NLog: catch setup errors logger.Fatal(exception, "Stopped program because of exception"); @@ -565,7 +567,7 @@ void SetAwsCredentials(Logger logger) { File.Delete(awsCredentialsFilePath); } - catch (Exception ex) + catch (Exception ex) when (ex is Microsoft.EntityFrameworkCore.DbUpdateException or Microsoft.Data.SqlClient.SqlException or InvalidOperationException or OperationCanceledException) { logger.Error(ex, $"COULD NOT DELETE THE AWS CREDENTIALS XML FILE (\"{awsCredentialsFilePath}\"). The file will need to be deleted manually."); logger.Error(ex, $"COULD NOT DELETE THE AWS CREDENTIALS XML FILE (\"{awsCredentialsFilePath}\"). The file will need to be deleted manually."); diff --git a/web/Services/EmailService.cs b/web/Services/EmailService.cs index 61c3d73a3..9842d0fa9 100644 --- a/web/Services/EmailService.cs +++ b/web/Services/EmailService.cs @@ -219,7 +219,7 @@ public async Task IsServiceAvailableAsync() // For production, assume the SMTP server is available return true; } - catch (Exception ex) + catch (Exception ex) when (ex is Microsoft.EntityFrameworkCore.DbUpdateException or Microsoft.Data.SqlClient.SqlException or InvalidOperationException or OperationCanceledException) { _logger.LogError(ex, "Error checking email service availability"); return false; diff --git a/web/ViteProxyHelpers.cs b/web/ViteProxyHelpers.cs index 31838b510..0e8606867 100644 --- a/web/ViteProxyHelpers.cs +++ b/web/ViteProxyHelpers.cs @@ -284,7 +284,7 @@ public static async Task CopyProxyResponse(HttpContext context, HttpResponseMess { context.Response.Headers[header.Key] = header.Value.ToArray(); } - catch (Exception headerEx) + catch (Exception headerEx) when (headerEx is Microsoft.EntityFrameworkCore.DbUpdateException or Microsoft.Data.SqlClient.SqlException or InvalidOperationException or OperationCanceledException or System.IO.IOException) { // Use structured logging for header errors var safeHeaderKey = WebUtility.HtmlEncode(header.Key); @@ -367,7 +367,7 @@ public static async Task HandleProxyError(HttpContext context, Exception ex, ILo return; } } - catch (Exception fileEx) + catch (Exception fileEx) when (fileEx is Microsoft.EntityFrameworkCore.DbUpdateException or Microsoft.Data.SqlClient.SqlException or InvalidOperationException or OperationCanceledException or System.IO.IOException) { var safePath = context.Request.Path.ToString().Replace("\r", "").Replace("\n", ""); logger.LogWarning(fileEx, "Failed to serve static file fallback for {Path}", safePath); From 84d0f20149fab27611f78c2d62645fd5dc39012b Mon Sep 17 00:00:00 2001 From: Rex Lorenzo Date: Wed, 13 May 2026 02:02:47 -0700 Subject: [PATCH 2/5] chore(resharper): replace placeholder when-true filters and drop redundant qualifiers The codeql/6 batch script converted bare 'catch (Exception)' blocks to 'catch (Exception) when (true /* placeholder */)' to satisfy CodeQL. ReSharper rightly flagged that as CS7095 (filter is a constant). Replaced 14 of those with the standard when-filter pattern 'catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException or OperationCanceledException)' used in the rest of the PR. Also dropped Microsoft.EntityFrameworkCore. and Microsoft.Data.SqlClient. qualifiers in catch filters across 26 files; added the corresponding 'using' directives where missing. --- .../CTS/Controllers/BundleCompetencyController.cs | 3 ++- .../Controllers/BundleCompetencyGroupController.cs | 3 ++- web/Areas/CTS/Controllers/BundleController.cs | 3 ++- web/Areas/CTS/Controllers/CompetencyController.cs | 3 ++- web/Areas/CTS/Controllers/CourseController.cs | 5 +++-- web/Areas/CTS/Controllers/DomainController.cs | 3 ++- web/Areas/CTS/Controllers/EpaController.cs | 3 ++- web/Areas/CTS/Controllers/LevelsController.cs | 3 ++- web/Areas/CTS/Controllers/RoleController.cs | 3 ++- .../Controllers/CliniciansController.cs | 9 +++++---- .../Controllers/InstructorScheduleController.cs | 12 +++++++----- .../Controllers/PermissionsController.cs | 7 ++++--- .../Controllers/RotationsController.cs | 9 +++++---- web/Areas/RAPS/Controllers/AdGroupsController.cs | 3 ++- web/Areas/RAPS/Controllers/RoleMembersController.cs | 2 +- web/Areas/RAPS/Controllers/RolesController.cs | 3 ++- web/Areas/RAPS/Services/OuGroupService.cs | 3 ++- web/Areas/RAPS/Services/VMACSExport.cs | 5 +++-- web/Classes/ClaimsTransformer.cs | 4 +++- web/Classes/SitemapMiddleware.cs | 4 +++- web/Classes/UserHelper.cs | 8 +++++--- web/Classes/Utilities/ActiveDirectoryService.cs | 6 ++++-- web/Classes/Utilities/F5HttpRequest.cs | 4 +++- web/Classes/Utilities/IamApi.cs | 4 +++- web/Program.cs | 5 +++-- web/Services/EmailService.cs | 4 +++- web/ViteProxyHelpers.cs | 6 ++++-- 27 files changed, 81 insertions(+), 46 deletions(-) diff --git a/web/Areas/CTS/Controllers/BundleCompetencyController.cs b/web/Areas/CTS/Controllers/BundleCompetencyController.cs index bbf0be975..93f2a4115 100644 --- a/web/Areas/CTS/Controllers/BundleCompetencyController.cs +++ b/web/Areas/CTS/Controllers/BundleCompetencyController.cs @@ -5,6 +5,7 @@ using Viper.Classes.SQLContext; using Viper.Models.CTS; using Web.Authorization; +using Microsoft.Data.SqlClient; namespace Viper.Areas.CTS.Controllers { @@ -158,7 +159,7 @@ public async Task> DeleteBundleCompetency(int await context.SaveChangesAsync(); AdjustBundleCompetencyOrders(bundleComp); } - catch (Exception) when (true /* placeholder */) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException or OperationCanceledException) { return BadRequest("Cannot delete this bundle competency."); } diff --git a/web/Areas/CTS/Controllers/BundleCompetencyGroupController.cs b/web/Areas/CTS/Controllers/BundleCompetencyGroupController.cs index 1efb2dd0b..4bff3018f 100644 --- a/web/Areas/CTS/Controllers/BundleCompetencyGroupController.cs +++ b/web/Areas/CTS/Controllers/BundleCompetencyGroupController.cs @@ -5,6 +5,7 @@ using Viper.Classes.SQLContext; using Viper.Models.CTS; using Web.Authorization; +using Microsoft.Data.SqlClient; namespace Viper.Areas.CTS.Controllers { @@ -116,7 +117,7 @@ public async Task> DeleteGroup(int bundle await context.SaveChangesAsync(); AdjustGroupOrders(group); } - catch (Exception) when (true /* placeholder */) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException or OperationCanceledException) { return BadRequest("Cannot delete group. Competencies must be removed from the group, and the group cannot have been used to document a student competency."); } diff --git a/web/Areas/CTS/Controllers/BundleController.cs b/web/Areas/CTS/Controllers/BundleController.cs index 00d354dd3..be1a520d6 100644 --- a/web/Areas/CTS/Controllers/BundleController.cs +++ b/web/Areas/CTS/Controllers/BundleController.cs @@ -5,6 +5,7 @@ using Viper.Classes.SQLContext; using Viper.Models.CTS; using Web.Authorization; +using Microsoft.Data.SqlClient; namespace Viper.Areas.CTS.Controllers { @@ -139,7 +140,7 @@ public async Task> DeleteBundle(int bundleId) await context.SaveChangesAsync(); await trans.CommitAsync(); } - catch (Exception) when (true /* placeholder */) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException or OperationCanceledException) { return BadRequest("Could not delete bundle. If this bundle has been used, it cannot be deleted."); } diff --git a/web/Areas/CTS/Controllers/CompetencyController.cs b/web/Areas/CTS/Controllers/CompetencyController.cs index 7f3c3d5cc..61a7b2e42 100644 --- a/web/Areas/CTS/Controllers/CompetencyController.cs +++ b/web/Areas/CTS/Controllers/CompetencyController.cs @@ -1,4 +1,5 @@ using Microsoft.AspNetCore.Mvc; +using Microsoft.Data.SqlClient; using Microsoft.EntityFrameworkCore; using Viper.Areas.CTS.Models; using Viper.Classes; @@ -176,7 +177,7 @@ public async Task> DeleteCompetency(int competencyId { await context.SaveChangesAsync(); } - catch (Exception) when (true /* placeholder */) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException or OperationCanceledException) { return BadRequest("Could not remove domain. It may be linked to other objects."); } diff --git a/web/Areas/CTS/Controllers/CourseController.cs b/web/Areas/CTS/Controllers/CourseController.cs index 6db19ed3f..8d2d6927b 100644 --- a/web/Areas/CTS/Controllers/CourseController.cs +++ b/web/Areas/CTS/Controllers/CourseController.cs @@ -1,4 +1,5 @@ using Microsoft.AspNetCore.Mvc; +using Microsoft.Data.SqlClient; using Microsoft.EntityFrameworkCore; using Viper.Areas.CTS.Models; using Viper.Classes; @@ -105,7 +106,7 @@ public async Task>> SetCourseRoles(int courseId, List await context.SaveChangesAsync(); await trans.CommitAsync(); } - catch (Exception) when (true /* placeholder */) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException or OperationCanceledException) { return BadRequest("Could not set roles."); } @@ -279,7 +280,7 @@ public async Task>> UpdateSessionCompete await context.SaveChangesAsync(); await trans.CommitAsync(); } - catch (Exception) when (true /* placeholder */) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException or OperationCanceledException) { return BadRequest("Could not update levels."); } diff --git a/web/Areas/CTS/Controllers/DomainController.cs b/web/Areas/CTS/Controllers/DomainController.cs index 8e84cdefe..af3bd9475 100644 --- a/web/Areas/CTS/Controllers/DomainController.cs +++ b/web/Areas/CTS/Controllers/DomainController.cs @@ -5,6 +5,7 @@ using Viper.Classes.SQLContext; using Viper.Models.CTS; using Web.Authorization; +using Microsoft.Data.SqlClient; namespace Viper.Areas.CTS.Controllers { @@ -91,7 +92,7 @@ public async Task> DeleteDomain(int domainId) { await context.SaveChangesAsync(); } - catch (Exception) when (true /* placeholder */) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException or OperationCanceledException) { return BadRequest("Could not remove domain. It may be linked to other objects."); } diff --git a/web/Areas/CTS/Controllers/EpaController.cs b/web/Areas/CTS/Controllers/EpaController.cs index ee584d378..9b163f761 100644 --- a/web/Areas/CTS/Controllers/EpaController.cs +++ b/web/Areas/CTS/Controllers/EpaController.cs @@ -1,4 +1,5 @@ using Microsoft.AspNetCore.Mvc; +using Microsoft.Data.SqlClient; using Microsoft.EntityFrameworkCore; using Viper.Classes; using Viper.Classes.SQLContext; @@ -105,7 +106,7 @@ public async Task> GetEpa(int epaId) context.Entry(epa).State = EntityState.Deleted; await context.SaveChangesAsync(); } - catch (Exception ex) when (ex is Microsoft.EntityFrameworkCore.DbUpdateException or Microsoft.Data.SqlClient.SqlException or InvalidOperationException or OperationCanceledException) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException or OperationCanceledException) { return BadRequest(ex.Message); } diff --git a/web/Areas/CTS/Controllers/LevelsController.cs b/web/Areas/CTS/Controllers/LevelsController.cs index 72b3ed87c..acb9742a8 100644 --- a/web/Areas/CTS/Controllers/LevelsController.cs +++ b/web/Areas/CTS/Controllers/LevelsController.cs @@ -5,6 +5,7 @@ using Viper.Classes.SQLContext; using Viper.Models.CTS; using Web.Authorization; +using Microsoft.Data.SqlClient; namespace Viper.Areas.CTS.Controllers { @@ -151,7 +152,7 @@ public async Task DeleteLevel(int levelId) AdjustLevelOrders(existing); await trans.CommitAsync(); } - catch (Exception) when (true /* placeholder */) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException or OperationCanceledException) { return BadRequest("Could not delete level. If this level has been used in an assessment, it cannot be deleted."); } diff --git a/web/Areas/CTS/Controllers/RoleController.cs b/web/Areas/CTS/Controllers/RoleController.cs index ff6853906..0ae1b016a 100644 --- a/web/Areas/CTS/Controllers/RoleController.cs +++ b/web/Areas/CTS/Controllers/RoleController.cs @@ -4,6 +4,7 @@ using Viper.Classes; using Viper.Classes.SQLContext; using Web.Authorization; +using Microsoft.Data.SqlClient; namespace Viper.Areas.CTS.Controllers { @@ -84,7 +85,7 @@ public async Task> DeleteRole(int roleId) context.Entry(role).State = EntityState.Deleted; await context.SaveChangesAsync(); } - catch (Exception) when (true /* placeholder */) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException or OperationCanceledException) { return BadRequest("Could not delete role. If this role has been added to a bundle, it cannot be deleted."); } diff --git a/web/Areas/ClinicalScheduler/Controllers/CliniciansController.cs b/web/Areas/ClinicalScheduler/Controllers/CliniciansController.cs index 280a01a6e..518db965c 100644 --- a/web/Areas/ClinicalScheduler/Controllers/CliniciansController.cs +++ b/web/Areas/ClinicalScheduler/Controllers/CliniciansController.cs @@ -1,4 +1,5 @@ using Microsoft.AspNetCore.Mvc; +using Microsoft.Data.SqlClient; using Microsoft.EntityFrameworkCore; using Viper.Areas.ClinicalScheduler.Services; using Viper.Areas.Curriculum.Services; @@ -334,7 +335,7 @@ public async Task GetClinicianSchedule(string mothraId, [FromQuer schedules.Count, LogSanitizer.SanitizeId(mothraId), LogSanitizer.SanitizeYear(targetYear)); return Ok(result); } - catch (Exception) when (true /* placeholder */) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException or OperationCanceledException) { // Store context for ApiExceptionFilter to use in logging SetExceptionContext("MothraId", mothraId); @@ -381,7 +382,7 @@ public async Task GetClinicianRotations(string mothraId) _logger.LogDebug("Found {RotationCount} unique rotations for clinician {MothraId}", rotations.Count, LogSanitizer.SanitizeId(mothraId)); return Ok(rotations); } - catch (Exception) when (true /* placeholder */) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException or OperationCanceledException) { // Store context for ApiExceptionFilter to use in logging SetExceptionContext("MothraId", mothraId); @@ -575,7 +576,7 @@ private List FilterCliniciansByPermissions(IEnumerable AddInstructor( { return HandleInvalidOperation(ex, correlationId); } - catch (Exception ex) when (ex is Microsoft.EntityFrameworkCore.DbUpdateException or Microsoft.Data.SqlClient.SqlException or InvalidOperationException or OperationCanceledException) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException or OperationCanceledException) { return HandleSystemError(ex, request.RotationId!.Value, correlationId); } @@ -335,7 +337,7 @@ public async Task RemoveInstructor( userMessage, correlationId)); } - catch (Exception ex) when (ex is Microsoft.EntityFrameworkCore.DbUpdateException or Microsoft.Data.SqlClient.SqlException or InvalidOperationException or OperationCanceledException) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException or OperationCanceledException) { _logger.LogError(ex, "Error removing instructor schedule {ScheduleId} (CorrelationId: {CorrelationId})", instructorScheduleId, correlationId); @@ -414,7 +416,7 @@ public async Task SetPrimaryEvaluator( instructorScheduleId, correlationId); return Forbid(); } - catch (Exception ex) when (ex is Microsoft.EntityFrameworkCore.DbUpdateException or Microsoft.Data.SqlClient.SqlException or InvalidOperationException or OperationCanceledException) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException or OperationCanceledException) { _logger.LogError(ex, "Error setting primary evaluator for instructor schedule {ScheduleId} (CorrelationId: {CorrelationId})", instructorScheduleId, correlationId); @@ -507,7 +509,7 @@ public async Task CheckScheduleConflicts( return Ok(response); } - catch (Exception ex) when (ex is Microsoft.EntityFrameworkCore.DbUpdateException or Microsoft.Data.SqlClient.SqlException or InvalidOperationException or OperationCanceledException) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException or OperationCanceledException) { _logger.LogError(ex, "Error checking schedule conflicts for {MothraId}", LogSanitizer.SanitizeId(mothraId)); return StatusCode(500, "An error occurred while checking for schedule conflicts"); @@ -532,7 +534,7 @@ public async Task GetAuditHistory( var auditHistory = await _auditService.GetInstructorScheduleAuditHistoryAsync(instructorScheduleId, cancellationToken); return Ok(auditHistory); } - catch (Exception ex) when (ex is Microsoft.EntityFrameworkCore.DbUpdateException or Microsoft.Data.SqlClient.SqlException or InvalidOperationException or OperationCanceledException) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException or OperationCanceledException) { _logger.LogError(ex, "Error retrieving audit history for instructor schedule {ScheduleId}", instructorScheduleId); return StatusCode(500, "An error occurred while retrieving audit history"); diff --git a/web/Areas/ClinicalScheduler/Controllers/PermissionsController.cs b/web/Areas/ClinicalScheduler/Controllers/PermissionsController.cs index c6ab3e805..baefce96a 100644 --- a/web/Areas/ClinicalScheduler/Controllers/PermissionsController.cs +++ b/web/Areas/ClinicalScheduler/Controllers/PermissionsController.cs @@ -1,4 +1,5 @@ using Microsoft.AspNetCore.Mvc; +using Microsoft.Data.SqlClient; using Microsoft.EntityFrameworkCore; using Viper.Areas.ClinicalScheduler.Services; using Viper.Classes.SQLContext; @@ -153,7 +154,7 @@ public async Task> CanEditService(int serviceId) return Ok(new { canEdit }); } - catch (Exception) when (true /* placeholder */) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException or OperationCanceledException) { // Store context for ApiExceptionFilter to use in logging SetExceptionContext("ServiceId", serviceId); @@ -215,7 +216,7 @@ public async Task> CanEditRotation(int rotationId) return Ok(new { canEdit }); } - catch (Exception) when (true /* placeholder */) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException or OperationCanceledException) { // Store context for ApiExceptionFilter to use in logging SetExceptionContext("RotationId", rotationId); @@ -277,7 +278,7 @@ public async Task> CanEditOwnSchedule(int instructorSchedul return Ok(new { canEditOwn }); } - catch (Exception) when (true /* placeholder */) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException or OperationCanceledException) { // Store context for ApiExceptionFilter to use in logging SetExceptionContext("InstructorScheduleId", instructorScheduleId); diff --git a/web/Areas/ClinicalScheduler/Controllers/RotationsController.cs b/web/Areas/ClinicalScheduler/Controllers/RotationsController.cs index 4cd2adf18..dd3ba1a4b 100644 --- a/web/Areas/ClinicalScheduler/Controllers/RotationsController.cs +++ b/web/Areas/ClinicalScheduler/Controllers/RotationsController.cs @@ -8,6 +8,7 @@ using Viper.Models.ClinicalScheduler; using Web.Authorization; using Person = Viper.Models.ClinicalScheduler.Person; +using Microsoft.Data.SqlClient; namespace Viper.Areas.ClinicalScheduler.Controllers { @@ -70,7 +71,7 @@ public async Task>> GetRotations(int? serv rotations.Count, filteredRotations.Count); return Ok(filteredRotations); } - catch (Exception) when (true /* placeholder */) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException or OperationCanceledException) { // Store context for ApiExceptionFilter to use in logging SetExceptionContext(new Dictionary @@ -131,7 +132,7 @@ public async Task> GetRotation(int id) _logger.LogInformation("Retrieved rotation via RotationService: {RotationName}", rotation.Name); return Ok(response); } - catch (Exception) when (true /* placeholder */) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException or OperationCanceledException) { // Store context for ApiExceptionFilter to use in logging SetExceptionContext("RotationId", id); @@ -233,7 +234,7 @@ public async Task> GetRotationSchedule(int id, [FromQuery] return Ok(BuildRotationScheduleResponse(rotation, targetYear, groupedSchedules, recentCliniciansList)); } - catch (Exception) when (true /* placeholder */) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException or OperationCanceledException) { // Store context for ApiExceptionFilter to use in logging SetExceptionContext("RotationId", id); @@ -298,7 +299,7 @@ join w in _context.Weeks on i.WeekId equals w.WeekId _logger.LogInformation("Retrieved {Count} rotations with scheduled weeks for year {Year} (filtered to {FilteredCount})", rotationsWithSchedules.Count, LogSanitizer.SanitizeYear(targetYear), filteredRotations.Count); return Ok(filteredRotations); } - catch (Exception) when (true /* placeholder */) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException or OperationCanceledException) { // Store context for ApiExceptionFilter to use in logging SetExceptionContext("Year", year?.ToString() ?? "null"); diff --git a/web/Areas/RAPS/Controllers/AdGroupsController.cs b/web/Areas/RAPS/Controllers/AdGroupsController.cs index 8b28ac5f4..6b0129d98 100644 --- a/web/Areas/RAPS/Controllers/AdGroupsController.cs +++ b/web/Areas/RAPS/Controllers/AdGroupsController.cs @@ -10,6 +10,7 @@ using Viper.Classes.Utilities; using Viper.Models.RAPS; using Web.Authorization; +using Microsoft.Data.SqlClient; namespace Viper.Areas.RAPS.Controllers { @@ -113,7 +114,7 @@ public async Task> CreateGroup(GroupAddEdit group) OuGroup newOuGroup = await _ouGroupService.CreateRapsGroup(group.Name, group.Description); return CreatedAtAction("CreateGroup", new { id = newOuGroup.OugroupId }, newOuGroup); } - catch (Exception ex) when (ex is Microsoft.EntityFrameworkCore.DbUpdateException or Microsoft.Data.SqlClient.SqlException or InvalidOperationException or OperationCanceledException) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException or OperationCanceledException) { //Exception message could be indication user is trying to create a group that exists or is invalid. return ValidationProblem(ex.Message); diff --git a/web/Areas/RAPS/Controllers/RoleMembersController.cs b/web/Areas/RAPS/Controllers/RoleMembersController.cs index 8ca25941f..7981bb98a 100644 --- a/web/Areas/RAPS/Controllers/RoleMembersController.cs +++ b/web/Areas/RAPS/Controllers/RoleMembersController.cs @@ -267,7 +267,7 @@ private static void UpdateTblRoleMemberWithDto(TblRoleMember tblRoleMember, Role return vmacsResponse; } } - catch (System.Text.Json.JsonException) + catch (JsonException) { return new VmacsResponse { diff --git a/web/Areas/RAPS/Controllers/RolesController.cs b/web/Areas/RAPS/Controllers/RolesController.cs index 3000b2030..11fa0412f 100644 --- a/web/Areas/RAPS/Controllers/RolesController.cs +++ b/web/Areas/RAPS/Controllers/RolesController.cs @@ -6,6 +6,7 @@ using Viper.Classes; using Viper.Classes.SQLContext; using Viper.Models.RAPS; +using Microsoft.Data.SqlClient; namespace Viper.Areas.RAPS.Controllers { @@ -222,7 +223,7 @@ public async Task> PostTblRole(string instance, RoleCreate { return Problem("The record was not updated because it was locked. " + ex.InnerException?.Message); } - catch (Exception ex) when (ex is Microsoft.EntityFrameworkCore.DbUpdateException or Microsoft.Data.SqlClient.SqlException or InvalidOperationException or OperationCanceledException) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException or OperationCanceledException) { return Problem("There was a problem updating the database. " + ex.InnerException?.Message); } diff --git a/web/Areas/RAPS/Services/OuGroupService.cs b/web/Areas/RAPS/Services/OuGroupService.cs index c7b555693..f1c8d3a60 100644 --- a/web/Areas/RAPS/Services/OuGroupService.cs +++ b/web/Areas/RAPS/Services/OuGroupService.cs @@ -8,6 +8,7 @@ using Viper.Classes.Utilities; using Viper.Models.RAPS; using static Viper.Areas.RAPS.Services.RAPSAuditService; +using Microsoft.Data.SqlClient; namespace Viper.Areas.RAPS.Services { @@ -45,7 +46,7 @@ public async Task> GetAllGroups(string? search) { ActiveDirectoryService.GetGroups(); } - catch (Exception ex) when (ex is Microsoft.EntityFrameworkCore.DbUpdateException or Microsoft.Data.SqlClient.SqlException or InvalidOperationException or OperationCanceledException) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException or OperationCanceledException) { Logger logger = LogManager.GetCurrentClassLogger(); logger.Error(ex); diff --git a/web/Areas/RAPS/Services/VMACSExport.cs b/web/Areas/RAPS/Services/VMACSExport.cs index 7fb014bf6..5f9afc5e5 100644 --- a/web/Areas/RAPS/Services/VMACSExport.cs +++ b/web/Areas/RAPS/Services/VMACSExport.cs @@ -2,6 +2,7 @@ using System.Text; using System.Text.Encodings.Web; using System.Text.Json; +using Microsoft.Data.SqlClient; using Microsoft.EntityFrameworkCore; using Viper.Areas.RAPS.Models; using Viper.Classes.SQLContext; @@ -132,7 +133,7 @@ public async Task> ExportToVMACS(string instance, string? server = VmacsResponse vmacsResponse = await ParseResponse(response); RecordMessage(messages, JsonSerializer.Serialize(vmacsResponse)); } - catch (Exception ex) when (ex is Microsoft.EntityFrameworkCore.DbUpdateException or Microsoft.Data.SqlClient.SqlException or InvalidOperationException or OperationCanceledException) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException or OperationCanceledException) { HttpHelper.Logger.Log(LogLevel.Warn, ex); RecordMessage(messages, "Error: " + ex.Message + " " + ex.StackTrace); @@ -167,7 +168,7 @@ private static async Task ParseResponse(HttpResponseMessage respo vmacsResponse.Success = response.IsSuccessStatusCode; } - catch (Exception) when (true /* placeholder */) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException or OperationCanceledException) { vmacsResponse = new() { diff --git a/web/Classes/ClaimsTransformer.cs b/web/Classes/ClaimsTransformer.cs index cfc7abbb9..550d58d17 100644 --- a/web/Classes/ClaimsTransformer.cs +++ b/web/Classes/ClaimsTransformer.cs @@ -1,6 +1,8 @@ using System.Security.Claims; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.DataProtection; +using Microsoft.Data.SqlClient; +using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Caching.Memory; using Viper; using Viper.Classes; @@ -90,7 +92,7 @@ public Task TransformAsync(ClaimsPrincipal principal) } - catch (Exception ex) when (ex is Microsoft.EntityFrameworkCore.DbUpdateException or Microsoft.Data.SqlClient.SqlException or InvalidOperationException or OperationCanceledException) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException or OperationCanceledException) { bool isProd = false; diff --git a/web/Classes/SitemapMiddleware.cs b/web/Classes/SitemapMiddleware.cs index 1cacea55a..352f2021d 100644 --- a/web/Classes/SitemapMiddleware.cs +++ b/web/Classes/SitemapMiddleware.cs @@ -5,6 +5,8 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Web.Authorization; +using Microsoft.EntityFrameworkCore; +using Microsoft.Data.SqlClient; namespace Viper.Classes { @@ -85,7 +87,7 @@ public async Task Invoke(HttpContext context) await memoryStream.CopyToAsync(stream, bytes.Length); } } - catch (Exception) when (true /* placeholder */) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException or OperationCanceledException) { await _next(context); } diff --git a/web/Classes/UserHelper.cs b/web/Classes/UserHelper.cs index df56833f7..106e569ae 100644 --- a/web/Classes/UserHelper.cs +++ b/web/Classes/UserHelper.cs @@ -5,6 +5,8 @@ using Viper.Models.AAUD; using Viper.Models.RAPS; using Web.Authorization; +using Microsoft.EntityFrameworkCore; +using Microsoft.Data.SqlClient; namespace Viper { @@ -255,7 +257,7 @@ public bool HasPermission(RAPSContext? rapsContext, AaudUser? user, string permi return user; } } - catch (Exception ex) when (ex is Microsoft.EntityFrameworkCore.DbUpdateException or Microsoft.Data.SqlClient.SqlException or InvalidOperationException or OperationCanceledException) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException or OperationCanceledException) { HttpHelper.Logger.Error(ex); return null; @@ -282,7 +284,7 @@ public bool HasPermission(RAPSContext? rapsContext, AaudUser? user, string permi return currentUser; } - catch (Exception ex) when (ex is Microsoft.EntityFrameworkCore.DbUpdateException or Microsoft.Data.SqlClient.SqlException or InvalidOperationException or OperationCanceledException) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException or OperationCanceledException) { HttpHelper.Logger.Error(ex); return null; @@ -314,7 +316,7 @@ public bool HasPermission(RAPSContext? rapsContext, AaudUser? user, string permi return GetCurrentUser(); } - catch (Exception ex) when (ex is Microsoft.EntityFrameworkCore.DbUpdateException or Microsoft.Data.SqlClient.SqlException or InvalidOperationException or OperationCanceledException) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException or OperationCanceledException) { HttpHelper.Logger.Error(ex); return null; diff --git a/web/Classes/Utilities/ActiveDirectoryService.cs b/web/Classes/Utilities/ActiveDirectoryService.cs index acb3e21c3..13aa30355 100644 --- a/web/Classes/Utilities/ActiveDirectoryService.cs +++ b/web/Classes/Utilities/ActiveDirectoryService.cs @@ -3,6 +3,8 @@ using System.Net; using System.Runtime.Versioning; using Viper.Areas.RAPS.Models; +using Microsoft.EntityFrameworkCore; +using Microsoft.Data.SqlClient; namespace Viper.Classes.Utilities { @@ -219,7 +221,7 @@ public static void AddUserToGroup(string userDn, string groupDn) group.Save(); } } - catch (Exception ex) when (ex is Microsoft.EntityFrameworkCore.DbUpdateException or Microsoft.Data.SqlClient.SqlException or InvalidOperationException or OperationCanceledException) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException or OperationCanceledException) { HttpHelper.Logger.Error(ex); } @@ -245,7 +247,7 @@ public static void RemoveUserFromGroup(string userDn, string groupDn) group.Save(); } } - catch (Exception ex) when (ex is Microsoft.EntityFrameworkCore.DbUpdateException or Microsoft.Data.SqlClient.SqlException or InvalidOperationException or OperationCanceledException) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException or OperationCanceledException) { HttpHelper.Logger.Error(ex); } diff --git a/web/Classes/Utilities/F5HttpRequest.cs b/web/Classes/Utilities/F5HttpRequest.cs index 77fa3894f..01e7b4955 100644 --- a/web/Classes/Utilities/F5HttpRequest.cs +++ b/web/Classes/Utilities/F5HttpRequest.cs @@ -1,5 +1,7 @@ using System.Net; using System.Text.Json; +using Microsoft.EntityFrameworkCore; +using Microsoft.Data.SqlClient; namespace Viper.Classes.Utilities { @@ -22,7 +24,7 @@ public async Task Send(HttpRequestMessage request, int atte { response = await _httpClient.SendAsync(request); } - catch (Exception) when (true /* placeholder */) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException or OperationCanceledException) { response = await HandleConnectionFail(request, attemptNumber); } diff --git a/web/Classes/Utilities/IamApi.cs b/web/Classes/Utilities/IamApi.cs index 5c8cf631b..e1baf1750 100644 --- a/web/Classes/Utilities/IamApi.cs +++ b/web/Classes/Utilities/IamApi.cs @@ -4,6 +4,8 @@ using System.Web; using NLog; using Viper.Models.IAM; +using Microsoft.EntityFrameworkCore; +using Microsoft.Data.SqlClient; namespace Viper.Classes.Utilities { @@ -398,7 +400,7 @@ static private async Task> ParseResponse(HttpResponseMessage? res } } - catch (Exception ex) when (ex is Microsoft.EntityFrameworkCore.DbUpdateException or Microsoft.Data.SqlClient.SqlException or InvalidOperationException or OperationCanceledException) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException or OperationCanceledException) { r.ErrorMessage = "Invalid response: " + ex.Message + "."; } diff --git a/web/Program.cs b/web/Program.cs index 4b6fb4458..688f334c8 100644 --- a/web/Program.cs +++ b/web/Program.cs @@ -14,6 +14,7 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.Rewrite; +using Microsoft.Data.SqlClient; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.FileProviders; @@ -86,7 +87,7 @@ .AddSystemsManager("/" + builder.Environment.EnvironmentName, awsOptions) .AddSystemsManager("/Shared", awsOptions); } - catch (Exception ex) when (ex is Microsoft.EntityFrameworkCore.DbUpdateException or Microsoft.Data.SqlClient.SqlException or InvalidOperationException or OperationCanceledException) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException or OperationCanceledException) { logger.Fatal(ex, "Failed to get secrets from AWS"); } @@ -567,7 +568,7 @@ void SetAwsCredentials(Logger logger) { File.Delete(awsCredentialsFilePath); } - catch (Exception ex) when (ex is Microsoft.EntityFrameworkCore.DbUpdateException or Microsoft.Data.SqlClient.SqlException or InvalidOperationException or OperationCanceledException) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException or OperationCanceledException) { logger.Error(ex, $"COULD NOT DELETE THE AWS CREDENTIALS XML FILE (\"{awsCredentialsFilePath}\"). The file will need to be deleted manually."); logger.Error(ex, $"COULD NOT DELETE THE AWS CREDENTIALS XML FILE (\"{awsCredentialsFilePath}\"). The file will need to be deleted manually."); diff --git a/web/Services/EmailService.cs b/web/Services/EmailService.cs index 9842d0fa9..17f206cc1 100644 --- a/web/Services/EmailService.cs +++ b/web/Services/EmailService.cs @@ -5,6 +5,8 @@ using Microsoft.Extensions.Options; using MimeKit; using Viper.Classes.Utilities; +using Microsoft.EntityFrameworkCore; +using Microsoft.Data.SqlClient; namespace Viper.Services { @@ -219,7 +221,7 @@ public async Task IsServiceAvailableAsync() // For production, assume the SMTP server is available return true; } - catch (Exception ex) when (ex is Microsoft.EntityFrameworkCore.DbUpdateException or Microsoft.Data.SqlClient.SqlException or InvalidOperationException or OperationCanceledException) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException or OperationCanceledException) { _logger.LogError(ex, "Error checking email service availability"); return false; diff --git a/web/ViteProxyHelpers.cs b/web/ViteProxyHelpers.cs index 0e8606867..40a5f225f 100644 --- a/web/ViteProxyHelpers.cs +++ b/web/ViteProxyHelpers.cs @@ -1,6 +1,8 @@ using System.Net; using System.Text.RegularExpressions; using Microsoft.AspNetCore.StaticFiles; +using Microsoft.Data.SqlClient; +using Microsoft.EntityFrameworkCore; namespace Web; @@ -284,7 +286,7 @@ public static async Task CopyProxyResponse(HttpContext context, HttpResponseMess { context.Response.Headers[header.Key] = header.Value.ToArray(); } - catch (Exception headerEx) when (headerEx is Microsoft.EntityFrameworkCore.DbUpdateException or Microsoft.Data.SqlClient.SqlException or InvalidOperationException or OperationCanceledException or System.IO.IOException) + catch (Exception headerEx) when (headerEx is DbUpdateException or SqlException or InvalidOperationException or OperationCanceledException or IOException) { // Use structured logging for header errors var safeHeaderKey = WebUtility.HtmlEncode(header.Key); @@ -367,7 +369,7 @@ public static async Task HandleProxyError(HttpContext context, Exception ex, ILo return; } } - catch (Exception fileEx) when (fileEx is Microsoft.EntityFrameworkCore.DbUpdateException or Microsoft.Data.SqlClient.SqlException or InvalidOperationException or OperationCanceledException or System.IO.IOException) + catch (Exception fileEx) when (fileEx is DbUpdateException or SqlException or InvalidOperationException or OperationCanceledException or IOException) { var safePath = context.Request.Path.ToString().Replace("\r", "").Replace("\n", ""); logger.LogWarning(fileEx, "Failed to serve static file fallback for {Path}", safePath); From f9b8ad681397dfc2cd583f1a6fa061febc33d63f Mon Sep 17 00:00:00 2001 From: Rex Lorenzo Date: Tue, 9 Jun 2026 22:10:18 -0700 Subject: [PATCH 3/5] refactor(email): narrow Mailpit reachability probe to SocketException Extends the catch-of-all cleanup to a probe that was added to EmailService after the original PR was written. A TCP connect fails with SocketException; other exception types now surface instead of being swallowed. The HangfireHealthCheck storage catch and EmailService.GetRequestContext are intentionally left broad: the former must not crash the health response on any storage error (it documents this), the latter is a defensive log-context builder that must never throw. --- web/Services/EmailService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/Services/EmailService.cs b/web/Services/EmailService.cs index 17f206cc1..d914ac7ae 100644 --- a/web/Services/EmailService.cs +++ b/web/Services/EmailService.cs @@ -248,7 +248,7 @@ private async Task IsMailpitAvailableAsync() await tcpClient.ConnectAsync(_emailSettings.SmtpHost, _emailSettings.SmtpPort); return tcpClient.Connected; } - catch + catch (SocketException) { return false; } From 86c493445b6096a20402b1f28a895ee3554f9b6e Mon Sep 17 00:00:00 2001 From: Rex Lorenzo Date: Wed, 10 Jun 2026 07:07:12 -0700 Subject: [PATCH 4/5] fix(exceptions): address PR review on catch filters Match each catch filter to what the operation actually throws instead of the uniform DB-exception filter (which let HTTP/AWS/AD/JSON failures escape): - AWS Parameter Store (Program.cs): AmazonServiceException/AmazonClientException - HTTP send/retry (F5HttpRequest, VMACSExport): HttpRequestException + TaskCanceledException - JSON parsing (IamApi, VMACSExport.ParseResponse): JsonException/NotSupportedException - Active Directory (ActiveDirectoryService): PrincipalException/DirectoryServicesCOMException - response headers (ViteProxyHelpers): ArgumentException/InvalidOperationException - static-file fallback (ViteProxyHelpers): IOException/UnauthorizedAccessException - sitemap middleware boundary: broad catch with CA1031 + rationale (must always fall through to the pipeline) Drop OperationCanceledException from controller action filters so request cancellation propagates instead of being turned into a 500. Stop returning raw exception text to clients (EpaController, AdGroupsController, RolesController) and correct a wrong entity name in CompetencyController. Remove the now-orphaned EntityFrameworkCore/SqlClient usings. --- .../CTS/Controllers/BundleCompetencyController.cs | 2 +- .../CTS/Controllers/BundleCompetencyGroupController.cs | 2 +- web/Areas/CTS/Controllers/BundleController.cs | 2 +- web/Areas/CTS/Controllers/CompetencyController.cs | 4 ++-- web/Areas/CTS/Controllers/CourseController.cs | 4 ++-- web/Areas/CTS/Controllers/DomainController.cs | 2 +- web/Areas/CTS/Controllers/EpaController.cs | 4 ++-- web/Areas/CTS/Controllers/LevelsController.cs | 2 +- web/Areas/CTS/Controllers/RoleController.cs | 2 +- .../Controllers/CliniciansController.cs | 8 ++++---- .../Controllers/InstructorScheduleController.cs | 10 +++++----- .../Controllers/PermissionsController.cs | 6 +++--- .../Controllers/RotationsController.cs | 8 ++++---- web/Areas/RAPS/Controllers/AdGroupsController.cs | 6 +++--- web/Areas/RAPS/Controllers/RolesController.cs | 8 ++++---- web/Areas/RAPS/Services/VMACSExport.cs | 5 ++--- web/Classes/SitemapMiddleware.cs | 9 ++++++--- web/Classes/Utilities/ActiveDirectoryService.cs | 6 ++---- web/Classes/Utilities/F5HttpRequest.cs | 4 +--- web/Classes/Utilities/IamApi.cs | 4 +--- web/Program.cs | 7 +++---- web/Services/EmailService.cs | 4 +--- web/ViteProxyHelpers.cs | 6 ++---- 23 files changed, 53 insertions(+), 62 deletions(-) diff --git a/web/Areas/CTS/Controllers/BundleCompetencyController.cs b/web/Areas/CTS/Controllers/BundleCompetencyController.cs index 93f2a4115..a3df4a6dc 100644 --- a/web/Areas/CTS/Controllers/BundleCompetencyController.cs +++ b/web/Areas/CTS/Controllers/BundleCompetencyController.cs @@ -159,7 +159,7 @@ public async Task> DeleteBundleCompetency(int await context.SaveChangesAsync(); AdjustBundleCompetencyOrders(bundleComp); } - catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException or OperationCanceledException) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException) { return BadRequest("Cannot delete this bundle competency."); } diff --git a/web/Areas/CTS/Controllers/BundleCompetencyGroupController.cs b/web/Areas/CTS/Controllers/BundleCompetencyGroupController.cs index 4bff3018f..a9ed6330b 100644 --- a/web/Areas/CTS/Controllers/BundleCompetencyGroupController.cs +++ b/web/Areas/CTS/Controllers/BundleCompetencyGroupController.cs @@ -117,7 +117,7 @@ public async Task> DeleteGroup(int bundle await context.SaveChangesAsync(); AdjustGroupOrders(group); } - catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException or OperationCanceledException) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException) { return BadRequest("Cannot delete group. Competencies must be removed from the group, and the group cannot have been used to document a student competency."); } diff --git a/web/Areas/CTS/Controllers/BundleController.cs b/web/Areas/CTS/Controllers/BundleController.cs index be1a520d6..45d8b0126 100644 --- a/web/Areas/CTS/Controllers/BundleController.cs +++ b/web/Areas/CTS/Controllers/BundleController.cs @@ -140,7 +140,7 @@ public async Task> DeleteBundle(int bundleId) await context.SaveChangesAsync(); await trans.CommitAsync(); } - catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException or OperationCanceledException) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException) { return BadRequest("Could not delete bundle. If this bundle has been used, it cannot be deleted."); } diff --git a/web/Areas/CTS/Controllers/CompetencyController.cs b/web/Areas/CTS/Controllers/CompetencyController.cs index 61a7b2e42..5bad0e084 100644 --- a/web/Areas/CTS/Controllers/CompetencyController.cs +++ b/web/Areas/CTS/Controllers/CompetencyController.cs @@ -177,9 +177,9 @@ public async Task> DeleteCompetency(int competencyId { await context.SaveChangesAsync(); } - catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException or OperationCanceledException) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException) { - return BadRequest("Could not remove domain. It may be linked to other objects."); + return BadRequest("Could not remove competency. It may be linked to other objects."); } await UpdateCompetencyOrders(); return new CompetencyDto(c); diff --git a/web/Areas/CTS/Controllers/CourseController.cs b/web/Areas/CTS/Controllers/CourseController.cs index 8d2d6927b..a0e487007 100644 --- a/web/Areas/CTS/Controllers/CourseController.cs +++ b/web/Areas/CTS/Controllers/CourseController.cs @@ -106,7 +106,7 @@ public async Task>> SetCourseRoles(int courseId, List await context.SaveChangesAsync(); await trans.CommitAsync(); } - catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException or OperationCanceledException) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException) { return BadRequest("Could not set roles."); } @@ -280,7 +280,7 @@ public async Task>> UpdateSessionCompete await context.SaveChangesAsync(); await trans.CommitAsync(); } - catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException or OperationCanceledException) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException) { return BadRequest("Could not update levels."); } diff --git a/web/Areas/CTS/Controllers/DomainController.cs b/web/Areas/CTS/Controllers/DomainController.cs index af3bd9475..7f54773eb 100644 --- a/web/Areas/CTS/Controllers/DomainController.cs +++ b/web/Areas/CTS/Controllers/DomainController.cs @@ -92,7 +92,7 @@ public async Task> DeleteDomain(int domainId) { await context.SaveChangesAsync(); } - catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException or OperationCanceledException) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException) { return BadRequest("Could not remove domain. It may be linked to other objects."); } diff --git a/web/Areas/CTS/Controllers/EpaController.cs b/web/Areas/CTS/Controllers/EpaController.cs index 9b163f761..9f0f1e009 100644 --- a/web/Areas/CTS/Controllers/EpaController.cs +++ b/web/Areas/CTS/Controllers/EpaController.cs @@ -106,9 +106,9 @@ public async Task> GetEpa(int epaId) context.Entry(epa).State = EntityState.Deleted; await context.SaveChangesAsync(); } - catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException or OperationCanceledException) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException) { - return BadRequest(ex.Message); + return BadRequest("Could not delete EPA. It may be linked to other objects."); } return epa; diff --git a/web/Areas/CTS/Controllers/LevelsController.cs b/web/Areas/CTS/Controllers/LevelsController.cs index acb9742a8..c6052f7ab 100644 --- a/web/Areas/CTS/Controllers/LevelsController.cs +++ b/web/Areas/CTS/Controllers/LevelsController.cs @@ -152,7 +152,7 @@ public async Task DeleteLevel(int levelId) AdjustLevelOrders(existing); await trans.CommitAsync(); } - catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException or OperationCanceledException) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException) { return BadRequest("Could not delete level. If this level has been used in an assessment, it cannot be deleted."); } diff --git a/web/Areas/CTS/Controllers/RoleController.cs b/web/Areas/CTS/Controllers/RoleController.cs index 0ae1b016a..9f04af8d7 100644 --- a/web/Areas/CTS/Controllers/RoleController.cs +++ b/web/Areas/CTS/Controllers/RoleController.cs @@ -85,7 +85,7 @@ public async Task> DeleteRole(int roleId) context.Entry(role).State = EntityState.Deleted; await context.SaveChangesAsync(); } - catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException or OperationCanceledException) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException) { return BadRequest("Could not delete role. If this role has been added to a bundle, it cannot be deleted."); } diff --git a/web/Areas/ClinicalScheduler/Controllers/CliniciansController.cs b/web/Areas/ClinicalScheduler/Controllers/CliniciansController.cs index 518db965c..a0c8b8c6c 100644 --- a/web/Areas/ClinicalScheduler/Controllers/CliniciansController.cs +++ b/web/Areas/ClinicalScheduler/Controllers/CliniciansController.cs @@ -335,7 +335,7 @@ public async Task GetClinicianSchedule(string mothraId, [FromQuer schedules.Count, LogSanitizer.SanitizeId(mothraId), LogSanitizer.SanitizeYear(targetYear)); return Ok(result); } - catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException or OperationCanceledException) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException) { // Store context for ApiExceptionFilter to use in logging SetExceptionContext("MothraId", mothraId); @@ -382,7 +382,7 @@ public async Task GetClinicianRotations(string mothraId) _logger.LogDebug("Found {RotationCount} unique rotations for clinician {MothraId}", rotations.Count, LogSanitizer.SanitizeId(mothraId)); return Ok(rotations); } - catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException or OperationCanceledException) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException) { // Store context for ApiExceptionFilter to use in logging SetExceptionContext("MothraId", mothraId); @@ -576,7 +576,7 @@ private List FilterCliniciansByPermissions(IEnumerable AddInstructor( { return HandleInvalidOperation(ex, correlationId); } - catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException or OperationCanceledException) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException) { return HandleSystemError(ex, request.RotationId!.Value, correlationId); } @@ -337,7 +337,7 @@ public async Task RemoveInstructor( userMessage, correlationId)); } - catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException or OperationCanceledException) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException) { _logger.LogError(ex, "Error removing instructor schedule {ScheduleId} (CorrelationId: {CorrelationId})", instructorScheduleId, correlationId); @@ -416,7 +416,7 @@ public async Task SetPrimaryEvaluator( instructorScheduleId, correlationId); return Forbid(); } - catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException or OperationCanceledException) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException) { _logger.LogError(ex, "Error setting primary evaluator for instructor schedule {ScheduleId} (CorrelationId: {CorrelationId})", instructorScheduleId, correlationId); @@ -509,7 +509,7 @@ public async Task CheckScheduleConflicts( return Ok(response); } - catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException or OperationCanceledException) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException) { _logger.LogError(ex, "Error checking schedule conflicts for {MothraId}", LogSanitizer.SanitizeId(mothraId)); return StatusCode(500, "An error occurred while checking for schedule conflicts"); @@ -534,7 +534,7 @@ public async Task GetAuditHistory( var auditHistory = await _auditService.GetInstructorScheduleAuditHistoryAsync(instructorScheduleId, cancellationToken); return Ok(auditHistory); } - catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException or OperationCanceledException) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException) { _logger.LogError(ex, "Error retrieving audit history for instructor schedule {ScheduleId}", instructorScheduleId); return StatusCode(500, "An error occurred while retrieving audit history"); diff --git a/web/Areas/ClinicalScheduler/Controllers/PermissionsController.cs b/web/Areas/ClinicalScheduler/Controllers/PermissionsController.cs index baefce96a..56e3ea450 100644 --- a/web/Areas/ClinicalScheduler/Controllers/PermissionsController.cs +++ b/web/Areas/ClinicalScheduler/Controllers/PermissionsController.cs @@ -154,7 +154,7 @@ public async Task> CanEditService(int serviceId) return Ok(new { canEdit }); } - catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException or OperationCanceledException) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException) { // Store context for ApiExceptionFilter to use in logging SetExceptionContext("ServiceId", serviceId); @@ -216,7 +216,7 @@ public async Task> CanEditRotation(int rotationId) return Ok(new { canEdit }); } - catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException or OperationCanceledException) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException) { // Store context for ApiExceptionFilter to use in logging SetExceptionContext("RotationId", rotationId); @@ -278,7 +278,7 @@ public async Task> CanEditOwnSchedule(int instructorSchedul return Ok(new { canEditOwn }); } - catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException or OperationCanceledException) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException) { // Store context for ApiExceptionFilter to use in logging SetExceptionContext("InstructorScheduleId", instructorScheduleId); diff --git a/web/Areas/ClinicalScheduler/Controllers/RotationsController.cs b/web/Areas/ClinicalScheduler/Controllers/RotationsController.cs index dd3ba1a4b..e0bf5c64b 100644 --- a/web/Areas/ClinicalScheduler/Controllers/RotationsController.cs +++ b/web/Areas/ClinicalScheduler/Controllers/RotationsController.cs @@ -71,7 +71,7 @@ public async Task>> GetRotations(int? serv rotations.Count, filteredRotations.Count); return Ok(filteredRotations); } - catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException or OperationCanceledException) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException) { // Store context for ApiExceptionFilter to use in logging SetExceptionContext(new Dictionary @@ -132,7 +132,7 @@ public async Task> GetRotation(int id) _logger.LogInformation("Retrieved rotation via RotationService: {RotationName}", rotation.Name); return Ok(response); } - catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException or OperationCanceledException) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException) { // Store context for ApiExceptionFilter to use in logging SetExceptionContext("RotationId", id); @@ -234,7 +234,7 @@ public async Task> GetRotationSchedule(int id, [FromQuery] return Ok(BuildRotationScheduleResponse(rotation, targetYear, groupedSchedules, recentCliniciansList)); } - catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException or OperationCanceledException) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException) { // Store context for ApiExceptionFilter to use in logging SetExceptionContext("RotationId", id); @@ -299,7 +299,7 @@ join w in _context.Weeks on i.WeekId equals w.WeekId _logger.LogInformation("Retrieved {Count} rotations with scheduled weeks for year {Year} (filtered to {FilteredCount})", rotationsWithSchedules.Count, LogSanitizer.SanitizeYear(targetYear), filteredRotations.Count); return Ok(filteredRotations); } - catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException or OperationCanceledException) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException) { // Store context for ApiExceptionFilter to use in logging SetExceptionContext("Year", year?.ToString() ?? "null"); diff --git a/web/Areas/RAPS/Controllers/AdGroupsController.cs b/web/Areas/RAPS/Controllers/AdGroupsController.cs index 6b0129d98..106d9dd03 100644 --- a/web/Areas/RAPS/Controllers/AdGroupsController.cs +++ b/web/Areas/RAPS/Controllers/AdGroupsController.cs @@ -114,10 +114,10 @@ public async Task> CreateGroup(GroupAddEdit group) OuGroup newOuGroup = await _ouGroupService.CreateRapsGroup(group.Name, group.Description); return CreatedAtAction("CreateGroup", new { id = newOuGroup.OugroupId }, newOuGroup); } - catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException or OperationCanceledException) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException) { - //Exception message could be indication user is trying to create a group that exists or is invalid. - return ValidationProblem(ex.Message); + // Return a generic message; the raw exception may reveal internal details. + return ValidationProblem("Unable to create or update the group. Verify the request and try again."); } } diff --git a/web/Areas/RAPS/Controllers/RolesController.cs b/web/Areas/RAPS/Controllers/RolesController.cs index 11fa0412f..121981bec 100644 --- a/web/Areas/RAPS/Controllers/RolesController.cs +++ b/web/Areas/RAPS/Controllers/RolesController.cs @@ -219,13 +219,13 @@ public async Task> PostTblRole(string instance, RoleCreate return CreatedAtAction("GetTblRole", new { id = tblRole.RoleId }, tblRole); } - catch (DbUpdateConcurrencyException ex) + catch (DbUpdateConcurrencyException) { - return Problem("The record was not updated because it was locked. " + ex.InnerException?.Message); + return Problem("The record was not updated because it was locked."); } - catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException or OperationCanceledException) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException) { - return Problem("There was a problem updating the database. " + ex.InnerException?.Message); + return Problem("There was a problem updating the database."); } } diff --git a/web/Areas/RAPS/Services/VMACSExport.cs b/web/Areas/RAPS/Services/VMACSExport.cs index 5f9afc5e5..95c2d86ca 100644 --- a/web/Areas/RAPS/Services/VMACSExport.cs +++ b/web/Areas/RAPS/Services/VMACSExport.cs @@ -2,7 +2,6 @@ using System.Text; using System.Text.Encodings.Web; using System.Text.Json; -using Microsoft.Data.SqlClient; using Microsoft.EntityFrameworkCore; using Viper.Areas.RAPS.Models; using Viper.Classes.SQLContext; @@ -133,7 +132,7 @@ public async Task> ExportToVMACS(string instance, string? server = VmacsResponse vmacsResponse = await ParseResponse(response); RecordMessage(messages, JsonSerializer.Serialize(vmacsResponse)); } - catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException or OperationCanceledException) + catch (Exception ex) when (ex is HttpRequestException or InvalidOperationException or OperationCanceledException) { HttpHelper.Logger.Log(LogLevel.Warn, ex); RecordMessage(messages, "Error: " + ex.Message + " " + ex.StackTrace); @@ -168,7 +167,7 @@ private static async Task ParseResponse(HttpResponseMessage respo vmacsResponse.Success = response.IsSuccessStatusCode; } - catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException or OperationCanceledException) + catch (Exception ex) when (ex is JsonException or NotSupportedException or InvalidOperationException) { vmacsResponse = new() { diff --git a/web/Classes/SitemapMiddleware.cs b/web/Classes/SitemapMiddleware.cs index 352f2021d..6c5f00556 100644 --- a/web/Classes/SitemapMiddleware.cs +++ b/web/Classes/SitemapMiddleware.cs @@ -5,8 +5,6 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Web.Authorization; -using Microsoft.EntityFrameworkCore; -using Microsoft.Data.SqlClient; namespace Viper.Classes { @@ -87,7 +85,12 @@ public async Task Invoke(HttpContext context) await memoryStream.CopyToAsync(stream, bytes.Length); } } - catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException or OperationCanceledException) + // Middleware boundary: any sitemap-generation failure (DB, IO, + // reflection, etc.) must fall through to the pipeline rather than + // break the request. +#pragma warning disable CA1031 + catch (Exception) +#pragma warning restore CA1031 { await _next(context); } diff --git a/web/Classes/Utilities/ActiveDirectoryService.cs b/web/Classes/Utilities/ActiveDirectoryService.cs index 13aa30355..d0aa38a06 100644 --- a/web/Classes/Utilities/ActiveDirectoryService.cs +++ b/web/Classes/Utilities/ActiveDirectoryService.cs @@ -3,8 +3,6 @@ using System.Net; using System.Runtime.Versioning; using Viper.Areas.RAPS.Models; -using Microsoft.EntityFrameworkCore; -using Microsoft.Data.SqlClient; namespace Viper.Classes.Utilities { @@ -221,7 +219,7 @@ public static void AddUserToGroup(string userDn, string groupDn) group.Save(); } } - catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException or OperationCanceledException) + catch (Exception ex) when (ex is PrincipalException or System.DirectoryServices.DirectoryServicesCOMException) { HttpHelper.Logger.Error(ex); } @@ -247,7 +245,7 @@ public static void RemoveUserFromGroup(string userDn, string groupDn) group.Save(); } } - catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException or OperationCanceledException) + catch (Exception ex) when (ex is PrincipalException or System.DirectoryServices.DirectoryServicesCOMException) { HttpHelper.Logger.Error(ex); } diff --git a/web/Classes/Utilities/F5HttpRequest.cs b/web/Classes/Utilities/F5HttpRequest.cs index 01e7b4955..fd487433e 100644 --- a/web/Classes/Utilities/F5HttpRequest.cs +++ b/web/Classes/Utilities/F5HttpRequest.cs @@ -1,7 +1,5 @@ using System.Net; using System.Text.Json; -using Microsoft.EntityFrameworkCore; -using Microsoft.Data.SqlClient; namespace Viper.Classes.Utilities { @@ -24,7 +22,7 @@ public async Task Send(HttpRequestMessage request, int atte { response = await _httpClient.SendAsync(request); } - catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException or OperationCanceledException) + catch (Exception ex) when (ex is HttpRequestException or TaskCanceledException) { response = await HandleConnectionFail(request, attemptNumber); } diff --git a/web/Classes/Utilities/IamApi.cs b/web/Classes/Utilities/IamApi.cs index e1baf1750..cda193007 100644 --- a/web/Classes/Utilities/IamApi.cs +++ b/web/Classes/Utilities/IamApi.cs @@ -4,8 +4,6 @@ using System.Web; using NLog; using Viper.Models.IAM; -using Microsoft.EntityFrameworkCore; -using Microsoft.Data.SqlClient; namespace Viper.Classes.Utilities { @@ -400,7 +398,7 @@ static private async Task> ParseResponse(HttpResponseMessage? res } } - catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException or OperationCanceledException) + catch (Exception ex) when (ex is JsonException or NotSupportedException or InvalidOperationException or OperationCanceledException) { r.ErrorMessage = "Invalid response: " + ex.Message + "."; } diff --git a/web/Program.cs b/web/Program.cs index 688f334c8..8f87dc1e7 100644 --- a/web/Program.cs +++ b/web/Program.cs @@ -6,6 +6,7 @@ using System.Xml.Linq; using Amazon; using Amazon.Extensions.NETCore.Setup; +using Amazon.Runtime; using Amazon.Runtime.CredentialManagement; using DotNetEnv; using Joonasw.AspNetCore.SecurityHeaders; @@ -14,7 +15,6 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.Rewrite; -using Microsoft.Data.SqlClient; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.FileProviders; @@ -87,7 +87,7 @@ .AddSystemsManager("/" + builder.Environment.EnvironmentName, awsOptions) .AddSystemsManager("/Shared", awsOptions); } - catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException or OperationCanceledException) + catch (Exception ex) when (ex is AmazonServiceException or AmazonClientException) { logger.Fatal(ex, "Failed to get secrets from AWS"); } @@ -568,10 +568,9 @@ void SetAwsCredentials(Logger logger) { File.Delete(awsCredentialsFilePath); } - catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException or OperationCanceledException) + catch (Exception ex) when (ex is IOException or UnauthorizedAccessException) { logger.Error(ex, $"COULD NOT DELETE THE AWS CREDENTIALS XML FILE (\"{awsCredentialsFilePath}\"). The file will need to be deleted manually."); - logger.Error(ex, $"COULD NOT DELETE THE AWS CREDENTIALS XML FILE (\"{awsCredentialsFilePath}\"). The file will need to be deleted manually."); } } else diff --git a/web/Services/EmailService.cs b/web/Services/EmailService.cs index d914ac7ae..8c91b75d1 100644 --- a/web/Services/EmailService.cs +++ b/web/Services/EmailService.cs @@ -5,8 +5,6 @@ using Microsoft.Extensions.Options; using MimeKit; using Viper.Classes.Utilities; -using Microsoft.EntityFrameworkCore; -using Microsoft.Data.SqlClient; namespace Viper.Services { @@ -221,7 +219,7 @@ public async Task IsServiceAvailableAsync() // For production, assume the SMTP server is available return true; } - catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException or OperationCanceledException) + catch (Exception ex) when (ex is SocketException or ArgumentException or InvalidOperationException) { _logger.LogError(ex, "Error checking email service availability"); return false; diff --git a/web/ViteProxyHelpers.cs b/web/ViteProxyHelpers.cs index 40a5f225f..2857ed170 100644 --- a/web/ViteProxyHelpers.cs +++ b/web/ViteProxyHelpers.cs @@ -1,8 +1,6 @@ using System.Net; using System.Text.RegularExpressions; using Microsoft.AspNetCore.StaticFiles; -using Microsoft.Data.SqlClient; -using Microsoft.EntityFrameworkCore; namespace Web; @@ -286,7 +284,7 @@ public static async Task CopyProxyResponse(HttpContext context, HttpResponseMess { context.Response.Headers[header.Key] = header.Value.ToArray(); } - catch (Exception headerEx) when (headerEx is DbUpdateException or SqlException or InvalidOperationException or OperationCanceledException or IOException) + catch (Exception headerEx) when (headerEx is ArgumentException or InvalidOperationException) { // Use structured logging for header errors var safeHeaderKey = WebUtility.HtmlEncode(header.Key); @@ -369,7 +367,7 @@ public static async Task HandleProxyError(HttpContext context, Exception ex, ILo return; } } - catch (Exception fileEx) when (fileEx is DbUpdateException or SqlException or InvalidOperationException or OperationCanceledException or IOException) + catch (Exception fileEx) when (fileEx is IOException or UnauthorizedAccessException or InvalidOperationException) { var safePath = context.Request.Path.ToString().Replace("\r", "").Replace("\n", ""); logger.LogWarning(fileEx, "Failed to serve static file fallback for {Path}", safePath); From 7bd1864cd8d09ac35407e0861eb56f50de043466 Mon Sep 17 00:00:00 2001 From: Rex Lorenzo Date: Fri, 12 Jun 2026 01:34:23 -0700 Subject: [PATCH 5/5] fix(raps): wire up AD group lookup for BoxSync in GetAllGroups GetGroups() result was discarded, leaving ldapGroups empty so BoxSyncEnabled was always false. Assign it, narrow the catch to the LDAP exception types the call actually throws, and pre-build set/map lookups instead of per-group FirstOrDefault scans. --- web/Areas/RAPS/Services/OuGroupService.cs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/web/Areas/RAPS/Services/OuGroupService.cs b/web/Areas/RAPS/Services/OuGroupService.cs index f1c8d3a60..48594de50 100644 --- a/web/Areas/RAPS/Services/OuGroupService.cs +++ b/web/Areas/RAPS/Services/OuGroupService.cs @@ -1,3 +1,4 @@ +using System.DirectoryServices.Protocols; using System.Runtime.Versioning; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Caching.Memory; @@ -8,7 +9,6 @@ using Viper.Classes.Utilities; using Viper.Models.RAPS; using static Viper.Areas.RAPS.Services.RAPSAuditService; -using Microsoft.Data.SqlClient; namespace Viper.Areas.RAPS.Services { @@ -44,9 +44,9 @@ public async Task> GetAllGroups(string? search) List ldapGroups = new(); try { - ActiveDirectoryService.GetGroups(); + ldapGroups = ActiveDirectoryService.GetGroups(); } - catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException or OperationCanceledException) + catch (Exception ex) when (ex is LdapException or DirectoryOperationException) { Logger logger = LogManager.GetCurrentClassLogger(); logger.Error(ex); @@ -54,14 +54,21 @@ public async Task> GetAllGroups(string? search) List managedGroups = await _uInformService.GetManagedGroups(); + HashSet boxSyncGroupDns = ldapGroups + .Where(lg => lg.ExtensionAttribute3?.ToLower() == "ucdboxsync") + .Select(lg => lg.DistinguishedName.ToLower()) + .ToHashSet(); + Dictionary displayNamesByDn = managedGroups + .GroupBy(mg => mg.DistinguishedName.ToLower()) + .ToDictionary(g => g.Key, g => g.First().DisplayName); + List groups = new(); foreach (OuGroup group in result) { //for OU groups, look up the box sync attribute - bool BoxSync = ldapGroups.FirstOrDefault(lg => lg.DistinguishedName.ToLower() == group.Name.ToLower()) - ?.ExtensionAttribute3?.ToLower() == "ucdboxsync"; + bool BoxSync = boxSyncGroupDns.Contains(group.Name.ToLower()); //for AD3 groups, look up display name - string? displayName = managedGroups.FirstOrDefault(mg => mg.DistinguishedName.ToLower() == group.Name.ToLower())?.DisplayName; + string? displayName = displayNamesByDn.GetValueOrDefault(group.Name.ToLower()); Group g = new(group) { BoxSyncEnabled = BoxSync,