Skip to content

Commit 6590f4e

Browse files
Disambiguate OData operations by action parameters. Fixes #697
1 parent 5149e5a commit 6590f4e

File tree

7 files changed

+118
-110
lines changed

7 files changed

+118
-110
lines changed

src/Common.OData.ApiExplorer/AspNet.OData/Routing/ODataRouteBuilder.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -741,15 +741,17 @@ static bool IsBuiltInParameter( Type parameterType ) =>
741741

742742
static bool IsKey( IReadOnlyList<IEdmStructuralProperty> keys, ApiParameterDescription parameter )
743743
{
744-
foreach ( var key in keys )
744+
var name = parameter.Name;
745+
746+
for ( var i = 0; i < keys.Count; i++ )
745747
{
746-
if ( key.Name.Equals( parameter.Name, OrdinalIgnoreCase ) )
748+
if ( keys[i].Name.Equals( name, OrdinalIgnoreCase ) )
747749
{
748750
return true;
749751
}
750752
}
751753

752-
return parameter.Name.StartsWith( Key, OrdinalIgnoreCase );
754+
return name.StartsWith( Key, OrdinalIgnoreCase );
753755
}
754756

755757
static bool IsFunctionParameter( IEdmOperation? operation, ApiParameterDescription parameter )

src/Common.OData.ApiExplorer/AspNet.OData/Routing/ODataRouteBuilderContext.cs

Lines changed: 45 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using Microsoft.AspNetCore.Mvc.Abstractions;
66
using Microsoft.AspNetCore.Mvc.ApiExplorer;
77
using Microsoft.AspNetCore.Mvc.Controllers;
8+
using Microsoft.AspNetCore.Mvc.ModelBinding;
89
using Microsoft.AspNetCore.Mvc.Routing;
910
using Microsoft.AspNetCore.Mvc.Versioning;
1011
#endif
@@ -23,6 +24,7 @@
2324
using System.Web.Http.Description;
2425
using System.Web.Http.Dispatcher;
2526
using ControllerActionDescriptor = System.Web.Http.Controllers.HttpActionDescriptor;
27+
using ParameterDescriptor = System.Web.Http.Controllers.HttpParameterDescriptor;
2628
#endif
2729
using static Microsoft.OData.ODataUrlKeyDelimiter;
2830
using static ODataRouteTemplateGenerationKind;
@@ -249,44 +251,68 @@ static bool IsNavigationPropertyLink( IEdmEntitySet? entitySet, IEdmSingleton? s
249251
return false;
250252
}
251253

252-
IEdmOperation? ResolveOperation( IEdmEntityContainer container, string name )
254+
IEdmOperation? ResolveOperation( IEdmEntityContainer container, ControllerActionDescriptor action )
253255
{
254-
var import = container.FindOperationImports( name ).SingleOrDefault();
255-
256-
if ( import != null )
256+
if ( container.FindOperationImports( action.ActionName ).SingleOrDefault() is IEdmOperationImport import )
257257
{
258258
return import.Operation;
259259
}
260260

261-
var qualifiedName = container.Namespace + "." + name;
262-
var entities = new List<IEdmType>( capacity: 2 );
261+
var qualifiedName = container.Namespace + "." + action.ActionName;
263262

264-
if ( Singleton != null )
263+
if ( Singleton is not null )
265264
{
266-
entities.Add( Singleton.EntityType() );
265+
return EdmModel.FindBoundOperations( qualifiedName, Singleton.EntityType() ).SingleOrDefault();
267266
}
268267

269-
if ( EntitySet != null )
268+
if ( EntitySet is null )
270269
{
271-
var entity = EntitySet.EntityType();
270+
return default;
271+
}
272272

273-
if ( entities.Count == 0 || !entities[0].Equals( entity ) )
274-
{
275-
entities.Add( entity );
276-
}
273+
var operation = EdmModel.FindBoundOperations( qualifiedName, EntitySet.Type ).SingleOrDefault();
274+
275+
if ( operation is not null && HasNoEntityKeysRemaining( operation, FilterParameters( action ) ) )
276+
{
277+
return operation;
277278
}
278279

279-
for ( var i = 0; i < entities.Count; i++ )
280+
return EdmModel.FindBoundOperations( qualifiedName, EntitySet.EntityType() ).SingleOrDefault();
281+
}
282+
283+
static bool HasNoEntityKeysRemaining( IEdmOperation operation, IList<ParameterDescriptor> parameters )
284+
{
285+
var actionParamCount = parameters.Count;
286+
var operationParamCount = 0;
287+
var matches = 0;
288+
289+
foreach ( var parameter in operation.Parameters )
280290
{
281-
var operation = EdmModel.FindBoundOperations( qualifiedName, entities[i] ).SingleOrDefault();
291+
if ( parameter.Name == "bindingParameter" )
292+
{
293+
continue;
294+
}
295+
296+
++operationParamCount;
282297

283-
if ( operation != null )
298+
for ( var i = 0; i < parameters.Count; i++ )
284299
{
285-
return operation;
300+
#if WEBAPI
301+
var name = parameters[i].ParameterName;
302+
#else
303+
var name = parameters[i].Name;
304+
#endif
305+
if ( name.Equals( parameter.Name, OrdinalIgnoreCase ) )
306+
{
307+
++matches;
308+
parameters.RemoveAt( i );
309+
break;
310+
}
286311
}
287312
}
288313

289-
return EdmModel.FindDeclaredOperations( qualifiedName ).SingleOrDefault();
314+
return operationParamCount == matches &&
315+
operationParamCount == actionParamCount;
290316
}
291317

