Skip to content
This repository was archived by the owner on Feb 17, 2025. It is now read-only.

Commit cba23fa

Browse files
author
Ihar Yakimush
committed
add order by
1 parent fbcad8e commit cba23fa

File tree

6 files changed

+263
-5
lines changed

6 files changed

+263
-5
lines changed

Community.Data.OData.Linq.xTests/OrderByTests.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System.Linq;
22
using Community.OData.Linq.xTests.SampleData;
3+
using Microsoft.OData;
34
using Xunit;
45

56
namespace Community.OData.Linq.xTests
@@ -29,5 +30,12 @@ public void OrderByIdDefault()
2930

3031
Assert.Equal(1, result.Id);
3132
}
33+
34+
[Fact]
35+
public void OrderByNotSortable()
36+
{
37+
Assert.Throws<ODataException>(() =>
38+
SimpleClass.CreateQuery().OData().OrderBy($"{nameof(SimpleClass.NotOrderable)}"));
39+
}
3240
}
3341
}

Community.Data.OData.Linq.xTests/SampleData/SimpleClass.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,5 +32,8 @@ public static IQueryable<SimpleClass> CreateQuery()
3232

3333
[NonFilterable]
3434
public string NameNotFilter { get; set; }
35+
36+
[NotSortable]
37+
public int NotOrderable { get; set; }
3538
}
3639
}
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License. See License.txt in the project root for license information.
3+
4+
using Community.OData.Linq.Common;
5+
using Community.OData.Linq.OData.Formatter;
6+
using Community.OData.Linq.Properties;
7+
using Microsoft.OData;
8+
using Microsoft.OData.Edm;
9+
using Microsoft.OData.UriParser;
10+
11+
namespace System.Web.OData.Query.Validators
12+
{
13+
internal class OrderByModelLimitationsValidator : QueryNodeVisitor<SingleValueNode>
14+
{
15+
private readonly IEdmModel _model;
16+
private readonly bool _enableOrderBy;
17+
private IEdmProperty _property;
18+
private IEdmStructuredType _structuredType;
19+
20+
public OrderByModelLimitationsValidator(IEdmModel model, bool enableOrderBy)
21+
{
22+
_model = model;
23+
_enableOrderBy = enableOrderBy;
24+
}
25+
26+
public bool TryValidate(IEdmProperty property, IEdmStructuredType structuredType, OrderByClause orderByClause,
27+
bool explicitPropertiesDefined)
28+
{
29+
_property = property;
30+
_structuredType = structuredType;
31+
return TryValidate(orderByClause, explicitPropertiesDefined);
32+
}
33+
34+
// Visits the expression to find the first node if any, that is not sortable and throws
35+
// an exception only if no explicit properties have been defined in AllowedOrderByProperties
36+
// on the ODataValidationSettings instance associated with this OrderByValidator.
37+
public bool TryValidate(OrderByClause orderByClause, bool explicitPropertiesDefined)
38+
{
39+
SingleValueNode invalidNode = orderByClause.Expression.Accept(this);
40+
if (invalidNode != null && !explicitPropertiesDefined)
41+
{
42+
throw new ODataException(Error.Format(SRResources.NotSortablePropertyUsedInOrderBy,
43+
GetPropertyName(invalidNode)));
44+
}
45+
return invalidNode == null;
46+
}
47+
48+
public override SingleValueNode Visit(SingleValuePropertyAccessNode nodeIn)
49+
{
50+
if (nodeIn.Source != null)
51+
{
52+
if (nodeIn.Source.Kind == QueryNodeKind.SingleNavigationNode)
53+
{
54+
SingleNavigationNode singleNavigationNode = nodeIn.Source as SingleNavigationNode;
55+
if (EdmLibHelpers.IsNotSortable(nodeIn.Property, singleNavigationNode.NavigationProperty,
56+
singleNavigationNode.NavigationProperty.ToEntityType(), _model, _enableOrderBy))
57+
{
58+
return nodeIn;
59+
}
60+
}
61+
else if (nodeIn.Source.Kind == QueryNodeKind.SingleComplexNode)
62+
{
63+
SingleComplexNode singleComplexNode = nodeIn.Source as SingleComplexNode;
64+
if (EdmLibHelpers.IsNotSortable(nodeIn.Property, singleComplexNode.Property,
65+
nodeIn.Property.DeclaringType, _model, _enableOrderBy))
66+
{
67+
return nodeIn;
68+
}
69+
}
70+
else if (EdmLibHelpers.IsNotSortable(nodeIn.Property, _property, _structuredType, _model, _enableOrderBy))
71+
{
72+
return nodeIn;
73+
}
74+
}
75+
76+
if (nodeIn.Source != null)
77+
{
78+
return nodeIn.Source.Accept(this);
79+
}
80+
81+
return null;
82+
}
83+
84+
public override SingleValueNode Visit(SingleComplexNode nodeIn)
85+
{
86+
if (EdmLibHelpers.IsNotSortable(nodeIn.Property, _property, _structuredType, _model, _enableOrderBy))
87+
{
88+
return nodeIn;
89+
}
90+
91+
if (nodeIn.Source != null)
92+
{
93+
return nodeIn.Source.Accept(this);
94+
}
95+
96+
return null;
97+
}
98+
99+
public override SingleValueNode Visit(SingleNavigationNode nodeIn)
100+
{
101+
if (EdmLibHelpers.IsNotSortable(nodeIn.NavigationProperty, _property, _structuredType, _model,
102+
_enableOrderBy))
103+
{
104+
return nodeIn;
105+
}
106+
107+
if (nodeIn.Source != null)
108+
{
109+
return nodeIn.Source.Accept(this);
110+
}
111+
112+
return null;
113+
}
114+
115+
public override SingleValueNode Visit(ResourceRangeVariableReferenceNode nodeIn)
116+
{
117+
return null;
118+
}
119+
120+
public override SingleValueNode Visit(NonResourceRangeVariableReferenceNode nodeIn)
121+
{
122+
return null;
123+
}
124+
125+
private static string GetPropertyName(SingleValueNode node)
126+
{
127+
if (node.Kind == QueryNodeKind.SingleNavigationNode)
128+
{
129+
return ((SingleNavigationNode)node).NavigationProperty.Name;
130+
}
131+
else if (node.Kind == QueryNodeKind.SingleValuePropertyAccess)
132+
{
133+
return ((SingleValuePropertyAccessNode)node).Property.Name;
134+
}
135+
else if (node.Kind == QueryNodeKind.SingleComplexNode)
136+
{
137+
return ((SingleComplexNode)node).Property.Name;
138+
}
139+
return null;
140+
}
141+
}
142+
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License. See License.txt in the project root for license information.
3+
4+
using System.Collections.Generic;
5+
using Community.OData.Linq.Common;
6+
using Community.OData.Linq.OData.Query;
7+
using Community.OData.Linq.Properties;
8+
using Microsoft.Extensions.DependencyInjection;
9+
using Microsoft.OData;
10+
using Microsoft.OData.Edm;
11+
using Microsoft.OData.UriParser;
12+
13+
namespace System.Web.OData.Query.Validators
14+
{
15+
/// <summary>
16+
/// Represents a validator used to validate an <see cref="OrderByQueryOption"/> based on the <see cref="ODataValidationSettings"/>.
17+
/// </summary>
18+
public class OrderByQueryValidator
19+
{
20+
private readonly DefaultQuerySettings _defaultQuerySettings;
21+
22+
/// <summary>
23+
/// Initializes a new instance of the <see cref="OrderByQueryValidator" /> class based on
24+
/// the <see cref="DefaultQuerySettings" />.
25+
/// </summary>
26+
/// <param name="defaultQuerySettings">The <see cref="DefaultQuerySettings" />.</param>
27+
public OrderByQueryValidator(DefaultQuerySettings defaultQuerySettings)
28+
{
29+
_defaultQuerySettings = defaultQuerySettings;
30+
}
31+
32+
/// <summary>
33+
/// Validates an <see cref="OrderByQueryOption" />.
34+
/// </summary>
35+
/// <param name="nodes">The $orderby query.</param>
36+
/// <param name="validationSettings">The validation settings.</param>
37+
public virtual void Validate(ICollection<OrderByNode> nodes, ODataValidationSettings validationSettings, IEdmModel model)
38+
{
39+
if (nodes.Count > validationSettings.MaxOrderByNodeCount)
40+
{
41+
throw new ODataException(Error.Format(SRResources.OrderByNodeCountExceeded,
42+
validationSettings.MaxOrderByNodeCount));
43+
}
44+
45+
OrderByModelLimitationsValidator validator =
46+
new OrderByModelLimitationsValidator(model, _defaultQuerySettings.EnableOrderBy);
47+
bool explicitAllowedProperties = validationSettings.AllowedOrderByProperties.Count > 0;
48+
49+
foreach (OrderByNode node in nodes)
50+
{
51+
string propertyName = null;
52+
OrderByPropertyNode propertyNode = node as OrderByPropertyNode;
53+
if (propertyNode != null)
54+
{
55+
propertyName = propertyNode.Property.Name;
56+
bool isValidPath = !validator.TryValidate(propertyNode.Property, propertyNode.Property.DeclaringType, propertyNode.OrderByClause, explicitAllowedProperties);
57+
if (propertyName != null && isValidPath && explicitAllowedProperties)
58+
{
59+
// Explicit allowed properties were specified, but this one isn't within the list of allowed
60+
// properties.
61+
if (!IsAllowed(validationSettings, propertyName))
62+
{
63+
throw new ODataException(Error.Format(SRResources.NotAllowedOrderByProperty, propertyName,
64+
"AllowedOrderByProperties"));
65+
}
66+
}
67+
else if (propertyName != null)
68+
{
69+
// The property wasn't limited but it wasn't contained in the set of explicitly allowed
70+
// properties.
71+
if (!IsAllowed(validationSettings, propertyName))
72+
{
73+
throw new ODataException(Error.Format(SRResources.NotAllowedOrderByProperty, propertyName,
74+
"AllowedOrderByProperties"));
75+
}
76+
}
77+
}
78+
else
79+
{
80+
propertyName = "$it";
81+
if (!IsAllowed(validationSettings, propertyName))
82+
{
83+
throw new ODataException(Error.Format(SRResources.NotAllowedOrderByProperty, propertyName,
84+
"AllowedOrderByProperties"));
85+
}
86+
}
87+
}
88+
}
89+
90+
private static bool IsAllowed(ODataValidationSettings validationSettings, string propertyName)
91+
{
92+
return validationSettings.AllowedOrderByProperties.Count == 0 ||
93+
validationSettings.AllowedOrderByProperties.Contains(propertyName);
94+
}
95+
}
96+
}

Community.Data.OData.Linq/OdataLinqExtensions.cs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using Community.OData.Linq.Builder.Validators;
1+
using System.Web.OData.Query.Validators;
2+
using Community.OData.Linq.Builder.Validators;
23
using Community.OData.Linq.Common;
34
using Community.OData.Linq.Properties;
45

@@ -26,6 +27,9 @@ public static class ODataLinqExtensions
2627
private static readonly FilterQueryValidator FilterValidator =
2728
new FilterQueryValidator(new DefaultQuerySettings {EnableFilter = true});
2829

30+
private static readonly OrderByQueryValidator OrderValidator =
31+
new OrderByQueryValidator(new DefaultQuerySettings { EnableOrderBy = true });
32+
2933
/// <summary>
3034
/// The simplified options.
3135
/// </summary>
@@ -117,14 +121,18 @@ public static IOrderedQueryable<T> OrderBy<T>(this ODataQuery<T> query, string o
117121

118122
ODataSettings settings = query.ServiceProvider.GetRequiredService<ODataSettings>();
119123

120-
IOrderedQueryable<T> result = (IOrderedQueryable<T>) OrderApplyToCore<T>(query, settings.QuerySettings, orderByClause, edmModel);
124+
ICollection<OrderByNode> nodes = OrderByNode.CreateCollection(orderByClause);
125+
126+
OrderValidator.Validate(nodes, settings.ValidationSettings, edmModel);
127+
128+
IOrderedQueryable<T> result = (IOrderedQueryable<T>) OrderApplyToCore<T>(query, settings.QuerySettings, nodes, edmModel);
121129

122130
return new ODataQueryOrdered<T>(result,query.ServiceProvider);
123131
}
124132

125-
private static IOrderedQueryable OrderApplyToCore<T>(ODataQuery<T> query, ODataQuerySettings querySettings, OrderByClause orderByClause, IEdmModel model)
133+
private static IOrderedQueryable OrderApplyToCore<T>(ODataQuery<T> query, ODataQuerySettings querySettings, ICollection<OrderByNode> nodes, IEdmModel model)
126134
{
127-
ICollection<OrderByNode> nodes = OrderByNode.CreateCollection(orderByClause);
135+
128136

129137
bool alreadyOrdered = false;
130138
IQueryable querySoFar = query;

TODO.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
+Functions in filter
22
+null in filter
3-
orderby
3+
+orderby
44
-validation custom exception ?
55
+datacontracts
66
try all data types in filter
7+
exception handling
78
+non filterable attributes
89
negative scenarious in unit tests
910
readme with samples

0 commit comments

Comments
 (0)