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 @@ -16,7 +16,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.OpenApi" Version="2.7.5" />
<PackageReference Include="Microsoft.OpenApi" Version="[2.7.5, 3.0.0)" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.*" />
</ItemGroup>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,88 +10,72 @@
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using System.Linq.Expressions;
using System.Runtime.CompilerServices;
using static System.Linq.Expressions.Expression;
using static System.Runtime.CompilerServices.UnsafeAccessorKind;

// HACK: all of these types are internal in Microsoft.AspNetCore.OpenApi
// REF: https://github.com/dotnet/aspnetcore/tree/main/src/OpenApi/src
internal static class Class
{
public static class OpenApiDocumentService
{
private static readonly Func<IServiceProvider, string, object> factory = NewFactory();

public static object New( IServiceProvider serviceProvider, string documentName ) => factory( serviceProvider, documentName );

private static Func<IServiceProvider, string, object> NewFactory()
public static object New( IServiceProvider serviceProvider, string documentName )
{
var constructor = Type.OpenApiDocumentService.GetConstructors().Single();
var serviceProvider = Parameter( typeof( IServiceProvider ), "serviceProvider" );
var documentName = Parameter( typeof( string ), "documentName" );
var getRequiredService = typeof( ServiceProviderServiceExtensions ).GetMethod(
nameof( ServiceProviderServiceExtensions.GetRequiredService ),
[typeof( IServiceProvider ), typeof( System.Type )] )!;
var apiDescriptionGroupCollectionProvider = typeof( IApiDescriptionGroupCollectionProvider );
var hostEnvironment = typeof( IHostEnvironment );
var optionsMonitor = typeof( IOptionsMonitor<OpenApiOptions> );
var server = typeof( IServer );
var body = Expression.New(
constructor,
var apiDescriptionGroupCollectionProvider = serviceProvider.GetRequiredService<IApiDescriptionGroupCollectionProvider>();
var hostEnvironment = serviceProvider.GetRequiredService<IHostEnvironment>();
var optionsMonitor = serviceProvider.GetRequiredService<IOptionsMonitor<OpenApiOptions>>();
var server = serviceProvider.GetRequiredService<IServer>();

return OpenApiDocumentServiceCtor(
documentName,
Convert( Call( getRequiredService, serviceProvider, Constant( apiDescriptionGroupCollectionProvider ) ), apiDescriptionGroupCollectionProvider ),
Convert( Call( getRequiredService, serviceProvider, Constant( hostEnvironment ) ), hostEnvironment ),
Convert( Call( getRequiredService, serviceProvider, Constant( optionsMonitor ) ), optionsMonitor ),
apiDescriptionGroupCollectionProvider,
hostEnvironment,
optionsMonitor,
serviceProvider,
Convert( Call( getRequiredService, serviceProvider, Constant( server ) ), server ) );
var lambda = Lambda<Func<IServiceProvider, string, object>>( body, serviceProvider, documentName );

return lambda.Compile();
server );

Check notice

Code scanning / CodeQL

Calls to unmanaged code Note

Replace this call with a call to managed code if possible.
Comment thread
commonsensesoftware marked this conversation as resolved.
Dismissed
}

[UnsafeAccessor( Constructor )]
[return: UnsafeAccessorType( Type.Name.OpenApiDocumentService )]
private static extern object OpenApiDocumentServiceCtor(

Check notice

Code scanning / CodeQL

Unmanaged code Note

Minimise the use of unmanaged code.
Comment thread
commonsensesoftware marked this conversation as resolved.
Dismissed
string documentName,
IApiDescriptionGroupCollectionProvider apiDescriptionGroupCollectionProvider,
IHostEnvironment hostEnvironment,
IOptionsMonitor<OpenApiOptions> optionsMonitor,
IServiceProvider serviceProvider,
IServer server );
}

public static class OpenApiSchemaService
{
private static readonly Func<IServiceProvider, string, object> factory = NewFactory();

public static object New( IServiceProvider serviceProvider, string documentName ) => factory( serviceProvider, documentName );

private static Func<IServiceProvider, string, object> NewFactory()
public static object New( IServiceProvider serviceProvider, string documentName )
{
var constructor = Type.OpenApiSchemaService.GetConstructors().Single();
var serviceProvider = Parameter( typeof( IServiceProvider ), "serviceProvider" );
var documentName = Parameter( typeof( string ), "documentName" );
var getRequiredService = typeof( ServiceProviderServiceExtensions ).GetMethod(
nameof( ServiceProviderServiceExtensions.GetRequiredService ),
[typeof( IServiceProvider ), typeof( System.Type )] )!;
var jsonOptions = typeof( IOptions<JsonOptions> );
var optionsMonitor = typeof( IOptionsMonitor<OpenApiOptions> );
var body = Expression.New(
constructor,
documentName,
Convert( Call( getRequiredService, serviceProvider, Constant( jsonOptions ) ), jsonOptions ),
Convert( Call( getRequiredService, serviceProvider, Constant( optionsMonitor ) ), optionsMonitor ) );
var lambda = Lambda<Func<IServiceProvider, string, object>>( body, serviceProvider, documentName );
var jsonOptions = serviceProvider.GetRequiredService<IOptions<JsonOptions>>();
var optionsMonitor = serviceProvider.GetRequiredService<IOptionsMonitor<OpenApiOptions>>();

return lambda.Compile();
return OpenApiSchemaServiceCtor( documentName, jsonOptions, optionsMonitor );

Check notice

Code scanning / CodeQL

Calls to unmanaged code Note

Replace this call with a call to managed code if possible.
Comment thread
commonsensesoftware marked this conversation as resolved.
Dismissed
}

[UnsafeAccessor( Constructor )]
[return: UnsafeAccessorType( Type.Name.OpenApiSchemaService )]
private static extern object OpenApiSchemaServiceCtor(

Check notice

Code scanning / CodeQL

Unmanaged code Note

Minimise the use of unmanaged code.
Comment thread
commonsensesoftware marked this conversation as resolved.
Dismissed
string documentName,
IOptions<JsonOptions> jsonOptions,
IOptionsMonitor<OpenApiOptions> optionsMonitor );
}

public static class OpenApiDocumentProvider
{
private static readonly Func<IServiceProvider, object> factory = NewFactory();

public static object New( IServiceProvider serviceProvider ) => factory( serviceProvider );
public static object New( IServiceProvider serviceProvider ) => OpenApiDocumentProviderCtor( serviceProvider );

Check notice

Code scanning / CodeQL

Calls to unmanaged code Note

Replace this call with a call to managed code if possible.
Comment thread
commonsensesoftware marked this conversation as resolved.
Dismissed

private static Func<IServiceProvider, object> NewFactory()
{
var constructor = Type.OpenApiDocumentProvider.GetConstructors().Single();
var serviceProvider = Parameter( typeof( IServiceProvider ), "serviceProvider" );
var body = Expression.New( constructor, serviceProvider );
var lambda = Lambda<Func<IServiceProvider, object>>( body, serviceProvider );

return lambda.Compile();
}
[UnsafeAccessor( Constructor )]
[return: UnsafeAccessorType( Type.Name.OpenApiDocumentProvider )]
private static extern object OpenApiDocumentProviderCtor( IServiceProvider serviceProvider );

Check notice

Code scanning / CodeQL

Unmanaged code Note

Minimise the use of unmanaged code.
Comment thread
commonsensesoftware marked this conversation as resolved.
Dismissed
}

// this class cannot use UnsafeAccessor or UnsafeAccessorType because it is generic and neither the class nor the
// type parameter is known at compile time
public static class NamedService
{
private static readonly Func<string, object> factory = NewFactory();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,17 @@
namespace Asp.Versioning.OpenApi.Reflection;

using Microsoft.AspNetCore.OpenApi;
using static System.Linq.Expressions.Expression;
using static System.Reflection.BindingFlags;
using System.Runtime.CompilerServices;

// HACK: all of these properties are internal in Microsoft.AspNetCore.OpenApi
// REF: https://github.com/dotnet/aspnetcore/tree/main/src/OpenApi/src
internal static class Property
{
private static readonly Action<OpenApiOptions, string> setDocumentName = NewSetDocumentName();

extension( OpenApiOptions options )
{
public void SetDocumentName( string value ) => setDocumentName( options, value );
public void SetDocumentName( string value ) => SetDocumentNameImpl( options, value );

Check notice

Code scanning / CodeQL

Calls to unmanaged code Note

Replace this call with a call to managed code if possible.
Comment thread
commonsensesoftware marked this conversation as resolved.
Dismissed
}

private static Action<OpenApiOptions, string> NewSetDocumentName()
{
var options = Parameter( typeof( OpenApiOptions ), "options" );
var documentName = Parameter( typeof( string ), "documentName" );
var property = typeof( OpenApiOptions ).GetProperty( nameof( OpenApiOptions.DocumentName ), Instance | NonPublic | Public )!;
var body = Assign( Property( options, property ), documentName );
var lambda = Lambda<Action<OpenApiOptions, string>>( body, options, documentName );

return lambda.Compile();
}
[UnsafeAccessor( UnsafeAccessorKind.Method, Name = "set_DocumentName" )]
private static extern void SetDocumentNameImpl( OpenApiOptions options, string value );

Check notice

Code scanning / CodeQL

Unmanaged code Note

Minimise the use of unmanaged code.
Comment thread
commonsensesoftware marked this conversation as resolved.
Dismissed
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,29 @@ namespace Asp.Versioning.OpenApi.Reflection;
internal static class Type
{
[DynamicallyAccessedMembers( PublicConstructors )]
public static readonly System.Type IDocumentProvider = System.Type.GetType( "Microsoft.Extensions.ApiDescriptions.IDocumentProvider, Microsoft.AspNetCore.OpenApi", throwOnError: true )!;
public static readonly System.Type IDocumentProvider = System.Type.GetType( Name.IDocumentProvider, throwOnError: true )!;

[DynamicallyAccessedMembers( PublicConstructors )]
public static readonly System.Type NamedService = System.Type.GetType( "Microsoft.AspNetCore.OpenApi.NamedService`1[[Microsoft.AspNetCore.OpenApi.OpenApiDocumentService, Microsoft.AspNetCore.OpenApi]], Microsoft.AspNetCore.OpenApi", throwOnError: true )!;
public static readonly System.Type NamedService = System.Type.GetType( Name.NamedService, throwOnError: true )!;

public static readonly System.Type IEnumerableOfNamedService = System.Type.GetType( "System.Collections.Generic.IEnumerable`1[[Microsoft.AspNetCore.OpenApi.NamedService`1[[Microsoft.AspNetCore.OpenApi.OpenApiDocumentService, Microsoft.AspNetCore.OpenApi]], Microsoft.AspNetCore.OpenApi]], System.Private.CoreLib", throwOnError: true )!;
public static readonly System.Type IEnumerableOfNamedService = System.Type.GetType( Name.IEnumerableOfNamedService, throwOnError: true )!;

[DynamicallyAccessedMembers( PublicConstructors )]
public static readonly System.Type OpenApiDocumentProvider = System.Type.GetType( "Microsoft.Extensions.ApiDescriptions.OpenApiDocumentProvider, Microsoft.AspNetCore.OpenApi", throwOnError: true )!;
public static readonly System.Type OpenApiDocumentProvider = System.Type.GetType( Name.OpenApiDocumentProvider, throwOnError: true )!;

[DynamicallyAccessedMembers( PublicConstructors )]
public static readonly System.Type OpenApiDocumentService = System.Type.GetType( "Microsoft.AspNetCore.OpenApi.OpenApiDocumentService, Microsoft.AspNetCore.OpenApi", throwOnError: true )!;
public static readonly System.Type OpenApiDocumentService = System.Type.GetType( Name.OpenApiDocumentService, throwOnError: true )!;

[DynamicallyAccessedMembers( PublicConstructors )]
public static readonly System.Type OpenApiSchemaService = System.Type.GetType( "Microsoft.AspNetCore.OpenApi.OpenApiSchemaService, Microsoft.AspNetCore.OpenApi", throwOnError: true )!;
public static readonly System.Type OpenApiSchemaService = System.Type.GetType( Name.OpenApiSchemaService, throwOnError: true )!;

public static class Name
{
public const string IDocumentProvider = "Microsoft.Extensions.ApiDescriptions.IDocumentProvider, Microsoft.AspNetCore.OpenApi";
public const string NamedService = "Microsoft.AspNetCore.OpenApi.NamedService`1[[Microsoft.AspNetCore.OpenApi.OpenApiDocumentService, Microsoft.AspNetCore.OpenApi]], Microsoft.AspNetCore.OpenApi";
public const string IEnumerableOfNamedService = "System.Collections.Generic.IEnumerable`1[[Microsoft.AspNetCore.OpenApi.NamedService`1[[Microsoft.AspNetCore.OpenApi.OpenApiDocumentService, Microsoft.AspNetCore.OpenApi]], Microsoft.AspNetCore.OpenApi]], System.Private.CoreLib";
public const string OpenApiDocumentProvider = "Microsoft.Extensions.ApiDescriptions.OpenApiDocumentProvider, Microsoft.AspNetCore.OpenApi";
public const string OpenApiDocumentService = "Microsoft.AspNetCore.OpenApi.OpenApiDocumentService, Microsoft.AspNetCore.OpenApi";
public const string OpenApiSchemaService = "Microsoft.AspNetCore.OpenApi.OpenApiSchemaService, Microsoft.AspNetCore.OpenApi";
}
}
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@

Clip dependent package version because the next major version is incompatible [dotnet/aspnetcore#67930](https://github.com/dotnet/aspnetcore/issues/67930)
Fix `<inheritdoc />` summaries [Issue #1189](https://github.com/dotnet/aspnet-api-versioning/issues/1189)
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ namespace Asp.Versioning.OpenApi.Transformers;
using System.Linq;
using System.Reflection;
using System.Xml.Linq;
using static System.Reflection.BindingFlags;

/// <summary>
/// Provides access to XML documentation comments, which enables the retrieval of summaries, remarks, return values,
Expand Down Expand Up @@ -189,7 +190,7 @@ public virtual string GetResponseDescription( MemberInfo member, string statusCo
{
var element = GetMemberById( XmlCommentsProvider.GetDocumentationMemberId( member ) );

// The C# compiler writes <inheritdoc /> verbatim into the XML file; following it is the consumer's
// The compiler writes <inheritdoc /> verbatim into the XML file; following it is the consumer's
// responsibility. Resolve it so members documented on a base type or an implemented interface still
// surface their summary, remarks, parameters, and so on.
if ( depth < MaxInheritDocDepth && element?.Element( "inheritdoc" ) is { } inheritdoc )
Expand Down Expand Up @@ -270,7 +271,7 @@ private static IEnumerable<MemberInfo> GetInheritedMembers( MemberInfo member )
[UnconditionalSuppressMessage( "ILLink", "IL2070" )]
private static MemberInfo? FindMatchingMember( Type type, MemberInfo member )
{
const BindingFlags Flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static;
const BindingFlags Flags = Public | NonPublic | Instance | Static;

switch ( member )
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,34 +27,27 @@ public void add_api_versioning_should_not_allow_default_neutral_api_version()
options.Should().Throw<OptionsValidationException>();
}

// REF: https://github.com/dotnet/aspnet-api-versioning/issues/1191
[Fact]
public void add_api_versioning_should_not_displace_or_wrap_user_registered_problem_details_writers()
{
// arrange
var services = new ServiceCollection();
var customWriter = new TestProblemDetailsWriter();
var writer = new Mock<IProblemDetailsWriter>();

writer.Setup( w => w.CanWrite( It.IsAny<ProblemDetailsContext>() ) ).Returns( true );
services.AddProblemDetails();
services.AddSingleton<IProblemDetailsWriter>( customWriter );
services.AddSingleton( writer.Object );

var writersBefore = services.Where( s => s.ServiceType == typeof( IProblemDetailsWriter ) ).ToArray();
var before = services.Where( s => s.ServiceType == typeof( IProblemDetailsWriter ) ).ToArray();

// act
services.AddApiVersioning();

// assert
var writersAfter = services.Where( s => s.ServiceType == typeof( IProblemDetailsWriter ) ).ToArray();
writersAfter.Should().Equal( writersBefore );

var after = services.Where( s => s.ServiceType == typeof( IProblemDetailsWriter ) ).ToArray();
using var provider = services.BuildServiceProvider();
provider.GetServices<IProblemDetailsWriter>().Should().Contain( customWriter );
}

private sealed class TestProblemDetailsWriter : IProblemDetailsWriter
{
public bool CanWrite( ProblemDetailsContext context ) => true;

public ValueTask WriteAsync( ProblemDetailsContext context ) => ValueTask.CompletedTask;
after.Should().Equal( before );
provider.GetServices<IProblemDetailsWriter>().Should().Contain( writer.Object );
}
}
Loading