Skip to content

Commit f9ab22d

Browse files
Correct binding source for [FromODataUri]. Fixes #693, Fixes #729, Fixes #737
1 parent 0494a71 commit f9ab22d

File tree

17 files changed

+58
-37
lines changed

17 files changed

+58
-37
lines changed

samples/aspnetcore/SwaggerODataSample/SwaggerODataSample.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
<ItemGroup>
1010
<PackageReference Include="Microsoft.Extensions.PlatformAbstractions" Version="1.1.0" />
11-
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.0.2-*" />
11+
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.1.4-*" />
1212
</ItemGroup>
1313

1414
<ItemGroup>

samples/aspnetcore/SwaggerODataSample/V1/OrdersController.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,6 @@ public IActionResult Post( [FromBody] Order order )
6161
/// <returns>The most expensive order.</returns>
6262
/// <response code="200">The order was successfully retrieved.</response>
6363
/// <response code="404">The no orders exist.</response>
64-
[HttpGet]
6564
[ODataRoute( "MostExpensive" )]
6665
[MapToApiVersion( "1.0" )]
6766
[Produces( "application/json" )]
@@ -77,13 +76,12 @@ public IActionResult Post( [FromBody] Order order )
7776
/// <returns>The order line items.</returns>
7877
/// <response code="200">The line items were successfully retrieved.</response>
7978
/// <response code="404">The order does not exist.</response>
80-
[HttpGet]
8179
[ODataRoute( "{key}/LineItems" )]
8280
[Produces( "application/json" )]
8381
[ProducesResponseType( typeof( ODataValue<IEnumerable<LineItem>> ), Status200OK )]
8482
[ProducesResponseType( Status404NotFound )]
8583
[EnableQuery( AllowedQueryOptions = Select )]
86-
public IActionResult LineItems( int key )
84+
public IActionResult GetLineItems( int key )
8785
{
8886
var lineItems = new[]
8987
{

samples/aspnetcore/SwaggerODataSample/V1/PeopleController.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ public class PeopleController : ODataController
2222
/// <returns>The requested person.</returns>
2323
/// <response code="200">The person was successfully retrieved.</response>
2424
/// <response code="404">The person does not exist.</response>
25-
[HttpGet]
2625
[Produces( "application/json" )]
2726
[ProducesResponseType( typeof( Person ), Status200OK )]
2827
[ProducesResponseType( Status404NotFound )]

samples/aspnetcore/SwaggerODataSample/V2/OrdersController.cs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,6 @@ public IActionResult Patch( int key, Delta<Order> delta )
110110
/// <returns>The most expensive order.</returns>
111111
/// <response code="200">The order was successfully retrieved.</response>
112112
/// <response code="404">The no orders exist.</response>
113-
[HttpGet]
114113
[ODataRoute( nameof( MostExpensive ) )]
115114
[Produces( "application/json" )]
116115
[ProducesResponseType( typeof( Order ), Status200OK )]
@@ -127,7 +126,6 @@ public IActionResult Patch( int key, Delta<Order> delta )
127126
/// <response code="204">The order was successfully rated.</response>
128127
/// <response code="400">The parameters are invalid.</response>
129128
/// <response code="404">The order does not exist.</response>
130-
[HttpPost]
131129
[ODataRoute( "{key}/Rate" )]
132130
[ProducesResponseType( Status204NoContent )]
133131
[ProducesResponseType( Status400BadRequest )]
@@ -150,13 +148,12 @@ public IActionResult Rate( int key, ODataActionParameters parameters )
150148
/// <returns>The order line items.</returns>
151149
/// <response code="200">The line items were successfully retrieved.</response>
152150
/// <response code="404">The order does not exist.</response>
153-
[HttpGet]
154151
[ODataRoute( "{key}/LineItems" )]
155152
[Produces( "application/json" )]
156153
[ProducesResponseType( typeof( ODataValue<IEnumerable<LineItem>> ), Status200OK )]
157154
[ProducesResponseType( Status404NotFound )]
158155
[EnableQuery( AllowedQueryOptions = Select )]
159-
public IActionResult LineItems( int key )
156+
public IActionResult GetLineItems( int key )
160157
{
161158
var lineItems = new[]
162159
{

samples/aspnetcore/SwaggerODataSample/V2/PeopleController.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,6 @@ public IActionResult Get( int key, ODataQueryOptions<Person> options )
116116
/// <param name="options">The current OData query options.</param>
117117
/// <returns>The matching new hires.</returns>
118118
/// <response code="200">The people were successfully retrieved.</response>
119-
[HttpGet]
120119
[Produces( "application/json" )]
121120
[ProducesResponseType( typeof( ODataValue<IEnumerable<Person>> ), Status200OK )]
122121
public IActionResult NewHires( DateTime since, ODataQueryOptions<Person> options ) => Get( options );
@@ -128,7 +127,6 @@ public IActionResult Get( int key, ODataQueryOptions<Person> options )
128127
/// <returns>The person's home address.</returns>
129128
/// <response code="200">The home address was successfully retrieved.</response>
130129
/// <response code="404">The person does not exist.</response>
131-
[HttpGet]
132130
[Produces( "application/json" )]
133131
[ProducesResponseType( typeof( Address ), Status200OK )]
134132
[ProducesResponseType( Status404NotFound )]

samples/aspnetcore/SwaggerODataSample/V3/AcmeController.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ public class AcmeController : ODataController
3737
/// <param name="navigationProperty">The product to link.</param>
3838
/// <param name="link">The product identifier.</param>
3939
/// <returns>None</returns>
40-
[HttpPost]
4140
[ProducesResponseType( Status204NoContent )]
4241
[ProducesResponseType( Status404NotFound )]
4342
public IActionResult CreateRef( [FromODataUri] string navigationProperty, [FromBody] Uri link ) => NoContent();

samples/aspnetcore/SwaggerODataSample/V3/OrdersController.cs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,6 @@ public IActionResult Patch( int key, Delta<Order> delta )
123123
/// <returns>The most expensive order.</returns>
124124
/// <response code="200">The order was successfully retrieved.</response>
125125
/// <response code="404">The no orders exist.</response>
126-
[HttpGet]
127126
[ODataRoute( nameof( MostExpensive ) )]
128127
[Produces( "application/json" )]
129128
[ProducesResponseType( typeof( Order ), Status200OK )]
@@ -140,7 +139,6 @@ public IActionResult Patch( int key, Delta<Order> delta )
140139
/// <response code="204">The order was successfully rated.</response>
141140
/// <response code="400">The parameters are invalid.</response>
142141
/// <response code="404">The order does not exist.</response>
143-
[HttpPost]
144142
[ODataRoute( "{key}/Rate" )]
145143
[ProducesResponseType( Status204NoContent )]
146144
[ProducesResponseType( Status400BadRequest )]
@@ -163,13 +161,12 @@ public IActionResult Rate( int key, ODataActionParameters parameters )
163161
/// <returns>The order line items.</returns>
164162
/// <response code="200">The line items were successfully retrieved.</response>
165163
/// <response code="404">The order does not exist.</response>
166-
[HttpGet]
167164
[ODataRoute( "{key}/LineItems" )]
168165
[Produces( "application/json" )]
169166
[ProducesResponseType( typeof( ODataValue<IEnumerable<LineItem>> ), Status200OK )]
170167
[ProducesResponseType( Status404NotFound )]
171168
[EnableQuery( AllowedQueryOptions = Select )]
172-
public IActionResult LineItems( int key )
169+
public IActionResult GetLineItems( int key )
173170
{
174171
var lineItems = new[]
175172
{

samples/aspnetcore/SwaggerODataSample/V3/PeopleController.cs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,6 @@ public IActionResult Post( [FromBody] Person person )
142142
/// <param name="options">The current OData query options.</param>
143143
/// <returns>The matching new hires.</returns>
144144
/// <response code="200">The people were successfully retrieved.</response>
145-
[HttpGet]
146145
[Produces( "application/json" )]
147146
[ProducesResponseType( typeof( ODataValue<IEnumerable<Person>> ), Status200OK )]
148147
public IActionResult NewHires( DateTime since, ODataQueryOptions<Person> options ) => Get( options );
@@ -156,7 +155,6 @@ public IActionResult Post( [FromBody] Person person )
156155
/// <response code="204">The person was successfully promoted.</response>
157156
/// <response code="400">The parameters are invalid.</response>
158157
/// <response code="404">The person does not exist.</response>
159-
[HttpPost]
160158
[ProducesResponseType( Status204NoContent )]
161159
[ProducesResponseType( Status400BadRequest )]
162160
[ProducesResponseType( Status404NotFound )]
@@ -178,7 +176,6 @@ public IActionResult Promote( int key, ODataActionParameters parameters )
178176
/// <returns>The person's home address.</returns>
179177
/// <response code="200">The home address was successfully retrieved.</response>
180178
/// <response code="404">The person does not exist.</response>
181-
[HttpGet]
182179
[Produces( "application/json" )]
183180
[ProducesResponseType( typeof( Address ), Status200OK )]
184181
[ProducesResponseType( Status404NotFound )]
@@ -199,7 +196,6 @@ public IActionResult GetHomeAddress( int key ) =>
199196
/// <returns>The person's work address.</returns>
200197
/// <response code="200">The work address was successfully retrieved.</response>
201198
/// <response code="404">The person does not exist.</response>
202-
[HttpGet]
203199
[Produces( "application/json" )]
204200
[ProducesResponseType( typeof( Address ), Status200OK )]
205201
[ProducesResponseType( Status404NotFound )]

samples/aspnetcore/SwaggerODataSample/V3/ProductsController.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,6 @@ public IActionResult GetRefToSupplier( [FromODataUri] int key, [FromODataUri] st
170170
/// <param name="navigationProperty">The supplier to link.</param>
171171
/// <param name="link">The supplier identifier.</param>
172172
/// <returns>None</returns>
173-
[HttpPut]
174173
[ProducesResponseType( Status204NoContent )]
175174
[ProducesResponseType( Status404NotFound )]
176175
public IActionResult CreateRefToSupplier( [FromODataUri] int key, [FromODataUri] string navigationProperty, [FromBody] Uri link ) => NoContent();

samples/aspnetcore/SwaggerODataSample/V3/SuppliersController.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,6 @@ public IActionResult Put( [FromODataUri] int key, [FromBody] Supplier update )
142142
/// <param name="navigationProperty">The product to link.</param>
143143
/// <param name="link">The product identifier.</param>
144144
/// <returns>None</returns>
145-
[HttpPost]
146145
[ProducesResponseType( Status204NoContent )]
147146
[ProducesResponseType( Status404NotFound )]
148147
public IActionResult CreateRefToProducts( [FromODataUri] int key, [FromODataUri] string navigationProperty, [FromBody] Uri link ) => NoContent();

samples/aspnetcore/SwaggerSample/SwaggerSample.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
<ItemGroup>
1010
<PackageReference Include="Microsoft.Extensions.PlatformAbstractions" Version="1.1.0" />
11-
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.0.2-*" />
11+
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.1.4-*" />
1212
</ItemGroup>
1313

1414
<ItemGroup>

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

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,9 @@
2727
public sealed class DefaultModelTypeBuilder : IModelTypeBuilder
2828
{
2929
static readonly Type IEnumerableOfT = typeof( IEnumerable<> );
30-
readonly ConcurrentDictionary<ApiVersion, ModuleBuilder> modules = new ConcurrentDictionary<ApiVersion, ModuleBuilder>();
31-
readonly ConcurrentDictionary<ApiVersion, IDictionary<EdmTypeKey, Type>> generatedEdmTypesPerVersion =
32-
new ConcurrentDictionary<ApiVersion, IDictionary<EdmTypeKey, Type>>();
33-
readonly ConcurrentDictionary<ApiVersion, ConcurrentDictionary<EdmTypeKey, Type>> generatedActionParamsPerVersion =
34-
new ConcurrentDictionary<ApiVersion, ConcurrentDictionary<EdmTypeKey, Type>>();
30+
readonly ConcurrentDictionary<ApiVersion, ModuleBuilder> modules = new();
31+
readonly ConcurrentDictionary<ApiVersion, IDictionary<EdmTypeKey, Type>> generatedEdmTypesPerVersion = new();
32+
readonly ConcurrentDictionary<ApiVersion, ConcurrentDictionary<EdmTypeKey, Type>> generatedActionParamsPerVersion = new();
3533

3634
/// <inheritdoc />
3735
public Type NewStructuredType( IEdmStructuredType structuredType, Type clrType, ApiVersion apiVersion, IEdmModel edmModel )
@@ -152,9 +150,12 @@ static Tuple<bool, bool> BuildSignatureProperties(
152150
var visitedEdmTypes = context.VisitedEdmTypes;
153151
var clrTypeMatchesEdmType = true;
154152
var hasUnfinishedTypes = false;
153+
var clrProperties = clrType.GetProperties( Public | Instance );
155154

156-
foreach ( var property in clrType.GetProperties( Public | Instance ) )
155+
for ( var i = 0; i < clrProperties.Length; i++ )
157156
{
157+
var property = clrProperties[i];
158+
158159
if ( !structuralProperties.TryGetValue( property.Name, out var structuralProperty ) &&
159160
!mappedClrProperties.TryGetValue( property, out structuralProperty ) )
160161
{

src/Common.OData.ApiExplorer/CollectionExtensions.cs

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,29 @@ static class CollectionExtensions
1010
{
1111
internal static void AddRange<T>( this ICollection<T> collection, IEnumerable<T> items )
1212
{
13-
foreach ( var item in items )
13+
switch ( items )
1414
{
15-
collection.Add( item );
15+
case IList<T> list:
16+
for ( var i = 0; i < list.Count; i++ )
17+
{
18+
collection.Add( list[i] );
19+
}
20+
21+
break;
22+
case IReadOnlyList<T> list:
23+
for ( var i = 0; i < list.Count; i++ )
24+
{
25+
collection.Add( list[i] );
26+
}
27+
28+
break;
29+
default:
30+
foreach ( var item in items )
31+
{
32+
collection.Add( item );
33+
}
34+
35+
break;
1636
}
1737
}
1838
}

src/Common.OData.ApiExplorer/OData.Edm/EdmExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ static class EdmExtensions
2121
{
2222
internal static Type? GetClrType( this IEdmType edmType, IEdmModel edmModel )
2323
{
24-
if ( !( edmType is IEdmSchemaType schemaType ) )
24+
if ( edmType is not IEdmSchemaType schemaType )
2525
{
2626
return null;
2727
}

src/Common.OData/TypeExtensions.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,5 +38,17 @@ internal static bool IsModelBound( this Type type ) =>
3838
Delta.IsAssignableFrom( type ) ||
3939
ODataActionParameters.IsAssignableFrom( type ) ||
4040
ODataParameterHelper.Equals( type );
41+
42+
#if !WEBAPI
43+
static readonly Type ODataModelBinder = ResolveType( "Microsoft.AspNet.OData.Formatter.ODataModelBinder" );
44+
45+
internal static bool IsODataModelBinder( this Type type ) => ODataModelBinder.IsAssignableFrom( type );
46+
47+
static Type ResolveType( string name )
48+
{
49+
var assembly = ODataRoutingAttributeType.Assembly;
50+
return assembly.GetType( name, throwOnError: true )!;
51+
}
52+
#endif
4153
}
4254
}

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,11 @@
2323
using System.Linq;
2424
using System.Threading.Tasks;
2525
using static Microsoft.AspNet.OData.Routing.ODataRouteActionType;
26+
using static Microsoft.AspNet.OData.Routing.ODataRouteConstants;
2627
using static Microsoft.AspNetCore.Http.StatusCodes;
2728
using static Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource;
2829
using static System.Linq.Enumerable;
2930
using static System.StringComparison;
30-
using static Microsoft.AspNet.OData.Routing.ODataRouteConstants;
3131

3232
/// <summary>
3333
/// Represents an API explorer that provides <see cref="ApiDescription">API descriptions</see> for actions represented by
@@ -570,6 +570,10 @@ static void UpdateBindingInfo( ApiParameterContext context, ParameterDescriptor
570570
{
571571
bindingInfo.BindingSource = Special;
572572
}
573+
else if ( bindingInfo.BinderType.IsODataModelBinder() )
574+
{
575+
bindingInfo.BindingSource = default;
576+
}
573577
}
574578

575579
if ( bindingInfo.BindingSource != null )
@@ -584,7 +588,7 @@ static void UpdateBindingInfo( ApiParameterContext context, ParameterDescriptor
584588
switch ( context.RouteContext.ActionType )
585589
{
586590
case EntitySet:
587-
var keys = context.RouteContext.EntitySet.EntityType().Key().ToArray();
591+
var keys = context.RouteContext.EntitySet.EntityType().Key();
588592

589593
key = keys.FirstOrDefault( k => k.Name.Equals( paramName, OrdinalIgnoreCase ) );
590594

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,10 @@ void UpdateBindingInfo( ActionParameterContext context, ParameterDescriptor para
192192
{
193193
bindingInfo.BindingSource = Special;
194194
}
195+
else if ( bindingInfo.BinderType.IsODataModelBinder() )
196+
{
197+
bindingInfo.BindingSource = default;
198+
}
195199
}
196200

197201
if ( bindingInfo.BindingSource != null )
@@ -206,8 +210,7 @@ void UpdateBindingInfo( ActionParameterContext context, ParameterDescriptor para
206210
switch ( context.RouteContext.ActionType )
207211
{
208212
case EntitySet:
209-
210-
var keys = context.RouteContext.EntitySet.EntityType().Key().ToArray();
213+
var keys = context.RouteContext.EntitySet.EntityType().Key();
211214

212215
key = keys.FirstOrDefault( k => k.Name.Equals( paramName, OrdinalIgnoreCase ) );
213216

@@ -229,7 +232,6 @@ void UpdateBindingInfo( ActionParameterContext context, ParameterDescriptor para
229232
break;
230233
case BoundOperation:
231234
case UnboundOperation:
232-
233235
var operation = context.RouteContext.Operation;
234236

235237
if ( operation == null )

0 commit comments

Comments
 (0)