Search code examples
c#.netcqrsfluentvalidationmediatr

FluentValidation conversion error using MediatR and C#


I am having trouble returning the response from my response class, which has the FluentValidation validations, it shows me the following error message: enter image description here This is my request class,

public class CreateCivilStatusCommand : IRequest<BaseResponse<CivilStatusResponseDto>>
{
    #region Properties
    public string? CivilStatusId { get; set; }
    public string? CivilStatusName { get; set; }
    public string? CreateBy { get; set; }
    public DateTime CreationDate { get; set; }
    public DateTime ModifiedDate { get; set; }

    #endregion
}

This is my Handle method,

public async Task<BaseResponse<CivilStatusResponseDto>> Handle(CreateCivilStatusCommand request, CancellationToken cancellationToken)
{
    BaseResponse<CivilStatusResponseDto> response = new BaseResponse<CivilStatusResponseDto>();

    try
    {
        CivilStatus civilStatus = await _civilStatusRepository.SaveCivilStatus(_mapper.Map<CivilStatus>(request));

        response.IsSuccess = true;
        response.Data = _mapper.Map<CivilStatusResponseDto>(civilStatus);
        response.Message = GlobalMessages.MESSAGE_SAVE;

    }
    catch (Exception ex)
    {
        if (ex.Source is not null && ex.Source.Contains("SqlClient"))
        {
            response.IsSuccess = false;
            response.Message = ex.Message;
        }
        else
        {
            response.IsSuccess = false;
            response.Message = GlobalMessages.MESSAGE_EXCEPTION;
            WatchLogger.Log(ex.Message);
        }
    }

    return response;
}

This is my validation rules,

public CreateCivilStatusValidator()
{
    RuleFor(x => x.CivilStatusId)
        .NotNull().WithMessage("El campo Codigo no puede ser nulo.")
        .NotEmpty().WithMessage("El campo Codigo no puede ser vacio.")
        .MaximumLength(1).WithMessage("La cantidad de caracteres permitidos, para el campo Codigo es de 1 caracter.");

    RuleFor(x => x.CivilStatusName)
        .NotNull().WithMessage("El campo Estado Civil no puede ser nulo.")
        .NotEmpty().WithMessage("El campo Estado Civil no puede ser vacio.")
        .MaximumLength(15).WithMessage("La cantidad de caracteres permitidos, para el campo Estado Civil son 15 caracteres.");

    RuleFor(x => x.CreateBy)
        .NotNull().WithMessage("El campo Creado Por no puede ser nulo.")
        .NotEmpty().WithMessage("El campo Creado Por no puede ser vacio.")
        .EmailAddress().WithMessage("En el campo Creado Por se debe de ingresar un correo electronico valido.")
        .MaximumLength(60).WithMessage("La cantidad de caracteres permitidos, para el campo Creado por son 60 caracteres.");

    RuleFor(x => x.CreationDate)
        .NotNull().WithMessage("El campo Fecha de Creacion no puede ser nulo.");

    RuleFor(x => x.ModifiedDate)
        .NotNull().WithMessage("El campo Fecha de Modificacion no puede ser nulo.");
}

And this is my BaseResponse class,

public class BaseResponse<T>
{
    #region Properties
    public bool IsSuccess { get; set; }
    public T? Data { get; set; }
    public string? Message { get; set; }
    public IEnumerable<BaseError>? Errors { get; set; }

    #endregion
}

And this is my BaseError class, where I try to save the field and the error message,

public class BaseError
{
    #region Properties
    public string? PropertyName { get; set; }
    public string? ErrorMessage { get; set; }

    #endregion
}

this is my ValidationMiddleware,

public class ValidationMiddleware<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> where TRequest : IRequest<TResponse>
    {
        #region Properties
        private readonly IEnumerable<IValidator<TRequest>> _validators;
    
        #endregion
    
        #region Constructors
        public ValidationMiddleware(IEnumerable<IValidator<TRequest>> validators)
        {
            _validators = validators;
        }
    
        #endregion
    
        #region Methods
        public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken cancellationToken)
        {
            ValidationContext<TRequest> context = new ValidationContext<TRequest>(request);
    
            ValidationResult[] validationResults = await Task.WhenAll(_validators.Select(v => v.ValidateAsync(context, cancellationToken)));
            
            List<ValidationFailure> failures = validationResults.SelectMany(r => r.Errors).Where(f => f != null).ToList();
    
            if (failures.Count > 0)
            {
                BaseResponse<TResponse> response = new BaseResponse<TResponse>
                {
                    IsSuccess = false,
                    Errors = failures.Select(error => new BaseError
                    {
                        PropertyName = error.PropertyName,
                        ErrorMessage = error.ErrorMessage
                    }).ToList(),
                    Message = GlobalMessages.MESSAGE_VALIDATE,
                };
    
                return (TResponse)(object)response;
            }
    
            return await next();
        }
    
        #endregion
    }

I can't find the way that, depending on the type of response, since I am using a ValidationMiddleware, so that all the classes that implement a request, in case there are validation values, return a response, indicating in which field and the description of the validation,

I have been researching in Google, but I do not give how to solve this problem, I would appreciate the help,

Stay tuned to your comments,


Solution

  • The problem is in the validation middleware, where u create response.

    BaseResponse<TResponse> response = new BaseResponse<TResponse>
    

    TResponse in this scenario is type of BaseResponse[CivilStatusResponseDto] and you are using it as TypePramateare for BaseResponse which duplicates it.

    and as the exception says it can not convert from BaseResponse[BaseResponse[CivilStatusResponseDto]] to BaseResponse[CivilStatusResponseDto]

    To fix that you can do this: you can split your base response into 2 classes like this:

     public class BaseResponse
    {
        public bool IsSuccess { get; set; }
        
        public string? Message { get; set; }
        public IEnumerable<BaseError>? Errors { get; set; }
    }
    public class BaseResponse<T>:BaseResponse
    {
        public T? Data { get; set; }
    }
    

    and then you can make TResponse of your middleware to be of type of none-generic BaseResponse like this:

     public class ValidationMiddleware<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> 
        where TRequest : IRequest<TResponse>
        where TResponse: BaseResponse
    

    and you can generate you response like this:

    BaseResponse response = new BaseResponse
            {
                IsSuccess = false,
                Errors = failures.Select(error => new BaseError
                {
                    PropertyName = error.PropertyName,
                    ErrorMessage = error.ErrorMessage
                }).ToList(),
                Message = GlobalMessages.MESSAGE_VALIDATE,
            };
    
            return (TResponse)response;
    

    Alternative:

    You can use reflection to create an instance of response and fill its properties like this:

     var response =(TResponse) Activator.CreateInstance(typeof(TResponse), new object[] { });
    
                var isSuccessProp = typeof(TResponse).GetProperty("IsSuccess");
                isSuccessProp?.SetValue(response, false);
                
                var errorsProp = typeof(TResponse).GetProperty("Errors");
                errorsProp?.SetValue(response, failures.Select(error => new BaseError
                {
                    PropertyName = error.PropertyName,
                    ErrorMessage = error.ErrorMessage
                }).ToList());
    
                var messageProp = typeof(TResponse).GetProperty("Message");
                messageProp?.SetValue(response, GlobalMessages.MESSAGE_VALIDATE);
    

    However i dont recoment using this since the other answer is much better approach.