Skip to content

Commit 419caef

Browse files
Chris Martinezcommonsensesoftware
authored andcommitted
Fix type substitutions for types with inner types that should be extracted
1 parent ac5dd2c commit 419caef

File tree

3 files changed

+114
-12
lines changed

3 files changed

+114
-12
lines changed

src/Common.OData.ApiExplorer/AspNet.OData/TypeExtensions.cs

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ public static partial class TypeExtensions
2525
static readonly Type ActionResultType = typeof( IActionResult );
2626
static readonly Type HttpResponseType = typeof( HttpResponseMessage );
2727
static readonly Type IEnumerableOfT = typeof( IEnumerable<> );
28-
static readonly Type DeltaOfT = typeof( Delta<> );
2928
static readonly Type ODataValueOfT = typeof( ODataValue<> );
3029

3130
/// <summary>
@@ -56,7 +55,13 @@ public static Type SubstituteIfNecessary( this Type type, TypeSubstitutionContex
5655
}
5756

5857
var newType = context.ModelTypeBuilder.NewStructuredType( structuredType, innerType, apiVersion );
59-
return innerType.Equals( newType ) ? type : CloseGeneric( openTypes, newType );
58+
59+
if ( innerType.Equals( newType ) )
60+
{
61+
return type.ExtractInnerType() ? innerType : type;
62+
}
63+
64+
return CloseGeneric( openTypes, newType );
6065
}
6166

6267
if ( CanBeSubstituted( type ) )
@@ -101,10 +106,7 @@ static bool IsSubstitutableGeneric( Type type, Stack<Type> openTypes, out Type i
101106

102107
var typeArg = typeArgs[0];
103108

104-
if ( typeDef.Equals( IEnumerableOfT ) ||
105-
typeDef.Equals( DeltaOfT ) ||
106-
typeDef.Equals( ODataValueOfT ) ||
107-
typeDef.FullName.Equals( "Microsoft.AspNetCore.Mvc.ActionResult`1", Ordinal ) )
109+
if ( typeDef.Equals( IEnumerableOfT ) || typeDef.IsDelta() || typeDef.Equals( ODataValueOfT ) || typeDef.IsActionResult() )
108110
{
109111
innerType = typeArg;
110112
}
@@ -140,7 +142,14 @@ static Type CloseGeneric( Stack<Type> openTypes, Type innerType )
140142
Contract.Requires( openTypes.Count > 0 );
141143
Contract.Requires( innerType != null );
142144

143-
var type = openTypes.Pop().MakeGenericType( innerType );
145+
var type = openTypes.Pop();
146+
147+
if ( type.ExtractInnerType() )
148+
{
149+
return innerType;
150+
}
151+
152+
type = type.MakeGenericType( innerType );
144153

145154
while ( openTypes.Count > 0 )
146155
{
@@ -158,7 +167,8 @@ static bool CanBeSubstituted( Type type )
158167
!type.IsValueType &&
159168
!type.Equals( VoidType ) &&
160169
!type.Equals( ActionResultType ) &&
161-
!type.Equals( HttpResponseType );
170+
!type.Equals( HttpResponseType ) &&
171+
!type.IsODataActionParameters();
162172
}
163173

164174
static bool IsEnumerable( this Type type, out Type itemType )
@@ -190,5 +200,11 @@ static bool IsEnumerable( this Type type, out Type itemType )
190200

191201
return false;
192202
}
203+
204+
static bool IsActionResult( this Type type ) =>
205+
type.IsGenericType &&
206+
type.GetGenericTypeDefinition().FullName.Equals( "Microsoft.AspNetCore.Mvc.ActionResult`1", Ordinal );
207+
208+
static bool ExtractInnerType( this Type type ) => type.IsDelta() || type.IsActionResult();
193209
}
194210
}

test/Microsoft.AspNet.OData.Versioning.ApiExplorer.Tests/AspNet.OData/DefaultModelTypeBuilderTest.cs

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,51 @@ public void substituted_type_should_be_same_as_original_type( Type originalType
3131
substitutedType.Should().Be( originalType );
3232
}
3333

