Skip to content

Commit

Permalink
Refactor to use a default internal container and dependency resolver …
Browse files Browse the repository at this point in the history
…instead of static singletons
  • Loading branch information
commonsensesoftware committed Aug 30, 2023
1 parent 49cdc8d commit d0b4fd5
Show file tree
Hide file tree
Showing 20 changed files with 226 additions and 55 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,7 @@ public void api_description_group_should_explore_model_bound_settings()
configuration.EnableDependencyInjection();
configuration.AddApiVersioning();
configuration.MapHttpAttributeRoutes();
configuration.EnsureInitialized();

var apiVersion = new ApiVersion( 1.0 );
var options = new ODataApiExplorerOptions( configuration );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ public async Task options_should_return_expected_headers()
configuration.AddApiVersioning(
options =>
{
options.ReportApiVersions = true;
options.Policies.Sunset( "VersionedMetadata" )
.Link( "policies" )
.Title( "Versioning Policy" )
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,6 @@ namespace Asp.Versioning;
/// </content>
public partial class DefaultApiVersionReporter
{
private static DefaultApiVersionReporter? instance;

internal static IReportApiVersions GetOrCreate( ISunsetPolicyManager sunsetPolicyManager ) =>
instance ??= new( sunsetPolicyManager );

private static void AddApiVersionHeader( HttpResponseHeaders headers, string headerName, IReadOnlyList<ApiVersion> versions )
{
if ( versions.Count == 0 || headers.Contains( headerName ) )
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.

namespace Asp.Versioning.Dependencies;

using Asp.Versioning;
using Asp.Versioning.Conventions;
using System.ComponentModel.Design;
using System.Web.Http.Dependencies;

internal sealed class DefaultContainer : IDependencyResolver, IDependencyScope
{
private readonly ServiceContainer container = new();
private bool disposed;

internal DefaultContainer()
{
container.AddService( typeof( ApiVersioningOptions ), static ( sc, t ) => new ApiVersioningOptions() );
container.AddService( typeof( IApiVersionParser ), static ( sc, t ) => ApiVersionParser.Default );
container.AddService( typeof( IControllerNameConvention ), static ( sc, t ) => ControllerNameConvention.Default );
container.AddService( typeof( IProblemDetailsFactory ), static ( sc, t ) => new ProblemDetailsFactory() );
container.AddService( typeof( ISunsetPolicyManager ), NewSunsetPolicyManager );
container.AddService( typeof( IReportApiVersions ), NewApiVersionReporter );
}

public ApiVersioningOptions ApiVersioningOptions
{
get => GetApiVersioningOptions( container );
set
{
container.RemoveService( typeof( ApiVersioningOptions ) );
container.AddService( typeof( ApiVersioningOptions ), value );
}
}

public void Replace( Type serviceType, ServiceCreatorCallback activator )
{
container.RemoveService( serviceType );
container.AddService( serviceType, activator );
}

public IDependencyScope BeginScope() => this;

public void Dispose()
{
if ( disposed )
{
return;
}

disposed = true;
container.Dispose();
}

public object GetService( Type serviceType ) => container.GetService( serviceType );

public IEnumerable<object> GetServices( Type serviceType )
{
var service = container.GetService( serviceType );

if ( service is not null )
{
yield return service;
}
}

private static ApiVersioningOptions GetApiVersioningOptions( IServiceProvider serviceProvider ) =>
(ApiVersioningOptions) serviceProvider.GetService( typeof( ApiVersioningOptions ) );

private static ISunsetPolicyManager NewSunsetPolicyManager( IServiceProvider serviceProvider, Type type ) =>
new SunsetPolicyManager( GetApiVersioningOptions( serviceProvider ) );

private static IReportApiVersions NewApiVersionReporter( IServiceProvider serviceProvider, Type type )
{
var options = GetApiVersioningOptions( serviceProvider );

if ( options.ReportApiVersions )
{
var sunsetPolicyManager = (ISunsetPolicyManager) serviceProvider.GetService( typeof( ISunsetPolicyManager ) );
return new DefaultApiVersionReporter( sunsetPolicyManager );
}

return new DoNotReportApiVersions();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.

namespace Asp.Versioning.Dependencies;

using System.Web.Http;
using System.Web.Http.Dependencies;

internal sealed class DependencyResolverChain : IDependencyResolver
{
private readonly HttpConfiguration configuration;
private readonly IDependencyResolver resolver;

internal DependencyResolverChain( HttpConfiguration configuration )
{
this.configuration = configuration;
resolver = configuration.DependencyResolver;
}

public IDependencyScope BeginScope() =>
new DependencyScopeChain( configuration, resolver.BeginScope() );

public void Dispose() => resolver.Dispose();

public object GetService( Type serviceType ) =>
resolver.GetService( serviceType ) ??
configuration.ApiVersioningServices().GetService( serviceType );

public IEnumerable<object> GetServices( Type serviceType ) => resolver.GetServices( serviceType );

private sealed class DependencyScopeChain : IDependencyScope
{
private readonly IDependencyScope scope;
private readonly HttpConfiguration configuration;

internal DependencyScopeChain( HttpConfiguration configuration, IDependencyScope scope )
{
this.configuration = configuration;
this.scope = scope;
}

public void Dispose() => scope.Dispose();

public object GetService( Type serviceType ) =>
scope.GetService( serviceType ) ??
configuration.ApiVersioningServices().GetService( serviceType );

public IEnumerable<object> GetServices( Type serviceType ) => scope.GetServices( serviceType );
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,30 @@
namespace Asp.Versioning;

using Asp.Versioning.Conventions;
using System.Globalization;
using System.Web.Http.Dependencies;

internal static class DependencyResolverExtensions
{
internal static TService? GetService<TService>( this IDependencyResolver resolver ) => (TService) resolver.GetService( typeof( TService ) );
internal static TService GetRequiredService<TService>( this IDependencyResolver resolver )
{
var service = (TService) resolver.GetService( typeof( TService ) );
var message = string.Format( CultureInfo.CurrentCulture, SR.NoServiceRegistered, typeof( TService ) );
return service ?? throw new InvalidOperationException( message );
}

internal static IApiVersionParser GetApiVersionParser( this IDependencyResolver resolver ) =>
resolver.GetService<IApiVersionParser>() ?? ApiVersionParser.Default;
resolver.GetRequiredService<IApiVersionParser>();

internal static IReportApiVersions GetApiVersionReporter( this IDependencyResolver resolver ) =>
resolver.GetService<IReportApiVersions>() ?? DefaultApiVersionReporter.GetOrCreate( resolver.GetSunsetPolicyManager() );
resolver.GetRequiredService<IReportApiVersions>();

internal static IControllerNameConvention GetControllerNameConvention( this IDependencyResolver resolver ) =>
resolver.GetService<IControllerNameConvention>() ?? ControllerNameConvention.Default;
resolver.GetRequiredService<IControllerNameConvention>();

internal static IProblemDetailsFactory GetProblemDetailsFactory( this IDependencyResolver resolver ) =>
resolver.GetService<IProblemDetailsFactory>() ?? ProblemDetailsFactory.Default;
resolver.GetRequiredService<IProblemDetailsFactory>();

internal static ISunsetPolicyManager GetSunsetPolicyManager( this IDependencyResolver resolver ) =>
resolver.GetService<ISunsetPolicyManager>() ?? SunsetPolicyManager.Default;
resolver.GetRequiredService<ISunsetPolicyManager>();
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ internal HttpResponseExceptionFactory( HttpRequestMessage request, ApiVersionMod

private ITraceWriter TraceWriter => configuration.Services.GetTraceWriter() ?? NullTraceWriter.Instance;

private IReportApiVersions ApiVersionReporter => configuration.GetApiVersionReporter();
private IReportApiVersions ApiVersionReporter => configuration.DependencyResolver.GetApiVersionReporter();

internal HttpResponseException NewUnmatchedException(
ControllerSelectionResult conventionRouteResult,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,6 @@ namespace Asp.Versioning;

internal sealed class DoNotReportApiVersions : IReportApiVersions
{
private static DoNotReportApiVersions? instance;

private DoNotReportApiVersions() { }

internal static IReportApiVersions Instance => instance ??= new();

public ApiVersionMapping Mapping => Explicit | Implicit;

public void Report( HttpResponseMessage response, ApiVersionModel apiVersionModel ) { }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public ProblemDetails CreateProblemDetails(
return ProblemDetailsFactory.AddUnsupportedExtensions( request, status, problem, ApplyMessage );
}

return ProblemDetailsFactory.Default.CreateProblemDetails(
return ProblemDetailsFactory.NewProblemDetails(
request,
statusCode,
title,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,16 @@ namespace Asp.Versioning;

internal sealed class ProblemDetailsFactory : IProblemDetailsFactory
{
private static IProblemDetailsFactory? @default;

public static IProblemDetailsFactory Default
{
get => @default ??= new ProblemDetailsFactory();
set => @default = value;
}

public ProblemDetails CreateProblemDetails(
HttpRequestMessage request,
int? statusCode = null,
string? title = null,
string? type = null,
string? detail = null,
string? instance = null ) =>
NewProblemDetails( request, statusCode, title, type, detail, instance );

internal static ProblemDetails NewProblemDetails(
HttpRequestMessage request,
int? statusCode = null,
string? title = null,
Expand Down
9 changes: 9 additions & 0 deletions src/AspNet/WebApi/src/Asp.Versioning.WebApi/SR.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions src/AspNet/WebApi/src/Asp.Versioning.WebApi/SR.resx
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,9 @@
<data name="NoControllerSelected" xml:space="preserve">
<value>A controller was not selected for request URI '{0}' and API version '{1}'.</value>
</data>
<data name="NoServiceRegistered" xml:space="preserve">
<value>No service for type '{0}' has been registered.</value>
</data>
<data name="ResourceNotFound" xml:space="preserve">
<value>No HTTP resource was found that matches the request URI '{0}'.</value>
</data>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,10 @@ namespace Asp.Versioning;
public partial class SunsetPolicyManager
{
private readonly ApiVersioningOptions options;
private static ISunsetPolicyManager? @default;

/// <summary>
/// Initializes a new instance of the <see cref="SunsetPolicyManager"/> class.
/// </summary>
/// <param name="options">The associated <see cref="ApiVersioningOptions">API versioning options</see>.</param>
public SunsetPolicyManager( ApiVersioningOptions options ) => this.options = options;

internal static ISunsetPolicyManager Default
{
get => @default ?? new SunsetPolicyManager( new ApiVersioningOptions() );
set => @default = value;
}
}
Loading

0 comments on commit d0b4fd5

Please sign in to comment.