Got at least one data fetching method working; turns out, we can't use a patched LogicStack to get the data

This commit is contained in:
2026-01-14 22:11:11 +01:00
parent 40a8431464
commit 3f7122d30a
350 changed files with 41444 additions and 119 deletions

View File

@@ -0,0 +1,13 @@
using System;
namespace Swan.Mappers
{
/// <summary>
/// Represents an attribute to select which properties are copyable between objects.
/// </summary>
/// <seealso cref="Attribute" />
[AttributeUsage(AttributeTargets.Property)]
public class CopyableAttribute : Attribute
{
}
}

View File

@@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.Reflection;
namespace Swan.Mappers
{
/// <summary>
/// Interface object map.
/// </summary>
public interface IObjectMap
{
/// <summary>
/// Gets or sets the map.
/// </summary>
Dictionary<PropertyInfo, List<PropertyInfo>> Map { get; }
/// <summary>
/// Gets or sets the type of the source.
/// </summary>
Type SourceType { get; }
/// <summary>
/// Gets or sets the type of the destination.
/// </summary>
Type DestinationType { get; }
}
}

View File

@@ -0,0 +1,120 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
namespace Swan.Mappers
{
/// <summary>
/// Represents an object map.
/// </summary>
/// <typeparam name="TSource">The type of the source.</typeparam>
/// <typeparam name="TDestination">The type of the destination.</typeparam>
/// <seealso cref="IObjectMap" />
public class ObjectMap<TSource, TDestination> : IObjectMap
{
internal ObjectMap(IEnumerable<PropertyInfo> intersect)
{
SourceType = typeof(TSource);
DestinationType = typeof(TDestination);
Map = intersect.ToDictionary(
property => DestinationType.GetProperty(property.Name),
property => new List<PropertyInfo> {SourceType.GetProperty(property.Name)});
}
/// <inheritdoc/>
public Dictionary<PropertyInfo, List<PropertyInfo>> Map { get; }
/// <inheritdoc/>
public Type SourceType { get; }
/// <inheritdoc/>
public Type DestinationType { get; }
/// <summary>
/// Maps the property.
/// </summary>
/// <typeparam name="TDestinationProperty">The type of the destination property.</typeparam>
/// <typeparam name="TSourceProperty">The type of the source property.</typeparam>
/// <param name="destinationProperty">The destination property.</param>
/// <param name="sourceProperty">The source property.</param>
/// <returns>
/// An object map representation of type of the destination property
/// and type of the source property.
/// </returns>
public ObjectMap<TSource, TDestination> MapProperty
<TDestinationProperty, TSourceProperty>(
Expression<Func<TDestination, TDestinationProperty>> destinationProperty,
Expression<Func<TSource, TSourceProperty>> sourceProperty)
{
if (destinationProperty == null)
throw new ArgumentNullException(nameof(destinationProperty));
var propertyDestinationInfo = (destinationProperty.Body as MemberExpression)?.Member as PropertyInfo;
if (propertyDestinationInfo == null)
throw new ArgumentException("Invalid destination expression", nameof(destinationProperty));
var sourceMembers = GetSourceMembers(sourceProperty);
if (!sourceMembers.Any())
throw new ArgumentException("Invalid source expression", nameof(sourceProperty));
// reverse order
sourceMembers.Reverse();
Map[propertyDestinationInfo] = sourceMembers;
return this;
}
/// <summary>
/// Removes the map property.
/// </summary>
/// <typeparam name="TDestinationProperty">The type of the destination property.</typeparam>
/// <param name="destinationProperty">The destination property.</param>
/// <returns>
/// An object map representation of type of the destination property
/// and type of the source property.
/// </returns>
/// <exception cref="System.Exception">Invalid destination expression.</exception>
public ObjectMap<TSource, TDestination> RemoveMapProperty<TDestinationProperty>(
Expression<Func<TDestination, TDestinationProperty>> destinationProperty)
{
if (destinationProperty == null)
throw new ArgumentNullException(nameof(destinationProperty));
var propertyDestinationInfo = (destinationProperty.Body as MemberExpression)?.Member as PropertyInfo;
if (propertyDestinationInfo == null)
throw new ArgumentException("Invalid destination expression", nameof(destinationProperty));
if (Map.ContainsKey(propertyDestinationInfo))
{
Map.Remove(propertyDestinationInfo);
}
return this;
}
private static List<PropertyInfo> GetSourceMembers<TSourceProperty>(Expression<Func<TSource, TSourceProperty>> sourceProperty)
{
if (sourceProperty == null)
throw new ArgumentNullException(nameof(sourceProperty));
var sourceMembers = new List<PropertyInfo>();
var initialExpression = sourceProperty.Body as MemberExpression;
while (true)
{
var propertySourceInfo = initialExpression?.Member as PropertyInfo;
if (propertySourceInfo == null) break;
sourceMembers.Add(propertySourceInfo);
initialExpression = initialExpression.Expression as MemberExpression;
}
return sourceMembers;
}
}
}

