Hello,
I currently have a problem with the Auto-Mapping methods, defined in ServiceStack.Common.Utils.ReflectionUtils. Whenever I try to map a collection-property, which setter-method is private or protected, the mapping fails with the following exception:
System.ArgumentNullException : Der Wert darf nicht NULL sein.
Parametername: method
bei System.Linq.Expressions.Expression.Call(Expression instance, MethodInfo method, IEnumerable`1 arguments)
bei System.Linq.Expressions.Expression.Call(Expression instance, MethodInfo method, Expression[] arguments)
bei ServiceStack.Common.Support.PropertyInvoker.GetPropertySetterFn(PropertyInfo propertyInfo) in PropertyInvoker.cs: line 31.
bei ServiceStack.Common.Support.AssignmentMember.GetSetValueFn() in AssignmentDefinition.cs: line 75.
bei ServiceStack.Common.Support.AssignmentEntry..ctor(String name, AssignmentMember from, AssignmentMember to) in AssignmentDefinition.cs: line 26.
bei ServiceStack.Common.Support.AssignmentDefinition.AddMatch(String name, AssignmentMember readMember, AssignmentMember writeMember) in AssignmentDefinition.cs: line 101.
bei ServiceStack.Common.Utils.ReflectionUtils.<>c__DisplayClass2.<GetAssignmentDefinition>b__1(String param0) in ReflectionUtils.cs: line 117.
bei ServiceStack.Net30.Collections.Concurrent.ConcurrentDictionary`2.<>c__DisplayClassb.<GetOrAdd>b__a() in ConcurrentDictionary.cs: line 162.
bei ServiceStack.Net30.Collections.Concurrent.SplitOrderedList`2.ListInsert(Node newNode, Node startPoint, ref Node current, Func`1 dataCreator) in SplitOrderedList.cs: line 412.
bei ServiceStack.Net30.Collections.Concurrent.SplitOrderedList`2.InsertInternal(UInt32 key, TKey subKey, T data, Func`1 dataCreator, ref Node current) in SplitOrderedList.cs: line 166.
bei ServiceStack.Net30.Collections.Concurrent.SplitOrderedList`2.InsertOrGet(UInt32 key, TKey subKey, T data, Func`1 dataCreator) in SplitOrderedList.cs: line 152.
bei ServiceStack.Net30.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd(TKey key, Func`2 valueFactory) in ConcurrentDictionary.cs: line 162.
bei ServiceStack.Common.Utils.ReflectionUtils.GetAssignmentDefinition(Type toType, Type fromType) in ReflectionUtils.cs: line 102.
bei ServiceStack.Common.Utils.ReflectionUtils.PopulateObject(To to, From from) in ReflectionUtils.cs: line 200.
bei ServiceStack.Common.ReflectionExtensions.PopulateWith(To to, From from) in ReflectionExtensions.cs: line 13.
bei ServiceStack.Common.ReflectionExtensions.TranslateTo(Object from) in ReflectionExtensions.cs: line 35.
bei ServiceStack.Common.Tests.ReflectionExtensionsTests.Can_translate_generic_list_with_protected_setter() in ReflectionExtensionsTests.cs: line 76.
The root cause of this problem is in ServiceStack.Common.Support.PropertyInvoker::GetPropertySetterFn. Here the call to propertyInfo.GetSetMethod() returns null for private or protected properties. On the other hand in ServiceStack.Common.Utils.ReflectionUtils::GetMembers, where the write-properties are collected, a call to propertyInfo.CanWrite determines wether the property is writeable or not. But as far as I understand the CanWrite-Property is true, if a setter exists, regardless of the access modifier. So in the end this means, that the current implementation adds properites to the AssignmentDefinition, on which it later fails to compile a SetterFn.
A test-case to reproduce the problem looks like this:
public class TestClassA
{
public IList<string> ToStringList { get; set; }
public ArrayOfString FromStringList { get; set; }
public IList<UserFileType> FromUserFileTypes { get; set; }
}
public class TestClassC
{
public IList<string> FromStringList { get; protected set; }
}
[Test]
public void Can_translate_generic_list_with_protected_setter()
{
var values = new[] { "A", "B", "C" };
var testA = new TestClassA
{
ToStringList = new List<string>(values),
};
var fromTestA = testA.TranslateTo<TestClassC>();
Assert.NotNull(fromTestA);
}
And now comes the question to the group. There are two possible fixes to the problem:
- Ignore protected properties: This would mean to not only check CanWrite on the propertyInfo but also if GetPropertySetterFn() != null
- Also include protected/private setter by adding GetSetMethod(nonPublic: true)
Currently I don't know which one fits better the Auto-Mapping mindset.
Let me know what you think, so that I can prepare a pull request.
Thank you
---
Joachim