generated from ricardo/MVCLogin
255 lines
11 KiB
C#
255 lines
11 KiB
C#
namespace SumaTube.Crosscutting.Mappers
|
|
{
|
|
using System;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
|
|
public static class GenericMapper
|
|
{
|
|
public static TDestination MapTo<TSource, TDestination>(this TSource source)
|
|
where TDestination : class
|
|
{
|
|
if (source == null)
|
|
return default;
|
|
|
|
var destType = typeof(TDestination);
|
|
|
|
// Verifica se o tipo de destino tem um construtor público
|
|
var constructors = destType.GetConstructors(BindingFlags.Public | BindingFlags.Instance)
|
|
.OrderByDescending(c => c.GetParameters().Length)
|
|
.ToList();
|
|
|
|
// Se o tipo de destino tem um construtor com parâmetros, tenta mapear para ele
|
|
if (constructors.Any() && constructors[0].GetParameters().Length > 0)
|
|
{
|
|
return MapToImmutableObject<TSource, TDestination>(source, constructors);
|
|
}
|
|
// Caso contrário, usa a abordagem de mapeamento de propriedades para objetos mutáveis
|
|
else
|
|
{
|
|
return MapToMutableObject<TSource, TDestination>(source);
|
|
}
|
|
}
|
|
|
|
private static TDestination MapToImmutableObject<TSource, TDestination>(TSource source, List<ConstructorInfo> constructors)
|
|
where TDestination : class
|
|
{
|
|
var sourceType = typeof(TSource);
|
|
var sourceProps = sourceType.GetProperties(BindingFlags.Public | BindingFlags.Instance);
|
|
|
|
// Tenta cada construtor, começando pelo que tem mais parâmetros
|
|
foreach (var constructor in constructors)
|
|
{
|
|
var parameters = constructor.GetParameters();
|
|
if (parameters.Length == 0)
|
|
{
|
|
// Construtor sem parâmetros, cria instância diretamente
|
|
var instance = constructor.Invoke(null);
|
|
return (TDestination)instance;
|
|
}
|
|
|
|
var parameterValues = new object[parameters.Length];
|
|
bool canUseConstructor = true;
|
|
|
|
// Tenta mapear os parâmetros do construtor
|
|
for (int i = 0; i < parameters.Length; i++)
|
|
{
|
|
var param = parameters[i];
|
|
|
|
// Procura propriedade com o mesmo nome (case insensitive)
|
|
var matchingProp = sourceProps.FirstOrDefault(p =>
|
|
string.Equals(p.Name, param.Name, StringComparison.OrdinalIgnoreCase));
|
|
|
|
if (matchingProp != null)
|
|
{
|
|
var value = matchingProp.GetValue(source);
|
|
|
|
// Se os tipos são compatíveis, usa o valor diretamente
|
|
if (param.ParameterType.IsAssignableFrom(matchingProp.PropertyType))
|
|
{
|
|
parameterValues[i] = value;
|
|
}
|
|
// Verifica se existe uma conversão implícita
|
|
else if (value != null && TryImplicitConversion(value, param.ParameterType, out var convertedValue))
|
|
{
|
|
parameterValues[i] = convertedValue;
|
|
}
|
|
// Se o valor é um tipo complexo, tenta mapear recursivamente
|
|
else if (value != null && !matchingProp.PropertyType.IsPrimitive &&
|
|
!matchingProp.PropertyType.Namespace.StartsWith("System"))
|
|
{
|
|
try
|
|
{
|
|
var method = typeof(GenericMapper).GetMethod(nameof(MapTo));
|
|
var genericMethod = method.MakeGenericMethod(matchingProp.PropertyType, param.ParameterType);
|
|
parameterValues[i] = genericMethod.Invoke(null, new[] { value });
|
|
}
|
|
catch
|
|
{
|
|
canUseConstructor = false;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
canUseConstructor = false;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Se não encontrou uma propriedade correspondente, verifica se o parâmetro é opcional
|
|
if (param.IsOptional)
|
|
{
|
|
parameterValues[i] = param.DefaultValue;
|
|
}
|
|
else
|
|
{
|
|
canUseConstructor = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (canUseConstructor)
|
|
{
|
|
try
|
|
{
|
|
var instance = constructor.Invoke(parameterValues);
|
|
return (TDestination)instance;
|
|
}
|
|
catch
|
|
{
|
|
// Se falhou ao criar a instância, tenta o próximo construtor
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Se não conseguiu usar nenhum construtor, lança exceção
|
|
throw new InvalidOperationException(
|
|
$"Não foi possível mapear {sourceType.Name} para {typeof(TDestination).Name} " +
|
|
$"usando os construtores disponíveis. Verifique se os nomes das propriedades " +
|
|
$"correspondem aos nomes dos parâmetros do construtor (case insensitive).");
|
|
}
|
|
|
|
private static TDestination MapToMutableObject<TSource, TDestination>(TSource source)
|
|
where TDestination : class
|
|
{
|
|
var destType = typeof(TDestination);
|
|
|
|
// Tenta criar uma instância usando o construtor sem parâmetros
|
|
TDestination destination;
|
|
try
|
|
{
|
|
destination = Activator.CreateInstance<TDestination>();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
throw new InvalidOperationException(
|
|
$"Não foi possível criar uma instância de {destType.Name}. " +
|
|
$"Certifique-se de que a classe tenha um construtor sem parâmetros acessível.", ex);
|
|
}
|
|
|
|
var sourceType = typeof(TSource);
|
|
var sourceProps = sourceType.GetProperties(BindingFlags.Public | BindingFlags.Instance);
|
|
var destProps = destType.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.SetProperty);
|
|
|
|
foreach (var destProp in destProps)
|
|
{
|
|
if (!destProp.CanWrite)
|
|
continue;
|
|
|
|
// Procura propriedade com o mesmo nome (case insensitive)
|
|
var sourceProp = sourceProps.FirstOrDefault(p =>
|
|
string.Equals(p.Name, destProp.Name, StringComparison.OrdinalIgnoreCase));
|
|
|
|
if (sourceProp != null)
|
|
{
|
|
var value = sourceProp.GetValue(source);
|
|
if (value != null)
|
|
{
|
|
// Se os tipos são compatíveis, atribui diretamente
|
|
if (destProp.PropertyType.IsAssignableFrom(sourceProp.PropertyType))
|
|
{
|
|
destProp.SetValue(destination, value);
|
|
}
|
|
// Verifica se existe uma conversão implícita
|
|
else if (TryImplicitConversion(value, destProp.PropertyType, out var convertedValue))
|
|
{
|
|
destProp.SetValue(destination, convertedValue);
|
|
}
|
|
// Se o valor é um tipo complexo, tenta mapear recursivamente
|
|
else if (!sourceProp.PropertyType.IsPrimitive &&
|
|
!sourceProp.PropertyType.Namespace.StartsWith("System"))
|
|
{
|
|
try
|
|
{
|
|
var method = typeof(GenericMapper).GetMethod(nameof(MapTo));
|
|
var genericMethod = method.MakeGenericMethod(sourceProp.PropertyType, destProp.PropertyType);
|
|
var mappedValue = genericMethod.Invoke(null, new[] { value });
|
|
destProp.SetValue(destination, mappedValue);
|
|
}
|
|
catch
|
|
{
|
|
// Ignora se não conseguir mapear
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return destination;
|
|
}
|
|
|
|
private static bool TryImplicitConversion(object source, Type destinationType, out object result)
|
|
{
|
|
result = null;
|
|
if (source == null) return false;
|
|
|
|
var sourceType = source.GetType();
|
|
|
|
// Verifica operador de conversão implícita no tipo de origem
|
|
var methodSource = sourceType.GetMethods(BindingFlags.Public | BindingFlags.Static)
|
|
.FirstOrDefault(m =>
|
|
m.Name == "op_Implicit" &&
|
|
m.ReturnType == destinationType &&
|
|
m.GetParameters().Length == 1 &&
|
|
m.GetParameters()[0].ParameterType == sourceType);
|
|
|
|
if (methodSource != null)
|
|
{
|
|
result = methodSource.Invoke(null, new[] { source });
|
|
return true;
|
|
}
|
|
|
|
// Verifica operador de conversão implícita no tipo de destino
|
|
var methodDest = destinationType.GetMethods(BindingFlags.Public | BindingFlags.Static)
|
|
.FirstOrDefault(m =>
|
|
m.Name == "op_Implicit" &&
|
|
m.ReturnType == destinationType &&
|
|
m.GetParameters().Length == 1 &&
|
|
m.GetParameters()[0].ParameterType == sourceType);
|
|
|
|
if (methodDest != null)
|
|
{
|
|
result = methodDest.Invoke(null, new[] { source });
|
|
return true;
|
|
}
|
|
|
|
// Tenta converter usando Convert.ChangeType
|
|
try
|
|
{
|
|
if (destinationType.IsValueType || destinationType == typeof(string))
|
|
{
|
|
result = Convert.ChangeType(source, destinationType);
|
|
return true;
|
|
}
|
|
}
|
|
catch { }
|
|
|
|
return false;
|
|
}
|
|
}
|
|
}
|