diff --git a/AspNetCore.slnx b/AspNetCore.slnx
index 2b5b7180956c..979c5d2165b8 100644
--- a/AspNetCore.slnx
+++ b/AspNetCore.slnx
@@ -1227,6 +1227,7 @@
+
diff --git a/src/Components/Components.slnf b/src/Components/Components.slnf
index 0ed65e49b0e2..aa83a0f8a9ef 100644
--- a/src/Components/Components.slnf
+++ b/src/Components/Components.slnf
@@ -19,6 +19,7 @@
"src\\Components\\Forms\\test\\Microsoft.AspNetCore.Components.Forms.Tests.csproj",
"src\\Components\\QuickGrid\\Microsoft.AspNetCore.Components.QuickGrid.EntityFrameworkAdapter\\src\\Microsoft.AspNetCore.Components.QuickGrid.EntityFrameworkAdapter.csproj",
"src\\Components\\QuickGrid\\Microsoft.AspNetCore.Components.QuickGrid\\src\\Microsoft.AspNetCore.Components.QuickGrid.csproj",
+ "src\\Components\\QuickGrid\\Microsoft.AspNetCore.Components.QuickGrid\\test\\Microsoft.AspNetCore.Components.QuickGrid.Tests.csproj",
"src\\Components\\Samples\\BlazorServerApp\\BlazorServerApp.csproj",
"src\\Components\\Samples\\BlazorUnitedApp.Client\\BlazorUnitedApp.Client.csproj",
"src\\Components\\Samples\\BlazorUnitedApp\\BlazorUnitedApp.csproj",
diff --git a/src/Components/QuickGrid/Microsoft.AspNetCore.Components.QuickGrid/src/Columns/GridSort.cs b/src/Components/QuickGrid/Microsoft.AspNetCore.Components.QuickGrid/src/Columns/GridSort.cs
index 0e891b2e206f..281616065ecf 100644
--- a/src/Components/QuickGrid/Microsoft.AspNetCore.Components.QuickGrid/src/Columns/GridSort.cs
+++ b/src/Components/QuickGrid/Microsoft.AspNetCore.Components.QuickGrid/src/Columns/GridSort.cs
@@ -135,7 +135,16 @@ private List BuildPropertyList(bool ascending)
// Not sure we really want this level of complexity, but it converts expressions like @(c => c.Medals.Gold) to "Medals.Gold"
private static string ToPropertyName(LambdaExpression expression)
{
- if (expression.Body is not MemberExpression body)
+ var expressionBody = expression.Body;
+
+ // Handle UnaryExpressions that can occur due to implicit conversions, such as nullable value types
+ if (expressionBody.NodeType == ExpressionType.Convert ||
+ expressionBody.NodeType == ExpressionType.ConvertChecked)
+ {
+ expressionBody = ((UnaryExpression)expressionBody).Operand;
+ }
+
+ if (expressionBody is not MemberExpression body)
{
throw new ArgumentException(ExpressionNotRepresentableMessage);
}
diff --git a/src/Components/QuickGrid/Microsoft.AspNetCore.Components.QuickGrid/src/Microsoft.AspNetCore.Components.QuickGrid.csproj b/src/Components/QuickGrid/Microsoft.AspNetCore.Components.QuickGrid/src/Microsoft.AspNetCore.Components.QuickGrid.csproj
index 6d43b9828a95..8603bc6d164e 100644
--- a/src/Components/QuickGrid/Microsoft.AspNetCore.Components.QuickGrid/src/Microsoft.AspNetCore.Components.QuickGrid.csproj
+++ b/src/Components/QuickGrid/Microsoft.AspNetCore.Components.QuickGrid/src/Microsoft.AspNetCore.Components.QuickGrid.csproj
@@ -23,4 +23,8 @@
+
+
+
+
diff --git a/src/Components/QuickGrid/Microsoft.AspNetCore.Components.QuickGrid/test/GridSortTest.cs b/src/Components/QuickGrid/Microsoft.AspNetCore.Components.QuickGrid/test/GridSortTest.cs
new file mode 100644
index 000000000000..ec7a91d7fcdf
--- /dev/null
+++ b/src/Components/QuickGrid/Microsoft.AspNetCore.Components.QuickGrid/test/GridSortTest.cs
@@ -0,0 +1,182 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Globalization;
+using System.Linq.Expressions;
+
+namespace Microsoft.AspNetCore.Components.QuickGrid.Tests;
+
+public class GridSortTest
+{
+ // Test model classes
+ private class TestEntity
+ {
+ public string Name { get; set; } = string.Empty;
+ public int Age { get; set; }
+ public DateTime? NullableDate { get; set; }
+ public int? NullableInt { get; set; }
+ public TestChild Child { get; set; } = new();
+ }
+
+ private class TestChild
+ {
+ public string ChildName { get; set; } = string.Empty;
+ public DateTime? ChildNullableDate { get; set; }
+ }
+
+ [Fact]
+ public void ToPropertyName_SimpleProperty_ReturnsPropertyName()
+ {
+ // Arrange
+ Expression> expression = x => x.Name;
+
+ // Act
+ var gridSort = GridSort.ByAscending(expression);
+ var propertyList = gridSort.ToPropertyList(ascending: true);
+
+ // Assert
+ Assert.Single(propertyList);
+ Assert.Equal("Name", propertyList.First().PropertyName);
+ Assert.Equal(SortDirection.Ascending, propertyList.First().Direction);
+ }
+
+ [Fact]
+ public void ToPropertyName_NullableProperty_ReturnsPropertyName()
+ {
+ // Arrange
+ Expression> expression = x => x.NullableDate;
+
+ // Act
+ var gridSort = GridSort.ByAscending(expression);
+ var propertyList = gridSort.ToPropertyList(ascending: true);
+
+ // Assert
+ Assert.Single(propertyList);
+ Assert.Equal("NullableDate", propertyList.First().PropertyName);
+ Assert.Equal(SortDirection.Ascending, propertyList.First().Direction);
+ }
+
+ [Fact]
+ public void ToPropertyName_NullableInt_ReturnsPropertyName()
+ {
+ // Arrange
+ Expression> expression = x => x.NullableInt;
+
+ // Act
+ var gridSort = GridSort.ByAscending(expression);
+ var propertyList = gridSort.ToPropertyList(ascending: true);
+
+ // Assert
+ Assert.Single(propertyList);
+ Assert.Equal("NullableInt", propertyList.First().PropertyName);
+ Assert.Equal(SortDirection.Ascending, propertyList.First().Direction);
+ }
+
+ [Fact]
+ public void ToPropertyName_NestedProperty_ReturnsNestedPropertyName()
+ {
+ // Arrange
+ Expression> expression = x => x.Child.ChildName;
+
+ // Act
+ var gridSort = GridSort.ByAscending(expression);
+ var propertyList = gridSort.ToPropertyList(ascending: true);
+
+ // Assert
+ Assert.Single(propertyList);
+ Assert.Equal("Child.ChildName", propertyList.First().PropertyName);
+ Assert.Equal(SortDirection.Ascending, propertyList.First().Direction);
+ }
+
+ [Fact]
+ public void ToPropertyName_NestedNullableProperty_ReturnsNestedPropertyName()
+ {
+ // Arrange
+ Expression> expression = x => x.Child.ChildNullableDate;
+
+ // Act
+ var gridSort = GridSort.ByAscending(expression);
+ var propertyList = gridSort.ToPropertyList(ascending: true);
+
+ // Assert
+ Assert.Single(propertyList);
+ Assert.Equal("Child.ChildNullableDate", propertyList.First().PropertyName);
+ Assert.Equal(SortDirection.Ascending, propertyList.First().Direction);
+ }
+
+ [Fact]
+ public void ToPropertyName_DescendingSort_ReturnsCorrectDirection()
+ {
+ // Arrange
+ Expression> expression = x => x.NullableDate;
+
+ // Act
+ var gridSort = GridSort.ByDescending(expression);
+ var propertyList = gridSort.ToPropertyList(ascending: true);
+
+ // Assert
+ Assert.Single(propertyList);
+ Assert.Equal("NullableDate", propertyList.First().PropertyName);
+ Assert.Equal(SortDirection.Descending, propertyList.First().Direction);
+ }
+
+ [Fact]
+ public void ToPropertyName_MultipleSort_ReturnsAllProperties()
+ {
+ // Arrange
+ Expression> firstExpression = x => x.Name;
+ Expression> secondExpression = x => x.NullableDate;
+
+ // Act
+ var gridSort = GridSort.ByAscending(firstExpression)
+ .ThenDescending(secondExpression);
+ var propertyList = gridSort.ToPropertyList(ascending: true);
+
+ // Assert
+ Assert.Equal(2, propertyList.Count);
+
+ var firstProperty = propertyList.First();
+ Assert.Equal("Name", firstProperty.PropertyName);
+ Assert.Equal(SortDirection.Ascending, firstProperty.Direction);
+
+ var secondProperty = propertyList.Last();
+ Assert.Equal("NullableDate", secondProperty.PropertyName);
+ Assert.Equal(SortDirection.Descending, secondProperty.Direction);
+ }
+
+ [Fact]
+ public void ToPropertyName_InvalidExpression_ThrowsArgumentException()
+ {
+ // Arrange
+ Expression> invalidExpression = x => x.Name.ToUpper(CultureInfo.InvariantCulture);
+
+ // Act & Assert
+ var gridSort = GridSort.ByAscending(invalidExpression);
+ var exception = Assert.Throws(() => gridSort.ToPropertyList(ascending: true));
+ Assert.Contains("The supplied expression can't be represented as a property name for sorting", exception.Message);
+ }
+
+ [Fact]
+ public void ToPropertyName_MethodCallExpression_ThrowsArgumentException()
+ {
+ // Arrange
+ Expression> invalidExpression = x => x.Name.Substring(0, 1);
+
+ // Act & Assert
+ var gridSort = GridSort.ByAscending(invalidExpression);
+ var exception = Assert.Throws(() => gridSort.ToPropertyList(ascending: true));
+ Assert.Contains("The supplied expression can't be represented as a property name for sorting", exception.Message);
+ }
+
+ [Fact]
+ public void ToPropertyName_ConstantExpression_ThrowsArgumentException()
+ {
+ // Arrange
+ Expression> invalidExpression = x => "constant";
+
+ // Act & Assert
+ var gridSort = GridSort.ByAscending(invalidExpression);
+ var exception = Assert.Throws(() => gridSort.ToPropertyList(ascending: true));
+ Assert.Contains("The supplied expression can't be represented as a property name for sorting", exception.Message);
+ }
+}
\ No newline at end of file
diff --git a/src/Components/QuickGrid/Microsoft.AspNetCore.Components.QuickGrid/test/Microsoft.AspNetCore.Components.QuickGrid.Tests.csproj b/src/Components/QuickGrid/Microsoft.AspNetCore.Components.QuickGrid/test/Microsoft.AspNetCore.Components.QuickGrid.Tests.csproj
new file mode 100644
index 000000000000..402e6a12a8f7
--- /dev/null
+++ b/src/Components/QuickGrid/Microsoft.AspNetCore.Components.QuickGrid/test/Microsoft.AspNetCore.Components.QuickGrid.Tests.csproj
@@ -0,0 +1,18 @@
+
+
+
+ $(DefaultNetCoreTargetFramework)
+ Microsoft.AspNetCore.Components.QuickGrid.Tests
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file