292318
sealed class FixedEdmModelServiceProviderDecorator : IServiceProvider

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

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
using Microsoft.Web.Http.Description;
1010
using System;
1111
using System.Collections.Generic;
12+
using System.Threading;
1213
using System.Web.Http;
1314
using System.Web.Http.Controllers;
1415
using System.Web.Http.Description;
@@ -55,7 +56,7 @@ internal ODataRouteBuilderContext(
5556
Services = new FixedEdmModelServiceProviderDecorator( Services, model );
5657
EntitySet = container.FindEntitySet( controllerName );
5758
Singleton = container.FindSingleton( controllerName );
58-
Operation = ResolveOperation( container, actionDescriptor.ActionName );
59+
Operation = ResolveOperation( container, actionDescriptor );
5960
ActionType = GetActionType( actionDescriptor );
6061
IsRouteExcluded = ActionType == ODataRouteActionType.Unknown;
6162

@@ -97,5 +98,26 @@ void ConvertODataActionParametersToTypedModel( IModelTypeBuilder modelTypeBuilde
9798
}
9899
}
99100
}
101+
102+
static IList<HttpParameterDescriptor> FilterParameters( HttpActionDescriptor action )
103+
{
104+
var parameters = action.GetParameters().ToList();
105+
var cancellationToken = typeof( CancellationToken );
106+
107+
for ( var i = parameters.Count - 1; i >= 0; i-- )
108+
{
109+
var type = parameters[i].ParameterType;
110+
111+
if ( type.IsODataQueryOptions() ||
112+
type.IsODataActionParameters() ||
113+
type.IsODataPath() ||
114+
type.Equals( cancellationToken ) )
115+
{
116+
parameters.RemoveAt( i );
117+
}
118+
}
119+
120+
return parameters;
121+
}
100122
}
101123
}

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

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,30 +3,43 @@
33
using Microsoft.AspNet.OData;
44
using Microsoft.AspNetCore.Mvc;
55
using Microsoft.AspNetCore.Mvc.Abstractions;
6+
#if API_EXPLORER
67
using Microsoft.AspNetCore.Mvc.ApiExplorer;
8+
#endif
79
using Microsoft.AspNetCore.Mvc.Controllers;
810
using Microsoft.AspNetCore.Mvc.ModelBinding;
911
using Microsoft.AspNetCore.Mvc.Routing;
12+
#if !API_EXPLORER
13+
using Microsoft.AspNetCore.Mvc.Versioning;
14+
#endif
1015
using Microsoft.Extensions.DependencyInjection;
1116
using Microsoft.OData;
1217
using Microsoft.OData.Edm;
1318
using System.Collections.Generic;
19+
using System.Linq;
20+
using static Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource;
1421

