Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[release/6.0] Add support for minimal APIs in ApiDescription.Server #44227

Merged
merged 3 commits into from
Oct 4, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
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 @@ -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>
Expand All @@ -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>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
using System.IO;
using System.Linq;
using System.Reflection;
#if NETCOREAPP2_1
#if NETCOREAPP
using System.Runtime.Loader;
#endif
using Microsoft.Extensions.CommandLineUtils;
Expand Down Expand Up @@ -69,7 +69,7 @@ protected override int Execute()
}
}

#if NETCOREAPP2_1
#if NETCOREAPP
AssemblyLoadContext.Default.Resolving += (loadContext, assemblyName) =>
{
var name = assemblyName.Name;
Expand Down
107 changes: 107 additions & 0 deletions src/Tools/GetDocumentInsider/src/Commands/GetDocumentCommandWorker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -53,6 +60,88 @@ public int Process()
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);
Expand Down Expand Up @@ -91,6 +180,7 @@ public int Process()
_reporter.WriteError(ex.ToString());
return 7;
}
#endif

return 0;
}
Expand Down Expand Up @@ -303,5 +393,22 @@ private object InvokeMethod(MethodInfo method, object instance, object[] argumen

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
}
}
7 changes: 6 additions & 1 deletion src/Tools/GetDocumentInsider/src/GetDocument.Insider.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,19 @@
<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>

<ItemGroup Condition="'$(TargetFramework)' == '$(DefaultNetFxTargetFramework)'">
<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>
Expand Down
9 changes: 8 additions & 1 deletion src/Tools/dotnet-getdocument/src/Commands/InvokeCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,16 @@ protected override int Execute()
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");
Expand Down
2 changes: 1 addition & 1 deletion src/Tools/dotnet-getdocument/src/dotnet-getdocument.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down