namespace BackOffice.BFF.WebApi.Common.Services; using Microsoft.Extensions.Logging; using System.Collections.Generic; public interface IDispatchRequestToCQRS { Task Handle(TRequest request, ServerCallContext context); Task Handle(TRequest request, ServerCallContext context); Task Handle(ServerCallContext context); } public class DispatchRequestToCQRS : IDispatchRequestToCQRS { private readonly ISender _sender; private readonly ILogger _logger; public DispatchRequestToCQRS(ISender sender, ILogger logger) { _sender = sender; _logger = logger; } public async Task Handle(TRequest request, ServerCallContext context) { try { if (request is null) { throw new ArgumentNullException(nameof(request)); } var cqrsInput = request.Adapt(); if (cqrsInput is null) { throw new ArgumentNullException(nameof(cqrsInput)); } var output = await _sender.Send(cqrsInput, context.CancellationToken); if (output is null) { _logger.LogError("Command/Query {CommandType} returned null output", typeof(TCommand).Name); throw new InvalidOperationException($"Command/Query {typeof(TCommand).Name} returned null output"); } try { _logger.LogInformation("Mapping from {SourceType} to {TargetType}", output.GetType().Name, typeof(TResponse).Name); // Try to detect problematic properties before mapping LogSourceObjectStructure(output); // Use TypeAdapter.Adapt instead of extension method to ensure MapWith() is used var result = TypeAdapter.Adapt(output, output.GetType(), typeof(TResponse)); _logger.LogInformation("Mapping successful: {SourceType} -> {TargetType}", output.GetType().Name, typeof(TResponse).Name); return (TResponse)result; } catch (Exception mappingEx) { _logger.LogError(mappingEx, "Mapping failed: {SourceType} -> {TargetType}. Full error: {ErrorMessage}", output.GetType().Name, typeof(TResponse).Name, mappingEx.ToString()); // Log detailed property information LogDetailedPropertyInfo(output, typeof(TResponse)); throw new InvalidOperationException( $"Failed to map {output.GetType().Name} to {typeof(TResponse).Name}. Check ProductsProfile configuration.", mappingEx); } } catch (Exception ex) { _logger.LogError(ex, "Error in DispatchRequestToCQRS.Handle<{Request},{Command},{Response}>", typeof(TRequest).Name, typeof(TCommand).Name, typeof(TResponse).Name); throw; } } public async Task Handle(ServerCallContext context) { try { var cqrsInput = Activator.CreateInstance(); if (cqrsInput is null) { throw new ArgumentNullException(nameof(cqrsInput)); } var output = await _sender.Send(cqrsInput, context.CancellationToken); if (output is null) { _logger.LogError("Command/Query {CommandType} returned null output", typeof(TCommand).Name); throw new InvalidOperationException($"Command/Query {typeof(TCommand).Name} returned null output"); } try { _logger.LogInformation("Mapping from {SourceType} to {TargetType}", output.GetType().Name, typeof(TResponse).Name); // Use TypeAdapter.Adapt instead of extension method to ensure MapWith() is used var result = TypeAdapter.Adapt(output, output.GetType(), typeof(TResponse)); _logger.LogInformation("Mapping successful: {SourceType} -> {TargetType}", output.GetType().Name, typeof(TResponse).Name); return (TResponse)result; } catch (Exception mappingEx) { _logger.LogError(mappingEx, "Mapping failed: {SourceType} -> {TargetType}. Source properties: {@SourceData}", output.GetType().Name, typeof(TResponse).Name, output); throw new InvalidOperationException( $"Failed to map {output.GetType().Name} to {typeof(TResponse).Name}. Check mapping Profile configuration.", mappingEx); } } catch (Exception ex) { _logger.LogError(ex, "Error in DispatchRequestToCQRS.Handle<{Command},{Response}>", typeof(TCommand).Name, typeof(TResponse).Name); throw; } } public async Task Handle(TRequest request, ServerCallContext context) { try { if (request is null) { throw new ArgumentNullException(nameof(request)); } var cqrsInput = request.Adapt(); if (cqrsInput is null) { throw new ArgumentNullException(nameof(cqrsInput)); } await _sender.Send(cqrsInput, context.CancellationToken); return new Empty(); } catch (Exception) { throw; } } private void LogSourceObjectStructure(object source) { try { var properties = source.GetType().GetProperties(); _logger.LogInformation("Source object has {PropertyCount} properties:", properties.Length); foreach (var prop in properties) { var value = prop.GetValue(source); var valueType = value?.GetType().Name ?? "null"; var isCollection = value != null && value.GetType().IsGenericType && value.GetType().GetGenericTypeDefinition() == typeof(List<>); if (isCollection) { var count = ((System.Collections.ICollection)value).Count; _logger.LogInformation(" - {PropertyName}: {PropertyType} (Collection with {Count} items)", prop.Name, prop.PropertyType.Name, count); } else { _logger.LogInformation(" - {PropertyName}: {PropertyType} = {Value}", prop.Name, prop.PropertyType.Name, value); } } } catch (Exception ex) { _logger.LogWarning(ex, "Could not log source object structure"); } } private void LogDetailedPropertyInfo(object source, System.Type targetType) { try { var sourceProps = source.GetType().GetProperties(); var targetProps = targetType.GetProperties(); _logger.LogError("=== DETAILED MAPPING ANALYSIS ==="); _logger.LogError("Source type: {SourceType} ({SourcePropsCount} properties)", source.GetType().FullName, sourceProps.Length); _logger.LogError("Target type: {TargetType} ({TargetPropsCount} properties)", targetType.FullName, targetProps.Length); foreach (var sourceProp in sourceProps) { var targetProp = targetProps.FirstOrDefault(p => p.Name.Equals(sourceProp.Name, StringComparison.OrdinalIgnoreCase)); if (targetProp != null) { var sourceValue = sourceProp.GetValue(source); var sourceType = sourceProp.PropertyType; var targetPropType = targetProp.PropertyType; var isImmutable = targetPropType.FullName?.Contains("Google.Protobuf") == true || targetPropType.Name.Contains("RepeatedField"); _logger.LogError(" Property '{PropertyName}':", sourceProp.Name); _logger.LogError(" Source: {SourceType} (value: {Value})", sourceType.Name, sourceValue); _logger.LogError(" Target: {TargetType} {ImmutableWarning}", targetPropType.Name, isImmutable ? "⚠️ IMMUTABLE - needs MapWith()" : ""); } else { _logger.LogWarning(" Property '{PropertyName}' exists in source but NOT in target", sourceProp.Name); } } } catch (Exception ex) { _logger.LogWarning(ex, "Could not log detailed property info"); } } }