Skip to content

Commit 35234f1

Browse files
LuukN2commonsensesoftware
authored andcommitted
Add support for the substitution of ODataActionParameter parameters.
1 parent 26c1c94 commit 35234f1

File tree

7 files changed

+103
-13
lines changed

7 files changed

+103
-13
lines changed

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

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,22 +24,27 @@ internal ClassProperty( PropertyInfo clrProperty, Type propertyType )
2424
Attributes = AttributesFromProperty( clrProperty );
2525
}
2626

27-
internal ClassProperty( IEnumerable<Assembly> assemblies, IEdmOperationParameter parameter )
27+
internal ClassProperty( IServiceProvider services, IEnumerable<Assembly> assemblies, IEdmOperationParameter parameter, IModelTypeBuilder typeBuilder )
2828
{
2929
Contract.Requires( assemblies != null );
3030
Contract.Requires( parameter != null );
3131

3232
Name = parameter.Name;
33+
var context = new TypeSubstitutionContext( services, assemblies, typeBuilder );
3334

3435
if ( parameter.Type.IsCollection() )
3536
{
3637
var collectionType = parameter.Type.AsCollection();
3738
var elementType = collectionType.ElementType().Definition.GetClrType( assemblies );
38-
type = typeof( IEnumerable<> ).MakeGenericType( elementType );
39+
var substitutedType = elementType.SubstituteIfNecessary( context );
40+
41+
type = typeof( IEnumerable<> ).MakeGenericType( substitutedType );
3942
}
4043
else
4144
{
42-
type = parameter.Type.Definition.GetClrType( assemblies );
45+
var parameterType = parameter.Type.Definition.GetClrType( assemblies );
46+
47+
type = parameterType.SubstituteIfNecessary( context );
4348
}
4449

4550
Attributes = AttributesFromOperationParameter( parameter );

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -171,14 +171,14 @@ public Type NewStructuredType( IEdmStructuredType structuredType, Type clrType,
171171
}
172172

173173
/// <inheritdoc />
174-
public Type NewActionParameters( IEdmAction action, ApiVersion apiVersion )
174+
public Type NewActionParameters( IServiceProvider services, IEdmAction action, ApiVersion apiVersion )
175175
{
176176
Arg.NotNull( action, nameof( action ) );
177177
Arg.NotNull( apiVersion, nameof( apiVersion ) );
178178
Contract.Ensures( Contract.Result<Type>() != null );
179179

180180
var name = action.FullName() + "Parameters";
181-
var properties = action.Parameters.Where( p => p.Name != "bindingParameter" ).Select( p => new ClassProperty( assemblies, p ) );
181+
var properties = action.Parameters.Where( p => p.Name != "bindingParameter" ).Select( p => new ClassProperty( services, assemblies, p, this ) );
182182
var signature = new ClassSignature( name, properties, apiVersion );
183183

184184
return CreateTypeInfoFromSignature( signature );

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,15 @@ public interface IModelTypeBuilder
2929
Type NewStructuredType( IEdmStructuredType structuredType, Type clrType, ApiVersion apiVersion );
3030

3131
/// <summary>
32-
/// Creates an returns a stronly-typed definition for OData action parameters.
32+
/// Creates an returns a strongly-typed definition for OData action parameters.
3333
/// </summary>
34+
/// <param name="services">The <see cref="IServiceProvider">services</see> needed to potentially substitute types.</param>
3435
/// <param name="action">The defining <see cref="IEdmAction">action</see>.</param>
3536
/// <param name="apiVersion">The <see cref="ApiVersion">API version</see> of the <paramref name="action"/> to create the parameter type for.</param>
3637
/// <returns>A strong <see cref="Type">type</see> definition for the OData <paramref name="action"/> parameters.</returns>
3738
/// <remarks><see cref="ODataActionParameters">OData action parameters</see> are modeled as a <see cref="Dictionary{TKey,TValue}">dictionary</see>,
3839
/// which is difficult to use effectively by documentation tools such as the API Explorer. The corresponding type is generated only once per
3940
/// <paramref name="apiVersion">API version</paramref>.</remarks>
40-
Type NewActionParameters( IEdmAction action, ApiVersion apiVersion );
41+
Type NewActionParameters( IServiceProvider services, IEdmAction action, ApiVersion apiVersion );
4142
}
4243
}

