Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes #1250: Enable query options on untyped properties #1270

Open
wants to merge 1 commit into
base: release-8.x
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
}
Comment on lines +204 to +211
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@xuzhg Do you have tests for both scenarios here?


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
Loading