Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 3f65e5b

Browse filesBrowse files
Chris Martinezcommonsensesoftware
authored andcommitted
Add support for exploring OData query options using an ad hoc EDM. Related #928
1 parent b893ad9 commit 3f65e5b
Copy full SHA for 3f65e5b

File tree

Expand file treeCollapse file tree

20 files changed

+857
-102
lines changed
Filter options
Expand file treeCollapse file tree

20 files changed

+857
-102
lines changed
+100Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
// Copyright (c) .NET Foundation and contributors. All rights reserved.
2+
3+
namespace Asp.Versioning.ApiExplorer;
4+
5+
using Asp.Versioning.Conventions;
6+
using Asp.Versioning.Description;
7+
using Asp.Versioning.OData;
8+
using Microsoft.OData.Edm;
9+
using System.Collections.Generic;
10+
using System.Web.Http;
11+
using System.Web.Http.Description;
12+
13+
internal sealed class AdHocEdmScope : IDisposable
14+
{
15+
private readonly IReadOnlyList<VersionedApiDescription> results;
16+
private bool disposed;
17+
18+
internal AdHocEdmScope(
19+
IReadOnlyList<VersionedApiDescription> apiDescriptions,
20+
VersionedODataModelBuilder builder )
21+
{
22+
var conventions = builder.ModelConfigurations.OfType<IODataQueryOptionsConvention>().ToArray();
23+
24+
results = FilterResults( apiDescriptions, conventions );
25+
26+
if ( results.Count > 0 )
27+
{
28+
ApplyAdHocEdm( builder.GetEdmModels(), results );
29+
}
30+
}
31+
32+
public void Dispose()
33+
{
34+
if ( disposed )
35+
{
36+
return;
37+
}
38+
39+
disposed = true;
40+
41+
for ( var i = 0; i < results.Count; i++ )
42+
{
43+
results[i].SetProperty( default( IEdmModel ) );
44+
}
45+
}
46+
47+
private static IReadOnlyList<VersionedApiDescription> FilterResults(
48+
IReadOnlyList<VersionedApiDescription> apiDescriptions,
49+
IReadOnlyList<IODataQueryOptionsConvention> conventions )
50+
{
51+
if ( conventions.Count == 0 )
52+
{
53+
return Array.Empty<VersionedApiDescription>();
54+
}
55+
56+
var results = default( List<VersionedApiDescription> );
57+
58+
for ( var i = 0; i < apiDescriptions.Count; i++ )
59+
{
60+
var apiDescription = apiDescriptions[i];
61+
62+
if ( apiDescription.EdmModel() != null || !apiDescription.IsODataLike() )
63+
{
64+
continue;
65+
}
66+
67+
results ??= new();
68+
results.Add( apiDescription );
69+
70+
for ( var j = 0; j < conventions.Count; j++ )
71+
{
72+
conventions[j].ApplyTo( apiDescription );
73+
}
74+
}
75+
76+
return results?.ToArray() ?? Array.Empty<VersionedApiDescription>();
77+
}
78+
79+
private static void ApplyAdHocEdm(
80+
IReadOnlyList<IEdmModel> models,
81+
IReadOnlyList<VersionedApiDescription> results )
82+
{
83+
for ( var i = 0; i < models.Count; i++ )
84+
{
85+
var model = models[i];
86+
var version = model.GetApiVersion();
87+
88+
for ( var j = 0; j < results.Count; j++ )
89+
{
90+
var result = results[j];
91+
var metadata = result.ActionDescriptor.GetApiVersionMetadata();
92+
93+
if ( metadata.IsMappedTo( version ) )
94+
{
95+
result.SetProperty( model );
96+
}
97+
}
98+
}
99+
}
100+
}

‎src/AspNet/OData/src/Asp.Versioning.WebApi.OData.ApiExplorer/ApiExplorer/ODataApiExplorer.cs

