Skip to content

Commit 9d18108

Browse files
Added IEndpointInspector so that controller action endpoints are not processed by min api endpoint collators. Related #1066
1 parent 1c13628 commit 9d18108

File tree

8 files changed

+112
-22
lines changed

8 files changed

+112
-22
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Copyright (c) .NET Foundation and contributors. All rights reserved.
2+
3+
namespace Asp.Versioning.ApiExplorer;
4+
5+
using Microsoft.AspNetCore.Http;
6+
7+
/// <summary>
8+
/// Represents the default <see cref="IEndpointInspector">endpoint inspector</see>.
9+
/// </summary>
10+
[CLSCompliant(false)]
11+
public sealed class DefaultEndpointInspector : IEndpointInspector
12+
{
13+
/// <inheritdoc />
14+
public bool IsControllerAction( Endpoint endpoint ) => false;
15+
}

src/AspNetCore/WebApi/src/Asp.Versioning.Http/ApiExplorer/EndpointApiVersionMetadataCollationProvider.cs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,29 @@ namespace Asp.Versioning.ApiExplorer;
1212
public sealed class EndpointApiVersionMetadataCollationProvider : IApiVersionMetadataCollationProvider
1313
{
1414
private readonly EndpointDataSource endpointDataSource;
15+
private readonly IEndpointInspector endpointInspector;
1516
private int version;
1617

1718
/// <summary>
1819
/// Initializes a new instance of the <see cref="EndpointApiVersionMetadataCollationProvider"/> class.
1920
/// </summary>
2021
/// <param name="endpointDataSource">The underlying <see cref="endpointDataSource">endpoint data source</see>.</param>
22+
[Obsolete( "Use the constructor that accepts IEndpointInspector. This constructor will be removed in a future version." )]
2123
public EndpointApiVersionMetadataCollationProvider( EndpointDataSource endpointDataSource )
24+
: this( endpointDataSource, new DefaultEndpointInspector() ) { }
25+
26+
/// <summary>
27+
/// Initializes a new instance of the <see cref="EndpointApiVersionMetadataCollationProvider"/> class.
28+
/// </summary>
29+
/// <param name="endpointDataSource">The underlying <see cref="endpointDataSource">endpoint data source</see>.</param>
30+
/// <param name="endpointInspector">The <see cref="IEndpointInspector">endpoint inspector</see> used to inspect endpoints.</param>
31+
public EndpointApiVersionMetadataCollationProvider( EndpointDataSource endpointDataSource, IEndpointInspector endpointInspector )
2232
{
23-
this.endpointDataSource = endpointDataSource ?? throw new ArgumentNullException( nameof( endpointDataSource ) );
33+
ArgumentNullException.ThrowIfNull( endpointDataSource );
34+
ArgumentNullException.ThrowIfNull( endpointInspector );
35+
36+
this.endpointDataSource = endpointDataSource;
37+
this.endpointInspector = endpointInspector;
2438
ChangeToken.OnChange( endpointDataSource.GetChangeToken, () => ++version );
2539
}
2640

@@ -38,7 +52,8 @@ public void Execute( ApiVersionMetadataCollationContext context )
3852
{
3953
var endpoint = endpoints[i];
4054

41-
if ( endpoint.Metadata.GetMetadata<ApiVersionMetadata>() is not ApiVersionMetadata item )
55+
if ( endpoint.Metadata.GetMetadata<ApiVersionMetadata>() is not ApiVersionMetadata item ||
56+
endpointInspector.IsControllerAction( endpoint ) )
4257
{
4358
continue;
4459
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Copyright (c) .NET Foundation and contributors. All rights reserved.
2+
3+
namespace Asp.Versioning.ApiExplorer;
4+
5+
using Microsoft.AspNetCore.Http;
6+
7+
/// <summary>
8+
/// Defines the behavior of an endpoint inspector.
9+
/// </summary>
10+
[CLSCompliant( false )]
11+
public interface IEndpointInspector
12+
{
13+
/// <summary>
14+
/// Determines whether the specified endpoint is a controller action.
15+
/// </summary>
16+
/// <param name="endpoint">The <see cref="Endpoint">endpoint</see> to inspect.</param>
17+
/// <returns>True if the <paramref name="endpoint"/> is for a controller action; otherwise, false.</returns>
18+
bool IsControllerAction( Endpoint endpoint );
19+
}

src/AspNetCore/WebApi/src/Asp.Versioning.Http/DependencyInjection/IServiceCollectionExtensions.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ private static void AddApiVersioningServices( IServiceCollection services )
8989
services.TryAddEnumerable( Transient<IPostConfigureOptions<RouteOptions>, ApiVersioningRouteOptionsSetup>() );
9090
services.TryAddEnumerable( Singleton<MatcherPolicy, ApiVersionMatcherPolicy>() );
9191
services.TryAddEnumerable( Singleton<IApiVersionMetadataCollationProvider, EndpointApiVersionMetadataCollationProvider>() );
92+
services.TryAddTransient<IEndpointInspector, DefaultEndpointInspector>();
9293
services.Replace( WithLinkGeneratorDecorator( services ) );
9394
TryAddProblemDetailsRfc7231Compliance( services );
9495
TryAddErrorObjectJsonOptions( services );

src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/ApiVersionDescriptionProviderFactory.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,26 +16,29 @@ internal sealed class ApiVersionDescriptionProviderFactory : IApiVersionDescript
1616
{
1717
private readonly ISunsetPolicyManager sunsetPolicyManager;
1818
private readonly IApiVersionMetadataCollationProvider[] providers;
19+
private readonly IEndpointInspector endpointInspector;
1920
private readonly IOptions<ApiExplorerOptions> options;
2021
private readonly Activator activator;
2122

2223
public ApiVersionDescriptionProviderFactory(
2324
Activator activator,
2425
ISunsetPolicyManager sunsetPolicyManager,
2526
IEnumerable<IApiVersionMetadataCollationProvider> providers,
27+
IEndpointInspector endpointInspector,
2628
IOptions<ApiExplorerOptions> options )
2729
{
2830
this.activator = activator;
2931
this.sunsetPolicyManager = sunsetPolicyManager;
3032
this.providers = providers.ToArray();
33+
this.endpointInspector = endpointInspector;
3134
this.options = options;
3235
}
3336

3437
public IApiVersionDescriptionProvider Create( EndpointDataSource endpointDataSource )
3538
{
3639
var collators = new List<IApiVersionMetadataCollationProvider>( capacity: providers.Length + 1 )
3740
{
38-
new EndpointApiVersionMetadataCollationProvider( endpointDataSource ),
41+
new EndpointApiVersionMetadataCollationProvider( endpointDataSource, endpointInspector ),
3942
};
4043

4144
collators.AddRange( providers );

src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/DependencyInjection/IApiVersioningBuilderExtensions.cs

Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@ namespace Microsoft.Extensions.DependencyInjection;
55
using Asp.Versioning;
66
using Asp.Versioning.ApiExplorer;
77
using Microsoft.AspNetCore.Builder;
8+
using Microsoft.AspNetCore.Http;
89
using Microsoft.AspNetCore.Mvc.ApiExplorer;
910
using Microsoft.AspNetCore.Mvc.ModelBinding;
1011
using Microsoft.AspNetCore.Routing;
1112
using Microsoft.Extensions.DependencyInjection.Extensions;
1213
using Microsoft.Extensions.Options;
14+
using Microsoft.Extensions.Primitives;
1315
using static ServiceDescriptor;
1416

1517
/// <summary>
@@ -70,40 +72,36 @@ private static IApiVersionDescriptionProviderFactory ResolveApiVersionDescriptio
7072
{
7173
var sunsetPolicyManager = serviceProvider.GetRequiredService<ISunsetPolicyManager>();
7274
var providers = serviceProvider.GetServices<IApiVersionMetadataCollationProvider>();
75+
var inspector = serviceProvider.GetRequiredService<IEndpointInspector>();
7376
var options = serviceProvider.GetRequiredService<IOptions<ApiExplorerOptions>>();
74-
var mightUseCustomGroups = options.Value.FormatGroupName is not null;
7577

7678
return new ApiVersionDescriptionProviderFactory(
77-
mightUseCustomGroups ? NewGroupedProvider : NewDefaultProvider,
79+
NewDefaultProvider,
7880
sunsetPolicyManager,
7981
providers,
82+
inspector,
8083
options );
8184

82-
static IApiVersionDescriptionProvider NewDefaultProvider(
85+
static DefaultApiVersionDescriptionProvider NewDefaultProvider(
8386
IEnumerable<IApiVersionMetadataCollationProvider> providers,
8487
ISunsetPolicyManager sunsetPolicyManager,
8588
IOptions<ApiExplorerOptions> apiExplorerOptions ) =>
86-
new DefaultApiVersionDescriptionProvider( providers, sunsetPolicyManager, apiExplorerOptions );
87-
88-
static IApiVersionDescriptionProvider NewGroupedProvider(
89-
IEnumerable<IApiVersionMetadataCollationProvider> providers,
90-
ISunsetPolicyManager sunsetPolicyManager,
91-
IOptions<ApiExplorerOptions> apiExplorerOptions ) =>
92-
new GroupedApiVersionDescriptionProvider( providers, sunsetPolicyManager, apiExplorerOptions );
89+
new( providers, sunsetPolicyManager, apiExplorerOptions );
9390
}
9491

9592
private static IApiVersionDescriptionProvider ResolveApiVersionDescriptionProvider( IServiceProvider serviceProvider )
9693
{
97-
var providers = serviceProvider.GetServices<IApiVersionMetadataCollationProvider>();
98-
var sunsetPolicyManager = serviceProvider.GetRequiredService<ISunsetPolicyManager>();
99-
var options = serviceProvider.GetRequiredService<IOptions<ApiExplorerOptions>>();
100-
var mightUseCustomGroups = options.Value.FormatGroupName is not null;
94+
var factory = serviceProvider.GetRequiredService<IApiVersionDescriptionProviderFactory>();
95+
var endpointDataSource = new EmptyEndpointDataSource();
96+
return factory.Create( endpointDataSource );
97+
}
98+
99+
private sealed class EmptyEndpointDataSource : EndpointDataSource
100+
{
101+
public override IReadOnlyList<Endpoint> Endpoints => Array.Empty<Endpoint>();
101102

102-
if ( mightUseCustomGroups )
103-
{
104-
return new GroupedApiVersionDescriptionProvider( providers, sunsetPolicyManager, options );
105-
}
103+
public override IChangeToken GetChangeToken() => new CancellationChangeToken( CancellationToken.None );
106104

107-
return new DefaultApiVersionDescriptionProvider( providers, sunsetPolicyManager, options );
105+
public override IReadOnlyList<Endpoint> GetGroupedEndpoints( RouteGroupContext context ) => Array.Empty<Endpoint>();
108106
}
109107
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Copyright (c) .NET Foundation and contributors. All rights reserved.
2+
3+
namespace Asp.Versioning.ApiExplorer;
4+
5+
using Microsoft.AspNetCore.Http;
6+
using Microsoft.AspNetCore.Mvc;
7+
8+
/// <summary>
9+
/// Represents the <see cref="IEndpointInspector">inspector</see> that understands
10+
/// <see cref="Endpoint">endpoints</see> defined by MVC controllers.
11+
/// </summary>
12+
[CLSCompliant(false)]
13+
public sealed class MvcEndpointInspector : IEndpointInspector
14+
{
15+
/// <inheritdoc />
16+
public bool IsControllerAction( Endpoint endpoint )
17+
{
18+
ArgumentNullException.ThrowIfNull( endpoint );
19+
return endpoint.Metadata.Any( static attribute => attribute is ControllerAttribute );
20+
}
21+
}

src/AspNetCore/WebApi/src/Asp.Versioning.Mvc/DependencyInjection/IApiVersioningBuilderExtensions.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ private static void AddServices( IServiceCollection services )
6767
services.TryAddEnumerable( Transient<IApiControllerSpecification, ApiBehaviorSpecification>() );
6868
services.TryAddEnumerable( Singleton<IApiVersionMetadataCollationProvider, ActionApiVersionMetadataCollationProvider>() );
6969
services.Replace( WithUrlHelperFactoryDecorator( services ) );
70+
services.TryReplace<IEndpointInspector, DefaultEndpointInspector, MvcEndpointInspector>();
7071
}
7172

7273
private static object CreateInstance( this IServiceProvider services, ServiceDescriptor descriptor )
@@ -84,6 +85,23 @@ private static object CreateInstance( this IServiceProvider services, ServiceDes
8485
return ActivatorUtilities.GetServiceOrCreateInstance( services, descriptor.ImplementationType! );
8586
}
8687

88+
private static void TryReplace<TService, TImplementation, TReplacement>( this IServiceCollection services )
89+
{
90+
var serviceType = typeof( TService );
91+
var implementationType = typeof( TImplementation );
92+
93+
for ( var i = services.Count - 1; i >= 0; i-- )
94+
{
95+
var service = services[i];
96+
97+
if ( service.ServiceType == serviceType && service.ImplementationType == implementationType )
98+
{
99+
services[i] = Describe( serviceType, typeof( TReplacement ), service.Lifetime );
100+
break;
101+
}
102+
}
103+
}
104+
87105
[SkipLocalsInit]
88106
private static DecoratedServiceDescriptor WithUrlHelperFactoryDecorator( IServiceCollection services )
89107
{

0 commit comments

Comments
 (0)