Skip to content

Commit b851fe2

Browse files
Chris Martinezcommonsensesoftware
authored andcommitted
Fixed binding source for 'special' model bound parameters
1 parent d8d61c3 commit b851fe2

File tree

5 files changed

+95
-71
lines changed

5 files changed

+95
-71
lines changed

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

Lines changed: 15 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -470,28 +470,17 @@ void UpdateBindingInfo( ApiParameterContext context, ParameterDescriptor paramet
470470
Contract.Requires( parameter != null );
471471
Contract.Requires( metadata != null );
472472

473-
var paramType = parameter.ParameterType;
474-
475473
if ( parameter.BindingInfo != null )
476474
{
477-
// HACK: it's unclear why the default ParameterDescriptor doesn't have the correct BindingSource
478-
// the DefaultApiDescriptionProvider does the right thing for non-OData actions
479-
if ( typeof( ApiVersion ).IsAssignableFrom( paramType ) )
480-
{
481-
parameter.BindingInfo.BindingSource = metadata.BindingSource;
482-
}
483-
484475
return;
485476
}
486477

487-
if ( paramType.IsODataQueryOptions() || paramType.IsODataPath() )
488-
{
489-
parameter.BindingInfo = new BindingInfo() { BindingSource = Special };
490-
return;
491-
}
492-
else if ( paramType.IsDelta() )
478+
var bindingInfo = new BindingInfo() { BindingSource = metadata.BindingSource };
479+
480+
parameter.BindingInfo = bindingInfo;
481+
482+
if ( bindingInfo.BindingSource != null )
493483
{
494-
parameter.BindingInfo = new BindingInfo() { BindingSource = Body };
495484
return;
496485
}
497486

@@ -537,31 +526,25 @@ void UpdateBindingInfo( ApiParameterContext context, ParameterDescriptor paramet
537526
break;
538527
}
539528

540-
if ( paramType.IsODataActionParameters() )
529+
key = operation.Parameters.FirstOrDefault( p => p.Name.Equals( paramName, OrdinalIgnoreCase ) );
530+
531+
if ( key == null )
541532
{
542-
source = Body;
533+
if ( operation.IsBound )
534+
{
535+
goto case EntitySet;
536+
}
543537
}
544538
else
545539
{
546-
key = operation.Parameters.FirstOrDefault( p => p.Name.Equals( paramName, OrdinalIgnoreCase ) );
547-
548-
if ( key == null )
549-
{
550-
if ( operation.IsBound )
551-
{
552-
goto case EntitySet;
553-
}
554-
}
555-
else
556-
{
557-
source = Path;
558-
}
540+
source = Path;
559541
}
560542

561543
break;
562544
}
563545

564-
parameter.BindingInfo = new BindingInfo() { BindingSource = source };
546+
bindingInfo.BindingSource = source;
547+
parameter.BindingInfo = bindingInfo;
565548
}
566549

567550
IReadOnlyList<ApiResponseType> GetApiResponseTypes( IReadOnlyList<IApiResponseMetadataProvider> responseMetadataAttributes, Type responseType, IServiceProvider serviceProvider )

src/Microsoft.AspNetCore.OData.Versioning/AspNet.OData/Routing/ODataRouteBindingInfoConvention.cs