Copy file name to clipboardExpand all lines: src/AspNet/OData/src/Asp.Versioning.WebApi.OData.ApiExplorer/ApiExplorer/ODataApiExplorer.cs
+48-10Lines changed: 48 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ namespace Asp.Versioning.ApiExplorer;
77
using Asp.Versioning.OData;
88
using Asp.Versioning.Routing;
99
using Microsoft.AspNet.OData;
10+
using Microsoft.AspNet.OData.Builder;
1011
using Microsoft.AspNet.OData.Extensions;
1112
using Microsoft.AspNet.OData.Formatter;
1213
using Microsoft.AspNet.OData.Routing;
@@ -16,6 +17,7 @@ namespace Asp.Versioning.ApiExplorer;
1617
using Microsoft.OData.UriParser;
1718
using System.Collections.ObjectModel;
1819
using System.Net.Http.Formatting;
20+
using System.Runtime.CompilerServices;
1921
using System.Text.RegularExpressions;
2022
using System.Web.Http;
2123
using System.Web.Http.Controllers;
@@ -50,7 +52,11 @@ public ODataApiExplorer( HttpConfiguration configuration )
5052
/// <param name="configuration">The current <see cref="HttpConfiguration">HTTP configuration</see>.</param>
5153
/// <param name="options">The associated <see cref="ODataApiExplorerOptions">API explorer options</see>.</param>
5254
public ODataApiExplorer( HttpConfiguration configuration, ODataApiExplorerOptions options )
53-
: base( configuration, options ) => this.options = options;
55+
: base( configuration, options )
56+
{
57+
this.options = options;
58+
options.AdHocModelBuilder.OnModelCreated += MarkAsAdHoc;
59+
}
5460

5561
/// <summary>
5662
/// Gets the options associated with the API explorer.
@@ -172,7 +178,20 @@ protected override Collection<VersionedApiDescription> ExploreRouteControllers(
172178
if ( route is not ODataRoute )
173179
{
174180
apiDescriptions = base.ExploreRouteControllers( controllerMappings, route, apiVersion );
175-
return ExploreQueryOptions( route, apiDescriptions );
181+
182+
if ( Options.AdHocModelBuilder.ModelConfigurations.Count == 0 )
183+
{
184+
ExploreQueryOptions( route, apiDescriptions );
185+
}
186+
else if ( apiDescriptions.Count > 0 )
187+
{
188+
using ( new AdHocEdmScope( apiDescriptions, Options.AdHocModelBuilder ) )
189+
{
190+
ExploreQueryOptions( route, apiDescriptions );
191+
}
192+
}
193+
194+
return apiDescriptions;
176195
}
177196

178197
apiDescriptions = new();
@@ -199,7 +218,8 @@ protected override Collection<VersionedApiDescription> ExploreRouteControllers(
199218
}
200219
}
201220

202-
return ExploreQueryOptions( route, apiDescriptions );
221+
ExploreQueryOptions( route, apiDescriptions );
222+
return apiDescriptions;
203223
}
204224

205225
/// <inheritdoc />
@@ -210,7 +230,25 @@ protected override Collection<VersionedApiDescription> ExploreDirectRouteControl
210230
ApiVersion apiVersion )
211231
{
212232
var apiDescriptions = base.ExploreDirectRouteControllers( controllerDescriptor, candidateActionDescriptors, route, apiVersion );
213-
return ExploreQueryOptions( route, apiDescriptions );
233+
234+
if ( apiDescriptions.Count == 0 )
235+
{
236+
return apiDescriptions;
237+
}
238+
239+
if ( Options.AdHocModelBuilder.ModelConfigurations.Count == 0 )
240+
{
241+
ExploreQueryOptions( route, apiDescriptions );
242+
}
243+
else if ( apiDescriptions.Count > 0 )
244+
{
245+
using ( new AdHocEdmScope( apiDescriptions, Options.AdHocModelBuilder ) )
246+
{
247+
ExploreQueryOptions( route, apiDescriptions );
248+
}
249+
}
250+
251+
return apiDescriptions;
214252
}
215253

216254
/// <summary>
@@ -238,20 +276,20 @@ protected virtual void ExploreQueryOptions(
238276
queryOptions.ApplyTo( apiDescriptions, settings );
239277
}
240278

