Skip to content

Commit 529e590

Browse files
Chris Martinezcommonsensesoftware
authored andcommitted
Clean up and improve dependency injection configurations
1 parent 34e178d commit 529e590

File tree

27 files changed

+586
-419
lines changed

27 files changed

+586
-419
lines changed
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
namespace Microsoft.AspNetCore.Mvc.ApiExplorer
2+
{
3+
using Microsoft.AspNetCore.Mvc.Versioning;
4+
using Microsoft.Extensions.Options;
5+
using System;
6+
using System.Collections.Generic;
7+
8+
/// <summary>
9+
/// Represents a factory to create API explorer options.
10+
/// </summary>
11+
/// <typeparam name="T">The type of options to create.</typeparam>
12+
[CLSCompliant( false )]
13+
public class ApiExplorerOptionsFactory<T> : IOptionsFactory<T> where T : ApiExplorerOptions, new()
14+
{
15+
readonly IOptions<ApiVersioningOptions> optionsHolder;
16+
17+
/// <summary>
18+
/// Initializes a new instance of the <see cref="ApiExplorerOptionsFactory{T}"/> class.
19+
/// </summary>
20+
/// <param name="options">The <see cref="ApiVersioningOptions">API versioning options</see>
21+
/// used to create API explorer options.</param>
22+
/// <param name="setups">The <see cref="IEnumerable{T}">sequence</see> of
23+
/// <see cref="IConfigureOptions{TOptions}">configuration actions</see> to run.</param>
24+
/// <param name="postConfigures">The <see cref="IEnumerable{T}">sequence</see> of
25+
/// <see cref="IPostConfigureOptions{TOptions}">initialization actions</see> to run.</param>
26+
public ApiExplorerOptionsFactory(
27+
IOptions<ApiVersioningOptions> options,
28+
IEnumerable<IConfigureOptions<T>> setups,
29+
IEnumerable<IPostConfigureOptions<T>> postConfigures )
30+
{
31+
Arg.NotNull( options, nameof( options ) );
32+
Arg.NotNull( setups, nameof( setups ) );
33+
Arg.NotNull( postConfigures, nameof( postConfigures ) );
34+
35+
optionsHolder = options;
36+
Setups = setups;
37+
PostConfigures = postConfigures;
38+
}
39+
40+
/// <summary>
41+
/// Gets the API versioning options associated with the factory.
42+
/// </summary>
43+
/// <value>The <see cref="ApiVersioningOptions">API versioning options</see> used to create API explorer options.</value>
44+
protected ApiVersioningOptions Options => optionsHolder.Value;
45+
46+
/// <summary>
47+
/// Gets the associated configuration actions to run.
48+
/// </summary>
49+
/// <value>The <see cref="IEnumerable{T}">sequence</see> of
50+
/// <see cref="IConfigureOptions{TOptions}">configuration actions</see> to run.</value>
51+
protected IEnumerable<IConfigureOptions<T>> Setups { get; }
52+
53+
/// <summary>
54+
/// Gets the associated initialization actions to run.
55+
/// </summary>
56+
/// <value>The <see cref="IEnumerable{T}">sequence</see> of
57+
/// <see cref="IPostConfigureOptions{TOptions}">initialization actions</see> to run.</value>
58+
protected IEnumerable<IPostConfigureOptions<T>> PostConfigures { get; }
59+
60+
/// <inheritdoc />
61+
public virtual T Create( string name )
62+
{
63+
var apiVersioningOptions = Options;
64+
var options = new T()
65+
{
66+
DefaultApiVersion = apiVersioningOptions.DefaultApiVersion,
67+
ApiVersionParameterSource = apiVersioningOptions.ApiVersionReader,
68+
AssumeDefaultVersionWhenUnspecified = apiVersioningOptions.AssumeDefaultVersionWhenUnspecified,
69+
};
70+
71+
foreach ( var setup in Setups )
72+
{
73+
if ( setup is IConfigureNamedOptions<T> namedSetup )
74+
{
75+
namedSetup.Configure( name, options );
76+
}
77+
else if ( name == Extensions.Options.Options.DefaultName )
78+
{
79+
setup.Configure( options );
80+
}
81+
}
82+
83+
foreach ( var post in PostConfigures )
84+
{
85+
post.PostConfigure( name, options );
86+
}
87+
88+
return options;
89+
}
90+
}
91+
}
Lines changed: 20 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
namespace Microsoft.Extensions.DependencyInjection
22
{
3-
using System;
4-
using System.Diagnostics.Contracts;
53
using Microsoft.AspNetCore.Mvc.ApiExplorer;
6-
using Microsoft.AspNetCore.Mvc.Versioning;
74
using Microsoft.Extensions.DependencyInjection.Extensions;
85
using Microsoft.Extensions.Options;
6+
using System;
7+
using System.Diagnostics.Contracts;
98
using static ServiceDescriptor;
109

1110
/// <summary>
@@ -19,7 +18,15 @@ public static class IServiceCollectionExtensions
1918
/// </summary>
2019
/// <param name="services">The <see cref="IServiceCollection">services</see> available in the application.</param>
2120
/// <returns>The original <paramref name="services"/> object.</returns>
22-
public static IServiceCollection AddVersionedApiExplorer( this IServiceCollection services ) => services.AddVersionedApiExplorer( _ => { } );
21+
public static IServiceCollection AddVersionedApiExplorer( this IServiceCollection services )
22+
{
23+
Arg.NotNull( services, nameof( services ) );
24+
Contract.Ensures( Contract.Result<IServiceCollection>() != null );
25+
26+
AddApiExplorerServices( services );
27+
28+
return services;
29+
}
2330

2431
/// <summary>
2532
/// Adds an API explorer that is API version aware.
@@ -31,35 +38,22 @@ public static IServiceCollection AddVersionedApiExplorer( this IServiceCollectio
3138
{
3239
Arg.NotNull( services, nameof( services ) );
3340
Arg.NotNull( setupAction, nameof( setupAction ) );
41+
Contract.Ensures( Contract.Result<IServiceCollection>() != null );
3442

35-
services.AddMvcCore().AddApiExplorer();
36-
services.Add( Singleton( serviceProvider => NewOptions( serviceProvider, setupAction ) ) );
37-
services.TryAddSingleton<IApiVersionDescriptionProvider, DefaultApiVersionDescriptionProvider>();
38-
services.TryAddSingleton<IApiDescriptionGroupCollectionProvider, ApiDescriptionGroupCollectionProvider>();
39-
services.TryAddEnumerable( Transient<IApiDescriptionProvider, VersionedApiDescriptionProvider>() );
43+
AddApiExplorerServices( services );
44+
services.Configure( setupAction );
4045

4146
return services;
4247
}
4348

44-
static IOptions<ApiExplorerOptions> NewOptions( IServiceProvider serviceProvider, Action<ApiExplorerOptions> setupAction )
49+
static void AddApiExplorerServices( IServiceCollection services )
4550
{
46-
Contract.Requires( serviceProvider != null );
47-
Contract.Requires( setupAction != null );
48-
Contract.Ensures( Contract.Result<IOptions<ApiExplorerOptions>>() != null );
49-
50-
var versioningOptions = serviceProvider.GetService<IOptions<ApiVersioningOptions>>()?.Value;
51-
var options = new ApiExplorerOptions();
52-
53-
if ( versioningOptions != null )
54-
{
55-
options.DefaultApiVersion = versioningOptions.DefaultApiVersion;
56-
options.ApiVersionParameterSource = versioningOptions.ApiVersionReader;
57-
options.AssumeDefaultVersionWhenUnspecified = versioningOptions.AssumeDefaultVersionWhenUnspecified;
58-
}
51+
Contract.Requires( services != null );
5952

60-
setupAction( options );
61-
62-
return new OptionsWrapper<ApiExplorerOptions>( options );
53+
services.AddMvcCore().AddApiExplorer();
54+
services.TryAdd( Singleton<IOptionsFactory<ApiExplorerOptions>, ApiExplorerOptionsFactory<ApiExplorerOptions>>() );
55+
services.TryAddSingleton<IApiVersionDescriptionProvider, DefaultApiVersionDescriptionProvider>();
56+
services.TryAddEnumerable( Transient<IApiDescriptionProvider, VersionedApiDescriptionProvider>() );
6357
}
6458
}
6559
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
namespace Microsoft.Extensions.DependencyInjection
2+
{
3+
using Microsoft.AspNetCore.Builder;
4+
using Microsoft.AspNetCore.Hosting;
5+
using Microsoft.AspNetCore.Mvc.Routing;
6+
using Microsoft.AspNetCore.Mvc.Versioning;
7+
using Microsoft.Extensions.Options;
8+
using System;
9+
using System.Diagnostics.Contracts;
10+
11+
sealed class AutoRegisterMiddleware : IStartupFilter
12+
{
13+
readonly IApiVersionRoutePolicy routePolicy;
14+
readonly IOptions<ApiVersioningOptions> options;
15+
16+
public AutoRegisterMiddleware( IApiVersionRoutePolicy routePolicy, IOptions<ApiVersioningOptions> options )
17+
{
18+
Contract.Requires( routePolicy != null );
19+
Contract.Requires( options != null );
20+
21+
this.routePolicy = routePolicy;
22+
this.options = options;
23+
}
24+
25+
public Action<IApplicationBuilder> Configure( Action<IApplicationBuilder> next )
26+
{
27+
Contract.Requires( next != null );
28+
Contract.Ensures( Contract.Result<Action<IApplicationBuilder>>() != null );
29+
30+
return app =>
31+
{
32+
if ( options.Value.RegisterMiddleware )
33+
{
34+
app.UseApiVersioning();
35+
}
36+
37+
next( app );
38+
app.UseRouter( builder => builder.Routes.Add( new CatchAllRouteHandler( routePolicy ) ) );
39+
};
40+
}
41+
}
42+
}
Lines changed: 33 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
namespace Microsoft.Extensions.DependencyInjection
22
{
3-
using System;
4-
using System.Diagnostics.Contracts;
5-
using Microsoft.AspNetCore.Builder;
63
using Microsoft.AspNetCore.Hosting;
74
using Microsoft.AspNetCore.Mvc;
85
using Microsoft.AspNetCore.Mvc.Abstractions;
6+
using Microsoft.AspNetCore.Mvc.ApplicationModels;
97
using Microsoft.AspNetCore.Mvc.Infrastructure;
108
using Microsoft.AspNetCore.Mvc.Routing;
119
using Microsoft.AspNetCore.Mvc.Versioning;
10+
using Microsoft.AspNetCore.Routing;
1211
using Microsoft.Extensions.DependencyInjection.Extensions;
1312
using Microsoft.Extensions.Options;
13+
using System;
14+
using System.Diagnostics.Contracts;
1415
using static ServiceDescriptor;
1516

1617
/// <summary>
@@ -24,7 +25,15 @@ public static class IServiceCollectionExtensions
2425
/// </summary>
2526
/// <param name="services">The <see cref="IServiceCollection">services</see> available in the application.</param>
2627
/// <returns>The original <paramref name="services"/> object.</returns>
27-
public static IServiceCollection AddApiVersioning( this IServiceCollection services ) => services.AddApiVersioning( _ => { } );
28+
public static IServiceCollection AddApiVersioning( this IServiceCollection services )
29+
{
30+
Arg.NotNull( services, nameof( services ) );
31+
Contract.Ensures( Contract.Result<IServiceCollection>() != null );
32+
33+
AddApiVersioningServices( services );
34+
35+
return services;
36+
}
2837

2938
/// <summary>
3039
/// Adds service API versioning to the specified services collection.
@@ -38,76 +47,38 @@ public static IServiceCollection AddApiVersioning( this IServiceCollection servi
3847
Arg.NotNull( setupAction, nameof( setupAction ) );
3948
Contract.Ensures( Contract.Result<IServiceCollection>() != null );
4049

41-
var options = new ApiVersioningOptions();
42-
43-
setupAction( options );
44-
services.Add( new ServiceDescriptor( typeof( IApiVersionReader ), options.ApiVersionReader ) );
45-
services.Add( new ServiceDescriptor( typeof( IApiVersionSelector ), options.ApiVersionSelector ) );
46-
services.Add( new ServiceDescriptor( typeof( IErrorResponseProvider ), options.ErrorResponses ) );
47-
services.Add( Singleton<IOptions<ApiVersioningOptions>>( new OptionsWrapper<ApiVersioningOptions>( options ) ) );
48-
services.Replace( Singleton<IActionSelector, ApiVersionActionSelector>() );
49-
services.TryAddSingleton<IApiVersionRoutePolicy, DefaultApiVersionRoutePolicy>();
50-
services.AddTransient<IStartupFilter, AutoRegisterMiddleware>();
51-
services.TryAddSingleton<ReportApiVersionsAttribute>();
52-
services.AddMvcCore( mvcOptions => AddMvcOptions( mvcOptions, options ) );
53-
services.AddRouting( routeOptions => routeOptions.ConstraintMap.Add( options.RouteConstraintName, typeof( ApiVersionRouteConstraint ) ) );
54-
55-
if ( options.ReportApiVersions )
56-
{
57-
services.TryAddSingleton<IReportApiVersions, DefaultApiVersionReporter>();
58-
services.AddTransient<IActionDescriptorProvider, ApiVersionCollator>();
59-
}
60-
else
61-
{
62-
services.TryAddSingleton<IReportApiVersions, DoNotReportApiVersions>();
63-
}
50+
AddApiVersioningServices( services );
51+
services.Configure( setupAction );
6452

6553
return services;
6654
}
6755

68-
static void AddMvcOptions( MvcOptions mvcOptions, ApiVersioningOptions options )
56+
static void AddApiVersioningServices( IServiceCollection services )
6957
{
70-
Contract.Requires( mvcOptions != null );
71-
Contract.Requires( options != null );
72-
73-
if ( options.ReportApiVersions )
74-
{
75-
mvcOptions.Filters.AddService<ReportApiVersionsAttribute>();
76-
}
77-
78-
mvcOptions.Conventions.Add( new ApiVersionConvention( options.DefaultApiVersion, options.Conventions ) );
58+
services.Add( Singleton( sp => sp.GetRequiredService<IOptions<ApiVersioningOptions>>().Value.ApiVersionReader ) );
59+
services.Add( Singleton( sp => sp.GetRequiredService<IOptions<ApiVersioningOptions>>().Value.ApiVersionSelector ) );
60+
services.Add( Singleton( sp => sp.GetRequiredService<IOptions<ApiVersioningOptions>>().Value.ErrorResponses ) );
61+
services.Replace( Singleton<IActionSelector, ApiVersionActionSelector>() );
62+
services.TryAddSingleton<IApiVersionRoutePolicy, DefaultApiVersionRoutePolicy>();
63+
services.TryAddSingleton<ReportApiVersionsAttribute>();
64+
services.TryAddSingleton( OnRequestIReportApiVersions );
65+
services.TryAddEnumerable( Transient<IPostConfigureOptions<MvcOptions>, ApiVersioningMvcOptionsSetup>() );
66+
services.TryAddEnumerable( Transient<IPostConfigureOptions<RouteOptions>, ApiVersioningRouteOptionsSetup>() );
67+
services.TryAddEnumerable( Transient<IApplicationModelProvider, ApiVersioningApplicationModelProvider>() );
68+
services.TryAddEnumerable( Transient<IActionDescriptorProvider, ApiVersionCollator>() );
69+
services.AddTransient<IStartupFilter, AutoRegisterMiddleware>();
7970
}
8071

81-
sealed class AutoRegisterMiddleware : IStartupFilter
72+
static IReportApiVersions OnRequestIReportApiVersions( IServiceProvider serviceProvider )
8273
{
83-
readonly IApiVersionRoutePolicy routePolicy;
84-
readonly IOptions<ApiVersioningOptions> options;
74+
var options = serviceProvider.GetRequiredService<IOptions<ApiVersioningOptions>>().Value;
8575

86-
public AutoRegisterMiddleware( IApiVersionRoutePolicy routePolicy, IOptions<ApiVersioningOptions> options )
76+
if ( options.ReportApiVersions )
8777
{
88-
Contract.Requires( routePolicy != null );
89-
Contract.Requires( options != null );
90-
91-
this.routePolicy = routePolicy;
92-
this.options = options;
78+
return new DefaultApiVersionReporter();
9379
}
9480

95-
public Action<IApplicationBuilder> Configure( Action<IApplicationBuilder> next )
96-
{
97-
Contract.Requires( next != null );
98-
Contract.Ensures( Contract.Result<Action<IApplicationBuilder>>() != null );
99-
100-
return app =>
101-
{
102-
if ( options.Value.RegisterMiddleware )
103-
{
104-
app.UseApiVersioning();
105-
}
106-
107-
next( app );
108-
app.UseRouter( builder => builder.Routes.Add( new CatchAllRouteHandler( routePolicy ) ) );
109-
};
110-
}
81+
return new DoNotReportApiVersions();
11182
}
11283
}
11384
}

0 commit comments

Comments
 (0)