Skip to content

Commit 2686fb6

Browse files
author
Chris Martinez
committed
Update ModelMetadata with substituted type when necessary. Fixes #562.
1 parent 8c0568f commit 2686fb6

File tree

8 files changed

+126
-43
lines changed

8 files changed

+126
-43
lines changed

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@
55
using System;
66
using System.Collections.Generic;
77
using System.ComponentModel.DataAnnotations;
8+
using System.Diagnostics;
89
using System.Linq;
910
using System.Reflection;
1011
using System.Reflection.Emit;
1112

13+
[DebuggerDisplay( "{Name,nq}" )]
1214
readonly struct ClassProperty
1315
{
1416
internal readonly Type Type;

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

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,25 +7,30 @@
77
#endif
88
using System;
99
using System.Collections.Generic;
10+
using System.Diagnostics;
1011
using System.Linq;
1112
using System.Reflection;
1213
using System.Reflection.Emit;
1314

15+
[DebuggerDisplay( "{Name,nq} ({ApiVersion,nq})" )]
1416
sealed class ClassSignature : IEquatable<ClassSignature>
1517
{
18+
static readonly ConstructorInfo newOriginalType = typeof( OriginalTypeAttribute ).GetConstructors()[0];
1619
#if WEBAPI
1720
static readonly CustomAttributeBuilder[] NoAttributes = new CustomAttributeBuilder[0];
1821
#else
1922
static readonly CustomAttributeBuilder[] NoAttributes = Array.Empty<CustomAttributeBuilder>();
2023
#endif
21-
static readonly ConstructorInfo newOriginalType = typeof( OriginalTypeAttribute ).GetConstructors()[0];
2224
readonly Lazy<int> hashCode;
2325

2426
internal ClassSignature( Type originalType, IEnumerable<ClassProperty> properties, ApiVersion apiVersion )
2527
{
26-
var attributeBuilders = new List<CustomAttributeBuilder>( originalType.DeclaredAttributes() );
28+
var attributeBuilders = new List<CustomAttributeBuilder>
29+
{
30+
new CustomAttributeBuilder( newOriginalType, new object[] { originalType } ),
31+
};
2732

28-
attributeBuilders.Insert( 0, new CustomAttributeBuilder( newOriginalType, new object[] { originalType } ) );
33+
attributeBuilders.AddRange( originalType.DeclaredAttributes() );
2934

3035
Name = originalType.FullName;
3136
Attributes = attributeBuilders.ToArray();

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

Lines changed: 78 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
using System;
1111
using System.Collections.Concurrent;
1212
using System.Collections.Generic;
13-
using System.Diagnostics.Contracts;
1413
using System.Linq;
1514
using System.Reflection;
1615
using System.Reflection.Emit;
@@ -19,6 +18,7 @@
1918
#endif
2019
using static System.Globalization.CultureInfo;
2120
using static System.Guid;
21+
using static System.Reflection.BindingFlags;
2222
using static System.Reflection.Emit.AssemblyBuilderAccess;
2323

2424
/// <summary>
@@ -69,45 +69,80 @@ IDictionary<EdmTypeKey, TypeInfo> GenerateTypesForEdmModel( IEdmModel model, Api
6969
return ResolveDependencies( context );
7070
}
7171

72+
static void MapEdmPropertiesToClrProperties(
73+
IEdmModel edmModel,
74+
IEdmStructuredType edmType,
75+
Dictionary<string, IEdmProperty> structuralProperties,
76+
Dictionary<PropertyInfo, IEdmProperty> mappedClrProperties )
77+
{
78+
foreach ( var edmProperty in edmType.Properties() )
79+
{
80+
structuralProperties.Add( edmProperty.Name, edmProperty );
81+
82+
var clrProperty = edmModel.GetAnnotationValue<ClrPropertyInfoAnnotation>( edmProperty )?.ClrPropertyInfo;
83+
84+
if ( clrProperty != null )
85+
{
86+
mappedClrProperties.Add( clrProperty, edmProperty );
87+
}
88+
}
89+
}
90+
7291
static Type GenerateTypeIfNeeded( IEdmStructuredType structuredType, BuilderContext context )
7392
{
74-
var apiVersion = context.ApiVersion;
75-
var edmTypes = context.EdmTypes;
76-
var typeKey = new EdmTypeKey( structuredType, apiVersion );
93+
var typeKey = new EdmTypeKey( structuredType, context.ApiVersion );
7794

78-
if ( edmTypes.TryGetValue( typeKey, out var generatedType ) )
95+
if ( context.EdmTypes.TryGetValue( typeKey, out var generatedType ) )
7996
{
8097
return generatedType;
8198
}
8299

83-
var edmModel = context.EdmModel;
84-
var clrType = structuredType.GetClrType( edmModel )!;
100+
var clrType = structuredType.GetClrType( context.EdmModel )!;
85101
var visitedEdmTypes = context.VisitedEdmTypes;
86102

87103
visitedEdmTypes.Add( typeKey );
88104

89-
const BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.Instance;
90-
91105
var properties = new List<ClassProperty>();
92106
var structuralProperties = new Dictionary<string, IEdmProperty>( StringComparer.OrdinalIgnoreCase );
93107
var mappedClrProperties = new Dictionary<PropertyInfo, IEdmProperty>();
94-
var clrTypeMatchesEdmType = true;
95-
var hasUnfinishedTypes = false;
96108
var dependentProperties = new List<PropertyDependency>();
97109

98-
foreach ( var property in structuredType.Properties() )
99-
{
100-
structuralProperties.Add( property.Name, property );
101-
102-
var clrProperty = edmModel.GetAnnotationValue<ClrPropertyInfoAnnotation>( property )?.ClrPropertyInfo;
110+
MapEdmPropertiesToClrProperties( context.EdmModel, structuredType, structuralProperties, mappedClrProperties );
111+
112+
var (clrTypeMatchesEdmType, hasUnfinishedTypes) =
113+
BuildSignatureProperties(
114+
clrType,
115+
structuralProperties,
116+
mappedClrProperties,
117+
properties,
118+
dependentProperties,
119+
context );
120+
121+
return ResolveType(
122+
typeKey,
123+
clrType,
124+
clrTypeMatchesEdmType,
125+
hasUnfinishedTypes,
126+
properties,
127+
dependentProperties,
128+
context );
129+
}
103130

104-
if ( clrProperty != null )
105-
{
106-
mappedClrProperties.Add( clrProperty, property );
107-
}
108-
}
131+
static Tuple<bool, bool> BuildSignatureProperties(
132+
Type clrType,
133+
IReadOnlyDictionary<string, IEdmProperty> structuralProperties,
134+
IReadOnlyDictionary<PropertyInfo, IEdmProperty> mappedClrProperties,
135+
List<ClassProperty> properties,
136+
List<PropertyDependency> dependentProperties,
137+
BuilderContext context )
138+
{
139+
var edmModel = context.EdmModel;
140+
var apiVersion = context.ApiVersion;
141+
var visitedEdmTypes = context.VisitedEdmTypes;
142+
var clrTypeMatchesEdmType = true;
143+
var hasUnfinishedTypes = false;
109144

110-
foreach ( var property in clrType.GetProperties( bindingFlags ) )
145+
foreach ( var property in clrType.GetProperties( Public | Instance ) )
111146
{
112147
if ( !structuralProperties.TryGetValue( property.Name, out var structuralProperty ) &&
113148
!mappedClrProperties.TryGetValue( property, out structuralProperty ) )
@@ -178,6 +213,21 @@ static Type GenerateTypeIfNeeded( IEdmStructuredType structuredType, BuilderCont
178213
properties.Add( new ClassProperty( property, propertyType ) );
179214
}
180215

216+
return Tuple.Create( clrTypeMatchesEdmType, hasUnfinishedTypes );
217+
}
218+
219+
static TypeInfo ResolveType(
220+
EdmTypeKey typeKey,
221+
Type clrType,
222+
bool clrTypeMatchesEdmType,
223+
bool hasUnfinishedTypes,
224+
List<ClassProperty> properties,
225+
List<PropertyDependency> dependentProperties,
226+
BuilderContext context )
227+
{
228+
var apiVersion = context.ApiVersion;
229+
var edmTypes = context.EdmTypes;
230+
181231
TypeInfo type;
182232

183233
if ( clrTypeMatchesEdmType )
@@ -240,6 +290,7 @@ static TypeBuilder CreateTypeBuilderFromSignature( ModuleBuilder moduleBuilder,
240290
ref var property = ref properties[i];
241291
var type = property.Type;
242292
var name = property.Name;
293+
243294
AddProperty( typeBuilder, type, name, property.Attributes );
244295
}
245296

@@ -253,15 +304,15 @@ static IDictionary<EdmTypeKey, TypeInfo> ResolveDependencies( BuilderContext con
253304

254305
for ( var i = 0; i < dependencies.Count; i++ )
255306
{
256-
var propertyDependency = dependencies[i];
257-
var dependentOnType = edmTypes[propertyDependency.DependentOnTypeKey];
307+
var dependency = dependencies[i];
308+
var dependentOnType = edmTypes[dependency.DependentOnTypeKey];
258309

259-
if ( propertyDependency.IsCollection )
310+
if ( dependency.IsCollection )
260311
{
261312
dependentOnType = IEnumerableOfT.MakeGenericType( dependentOnType ).GetTypeInfo();
262313
}
263314

264-
AddProperty( propertyDependency.DependentType!, dependentOnType, propertyDependency.PropertyName, propertyDependency.CustomAttributes );
315+
AddProperty( dependency.DependentType!, dependentOnType, dependency.PropertyName, dependency.CustomAttributes );
265316
}
266317

267318
var keys = edmTypes.Keys.ToArray();
@@ -338,7 +389,7 @@ internal BuilderContext( IEdmModel edmModel, ApiVersion apiVersion, Func<ModuleB
338389

339390
internal IDictionary<EdmTypeKey, TypeInfo> EdmTypes { get; } = new Dictionary<EdmTypeKey, TypeInfo>();
340391

341-
internal ICollection<EdmTypeKey> VisitedEdmTypes { get; } = new HashSet<EdmTypeKey>();
392+
internal ISet<EdmTypeKey> VisitedEdmTypes { get; } = new HashSet<EdmTypeKey>();
342393

343394
internal IList<PropertyDependency> Dependencies { get; } = new List<PropertyDependency>();
344395
}

src/Microsoft.AspNet.WebApi.Versioning/TupleExtensions.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
namespace Microsoft.Web.Http
22
{
33
using System;
4-
using System.Diagnostics.Contracts;
54

65
static class TupleExtensions
76
{
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
namespace Microsoft.AspNetCore.Mvc.ApiExplorer
2+
{
3+
using Microsoft.AspNetCore.Mvc.ModelBinding;
4+
using System;
5+
6+
internal static class ModelMetadataExtensions
7+
{
8+
internal static ModelMetadata SubstituteIfNecessary( this ModelMetadata modelMetadata, Type type )
9+
{
10+
if ( type.Equals( modelMetadata.ModelType ) )
11+
{
12+
return modelMetadata;
13+
}
14+
15+
return new SubstitutedModelMetadata( modelMetadata, type );
16+
}
17+
}
18+
}

src/Microsoft.AspNetCore.OData.Versioning.ApiExplorer/AspNetCore.Mvc.ApiExplorer/ODataApiDescriptionProvider.cs

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -707,22 +707,29 @@ IReadOnlyList<ApiResponseType> GetApiResponseTypes( IReadOnlyList<IApiResponseMe
707707

708708
foreach ( var objectType in objectTypes )
709709
{
710+
Type type;
711+
710712
if ( objectType.Value == typeof( void ) )
711713
{
714+
type = objectType.Value.SubstituteIfNecessary( new TypeSubstitutionContext( serviceProvider, ModelTypeBuilder ) );
715+
712716
results.Add( new ApiResponseType()
713717
{
714718
StatusCode = objectType.Key,
715-
Type = objectType.Value.SubstituteIfNecessary( new TypeSubstitutionContext( serviceProvider, ModelTypeBuilder ) ),
719+
Type = type,
720+
ModelMetadata = MetadataProvider.GetMetadataForType( objectType.Value ).SubstituteIfNecessary( type ),
716721
} );
717722

718723
continue;
719724
}
720725

726+
type = objectType.Value.SubstituteIfNecessary( new TypeSubstitutionContext( serviceProvider, ModelTypeBuilder ) );
727+
721728
var apiResponseType = new ApiResponseType()
722729
{
723730
StatusCode = objectType.Key,
724-
Type = objectType.Value.SubstituteIfNecessary( new TypeSubstitutionContext( serviceProvider, ModelTypeBuilder ) ),
725-
ModelMetadata = MetadataProvider.GetMetadataForType( objectType.Value ),
731+
Type = type,
732+
ModelMetadata = MetadataProvider.GetMetadataForType( objectType.Value ).SubstituteIfNecessary( type ),
726733
};
727734

728735
for ( var i = 0; i < contentTypes.Count; i++ )
@@ -779,13 +786,16 @@ void ProcessRouteParameters( ApiParameterContext context )
779786
var routeTemplate = TemplateParser.Parse( prefix );
780787
var routeParameters = new Dictionary<string, ApiParameterRouteInfo>( StringComparer.OrdinalIgnoreCase );
781788

782-
foreach ( var routeParameter in routeTemplate.Parameters )
789+
for ( var i = 0; i < routeTemplate.Parameters.Count; i++ )
783790
{
791+
var routeParameter = routeTemplate.Parameters[i];
784792
routeParameters.Add( routeParameter.Name, CreateRouteInfo( routeParameter ) );
785793
}
786794

787-
foreach ( var parameter in context.Results )
795+
for ( var i = 0; i < context.Results.Count; i++ )
788796
{
797+
var parameter = context.Results[i];
798+
789799
if ( parameter.Source == Path || parameter.Source == ModelBinding || parameter.Source == Custom )
790800
{
791801
if ( routeParameters.TryGetValue( parameter.Name, out var routeInfo ) )

src/Microsoft.AspNetCore.OData.Versioning.ApiExplorer/AspNetCore.Mvc.ApiExplorer/PseudoModelBindingVisitor.cs

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -87,29 +87,25 @@ ApiParameterDescription CreateResult( ApiParameterDescriptionContext bindingCont
8787
{
8888
var action = (IEdmAction) Context.RouteContext.Operation;
8989
var apiVersion = Context.RouteContext.ApiVersion;
90+
9091
type = Context.TypeBuilder.NewActionParameters( Context.Services, action, apiVersion, Context.RouteContext.ActionDescriptor.ControllerName );
9192
}
9293
else
9394
{
9495
type = type.SubstituteIfNecessary( new TypeSubstitutionContext( Context.Services, Context.TypeBuilder ) );
9596
}
9697

97-
if ( !type.Equals( modelMetadata.ModelType ) )
98-
{
99-
modelMetadata = new SubstitutedModelMetadata( modelMetadata, type );
100-
}
101-
10298
return new ApiParameterDescription()
10399
{
104-
ModelMetadata = modelMetadata,
100+
ModelMetadata = modelMetadata.SubstituteIfNecessary( type ),
105101
Name = GetName( containerName, bindingContext ),
106102
Source = source,
107103
Type = type,
108104
ParameterDescriptor = Parameter,
109105
};
110106
}
111107

112-
struct PropertyKey
108+
readonly struct PropertyKey
113109
{
114110
public readonly Type ContainerType;
115111
public readonly string PropertyName;

src/Microsoft.AspNetCore.OData.Versioning.ApiExplorer/AspNetCore.Mvc.ApiExplorer/SubstitutedModelMetadata.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
using System;
66
using System.Collections.Generic;
77

8+
#pragma warning disable CA1812
9+
810
sealed class SubstitutedModelMetadata : ModelMetadata
911
{
1012
readonly ModelMetadata inner;

0 commit comments

Comments
 (0)