Skip to content

Commit 75ea342

Browse files
committed
Add IHostApplicationBuilder and implement it in HostApplicationBuilder.
- Move ILoggingBuilder from MS.Ext.Logging to MS.Ext.Logging.Abstractions so IHostApplicationBuilder can reference it. - Add IConfigurationManager and implement it in ConfigurationManager. - Add CompatiibilitySuppressions. These errors are caused by ILoggingBuilder being moved to Logging.Abstractions, and ApiCompat not respecting TypeForwardedTo attributes. Fix #85486
1 parent c3859ad commit 75ea342

19 files changed

+232
-50
lines changed

src/libraries/Microsoft.Extensions.Configuration.Abstractions/ref/Microsoft.Extensions.Configuration.Abstractions.cs

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,22 @@
66

77
namespace Microsoft.Extensions.Configuration
88
{
9+
public readonly partial struct ConfigurationDebugViewContext
10+
{
11+
private readonly object _dummy;
12+
private readonly int _dummyPrimitive;
13+
public ConfigurationDebugViewContext(string path, string key, string? value, Microsoft.Extensions.Configuration.IConfigurationProvider configurationProvider) { throw null; }
14+
public Microsoft.Extensions.Configuration.IConfigurationProvider ConfigurationProvider { get { throw null; } }
15+
public string Key { get { throw null; } }
16+
public string Path { get { throw null; } }
17+
public string? Value { get { throw null; } }
18+
}
919
public static partial class ConfigurationExtensions
1020
{
1121
public static Microsoft.Extensions.Configuration.IConfigurationBuilder Add<TSource>(this Microsoft.Extensions.Configuration.IConfigurationBuilder builder, System.Action<TSource>? configureSource) where TSource : Microsoft.Extensions.Configuration.IConfigurationSource, new() { throw null; }
1222
public static System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<string, string?>> AsEnumerable(this Microsoft.Extensions.Configuration.IConfiguration configuration) { throw null; }
1323
public static System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<string, string?>> AsEnumerable(this Microsoft.Extensions.Configuration.IConfiguration configuration, bool makePathsRelative) { throw null; }
14-
public static bool Exists([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] this Microsoft.Extensions.Configuration.IConfigurationSection? section) { throw null; }
24+
public static bool Exists([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] this Microsoft.Extensions.Configuration.IConfigurationSection? section) { throw null; }
1525
public static string? GetConnectionString(this Microsoft.Extensions.Configuration.IConfiguration configuration, string name) { throw null; }
1626
public static Microsoft.Extensions.Configuration.IConfigurationSection GetRequiredSection(this Microsoft.Extensions.Configuration.IConfiguration configuration, string key) { throw null; }
1727
}
@@ -33,15 +43,7 @@ public static partial class ConfigurationPath
3343
public static partial class ConfigurationRootExtensions
3444
{
3545
public static string GetDebugView(this Microsoft.Extensions.Configuration.IConfigurationRoot root) { throw null; }
36-
public static string GetDebugView(this IConfigurationRoot root, System.Func<ConfigurationDebugViewContext, string>? processValue) { throw null; }
37-
}
38-
public readonly partial struct ConfigurationDebugViewContext
39-
{
40-
public ConfigurationDebugViewContext(string path, string key, string? value, IConfigurationProvider configurationProvider) { throw null; }
41-
public string Path { get; }
42-
public string Key { get; }
43-
public string? Value { get; }
44-
public IConfigurationProvider ConfigurationProvider { get; }
46+
public static string GetDebugView(this Microsoft.Extensions.Configuration.IConfigurationRoot root, System.Func<Microsoft.Extensions.Configuration.ConfigurationDebugViewContext, string>? processValue) { throw null; }
4547
}
4648
public partial interface IConfiguration
4749
{
@@ -57,6 +59,9 @@ public partial interface IConfigurationBuilder
5759
Microsoft.Extensions.Configuration.IConfigurationBuilder Add(Microsoft.Extensions.Configuration.IConfigurationSource source);
5860
Microsoft.Extensions.Configuration.IConfigurationRoot Build();
5961
}
62+
public partial interface IConfigurationManager : Microsoft.Extensions.Configuration.IConfiguration, Microsoft.Extensions.Configuration.IConfigurationBuilder
63+
{
64+
}
6065
public partial interface IConfigurationProvider
6166
{
6267
System.Collections.Generic.IEnumerable<string> GetChildKeys(System.Collections.Generic.IEnumerable<string> earlierKeys, string? parentPath);
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace Microsoft.Extensions.Configuration;
5+
6+
/// <summary>
7+
/// Represents a mutable configuration object.
8+
/// </summary>
9+
/// <remarks>
10+
/// It is both an <see cref="IConfigurationBuilder"/> and an <see cref="IConfiguration"/>.
11+
/// As sources are added, it updates its current view of configuration.
12+
/// </remarks>
13+
public interface IConfigurationManager : IConfiguration, IConfigurationBuilder
14+
{
15+
}

src/libraries/Microsoft.Extensions.Configuration/ref/Microsoft.Extensions.Configuration.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ public ConfigurationKeyComparer() { }
4444
public static Microsoft.Extensions.Configuration.ConfigurationKeyComparer Instance { get { throw null; } }
4545
public int Compare(string? x, string? y) { throw null; }
4646
}
47-
public sealed partial class ConfigurationManager : Microsoft.Extensions.Configuration.IConfiguration, Microsoft.Extensions.Configuration.IConfigurationBuilder, Microsoft.Extensions.Configuration.IConfigurationRoot, System.IDisposable
47+
public sealed partial class ConfigurationManager : Microsoft.Extensions.Configuration.IConfiguration, Microsoft.Extensions.Configuration.IConfigurationBuilder, Microsoft.Extensions.Configuration.IConfigurationManager, Microsoft.Extensions.Configuration.IConfigurationRoot, System.IDisposable
4848
{
4949
public ConfigurationManager() { }
5050
public string? this[string key] { get { throw null; } set { } }

src/libraries/Microsoft.Extensions.Configuration/src/ConfigurationManager.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,15 @@
1414
namespace Microsoft.Extensions.Configuration
1515
{
1616
/// <summary>
17-
/// ConfigurationManager is a mutable configuration object. It is both an <see cref="IConfigurationBuilder"/> and an <see cref="IConfigurationRoot"/>.
18-
/// As sources are added, it updates its current view of configuration.
17+
/// Represents a mutable configuration object.
1918
/// </summary>
19+
/// <remarks>
20+
/// It is both an <see cref="IConfigurationBuilder"/> and an <see cref="IConfigurationRoot"/>.
21+
/// As sources are added, it updates its current view of configuration.
22+
/// </remarks>
2023
[DebuggerDisplay("{DebuggerToString(),nq}")]
2124
[DebuggerTypeProxy(typeof(ConfigurationManagerDebugView))]
22-
public sealed class ConfigurationManager : IConfigurationBuilder, IConfigurationRoot, IDisposable
25+
public sealed class ConfigurationManager : IConfigurationManager, IConfigurationBuilder, IConfigurationRoot, IDisposable
2326
{
2427
// Concurrently modifying config sources or properties is not thread-safe. However, it is thread-safe to read config while modifying sources or properties.
2528
private readonly ConfigurationSources _sources;

src/libraries/Microsoft.Extensions.Hosting.Abstractions/ref/Microsoft.Extensions.Hosting.Abstractions.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,15 @@ public partial interface IHost : System.IDisposable
9797
System.Threading.Tasks.Task StartAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken));
9898
System.Threading.Tasks.Task StopAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken));
9999
}
100+
public partial interface IHostApplicationBuilder
101+
{
102+
Microsoft.Extensions.Configuration.IConfigurationManager Configuration { get; }
103+
Microsoft.Extensions.Hosting.IHostEnvironment Environment { get; }
104+
Microsoft.Extensions.Logging.ILoggingBuilder Logging { get; }
105+
System.Collections.Generic.IDictionary<object, object> Properties { get; }
106+
Microsoft.Extensions.DependencyInjection.IServiceCollection Services { get; }
107+
void ConfigureContainer<TContainerBuilder>(Microsoft.Extensions.DependencyInjection.IServiceProviderFactory<TContainerBuilder> factory, System.Action<TContainerBuilder>? configure = null) where TContainerBuilder : notnull;
108+
}
100109
public partial interface IHostApplicationLifetime
101110
{
102111
System.Threading.CancellationToken ApplicationStarted { get; }
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using Microsoft.Extensions.Configuration;
7+
using Microsoft.Extensions.DependencyInjection;
8+
using Microsoft.Extensions.Logging;
9+
10+
namespace Microsoft.Extensions.Hosting;
11+
12+
/// <summary>
13+
/// Represents a hosted applications and services builder which helps manage configuration, logging, lifetime, and more.
14+
/// </summary>
15+
public interface IHostApplicationBuilder
16+
{
17+
/// <summary>
18+
/// Gets a central location for sharing state between components during the host building process.
19+
/// </summary>
20+
IDictionary<object, object> Properties { get; }
21+
22+
/// <summary>
23+
/// Gets the set of key/value configuration properties.
24+
/// </summary>
25+
/// <remarks>
26+
/// This can be mutated by adding more configuration sources, which will update its current view.
27+
/// </remarks>
28+
IConfigurationManager Configuration { get; }
29+
30+
/// <summary>
31+
/// Gets the information about the hosting environment an application is running in.
32+
/// </summary>
33+
IHostEnvironment Environment { get; }
34+
35+
/// <summary>
36+
/// Gets a collection of logging providers for the application to compose. This is useful for adding new logging providers.
37+
/// </summary>
38+
ILoggingBuilder Logging { get; }
39+
40+
/// <summary>
41+
/// Gets a collection of services for the application to compose. This is useful for adding user provided or framework provided services.
42+
/// </summary>
43+
IServiceCollection Services { get; }
44+
45+
/// <summary>
46+
/// Registers a <see cref="IServiceProviderFactory{TContainerBuilder}" /> instance to be used to create the <see cref="IServiceProvider" />.
47+
/// </summary>
48+
/// <param name="factory">The factory object that can create the <typeparamref name="TContainerBuilder"/> and <see cref="IServiceProvider"/>.</param>
49+
/// <param name="configure">
50+
/// A delegate used to configure the <typeparamref T="TContainerBuilder" />. This can be used to configure services using
51+
/// APIS specific to the <see cref="IServiceProviderFactory{TContainerBuilder}" /> implementation.
52+
/// </param>
53+
/// <typeparam name="TContainerBuilder">The type of builder provided by the <see cref="IServiceProviderFactory{TContainerBuilder}" />.</typeparam>
54+
/// <remarks>
55+
/// <para>
56+
/// The <see cref="IServiceProvider"/> is created when this builder is built and so the delegate provided
57+
/// by <paramref name="configure"/> will run after all other services have been registered.
58+
/// </para>
59+
/// <para>
60+
/// Multiple calls to <see cref="ConfigureContainer{TContainerBuilder}(IServiceProviderFactory{TContainerBuilder}, Action{TContainerBuilder})"/> will replace
61+
/// the previously stored <paramref name="factory"/> and <paramref name="configure"/> delegate.
62+
/// </para>
63+
/// </remarks>
64+
void ConfigureContainer<TContainerBuilder>(IServiceProviderFactory<TContainerBuilder> factory, Action<TContainerBuilder>? configure = null) where TContainerBuilder : notnull;
65+
}

src/libraries/Microsoft.Extensions.Hosting.Abstractions/src/Microsoft.Extensions.Hosting.Abstractions.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
<ProjectReference Include="$(LibrariesProjectRoot)Microsoft.Extensions.Configuration.Abstractions\src\Microsoft.Extensions.Configuration.Abstractions.csproj" />
2323
<ProjectReference Include="$(LibrariesProjectRoot)Microsoft.Extensions.DependencyInjection.Abstractions\src\Microsoft.Extensions.DependencyInjection.Abstractions.csproj" />
2424
<ProjectReference Include="$(LibrariesProjectRoot)Microsoft.Extensions.FileProviders.Abstractions\src\Microsoft.Extensions.FileProviders.Abstractions.csproj" />
25+
<ProjectReference Include="$(LibrariesProjectRoot)Microsoft.Extensions.Logging.Abstractions\src\Microsoft.Extensions.Logging.Abstractions.csproj" />
2526
</ItemGroup>
2627

2728
<ItemGroup Condition="!$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'netstandard2.1'))">

src/libraries/Microsoft.Extensions.Hosting/ref/Microsoft.Extensions.Hosting.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,16 @@ public static partial class Host
3232
public static Microsoft.Extensions.Hosting.IHostBuilder CreateDefaultBuilder(string[]? args) { throw null; }
3333
public static Microsoft.Extensions.Hosting.HostApplicationBuilder CreateEmptyApplicationBuilder(Microsoft.Extensions.Hosting.HostApplicationBuilderSettings? settings) { throw null; }
3434
}
35-
public sealed partial class HostApplicationBuilder
35+
public sealed partial class HostApplicationBuilder : Microsoft.Extensions.Hosting.IHostApplicationBuilder
3636
{
3737
public HostApplicationBuilder() { }
3838
public HostApplicationBuilder(Microsoft.Extensions.Hosting.HostApplicationBuilderSettings? settings) { }
3939
public HostApplicationBuilder(string[]? args) { }
4040
public Microsoft.Extensions.Configuration.ConfigurationManager Configuration { get { throw null; } }
4141
public Microsoft.Extensions.Hosting.IHostEnvironment Environment { get { throw null; } }
4242
public Microsoft.Extensions.Logging.ILoggingBuilder Logging { get { throw null; } }
43+
Microsoft.Extensions.Configuration.IConfigurationManager Microsoft.Extensions.Hosting.IHostApplicationBuilder.Configuration { get { throw null; } }
44+
System.Collections.Generic.IDictionary<object, object> Microsoft.Extensions.Hosting.IHostApplicationBuilder.Properties { get { throw null; } }
4345
public Microsoft.Extensions.DependencyInjection.IServiceCollection Services { get { throw null; } }
4446
public Microsoft.Extensions.Hosting.IHost Build() { throw null; }
4547
public void ConfigureContainer<TContainerBuilder>(Microsoft.Extensions.DependencyInjection.IServiceProviderFactory<TContainerBuilder> factory, System.Action<TContainerBuilder>? configure = null) where TContainerBuilder : notnull { }

src/libraries/Microsoft.Extensions.Hosting/src/HostApplicationBuilder.cs

Lines changed: 14 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@
1414
namespace Microsoft.Extensions.Hosting
1515
{
1616
/// <summary>
17-
/// A builder for hosted applications and services which helps manage configuration, logging, lifetime and more.
17+
/// Represents a hosted applications and services builder which helps manage configuration, logging, lifetime, and more.
1818
/// </summary>
19-
public sealed class HostApplicationBuilder
19+
public sealed class HostApplicationBuilder : IHostApplicationBuilder
2020
{
2121
private readonly HostBuilderContext _hostBuilderContext;
2222
private readonly ServiceCollection _serviceCollection = new();
@@ -182,45 +182,28 @@ private void Initialize(HostApplicationBuilderSettings settings, out HostBuilder
182182
logging = new LoggingBuilder(Services);
183183
}
184184

185-
/// <summary>
186-
/// Provides information about the hosting environment an application is running in.
187-
/// </summary>
185+
IDictionary<object, object> IHostApplicationBuilder.Properties => _hostBuilderContext.Properties;
186+
187+
/// <inheritdoc />
188188
public IHostEnvironment Environment => _environment;
189189

190190
/// <summary>
191-
/// A collection of services for the application to compose. This is useful for adding user provided or framework provided services.
191+
/// Gets the set of key/value configuration properties.
192192
/// </summary>
193+
/// <remarks>
194+
/// This can be mutated by adding more configuration sources, which will update its current view.
195+
/// </remarks>
193196
public ConfigurationManager Configuration { get; }
194197

195-
/// <summary>
196-
/// A collection of services for the application to compose. This is useful for adding user provided or framework provided services.
197-
/// </summary>
198+
IConfigurationManager IHostApplicationBuilder.Configuration => Configuration;
199+
200+
/// <inheritdoc />
198201
public IServiceCollection Services => _serviceCollection;
199202

200-
/// <summary>
201-
/// A collection of logging providers for the application to compose. This is useful for adding new logging providers.
202-
/// </summary>
203+
/// <inheritdoc />
203204
public ILoggingBuilder Logging => _logging;
204205

205-
/// <summary>
206-
/// Registers a <see cref="IServiceProviderFactory{TContainerBuilder}" /> instance to be used to create the <see cref="IServiceProvider" />.
207-
/// </summary>
208-
/// <param name="factory">The <see cref="IServiceProviderFactory{TContainerBuilder}" />.</param>
209-
/// <param name="configure">
210-
/// A delegate used to configure the <typeparamref T="TContainerBuilder" />. This can be used to configure services using
211-
/// APIS specific to the <see cref="IServiceProviderFactory{TContainerBuilder}" /> implementation.
212-
/// </param>
213-
/// <typeparam name="TContainerBuilder">The type of builder provided by the <see cref="IServiceProviderFactory{TContainerBuilder}" />.</typeparam>
214-
/// <remarks>
215-
/// <para>
216-
/// <see cref="ConfigureContainer{TContainerBuilder}(IServiceProviderFactory{TContainerBuilder}, Action{TContainerBuilder})"/> is called by <see cref="Build"/>
217-
/// and so the delegate provided by <paramref name="configure"/> will run after all other services have been registered.
218-
/// </para>
219-
/// <para>
220-
/// Multiple calls to <see cref="ConfigureContainer{TContainerBuilder}(IServiceProviderFactory{TContainerBuilder}, Action{TContainerBuilder})"/> will replace
221-
/// the previously stored <paramref name="factory"/> and <paramref name="configure"/> delegate.
222-
/// </para>
223-
/// </remarks>
206+
/// <inheritdoc />
224207
public void ConfigureContainer<TContainerBuilder>(IServiceProviderFactory<TContainerBuilder> factory, Action<TContainerBuilder>? configure = null) where TContainerBuilder : notnull
225208
{
226209
_createServiceProvider = () =>
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Linq;
7+
using Microsoft.Extensions.Configuration;
8+
using Microsoft.Extensions.DependencyInjection;
9+
using Microsoft.Extensions.Hosting.Fakes;
10+
using Microsoft.Extensions.Logging;
11+
using Microsoft.Extensions.Logging.Console;
12+
using Xunit;
13+
14+
namespace Microsoft.Extensions.Hosting.Tests;
15+
16+
public class IHostApplicationBuilderTests
17+
{
18+
[Fact]
19+
public void TestIHostApplicationBuilderCanBeUsedInExtensionMethod()
20+
{
21+
HostApplicationBuilder builder = Host.CreateEmptyApplicationBuilder(new HostApplicationBuilderSettings()
22+
{
23+
EnvironmentName = "Development"
24+
});
25+
26+
builder.VerifyBuilderWorks();
27+
28+
using IHost host = builder.Build();
29+
30+
// VerifyBuilderWorks should have configured a FakeServiceProviderFactory with the following State.
31+
FakeServiceCollection fakeServices = host.Services.GetRequiredService<FakeServiceCollection>();
32+
Assert.Equal("Hi!", fakeServices.State);
33+
}
34+
}
35+
36+
internal static class HostBuilderExtensions
37+
{
38+
public static void VerifyBuilderWorks(this IHostApplicationBuilder builder)
39+
{
40+
var propertyKey = typeof(HostBuilderExtensions);
41+
builder.Properties[propertyKey] = 3;
42+
Assert.Equal(3, builder.Properties[propertyKey]);
43+
44+
Assert.Equal(1, builder.Configuration.GetChildren().Count());
45+
Assert.Equal(2, builder.Configuration.Sources.Count); // there's an empty source by default
46+
Assert.Equal("Development", builder.Configuration[HostDefaults.EnvironmentKey]);
47+
48+
builder.Configuration.AddInMemoryCollection(new Dictionary<string, string>
49+
{
50+
{ "Key1", "value1" }
51+
});
52+
53+
Assert.Equal(2, builder.Configuration.GetChildren().Count());
54+
Assert.Equal(3, builder.Configuration.Sources.Count);
55+
Assert.Equal("value1", builder.Configuration["Key1"]);
56+
Assert.Null(builder.Configuration["Key2"]);
57+
58+
Assert.True(builder.Environment.IsDevelopment());
59+
Assert.NotNull(builder.Environment.ContentRootFileProvider);
60+
61+
Assert.DoesNotContain(builder.Services, sd => sd.ImplementationType == typeof(ConsoleLoggerProvider));
62+
builder.Logging.AddConsole();
63+
Assert.Contains(builder.Services, sd => sd.ImplementationType == typeof(ConsoleLoggerProvider));
64+
65+
builder.Services.AddSingleton(typeof(IHostApplicationBuilderTests));
66+
67+
builder.ConfigureContainer(new FakeServiceProviderFactory(), container => container.State = "Hi!");
68+
}
69+
}

0 commit comments

Comments
 (0)