Skip to content

Commit 71d4ab3

Browse files
Chris Martinezcommonsensesoftware
authored andcommitted
Add ad hoc annotation and resolve annotations via extension method
1 parent 756f21b commit 71d4ab3

File tree

13 files changed

+113
-44
lines changed

13 files changed

+113
-44
lines changed

src/AspNet/OData/src/Asp.Versioning.WebApi.OData/OData/EdmModelSelector.cs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -130,16 +130,12 @@ public EdmModelSelector( IEnumerable<IEdmModel> models, IApiVersionSelector apiV
130130

131131
private static void AddVersionFromModel( IEdmModel model, IList<ApiVersion> versions, IDictionary<ApiVersion, IEdmModel> collection )
132132
{
133-
var annotation = model.GetAnnotationValue<ApiVersionAnnotation>( model );
134-
135-
if ( annotation == null )
133+
if ( model.GetApiVersion() is not ApiVersion version )
136134
{
137135
var message = string.Format( CultureInfo.CurrentCulture, SR.MissingAnnotation, typeof( ApiVersionAnnotation ).Name );
138136
throw new ArgumentException( message );
139137
}
140138

141-
var version = annotation.ApiVersion;
142-
143139
collection.Add( version, model );
144140
versions.Add( version );
145141
}

src/AspNet/OData/test/Asp.Versioning.WebApi.OData.Tests/OData/VersionedODataModelBuilderTest.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ public void get_edm_models_should_return_expected_results()
3434
var model = builder.GetEdmModels().Single();
3535

3636
// assert
37-
model.GetAnnotationValue<ApiVersionAnnotation>( model ).ApiVersion.Should().Be( apiVersion );
37+
model.GetApiVersion().Should().Be( apiVersion );
3838
modelCreated.Verify( f => f( It.IsAny<ODataModelBuilder>(), model ), Times.Once() );
3939
}
4040

