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