View File

@@ -0,0 +1,24 @@
using System.Collections.Generic;
using System.Reflection;
namespace Swan.Mappers
{
/// <summary>
/// Represents an AutoMapper-like object to map from one object type
/// to another using defined properties map or using the default behaviour
/// to copy same named properties from one object to another.
///
/// The extension methods like CopyPropertiesTo use the default behaviour.
/// </summary>
public partial class ObjectMapper
{
internal class PropertyInfoComparer : IEqualityComparer<PropertyInfo>
{
public bool Equals(PropertyInfo x, PropertyInfo y)
=> x != null && y != null && x.Name == y.Name && x.PropertyType == y.PropertyType;
public int GetHashCode(PropertyInfo obj)
=> obj.Name.GetHashCode() + obj.PropertyType.Name.GetHashCode();
}
}
}

View File

@@ -0,0 +1,374 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Swan.Reflection;
namespace Swan.Mappers
{
/// <summary>
/// Represents an AutoMapper-like object to map from one object type
/// to another using defined properties map or using the default behaviour
/// to copy same named properties from one object to another.
///
/// The extension methods like CopyPropertiesTo use the default behaviour.
/// </summary>
/// <example>
/// The following code explains how to map an object's properties into an instance of type T.
/// <code>
/// using Swan.Mappers;
///
/// class Example
/// {
/// class Person
/// {
/// public string Name { get; set; }
/// public int Age { get; set; }
/// }
///
/// static void Main()
/// {
/// var obj = new { Name = "John", Age = 42 };
///
/// var person = Runtime.ObjectMapper.Map&lt;Person&gt;(obj);
/// }
/// }
/// </code>
///
/// The following code explains how to explicitly map certain properties.
/// <code>
/// using Swan.Mappers;
///
/// class Example
/// {
/// class User
/// {
/// public string Name { get; set; }
/// public Role Role { get; set; }
/// }
///
/// public class Role
/// {
/// public string Name { get; set; }
/// }
///
/// class UserDto
/// {
/// public string Name { get; set; }
/// public string Role { get; set; }
/// }
///
/// static void Main()
/// {
/// // create a User object
/// var person =
/// new User { Name = "Phillip", Role = new Role { Name = "Admin" } };
///
/// // create an Object Mapper
/// var mapper = new ObjectMapper();
///
/// // map the User's Role.Name to UserDto's Role
/// mapper.CreateMap&lt;User, UserDto&gt;()
/// .MapProperty(d => d.Role, x => x.Role.Name);
///
/// // apply the previous map and retrieve a UserDto object
/// var destination = mapper.Map&lt;UserDto&gt;(person);
/// }
/// }
/// </code>
/// </example>
public partial class ObjectMapper
{
private static readonly Lazy<ObjectMapper> LazyInstance = new Lazy<ObjectMapper>(() => new ObjectMapper());
private readonly List<IObjectMap> _maps = new List<IObjectMap>();
/// <summary>
/// Gets the current.
/// </summary>
/// <value>
/// The current.
/// </value>
public static ObjectMapper Current => LazyInstance.Value;
/// <summary>
/// Copies the specified source.
/// </summary>
/// <param name="source">The source.</param>
/// <param name="target">The target.</param>
/// <param name="propertiesToCopy">The properties to copy.</param>
/// <param name="ignoreProperties">The ignore properties.</param>
/// <returns>
/// Copied properties count.
/// </returns>
/// <exception cref="ArgumentNullException">
/// source
/// or
/// target.
/// </exception>
public static int Copy(
object source,
object target,
IEnumerable<string>? propertiesToCopy = null,
params string[]? ignoreProperties)
{
if (source == null)
throw new ArgumentNullException(nameof(source));
if (target == null)
throw new ArgumentNullException(nameof(target));
return CopyInternal(
target,
GetSourceMap(source),
propertiesToCopy,
ignoreProperties);
}
/// <summary>
/// Copies the specified source.
/// </summary>
/// <param name="source">The source.</param>
/// <param name="target">The target.</param>
/// <param name="propertiesToCopy">The properties to copy.</param>
/// <param name="ignoreProperties">The ignore properties.</param>
/// <returns>
/// Copied properties count.
/// </returns>
/// <exception cref="ArgumentNullException">
/// source
/// or
/// target.
/// </exception>
public static int Copy(
IDictionary<string, object>? source,
object? target,
IEnumerable<string>? propertiesToCopy = null,
params string[] ignoreProperties)
{
if (source == null)
throw new ArgumentNullException(nameof(source));
if (target == null)
throw new ArgumentNullException(nameof(target));
return CopyInternal(
target,
source.ToDictionary(
x => x.Key.ToLowerInvariant(),
x => Tuple.Create(typeof(object), x.Value)),
propertiesToCopy,
ignoreProperties);
}
/// <summary>
/// Creates the map.
/// </summary>
/// <typeparam name="TSource">The type of the source.</typeparam>
/// <typeparam name="TDestination">The type of the destination.</typeparam>
/// <returns>
/// An object map representation of type of the destination property
/// and type of the source property.
/// </returns>
/// <exception cref="InvalidOperationException">
/// You can't create an existing map
/// or
/// Types doesn't match.
/// </exception>
public ObjectMap<TSource, TDestination> CreateMap<TSource, TDestination>()
{
if (_maps.Any(x => x.SourceType == typeof(TSource) && x.DestinationType == typeof(TDestination)))
throw new InvalidOperationException("You can't create an existing map");
var sourceType = PropertyTypeCache.DefaultCache.Value.RetrieveAllProperties<TSource>(true);
var destinationType = PropertyTypeCache.DefaultCache.Value.RetrieveAllProperties<TDestination>(true);
var intersect = sourceType.Intersect(destinationType, new PropertyInfoComparer()).ToArray();
if (!intersect.Any())
throw new InvalidOperationException("Types doesn't match");
var map = new ObjectMap<TSource, TDestination>(intersect);
_maps.Add(map);
return map;
}
/// <summary>
/// Maps the specified source.
/// </summary>
/// <typeparam name="TDestination">The type of the destination.</typeparam>
/// <param name="source">The source.</param>
/// <param name="autoResolve">if set to <c>true</c> [automatic resolve].</param>
/// <returns>
/// A new instance of the map.
/// </returns>
/// <exception cref="ArgumentNullException">source.</exception>
/// <exception cref="InvalidOperationException">You can't map from type {source.GetType().Name} to {typeof(TDestination).Name}.</exception>
public TDestination Map<TDestination>(object source, bool autoResolve = true)
{
if (source == null)
throw new ArgumentNullException(nameof(source));
var destination = Activator.CreateInstance<TDestination>();
var map = _maps
.FirstOrDefault(x => x.SourceType == source.GetType() && x.DestinationType == typeof(TDestination));
if (map != null)
{
foreach (var property in map.Map)
{
var finalSource = property.Value.Aggregate(source,
(current, sourceProperty) => sourceProperty.GetValue(current));
property.Key.SetValue(destination, finalSource);
}
}
else
{
if (!autoResolve)
{
throw new InvalidOperationException(
$"You can't map from type {source.GetType().Name} to {typeof(TDestination).Name}");
}
// Missing mapping, try to use default behavior
Copy(source, destination!);
}
return destination;
}
private static int CopyInternal(
object target,
Dictionary<string, Tuple<Type, object>> sourceProperties,
IEnumerable<string>? propertiesToCopy,
IEnumerable<string>? ignoreProperties)
{
// Filter properties
var requiredProperties = propertiesToCopy?
.Where(p => !string.IsNullOrWhiteSpace(p))
.Select(p => p.ToLowerInvariant());
var ignoredProperties = ignoreProperties?
.Where(p => !string.IsNullOrWhiteSpace(p))
.Select(p => p.ToLowerInvariant());
var properties = PropertyTypeCache.DefaultCache.Value
.RetrieveFilteredProperties(target.GetType(), true, x => x.CanWrite);
return properties
.Select(x => x.Name)
.Distinct()
.ToDictionary(x => x.ToLowerInvariant(), x => properties.First(y => y.Name == x))
.Where(x => sourceProperties.Keys.Contains(x.Key))
.When(() => requiredProperties != null, q => q.Where(y => requiredProperties!.Contains(y.Key)))
.When(() => ignoredProperties != null, q => q.Where(y => !ignoredProperties!.Contains(y.Key)))
.ToDictionary(x => x.Value, x => sourceProperties[x.Key])
.Sum(x => TrySetValue(x.Key, x.Value, target) ? 1 : 0);
}
private static bool TrySetValue(PropertyInfo propertyInfo, Tuple<Type, object> property, object target)
{
try
{
var (type, value) = property;
if (type.IsEnum)
{
propertyInfo.SetValue(target,
Enum.ToObject(propertyInfo.PropertyType, value));
return true;
}
if (type.IsValueType || propertyInfo.PropertyType != type)
return propertyInfo.TrySetBasicType(value, target);
if (propertyInfo.PropertyType.IsArray)
{
propertyInfo.TrySetArray(value as IEnumerable<object>, target);
return true;
}
propertyInfo.SetValue(target, GetValue(value, propertyInfo.PropertyType));
return true;
}
#pragma warning disable CA1031 // Do not catch general exception types
catch
#pragma warning restore CA1031 // Do not catch general exception types
{
// swallow
}
return false;
}
private static object? GetValue(object source, Type targetType)
{
if (source == null)
return null;
object? target = null;
source.CreateTarget(targetType, false, ref target);
switch (source)
{
case string _:
target = source;
break;
case IList sourceList when target is IList targetList:
var addMethod = targetType.GetMethods()
.FirstOrDefault(
m => m.Name == Formatters.Json.AddMethodName && m.IsPublic && m.GetParameters().Length == 1);
if (addMethod == null) return target;
var isItemValueType = targetList.GetType().GetElementType().IsValueType;
foreach (var item in sourceList)
{
try
{
targetList.Add(isItemValueType
? item
: item.CopyPropertiesToNew<object>());
}
#pragma warning disable CA1031 // Do not catch general exception types
catch
#pragma warning restore CA1031 // Do not catch general exception types
{
// ignored
}
}
break;
default:
source.CopyPropertiesTo(target!);
break;
}
return target;
}
private static Dictionary<string, Tuple<Type, object>> GetSourceMap(object source)
{
// select distinct properties because they can be duplicated by inheritance
var sourceProperties = PropertyTypeCache.DefaultCache.Value
.RetrieveFilteredProperties(source.GetType(), true, x => x.CanRead)
.ToArray();
return sourceProperties
.Select(x => x.Name)
.Distinct()
.ToDictionary(
x => x.ToLowerInvariant(),
x => Tuple.Create(sourceProperties.First(y => y.Name == x).PropertyType,
sourceProperties.First(y => y.Name == x).GetValue(source)));
}
}
}