commit
This commit is contained in:
@@ -0,0 +1,12 @@
|
|||||||
|
namespace MagicCarpetContracts.Mapper;
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = true)]
|
||||||
|
public class AlternativeNameAttribute : Attribute
|
||||||
|
{
|
||||||
|
public string AlternativeName { get; }
|
||||||
|
|
||||||
|
public AlternativeNameAttribute(string alternativeName)
|
||||||
|
{
|
||||||
|
AlternativeName = alternativeName;
|
||||||
|
}
|
||||||
|
}
|
||||||
281
CandyHouseSolution/CandyHouseContracts/Mapper/CustomMapper.cs
Normal file
281
CandyHouseSolution/CandyHouseContracts/Mapper/CustomMapper.cs
Normal file
@@ -0,0 +1,281 @@
|
|||||||
|
using System.Collections;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace MagicCarpetContracts.Mapper;
|
||||||
|
|
||||||
|
internal static class CustomMapper
|
||||||
|
{
|
||||||
|
public static To MapObject<To>(object obj, To newObject)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(obj);
|
||||||
|
ArgumentNullException.ThrowIfNull(newObject);
|
||||||
|
|
||||||
|
var typeFrom = obj.GetType();
|
||||||
|
var typeTo = newObject.GetType();
|
||||||
|
|
||||||
|
var propertiesFrom = typeFrom.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
|
||||||
|
.Where(x => x.CanRead)
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
// свойств
|
||||||
|
foreach (var property in typeTo.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
|
||||||
|
.Where(x => x.CanWrite))
|
||||||
|
{
|
||||||
|
if (property.GetCustomAttribute<IgnoreMappingAttribute>() is not null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var propertyFrom = TryGetPropertyFrom(property, propertiesFrom);
|
||||||
|
if (propertyFrom is null)
|
||||||
|
{
|
||||||
|
FindAndMapDefaultValue(property, newObject);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var fromValue = propertyFrom.GetValue(obj);
|
||||||
|
var postProcessingAttribute = property.GetCustomAttribute<PostProcessingAttribute>();
|
||||||
|
if (postProcessingAttribute is not null)
|
||||||
|
{
|
||||||
|
var value = PostProcessing(fromValue, postProcessingAttribute, newObject);
|
||||||
|
if (value is not null)
|
||||||
|
{
|
||||||
|
property.SetValue(newObject, value);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (propertyFrom.PropertyType.IsGenericType && propertyFrom.PropertyType.Name.StartsWith("List") && fromValue is not null)
|
||||||
|
{
|
||||||
|
fromValue = MapListOfObjects(property, fromValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (propertyFrom.PropertyType.IsEnum && property.PropertyType == typeof(string) && fromValue != null)
|
||||||
|
{
|
||||||
|
fromValue = fromValue.ToString();
|
||||||
|
}
|
||||||
|
else if (!propertyFrom.PropertyType.IsEnum && property.PropertyType.IsEnum && fromValue is not null)
|
||||||
|
{
|
||||||
|
if (fromValue is string stringValue)
|
||||||
|
fromValue = Enum.Parse(property.PropertyType, stringValue);
|
||||||
|
else
|
||||||
|
fromValue = Enum.ToObject(property.PropertyType, fromValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fromValue is not null)
|
||||||
|
{
|
||||||
|
if (propertyFrom.PropertyType.IsClass
|
||||||
|
&& property.PropertyType.IsClass
|
||||||
|
&& propertyFrom.PropertyType != typeof(string)
|
||||||
|
&& property.PropertyType != typeof(string)
|
||||||
|
&& !property.PropertyType.IsAssignableFrom(propertyFrom.PropertyType))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var nestedInstance = Activator.CreateInstance(property.PropertyType);
|
||||||
|
if (nestedInstance != null)
|
||||||
|
{
|
||||||
|
var nestedMapped = MapObject(fromValue, nestedInstance);
|
||||||
|
property.SetValue(newObject, nestedMapped);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
property.SetValue(newObject, fromValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// полей
|
||||||
|
var fieldsTo = typeTo.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
|
||||||
|
var fieldsFrom = typeFrom.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
|
||||||
|
|
||||||
|
foreach (var field in fieldsTo)
|
||||||
|
{
|
||||||
|
if (field.Name.Contains("k__BackingField"))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (field.GetCustomAttribute<IgnoreMappingAttribute>() is not null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var sourceField = fieldsFrom.FirstOrDefault(f => f.Name == field.Name);
|
||||||
|
object? fromValue = null;
|
||||||
|
|
||||||
|
if (sourceField is not null)
|
||||||
|
{
|
||||||
|
fromValue = sourceField.GetValue(obj);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var propertyName = field.Name.TrimStart('_');
|
||||||
|
var sourceProperty = typeFrom.GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
||||||
|
if (sourceProperty is not null && sourceProperty.CanRead)
|
||||||
|
{
|
||||||
|
fromValue = sourceProperty.GetValue(obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fromValue is null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (field.FieldType.IsClass && field.FieldType != typeof(string))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var nested = Activator.CreateInstance(field.FieldType)!;
|
||||||
|
var mapped = MapObject(fromValue, nested);
|
||||||
|
RemoveReadOnly(field);
|
||||||
|
field.SetValue(newObject, mapped);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RemoveReadOnly(field);
|
||||||
|
field.SetValue(newObject, fromValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
var classPostProcessing = typeTo.GetCustomAttribute<PostProcessingAttribute>();
|
||||||
|
if (classPostProcessing is not null && classPostProcessing.MappingCallMethodName is not null)
|
||||||
|
{
|
||||||
|
var methodInfo = typeTo.GetMethod(classPostProcessing.MappingCallMethodName, BindingFlags.NonPublic | BindingFlags.Instance);
|
||||||
|
methodInfo?.Invoke(newObject, []);
|
||||||
|
}
|
||||||
|
|
||||||
|
return newObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RemoveReadOnly(FieldInfo field)
|
||||||
|
{
|
||||||
|
if (!field.IsInitOnly)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var attr = typeof(FieldInfo).GetField("m_fieldAttributes", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||||
|
if (attr != null)
|
||||||
|
{
|
||||||
|
var current = (FieldAttributes)attr.GetValue(field)!;
|
||||||
|
attr.SetValue(field, current & ~FieldAttributes.InitOnly);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static To MapObject<To>(object obj) => MapObject(obj, Activator.CreateInstance<To>()!);
|
||||||
|
|
||||||
|
public static To? MapObjectWithNull<To>(object? obj) => obj is null ? default : MapObject(obj, Activator.CreateInstance<To>());
|
||||||
|
|
||||||
|
private static PropertyInfo? TryGetPropertyFrom(PropertyInfo propertyTo, PropertyInfo[] propertiesFrom)
|
||||||
|
{
|
||||||
|
var customAttribute = propertyTo.GetCustomAttributes<AlternativeNameAttribute>()?
|
||||||
|
.ToArray()
|
||||||
|
.FirstOrDefault(x => propertiesFrom.Any(y => y.Name == x.AlternativeName));
|
||||||
|
if (customAttribute is not null)
|
||||||
|
{
|
||||||
|
return propertiesFrom.FirstOrDefault(x => x.Name == customAttribute.AlternativeName);
|
||||||
|
}
|
||||||
|
return propertiesFrom.FirstOrDefault(x => x.Name == propertyTo.Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static object? PostProcessing<T>(object? value, PostProcessingAttribute postProcessingAttribute, T newObject)
|
||||||
|
{
|
||||||
|
if (value is null || newObject is null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (!string.IsNullOrEmpty(postProcessingAttribute.MappingCallMethodName))
|
||||||
|
{
|
||||||
|
var methodInfo =
|
||||||
|
newObject.GetType().GetMethod(postProcessingAttribute.MappingCallMethodName, BindingFlags.NonPublic | BindingFlags.Instance);
|
||||||
|
if (methodInfo is not null)
|
||||||
|
{
|
||||||
|
return methodInfo.Invoke(newObject, [value]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (postProcessingAttribute.ActionType != PostProcessingType.None)
|
||||||
|
{
|
||||||
|
switch (postProcessingAttribute.ActionType)
|
||||||
|
{
|
||||||
|
case PostProcessingType.ToUniversalTime:
|
||||||
|
return ToUniversalTime(value);
|
||||||
|
case PostProcessingType.ToLocalTime:
|
||||||
|
return ToLocalTime(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static object? ToLocalTime(object? obj)
|
||||||
|
{
|
||||||
|
if (obj is DateTime date)
|
||||||
|
return date.ToLocalTime();
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static object? ToUniversalTime(object? obj)
|
||||||
|
{
|
||||||
|
if (obj is DateTime date)
|
||||||
|
return date.ToUniversalTime();
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void FindAndMapDefaultValue<T>(PropertyInfo property, T newObject)
|
||||||
|
{
|
||||||
|
var defaultValueAttribute = property.GetCustomAttribute<DefaultValueAttribute>();
|
||||||
|
if (defaultValueAttribute is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (defaultValueAttribute.DefaultValue is not null)
|
||||||
|
{
|
||||||
|
property.SetValue(newObject, defaultValueAttribute.DefaultValue);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var value = defaultValueAttribute.Func switch
|
||||||
|
{
|
||||||
|
DefaultValueFunc.UtcNow => DateTime.UtcNow,
|
||||||
|
_ => (object?)null,
|
||||||
|
};
|
||||||
|
if (value is not null)
|
||||||
|
{
|
||||||
|
property.SetValue(newObject, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static object? MapListOfObjects(PropertyInfo propertyTo, object list)
|
||||||
|
{
|
||||||
|
var listResult = Activator.CreateInstance(propertyTo.PropertyType);
|
||||||
|
var elementType = propertyTo.PropertyType.GenericTypeArguments[0];
|
||||||
|
|
||||||
|
foreach (var elem in (IEnumerable)list)
|
||||||
|
{
|
||||||
|
object? newElem;
|
||||||
|
|
||||||
|
if (elementType.IsPrimitive || elementType == typeof(string) || elementType == typeof(decimal) || elementType == typeof(DateTime))
|
||||||
|
{
|
||||||
|
newElem = elem;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
newElem = MapObject(elem, Activator.CreateInstance(elementType)!);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newElem is not null)
|
||||||
|
{
|
||||||
|
propertyTo.PropertyType.GetMethod("Add")!.Invoke(listResult, [newElem]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return listResult;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum DefaultValueFunc
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
UtcNow
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
namespace MagicCarpetContracts.Mapper;
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Class)]
|
||||||
|
class DefaultValueAttribute : Attribute
|
||||||
|
{
|
||||||
|
public object? DefaultValue { get; set; }
|
||||||
|
|
||||||
|
public string? FuncName { get; set; }
|
||||||
|
|
||||||
|
public DefaultValueFunc Func { get; set; } = DefaultValueFunc.None;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
namespace MagicCarpetContracts.Mapper;
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Property)]
|
||||||
|
class IgnoreMappingAttribute : Attribute
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
namespace MagicCarpetContracts.Mapper;
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Class | AttributeTargets.Field)]
|
||||||
|
class PostProcessingAttribute : Attribute
|
||||||
|
{
|
||||||
|
public string? MappingCallMethodName { get; set; }
|
||||||
|
|
||||||
|
public PostProcessingType ActionType { get; set; } = PostProcessingType.None;
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
namespace MagicCarpetContracts.Mapper;
|
||||||
|
|
||||||
|
enum PostProcessingType
|
||||||
|
{
|
||||||
|
None = -1,
|
||||||
|
|
||||||
|
ToUniversalTime = 1,
|
||||||
|
|
||||||
|
ToLocalTime = 2
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user