Skip to content

Commit 506f8ce

Browse files
authored
Minimal hosting (#31825)
* Port WebApplication WIP * Remove private copy of Host's ConfigureDefaults * Add sample. "Fix" startup. * Add IAsyncDisposable support * Add unit tests and make IServer replaceable * Hide WebApplication.Dispose in favor of DisposeAsync * seal WebApplication and WebApplicationBuilder * Add logging config functional test * Update MinimalSample to use WebApplication!!! * Seal Configuration and add WebApplicationBuilder_CanClearDefaultLoggers test * Explain why it's OK to noop in ConfigurationHostBuilder * Match the proposed API * Make Configure(Web)?HostBuilder public - simplify sample * missed one * Fix host builder case sensitivity and add tests. * "NotFound" -> string.Empty * Address PR feedback * Fix handling of ASPNETCORE_ environment variables * Add WebHostEnvironment.ApplyConfigurationSettings * cleanup * React to API review feedback * Addresses IEnumerable<string> -> ICollection<string> * Addresses -> Urls * Make Urls non-nullable * Add more tests
1 parent 1accbe2 commit 506f8ce

File tree

13 files changed

+1511
-42
lines changed

13 files changed

+1511
-42
lines changed

src/DefaultBuilder/DefaultBuilder.slnf

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,6 @@
55
"src\\DefaultBuilder\\samples\\SampleApp\\DefaultBuilder.SampleApp.csproj",
66
"src\\DefaultBuilder\\test\\Microsoft.AspNetCore.Tests\\Microsoft.AspNetCore.Tests.csproj",
77
"src\\DefaultBuilder\\test\\Microsoft.AspNetCore.FunctionalTests\\Microsoft.AspNetCore.FunctionalTests.csproj",
8-
"src\\DefaultBuilder\\testassets\\CreateDefaultBuilderApp\\CreateDefaultBuilderApp.csproj",
9-
"src\\DefaultBuilder\\testassets\\CreateDefaultBuilderOfTApp\\CreateDefaultBuilderOfTApp.csproj",
10-
"src\\DefaultBuilder\\testassets\\DependencyInjectionApp\\DependencyInjectionApp.csproj",
11-
"src\\DefaultBuilder\\testassets\\StartRequestDelegateUrlApp\\StartRequestDelegateUrlApp.csproj",
12-
"src\\DefaultBuilder\\testassets\\StartRouteBuilderUrlApp\\StartRouteBuilderUrlApp.csproj",
13-
"src\\DefaultBuilder\\testassets\\StartWithIApplicationBuilderUrlApp\\StartWithIApplicationBuilderUrlApp.csproj",
148
"src\\DefaultBuilder\\src\\Microsoft.AspNetCore.csproj",
159
"src\\Hosting\\Server.IntegrationTesting\\src\\Microsoft.AspNetCore.Server.IntegrationTesting.csproj",
1610
"src\\Middleware\\StaticFiles\\src\\Microsoft.AspNetCore.StaticFiles.csproj"

src/DefaultBuilder/samples/SampleApp/Program.cs

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
5+
using System.Threading.Tasks;
56
using Microsoft.AspNetCore;
67
using Microsoft.AspNetCore.Builder;
78
using Microsoft.AspNetCore.Hosting;
@@ -14,17 +15,14 @@ namespace SampleApp
1415
{
1516
public class Program
1617
{
17-
public static void Main(string[] args)
18+
public static async Task Main(string[] args)
1819
{
19-
CreateHostBuilder(args).Build().Run();
20-
}
20+
await using var webApp = WebApplication.Create(args);
2121

22-
public static IHostBuilder CreateHostBuilder(string[] args) =>
23-
Host.CreateDefaultBuilder(args)
24-
.ConfigureWebHostDefaults(webBuilder =>
25-
{
26-
webBuilder.UseStartup<Startup>();
27-
});
22+
webApp.MapGet("/", (Func<string>)(() => "Hello, World!"));
23+
24+
await webApp.RunAsync();
25+
}
2826

2927
private static void HelloWorld()
3028
{
@@ -80,8 +78,7 @@ private static void CustomApplicationBuilder()
8078
Console.ReadKey();
8179
}
8280
}
83-
84-
private static void StartupClass(string[] args)
81+
private static void DirectWebHost(string[] args)
8582
{
8683
// Using defaults with a Startup class
8784
using (var host = WebHost.CreateDefaultBuilder(args)
@@ -107,5 +104,17 @@ private static void HostBuilderWithWebHost(string[] args)
107104

108105
host.Run();
109106
}
107+
108+
private static void DefaultGenericHost(string[] args)
109+
{
110+
CreateHostBuilder(args).Build().Run();
111+
}
112+
113+
public static IHostBuilder CreateHostBuilder(string[] args) =>
114+
Host.CreateDefaultBuilder(args)
115+
.ConfigureWebHostDefaults(webBuilder =>
116+
{
117+
webBuilder.UseStartup<Startup>();
118+
});
110119
}
111120
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using Microsoft.AspNetCore.Builder;
7+
using Microsoft.Extensions.Configuration;
8+
using Microsoft.Extensions.DependencyInjection;
9+
using Microsoft.Extensions.Hosting;
10+
11+
namespace Microsoft.AspNetCore.Hosting
12+
{
13+
// This exists solely to bootstrap the configuration
14+
internal class BootstrapHostBuilder : IHostBuilder
15+
{
16+
public IDictionary<object, object> Properties { get; } = new Dictionary<object, object>();
17+
private readonly HostBuilderContext _context;
18+
private readonly Configuration _configuration;
19+
private readonly WebHostEnvironment _environment;
20+
21+
public BootstrapHostBuilder(Configuration configuration, WebHostEnvironment webHostEnvironment)
22+
{
23+
_configuration = configuration;
24+
_environment = webHostEnvironment;
25+
_context = new HostBuilderContext(Properties)
26+
{
27+
Configuration = configuration,
28+
HostingEnvironment = webHostEnvironment
29+
};
30+
}
31+
32+
public IHost Build()
33+
{
34+
// HostingHostBuilderExtensions.ConfigureDefaults should never call this.
35+
throw new InvalidOperationException();
36+
}
37+
38+
public IHostBuilder ConfigureAppConfiguration(Action<HostBuilderContext, IConfigurationBuilder> configureDelegate)
39+
{
40+
configureDelegate(_context, _configuration);
41+
_environment.ApplyConfigurationSettings(_configuration);
42+
_configuration.ChangeBasePath(_environment.ContentRootPath);
43+
return this;
44+
}
45+
46+
public IHostBuilder ConfigureContainer<TContainerBuilder>(Action<HostBuilderContext, TContainerBuilder> configureDelegate)
47+
{
48+
// This is not called by HostingHostBuilderExtensions.ConfigureDefaults currently, but that could change in the future.
49+
// If this does get called in the future, it should be called again at a later stage on the ConfigureHostBuilder.
50+
return this;
51+
}
52+
53+
public IHostBuilder ConfigureHostConfiguration(Action<IConfigurationBuilder> configureDelegate)
54+
{
55+
configureDelegate(_configuration);
56+
_environment.ApplyConfigurationSettings(_configuration);
57+
_configuration.ChangeBasePath(_environment.ContentRootPath);
58+
return this;
59+
}
60+
61+
public IHostBuilder ConfigureServices(Action<HostBuilderContext, IServiceCollection> configureDelegate)
62+
{
63+
// HostingHostBuilderExtensions.ConfigureDefaults calls this via ConfigureLogging
64+
// during the initial config stage. It should be called again later on the ConfigureHostBuilder.
65+
return this;
66+
}
67+
68+
public IHostBuilder UseServiceProviderFactory<TContainerBuilder>(IServiceProviderFactory<TContainerBuilder> factory) where TContainerBuilder : notnull
69+
{
70+
// This is not called by HostingHostBuilderExtensions.ConfigureDefaults currently, but that chould change in the future.
71+
// If this does get called in the future, it should be called again at a later stage on the ConfigureHostBuilder.
72+
return this;
73+
}
74+
75+
public IHostBuilder UseServiceProviderFactory<TContainerBuilder>(Func<HostBuilderContext, IServiceProviderFactory<TContainerBuilder>> factory) where TContainerBuilder : notnull
76+
{
77+
// HostingHostBuilderExtensions.ConfigureDefaults calls this via UseDefaultServiceProvider
78+
// during the initial config stage. It should be called again later on the ConfigureHostBuilder.
79+
return this;
80+
}
81+
}
82+
}
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Collections;
6+
using System.Collections.Generic;
7+
using Microsoft.Extensions.Configuration;
8+
using Microsoft.Extensions.FileProviders;
9+
using Microsoft.Extensions.Primitives;
10+
11+
// TODO: Microsft.Extensions.Configuration API Proposal
12+
namespace Microsoft.AspNetCore.Builder
13+
{
14+
/// <summary>
15+
/// Configuration is mutable configuration object. It is both a configuration builder and an IConfigurationRoot.
16+
/// As sources are added, it updates its current view of configuration. Once Build is called, configuration is frozen.
17+
/// </summary>
18+
public sealed class Configuration : IConfigurationRoot, IConfigurationBuilder
19+
{
20+
private readonly ConfigurationBuilder _builder = new();
21+
private IConfigurationRoot _configuration;
22+
23+
/// <summary>
24+
/// Gets or sets a configuration value.
25+
/// </summary>
26+
/// <param name="key">The configuration key.</param>
27+
/// <returns>The configuration value.</returns>
28+
public string this[string key] { get => _configuration[key]; set => _configuration[key] = value; }
29+
30+
/// <summary>
31+
/// Gets a configuration sub-section with the specified key.
32+
/// </summary>
33+
/// <param name="key">The key of the configuration section.</param>
34+
/// <returns>The <see cref="IConfigurationSection"/>.</returns>
35+
/// <remarks>
36+
/// This method will never return <c>null</c>. If no matching sub-section is found with the specified key,
37+
/// an empty <see cref="IConfigurationSection"/> will be returned.
38+
/// </remarks>
39+
public IConfigurationSection GetSection(string key)
40+
{
41+
return _configuration.GetSection(key);
42+
}
43+
44+
/// <summary>
45+
/// Gets the immediate descendant configuration sub-sections.
46+
/// </summary>
47+
/// <returns>The configuration sub-sections.</returns>
48+
public IEnumerable<IConfigurationSection> GetChildren() => _configuration.GetChildren();
49+
50+
IDictionary<string, object> IConfigurationBuilder.Properties => _builder.Properties;
51+
52+
// TODO: Handle modifications to Sources and keep the configuration root in sync
53+
IList<IConfigurationSource> IConfigurationBuilder.Sources => Sources;
54+
55+
internal IList<IConfigurationSource> Sources { get; }
56+
57+
IEnumerable<IConfigurationProvider> IConfigurationRoot.Providers => _configuration.Providers;
58+
59+
/// <summary>
60+
/// Creates a new <see cref="Configuration"/>.
61+
/// </summary>
62+
public Configuration()
63+
{
64+
_configuration = _builder.Build();
65+
66+
var sources = new ConfigurationSources(_builder.Sources, UpdateConfigurationRoot);
67+
68+
Sources = sources;
69+
}
70+
71+
internal void ChangeBasePath(string path)
72+
{
73+
this.SetBasePath(path);
74+
UpdateConfigurationRoot();
75+
}
76+
77+
internal void ChangeFileProvider(IFileProvider fileProvider)
78+
{
79+
this.SetFileProvider(fileProvider);
80+
UpdateConfigurationRoot();
81+
}
82+
83+
private void UpdateConfigurationRoot()
84+
{
85+
var current = _configuration;
86+
if (current is IDisposable disposable)
87+
{
88+
disposable.Dispose();
89+
}
90+
_configuration = _builder.Build();
91+
}
92+
93+
IConfigurationBuilder IConfigurationBuilder.Add(IConfigurationSource source)
94+
{
95+
Sources.Add(source);
96+
return this;
97+
}
98+
99+
IConfigurationRoot IConfigurationBuilder.Build()
100+
{
101+
// No more modification is expected after this final build
102+
UpdateConfigurationRoot();
103+
return this;
104+
}
105+
106+
IChangeToken IConfiguration.GetReloadToken()
107+
{
108+
// REVIEW: Is this correct?
109+
return _configuration.GetReloadToken();
110+
}
111+
112+
void IConfigurationRoot.Reload()
113+
{
114+
_configuration.Reload();
115+
}
116+
117+
// On source modifications, we rebuild configuration
118+
private class ConfigurationSources : IList<IConfigurationSource>
119+
{
120+
private readonly IList<IConfigurationSource> _sources;
121+
private readonly Action _sourcesModified;
122+
123+
public ConfigurationSources(IList<IConfigurationSource> sources, Action sourcesModified)
124+
{
125+
_sources = sources;
126+
_sourcesModified = sourcesModified;
127+
}
128+
129+
public IConfigurationSource this[int index]
130+
{
131+
get => _sources[index];
132+
set
133+
{
134+
_sources[index] = value;
135+
_sourcesModified();
136+
}
137+
}
138+
139+
public int Count => _sources.Count;
140+
141+
public bool IsReadOnly => _sources.IsReadOnly;
142+
143+
public void Add(IConfigurationSource item)
144+
{
145+
_sources.Add(item);
146+
_sourcesModified();
147+
}
148+
149+
public void Clear()
150+
{
151+
_sources.Clear();
152+
_sourcesModified();
153+
}
154+
155+
public bool Contains(IConfigurationSource item)
156+
{
157+
return _sources.Contains(item);
158+
}
159+
160+
public void CopyTo(IConfigurationSource[] array, int arrayIndex)
161+
{
162+
_sources.CopyTo(array, arrayIndex);
163+
}
164+
165+
public IEnumerator<IConfigurationSource> GetEnumerator()
166+
{
167+
return _sources.GetEnumerator();
168+
}
169+
170+
public int IndexOf(IConfigurationSource item)
171+
{
172+
return _sources.IndexOf(item);
173+
}
174+
175+
public void Insert(int index, IConfigurationSource item)
176+
{
177+
_sources.Insert(index, item);
178+
_sourcesModified();
179+
}
180+
181+
public bool Remove(IConfigurationSource item)
182+
{
183+
var removed = _sources.Remove(item);
184+
_sourcesModified();
185+
return removed;
186+
}
187+
188+
public void RemoveAt(int index)
189+
{
190+
_sources.RemoveAt(index);
191+
_sourcesModified();
192+
}
193+
194+
IEnumerator IEnumerable.GetEnumerator()
195+
{
196+
return GetEnumerator();
197+
}
198+
}
199+
}
200+
}

0 commit comments

Comments
 (0)