Skip to content

Commit 32d4cb5

Browse files
committed
Check non-enumerable properties for null when navigating nested enumerable property path
1 parent acdad01 commit 32d4cb5

3 files changed

Lines changed: 40 additions & 10 deletions

File tree

src/Dibix.Http.Server/Runtime/HttpParameterResolver.cs

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -547,8 +547,6 @@ private static Expression CollectSourcePropertyValue(IHttpActionDescriptor actio
547547
value = sourcePropertyExpression;
548548
continue;
549549
}
550-
551-
nullCheckTarget = sourcePropertyExpression;
552550
}
553551

554552
resultEnumerableType ??= GetEnumerableType(sourcePropertyExpression.Type);
@@ -562,15 +560,15 @@ private static Expression CollectSourcePropertyValue(IHttpActionDescriptor actio
562560
CollectSourcePropertyValue(propertyName, previousPropertyName, isNestedEnumerablePair, nestedEnumerableAnchor, ref value, ref nullCheckTarget);
563561
}
564562

565-
if (ensureNullPropagation && nullCheckTarget != null && i + 1 < parts.Length)
563+
if (ensureNullPropagation && nullCheckTarget != null && !reachedEnd)
566564
nullCheckTargets.Add(nullCheckTarget);
567565
}
568566

569567
if (nullCheckTargets.Any())
570568
{
571569
Expression test = nullCheckTargets.Select(x => Expression.NotEqual(x, Expression.Constant(null))).Aggregate(Expression.AndAlso/* Short-circuit behavior like && in C# */);
572570
bool hasDefaultValue = parameter.DefaultValue != DBNull.Value && parameter.DefaultValue != null;
573-
Expression fallbackValue = hasDefaultValue ? (Expression)Expression.Constant(parameter.DefaultValue) : Expression.Default(parameter.ParameterType);
571+
Expression fallbackValue = hasDefaultValue ? Expression.Constant(parameter.DefaultValue) : Expression.Default(parameter.ParameterType);
574572
value = EnsureCorrectType(parameter.InternalParameterName, value, parameter.ParameterType, actionParameter);
575573
value = Expression.Condition(test, value, fallbackValue);
576574
}
@@ -732,6 +730,11 @@ private static Expression EnsureCorrectType(string parameterName, Expression val
732730
if (targetType == typeof(object))
733731
return valueExpression;
734732

733+
// Prefer Expression.Cast/Boxing over custom runtime type conversion
734+
// This scenario wraps int into Nullable<int>
735+
if (valueExpression.Type == Nullable.GetUnderlyingType(targetType))
736+
return Expression.Convert(valueExpression, targetType);
737+
735738
if (TryStructuredTypeConversion(parameterName, valueExpression, targetType, out Expression newValueExpression))
736739
return newValueExpression;
737740

@@ -803,6 +806,7 @@ private static Expression BuildFlattenNestedEnumerableExpression(Expression valu
803806
Type parentGenericType = propertyEnumerableType;
804807
ParameterExpression selectorParameter = Expression.Parameter(propertyEnumerableType, "x");
805808
Expression selectorProperty = selectorParameter;
809+
ICollection<Expression> nullCheckTargets = new List<Expression>();
806810

807811
for (int i = startIndex; i < tokens.Length; i++)
808812
{
@@ -819,13 +823,25 @@ private static Expression BuildFlattenNestedEnumerableExpression(Expression valu
819823
}
820824

821825
if (enumerableType == null)
826+
{
827+
nullCheckTargets.Add(selectorProperty);
822828
continue;
829+
}
823830

824831
Type childGenericType = enumerableType;
825832
MethodInfo flattenMethod = typeof(HttpParameterResolver).SafeGetMethod(nameof(FlattenNestedEnumerable), BindingFlags.NonPublic | BindingFlags.Static)
826833
.MakeGenericMethod(parentGenericType, childGenericType);
827834

828-
Expression collectionSelector = Expression.Lambda(selectorProperty, selectorParameter);
835+
Expression collectionSelectorProperty = selectorProperty;
836+
if (nullCheckTargets.Any())
837+
{
838+
Expression test = nullCheckTargets.Select(x => Expression.NotEqual(x, Expression.Constant(null))).Aggregate(Expression.AndAlso/* Short-circuit behavior like && in C# */);
839+
Expression fallbackValue = Expression.Convert(Expression.Call(typeof(Array), nameof(Array.Empty), [enumerableType]), collectionSelectorProperty.Type);
840+
collectionSelectorProperty = Expression.Condition(test, collectionSelectorProperty, fallbackValue);
841+
nullCheckTargets.Clear();
842+
}
843+
844+
Expression collectionSelector = Expression.Lambda(collectionSelectorProperty, selectorParameter);
829845
result = Expression.Call(flattenMethod, result, collectionSelector);
830846
parentGenericType = typeof(NestedEnumerablePair<,>).MakeGenericType(parentGenericType, childGenericType);
831847
selectorParameter = Expression.Parameter(parentGenericType, "x");

tests/Dibix.Http.Server.Tests/HttpParameterResolverTest.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,8 @@ public void Compile_ExplicitBodySource()
225225
66
226226
}
227227
}
228-
}
228+
},
229+
new ExplicitHttpBodyItem(7, "Z")
229230
}
230231
};
231232
IHttpRequestDescriptor request = new HttpRequestMessageDescriptor(new HttpRequestMessage());
@@ -248,7 +249,8 @@ public void Compile_ExplicitBodySource()
248249
Assert.AreEqual(@"id_ INT(4) idx INT(4) age_ INT(4) name_ NVARCHAR(MAX)
249250
---------- ---------- ----------- -------------------
250251
710 1 5 X
251-
710 2 5 Y ", itemsa_.Dump());
252+
710 2 5 Y
253+
710 3 5 Z ", itemsa_.Dump());
252254
StructuredType childrena_ = AssertIsType<ExplicitHttpBodyItemChildSet>(arguments["childrena_"]);
253255
Assert.AreEqual(@"itemid INT(4) childid INT(4)
254256
------------- --------------

tests/Dibix.Http.Server.Tests/Resources/Compile_ExplicitBodySource.txt

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,11 @@
8484

8585
.Lambda #Lambda3<System.Func`2[Dibix.Http.Server.Tests.HttpParameterResolverTest+ExplicitHttpBodyItem,System.Collections.Generic.ICollection`1[Dibix.Http.Server.Tests.HttpParameterResolverTest+ExplicitHttpBodyItemChild]]>(Dibix.Http.Server.Tests.HttpParameterResolverTest+ExplicitHttpBodyItem $x)
8686
{
87-
($x.Child).Children
87+
.If ($x.Child != null) {
88+
($x.Child).Children
89+
} .Else {
90+
(System.Collections.Generic.ICollection`1[Dibix.Http.Server.Tests.HttpParameterResolverTest+ExplicitHttpBodyItemChild]).Call System.Array.Empty()
91+
}
8892
}
8993

9094
.Lambda #Lambda4<System.Action`2[Dibix.Http.Server.Tests.HttpParameterResolverTest+ExplicitHttpBodyItemChildSet,Dibix.NestedEnumerablePair`2[Dibix.Http.Server.Tests.HttpParameterResolverTest+ExplicitHttpBodyItem,Dibix.Http.Server.Tests.HttpParameterResolverTest+ExplicitHttpBodyItemChild]]>(
@@ -98,7 +102,11 @@
98102

99103
.Lambda #Lambda5<System.Func`2[Dibix.Http.Server.Tests.HttpParameterResolverTest+ExplicitHttpBodyItem,System.Collections.Generic.ICollection`1[System.Int32]]>(Dibix.Http.Server.Tests.HttpParameterResolverTest+ExplicitHttpBodyItem $x)
100104
{
101-
($x.Child).PrimitiveChildren
105+
.If ($x.Child != null) {
106+
($x.Child).PrimitiveChildren
107+
} .Else {
108+
(System.Collections.Generic.ICollection`1[System.Int32]).Call System.Array.Empty()
109+
}
102110
}
103111

104112
.Lambda #Lambda6<System.Action`2[Dibix.Http.Server.Tests.HttpParameterResolverTest+ExplicitHttpBodyItemChildSet,Dibix.NestedEnumerablePair`2[Dibix.Http.Server.Tests.HttpParameterResolverTest+ExplicitHttpBodyItem,System.Int32]]>(
@@ -112,7 +120,11 @@
112120

113121
.Lambda #Lambda7<System.Func`2[Dibix.Http.Server.Tests.HttpParameterResolverTest+ExplicitHttpBodyItem,System.Collections.Generic.ICollection`1[Dibix.Http.Server.Tests.HttpParameterResolverTest+ExplicitHttpBodyItemChild]]>(Dibix.Http.Server.Tests.HttpParameterResolverTest+ExplicitHttpBodyItem $x)
114122
{
115-
($x.Child).Children
123+
.If ($x.Child != null) {
124+
($x.Child).Children
125+
} .Else {
126+
(System.Collections.Generic.ICollection`1[Dibix.Http.Server.Tests.HttpParameterResolverTest+ExplicitHttpBodyItemChild]).Call System.Array.Empty()
127+
}
116128
}
117129

118130
.Lambda #Lambda8<System.Func`2[Dibix.NestedEnumerablePair`2[Dibix.Http.Server.Tests.HttpParameterResolverTest+ExplicitHttpBodyItem,Dibix.Http.Server.Tests.HttpParameterResolverTest+ExplicitHttpBodyItemChild],System.Collections.Generic.ICollection`1[Dibix.Http.Server.Tests.HttpParameterResolverTest+ExplicitHttpBodyItemNestedChild]]>(Dibix.NestedEnumerablePair`2[Dibix.Http.Server.Tests.HttpParameterResolverTest+ExplicitHttpBodyItem,Dibix.Http.Server.Tests.HttpParameterResolverTest+ExplicitHttpBodyItemChild] $x)

0 commit comments

Comments
 (0)