Skip to content

Apply hosting environment via command line args #34794

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

Merged
merged 3 commits into from
Jul 28, 2021
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
19 changes: 17 additions & 2 deletions src/Mvc/Mvc.Testing/src/DeferredHostBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ internal class DeferredHostBuilder : IHostBuilder
private Action<IHostBuilder> _configure;
private Func<string[], object>? _hostFactory;

private readonly ConfigurationManager _hostConfiguration = new();

// 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);
Expand All @@ -38,8 +40,18 @@ public DeferredHostBuilder()

public IHost Build()
{
// Hosting configuration is being provided by args so that
// we can impact WebApplicationBuilder based applications.
var args = new List<string>();

// Transform the host configuration into command line arguments
foreach (var (key, value) in _hostConfiguration.AsEnumerable())
{
args.Add($"--{key}={value}");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to do any fancy escaping for value? Like can values have spaces which would break config parsing? I'm not super familiar with this area

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see how it breaks given how the parsing works. We're not parsing the command line string, we're parsing each arg.

I also don't see a way to escape - or / so that might be the only problem.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An = in the key would also mess this up, right? I'm not sure how realistic that is.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also don't see a way to escape - or / so that might be the only problem.

You could always base64 encode/decode. Stripping and adding back the = padding characters might be a pain though.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we wait until somebody hits this then we fix it by supporting escape sequences in the command line configuration provider, the problem is general.

}

// This will never be null if the case where Build is being called
var host = (IHost)_hostFactory!(Array.Empty<string>());
var host = (IHost)_hostFactory!(args.ToArray());

// We can't return the host directly since we need to defer the call to StartAsync
return new DeferredHost(host, _hostStartTcs);
Expand All @@ -59,7 +71,10 @@ public IHostBuilder ConfigureContainer<TContainerBuilder>(Action<HostBuilderCont

public IHostBuilder ConfigureHostConfiguration(Action<IConfigurationBuilder> configureDelegate)
{
_configure += b => b.ConfigureHostConfiguration(configureDelegate);
// Run this immediately so that we can capture the host configuration
// before we pass it to the application. We can do this for app configuration
// as well if it becomes necessary.
configureDelegate(_hostConfiguration);
return this;
}

Expand Down
1 change: 1 addition & 0 deletions src/Mvc/Mvc.Testing/src/WebApplicationFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ private void EnsureServer()
if (builder is null)
{
var deferredHostBuilder = new DeferredHostBuilder();
deferredHostBuilder.UseEnvironment(Environments.Development);
// 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,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,30 +1,37 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Xunit;

namespace Microsoft.AspNetCore.Mvc.FunctionalTests
{
public class SimpleWithWebApplicationBuilderTests : IClassFixture<MvcTestFixture<SimpleWebSiteWithWebApplicationBuilder.FakeStartup>>
{
private readonly MvcTestFixture<SimpleWebSiteWithWebApplicationBuilder.FakeStartup> _fixture;

public SimpleWithWebApplicationBuilderTests(MvcTestFixture<SimpleWebSiteWithWebApplicationBuilder.FakeStartup> fixture)
{
Client = fixture.CreateDefaultClient();
_fixture = fixture;
}

public HttpClient Client { get; }

[Fact]
public async Task HelloWorld()
{
// Arrange
var expected = "Hello World";
using var client = _fixture.CreateDefaultClient();

// Act
var content = await Client.GetStringAsync("http://localhost/");
var content = await client.GetStringAsync("http://localhost/");

// Assert
Assert.Equal(expected, content);
Expand All @@ -35,9 +42,10 @@ public async Task JsonResult_Works()
{
// Arrange
var expected = "{\"name\":\"John\",\"age\":42}";
using var client = _fixture.CreateDefaultClient();

// Act
var response = await Client.GetAsync("/json");
var response = await client.GetAsync("/json");

// Assert
await response.AssertStatusCodeAsync(HttpStatusCode.OK);
Expand All @@ -50,9 +58,10 @@ public async Task OkObjectResult_Works()
{
// Arrange
var expected = "{\"name\":\"John\",\"age\":42}";
using var client = _fixture.CreateDefaultClient();

// Act
var response = await Client.GetAsync("/ok-object");
var response = await client.GetAsync("/ok-object");

// Assert
await response.AssertStatusCodeAsync(HttpStatusCode.OK);
Expand All @@ -65,9 +74,10 @@ public async Task AcceptedObjectResult_Works()
{
// Arrange
var expected = "{\"name\":\"John\",\"age\":42}";
using var client = _fixture.CreateDefaultClient();

// Act
var response = await Client.GetAsync("/accepted-object");
var response = await client.GetAsync("/accepted-object");

// Assert
await response.AssertStatusCodeAsync(HttpStatusCode.Accepted);
Expand All @@ -79,8 +89,11 @@ public async Task AcceptedObjectResult_Works()
[Fact]
public async Task ActionReturningMoreThanOneResult_NotFound()
{
// Arrange
using var client = _fixture.CreateDefaultClient();

// Act
var response = await Client.GetAsync("/many-results?id=-1");
var response = await client.GetAsync("/many-results?id=-1");

// Assert
await response.AssertStatusCodeAsync(HttpStatusCode.NotFound);
Expand All @@ -89,23 +102,75 @@ public async Task ActionReturningMoreThanOneResult_NotFound()
[Fact]
public async Task ActionReturningMoreThanOneResult_Found()
{
// Arrange
using var client = _fixture.CreateDefaultClient();

// Act
var response = await Client.GetAsync("/many-results?id=7");
var response = await client.GetAsync("/many-results?id=7");

// Assert
await response.AssertStatusCodeAsync(HttpStatusCode.MovedPermanently);
Assert.Equal("/json", response.Headers.Location.ToString());
}

[Fact]
public async Task ActionReturningProblemDetails_ConfiguresContentType()
public async Task DefaultEnvironment_Is_Development()
{
// Arrange
var expected = "Development";
using var client = new WebApplicationFactory<SimpleWebSiteWithWebApplicationBuilder.FakeStartup>().CreateClient();

// Act
var content = await client.GetStringAsync("http://localhost/environment");

// Assert
Assert.Equal(expected, content);
}

[Fact]
public async Task Configuration_Can_Be_Overridden()
{
// Arrange
var fixture = _fixture.WithWebHostBuilder(builder =>
{
builder.ConfigureAppConfiguration(builder =>
{
var config = new[]
{
KeyValuePair.Create("Greeting", "Bonjour tout le monde"),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You kept it 😄

};

builder.AddInMemoryCollection(config);
});
});

var expected = "Bonjour tout le monde";
using var client = fixture.CreateDefaultClient();

// Act
var response = await Client.GetAsync("/problem");
var content = await client.GetStringAsync("http://localhost/greeting");

// Assert
await response.AssertStatusCodeAsync(HttpStatusCode.InternalServerError);
Assert.Equal("application/problem+json", response.Content.Headers.ContentType.ToString());
Assert.Equal(expected, content);
}

[Fact]
public async Task Environment_Can_Be_Overridden()
{
// Arrange
var fixture = _fixture.WithWebHostBuilder(builder =>
{
builder.UseEnvironment(Environments.Staging);
});

var expected = "Staging";
using var client = fixture.CreateDefaultClient();

// Act
var content = await client.GetStringAsync("http://localhost/environment");

// Assert
Assert.Equal(expected, content);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@

app.MapGet("/problem", () => Results.Problem("Some problem"));

app.MapGet("/environment", (IHostEnvironment environment) => environment.EnvironmentName);

app.MapGet("/greeting", (IConfiguration config) => config["Greeting"]);

app.Run();

record Person(string Name, int Age);