src/Microsoft.AspNet.OData.Versioning.ApiExplorer/AspNet.OData/Routing/ODataRouteBuilderContext.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ void ConvertODataActionParametersToTypedModel( IModelTypeBuilder modelTypeBuilde
7979

8080
if ( parameter.ParameterType.IsODataActionParameters() )
8181
{
82-
description.ParameterDescriptor = new ODataModelBoundParameterDescriptor( parameter, modelTypeBuilder.NewActionParameters( action, apiVersion.Value ) );
82+
description.ParameterDescriptor = new ODataModelBoundParameterDescriptor( parameter, modelTypeBuilder.NewActionParameters( serviceProvider, action, apiVersion.Value ) );
8383
break;
8484
}
8585
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ ApiParameterDescription CreateResult( ApiParameterDescriptionContext bindingCont
8888
{
8989
var action = (IEdmAction) Context.RouteContext.Operation;
9090
var apiVersion = Context.RouteContext.ApiVersion;
91-
type = Context.TypeBuilder.NewActionParameters( action, apiVersion );
91+
type = Context.TypeBuilder.NewActionParameters( Context.Services, action, apiVersion );
9292
}
9393
else
9494
{

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

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using System.Collections.Generic;
99
using System.Linq;
1010
using System.Reflection;
11+
using Microsoft.Extensions.DependencyInjection;
1112
using Xunit;
1213

1314
public class DefaultModelTypeBuilderTest
@@ -231,9 +232,11 @@ public void substitute_should_generate_type_for_action_parameters()
231232
var model = context.Model;
232233
var qualifiedName = $"{model.EntityContainer.Namespace}.{action.Name}";
233234
var operation = (IEdmAction) model.FindDeclaredOperations( qualifiedName ).Single();
235+
var services = new ServiceCollection();
236+
services.AddSingleton( model );
234237

235238
// act
236-
var substitutionType = context.ModelTypeBuilder.NewActionParameters( operation, ApiVersion.Default );
239+
var substitutionType = context.ModelTypeBuilder.NewActionParameters( services.BuildServiceProvider(), operation, ApiVersion.Default );
237240

238241
// assert
239242
substitutionType.GetRuntimeProperties().Should().HaveCount( 3 );
@@ -242,6 +245,43 @@ public void substitute_should_generate_type_for_action_parameters()
242245
substitutionType.Should().HaveProperty<bool>( "callbackRequired" );
243246
}
244247

248+
[Fact]
249+
public void substitute_should_generate_type_for_action_parameters_with_substituted_types()
250+
{
251+
// arrange
252+
var modelBuilder = new ODataConventionModelBuilder();
253+
var contact = modelBuilder.EntitySet<Contact>( "Contacts" ).EntityType;
254+
contact.Ignore( c => c.Email );
255+
var action = contact.Action( "PlanInterview" );
256+
257+
action.Parameter<DateTime>( "when" );
258+
action.Parameter<Contact>( "interviewer" );
259+
action.Parameter<Contact>( "interviewee" );
260+
261+
var context = NewContext( modelBuilder.GetEdmModel() );
262+
var model = context.Model;
263+
var qualifiedName = $"{model.EntityContainer.Namespace}.{action.Name}";
264+
var operation = (IEdmAction) model.FindDeclaredOperations( qualifiedName ).Single();
265+
var services = new ServiceCollection();
266+
services.AddSingleton( model );
267+
268+
// act
269+
var substitutionType = context.ModelTypeBuilder.NewActionParameters( services.BuildServiceProvider(), operation, ApiVersion.Default );
270+
271+
// assert
272+
substitutionType.GetRuntimeProperties().Should().HaveCount( 3 );
273+
substitutionType.Should().HaveProperty<DateTimeOffset>( "when" );
274+
var contactType = substitutionType.GetRuntimeProperty( "interviewer" ).PropertyType;
275+
contactType.Should().Be( substitutionType.GetRuntimeProperty( "interviewee" ).PropertyType );
276+
277+
contactType.GetRuntimeProperties().Should().HaveCount( 5 );
278+
contactType.Should().HaveProperty<int>( "ContactId" );
279+
contactType.Should().HaveProperty<string>( "FirstName" );
280+
contactType.Should().HaveProperty<string>( "LastName" );
281+
contactType.Should().HaveProperty<string>( "Phone" );
282+
contactType.Should().HaveProperty<List<Address>>( "Addresses" );
283+
}
284+
245285
[Fact]
246286
public void substitute_should_generate_type_for_action_parameters_with_collection_parameters()
247287
{
@@ -258,9 +298,11 @@ public void substitute_should_generate_type_for_action_parameters_with_collectio
258298
var model = context.Model;
259299
var qualifiedName = $"{model.EntityContainer.Namespace}.{action.Name}";
260300
var operation = (IEdmAction) model.FindDeclaredOperations( qualifiedName ).Single();
301+
var services = new ServiceCollection();
302+
services.AddSingleton( model );
261303

262304
// act
263-
var substitutionType = context.ModelTypeBuilder.NewActionParameters( operation, ApiVersion.Default );
305+
var substitutionType = context.ModelTypeBuilder.NewActionParameters( services.BuildServiceProvider(), operation, ApiVersion.Default );
264306

265307
// assert
266308
substitutionType.GetRuntimeProperties().Should().HaveCount( 3 );

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

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using System.Collections.Generic;
99
using System.Linq;
1010
using System.Reflection;
11+
using Microsoft.Extensions.DependencyInjection;
1112
using Xunit;
1213

1314
public class DefaultModelTypeBuilderTest
@@ -231,9 +232,11 @@ public void substitute_should_generate_type_for_action_parameters()
231232
var model = context.Model;
232233
var qualifiedName = $"{model.EntityContainer.Namespace}.{action.Name}";
233234
var operation = (IEdmAction) model.FindDeclaredOperations( qualifiedName ).Single();
235+
var services = new ServiceCollection();
236+
services.AddSingleton( model );
234237

235238
// act
236-
var substitutionType = context.ModelTypeBuilder.NewActionParameters( operation, ApiVersion.Default );
239+
var substitutionType = context.ModelTypeBuilder.NewActionParameters( services.BuildServiceProvider(), operation, ApiVersion.Default );
237240

238241
// assert
239242
substitutionType.GetRuntimeProperties().Should().HaveCount( 3 );
@@ -242,6 +245,43 @@ public void substitute_should_generate_type_for_action_parameters()
242245
substitutionType.Should().HaveProperty<bool>( "callbackRequired" );
243246
}
244247

248+
[Fact]
249+
public void substitute_should_generate_type_for_action_parameters_with_substituted_types()
250+
{
251+
// arrange
252+
var modelBuilder = new ODataConventionModelBuilder();
253+
var contact = modelBuilder.EntitySet<Contact>( "Contacts" ).EntityType;
254+
contact.Ignore( c => c.Email );
255+
var action = contact.Action( "PlanInterview" );
256+
257+
action.Parameter<DateTime>( "when" );
258+
action.Parameter<Contact>( "interviewer" );
259+
action.Parameter<Contact>( "interviewee" );
260+
261+
var context = NewContext( modelBuilder.GetEdmModel() );
262+
var model = context.Model;
263+
var qualifiedName = $"{model.EntityContainer.Namespace}.{action.Name}";
264+
var operation = (IEdmAction) model.FindDeclaredOperations( qualifiedName ).Single();
265+
var services = new ServiceCollection();
266+
services.AddSingleton( model );
267+
268+
// act
269+
var substitutionType = context.ModelTypeBuilder.NewActionParameters( services.BuildServiceProvider(), operation, ApiVersion.Default );
270+
271+
// assert
272+
substitutionType.GetRuntimeProperties().Should().HaveCount( 3 );
273+
substitutionType.Should().HaveProperty<DateTimeOffset>( "when" );
274+
var contactType = substitutionType.GetRuntimeProperty( "interviewer" ).PropertyType;
275+
contactType.Should().Be( substitutionType.GetRuntimeProperty( "interviewee" ).PropertyType );
276+
277+
contactType.GetRuntimeProperties().Should().HaveCount( 5 );
278+
contactType.Should().HaveProperty<int>( "ContactId" );
279+
contactType.Should().HaveProperty<string>( "FirstName" );
280+
contactType.Should().HaveProperty<string>( "LastName" );
281+
contactType.Should().HaveProperty<string>( "Phone" );
282+
contactType.Should().HaveProperty<List<Address>>( "Addresses" );
283+
}
284+
245285
[Fact]
246286
public void substitute_should_generate_type_for_action_parameters_with_collection_parameters()
247287
{
@@ -258,9 +298,11 @@ public void substitute_should_generate_type_for_action_parameters_with_collectio
258298
var model = context.Model;
259299
var qualifiedName = $"{model.EntityContainer.Namespace}.{action.Name}";
260300
var operation = (IEdmAction) model.FindDeclaredOperations( qualifiedName ).Single();
301+
var services = new ServiceCollection();
302+
services.AddSingleton( model );
261303

262304
// act
263-
var substitutionType = context.ModelTypeBuilder.NewActionParameters( operation, ApiVersion.Default );
305+
var substitutionType = context.ModelTypeBuilder.NewActionParameters( services.BuildServiceProvider(), operation, ApiVersion.Default );
264306

265307
// assert
266308
substitutionType.GetRuntimeProperties().Should().HaveCount( 3 );

0 commit comments

Comments
 (0)