Files
BackOffice.BFF/src/BackOffice.BFF.WebApi/Common/Services/DispatchRequestToCQRS.cs

233 lines
9.2 KiB
C#
Raw Normal View History

2025-09-28 15:24:13 +03:30
namespace BackOffice.BFF.WebApi.Common.Services;
using Microsoft.Extensions.Logging;
using System.Collections.Generic;
2025-09-28 15:24:13 +03:30
public interface IDispatchRequestToCQRS
{
Task<TResponse> Handle<TRequest, TCommand, TResponse>(TRequest request,
ServerCallContext context);
Task<Empty> Handle<TRequest, TCommand>(TRequest request,
ServerCallContext context);
Task<TResponse> Handle<TCommand, TResponse>(ServerCallContext context);
}
public class DispatchRequestToCQRS : IDispatchRequestToCQRS
{
private readonly ISender _sender;
private readonly ILogger<DispatchRequestToCQRS> _logger;
2025-09-28 15:24:13 +03:30
public DispatchRequestToCQRS(ISender sender, ILogger<DispatchRequestToCQRS> logger)
2025-09-28 15:24:13 +03:30
{
_sender = sender;
_logger = logger;
2025-09-28 15:24:13 +03:30
}
public async Task<TResponse> Handle<TRequest, TCommand, TResponse>(TRequest request,
ServerCallContext context)
{
try
{
if (request is null)
{
throw new ArgumentNullException(nameof(request));
}
var cqrsInput = request.Adapt<TCommand>();
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);
}
2025-09-28 15:24:13 +03:30
}
catch (Exception ex)
2025-09-28 15:24:13 +03:30
{
_logger.LogError(ex, "Error in DispatchRequestToCQRS.Handle<{Request},{Command},{Response}>",
typeof(TRequest).Name, typeof(TCommand).Name, typeof(TResponse).Name);
2025-09-28 15:24:13 +03:30
throw;
}
}
public async Task<TResponse> Handle<TCommand, TResponse>(ServerCallContext context)
{
try
{
var cqrsInput = Activator.CreateInstance<TCommand>();
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);
}
2025-09-28 15:24:13 +03:30
}
catch (Exception ex)
2025-09-28 15:24:13 +03:30
{
_logger.LogError(ex, "Error in DispatchRequestToCQRS.Handle<{Command},{Response}>",
typeof(TCommand).Name, typeof(TResponse).Name);
2025-09-28 15:24:13 +03:30
throw;
}
}
public async Task<Empty> Handle<TRequest, TCommand>(TRequest request,
ServerCallContext context)
{
try
{
if (request is null)
{
throw new ArgumentNullException(nameof(request));
}
var cqrsInput = request.Adapt<TCommand>();
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");
}
}
2025-09-28 15:24:13 +03:30
}