34+
[Fact]
35+
public void substituted_type_should_be_extracted_from_parent_generic()
36+
{
37+
// arrange
38+
var modelBuilder = new ODataConventionModelBuilder();
39+
40+
modelBuilder.EntitySet<Contact>( "Contacts" );
41+
modelBuilder.EntityType<Address>();
42+
43+
var context = NewContext( modelBuilder.GetEdmModel() );
44+
var originalType = typeof( Delta<Contact> );
45+
46+
// act
47+
var substitutedType = originalType.SubstituteIfNecessary( context );
48+
49+
// assert
50+
substitutedType.Should().Be( typeof( Contact ) );
51+
}
52+
53+
[Fact]
54+
public void type_should_be_match_edm_when_extracted_and_substituted_from_parent_generic()
55+
{
56+
// arrange
57+
var modelBuilder = new ODataConventionModelBuilder();
58+
var contact = modelBuilder.EntitySet<Contact>( "Contacts" ).EntityType;
59+
60+
contact.Ignore( p => p.Email );
61+
contact.Ignore( p => p.Phone );
62+
contact.Ignore( p => p.Addresses );
63+
64+
var context = NewContext( modelBuilder.GetEdmModel() );
65+
var originalType = typeof( Delta<Contact> );
66+
67+
// act
68+
var substitutedType = originalType.SubstituteIfNecessary( context );
69+
70+
// assert
71+
substitutedType.Should().NotBe( originalType );
72+
substitutedType.Should().NotBe( typeof( Contact ) );
73+
substitutedType.GetRuntimeProperties().Should().HaveCount( 3 );
74+
substitutedType.Should().HaveProperty<int>( nameof( Contact.ContactId ) );
75+
substitutedType.Should().HaveProperty<string>( nameof( Contact.FirstName ) );
76+
substitutedType.Should().HaveProperty<string>( nameof( Contact.LastName ) );
77+
}
78+
3479
[Theory]
3580
[MemberData( nameof( SubstitutionData ) )]
3681
public void type_should_match_edm_with_top_entity_substitution( Type originalType )
@@ -156,7 +201,6 @@ public static IEnumerable<object[]> SubstitutionNotRequiredData
156201
yield return new object[] { typeof( IEnumerable<string> ) };
157202
yield return new object[] { typeof( IEnumerable<Contact> ) };
158203
yield return new object[] { typeof( ODataValue<IEnumerable<Contact>> ) };
159-
yield return new object[] { typeof( Delta<Contact> ) };
160204
}
161205
}
162206

@@ -166,7 +210,6 @@ public static IEnumerable<object[]> SubstitutionData
166210
{
167211
yield return new object[] { typeof( IEnumerable<Contact> ) };
168212
yield return new object[] { typeof( ODataValue<Contact> ) };
169-
yield return new object[] { typeof( Delta<Contact> ) };
170213
}
171214
}
172215

test/Microsoft.AspNetCore.OData.Versioning.ApiExplorer.Tests/AspNet.OData/DefaultModelTypeBuilderTest.cs

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,51 @@ public void substituted_type_should_be_same_as_original_type( Type originalType
3131
substitutedType.Should().Be( originalType );
3232
}
3333

34+
[Fact]
35+
public void substituted_type_should_be_extracted_from_parent_generic()
36+
{
37+
// arrange
38+
var modelBuilder = new ODataConventionModelBuilder();
39+
40+
modelBuilder.EntitySet<Contact>( "Contacts" );
41+
modelBuilder.EntityType<Address>();
42+
43+
var context = NewContext( modelBuilder.GetEdmModel() );
44+
var originalType = typeof( Delta<Contact> );
45+
46+
// act
47+
var substitutedType = originalType.SubstituteIfNecessary( context );
48+
49+
// assert
50+
substitutedType.Should().Be( typeof( Contact ) );
51+
}
52+
53+
[Fact]
54+
public void type_should_be_match_edm_when_extracted_and_substituted_from_parent_generic()
55+
{
56+
// arrange
57+
var modelBuilder = new ODataConventionModelBuilder();
58+
var contact = modelBuilder.EntitySet<Contact>( "Contacts" ).EntityType;
59+
60+
contact.Ignore( p => p.Email );
61+
contact.Ignore( p => p.Phone );
62+
contact.Ignore( p => p.Addresses );
63+
64+
var context = NewContext( modelBuilder.GetEdmModel() );
65+
var originalType = typeof( Delta<Contact> );
66+
67+
// act
68+
var substitutedType = originalType.SubstituteIfNecessary( context );
69+
70+
// assert
71+
substitutedType.Should().NotBe( originalType );
72+
substitutedType.Should().NotBe( typeof( Contact ) );
73+
substitutedType.GetRuntimeProperties().Should().HaveCount( 3 );
74+
substitutedType.Should().HaveProperty<int>( nameof( Contact.ContactId ) );
75+
substitutedType.Should().HaveProperty<string>( nameof( Contact.FirstName ) );
76+
substitutedType.Should().HaveProperty<string>( nameof( Contact.LastName ) );
77+
}
78+
3479
[Theory]
3580
[MemberData( nameof( SubstitutionData ) )]
3681
public void type_should_match_edm_with_top_entity_substitution( Type originalType )
@@ -156,7 +201,6 @@ public static IEnumerable<object[]> SubstitutionNotRequiredData
156201
yield return new object[] { typeof( IEnumerable<string> ) };
157202
yield return new object[] { typeof( IEnumerable<Contact> ) };
158203
yield return new object[] { typeof( ODataValue<IEnumerable<Contact>> ) };
159-
yield return new object[] { typeof( Delta<Contact> ) };
160204
}
161205
}
162206

@@ -166,7 +210,6 @@ public static IEnumerable<object[]> SubstitutionData
166210
{
167211
yield return new object[] { typeof( IEnumerable<Contact> ) };
168212
yield return new object[] { typeof( ODataValue<Contact> ) };
169-
yield return new object[] { typeof( Delta<Contact> ) };
170213
}
171214
}
172215

0 commit comments

Comments
 (0)