Browse Source

Merge pull request #18970 from namtab00/dev

Add ObjectHelper support for property resolution for nullable value types
pull/18991/head
maliming 2 years ago
committed by GitHub
parent
commit
fd2e9548ca
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 54
      framework/src/Volo.Abp.Core/Volo/Abp/ObjectHelper.cs
  2. 96
      framework/test/Volo.Abp.Core.Tests/Volo/Abp/ObjectHelper_Tests.cs

54
framework/src/Volo.Abp.Core/Volo/Abp/ObjectHelper.cs

@ -9,8 +9,7 @@ namespace Volo.Abp;
public static class ObjectHelper
{
private static readonly ConcurrentDictionary<string, PropertyInfo?> CachedObjectProperties =
new ConcurrentDictionary<string, PropertyInfo?>();
private static readonly ConcurrentDictionary<string, PropertyInfo?> _cachedObjectProperties = new();
public static void TrySetProperty<TObject, TValue>(
TObject obj,
@ -27,37 +26,58 @@ public static class ObjectHelper
Func<TObject, TValue> valueFactory,
params Type[]? ignoreAttributeTypes)
{
var cacheKey = $"{obj?.GetType().FullName}-" +
$"{propertySelector}-" +
$"{(ignoreAttributeTypes != null ? "-" + string.Join("-", ignoreAttributeTypes.Select(x => x.FullName)) : "")}";
var cacheKey =
$"{obj?.GetType().FullName}-{propertySelector}-{(ignoreAttributeTypes != null ? "-" + string.Join("-", ignoreAttributeTypes.Select(x => x.FullName)) : "")}";
var property = CachedObjectProperties.GetOrAdd(cacheKey, () =>
var property = _cachedObjectProperties.GetOrAdd(cacheKey, PropertyFactory);
property?.SetValue(obj, valueFactory(obj));
return;
PropertyInfo? PropertyFactory(string _)
{
if (propertySelector.Body.NodeType != ExpressionType.MemberAccess)
MemberExpression? memberExpression;
switch (propertySelector.Body.NodeType)
{
return null;
case ExpressionType.Convert: {
memberExpression = propertySelector.Body.As<UnaryExpression>().Operand as MemberExpression;
break;
}
case ExpressionType.MemberAccess: {
memberExpression = propertySelector.Body.As<MemberExpression>();
break;
}
default: {
return null;
}
}
var memberExpression = propertySelector.Body.As<MemberExpression>();
if (memberExpression == null)
{
return null;
}
var propertyInfo = obj?.GetType().GetProperties().FirstOrDefault(x =>
x.Name == memberExpression.Member.Name &&
x.GetSetMethod(true) != null);
var propertyInfo = obj?.GetType()
.GetProperties()
.FirstOrDefault(x => x.Name == memberExpression.Member.Name);
if (propertyInfo == null)
{
return null;
}
if (ignoreAttributeTypes != null &&
ignoreAttributeTypes.Any(ignoreAttribute => propertyInfo.IsDefined(ignoreAttribute, true)))
var propPrivateSetMethod = propertyInfo.GetSetMethod(true);
if (propPrivateSetMethod == null)
{
return null;
}
return propertyInfo;
});
if (ignoreAttributeTypes != null && ignoreAttributeTypes.Any(ignoreAttribute => propertyInfo.IsDefined(ignoreAttribute, true)))
{
return null;
}
property?.SetValue(obj, valueFactory(obj));
return propertyInfo;
}
}
}

96
framework/test/Volo.Abp.Core.Tests/Volo/Abp/ObjectHelper_Tests.cs

@ -39,6 +39,91 @@ public class ObjectHelper_Tests
testClass.ChildClass.Name.ShouldBe("NewChildName");
}
[Fact]
public void TrySetProperty_WithNullableNewValueType_SetsCorrectly()
{
// Arrange
var sut = new AbstractParentImpl();
long? newValue = 10;
// Act & Assert
var sutAsIFirst = (IFirst)sut;
ObjectHelper.TrySetProperty(sutAsIFirst, x => x.ValueProp1FromIFirst, () => newValue);
sutAsIFirst.ValueProp1FromIFirst.ShouldBe(newValue.Value);
ObjectHelper.TrySetProperty(sutAsIFirst, x => x.ValueProp2FromIFirst, () => newValue);
sutAsIFirst.ValueProp2FromIFirst.ShouldBe(newValue.Value);
ObjectHelper.TrySetProperty(sutAsIFirst, x => x.ValueProp3FromIFirst, () => newValue);
sutAsIFirst.ValueProp3FromIFirst.ShouldNotBe(newValue.Value); // private set on implementation not accessible
ObjectHelper.TrySetProperty(sutAsIFirst, x => x.ValueProp4FromIFirst, () => newValue);
sutAsIFirst.ValueProp4FromIFirst.ShouldNotBe(newValue.Value); // readonly
ObjectHelper.TrySetProperty(sutAsIFirst, x => x.ValueProp5FromIFirst, () => newValue,
ignoreAttributeTypes: typeof(IgnoreDataMemberAttribute));
sutAsIFirst.ValueProp5FromIFirst.ShouldNotBe(newValue.Value); // ignore by attribute
var sutAsISecond = (ISecond)sut;
ObjectHelper.TrySetProperty(sutAsISecond, x => x.ValueProp1FromISecond, () => newValue);
sutAsISecond.ValueProp1FromISecond.ShouldNotBe(newValue.Value); // readonly
}
internal interface IFirst
{
public long ValueProp1FromIFirst { get; }
public long ValueProp2FromIFirst { get; }
public long ValueProp3FromIFirst { get; }
public long ValueProp4FromIFirst { get; }
public long ValueProp5FromIFirst { get; }
}
internal interface ISecond
{
public long ValueProp1FromISecond { get; }
}
internal interface IHasKey<out TKey>
{
TKey Id { get; }
}
internal interface IHaveMixedProps : IFirst, ISecond
{
}
abstract internal class GenericBase<TKey> : IHasKey<TKey>
{
public virtual TKey Id { get; protected set; }
}
abstract internal class AbstractParent<TKey> : GenericBase<TKey>, IHaveMixedProps
{
public long ValueProp1FromIFirst { get; set; }
public long ValueProp2FromIFirst { get; protected set; }
public long ValueProp3FromIFirst { get; private set; }
public long ValueProp4FromIFirst { get; }
[IgnoreDataMember] public long ValueProp5FromIFirst { get; }
public long ValueProp1FromISecond { get; }
}
internal class AbstractParentImpl : AbstractParent<long>
{
public long OwnProp1 { get; set; }
public string OwnProp2 { get; set; }
}
class MyClass
{
public string Name { get; set; }
@ -52,6 +137,17 @@ public class ObjectHelper_Tests
[IgnoreDataMember]
public string Name5 { get; }
public long Number { get; set; }
public long Number2 { get; protected set; }
public long Number3 { get; private set; }
public long Number4 { get; }
[IgnoreDataMember]
public long Number5 { get; }
public MyChildClass ChildClass { get; set; }
public MyClass()

Loading…
Cancel
Save