Skip to content

Commit c0b0121

Browse files
authored
Fix QuickGrid nullable property sorting by handling UnaryExpression in ToPropertyName (#62602)
Fix sorting on nullable properties
1 parent ea282bf commit c0b0121

File tree

6 files changed

+216
-1
lines changed

6 files changed

+216
-1
lines changed

AspNetCore.slnx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1227,6 +1227,7 @@
12271227
<Project Path="src/Caching/perf/MicroBenchmarks/Microsoft.Extensions.Caching.MicroBenchmarks/Microsoft.Extensions.Caching.MicroBenchmarks.csproj" />
12281228
<Project Path="src/Components/QuickGrid/Microsoft.AspNetCore.Components.QuickGrid.EntityFrameworkAdapter/src/Microsoft.AspNetCore.Components.QuickGrid.EntityFrameworkAdapter.csproj" />
12291229
<Project Path="src/Components/QuickGrid/Microsoft.AspNetCore.Components.QuickGrid/src/Microsoft.AspNetCore.Components.QuickGrid.csproj" />
1230+
<Project Path="src/Components/QuickGrid/Microsoft.AspNetCore.Components.QuickGrid/test/Microsoft.AspNetCore.Components.QuickGrid.Tests.csproj" />
12301231
<Project Path="src/Components/WebView/test/E2ETest/Microsoft.AspNetCore.Components.WebViewE2E.Test.csproj" />
12311232
<Project Path="src/DataProtection/samples/KeyManagementSimulator/KeyManagementSimulator.csproj" />
12321233
<Project Path="src/Framework/AspNetCoreAnalyzers/samples/WebAppSample/WebAppSample.csproj" />

src/Components/Components.slnf

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"src\\Components\\Forms\\test\\Microsoft.AspNetCore.Components.Forms.Tests.csproj",
2020
"src\\Components\\QuickGrid\\Microsoft.AspNetCore.Components.QuickGrid.EntityFrameworkAdapter\\src\\Microsoft.AspNetCore.Components.QuickGrid.EntityFrameworkAdapter.csproj",
2121
"src\\Components\\QuickGrid\\Microsoft.AspNetCore.Components.QuickGrid\\src\\Microsoft.AspNetCore.Components.QuickGrid.csproj",
22+
"src\\Components\\QuickGrid\\Microsoft.AspNetCore.Components.QuickGrid\\test\\Microsoft.AspNetCore.Components.QuickGrid.Tests.csproj",
2223
"src\\Components\\Samples\\BlazorServerApp\\BlazorServerApp.csproj",
2324
"src\\Components\\Samples\\BlazorUnitedApp.Client\\BlazorUnitedApp.Client.csproj",
2425
"src\\Components\\Samples\\BlazorUnitedApp\\BlazorUnitedApp.csproj",

src/Components/QuickGrid/Microsoft.AspNetCore.Components.QuickGrid/src/Columns/GridSort.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,16 @@ private List<SortedProperty> BuildPropertyList(bool ascending)
135135
// Not sure we really want this level of complexity, but it converts expressions like @(c => c.Medals.Gold) to "Medals.Gold"
136136
private static string ToPropertyName(LambdaExpression expression)
137137
{
138-
if (expression.Body is not MemberExpression body)
138+
var expressionBody = expression.Body;
139+
140+
// Handle UnaryExpressions that can occur due to implicit conversions, such as nullable value types
141+
if (expressionBody.NodeType == ExpressionType.Convert ||
142+
expressionBody.NodeType == ExpressionType.ConvertChecked)
143+
{
144+
expressionBody = ((UnaryExpression)expressionBody).Operand;
145+
}
146+
147+
if (expressionBody is not MemberExpression body)
139148
{
140149
throw new ArgumentException(ExpressionNotRepresentableMessage);
141150
}

src/Components/QuickGrid/Microsoft.AspNetCore.Components.QuickGrid/src/Microsoft.AspNetCore.Components.QuickGrid.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,8 @@
2323
<Reference Include="Microsoft.AspNetCore.Components.Web" />
2424
</ItemGroup>
2525

26+
<ItemGroup>
27+
<InternalsVisibleTo Include="Microsoft.AspNetCore.Components.QuickGrid.Tests" />
28+
</ItemGroup>
29+
2630
</Project>
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Globalization;
5+
using System.Linq.Expressions;
6+
7+
namespace Microsoft.AspNetCore.Components.QuickGrid.Tests;
8+
9+
public class GridSortTest
10+
{
11+
// Test model classes
12+
private class TestEntity
13+
{
14+
public string Name { get; set; } = string.Empty;
15+
public int Age { get; set; }
16+
public DateTime? NullableDate { get; set; }
17+
public int? NullableInt { get; set; }
18+
public TestChild Child { get; set; } = new();
19+
}
20+
21+
private class TestChild
22+
{
23+
public string ChildName { get; set; } = string.Empty;
24+
public DateTime? ChildNullableDate { get; set; }
25+
}
26+
27+
[Fact]
28+
public void ToPropertyName_SimpleProperty_ReturnsPropertyName()
29+
{
30+
// Arrange
31+
Expression<Func<TestEntity, string>> expression = x => x.Name;
32+
33+
// Act
34+
var gridSort = GridSort<TestEntity>.ByAscending(expression);
35+
var propertyList = gridSort.ToPropertyList(ascending: true);
36+
37+
// Assert
38+
Assert.Single(propertyList);
39+
Assert.Equal("Name", propertyList.First().PropertyName);
40+
Assert.Equal(SortDirection.Ascending, propertyList.First().Direction);
41+
}
42+
43+
[Fact]
44+
public void ToPropertyName_NullableProperty_ReturnsPropertyName()
45+
{
46+
// Arrange
47+
Expression<Func<TestEntity, DateTime?>> expression = x => x.NullableDate;
48+
49+
// Act
50+
var gridSort = GridSort<TestEntity>.ByAscending(expression);
51+
var propertyList = gridSort.ToPropertyList(ascending: true);
52+
53+
// Assert
54+
Assert.Single(propertyList);
55+
Assert.Equal("NullableDate", propertyList.First().PropertyName);
56+
Assert.Equal(SortDirection.Ascending, propertyList.First().Direction);
57+
}
58+
59+
[Fact]
60+
public void ToPropertyName_NullableInt_ReturnsPropertyName()
61+
{
62+
// Arrange
63+
Expression<Func<TestEntity, int?>> expression = x => x.NullableInt;
64+
65+
// Act
66+
var gridSort = GridSort<TestEntity>.ByAscending(expression);
67+
var propertyList = gridSort.ToPropertyList(ascending: true);
68+
69+
// Assert
70+
Assert.Single(propertyList);
71+
Assert.Equal("NullableInt", propertyList.First().PropertyName);
72+
Assert.Equal(SortDirection.Ascending, propertyList.First().Direction);
73+
}
74+
75+
[Fact]
76+
public void ToPropertyName_NestedProperty_ReturnsNestedPropertyName()
77+
{
78+
// Arrange
79+
Expression<Func<TestEntity, string>> expression = x => x.Child.ChildName;
80+
81+
// Act
82+
var gridSort = GridSort<TestEntity>.ByAscending(expression);
83+
var propertyList = gridSort.ToPropertyList(ascending: true);
84+
85+
// Assert
86+
Assert.Single(propertyList);
87+
Assert.Equal("Child.ChildName", propertyList.First().PropertyName);
88+
Assert.Equal(SortDirection.Ascending, propertyList.First().Direction);
89+
}
90+
91+
[Fact]
92+
public void ToPropertyName_NestedNullableProperty_ReturnsNestedPropertyName()
93+
{
94+
// Arrange
95+
Expression<Func<TestEntity, DateTime?>> expression = x => x.Child.ChildNullableDate;
96+
97+
// Act
98+
var gridSort = GridSort<TestEntity>.ByAscending(expression);
99+
var propertyList = gridSort.ToPropertyList(ascending: true);
100+
101+
// Assert
102+
Assert.Single(propertyList);
103+
Assert.Equal("Child.ChildNullableDate", propertyList.First().PropertyName);
104+
Assert.Equal(SortDirection.Ascending, propertyList.First().Direction);
105+
}
106+
107+
[Fact]
108+
public void ToPropertyName_DescendingSort_ReturnsCorrectDirection()
109+
{
110+
// Arrange
111+
Expression<Func<TestEntity, DateTime?>> expression = x => x.NullableDate;
112+
113+
// Act
114+
var gridSort = GridSort<TestEntity>.ByDescending(expression);
115+
var propertyList = gridSort.ToPropertyList(ascending: true);
116+
117+
// Assert
118+
Assert.Single(propertyList);
119+
Assert.Equal("NullableDate", propertyList.First().PropertyName);
120+
Assert.Equal(SortDirection.Descending, propertyList.First().Direction);
121+
}
122+
123+
[Fact]
124+
public void ToPropertyName_MultipleSort_ReturnsAllProperties()
125+
{
126+
// Arrange
127+
Expression<Func<TestEntity, string>> firstExpression = x => x.Name;
128+
Expression<Func<TestEntity, DateTime?>> secondExpression = x => x.NullableDate;
129+
130+
// Act
131+
var gridSort = GridSort<TestEntity>.ByAscending(firstExpression)
132+
.ThenDescending(secondExpression);
133+
var propertyList = gridSort.ToPropertyList(ascending: true);
134+
135+
// Assert
136+
Assert.Equal(2, propertyList.Count);
137+
138+
var firstProperty = propertyList.First();
139+
Assert.Equal("Name", firstProperty.PropertyName);
140+
Assert.Equal(SortDirection.Ascending, firstProperty.Direction);
141+
142+
var secondProperty = propertyList.Last();
143+
Assert.Equal("NullableDate", secondProperty.PropertyName);
144+
Assert.Equal(SortDirection.Descending, secondProperty.Direction);
145+
}
146+
147+
[Fact]
148+
public void ToPropertyName_InvalidExpression_ThrowsArgumentException()
149+
{
150+
// Arrange
151+
Expression<Func<TestEntity, string>> invalidExpression = x => x.Name.ToUpper(CultureInfo.InvariantCulture);
152+
153+
// Act & Assert
154+
var gridSort = GridSort<TestEntity>.ByAscending(invalidExpression);
155+
var exception = Assert.Throws<ArgumentException>(() => gridSort.ToPropertyList(ascending: true));
156+
Assert.Contains("The supplied expression can't be represented as a property name for sorting", exception.Message);
157+
}
158+
159+
[Fact]
160+
public void ToPropertyName_MethodCallExpression_ThrowsArgumentException()
161+
{
162+
// Arrange
163+
Expression<Func<TestEntity, string>> invalidExpression = x => x.Name.Substring(0, 1);
164+
165+
// Act & Assert
166+
var gridSort = GridSort<TestEntity>.ByAscending(invalidExpression);
167+
var exception = Assert.Throws<ArgumentException>(() => gridSort.ToPropertyList(ascending: true));
168+
Assert.Contains("The supplied expression can't be represented as a property name for sorting", exception.Message);
169+
}
170+
171+
[Fact]
172+
public void ToPropertyName_ConstantExpression_ThrowsArgumentException()
173+
{
174+
// Arrange
175+
Expression<Func<TestEntity, string>> invalidExpression = x => "constant";
176+
177+
// Act & Assert
178+
var gridSort = GridSort<TestEntity>.ByAscending(invalidExpression);
179+
var exception = Assert.Throws<ArgumentException>(() => gridSort.ToPropertyList(ascending: true));
180+
Assert.Contains("The supplied expression can't be represented as a property name for sorting", exception.Message);
181+
}
182+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
5+
<RootNamespace>Microsoft.AspNetCore.Components.QuickGrid.Tests</RootNamespace>
6+
</PropertyGroup>
7+
8+
<ItemGroup>
9+
<Reference Include="Microsoft.AspNetCore.Components.QuickGrid" />
10+
</ItemGroup>
11+
12+
13+
14+
<ItemGroup>
15+
<Using Include="Xunit" />
16+
</ItemGroup>
17+
18+
</Project>

0 commit comments

Comments
 (0)