1522
partial class ODataRouteBuilderContext
1623
{
1724
internal ODataRouteBuilderContext(
1825
ODataRouteMapping routeMapping,
1926
ApiVersion apiVersion,
2027
ControllerActionDescriptor actionDescriptor,
28+
#if API_EXPLORER
2129
ODataApiExplorerOptions options )
30+
#else
31+
ODataApiVersioningOptions options )
32+
#endif
2233
{
2334
ApiVersion = apiVersion;
2435
Services = routeMapping.Services;
2536
(RouteTemplateProvider, routeAttribute) = TryGetRouteAttribute( actionDescriptor );
2637
RouteTemplate = routeAttribute?.PathTemplate ?? RouteTemplateProvider?.Template;
2738
RoutePrefix = routeMapping.RoutePrefix;
2839
ActionDescriptor = actionDescriptor;
40+
#if API_EXPLORER
2941
ParameterDescriptions = new List<ApiParameterDescription>();
42+
#endif
3043
Options = options;
3144
UrlKeyDelimiter = UrlKeyDelimiterOrDefault( Services.GetRequiredService<ODataOptions>().UrlKeyDelimiter );
3245

@@ -45,7 +58,7 @@ internal ODataRouteBuilderContext(
4558
Services = new FixedEdmModelServiceProviderDecorator( Services, model );
4659
EntitySet = container.FindEntitySet( actionDescriptor.ControllerName );
4760
Singleton = container.FindSingleton( actionDescriptor.ControllerName );
48-
Operation = ResolveOperation( container, actionDescriptor.ActionName );
61+
Operation = ResolveOperation( container, actionDescriptor );
4962
ActionType = GetActionType( actionDescriptor );
5063
IsRouteExcluded = ActionType == ODataRouteActionType.Unknown;
5164
}
@@ -79,5 +92,34 @@ internal ODataRouteBuilderContext(
7992

8093
return templateProvider is null ? default : (templateProvider, new ODataRouteAttribute( templateProvider.Template ));
8194
}
95+
96+
static IList<ParameterDescriptor> FilterParameters( ControllerActionDescriptor action )
97+
{
98+
var parameters = action.Parameters.ToList();
99+
100+
for ( var i = parameters.Count - 1; i >= 0; i-- )
101+
{
102+
if ( parameters[i].BindingInfo is not BindingInfo info )
103+
{
104+
continue;
105+
}
106+
107+
if ( info.BindingSource == Special )
108+
{
109+
parameters.RemoveAt( i );
110+
}
111+
else if ( info.BindingSource == Custom )
112+
{
113+
var type = parameters[i].ParameterType;
114+
115+
if ( type.IsODataQueryOptions() || type.IsODataActionParameters() || type.IsODataPath() )
116+
{
117+
parameters.RemoveAt( i );
118+
}
119+
}
120+
}
121+
122+
return parameters;
123+
}
82124
}
83125
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ void UpdateBindingInfo(
125125
ODataRouteMapping mapping,
126126
ICollection<ODataAttributeRouteInfo> routeInfos )
127127
{
128-
var routeContext = new ODataRouteBuilderContext( apiVersion, mapping, action, Options );
128+
var routeContext = new ODataRouteBuilderContext( mapping, apiVersion, action, Options );
129129

130130
if ( routeContext.IsRouteExcluded )
131131
{

src/Microsoft.AspNetCore.OData.Versioning/AspNet.OData/Routing/ODataRouteBuilderContext.Core.cs

Lines changed: 0 additions & 79 deletions
This file was deleted.

src/Microsoft.AspNetCore.OData.Versioning/Microsoft.AspNetCore.OData.Versioning.csproj

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
<ItemGroup>
1414
<Compile Include="..\Common.OData.ApiExplorer\AspNet.OData\Routing\*.cs" Link="AspNet.OData\Routing\%(Filename)%(Extension)" />
1515
<Compile Include="..\Common.OData.ApiExplorer\OData.Edm\*.cs" Link="OData.Edm\%(Filename)%(Extension)" />
16+
<Compile Include="..\Microsoft.AspNetCore.OData.Versioning.ApiExplorer\AspNet.OData\Routing\ODataRouteBuilderContext.cs" Link="AspNet.OData\Routing\%(Filename).Core%(Extension)" />
1617
</ItemGroup>
1718

1819
<ItemGroup>
@@ -23,12 +24,6 @@
2324
<PackageReference Include="Microsoft.AspNetCore.OData" Version="[7.5.8,8.0.0)" />
2425
</ItemGroup>
2526

26-
<ItemGroup>
27-
<Compile Update="LocalSR.Designer.cs">
28-
<DesignTime>True</DesignTime>
29-
</Compile>
30-
</ItemGroup>
31-
3227
<Import Project="..\Common.OData\Common.OData.projitems" Label="Shared" />
3328
<Import Project="..\Shared\Shared.projitems" Label="Shared" />
3429

0 commit comments

Comments
 (0)