Skip to content

Commit

Permalink
Fixes #1250: Enable query options on untyped properties
Browse files Browse the repository at this point in the history
  • Loading branch information
xuzhg committed Jul 1, 2024
1 parent 599dc1b commit 95979c7
Show file tree
Hide file tree
Showing 5 changed files with 145 additions and 10 deletions.
18 changes: 17 additions & 1 deletion src/Microsoft.AspNetCore.OData/Edm/DefaultODataTypeMapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down Expand Up @@ -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()));
}
}
Expand All @@ -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);
Expand Down Expand Up @@ -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));
Expand Down
10 changes: 10 additions & 0 deletions src/Microsoft.AspNetCore.OData/Edm/EdmHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
Expand Down
92 changes: 84 additions & 8 deletions test/Microsoft.AspNetCore.OData.E2E.Tests/Untyped/UntypedTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,17 @@
// </copyright>
//------------------------------------------------------------------------------

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
{
Expand Down Expand Up @@ -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<string, string> DeclaredPropertyQueryCases
{
get
Expand Down Expand Up @@ -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()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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]
Expand Down
3 changes: 2 additions & 1 deletion test/Microsoft.AspNetCore.OData.Tests/Edm/EdmHelpersTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -160,6 +159,8 @@ public static TheoryDataSet<IEdmType, bool, Type> ToEdmTypeReferenceTestData
{ typeDefinition, false, typeof(IEdmTypeDefinitionReference) },
{ entityReferenceType, true, typeof(IEdmEntityReferenceTypeReference) },
{ entityReferenceType, true, typeof(IEdmEntityReferenceTypeReference) },
{ EdmUntypedStructuredType.Instance, true, typeof(IEdmUntypedTypeReference) },
{ EdmUntypedStructuredType.Instance, false, typeof(IEdmUntypedTypeReference) }
};
}
}
Expand Down

0 comments on commit 95979c7

Please sign in to comment.