From 08072a8622b6235bb9c41a61c519182b77af75bc Mon Sep 17 00:00:00 2001 From: Safia Abdalla <safia@microsoft.com> Date: Tue, 4 Oct 2022 13:05:09 -0700 Subject: [PATCH] [release/6.0] Add support for minimal APIs in ApiDescription.Server (#44227) * [release/6.0] Add support for minimal APIs in ApiDescription.Server * [release/7.0] Quarantine `ClosingTheBrowserWindow_GracefullyDisconnects_TheCurrentCircuit` (#44186) - #44185 - also upload test logs for forks * Update CircuitGracefulTerminationTests.cs Co-authored-by: Doug Bunting <6431421+dougbu@users.noreply.github.com> --- .azure/pipelines/components-e2e-tests.yml | 1 + .../CircuitGracefulTerminationTests.cs | 3 +- ...ft.Extensions.ApiDescription.Server.csproj | 18 +-- ...ft.Extensions.ApiDescription.Server.nuspec | 1 + .../src/Commands/GetDocumentCommand.cs | 4 +- .../src/Commands/GetDocumentCommandWorker.cs | 107 ++++++++++++++++++ .../src/GetDocument.Insider.csproj | 7 +- .../src/Commands/InvokeCommand.cs | 9 +- .../src/dotnet-getdocument.csproj | 2 +- 9 files changed, 139 insertions(+), 13 deletions(-) diff --git a/.azure/pipelines/components-e2e-tests.yml b/.azure/pipelines/components-e2e-tests.yml index 057a6402772..8e3b37199c2 100644 --- a/.azure/pipelines/components-e2e-tests.yml +++ b/.azure/pipelines/components-e2e-tests.yml @@ -75,4 +75,5 @@ jobs: artifacts: - name: Components_E2E_Test_Logs path: '$(Build.SourcesDirectory)/artifacts/TestResults/$(BuildConfiguration)' + includeForks: true publishOnError: true diff --git a/src/Components/test/E2ETest/ServerExecutionTests/CircuitGracefulTerminationTests.cs b/src/Components/test/E2ETest/ServerExecutionTests/CircuitGracefulTerminationTests.cs index 7f14ac9b082..aba2951cf7e 100644 --- a/src/Components/test/E2ETest/ServerExecutionTests/CircuitGracefulTerminationTests.cs +++ b/src/Components/test/E2ETest/ServerExecutionTests/CircuitGracefulTerminationTests.cs @@ -7,8 +7,8 @@ using System.Threading.Tasks; using BasicTestApp; using Microsoft.AspNetCore.Components.E2ETest.Infrastructure; using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures; -using Microsoft.AspNetCore.Testing; using Microsoft.AspNetCore.E2ETesting; +using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging.Testing; using OpenQA.Selenium; @@ -66,6 +66,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests } [Fact] + [QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/44185")] public async Task ClosingTheBrowserWindow_GracefullyDisconnects_TheCurrentCircuit() { // Arrange & Act diff --git a/src/Tools/Extensions.ApiDescription.Server/src/Microsoft.Extensions.ApiDescription.Server.csproj b/src/Tools/Extensions.ApiDescription.Server/src/Microsoft.Extensions.ApiDescription.Server.csproj index 712fa8362b4..2db57db2538 100644 --- a/src/Tools/Extensions.ApiDescription.Server/src/Microsoft.Extensions.ApiDescription.Server.csproj +++ b/src/Tools/Extensions.ApiDescription.Server/src/Microsoft.Extensions.ApiDescription.Server.csproj @@ -3,7 +3,7 @@ <PropertyGroup> <!-- Included primarily to ensure dotnet-getdocument and GetDocument.Insider can be referenced. --> - <TargetFrameworks>netcoreapp2.1;$(DefaultNetFxTargetFramework)</TargetFrameworks> + <TargetFrameworks>netcoreapp2.1;$(DefaultNetCoreTargetFramework);$(DefaultNetFxTargetFramework)</TargetFrameworks> <Description>MSBuild tasks and targets for build-time Swagger and OpenApi document generation</Description> <DisableImplicitFrameworkReferences>true</DisableImplicitFrameworkReferences> @@ -19,12 +19,16 @@ because publish output matches what was built. --> <Reference Include="dotnet-getdocument" - Targets="Publish" - ReferenceOutputAssembly="false" - SkipGetTargetFrameworkProperties="true" - UndefineProperties="TargetFramework;TargetFrameworks;RuntimeIdentifier;PublishDir" /> - <Reference Include="GetDocument.Insider" ReferenceOutputAssembly="false"> - <Targets Condition=" '$(TargetFramework)' == 'netcoreapp2.1' ">Publish</Targets> + Condition=" '$(TargetFramework)' != '$(DefaultNetFxTargetFramework)' " + Targets="Publish" + Private="false" + ReferenceOutputAssembly="false" + SkipGetTargetFrameworkProperties="true" /> + <Reference Include="GetDocument.Insider" + Private="false" + ReferenceOutputAssembly="false" + SkipGetTargetFrameworkProperties="true"> + <Targets Condition=" '$(TargetFramework)' != '$(DefaultNetFxTargetFramework)' ">Publish</Targets> </Reference> </ItemGroup> diff --git a/src/Tools/Extensions.ApiDescription.Server/src/Microsoft.Extensions.ApiDescription.Server.nuspec b/src/Tools/Extensions.ApiDescription.Server/src/Microsoft.Extensions.ApiDescription.Server.nuspec index a85edc2f4ad..bc8028564e4 100644 --- a/src/Tools/Extensions.ApiDescription.Server/src/Microsoft.Extensions.ApiDescription.Server.nuspec +++ b/src/Tools/Extensions.ApiDescription.Server/src/Microsoft.Extensions.ApiDescription.Server.nuspec @@ -12,5 +12,6 @@ <file src="$artifactsBinDir$\GetDocument.Insider\$configuration$\net461\*.*" target="tools\net461" /> <file src="$artifactsBinDir$\GetDocument.Insider\x86\$configuration$\net461\*.*" target="tools\net461-x86" /> <file src="$artifactsBinDir$\GetDocument.Insider\$configuration$\netcoreapp2.1\publish\*.*" target="tools\netcoreapp2.1" /> + <file src="$artifactsBinDir$\GetDocument.Insider\$configuration$\net6.0\publish\*.*" target="tools\net6.0" /> </files> </package> diff --git a/src/Tools/GetDocumentInsider/src/Commands/GetDocumentCommand.cs b/src/Tools/GetDocumentInsider/src/Commands/GetDocumentCommand.cs index d115e46c860..dc3597bd28f 100644 --- a/src/Tools/GetDocumentInsider/src/Commands/GetDocumentCommand.cs +++ b/src/Tools/GetDocumentInsider/src/Commands/GetDocumentCommand.cs @@ -5,7 +5,7 @@ using System; using System.IO; using System.Linq; using System.Reflection; -#if NETCOREAPP2_1 +#if NETCOREAPP using System.Runtime.Loader; #endif using Microsoft.Extensions.CommandLineUtils; @@ -69,7 +69,7 @@ namespace Microsoft.Extensions.ApiDescription.Tool.Commands } } -#if NETCOREAPP2_1 +#if NETCOREAPP AssemblyLoadContext.Default.Resolving += (loadContext, assemblyName) => { var name = assemblyName.Name; diff --git a/src/Tools/GetDocumentInsider/src/Commands/GetDocumentCommandWorker.cs b/src/Tools/GetDocumentInsider/src/Commands/GetDocumentCommandWorker.cs index 08946a297f2..ec8d884559a 100644 --- a/src/Tools/GetDocumentInsider/src/Commands/GetDocumentCommandWorker.cs +++ b/src/Tools/GetDocumentInsider/src/Commands/GetDocumentCommandWorker.cs @@ -2,13 +2,20 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Collections; using System.Collections.Generic; using System.IO; using System.Reflection; using System.Text; +using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Tools.Internal; +#if NET6_0_OR_GREATER +using Microsoft.AspNetCore.Hosting.Server; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.AspNetCore.Http.Features; +#endif namespace Microsoft.Extensions.ApiDescription.Tool.Commands { @@ -53,6 +60,88 @@ namespace Microsoft.Extensions.ApiDescription.Tool.Commands return 3; } + #if NET6_0_OR_GREATER + // Register no-op implementations of IServer and IHostLifetime + // to prevent the application server from actually launching after build. + void ConfigureHostBuilder(object hostBuilder) + { + ((IHostBuilder)hostBuilder).ConfigureServices((context, services) => + { + services.AddSingleton<IServer, NoopServer>(); + services.AddSingleton<IHostLifetime, NoopHostLifetime>(); + }); + } + + // Register a TCS to be invoked when the entrypoint (aka Program.Main) + // has finished running. For minimal APIs, this means that all app.X + // calls about the host has been built have been executed. + var waitForStartTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously); + void OnEntryPointExit(Exception exception) + { + // If the entry point exited, we'll try to complete the wait + if (exception != null) + { + waitForStartTcs.TrySetException(exception); + } + else + { + waitForStartTcs.TrySetResult(null); + } + } + + // Resolve the host factory, ensuring that we don't stop the + // application after the host has been built. + var factory = HostFactoryResolver.ResolveHostFactory(assembly, + stopApplication: false, + configureHostBuilder: ConfigureHostBuilder, + entrypointCompleted: OnEntryPointExit); + + if (factory == null) + { + _reporter.WriteError(Resources.FormatMethodsNotFound( + HostFactoryResolver.BuildWebHost, + HostFactoryResolver.CreateHostBuilder, + HostFactoryResolver.CreateWebHostBuilder, + entryPointType)); + + return 8; + } + + try + { + // Retrieve the service provider from the target host. + var services = ((IHost)factory(new[] { $"--{HostDefaults.ApplicationKey}={assemblyName}" })).Services; + if (services == null) + { + _reporter.WriteError(Resources.FormatServiceProviderNotFound( + typeof(IServiceProvider), + HostFactoryResolver.BuildWebHost, + HostFactoryResolver.CreateHostBuilder, + HostFactoryResolver.CreateWebHostBuilder, + entryPointType)); + + return 9; + } + + // Wait for the application to start to ensure that all configurations + // on the WebApplicationBuilder have been processed. + var applicationLifetime = services.GetRequiredService<IHostApplicationLifetime>(); + using (var registration = applicationLifetime.ApplicationStarted.Register(() => waitForStartTcs.TrySetResult(null))) + { + waitForStartTcs.Task.Wait(); + var success = GetDocuments(services); + if (!success) + { + return 10; + } + } + } + catch (Exception ex) + { + _reporter.WriteError(ex.ToString()); + return 11; + } + #else try { var serviceFactory = HostFactoryResolver.ResolveServiceProviderFactory(assembly); @@ -91,6 +180,7 @@ namespace Microsoft.Extensions.ApiDescription.Tool.Commands _reporter.WriteError(ex.ToString()); return 7; } + #endif return 0; } @@ -303,5 +393,22 @@ namespace Microsoft.Extensions.ApiDescription.Tool.Commands return result; } + + #if NET6_0_OR_GREATER + private sealed class NoopHostLifetime : IHostLifetime + { + public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; + public Task WaitForStartAsync(CancellationToken cancellationToken) => Task.CompletedTask; + } + + private sealed class NoopServer : IServer + { + public IFeatureCollection Features { get; } = new FeatureCollection(); + public void Dispose() { } + public Task StartAsync<TContext>(IHttpApplication<TContext> application, CancellationToken cancellationToken) => Task.CompletedTask; + public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; + + } + #endif } } diff --git a/src/Tools/GetDocumentInsider/src/GetDocument.Insider.csproj b/src/Tools/GetDocumentInsider/src/GetDocument.Insider.csproj index 323336d9854..01e9549aecd 100644 --- a/src/Tools/GetDocumentInsider/src/GetDocument.Insider.csproj +++ b/src/Tools/GetDocumentInsider/src/GetDocument.Insider.csproj @@ -5,7 +5,7 @@ <IsPackable>false</IsPackable> <OutputType>Exe</OutputType> <RootNamespace>Microsoft.Extensions.ApiDescription.Tool</RootNamespace> - <TargetFrameworks>netcoreapp2.1;$(DefaultNetFxTargetFramework)</TargetFrameworks> + <TargetFrameworks>netcoreapp2.1;$(DefaultNetCoreTargetFramework);$(DefaultNetFxTargetFramework)</TargetFrameworks> <IsShippingPackage>false</IsShippingPackage> </PropertyGroup> @@ -13,6 +13,11 @@ <Reference Include="Microsoft.CSharp" /> </ItemGroup> + <ItemGroup Condition="'$(TargetFramework)' == '$(DefaultNetCoreTargetFramework)'"> + <Reference Include="Microsoft.AspNetCore.Hosting.Server.Abstractions" /> + <Reference Include="Microsoft.Extensions.Hosting.Abstractions" /> + </ItemGroup> + <ItemGroup> <PackageReference Include="System.Diagnostics.DiagnosticSource" Version="4.6.0"> <AllowExplicitReference>true</AllowExplicitReference> diff --git a/src/Tools/dotnet-getdocument/src/Commands/InvokeCommand.cs b/src/Tools/dotnet-getdocument/src/Commands/InvokeCommand.cs index 45f7776aba5..aca43a840aa 100644 --- a/src/Tools/dotnet-getdocument/src/Commands/InvokeCommand.cs +++ b/src/Tools/dotnet-getdocument/src/Commands/InvokeCommand.cs @@ -79,9 +79,16 @@ namespace Microsoft.Extensions.ApiDescription.Tool.Commands projectName, targetFramework.Version)); } + else if (targetFramework.Version >= new Version(6, 0)) + { + toolsDirectory = Path.Combine(thisPath, $"net{targetFramework.Version}"); + } + else + { + toolsDirectory = Path.Combine(thisPath, "netcoreapp2.1"); + } executable = DotNetMuxer.MuxerPathOrDefault(); - toolsDirectory = Path.Combine(thisPath, "netcoreapp2.1"); args.Add("exec"); args.Add("--depsFile"); diff --git a/src/Tools/dotnet-getdocument/src/dotnet-getdocument.csproj b/src/Tools/dotnet-getdocument/src/dotnet-getdocument.csproj index 7bcdcbcd7ac..3b37a5ea784 100644 --- a/src/Tools/dotnet-getdocument/src/dotnet-getdocument.csproj +++ b/src/Tools/dotnet-getdocument/src/dotnet-getdocument.csproj @@ -5,7 +5,7 @@ <IsPackable>false</IsPackable> <OutputType>Exe</OutputType> <RootNamespace>Microsoft.Extensions.ApiDescription.Tool</RootNamespace> - <TargetFramework>netcoreapp2.1</TargetFramework> + <TargetFrameworks>netcoreapp2.1;$(DefaultNetCoreTargetFramework)</TargetFrameworks> <UseAppHost>false</UseAppHost> <IsShippingPackage>false</IsShippingPackage> </PropertyGroup> -- GitLab