src/AspNetCore/OData/src/Asp.Versioning.OData.ApiExplorer/ApiExplorer/ODataApiDescriptionProvider.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ private static bool TryMatchModelVersion(
221221
for ( var i = 0; i < items.Count; i++ )
222222
{
223223
var item = items[i];
224-
var otherApiVersion = item.Model.GetAnnotationValue<ApiVersionAnnotation>( item.Model )?.ApiVersion;
224+
var otherApiVersion = item.Model.GetApiVersion();
225225

226226
if ( apiVersion.Equals( otherApiVersion ) )
227227
{

src/AspNetCore/OData/src/Asp.Versioning.OData.ApiExplorer/Conventions/ODataQueryOptionDescriptionContext.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ public partial class ODataQueryOptionDescriptionContext
3434
foreach ( var item in items )
3535
{
3636
var model = item.Model;
37-
var otherVersion = model.GetAnnotationValue<ApiVersionAnnotation>( model )?.ApiVersion;
37+
var otherVersion = model.GetApiVersion();
3838

3939
if ( version.Equals( otherVersion ) )
4040
{

src/AspNetCore/OData/src/Asp.Versioning.OData/OData/ODataMultiModelApplicationModelProvider.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,11 @@ private void AddRouteComponents(
152152
for ( var i = 0; i < models.Count; i++ )
153153
{
154154
var model = models[i];
155-
var version = model.GetAnnotationValue<ApiVersionAnnotation>( model ).ApiVersion;
155+
156+
if ( model.GetApiVersion() is not ApiVersion version )
157+
{
158+
continue;
159+
}
156160

157161
if ( !mappings.TryGetValue( version, out var options ) )
158162
{

src/AspNetCore/OData/src/Asp.Versioning.OData/OData/VersionedODataTemplateTranslator.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public sealed class VersionedODataTemplateTranslator : IODataTemplateTranslator
3939
else
4040
{
4141
var model = context.Model;
42-
var otherApiVersion = model.GetAnnotationValue<ApiVersionAnnotation>( model )?.ApiVersion;
42+
var otherApiVersion = model.GetApiVersion();
4343

4444
// HACK: a version-neutral endpoint can fail to match here because odata tries to match the
4545
// first endpoint metadata when there could be multiple. such an endpoint is expected to be

src/AspNetCore/OData/src/Asp.Versioning.OData/Routing/VersionedAttributeRoutingConvention.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ public override bool AppliesToAction( ODataControllerActionContext context )
5555
return false;
5656
}
5757

58-
var apiVersion = edm.GetAnnotationValue<ApiVersionAnnotation>( edm )?.ApiVersion;
58+
var apiVersion = edm.GetApiVersion();
5959

6060
if ( apiVersion == null || !metadata.IsMappedTo( apiVersion ) )
6161
{

src/AspNetCore/OData/test/Asp.Versioning.OData.Tests/OData/VersionedODataModelBuilderTest.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ public void get_edm_models_should_return_expected_results()
2929
var model = builder.GetEdmModels().Single();
3030

3131
// assert
32-
model.GetAnnotationValue<ApiVersionAnnotation>( model ).ApiVersion.Should().Be( apiVersion );
32+
model.GetApiVersion().Should().Be( apiVersion );
3333
modelCreated.Verify( f => f( It.IsAny<ODataModelBuilder>(), model ), Once() );
3434
}
3535

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

Lines changed: 42 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,19 @@ namespace Asp.Versioning.OData;
2525
public sealed class DefaultModelTypeBuilder : IModelTypeBuilder
2626
{
2727
private static Type? ienumerableOfT;
28+
private readonly bool adHoc;
29+
private DefaultModelTypeBuilder? adHocBuilder;
2830
private ConcurrentDictionary<ApiVersion, ModuleBuilder>? modules;
2931
private ConcurrentDictionary<ApiVersion, IDictionary<EdmTypeKey, Type>>? generatedEdmTypesPerVersion;
3032
private ConcurrentDictionary<ApiVersion, ConcurrentDictionary<EdmTypeKey, Type>>? generatedActionParamsPerVersion;
3133

34+
private DefaultModelTypeBuilder( bool adHoc ) => this.adHoc = adHoc;
35+
36+
/// <summary>
37+
/// Initializes a new instance of the <see cref="DefaultModelTypeBuilder"/> class.
38+
/// </summary>
39+
public DefaultModelTypeBuilder() { }
40+
3241
/// <inheritdoc />
3342
public Type NewStructuredType( IEdmModel model, IEdmStructuredType structuredType, Type clrType, ApiVersion apiVersion )
3443
{
@@ -37,6 +46,12 @@ public Type NewStructuredType( IEdmModel model, IEdmStructuredType structuredTyp
3746
throw new ArgumentNullException( nameof( model ) );
3847
}
3948

49+
if ( !adHoc && model.IsAdHoc() )
50+
{
51+
adHocBuilder ??= new( adHoc: true );
52+
return adHocBuilder.NewStructuredType( model, structuredType, clrType, apiVersion );
53+
}
54+
4055
if ( structuredType == null )
4156
{
4257
throw new ArgumentNullException( nameof( structuredType ) );
@@ -54,10 +69,9 @@ public Type NewStructuredType( IEdmModel model, IEdmStructuredType structuredTyp
5469

5570
generatedEdmTypesPerVersion ??= new();
5671

57-
var typeKey = new EdmTypeKey( structuredType, apiVersion );
5872
var edmTypes = generatedEdmTypesPerVersion.GetOrAdd( apiVersion, key => GenerateTypesForEdmModel( model, key ) );
5973

60-
return edmTypes[typeKey];
74+
return edmTypes[new( structuredType, apiVersion )];
6175
}
6276

6377
/// <inheritdoc />
@@ -68,6 +82,12 @@ public Type NewActionParameters( IEdmModel model, IEdmAction action, string cont
6882
throw new ArgumentNullException( nameof( model ) );
6983
}
7084

85+
if ( !adHoc && model.IsAdHoc() )
86+
{
87+
adHocBuilder ??= new( adHoc: true );
88+
return adHocBuilder.NewActionParameters( model, action, controllerName, apiVersion );
89+
}
90+
7191
if ( action == null )
7292
{
7393
throw new ArgumentNullException( nameof( action ) );
@@ -85,7 +105,7 @@ public Type NewActionParameters( IEdmModel model, IEdmAction action, string cont
85105

86106
generatedActionParamsPerVersion ??= new();
87107

88-
var paramTypes = generatedActionParamsPerVersion.GetOrAdd( apiVersion, _ => new ConcurrentDictionary<EdmTypeKey, Type>() );
108+
var paramTypes = generatedActionParamsPerVersion.GetOrAdd( apiVersion, _ => new() );
89109
var fullTypeName = $"{controllerName}.{action.Namespace}.{controllerName}{action.Name}Parameters";
90110
var key = new EdmTypeKey( fullTypeName, apiVersion );
91111
var type = paramTypes.GetOrAdd( key, _ =>
@@ -322,8 +342,7 @@ private static Type ResolveType(
322342
}
323343

324344
[MethodImpl( MethodImplOptions.AggressiveInlining )]
325-
private static Type MakeEnumerable( Type itemType ) =>
326-
( ienumerableOfT ??= typeof( IEnumerable<> ) ).MakeGenericType( itemType );
345+
private static Type MakeEnumerable( Type itemType ) => ( ienumerableOfT ??= typeof( IEnumerable<> ) ).MakeGenericType( itemType );
327346

328347
[MethodImpl( MethodImplOptions.AggressiveInlining )]
329348
private static Type CreateTypeFromSignature( ModuleBuilder moduleBuilder, ClassSignature @class ) =>
@@ -389,7 +408,11 @@ private static IDictionary<EdmTypeKey, Type> ResolveDependencies( BuilderContext
389408
return edmTypes;
390409
}
391410

392-
private static PropertyBuilder AddProperty( TypeBuilder addTo, Type shouldBeAdded, string name, IReadOnlyList<CustomAttributeBuilder> customAttributes )
411+
private static PropertyBuilder AddProperty(
412+
TypeBuilder addTo,
413+
Type shouldBeAdded,
414+
string name,
415+
IReadOnlyList<CustomAttributeBuilder> customAttributes )
393416
{
394417
const MethodAttributes propertyMethodAttributes = MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig;
395418
var field = addTo.DefineField( "_" + name, shouldBeAdded, FieldAttributes.Private );
@@ -418,7 +441,7 @@ private static PropertyBuilder AddProperty( TypeBuilder addTo, Type shouldBeAdde
418441
return propertyBuilder;
419442
}
420443

421-
private static AssemblyName NewAssemblyName(ApiVersion apiVersion)
444+
private static AssemblyName NewAssemblyName( ApiVersion apiVersion, bool adHoc )
422445
{
423446
// this is not strictly necessary, but it makes debugging a bit easier as each
424447
// assembly-qualified type name provides visibility as to which api version a
@@ -455,20 +478,26 @@ private static AssemblyName NewAssemblyName(ApiVersion apiVersion)
455478
}
456479

457480
name.Insert( 0, 'V' )
458-
.Append( NewGuid().ToString( "n", InvariantCulture ) )
459-
.Append( ".DynamicModels" );
481+
.Append( NewGuid().ToString( "n", InvariantCulture ) );
482+
483+
if ( adHoc )
484+
{
485+
name.Append( ".AdHoc" );
486+
}
487+
488+
name.Append( ".DynamicModels" );
460489

461490
return new( name.ToString() );
462491
}
463492

464493
[MethodImpl( MethodImplOptions.AggressiveInlining )]
465-
private static ModuleBuilder CreateModuleForApiVersion( ApiVersion apiVersion )
494+
private ModuleBuilder CreateModuleForApiVersion( ApiVersion apiVersion )
466495
{
467-
var name = NewAssemblyName( apiVersion );
496+
var assemblyName = NewAssemblyName( apiVersion, adHoc );
468497
#if NETFRAMEWORK
469-
var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly( name, Run );
498+
var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly( assemblyName, Run );
470499
#else
471-
var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly( name, Run );
500+
var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly( assemblyName, Run );
472501
#endif
473502
return assemblyBuilder.DefineDynamicModule( "<module>" );
474503
}

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

Lines changed: 15 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@
33
namespace Asp.Versioning.OData;
44

55
#if NETFRAMEWORK
6+
using Microsoft.OData.Edm;
67
using System.Net.Http;
78
using System.Web.Http;
89
#else
910
using Microsoft.AspNetCore.Mvc;
1011
using Microsoft.AspNetCore.OData.Results;
12+
using Microsoft.OData.Edm;
1113
#endif
1214
using System.Reflection;
1315
using System.Reflection.Emit;
@@ -54,12 +56,11 @@ public static Type SubstituteIfNecessary( this Type type, TypeSubstitutionContex
5456
var openTypes = new Stack<Type>();
5557
var apiVersion = context.ApiVersion;
5658
var resolver = new StructuredTypeResolver( context.Model );
59+
IEdmStructuredType? structuredType;
5760

5861
if ( IsSubstitutableGeneric( type, openTypes, out var innerType ) )
5962
{
60-
var structuredType = resolver.GetStructuredType( innerType! );
61-
62-
if ( structuredType == null )
63+
if ( ( structuredType = resolver.GetStructuredType( innerType! ) ) == null )
6364
{
6465
return type;
6566
}
@@ -74,14 +75,9 @@ public static Type SubstituteIfNecessary( this Type type, TypeSubstitutionContex
7475
return CloseGeneric( openTypes, newType );
7576
}
7677

77-
if ( CanBeSubstituted( type ) )
78+
if ( CanBeSubstituted( type ) && ( structuredType = resolver.GetStructuredType( type ) ) != null )
7879
{
79-
var structuredType = resolver.GetStructuredType( type );
80-
81-
if ( structuredType != null )
82-
{
83-
type = context.ModelTypeBuilder.NewStructuredType( context.Model, structuredType, type, apiVersion );
84-
}
80+
type = context.ModelTypeBuilder.NewStructuredType( context.Model, structuredType, type, apiVersion );
8581
}
8682

8783
return type;
@@ -242,16 +238,15 @@ private static Type CloseGeneric( Stack<Type> openTypes, Type innerType )
242238
return type;
243239
}
244240

245-
private static bool CanBeSubstituted( Type type )
246-
{
247-
return Type.GetTypeCode( type ) == TypeCode.Object &&
248-
!type.IsValueType &&
249-
!type.Equals( ActionResultType ) &&
241+
[MethodImpl( MethodImplOptions.AggressiveInlining )]
242+
private static bool CanBeSubstituted( Type type ) =>
243+
Type.GetTypeCode( type ) == TypeCode.Object &&
244+
!type.IsValueType &&
245+
!type.Equals( ActionResultType ) &&
250246
#if NETFRAMEWORK
251-
!type.Equals( HttpResponseType ) &&
247+
!type.Equals( HttpResponseType ) &&
252248
#endif
253-
!type.IsODataActionParameters();
254-
}
249+
!type.IsODataActionParameters();
255250

256251
internal static bool IsEnumerable(
257252
this Type type,
@@ -295,6 +290,7 @@ internal static bool IsEnumerable(
295290
return false;
296291
}
297292

293+
[MethodImpl( MethodImplOptions.AggressiveInlining )]
298294
private static bool IsSingleResult( this Type type ) => type.Is( SingleResultOfT );
299295

300296
private static bool IsODataValue( this Type? type )
@@ -323,6 +319,7 @@ private static bool IsODataValue( this Type? type )
323319
private static bool Is( this Type type, Type typeDefinition ) =>
324320
type.IsGenericType && type.GetGenericTypeDefinition().Equals( typeDefinition );
325321

322+
[MethodImpl( MethodImplOptions.AggressiveInlining )]
326323
private static bool ShouldExtractInnerType( this Type type ) =>
327324
type.IsDelta() ||
328325
#if !NETFRAMEWORK

src/Common/src/Common.OData.ApiExplorer/OData/TypeSubstitutionContext.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ public class TypeSubstitutionContext
3636
/// Gets API version associated with the source model.
3737
/// </summary>
3838
/// <value>The associated <see cref="ApiVersion">API version</see>.</value>
39-
public ApiVersion ApiVersion => apiVersion ??= Model.GetAnnotationValue<ApiVersionAnnotation>( Model )?.ApiVersion ?? ApiVersion.Neutral;
39+
public ApiVersion ApiVersion => apiVersion ??= Model.GetApiVersion() ?? ApiVersion.Neutral;
4040

4141
/// <summary>
4242
/// Gets the model type builder used to create substitution types.
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Copyright (c) .NET Foundation and contributors. All rights reserved.
2+
3+
namespace Microsoft.OData.Edm;
4+
5+
using Asp.Versioning;
6+
using Asp.Versioning.OData;
7+
8+
/// <summary>
9+
/// Provides extension methods for <see cref="IEdmModel"/>.
10+
/// </summary>
11+
public static class IEdmModelExtensions
12+
{
13+
/// <summary>
14+
/// Gets the API version associated with the Entity Data Model (EDM).
15+
/// </summary>
16+
/// <param name="model">The extended <see cref="IEdmModel">EDM</see>.</param>
17+
/// <returns>The associated <see cref="ApiVersion">API version</see> or <c>null</c>.</returns>
18+
public static ApiVersion? GetApiVersion( this IEdmModel model ) =>
19+
model.GetAnnotationValue<ApiVersionAnnotation>( model )?.ApiVersion;
20+
21+
/// <summary>
22+
/// Gets a value indicating whether the Entity Data Model (EDM) is for defined ad hoc usage.
23+
/// </summary>
24+
/// <param name="model">The extended <see cref="IEdmModel">EDM</see>.</param>
25+
/// <returns>True if the EDM is defined for ad hoc usage; otherwise, false.</returns>
26+
public static bool IsAdHoc( this IEdmModel model ) =>
27+
model.GetAnnotationValue<AdHocAnnotation>( model ) is not null;
28+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Copyright (c) .NET Foundation and contributors. All rights reserved.
2+
3+
namespace Asp.Versioning.OData;
4+
5+
/// <summary>
6+
/// Represents an annotation for ad hoc usage.
7+
/// </summary>
8+
public sealed class AdHocAnnotation
9+
{
10+
/// <summary>
11+
/// Gets a singleton instance of the annotation.
12+
/// </summary>
13+
/// <value>A singleton <see cref="AdHocAnnotation">annotation</see> instance.</value>
14+
public static AdHocAnnotation Instance { get; } = new();
15+
}

0 commit comments

Comments
 (0)