Skip to content

[release/6.0-preview6] Handle more cases with the new entry point pattern #33574

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

Closed
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
15 changes: 15 additions & 0 deletions AspNetCore.sln
Original file line number Diff line number Diff line change
Expand Up @@ -1624,6 +1624,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PhotinoTestApp", "src\Compo
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Components.WebView.Photino", "src\Components\WebView\Samples\PhotinoPlatform\src\Microsoft.AspNetCore.Components.WebView.Photino.csproj", "{B1AA24A4-5E02-4DC1-B57F-6EB03F91E4DD}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SimpleWebSiteWithWebApplicationBuilderException", "src\Mvc\test\WebSites\SimpleWebSiteWithWebApplicationBuilderException\SimpleWebSiteWithWebApplicationBuilderException.csproj", "{5C641396-7E92-4F5C-A5A1-B4CDF480539B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -7731,6 +7733,18 @@ Global
{B1AA24A4-5E02-4DC1-B57F-6EB03F91E4DD}.Release|x64.Build.0 = Release|Any CPU
{B1AA24A4-5E02-4DC1-B57F-6EB03F91E4DD}.Release|x86.ActiveCfg = Release|Any CPU
{B1AA24A4-5E02-4DC1-B57F-6EB03F91E4DD}.Release|x86.Build.0 = Release|Any CPU
{5C641396-7E92-4F5C-A5A1-B4CDF480539B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5C641396-7E92-4F5C-A5A1-B4CDF480539B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5C641396-7E92-4F5C-A5A1-B4CDF480539B}.Debug|x64.ActiveCfg = Debug|Any CPU
{5C641396-7E92-4F5C-A5A1-B4CDF480539B}.Debug|x64.Build.0 = Debug|Any CPU
{5C641396-7E92-4F5C-A5A1-B4CDF480539B}.Debug|x86.ActiveCfg = Debug|Any CPU
{5C641396-7E92-4F5C-A5A1-B4CDF480539B}.Debug|x86.Build.0 = Debug|Any CPU
{5C641396-7E92-4F5C-A5A1-B4CDF480539B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5C641396-7E92-4F5C-A5A1-B4CDF480539B}.Release|Any CPU.Build.0 = Release|Any CPU
{5C641396-7E92-4F5C-A5A1-B4CDF480539B}.Release|x64.ActiveCfg = Release|Any CPU
{5C641396-7E92-4F5C-A5A1-B4CDF480539B}.Release|x64.Build.0 = Release|Any CPU
{5C641396-7E92-4F5C-A5A1-B4CDF480539B}.Release|x86.ActiveCfg = Release|Any CPU
{5C641396-7E92-4F5C-A5A1-B4CDF480539B}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -8535,6 +8549,7 @@ Global
{3EC71A0E-6515-4A5A-B759-F0BCF1BCFC56} = {44963D50-8B58-44E6-918D-788BCB406695}
{558C46DE-DE16-41D5-8DB7-D6D748E32977} = {3EC71A0E-6515-4A5A-B759-F0BCF1BCFC56}
{B1AA24A4-5E02-4DC1-B57F-6EB03F91E4DD} = {44963D50-8B58-44E6-918D-788BCB406695}
{5C641396-7E92-4F5C-A5A1-B4CDF480539B} = {088C37A5-30D2-40FB-B031-D163CFBED006}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {3E8720B3-DBDD-498C-B383-2CC32A054E8F}
Expand Down
33 changes: 25 additions & 8 deletions src/Mvc/Mvc.Testing/src/DeferredHostBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ internal class DeferredHostBuilder : IHostBuilder
private Action<IHostBuilder> _configure;
private Func<string[], object>? _hostFactory;

// This task represents a call to IHost.Start, we create it here preemptively in case the application
// exits due to an exception or because it didn't wait for the shutdown signal
private readonly TaskCompletionSource _hostStartTcs = new(TaskCreationOptions.RunContinuationsAsynchronously);

public DeferredHostBuilder()
{
_configure = b =>
Expand All @@ -37,7 +41,7 @@ public IHost Build()
var host = (IHost)_hostFactory!(Array.Empty<string>());

// We can't return the host directly since we need to defer the call to StartAsync
return new DeferredHost(host);
return new DeferredHost(host, _hostStartTcs);
}

public IHostBuilder ConfigureAppConfiguration(Action<HostBuilderContext, IConfigurationBuilder> configureDelegate)
Expand Down Expand Up @@ -81,6 +85,19 @@ public void ConfigureHostBuilder(object hostBuilder)
_configure(((IHostBuilder)hostBuilder));
}

public void EntryPointCompleted(Exception? exception)
{
// If the entry point completed we'll set the tcs just in case the application doesn't call IHost.Start/StartAsync.
if (exception is not null)
{
_hostStartTcs.TrySetException(exception);
}
else
{
_hostStartTcs.TrySetResult();
}
}

