From 95979c7a696646b0a53b2fdfcf675689316b321f Mon Sep 17 00:00:00 2001 From: Sam Xu Date: Mon, 1 Jul 2024 11:00:12 -0700 Subject: [PATCH] Fixes #1250: Enable query options on untyped properties --- .../Edm/DefaultODataTypeMapper.cs | 18 +++- .../Edm/EdmHelpers.cs | 10 ++ .../Untyped/UntypedTests.cs | 92 +++++++++++++++++-- .../Edm/DefaultODataTypeMapperTests.cs | 32 +++++++ .../Edm/EdmHelpersTests.cs | 3 +- 5 files changed, 145 insertions(+), 10 deletions(-) diff --git a/src/Microsoft.AspNetCore.OData/Edm/DefaultODataTypeMapper.cs b/src/Microsoft.AspNetCore.OData/Edm/DefaultODataTypeMapper.cs index f6de16954..af34a5d3b 100644 --- a/src/Microsoft.AspNetCore.OData/Edm/DefaultODataTypeMapper.cs +++ b/src/Microsoft.AspNetCore.OData/Edm/DefaultODataTypeMapper.cs @@ -202,6 +202,12 @@ private IEdmType GetEdmType(IEdmModel edmModel, Type clrType, bool testCollectio Contract.Assert(edmModel != null); Contract.Assert(clrType != null); + // Be noted, let's treat the 'object' as structured untyped. + if (clrType == typeof(object)) + { + return EdmUntypedStructuredType.Instance; + } + IEdmPrimitiveTypeReference primitiveType = GetEdmPrimitiveType(clrType); if (primitiveType != null) { @@ -251,6 +257,11 @@ private IEdmType GetEdmType(IEdmModel edmModel, Type clrType, bool testCollectio IEdmType elementType = GetEdmType(edmModel, elementClrType, testCollections: false); if (elementType != null) { + if (elementType.IsUntyped()) + { + return EdmUntypedHelpers.NullableUntypedCollectionReference.Definition; + } + return new EdmCollectionType(elementType.ToEdmTypeReference(elementClrType.IsNullable())); } } @@ -275,7 +286,7 @@ private IEdmType GetEdmType(IEdmModel edmModel, Type clrType, bool testCollectio // default to the EdmType with the same name as the ClrType name returnType = returnType ?? edmModel.FindType(clrType.EdmFullName()); - if (clrType.BaseType != null) + if (clrType.BaseType != null && clrType.BaseType != typeof(object)) { // go up the inheritance tree to see if we have a mapping defined for the base type. returnType = returnType ?? GetEdmType(edmModel, clrType.BaseType, testCollections); @@ -307,6 +318,11 @@ public virtual Type GetClrType(IEdmModel edmModel, IEdmType edmType, bool nullab return GetClrPrimitiveType((IEdmPrimitiveType)edmType, nullable); } + if (edmType.IsUntyped()) + { + return typeof(object); + } + if (edmModel == null) { throw Error.ArgumentNull(nameof(edmModel)); diff --git a/src/Microsoft.AspNetCore.OData/Edm/EdmHelpers.cs b/src/Microsoft.AspNetCore.OData/Edm/EdmHelpers.cs index ad545ca52..1949e1d18 100644 --- a/src/Microsoft.AspNetCore.OData/Edm/EdmHelpers.cs +++ b/src/Microsoft.AspNetCore.OData/Edm/EdmHelpers.cs @@ -200,6 +200,16 @@ public static IEdmTypeReference ToEdmTypeReference(this IEdmType edmType, bool i case EdmTypeKind.TypeDefinition: return new EdmTypeDefinitionReference((IEdmTypeDefinition)edmType, isNullable); + case EdmTypeKind.Untyped: + if (isNullable) + { + return EdmUntypedStructuredTypeReference.NullableTypeReference; + } + else + { + return EdmUntypedStructuredTypeReference.NonNullableTypeReference; + } + default: throw Error.NotSupported(SRResources.EdmTypeNotSupported, edmType.ToTraceString()); } diff --git a/test/Microsoft.AspNetCore.OData.E2E.Tests/Untyped/UntypedTests.cs b/test/Microsoft.AspNetCore.OData.E2E.Tests/Untyped/UntypedTests.cs index a36dcfd34..0c6151bf0 100644 --- a/test/Microsoft.AspNetCore.OData.E2E.Tests/Untyped/UntypedTests.cs +++ b/test/Microsoft.AspNetCore.OData.E2E.Tests/Untyped/UntypedTests.cs @@ -5,20 +5,17 @@ // //------------------------------------------------------------------------------ -using Microsoft.AspNetCore.OData.Routing.Controllers; -using Microsoft.AspNetCore.OData.TestCommon; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.OData.Edm; using System.Net.Http; using System.Net; using System.Text.RegularExpressions; using System.Threading.Tasks; +using System.Net.Http.Headers; +using Microsoft.AspNetCore.OData.Routing.Controllers; +using Microsoft.AspNetCore.OData.TestCommon; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OData.Edm; using Xunit; using Xunit.Abstractions; -using Newtonsoft.Json.Linq; -using System.Linq; -using System.Net.Http.Headers; -using System.Xml.Linq; namespace Microsoft.AspNetCore.OData.E2E.Tests.Untyped { @@ -215,6 +212,36 @@ public async Task QueryUntypedEntitySet_OnDifferentMetadataLevel(string format, Assert.Equal(expected, payloadBody); } + [Fact] + public async Task QueryUntypedEntitySet_WithDollarSelectOnUntypedProperties() + { + // Arrange + HttpClient client = CreateClient(); + + // Act + HttpResponseMessage response = await client.GetAsync($"odata/managers?$select=data,infos"); + + // Assert + Assert.NotNull(response); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.NotNull(response.Content); + + string payloadBody = await response.Content.ReadAsStringAsync(); + + Assert.Equal("{\"@odata.context\":\"http://localhost/odata/$metadata#Managers(Data,Infos)\"," + + "\"value\":[" + + "{" + + "\"Data\":{\"City\":\"Shanghai\",\"Street\":\"Fengjin RD\"}," + + "\"Infos\":[1,\"abc\",3]" + + "}," + + "{" + + "\"Data\":[42,null,\"abc\",{\"ACity\":\"Shanghai\",\"AData\":[42,\"Red\"]}]," + + "\"Infos\":[42,\"Red\"]" + + "}" + + "]" + + "}", payloadBody); + } + public static TheoryDataSet DeclaredPropertyQueryCases { get @@ -324,6 +351,55 @@ public async Task QuerySinglePeople_OnDeclaredUntypedProperty(string request, st Assert.Equal(expected, payloadBody); } + [Fact] + public async Task QueryUntypedEntitySet_WithDollarFilterOnUntypedProperties_IsTypeOf() + { + // Arrange + HttpClient client = CreateClient(); + + // Act + HttpResponseMessage response = await client.GetAsync($"odata/people?$filter=isof(data,'Edm.Int32')"); + + // Assert + Assert.NotNull(response); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.NotNull(response.Content); + + string payloadBody = await response.Content.ReadAsStringAsync(); + + Assert.Equal("{\"@odata.context\":\"http://localhost/odata/$metadata#People\"," + + "\"value\":[" + + "{\"Id\":1,\"Name\":\"Kerry\",\"Dynamic1\":13,\"Dynamic2\":true,\"Data@odata.type\":\"#Int32\",\"Data\":13,\"Infos\":[1,2,3]}" + + "]}", payloadBody); + } + + [Fact] + public async Task QueryUntypedEntitySet_WithDollarFilterOnUntypedProperties_IsNotTypeOf() + { + // Arrange + HttpClient client = CreateClient(); + + // Act + HttpResponseMessage response = await client.GetAsync($"odata/people?$filter=not isof(data,'Edm.Int32')&$select=Id,data"); + + // Assert + Assert.NotNull(response); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.NotNull(response.Content); + + string payloadBody = await response.Content.ReadAsStringAsync(); + + Assert.Equal("{\"@odata.context\":\"http://localhost/odata/$metadata#People(Id,Data)\"," + + "\"value\":[" + + "{\"Id\":2,\"Data\":\"Red\"}," + + "{\"Id\":22,\"Data@odata.type\":\"#String\",\"Data\":\"Apple\"}," + + "{\"Id\":3,\"Data\":{\"City\":\"Redmond\",\"Street\":\"134TH AVE\"}}," + + "{\"Id\":4,\"Data\":{\"ZipCode\":\"<--->\",\"Location\":\"******\"}}," + + "{\"Id\":5,\"Data\":[null,42,{\"@odata.type\":\"#Microsoft.AspNetCore.OData.E2E.Tests.Untyped.InModelAddress\",\"City\":\"Redmond\",\"Street\":\"134TH AVE\"}]}," + + "{\"Id\":99,\"Data\":[null,[42,{\"@odata.type\":\"#Microsoft.AspNetCore.OData.E2E.Tests.Untyped.InModelAddress\",\"City\":\"Redmond\",\"Street\":\"134TH AVE\"}]]}" + + "]}", payloadBody); + } + [Fact] public async Task QuerySinglePeople_WithDeclaredOrUndeclaredEnum_OnUntypedAndDynamicProperty() { diff --git a/test/Microsoft.AspNetCore.OData.Tests/Edm/DefaultODataTypeMapperTests.cs b/test/Microsoft.AspNetCore.OData.Tests/Edm/DefaultODataTypeMapperTests.cs index 9781563ca..12c25ce05 100644 --- a/test/Microsoft.AspNetCore.OData.Tests/Edm/DefaultODataTypeMapperTests.cs +++ b/test/Microsoft.AspNetCore.OData.Tests/Edm/DefaultODataTypeMapperTests.cs @@ -330,6 +330,30 @@ public void GetClrType_WorksAsExpected_ForSchemaEnumType() Assert.Same(typeof(MyColor), clrType); } + + [Fact] + public void GetClrType_WorksAsExpected_ForUntypedType() + { + // 1) Arrange : using primitive untyped + IEdmType untypedType = EdmCoreModel.Instance.GetUntypedType(); + + // 1) Act & Assert + Type clrType = _mapper.GetClrType(EdmModel, untypedType, true, null); + Assert.Same(typeof(object), clrType); + + clrType = _mapper.GetClrType(EdmModel, untypedType, false, null); + Assert.Same(typeof(object), clrType); + + // 2) Arrange : using structured untyped + untypedType = EdmUntypedStructuredType.Instance; + + // 2) Act & Assert + clrType = _mapper.GetClrType(EdmModel, untypedType, true, null); + Assert.Same(typeof(object), clrType); + clrType = _mapper.GetClrType(EdmModel, untypedType, false, null); + Assert.Same(typeof(object), clrType); + } + #endregion #region GetEdmType @@ -409,6 +433,14 @@ public void GetEdmTypeReference_WorksAsExpected_ForSchemaEnumType() Assert.Same(expectedType, colorType.Definition); Assert.True(colorType.IsNullable); } + + [Fact] + public void GetEdmTypeReference_WorksAsExpected_FoUntypedTpe() + { + // Arrange & Act & Assert + IEdmTypeReference untyped = _mapper.GetEdmTypeReference(EdmModel, typeof(object)); + Assert.Same(EdmUntypedStructuredType.Instance, untyped.Definition); + } #endregion [Theory] diff --git a/test/Microsoft.AspNetCore.OData.Tests/Edm/EdmHelpersTests.cs b/test/Microsoft.AspNetCore.OData.Tests/Edm/EdmHelpersTests.cs index 8cda7f1a4..64425fdab 100644 --- a/test/Microsoft.AspNetCore.OData.Tests/Edm/EdmHelpersTests.cs +++ b/test/Microsoft.AspNetCore.OData.Tests/Edm/EdmHelpersTests.cs @@ -6,7 +6,6 @@ //------------------------------------------------------------------------------ using System; -using System.Data; using Microsoft.AspNetCore.OData.Edm; using Microsoft.AspNetCore.OData.TestCommon; using Microsoft.AspNetCore.OData.Tests.Commons; @@ -160,6 +159,8 @@ public static TheoryDataSet ToEdmTypeReferenceTestData { typeDefinition, false, typeof(IEdmTypeDefinitionReference) }, { entityReferenceType, true, typeof(IEdmEntityReferenceTypeReference) }, { entityReferenceType, true, typeof(IEdmEntityReferenceTypeReference) }, + { EdmUntypedStructuredType.Instance, true, typeof(IEdmUntypedTypeReference) }, + { EdmUntypedStructuredType.Instance, false, typeof(IEdmUntypedTypeReference) } }; } }