241-
private Collection<VersionedApiDescription> ExploreQueryOptions(
242-
IHttpRoute route,
243-
Collection<VersionedApiDescription> apiDescriptions )
279+
[MethodImpl( MethodImplOptions.AggressiveInlining )]
280+
private static void MarkAsAdHoc( ODataModelBuilder builder, IEdmModel model ) =>
281+
model.SetAnnotationValue( model, AdHocAnnotation.Instance );
282+
283+
private void ExploreQueryOptions( IHttpRoute route, Collection<VersionedApiDescription> apiDescriptions )
244284
{
245285
if ( apiDescriptions.Count == 0 )
246286
{
247-
return apiDescriptions;
287+
return;
248288
}
249289

250290
var uriResolver = Configuration.GetODataRootContainer( route ).GetRequiredService<ODataUriResolver>();
251291

252292
ExploreQueryOptions( apiDescriptions, uriResolver );
253-
254-
return apiDescriptions;
255293
}
256294

257295
private ResponseDescription CreateResponseDescriptionWithRoute(

‎src/AspNet/OData/src/Asp.Versioning.WebApi.OData.ApiExplorer/ApiExplorer/ODataApiExplorerOptions.cs

Copy file name to clipboardExpand all lines: src/AspNet/OData/src/Asp.Versioning.WebApi.OData.ApiExplorer/ApiExplorer/ODataApiExplorerOptions.cs
+2-1Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ public partial class ODataApiExplorerOptions : ApiExplorerOptions
1515
/// Initializes a new instance of the <see cref="ODataApiExplorerOptions"/> class.
1616
/// </summary>
1717
/// <param name="configuration">The current <see cref="HttpConfiguration">configuration</see> associated with the options.</param>
18-
public ODataApiExplorerOptions( HttpConfiguration configuration ) : base( configuration ) { }
18+
public ODataApiExplorerOptions( HttpConfiguration configuration )
19+
: base( configuration ) => AdHocModelBuilder = new( configuration );
1920

2021
/// <summary>
2122
/// Gets or sets a value indicating whether the API explorer settings are honored.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Copyright (c) .NET Foundation and contributors. All rights reserved.
2+
3+
namespace Asp.Versioning.Conventions;
4+
5+
using Asp.Versioning.OData;
6+
using System.Web.Http.Description;
7+
8+
/// <content>
9+
/// Provides additional implementation specific to ASP.NET Web API.
10+
/// </content>
11+
public partial class ImplicitModelBoundSettingsConvention : IModelConfiguration, IODataQueryOptionsConvention
12+
{
13+
/// <inheritdoc />
14+
public void ApplyTo( ApiDescription apiDescription )
15+
{
16+
var response = apiDescription.ResponseDescription;
17+
var type = response.ResponseType ?? response.DeclaredType;
18+
19+
if ( type == null )
20+
{
21+
return;
22+
}
23+
24+
if ( type.IsEnumerable( out var itemType ) )
25+
{
26+
type = itemType;
27+
}
28+
29+
types.Add( type! );
30+
}
31+
}

‎src/AspNet/OData/src/Asp.Versioning.WebApi.OData.ApiExplorer/Conventions/ODataQueryOptionsConventionBuilder.cs

Copy file name to clipboardExpand all lines: src/AspNet/OData/src/Asp.Versioning.WebApi.OData.ApiExplorer/Conventions/ODataQueryOptionsConventionBuilder.cs
-17Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
namespace Asp.Versioning.Conventions;
44

5-
using Microsoft.AspNet.OData;
65
using System.Runtime.CompilerServices;
76
using System.Web.Http.Description;
87

@@ -14,20 +13,4 @@ public partial class ODataQueryOptionsConventionBuilder
1413
[MethodImpl( MethodImplOptions.AggressiveInlining )]
1514
private static Type GetController( ApiDescription apiDescription ) =>
1615
apiDescription.ActionDescriptor.ControllerDescriptor.ControllerType;
17-
18-
[MethodImpl( MethodImplOptions.AggressiveInlining )]
19-
private static bool IsODataLike( ApiDescription description )
20-
{
21-
var parameters = description.ParameterDescriptions;
22-
23-
for ( var i = 0; i < parameters.Count; i++ )
24-
{
25-
if ( parameters[i].ParameterDescriptor.ParameterType.IsODataQueryOptions() )
26-
{
27-
return true;
28-
}
29-
}
30-
31-
return false;
32-
}
3316
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.