diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/Asp.Versioning.OpenApi.csproj b/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/Asp.Versioning.OpenApi.csproj index c4007148..01d47b6d 100644 --- a/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/Asp.Versioning.OpenApi.csproj +++ b/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/Asp.Versioning.OpenApi.csproj @@ -16,7 +16,7 @@ - + diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/Reflection/Class.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/Reflection/Class.cs index 4d4ec845..3ae15fda 100644 --- a/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/Reflection/Class.cs +++ b/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/Reflection/Class.cs @@ -10,7 +10,9 @@ namespace Asp.Versioning.OpenApi.Reflection; 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 @@ -18,80 +20,62 @@ internal static class Class { public static class OpenApiDocumentService { - private static readonly Func factory = NewFactory(); - - public static object New( IServiceProvider serviceProvider, string documentName ) => factory( serviceProvider, documentName ); - - private static Func 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 ); - var server = typeof( IServer ); - var body = Expression.New( - constructor, + var apiDescriptionGroupCollectionProvider = serviceProvider.GetRequiredService(); + var hostEnvironment = serviceProvider.GetRequiredService(); + var optionsMonitor = serviceProvider.GetRequiredService>(); + var server = serviceProvider.GetRequiredService(); + + 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>( body, serviceProvider, documentName ); - - return lambda.Compile(); + server ); } + + [UnsafeAccessor( Constructor )] + [return: UnsafeAccessorType( Type.Name.OpenApiDocumentService )] + private static extern object OpenApiDocumentServiceCtor( + string documentName, + IApiDescriptionGroupCollectionProvider apiDescriptionGroupCollectionProvider, + IHostEnvironment hostEnvironment, + IOptionsMonitor optionsMonitor, + IServiceProvider serviceProvider, + IServer server ); } public static class OpenApiSchemaService { - private static readonly Func factory = NewFactory(); - - public static object New( IServiceProvider serviceProvider, string documentName ) => factory( serviceProvider, documentName ); - - private static Func 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 ); - var optionsMonitor = typeof( IOptionsMonitor ); - var body = Expression.New( - constructor, - documentName, - Convert( Call( getRequiredService, serviceProvider, Constant( jsonOptions ) ), jsonOptions ), - Convert( Call( getRequiredService, serviceProvider, Constant( optionsMonitor ) ), optionsMonitor ) ); - var lambda = Lambda>( body, serviceProvider, documentName ); + var jsonOptions = serviceProvider.GetRequiredService>(); + var optionsMonitor = serviceProvider.GetRequiredService>(); - return lambda.Compile(); + return OpenApiSchemaServiceCtor( documentName, jsonOptions, optionsMonitor ); } + + [UnsafeAccessor( Constructor )] + [return: UnsafeAccessorType( Type.Name.OpenApiSchemaService )] + private static extern object OpenApiSchemaServiceCtor( + string documentName, + IOptions jsonOptions, + IOptionsMonitor optionsMonitor ); } public static class OpenApiDocumentProvider { - private static readonly Func factory = NewFactory(); - - public static object New( IServiceProvider serviceProvider ) => factory( serviceProvider ); + public static object New( IServiceProvider serviceProvider ) => OpenApiDocumentProviderCtor( serviceProvider ); - private static Func NewFactory() - { - var constructor = Type.OpenApiDocumentProvider.GetConstructors().Single(); - var serviceProvider = Parameter( typeof( IServiceProvider ), "serviceProvider" ); - var body = Expression.New( constructor, serviceProvider ); - var lambda = Lambda>( body, serviceProvider ); - - return lambda.Compile(); - } + [UnsafeAccessor( Constructor )] + [return: UnsafeAccessorType( Type.Name.OpenApiDocumentProvider )] + private static extern object OpenApiDocumentProviderCtor( IServiceProvider serviceProvider ); } + // 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 factory = NewFactory(); diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/Reflection/Property.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/Reflection/Property.cs index b8c7cec0..ca239519 100644 --- a/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/Reflection/Property.cs +++ b/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/Reflection/Property.cs @@ -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 setDocumentName = NewSetDocumentName(); - extension( OpenApiOptions options ) { - public void SetDocumentName( string value ) => setDocumentName( options, value ); + public void SetDocumentName( string value ) => SetDocumentNameImpl( options, value ); } - private static Action 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>( body, options, documentName ); - - return lambda.Compile(); - } + [UnsafeAccessor( UnsafeAccessorKind.Method, Name = "set_DocumentName" )] + private static extern void SetDocumentNameImpl( OpenApiOptions options, string value ); } \ No newline at end of file diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/Reflection/Type.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/Reflection/Type.cs index 14e85ea8..a4310614 100644 --- a/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/Reflection/Type.cs +++ b/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/Reflection/Type.cs @@ -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"; + } } \ No newline at end of file diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/ReleaseNotes.txt b/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/ReleaseNotes.txt index 5f282702..c91871c9 100644 --- a/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/ReleaseNotes.txt +++ b/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/ReleaseNotes.txt @@ -1 +1,2 @@ - \ No newline at end of file +Clip dependent package version because the next major version is incompatible [dotnet/aspnetcore#67930](https://github.com/dotnet/aspnetcore/issues/67930) +Fix `` summaries [Issue #1189](https://github.com/dotnet/aspnet-api-versioning/issues/1189) \ No newline at end of file diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/Transformers/XmlComments.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/Transformers/XmlComments.cs index 9601301a..915ada04 100644 --- a/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/Transformers/XmlComments.cs +++ b/src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/Transformers/XmlComments.cs @@ -8,6 +8,7 @@ namespace Asp.Versioning.OpenApi.Transformers; using System.Linq; using System.Reflection; using System.Xml.Linq; +using static System.Reflection.BindingFlags; /// /// Provides access to XML documentation comments, which enables the retrieval of summaries, remarks, return values, @@ -189,7 +190,7 @@ public virtual string GetResponseDescription( MemberInfo member, string statusCo { var element = GetMemberById( XmlCommentsProvider.GetDocumentationMemberId( member ) ); - // The C# compiler writes verbatim into the XML file; following it is the consumer's + // The compiler writes 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 ) @@ -270,7 +271,7 @@ private static IEnumerable 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 ) { diff --git a/src/AspNetCore/WebApi/test/Asp.Versioning.Http.Tests/DependencyInjection/IServiceCollectionExtensionsTest.cs b/src/AspNetCore/WebApi/test/Asp.Versioning.Http.Tests/DependencyInjection/IServiceCollectionExtensionsTest.cs index 8e7f2219..bd22841d 100644 --- a/src/AspNetCore/WebApi/test/Asp.Versioning.Http.Tests/DependencyInjection/IServiceCollectionExtensionsTest.cs +++ b/src/AspNetCore/WebApi/test/Asp.Versioning.Http.Tests/DependencyInjection/IServiceCollectionExtensionsTest.cs @@ -27,34 +27,27 @@ public void add_api_versioning_should_not_allow_default_neutral_api_version() options.Should().Throw(); } - // 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(); + writer.Setup( w => w.CanWrite( It.IsAny() ) ).Returns( true ); services.AddProblemDetails(); - services.AddSingleton( 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().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().Should().Contain( writer.Object ); } } \ No newline at end of file