Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
namespace Asp.Versioning;

using static System.AttributeTargets;
#if NETSTANDARD
using DateOnly = System.DateTime;
#endif

/// <summary>
/// Represents the metadata that describes the advertised <see cref="ApiVersion">API versions</see>.
Expand Down Expand Up @@ -61,8 +64,20 @@ public AdvertiseApiVersionsAttribute( double version, params double[] otherVersi
/// <summary>
/// Initializes a new instance of the <see cref="AdvertiseApiVersionsAttribute"/> class.
/// </summary>
/// <param name="version">The API version string.</param>
public AdvertiseApiVersionsAttribute( string version ) : base( version ) { }
/// <param name="version">A numeric API version.</param>
/// <param name="status">The status associated with the API version, if any.</param>
public AdvertiseApiVersionsAttribute( double version, string? status = default )
: base( new ApiVersion( version, status ) ) { }

/// <summary>
/// Initializes a new instance of the <see cref="AdvertiseApiVersionsAttribute"/> class.
/// </summary>
/// <param name="year">The version year.</param>
/// <param name="month">The version month.</param>
/// <param name="day">The version day.</param>
/// <param name="status">The status associated with the API version, if any.</param>
public AdvertiseApiVersionsAttribute( int year, int month, int day, string? status = default )
: base( new ApiVersion( new DateOnly( year, month, day ), status ) ) { }

/// <summary>
/// Initializes a new instance of the <see cref="AdvertiseApiVersionsAttribute"/> class.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
namespace Asp.Versioning;

using static System.AttributeTargets;
#if NETSTANDARD
using DateOnly = System.DateTime;
#endif

/// <summary>
/// Represents the metadata that describes the <see cref="ApiVersion">versions</see> associated with an API.
Expand Down Expand Up @@ -38,6 +41,16 @@ protected ApiVersionAttribute( IApiVersionParser parser, string version ) : base
/// <param name="status">The status associated with the API version, if any.</param>
public ApiVersionAttribute( double version, string? status = default ) : base( new ApiVersion( version, status ) ) { }

/// <summary>
/// Initializes a new instance of the <see cref="ApiVersionAttribute"/> class.
/// </summary>
/// <param name="year">The version year.</param>
/// <param name="month">The version month.</param>
/// <param name="day">The version day.</param>
/// <param name="status">The status associated with the API version, if any.</param>
public ApiVersionAttribute( int year, int month, int day, string? status = default )
: base( new ApiVersion( new DateOnly( year, month, day ), status ) ) { }

/// <summary>
/// Initializes a new instance of the <see cref="ApiVersionAttribute"/> class.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
namespace Asp.Versioning;

using static System.AttributeTargets;
#if NETSTANDARD
using DateOnly = System.DateTime;
#endif

/// <summary>
/// Represents the metadata that describes the <see cref="ApiVersion">version</see>-specific implementation of an API.
Expand All @@ -32,7 +35,19 @@ protected MapToApiVersionAttribute( IApiVersionParser parser, string version ) :
/// Initializes a new instance of the <see cref="MapToApiVersionAttribute"/> class.
/// </summary>
/// <param name="version">A numeric API version.</param>
public MapToApiVersionAttribute( double version ) : base( version ) { }
/// <param name="status">The status associated with the API version, if any.</param>
public MapToApiVersionAttribute( double version, string? status = default )
: base( new ApiVersion( version, status ) ) { }

/// <summary>
/// Initializes a new instance of the <see cref="MapToApiVersionAttribute"/> class.
/// </summary>
/// <param name="year">The version year.</param>
/// <param name="month">The version month.</param>
/// <param name="day">The version day.</param>
/// <param name="status">The status associated with the API version, if any.</param>
public MapToApiVersionAttribute( int year, int month, int day, string? status = default )
: base( new ApiVersion( new DateOnly( year, month, day ), status ) ) { }

/// <summary>
/// Initializes a new instance of the <see cref="MapToApiVersionAttribute"/> class.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
namespace Asp.Versioning;

using static Asp.Versioning.ApiVersionProviderOptions;
#if NETFRAMEWORK
using DateOnly = System.DateTime;
#endif

public class AdvertiseApiVersionsAttributeTest
{
Expand All @@ -20,4 +23,58 @@ public void new_advertise_api_versions_attribute_should_have_expected_options( b
// assert
options.Should().Be( expected );
}

[Fact]
public void advertise_api_versions_base_attribute_should_initialize_from_array_of_double()
{
// arrange
var version = 1.0;
var otherVersions = new[] { 2.0, 3.0 };

// act
var attribute = new AdvertiseApiVersionsAttribute( version, otherVersions );

// asserts
attribute.Versions.Should().BeEquivalentTo( new ApiVersion[] { new( 1.0 ), new( 2.0 ), new( 3.0 ) } );
}

[Fact]
public void advertise_api_versions_base_attribute_should_initialize_from_array_of_string()
{
// arrange
var version = "1.0";
var otherVersions = new[] { "2.0", "3.0" };

// act
var attribute = new AdvertiseApiVersionsAttribute( version, otherVersions );

// assert
attribute.Versions.Should().BeEquivalentTo( new ApiVersion[] { new( 1.0 ), new( 2.0 ), new( 3.0 ) } );
}

[Fact]
public void advertise_api_version_attribute_should_initialize_from_date()
{
// arrange
var expected = new ApiVersion( new DateOnly( 2016, 1, 1 ) );

// act
var attribute = new AdvertiseApiVersionsAttribute( 2016, 1, 1 );

// assert
attribute.Versions[0].Should().Be( expected );
}

[Fact]
public void advertise_api_version_attribute_should_initialize_from_date_with_status()
{
// arrange
var expected = new ApiVersion( new DateOnly( 2016, 1, 1 ), "alpha" );

// act
var attribute = new AdvertiseApiVersionsAttribute( 2016, 1, 1, "alpha" );

// assert
attribute.Versions[0].Should().Be( expected );
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

namespace Asp.Versioning;

#if NETFRAMEWORK
using DateOnly = System.DateTime;
#endif

public class ApiVersionAttributeTest
{
[Theory]
Expand Down Expand Up @@ -50,4 +54,30 @@ public void api_version_attribute_should_initialize_from_double_with_status()
// assert
attribute.Versions[0].Should().Be( expected );
}

[Fact]
public void api_version_attribute_should_initialize_from_date()
{
// arrange
var expected = new ApiVersion( new DateOnly( 2016, 1, 1 ) );

// act
var attribute = new ApiVersionAttribute( 2016, 1, 1 );

// assert
attribute.Versions[0].Should().Be( expected );
}

[Fact]
public void api_version_attribute_should_initialize_from_date_with_status()
{
// arrange
var expected = new ApiVersion( new DateOnly( 2016, 1, 1 ), "alpha" );

// act
var attribute = new ApiVersionAttribute( 2016, 1, 1, "alpha" );

// assert
attribute.Versions[0].Should().Be( expected );
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,11 @@ private static void Finialize( EndpointBuilder endpointBuilder, ApiVersionSet? v

endpointBuilder.RequestDelegate = context =>
{
context.RequestServices = new InjectApiVersion( context );
if ( context.RequestServices is not InjectApiVersion )
{
context.RequestServices = new InjectApiVersion( context );
}

return requestDelegate( context );
};
}
Expand Down Expand Up @@ -282,11 +286,12 @@ private record struct ApiVersionBuckets(
&& AdvertisedDeprecated.Count == 0;
}

private sealed class InjectApiVersion : IServiceProvider
private sealed class InjectApiVersion : IKeyedServiceProvider, IServiceScopeFactory
{
private static readonly Type ApiVersionType = typeof( ApiVersion );
private readonly IServiceProvider provider;
private readonly HttpContext context;
internal static readonly Type ApiVersionType = typeof( ApiVersion );
internal static readonly Type ServiceScopeFactoryType = typeof( IServiceScopeFactory );

public InjectApiVersion( HttpContext context )
{
Expand All @@ -295,14 +300,71 @@ public InjectApiVersion( HttpContext context )
context.RequestServices = this;
}

#pragma warning disable CA2000 // Dispose objects before losing scope
public IServiceScope CreateScope() => new ApiVersionScope( context, provider.CreateScope() );
#pragma warning restore CA2000

public object? GetKeyedService( Type serviceType, object? serviceKey ) =>
provider.GetKeyedService( serviceType, serviceKey );

public object GetRequiredKeyedService( Type serviceType, object? serviceKey ) =>
provider.GetRequiredKeyedService( serviceType, serviceKey );

public object? GetService( Type serviceType )
{
if ( serviceType.IsAssignableFrom( ApiVersionType ) )
{
return context.RequestedApiVersion;
}
else if ( serviceType.Equals( ServiceScopeFactoryType ) )
{
return this;
}

return provider.GetService( serviceType );
}
}

private sealed class ApiVersionScope( HttpContext context, IServiceScope scope )
: IKeyedServiceProvider, IServiceScopeFactory, IServiceScope
{
private bool disposed;

public IServiceProvider ServiceProvider => this;

#pragma warning disable CA2000 // Dispose objects before losing scope
public IServiceScope CreateScope() => new ApiVersionScope( context, scope.ServiceProvider.CreateScope() );
#pragma warning restore CA2000

public object? GetKeyedService( Type serviceType, object? serviceKey ) =>
scope.ServiceProvider.GetKeyedService( serviceType, serviceKey );

public object GetRequiredKeyedService( Type serviceType, object? serviceKey ) =>
scope.ServiceProvider.GetRequiredKeyedService( serviceType, serviceKey );

public object? GetService( Type serviceType )
{
if ( serviceType.IsAssignableFrom( InjectApiVersion.ApiVersionType ) )
{
return context.RequestedApiVersion;
}
else if ( serviceType.Equals( InjectApiVersion.ServiceScopeFactoryType ) )
{
return this;
}

return scope.ServiceProvider.GetService( serviceType );
}

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

disposed = true;
scope.Dispose();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,16 @@

#pragma warning disable CA1812

namespace Asp.Versioning.OpenApi;
namespace Asp.Versioning.OpenApi.Configuration;

using Asp.Versioning.ApiExplorer;
using Asp.Versioning.OpenApi.Configuration;
using Asp.Versioning.OpenApi.Reflection;
using Asp.Versioning.OpenApi.Transformers;
using Microsoft.AspNetCore.OpenApi;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;

internal sealed class ConfigureOpenApiOptions(
XmlCommentsFile file,
XmlCommentsTransformer xmlComments,
IApiVersionDescriptionProvider provider,
VersionedOpenApiOptionsFactory factory )
: IPostConfigureOptions<OpenApiOptions>
Expand All @@ -22,7 +20,6 @@ public void PostConfigure( string? name, OpenApiOptions options )
{
var comparer = StringComparer.OrdinalIgnoreCase;
var descriptions = provider.ApiVersionDescriptions;
var xmlComments = new XmlCommentsTransformer( file );

for ( var i = 0; i < descriptions.Count; i++ )
{
Expand Down
Loading
Loading