Property restrictions not applied when not using OData model builder
Nobody has claimed this yet.
- Dominant language
- C#
- Stars
- 505
- Forks
- 186
- PR merge metrics
- No merged PRs in 30d
Description
Assemblies affected
ASP.NET Core OData 8.2.3
Describe the bug
Consider the following data model and OData controller:
namespace NS.Models
{
public class Employee
{
public int Id { get; set; }
public decimal Salary { get; set; }
}
}
public class EmployeesController : ODataController
{
[EnableQuery]
public ActionResult<IEnumerable<Employee>> Get()
{
return Ok(new List<Employee>
{
new Employee { Id = 1, Salary = 1700 },
new Employee { Id = 2, Salary = 1300 }
});
}
}
Consider further the following 3 methods with each initializing a matching Edm model as well as adding a sort restriction on the Salary property:
IEdmModel GetEdmModel01()
{
var modelBuilder = new ODataConventionModelBuilder();
var entityTypeConfiguration = modelBuilder.EntitySet<Employee>("Employees").EntityType;
entityTypeConfiguration.Property(d => d.Salary).NotSortable = true;
return modelBuilder.GetEdmModel();
}
IEdmModel GetEdmModel02()
{
var model = new EdmModel();
var employeeEntityType = model.AddEntityType("NS.Models", "Employee");
employeeEntityType.AddKeys(employeeEntityType.AddStructuralProperty("Id", EdmPrimitiveTypeKind.Int32));
employeeEntityType.AddStructuralProperty("Salary", EdmPrimitiveTypeKind.Decimal);
var entityContainer = model.AddEntityContainer("Default", "Container");
var employeesEntitySet = entityContainer.AddEntitySet("Employees", employeeEntityType);
var notSortableVocabularyAnnotation = new EdmVocabularyAnnotation(
employeesEntitySet,
new EdmTerm(
"Org.OData.Capabilities.V1",
"SortRestrictions",
new EdmEntityTypeReference(employeeEntityType, isNullable: false)),
new EdmRecordExpression(
new EdmPropertyConstructor("Sortable", new EdmBooleanConstant(true)),
new EdmPropertyConstructor("AscendingOnlyProperties", new EdmCollectionExpression()),
new EdmPropertyConstructor("DescendingOnlyProperties", new EdmCollectionExpression()),
new EdmPropertyConstructor("NonSortableProperties", new EdmCollectionExpression(
new EdmPropertyPathExpression("Salary")))));
model.SetVocabularyAnnotation(notSortableVocabularyAnnotation);
return model;
}
IEdmModel GetEdmModel03()
{
var csdl = @"<?xml version=""1.0"" encoding=""utf-8""?>
<edmx:Edmx Version=""4.0"" xmlns:edmx=""http://docs.oasis-open.org/odata/ns/edmx"">
<edmx:DataServices>
<Schema Namespace=""NS.Models"" xmlns=""http://docs.oasis-open.org/odata/ns/edm"">
<EntityType Name=""Employee"">
<Key>
<PropertyRef Name=""Id"" />
</Key>
<Property Name=""Salary"" Type=""Edm.Decimal"" Nullable=""false"" Scale=""Variable"" />
<Property Name=""Id"" Type=""Edm.Int32"" Nullable=""false"" />
<Property Name=""Name"" Type=""Edm.String"" />
</EntityType>
</Schema>
<Schema Namespace=""Default"" xmlns=""http://docs.oasis-open.org/odata/ns/edm"">
<EntityContainer Name=""Container"">
<EntitySet Name=""Employees"" EntityType=""NS.Models.Employee"">
<Annotation Term=""Org.OData.Capabilities.V1.SortRestrictions"">
<Record>
<PropertyValue Property=""Sortable"" Bool=""true"" />
<PropertyValue Property=""AscendingOnlyProperties"">
<Collection />
</PropertyValue>
<PropertyValue Property=""DescendingOnlyProperties"">
<Collection />
</PropertyValue>
<PropertyValue Property=""NonSortableProperties"">
<Collection>
<PropertyPath>Salary</PropertyPath>
</Collection>
</PropertyValue>
</Record>
</Annotation>
</EntitySet>
</EntityContainer>
</Schema>
</edmx:DataServices>
</edmx:Edmx>";
IEdmModel model;
using (var memoryStream = new MemoryStream(Encoding.UTF8.GetBytes(csdl)))
using (var reader = XmlReader.Create(memoryStream))
{
if (!CsdlReader.TryParse(reader, out model, out IEnumerable<EdmError> errors))
{
throw new Exception(string.Join("\r\n", errors.Select(d => d.ErrorMessage)));
}
return model;
}
}
If you configure an OData service with the Edm model based off of GetEdmModel01 method, the sort restriction is enforced such that sorting by Salary property is not allowed, but if you do the same using the Edm model based off of either GetEdmModel02 or GetEdmModel03 methods, sorting is not restricted.
using System.Text;
using System.Xml;
using Microsoft.AspNetCore.OData;
using Microsoft.OData.Edm;
using Microsoft.OData.Edm.Csdl;
using Microsoft.OData.Edm.Validation;
using Microsoft.OData.Edm.Vocabularies;
using Microsoft.OData.ModelBuilder;
using NS.Models;
var builder = WebApplication.CreateBuilder(args);
var model = GetEdmModel01(); // GetEdmModel02() or GetEdmModel03()
builder.Services.AddControllers().AddOData(
options => options.EnableQueryFeatures().AddRouteComponents(
model));
var app = builder.Build();
app.UseRouting();
app.UseEndpoints(endpoints => endpoints.MapControllers());
app.Run();
Request/Response
Scenario 1: GetEdmModel01()
GET http://localhost:5242/Employees?$orderby=Salary
Result:
{
"error": {
"code": "",
"message": "The query specified in the URI is not valid. The property 'Salary' cannot be used in the $orderby query option.",
"details": [],
"innererror": {
"message": "The property 'Salary' cannot be used in the $orderby query option.",
"type": "Microsoft.OData.ODataException",
"stacktrace": " at Microsoft.AspNetCore.OData.Query.Validator.OrderByModelLimitationsValidator.TryValidate(OrderByClause orderByClause, Boolean explicitPropertiesDefined) in C:\\Users\\jogathog\\Projects\\AspNetCoreOData\\src\\Microsoft.AspNetCore.OData\\Query\\Validator\\OrderByModelLimitationsValidator.cs:line 50\r\n at Microsoft.AspNetCore.OData.Query.Validator.OrderByQueryValidator.Validate(OrderByQueryOption orderByOption, ODataValidationSettings validationSettings) in C:\\Users\\jogathog\\Projects\\AspNetCoreOData\\src\\Microsoft.AspNetCore.OData\\Query\\Validator\\OrderByQueryValidator.cs:line 57\r\n at Microsoft.AspNetCore.OData.Query.OrderByQueryOption.Validate(ODataValidationSettings validationSettings) in C:\\Users\\jogathog\\Projects\\AspNetCoreOData\\src\\Microsoft.AspNetCore.OData\\Query\\Query\\OrderByQueryOption.cs:line 231\r\n at Microsoft.AspNetCore.OData.Query.Validator.ODataQueryValidator.Validate(ODataQueryOptions options, ODataValidationSettings validationSettings) in C:\\Users\\jogathog\\Projects\\AspNetCoreOData\\src\\Microsoft.AspNetCore.OData\\Query\\Validator\\ODataQueryValidator.cs:line 62\r\n at Microsoft.AspNetCore.OData.Query.ODataQueryOptions.Validate(ODataValidationSettings validationSettings) in C:\\Users\\jogathog\\Projects\\AspNetCoreOData\\src\\Microsoft.AspNetCore.OData\\Query\\ODataQueryOptions.cs:line 651\r\n at Microsoft.AspNetCore.OData.Query.EnableQueryAttribute.ValidateQuery(HttpRequest request, ODataQueryOptions queryOptions) in C:\\Users\\jogathog\\Projects\\AspNetCoreOData\\src\\Microsoft.AspNetCore.OData\\Query\\EnableQueryAttribute.cs:line 743\r\n at Microsoft.AspNetCore.OData.Query.EnableQueryAttribute.OnActionExecuting(ActionExecutingContext actionExecutingContext) in C:\\Users\\jogathog\\Projects\\AspNetCoreOData\\src\\Microsoft.AspNetCore.OData\\Query\\EnableQueryAttribute.cs:line 79"
}
}
}
Scenario 2: GetEdmModel02() or GetEdmModel03()
GET http://localhost:5242/Employees?$orderby=Salary
Result:
{
"@odata.context": "http://localhost:5242/$metadata#Employees",
"value": [
{
"Id": 2,
"Salary": 1300
},
{
"Id": 1,
"Salary": 1700
}
]
}
Expected behavior
Independent of the way the Edm model is built, the property restrictions should be applied.
Additional context
A QueryablePropertyRestriction annotation value is added to the model's annotation manager in the working scenario but not in the non-working scenario. That restriction is relied upon when checking whether a restriction is enabled or not.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Reproduce the request with GetEdmModel01(), GetEdmModel02(), and GetEdmModel03(), then compare how their annotations reach OrderByModelLimitationsValidator.cs, identified in the stack trace. Inspect the model annotation handling and add regression coverage showing that $orderby=Salary is rejected for manually built and parsed models, matching the convention-builder result.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- csharp
- Domain
- api, backend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 42/100