Lines changed: 24 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2,39 +2,44 @@
22
{
33
using Microsoft.AspNet.OData.Routing.Template;
44
using Microsoft.AspNetCore.Mvc.Abstractions;
5-
using Microsoft.AspNetCore.Mvc.ApplicationModels;
6-
using Microsoft.AspNetCore.Mvc.ApplicationParts;
75
using Microsoft.AspNetCore.Mvc.Controllers;
86
using Microsoft.AspNetCore.Mvc.ModelBinding;
97
using Microsoft.AspNetCore.Mvc.Routing;
10-
using Microsoft.AspNetCore.Mvc.Versioning;
118
using Microsoft.OData.Edm;
129
using System;
1310
using System.Collections.Generic;
1411
using System.Diagnostics.Contracts;
1512
using System.Linq;
16-
using System.Reflection;
1713
using static Microsoft.AspNet.OData.Routing.ODataRouteActionType;
1814
using static Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource;
1915
using static System.Linq.Enumerable;
2016
using static System.StringComparison;
2117

2218
sealed class ODataRouteBindingInfoConvention : IODataActionDescriptorConvention
2319
{
24-
internal ODataRouteBindingInfoConvention( IODataRouteCollectionProvider routeCollectionProvider )
20+
internal ODataRouteBindingInfoConvention(
21+
IODataRouteCollectionProvider routeCollectionProvider,
22+
IModelMetadataProvider modelMetadataProvider )
2523
{
2624
Contract.Requires( routeCollectionProvider != null );
25+
Contract.Requires( modelMetadataProvider != null );
2726

2827
RouteCollectionProvider = routeCollectionProvider;
28+
ModelMetadataProvider = modelMetadataProvider;
2929
}
3030

3131
IODataRouteCollectionProvider RouteCollectionProvider { get; }
3232

33+
IModelMetadataProvider ModelMetadataProvider { get; }
34+
3335
public void Apply( ActionDescriptorProviderContext context, ControllerActionDescriptor action )
3436
{
3537
Contract.Requires( context != null );
3638
Contract.Requires( action != null );
3739

40+
// any existing AttributeRouteInfo is expected to be fake up to this point so clear it
41+
action.AttributeRouteInfo = null;
42+
3843
var model = action.GetApiVersionModel();
3944
var mappings = RouteCollectionProvider.Items;
4045
var routeInfos = new HashSet<ODataAttributeRouteInfo>( new ODataAttributeRouteInfoComparer() );
@@ -48,7 +53,7 @@ public void Apply( ActionDescriptorProviderContext context, ControllerActionDesc
4853
return;
4954
}
5055

51-
// note: any mapping will do for a version-neutral action; just take the first one
56+
// any mapping will do for a version-neutral action; just take the first one
5257
var mapping = mappings[0];
5358

5459
UpdateBindingInfo( action, mapping, routeInfos );
@@ -110,32 +115,24 @@ void UpdateBindingInfo( ControllerActionDescriptor action, ODataRouteMapping map
110115
routeInfos.Add( routeInfo );
111116
}
112117

113-
static void UpdateBindingInfo( ActionParameterContext context, ParameterDescriptor parameter )
118+
void UpdateBindingInfo( ActionParameterContext context, ParameterDescriptor parameter )
114119
{
115120
Contract.Requires( context != null );
116121
Contract.Requires( parameter != null );
117122

118123
var bindingInfo = parameter.BindingInfo;
119124

120-
if ( bindingInfo != null && bindingInfo.BindingSource != Custom )
125+
if ( bindingInfo != null )
121126
{
122127
return;
123128
}
124129

125-
bindingInfo = parameter.BindingInfo ?? new BindingInfo();
130+
var metadata = ModelMetadataProvider.GetMetadataForType( parameter.ParameterType );
126131

127-
var paramType = parameter.ParameterType;
132+
parameter.BindingInfo = bindingInfo = new BindingInfo() { BindingSource = metadata.BindingSource };
128133

129-
if ( paramType.IsODataQueryOptions() || paramType.IsODataPath() )
130-
{
131-
bindingInfo.BindingSource = ModelBinding;
132-
parameter.BindingInfo = bindingInfo;
133-
return;
134-
}
135-
else if ( paramType.IsDelta() )
134+
if ( bindingInfo.BindingSource != null )
136135
{
137-
bindingInfo.BindingSource = Body;
138-
parameter.BindingInfo = bindingInfo;
139136
return;
140137
}
141138

@@ -181,25 +178,18 @@ static void UpdateBindingInfo( ActionParameterContext context, ParameterDescript
181178
break;
182179
}
183180

184-
if ( paramType.IsODataActionParameters() )
181+
key = operation.Parameters.FirstOrDefault( p => p.Name.Equals( paramName, OrdinalIgnoreCase ) );
182+
183+
if ( key == null )
185184
{
186-
source = Body;
185+
if ( operation.IsBound )
186+
{
187+
goto case EntitySet;
188+
}
187189
}
188190
else
189191
{
190-
key = operation.Parameters.FirstOrDefault( p => p.Name.Equals( paramName, OrdinalIgnoreCase ) );
191-
192-
if ( key == null )
193-
{
194-
if ( operation.IsBound )
195-
{
196-
goto case EntitySet;
197-
}
198-
}
199-
else
200-
{
201-
source = Path;
202-
}
192+
source = Path;
203193
}
204194

205195
break;

src/Microsoft.AspNetCore.OData.Versioning/AspNetCore.Mvc/ODataActionDescriptorProvider.cs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using Microsoft.AspNetCore.Mvc.Abstractions;
66
using Microsoft.AspNetCore.Mvc.ApplicationParts;
77
using Microsoft.AspNetCore.Mvc.Controllers;
8+
using Microsoft.AspNetCore.Mvc.ModelBinding;
89
using System.Collections.Generic;
910
using System.Diagnostics.Contracts;
1011
using System.Linq;
@@ -13,14 +14,20 @@ sealed class ODataActionDescriptorProvider : IActionDescriptorProvider
1314
{
1415
readonly IODataRouteCollectionProvider routeCollectionProvider;
1516
readonly ApplicationPartManager partManager;
17+
readonly IModelMetadataProvider modelMetadataProvider;
1618

17-
public ODataActionDescriptorProvider( IODataRouteCollectionProvider routeCollectionProvider, ApplicationPartManager partManager )
19+
public ODataActionDescriptorProvider(
20+
IODataRouteCollectionProvider routeCollectionProvider,
21+
ApplicationPartManager partManager,
22+
IModelMetadataProvider modelMetadataProvider )
1823
{
1924
Contract.Requires( routeCollectionProvider != null );
2025
Contract.Requires( partManager != null );
26+
Contract.Requires( modelMetadataProvider != null );
2127

2228
this.routeCollectionProvider = routeCollectionProvider;
2329
this.partManager = partManager;
30+
this.modelMetadataProvider = modelMetadataProvider;
2431
}
2532

2633
public int Order => 0;
@@ -38,7 +45,7 @@ public void OnProvidersExecuted( ActionDescriptorProviderContext context )
3845
var conventions = new IODataActionDescriptorConvention[]
3946
{
4047
new ImplicitHttpMethodConvention(),
41-
new ODataRouteBindingInfoConvention( routeCollectionProvider ),
48+
new ODataRouteBindingInfoConvention( routeCollectionProvider, modelMetadataProvider ),
4249
};
4350

4451
foreach ( var action in ODataActions( results ) )
@@ -60,7 +67,7 @@ static IEnumerable<ControllerActionDescriptor> ODataActions( IEnumerable<ActionD
6067
foreach ( var result in results )
6168
{
6269
if ( result is ControllerActionDescriptor action &&
63-
action.ControllerTypeInfo.IsODataController() &&
70+
action.ControllerTypeInfo.IsODataController() &&
6471
!action.ControllerTypeInfo.IsMetadataController() )
6572
{
6673
yield return action;
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
namespace Microsoft.AspNetCore.Mvc.Versioning
2+
{
3+
using Microsoft.AspNet.OData;
4+
using Microsoft.AspNet.OData.Query;
5+
using Microsoft.AspNet.OData.Routing;
6+
using Microsoft.AspNetCore.Mvc.ModelBinding;
7+
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
8+
using Microsoft.Extensions.Options;
9+
using System;
10+
using System.Collections.Generic;
11+
using System.Diagnostics.Contracts;
12+
using static Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource;
13+
14+
/// <summary>
15+
/// Represents the API versioning configuration for ASP.NET Core <see cref="MvcOptions">MVC options</see>.
16+
/// </summary>
17+
[CLSCompliant( false )]
18+
public class ODataMvcOptionsSetup : IPostConfigureOptions<MvcOptions>
19+
{
20+
/// <inheritdoc />
21+
public virtual void PostConfigure( string name, MvcOptions options )
22+
{
23+
var modelMetadataDetailsProviders = options.ModelMetadataDetailsProviders;
24+
25+
ConfigureModelBoundType( modelMetadataDetailsProviders, typeof( ODataQueryOptions ), Special );
26+
ConfigureModelBoundType( modelMetadataDetailsProviders, typeof( ODataPath ), Special );
27+
ConfigureModelBoundType( modelMetadataDetailsProviders, typeof( IDelta ), Body );
28+
ConfigureModelBoundType( modelMetadataDetailsProviders, typeof( ODataActionParameters ), Body );
29+
}
30+
31+
static void ConfigureModelBoundType(
32+
IList<IMetadataDetailsProvider> modelMetadataDetailsProviders,
33+
Type modelType,
34+
BindingSource bindingSource )
35+
{
36+
Contract.Requires( modelMetadataDetailsProviders != null );
37+
Contract.Requires( modelType != null );
38+
Contract.Requires( bindingSource != null );
39+
40+
modelMetadataDetailsProviders.Insert( 0, new SuppressChildValidationMetadataProvider( modelType ) );
41+
modelMetadataDetailsProviders.Insert( 0, new BindingSourceMetadataProvider( modelType, bindingSource ) );
42+
}
43+
}
44+
}

src/Microsoft.AspNetCore.OData.Versioning/Extensions.DependencyInjection/IODataBuilderExtensions.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,14 @@
33
using Microsoft.AspNet.OData.Builder;
44
using Microsoft.AspNet.OData.Interfaces;
55
using Microsoft.AspNet.OData.Routing;
6-
using Microsoft.AspNetCore.Builder;
7-
using Microsoft.AspNetCore.Hosting;
86
using Microsoft.AspNetCore.Mvc;
97
using Microsoft.AspNetCore.Mvc.Abstractions;
108
using Microsoft.AspNetCore.Mvc.ApplicationModels;
119
using Microsoft.AspNetCore.Mvc.ApplicationParts;
1210
using Microsoft.AspNetCore.Mvc.Infrastructure;
1311
using Microsoft.AspNetCore.Mvc.Versioning;
1412
using Microsoft.Extensions.DependencyInjection.Extensions;
13+
using Microsoft.Extensions.Options;
1514
using System;
1615
using System.Diagnostics.Contracts;
1716
using System.Linq;
@@ -69,6 +68,7 @@ static void AddODataServices( IServiceCollection services )
6968

7069
ConfigureDefaultFeatureProviders( partManager );
7170
services.Replace( Singleton<IActionSelector, ODataApiVersionActionSelector>() );
71+
services.AddTransient<IPostConfigureOptions<MvcOptions>, ODataMvcOptionsSetup>();
7272
services.TryAdd( Transient<VersionedODataModelBuilder, VersionedODataModelBuilder>() );
7373
services.TryAdd( Singleton<IODataRouteCollectionProvider, ODataRouteCollectionProvider>() );
7474
services.AddTransient<IApplicationModelProvider, ODataApplicationModelProvider>();

0 commit comments

Comments
 (0)