public void SetHostFactory(Func<string[], object> hostFactory)
{
_hostFactory = hostFactory;
Expand All @@ -89,10 +106,12 @@ public void SetHostFactory(Func<string[], object> hostFactory)
private class DeferredHost : IHost, IAsyncDisposable
{
private readonly IHost _host;
private readonly TaskCompletionSource _hostStartedTcs;

public DeferredHost(IHost host)
public DeferredHost(IHost host, TaskCompletionSource hostStartedTcs)
{
_host = host;
_hostStartedTcs = hostStartedTcs;
}

public IServiceProvider Services => _host.Services;
Expand All @@ -109,20 +128,18 @@ public ValueTask DisposeAsync()
return default;
}

public Task StartAsync(CancellationToken cancellationToken = default)
public async Task StartAsync(CancellationToken cancellationToken = default)
{
var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);

// Wait on the existing host to start running and have this call wait on that. This avoids starting the actual host too early and
// leaves the application in charge of calling start.

using var reg = cancellationToken.UnsafeRegister(_ => tcs.TrySetCanceled(), null);
using var reg = cancellationToken.UnsafeRegister(_ => _hostStartedTcs.TrySetCanceled(), null);

// REVIEW: This will deadlock if the application creates the host but never calls start. This is mitigated by the cancellationToken
// but it's rarely a valid token for Start
_host.Services.GetRequiredService<IHostApplicationLifetime>().ApplicationStarted.UnsafeRegister(_ => tcs.TrySetResult(), null);
using var reg2 = _host.Services.GetRequiredService<IHostApplicationLifetime>().ApplicationStarted.UnsafeRegister(_ => _hostStartedTcs.TrySetResult(), null);

return tcs.Task;
await _hostStartedTcs.Task;
}

public Task StopAsync(CancellationToken cancellationToken = default) => _host.StopAsync(cancellationToken);
Expand Down
6 changes: 5 additions & 1 deletion src/Mvc/Mvc.Testing/src/WebApplicationFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,11 @@ private void EnsureServer()
{
var deferredHostBuilder = new DeferredHostBuilder();
// This helper call does the hard work to determine if we can fallback to diagnostic source events to get the host instance
var factory = HostFactoryResolver.ResolveHostFactory(typeof(TEntryPoint).Assembly, stopApplication: false, configureHostBuilder: deferredHostBuilder.ConfigureHostBuilder);
var factory = HostFactoryResolver.ResolveHostFactory(
typeof(TEntryPoint).Assembly,
stopApplication: false,
configureHostBuilder: deferredHostBuilder.ConfigureHostBuilder,
entrypointCompleted: deferredHostBuilder.EntryPointCompleted);

if (factory is not null)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
<ProjectReference Include="..\WebSites\SecurityWebSite\SecurityWebSite.csproj" />
<ProjectReference Include="..\WebSites\SimpleWebSite\SimpleWebSite.csproj" />
<ProjectReference Include="..\WebSites\SimpleWebSiteWithWebApplicationBuilder\SimpleWebSiteWithWebApplicationBuilder.csproj" />
<ProjectReference Include="..\WebSites\SimpleWebSiteWithWebApplicationBuilderException\SimpleWebSiteWithWebApplicationBuilderException.csproj" />
<ProjectReference Include="..\WebSites\TagHelpersWebSite\TagHelpersWebSite.csproj" />
<ProjectReference Include="..\WebSites\VersioningWebSite\VersioningWebSite.csproj" />
<ProjectReference Include="..\WebSites\XmlFormattersWebSite\XmlFormattersWebSite.csproj" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Xunit;

namespace Microsoft.AspNetCore.Mvc.FunctionalTests
{
public class SimpleWithWebApplicationBuilderExceptionTests : IClassFixture<MvcTestFixture<SimpleWebSiteWithWebApplicationBuilderException.FakeStartup>>
{
private MvcTestFixture<SimpleWebSiteWithWebApplicationBuilderException.FakeStartup> _fixture;

public SimpleWithWebApplicationBuilderExceptionTests(MvcTestFixture<SimpleWebSiteWithWebApplicationBuilderException.FakeStartup> fixture)
{
_fixture = fixture;
}

[Fact]
public void ExceptionThrownFromApplicationCanBeObserved()
{
var ex = Assert.Throws<InvalidOperationException>(() => _fixture.CreateClient());
Assert.Equal("This application failed to start", ex.Message);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

/// <summary>
/// This is a class we use to reference this assembly statically from tests
/// </summary>
namespace SimpleWebSiteWithWebApplicationBuilderException
{
public class FakeStartup
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using Microsoft.AspNetCore.Builder;

var app = WebApplication.Create(args);

app.MapGet("/", (Func<string>)(() => "Hello World"));

throw new InvalidOperationException("This application failed to start");
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:51807/",
"sslPort": 44365
}
},
"profiles": {
"SimpleWebSite": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "https://localhost:5001;http://localhost:5000"
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
</PropertyGroup>

<ItemGroup>
<Reference Include="Microsoft.AspNetCore" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
SimpleWebSiteWithWebApplicationBuilderException
===
This sample web project illustrates a minimal site using WebApplicationBuilder that throws in main.
Please build from root (`.\build.cmd` on Windows; `./build.sh` elsewhere) before using this site.