Skip to content

Commit 45072f6

Browse files
Chris Martinezcommonsensesoftware
authored andcommitted
Enables type substitution by API version over an object graph. Fixes #335. Fixes #358.
1 parent 94f73c9 commit 45072f6

File tree

47 files changed

+839
-206
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+839
-206
lines changed

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

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
namespace Microsoft.AspNet.OData
22
{
3-
#if !WEBAPI
4-
using Microsoft.AspNetCore.Mvc.ApiExplorer;
5-
#endif
63
using Microsoft.OData.Edm;
74
using System;
85
using System.Collections.Generic;
@@ -11,21 +8,19 @@
118
using System.Linq;
129
using System.Reflection;
1310
using System.Reflection.Emit;
14-
#if WEBAPI
15-
using System.Web.Http.Dispatcher;
16-
#endif
1711

1812
struct ClassProperty
1913
{
2014
internal readonly string Name;
2115
internal readonly Type Type;
2216

23-
internal ClassProperty( PropertyInfo clrProperty )
17+
internal ClassProperty( PropertyInfo clrProperty, Type propertyType )
2418
{
2519
Contract.Requires( clrProperty != null );
20+
Contract.Requires( propertyType != null );
2621

2722
Name = clrProperty.Name;
28-
Type = clrProperty.PropertyType;
23+
Type = propertyType;
2924
Attributes = AttributesFromProperty( clrProperty );
3025
}
3126

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

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
sealed class ClassSignature : IEquatable<ClassSignature>
1414
{
15-
readonly int hashCode;
15+
readonly Lazy<int> hashCode;
1616

1717
internal ClassSignature( string name, IEnumerable<ClassProperty> properties, ApiVersion apiVersion )
1818
{
@@ -23,18 +23,7 @@ internal ClassSignature( string name, IEnumerable<ClassProperty> properties, Api
2323
Name = name;
2424
Properties = properties.ToArray();
2525
ApiVersion = apiVersion;
26-
27-
if ( Properties.Count == 0 )
28-
{
29-
return;
30-
}
31-
32-
hashCode = Properties[0].GetHashCode();
33-
34-
for ( var i = 1; i < Properties.Count; i++ )
35-
{
36-
hashCode = ( hashCode * 397 ) ^ Properties[i].GetHashCode();
37-
}
26+
hashCode = new Lazy<int>( ComputeHashCode );
3827
}
3928

4029
internal string Name { get; }
@@ -43,10 +32,27 @@ internal ClassSignature( string name, IEnumerable<ClassProperty> properties, Api
4332

4433
internal ApiVersion ApiVersion { get; }
4534

46-
public override int GetHashCode() => hashCode;
35+
public override int GetHashCode() => hashCode.Value;
4736

4837
public override bool Equals( object obj ) => obj is ClassSignature s && Equals( s );
4938

50-
public bool Equals( ClassSignature other ) => hashCode == other?.hashCode;
39+
public bool Equals( ClassSignature other ) => GetHashCode() == other?.GetHashCode();
40+
41+
int ComputeHashCode()
42+
{
43+
if ( Properties.Count == 0 )
44+
{
45+
return 0;
46+
}
47+
48+
var hash = Properties[0].GetHashCode();
49+
50+
for ( var i = 1; i < Properties.Count; i++ )
51+
{
52+
hash = ( hash * 397 ) ^ Properties[i].GetHashCode();
53+
}
54+
55+
return hash;
56+
}
5157
}
5258
}

src/Common.OData.ApiExplorer/AspNet.OData/ModelTypeBuilder.cs renamed to src/Common.OData.ApiExplorer/AspNet.OData/DefaultModelTypeBuilder.cs

Lines changed: 64 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
using Microsoft.Web.Http;
66
#else
77
using Microsoft.AspNetCore.Mvc;
8-
using Microsoft.AspNetCore.Mvc.ApiExplorer;
98
using Microsoft.OData.Edm;
109
#endif
1110
using System;
@@ -22,44 +21,93 @@
2221
using static System.Guid;
2322
using static System.Reflection.Emit.AssemblyBuilderAccess;
2423

25-
sealed class ModelTypeBuilder
24+
/// <summary>
25+
/// Represents the default model type builder.
26+
/// </summary>
27+
public sealed class DefaultModelTypeBuilder : IModelTypeBuilder
2628
{
27-
readonly IEnumerable<Assembly> assemblies;
29+
static readonly Type IEnumerableOfT = typeof( IEnumerable<> );
30+
readonly ICollection<Assembly> assemblies;
2831
readonly ConcurrentDictionary<ApiVersion, ModuleBuilder> modules = new ConcurrentDictionary<ApiVersion, ModuleBuilder>();
2932
readonly ConcurrentDictionary<ClassSignature, Type> generatedTypes = new ConcurrentDictionary<ClassSignature, Type>();
3033

31-
internal ModelTypeBuilder( IEnumerable<Assembly> assemblies ) => this.assemblies = assemblies;
34+
/// <summary>
35+
/// Initializes a new instance of the <see cref="DefaultModelTypeBuilder"/> class.
36+
/// </summary>
37+
/// <param name="assemblies">The <see cref="IEnumerable{T}">sequence</see> of application <see cref="Assembly">assemblies</see>.</param>
38+
public DefaultModelTypeBuilder( IEnumerable<Assembly> assemblies )
39+
{
40+
Arg.NotNull( assemblies, nameof( assemblies ) );
41+
this.assemblies = new HashSet<Assembly>( assemblies );
42+
}
3243

33-
internal Type NewStructuredType( IEdmStructuredType structuredType, Type clrType, ApiVersion apiVersion )
44+
/// <inheritdoc />
45+
public Type NewStructuredType( IEdmStructuredType structuredType, Type clrType, ApiVersion apiVersion )
3446
{
35-
Contract.Requires( structuredType != null );
36-
Contract.Requires( clrType != null );
37-
Contract.Requires( apiVersion != null );
47+
Arg.NotNull( structuredType, nameof( structuredType ) );
48+
Arg.NotNull( clrType, nameof( clrType ) );
49+
Arg.NotNull( apiVersion, nameof( apiVersion ) );
3850
Contract.Ensures( Contract.Result<Type>() != null );
3951

4052
const BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.Instance;
4153

4254
var properties = new List<ClassProperty>();
43-
var structuralProperties = new HashSet<string>( structuredType.StructuralProperties().Select( p => p.Name ), StringComparer.OrdinalIgnoreCase );
55+
var structuralProperties = structuredType.Properties().ToDictionary( p => p.Name, StringComparer.OrdinalIgnoreCase );
56+
var clrTypeMatchesEdmType = true;
4457

4558
foreach ( var property in clrType.GetProperties( bindingFlags ) )
4659
{
47-
if ( structuralProperties.Contains( property.Name ) )
60+
if ( !structuralProperties.TryGetValue( property.Name, out var structuralProperty ) )
61+
{
62+
clrTypeMatchesEdmType = false;
63+
continue;
64+
}
65+
66+
var propertyType = property.PropertyType;
67+
var structuredTypeRef = structuralProperty.Type;
68+
69+
if ( structuredTypeRef.IsCollection() )
70+
{
71+
var collectionType = structuredTypeRef.AsCollection();
72+
var elementType = collectionType.ElementType();
73+
74+
if ( elementType.IsStructured() )
75+
{
76+
assemblies.Add( clrType.Assembly );
77+
78+
var itemType = elementType.Definition.GetClrType( assemblies );
79+
var newItemType = NewStructuredType( elementType.ToStructuredType(), itemType, apiVersion );
80+
81+
if ( !itemType.Equals( newItemType ) )
82+
{
83+
propertyType = IEnumerableOfT.MakeGenericType( newItemType );
84+
}
85+
}
86+
}
87+
else if ( structuredTypeRef.IsStructured() )
4888
{
49-
properties.Add( new ClassProperty( property ) );
89+
propertyType = NewStructuredType( structuredTypeRef.ToStructuredType(), property.PropertyType, apiVersion );
5090
}
91+
92+
clrTypeMatchesEdmType &= property.PropertyType.Equals( propertyType );
93+
properties.Add( new ClassProperty( property, propertyType ) );
5194
}
5295

53-
var name = clrType.FullName;
54-
var signature = new ClassSignature( name, properties, apiVersion );
96+
if ( clrTypeMatchesEdmType )
97+
{
98+
return clrType;
99+
}
100+
101+
var signature = new ClassSignature( clrType.FullName, properties, apiVersion );
55102

56103
return generatedTypes.GetOrAdd( signature, CreateFromSignature );
57104
}
58105

59-
internal Type NewActionParameters( IEdmAction action, ApiVersion apiVersion )
106+
/// <inheritdoc />
107+
public Type NewActionParameters( IEdmAction action, ApiVersion apiVersion )
60108
{
61-
Contract.Requires( action != null );
62-
Contract.Requires( apiVersion != null );
109+
Arg.NotNull( action, nameof( action ) );
110+
Arg.NotNull( apiVersion, nameof( apiVersion ) );
63111
Contract.Ensures( Contract.Result<Type>() != null );
64112

65113
var name = action.FullName() + "Parameters";
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
namespace Microsoft.AspNet.OData
2+
{
3+
#if WEBAPI
4+
using Microsoft.OData.Edm;
5+
using Microsoft.Web.Http;
6+
#else
7+
using Microsoft.AspNetCore.Mvc;
8+
using Microsoft.OData.Edm;
9+
#endif
10+
using System;
11+
using System.Collections.Generic;
12+
13+
/// <summary>
14+
/// Defines the behavior of a model type builder.
15+
/// </summary>
16+
public interface IModelTypeBuilder
17+
{
18+
/// <summary>
19+
/// Creates and returns a new structured type given the specified structured type, CLR type, and API version.
20+
/// </summary>
21+
/// <param name="structuredType">The <see cref="IEdmStructuredType">structured type</see> to evaluate.</param>
22+
/// <param name="clrType">The CLR <see cref="Type">type</see> mapped to the <paramref name="structuredType">structured type</paramref>.</param>
23+
/// <param name="apiVersion">The <see cref="ApiVersion">API version</see> associated with the type mapping.</param>
24+
/// <returns>The original <paramref name="clrType">CLR type</paramref> or a new, dynamically generated substitute <see cref="Type">type</see>
25+
/// that is a subset of the original <paramref name="clrType">CLR type</paramref>, but maps one-to-one with the
26+
/// <paramref name="structuredType">structured type</paramref>.</returns>
27+
/// <remarks>If a substitution is not required, the original <paramref name="clrType">CLR type</paramref> is returned. When a substitution
28+
/// <see cref="Type">type</see> is generated, it is performed only once per <paramref name="apiVersion">API version</paramref>.</remarks>
29+
Type NewStructuredType( IEdmStructuredType structuredType, Type clrType, ApiVersion apiVersion );
30+
31+
/// <summary>
32+
/// Creates an returns a stronly-typed definition for OData action parameters.
33+
/// </summary>
34+
/// <param name="action">The defining <see cref="IEdmAction">action</see>.</param>
35+
/// <param name="apiVersion">The <see cref="ApiVersion">API version</see> of the <paramref name="action"/> to create the parameter type for.</param>
36+
/// <returns>A strong <see cref="Type">type</see> definition for the OData <paramref name="action"/> parameters.</returns>
37+
/// <remarks><see cref="ODataActionParameters">OData action parameters</see> are modeled as a <see cref="Dictionary{TKey,TValue}">dictionary</see>,
38+
/// which is difficult to use effectively by documentation tools such as the API Explorer. The corresponding type is generated only once per
39+
/// <paramref name="apiVersion">API version</paramref>.</remarks>
40+
Type NewActionParameters( IEdmAction action, ApiVersion apiVersion );
41+
}
42+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
namespace Microsoft.AspNet.OData
2+
{
3+
using Microsoft.OData.Edm;
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Diagnostics.Contracts;
7+
using System.Linq;
8+
using System.Reflection;
9+
10+
sealed class StructuredTypeResolver
11+
{
12+
readonly IEdmModel model;
13+
readonly HashSet<Assembly> assemblies;
14+
15+
internal StructuredTypeResolver( IEdmModel model, IEnumerable<Assembly> assemblies )
16+
{
17+
Contract.Requires( model != null );
18+
Contract.Requires( assemblies != null );
19+
20+
this.model = model;
21+
this.assemblies = new HashSet<Assembly>( assemblies );
22+
}
23+
24+
internal IEdmStructuredType GetStructuredType( Type type )
25+
{
26+
Contract.Requires( type != null );
27+
28+
var structuredTypes = model.SchemaElements.OfType<IEdmStructuredType>();
29+
var structuredType = structuredTypes.FirstOrDefault( t => type.Equals( t.GetClrType( assemblies ) ) );
30+
31+
if ( structuredType == null && assemblies.Add( type.Assembly ) )
32+
{
33+
structuredType = structuredTypes.FirstOrDefault( t => type.Equals( t.GetClrType( assemblies ) ) );
34+
}
35+
36+
return structuredType;
37+
}
38+
}
39+
}

0 commit comments

Comments
 (0)