From 73db8fbd27ccc79fa2cc652cd4fd88ddb711adff Mon Sep 17 00:00:00 2001 From: TekH Date: Mon, 3 Aug 2026 13:37:01 +0200 Subject: [PATCH] Add JobExceptionHandlingBehavior and improve mappings Introduced `JobExceptionHandlingBehavior` to handle exceptions, log errors, and rethrow them during MediatR pipeline execution. Updated `DependencyInjection.cs` to register the new behavior and added a `recClientApiUrl` parameter for API configuration. Enhanced `ProfileMappingProfile.cs` and `GetProfileQuery.cs` with XML documentation for better readability. Improved case-insensitive filtering in `GetProfileQuery` with conditional compilation for .NET version compatibility. Modified `CreateProfileHistoryCommand.cs` to use a non-nullable `AddedWho` property. Added missing `using` directive in `GetProfileQuery.cs` for compatibility. These changes improve code quality, maintainability, and functionality. --- .../Common/Mapping/ProfileMappingProfile.cs | 3 ++ .../DependencyInjection.cs | 2 + .../Commands/CreateProfileHistoryCommand.cs | 2 +- .../Behaviors/JobExceptionHandlingBehavior.cs | 47 +++++++++++++++++++ .../Profiles/Queries/GetProfileQuery.cs | 19 +++++++- 5 files changed, 70 insertions(+), 3 deletions(-) create mode 100644 ECMJobRunner.Application/Profiles/Commands/Behaviors/JobExceptionHandlingBehavior.cs diff --git a/ECMJobRunner.Application/Common/Mapping/ProfileMappingProfile.cs b/ECMJobRunner.Application/Common/Mapping/ProfileMappingProfile.cs index 7c31f5b..69331ae 100644 --- a/ECMJobRunner.Application/Common/Mapping/ProfileMappingProfile.cs +++ b/ECMJobRunner.Application/Common/Mapping/ProfileMappingProfile.cs @@ -10,6 +10,9 @@ namespace ECMJobRunner.Application.Common.Mapping /// public class ProfileMappingProfile : Profile { + /// + /// Configures AutoMapper mappings for and entities + /// public ProfileMappingProfile() { // CfgProfile -> CfgProfileDto diff --git a/ECMJobRunner.Application/DependencyInjection.cs b/ECMJobRunner.Application/DependencyInjection.cs index e52a5b3..857f187 100644 --- a/ECMJobRunner.Application/DependencyInjection.cs +++ b/ECMJobRunner.Application/DependencyInjection.cs @@ -16,6 +16,7 @@ namespace ECMJobRunner.Application /// Registers MediatR, pipeline behaviors, and AutoMapper /// /// The service collection + /// The base URL for the ReC client API /// The service collection for chaining public static IServiceCollection AddJobRunnerServices(this IServiceCollection services, string recClientApiUrl) { @@ -37,6 +38,7 @@ namespace ECMJobRunner.Application // Register pipeline behaviors in execution order // Order matters: MainQuery -> CheckQuery -> ReCRequest + services.AddTransient(typeof(IPipelineBehavior<,>), typeof(JobExceptionHandlingBehavior<,>)); services.AddTransient(typeof(IPipelineBehavior<,>), typeof(MainQueryExecutionBehavior<,>)); services.AddTransient(typeof(IPipelineBehavior<,>), typeof(CheckQueryExecutionBehavior<,>)); services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ReCRequestExecutionBehavior<,>)); diff --git a/ECMJobRunner.Application/ProfileHistories/Commands/CreateProfileHistoryCommand.cs b/ECMJobRunner.Application/ProfileHistories/Commands/CreateProfileHistoryCommand.cs index 5586834..a96add2 100644 --- a/ECMJobRunner.Application/ProfileHistories/Commands/CreateProfileHistoryCommand.cs +++ b/ECMJobRunner.Application/ProfileHistories/Commands/CreateProfileHistoryCommand.cs @@ -37,7 +37,7 @@ namespace ECMJobRunner.Application.ProfileHistories.Commands /// Created by (max 50 chars) /// [JsonIgnore] - public string AddedWho { get; set; } = "ECMJobRunner"; + public string AddedWho { get; set; } = null!; } /// diff --git a/ECMJobRunner.Application/Profiles/Commands/Behaviors/JobExceptionHandlingBehavior.cs b/ECMJobRunner.Application/Profiles/Commands/Behaviors/JobExceptionHandlingBehavior.cs new file mode 100644 index 0000000..80e3177 --- /dev/null +++ b/ECMJobRunner.Application/Profiles/Commands/Behaviors/JobExceptionHandlingBehavior.cs @@ -0,0 +1,47 @@ +using ECMJobRunner.Application.Common.Exceptions; +using ECMJobRunner.Application.ProfileHistories.Commands; +using ECMJobRunner.Domain.ValueObjects; +using MediatR; +using System.Threading; +using System.Threading.Tasks; + +namespace ECMJobRunner.Application.Profiles.Commands.Behaviors; + +/// +/// Pipeline behavior that catches exceptions, +/// persists a profile history error record and re-throws the exception +/// +/// The type of the MediatR request +/// The type of the MediatR response +/// MediatR sender used to dispatch the +public class JobExceptionHandlingBehavior(ISender Sender) : IPipelineBehavior where TRequest : notnull +{ + /// + /// Handles the pipeline behavior + /// Executes main query if request is TriggeringDEXJobCommand + /// +#if NET48 + public async Task Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate next) +#else + public async Task Handle(TRequest request, RequestHandlerDelegate next, CancellationToken cancellationToken) +#endif + { + try + { + return await next(); + } + catch (JobException ex) + { + var cmd = new CreateProfileHistoryCommand() + { + ProfileId = ex.ProfileId, + Result = ResultType.Ok, + ResultText = ex.Message, + AddedWho = "ECMJobRunner" + }; + await Sender.Send(cmd, cancellationToken); + + throw; + } + } +} diff --git a/ECMJobRunner.Application/Profiles/Queries/GetProfileQuery.cs b/ECMJobRunner.Application/Profiles/Queries/GetProfileQuery.cs index dbd2ca3..f9dcca4 100644 --- a/ECMJobRunner.Application/Profiles/Queries/GetProfileQuery.cs +++ b/ECMJobRunner.Application/Profiles/Queries/GetProfileQuery.cs @@ -2,6 +2,7 @@ using AutoMapper; using ECMJobRunner.Application.Common.Dtos; using ECMJobRunner.Domain.Interfaces; using MediatR; +using System; using System.Collections.Generic; using System.Linq; using System.Threading; @@ -58,12 +59,23 @@ namespace ECMJobRunner.Application.Profiles.Queries private readonly ICfgProfileRepository _profileRepository; private readonly IMapper _mapper; + /// + /// Constructor + /// + /// Repository for profile data access + /// AutoMapper instance for entity-to-DTO mapping public GetProfileQueryHandler(ICfgProfileRepository profileRepository, IMapper mapper) { _profileRepository = profileRepository; _mapper = mapper; } + /// + /// Handles the by retrieving and mapping profiles + /// + /// The query containing optional filter parameters + /// Cancellation token + /// List of matched profiles mapped to public async Task> Handle(GetProfileQuery request, CancellationToken cancellationToken) { IEnumerable profiles; @@ -112,8 +124,11 @@ namespace ECMJobRunner.Application.Profiles.Queries if (!string.IsNullOrWhiteSpace(request.ProfileName)) { - var searchName = request.ProfileName.ToLowerInvariant(); - profiles = profiles.Where(p => p.ProfileName.ToLowerInvariant().Contains(searchName)); +#if NET + profiles = profiles.Where(p => p.ProfileName.Contains(request.ProfileName, StringComparison.OrdinalIgnoreCase)); +#else + profiles = profiles.Where(p => p.ProfileName.IndexOf(request.ProfileName!, StringComparison.OrdinalIgnoreCase) >= 0); +#endif } }