diff --git a/backend/src/Squidex.Infrastructure/Reflection/Internal/PropertyAccessor.cs b/backend/src/Squidex.Infrastructure/Reflection/Internal/PropertyAccessor.cs index 15e25b5c3..3cba0886d 100644 --- a/backend/src/Squidex.Infrastructure/Reflection/Internal/PropertyAccessor.cs +++ b/backend/src/Squidex.Infrastructure/Reflection/Internal/PropertyAccessor.cs @@ -57,9 +57,8 @@ namespace Squidex.Infrastructure.Reflection.Internal private readonly IPropertyAccessor internalAccessor; - public PropertyAccessor(Type targetType, PropertyInfo propertyInfo) + public PropertyAccessor(PropertyInfo propertyInfo) { - Guard.NotNull(targetType); Guard.NotNull(propertyInfo); var type = typeof(PropertyWrapper<,>).MakeGenericType(propertyInfo.DeclaringType!, propertyInfo.PropertyType); diff --git a/backend/src/Squidex.Infrastructure/Reflection/SimpleMapper.cs b/backend/src/Squidex.Infrastructure/Reflection/SimpleMapper.cs index 8412a1fb6..1c0f25082 100644 --- a/backend/src/Squidex.Infrastructure/Reflection/SimpleMapper.cs +++ b/backend/src/Squidex.Infrastructure/Reflection/SimpleMapper.cs @@ -5,6 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using System.ComponentModel; using System.Globalization; using Squidex.Infrastructure.Reflection.Internal; @@ -66,6 +67,41 @@ namespace Squidex.Infrastructure.Reflection } } + private sealed class TypeConverterPropertyMapper : PropertyMapper + { + private readonly TypeConverter converter; + + public TypeConverterPropertyMapper( + PropertyAccessor sourceAccessor, + PropertyAccessor targetAccessor, + TypeConverter converter) + : base(sourceAccessor, targetAccessor) + { + this.converter = converter; + } + + public override void MapProperty(object source, object target, CultureInfo culture) + { + var value = GetValue(source); + + if (value == null) + { + return; + } + + try + { + var converted = converter.ConvertFrom(null, culture, value); + + SetValue(target, converted); + } + catch + { + return; + } + } + } + private class PropertyMapper { private readonly PropertyAccessor sourceAccessor; @@ -126,21 +162,33 @@ namespace Squidex.Infrastructure.Reflection if (sourceType == targetType) { Mappers.Add(new PropertyMapper( - new PropertyAccessor(sourceClassType, sourceProperty), - new PropertyAccessor(targetClassType, targetProperty))); + new PropertyAccessor(sourceProperty), + new PropertyAccessor(targetProperty))); } else if (targetType == typeof(string)) { Mappers.Add(new StringConversionPropertyMapper( - new PropertyAccessor(sourceClassType, sourceProperty), - new PropertyAccessor(targetClassType, targetProperty))); + new PropertyAccessor(sourceProperty), + new PropertyAccessor(targetProperty))); } - else if (sourceType.Implements() || targetType.Implements()) + else { - Mappers.Add(new ConversionPropertyMapper( - new PropertyAccessor(sourceClassType, sourceProperty), - new PropertyAccessor(targetClassType, targetProperty), - targetType)); + var converter = TypeDescriptor.GetConverter(targetType); + + if (converter.CanConvertFrom(sourceType)) + { + Mappers.Add(new TypeConverterPropertyMapper( + new PropertyAccessor(sourceProperty), + new PropertyAccessor(targetProperty), + converter)); + } + else if (sourceType.Implements() || targetType.Implements()) + { + Mappers.Add(new ConversionPropertyMapper( + new PropertyAccessor(sourceProperty), + new PropertyAccessor(targetProperty), + targetType)); + } } } } diff --git a/backend/tests/Squidex.Infrastructure.Tests/Reflection/PropertiesTypeAccessorTests.cs b/backend/tests/Squidex.Infrastructure.Tests/Reflection/PropertiesTypeAccessorTests.cs index 065b54f14..06378fc2c 100644 --- a/backend/tests/Squidex.Infrastructure.Tests/Reflection/PropertiesTypeAccessorTests.cs +++ b/backend/tests/Squidex.Infrastructure.Tests/Reflection/PropertiesTypeAccessorTests.cs @@ -44,7 +44,7 @@ namespace Squidex.Infrastructure.Reflection [Fact] public void Should_set_read_write_property() { - var sut = new PropertyAccessor(typeof(TestClass), typeof(TestClass).GetProperty("ReadWrite")!); + var sut = new PropertyAccessor(typeof(TestClass).GetProperty("ReadWrite")!); sut.Set(target, 123); @@ -54,7 +54,7 @@ namespace Squidex.Infrastructure.Reflection [Fact] public void Should_set_write_property() { - var accessor = new PropertyAccessor(typeof(TestClass), typeof(TestClass).GetProperty("Write")!); + var accessor = new PropertyAccessor(typeof(TestClass).GetProperty("Write")!); accessor.Set(target, 123); @@ -64,7 +64,7 @@ namespace Squidex.Infrastructure.Reflection [Fact] public void Should_throw_exception_if_setting_readonly() { - var sut = new PropertyAccessor(typeof(TestClass), typeof(TestClass).GetProperty("Read")!); + var sut = new PropertyAccessor(typeof(TestClass).GetProperty("Read")!); Assert.Throws(() => sut.Set(target, 123)); } @@ -72,7 +72,7 @@ namespace Squidex.Infrastructure.Reflection [Fact] public void Should_get_read_write_property() { - var sut = new PropertyAccessor(typeof(TestClass), typeof(TestClass).GetProperty("ReadWrite")!); + var sut = new PropertyAccessor(typeof(TestClass).GetProperty("ReadWrite")!); target.Write = 123; @@ -82,7 +82,7 @@ namespace Squidex.Infrastructure.Reflection [Fact] public void Should_get_read_property() { - var sut = new PropertyAccessor(typeof(TestClass), typeof(TestClass).GetProperty("Read")!); + var sut = new PropertyAccessor(typeof(TestClass).GetProperty("Read")!); target.Write = 123; @@ -92,7 +92,7 @@ namespace Squidex.Infrastructure.Reflection [Fact] public void Should_throw_exception_if_getting_writeonly_property() { - var sut = new PropertyAccessor(typeof(TestClass), typeof(TestClass).GetProperty("Write")!); + var sut = new PropertyAccessor(typeof(TestClass).GetProperty("Write")!); Assert.Throws(() => sut.Get(target)); } diff --git a/backend/tests/Squidex.Infrastructure.Tests/Reflection/SimpleMapperTests.cs b/backend/tests/Squidex.Infrastructure.Tests/Reflection/SimpleMapperTests.cs index 46b6fcd4b..c7094d758 100644 --- a/backend/tests/Squidex.Infrastructure.Tests/Reflection/SimpleMapperTests.cs +++ b/backend/tests/Squidex.Infrastructure.Tests/Reflection/SimpleMapperTests.cs @@ -102,17 +102,31 @@ namespace Squidex.Infrastructure.Reflection } [Fact] - public void Should_map_nullables() + public void Should_map_from_nullable() { - var obj1 = new Class1 + var obj1 = new Class1 { - P1 = true, - P2 = true + P1 = 6, + P2 = 8 + }; + var obj2 = SimpleMapper.Map(obj1, new Class2()); + + Assert.Equal(8, obj2.P2); + Assert.Equal(0, obj2.P3); + } + + [Fact] + public void Should_map_to_nullable() + { + var obj1 = new Class1 + { + P1 = 6, + P2 = 8 }; - var obj2 = SimpleMapper.Map(obj1, new Class2()); + var obj2 = SimpleMapper.Map(obj1, new Class2()); - Assert.True(obj2.P2); - Assert.False(obj2.P3); + Assert.Equal(8, obj2.P2); + Assert.Null(obj2.P3); } [Fact] diff --git a/frontend/src/app/theme/_vars.scss b/frontend/src/app/theme/_vars.scss index cd0587363..df4b514f0 100644 --- a/frontend/src/app/theme/_vars.scss +++ b/frontend/src/app/theme/_vars.scss @@ -10,7 +10,7 @@ $color-border-darker: darken($color-border, 15%); $color-title: #000; $color-text: #373a3c; -$color-text-decent: #9aa1b6; +$color-text-decent: #6c707f; $color-tooltip: #1a2129; $color-code-background: #f5f7f9;