OData / AspNetCoreOData

ASP.NET Core OData: A server library built upon ODataLib and ASP.NET Core
Other
457 stars 158 forks source link

NullReference exception generated when using FilterClause.ApplyTo method with open properties in the filter query #1214

Open vibatra opened 7 months ago

vibatra commented 7 months ago

Assemblies affected ASP.NET Core OData 8.2.5

Describe the bug FilterClause.ApplyTo method generates a NullReferenceException if a open/dynamic property is used in the filter query. The exception happens in QueryBinder.BindPropertyAccessExpression method.

Reproduce steps

// Get local copy of graph model metadata for generating the EDM model
var xmlReader = XmlReader.Create(new StringReader(GraphMetadataForValidation.GraphMetadata));
var EdmModel = CsdlReader.Parse(xmlReader);
// Set annotations to bind the User entity in EDM model to the User class in the SDK
EdmModel.SetAnnotationValue<ClrTypeAnnotation>(EdmModel.FindType("microsoft.graph.user"), new ClrTypeAnnotation(typeof(Graph.User)));
// Generate query option for FilterClause
IDictionary<string, string> queryOptions = new Dictionary<string, string> { { "$filter", "extension_123456_attr1 eq 'Test'"} };
ODataQueryContext ctx2 = new ODataQueryContext(EdmModel, typeof(Graph.User), new ODataPath())
{
    DefaultQueryConfigurations = { EnableFilter = true },
};
var parser = new ODataQueryOptionParser(
    EdmModel,
    ctx2.ElementType,
    ctx2.NavigationSource,
    queryOptions);
parser.Resolver = new ODataUriResolver { EnableCaseInsensitive = true };

var filterQueryOption = new FilterQueryOption("extension_123456_attr1 eq 'Test'", ctx2, parser);
var results = filterQueryOption.ApplyTo(users.AsQueryable(), new ODataQuerySettings());

Data Model The User class in Graph SDK but it can be any othr

EDM (CSDL) Model https://[graph.microsoft.com/v1.0/$metadata#users](https://graph.microsoft.com/v1.0/$metadata#users)

Request/Response No request/response. We are using it as stand alone.

Expected behavior A filtered data set is returned.

Actual behavior A NullReference exception is thrown when the ApplyTo method is called as it is not able to bind to the dynamic/additional data container on the CLR type.

Additional context There is no way for us inject a custom implementation of FilterBinder in the stand alone mode.

corranrogue9 commented 7 months ago

We certainly shouldn't be throwing a NullReferenceException. Let me look more using your repro steps.

vibatra commented 7 months ago

We certainly shouldn't be throwing a NullReferenceException. Let me look more using your repro steps.

Below is a version of the repro steps without depending on the SDK and Graph metadata. Result is same - NRE thrown when ApplyTo is called.

        string edmMetadata = @"
<edmx:Edmx xmlns:edmx=""http://docs.oasis-open.org/odata/ns/edmx"" Version=""4.0"">
<edmx:DataServices>
<Schema xmlns=""http://docs.oasis-open.org/odata/ns/edm"" Namespace=""testSpace.filterClause"">
<EntityType Name=""customer"" OpenType=""true"">
<Key>
<PropertyRef Name=""id""/>
</Key>
<Property Name=""dateTimeOffsetProperty"" Type=""Edm.DateTimeOffset""/>
<Property Name=""id"" Type=""Edm.String"" Nullable=""false""/>
<Property Name=""stringProperty"" Type=""Edm.String""/>
<Property Name=""booleanProperty"" Type=""Edm.Boolean""/>
</EntityType>
</Schema>
</edmx:DataServices>
</edmx:Edmx>";
        // Generate model from the metadata
        var xmlReader = XmlReader.Create(new StringReader(edmMetadata));
        var EdmModel = CsdlReader.Parse(xmlReader);
        // Set annotations to bind the User entity in EDM model to the User class in the SDK
        EdmModel.SetAnnotationValue(EdmModel.FindType("testSpace.filterClause.customer"), new ClrTypeAnnotation(typeof(Customer)));
        // Generate query option for FilterClause
        IDictionary<string, string> queryOptions = new Dictionary<string, string> { { "$filter", "extension_123456_attr1 eq 'Test'" } };
        ODataQueryContext ctx2 = new ODataQueryContext(EdmModel, typeof(Customer), new ODataPath())
        {
            DefaultQueryConfigurations = { EnableFilter = true },
        };
        var parser = new ODataQueryOptionParser(
            EdmModel,
            ctx2.ElementType,
            ctx2.NavigationSource,
            queryOptions);
        parser.Resolver = new ODataUriResolver { EnableCaseInsensitive = true };

        var filterQueryOption = new FilterQueryOption("extension_123456_attr1 eq 'Test'", ctx2, parser);
        List<Customer> customers = new List<Customer>() { new Customer() { Id = "test1", StringProperty = "string", BooleanProperty = true, DateTimeOffsetProperty = DateTimeOffset.UtcNow, ExtensionData = new Dictionary<string, object>() } };
        var results = filterQueryOption.ApplyTo(customers.AsQueryable(), new ODataQuerySettings());

The CLR model -

        public class Customer
        {
            public string Id { get; set; }
            public string StringProperty { get; set; }
            public bool BooleanProperty { get; set; }
            public DateTimeOffset DateTimeOffsetProperty { get; set; }

            public IDictionary<string, object> ExtensionData { get; set; }
        }