Skip to content
Open
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
1 change: 1 addition & 0 deletions docs/workflow/trimming/feature-switches.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ configurations but their defaults might vary as any SDK can set the defaults dif
| _DesignerHostSupport | System.ComponentModel.Design.IDesignerHost.IsSupported | When set to true, supports creating design components at runtime. |
| _EnableConsumingManagedCodeFromNativeHosting | System.Runtime.InteropServices.EnableConsumingManagedCodeFromNativeHosting | Getting a managed function from native hosting is disabled when set to false and related functionality can be trimmed. |
| _UseManagedNtlm | System.Net.Security.UseManagedNtlm | When set to true, uses built-in managed implementation of NTLM and SPNEGO algorithm for HTTP, SMTP authentication, and NegotiateAuthentication API instead of system provided GSSAPI implementation. |
| - | Microsoft.Extensions.Configuration.DisableConfigurationTransformations | When set to true, Configuration hands values back exactly as their providers hold them, so `$ref(...)` values are not resolved and reference resolution is trimmed away. |

Any feature-switch which defines property can be set in csproj file or
on the command line as any other MSBuild property. Those without predefined property name
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// 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;

namespace Microsoft.Extensions.Configuration
{
/// <summary>
/// One stage of the pipeline a configuration root reads through. The stage at the end of the pipeline reads the
/// providers; every stage before it interprets what those providers hold.
/// </summary>
/// <remarks>
/// A stage holds no state about the configuration it serves: the providers are given to it on each call, so one
/// pipeline serves a root for its whole life however often its sources change, and a stage can be shared.
/// <para>
/// Both methods read straight through to <see cref="Next"/> by default, so a stage overrides only what it changes.
/// Reference resolution reinterprets a value and leaves enumeration alone; a stage that hides a key would have to do
/// both, since the key has to stop being readable and stop being listed.
/// </para>
/// </remarks>
internal abstract class ConfigurationEngine
{
/// <summary>
/// The pipeline a configuration root reads through.
/// </summary>
/// <remarks>
/// The one place the pipeline is composed, so its order is stated once. Reference resolution reads its targets
/// through the stage after it rather than from the front of the pipeline, so a stage placed before it cannot
/// change what a reference resolves to, and a stage placed after it can.
/// <para>
/// Turning transformations off leaves nothing here but the providers, which is what lets the trimmer drop
/// reference resolution outright rather than merely leaving it unreachable.
/// </para>
/// </remarks>
internal static ConfigurationEngine Default { get; } = ReferenceEngine.Disabled
? ProviderEngine.Instance
: new ReferenceEngine(ProviderEngine.Instance);

protected ConfigurationEngine(ConfigurationEngine next) => Next = next;

/// <summary>
/// The stage this one reads through. The stage at the end of the pipeline has none, and overrides everything
/// that would read it.
/// </summary>
protected ConfigurationEngine Next { get; }

/// <summary>
/// Reads <paramref name="key"/>, and reports whether anything declared it.
/// </summary>
/// <param name="providers">The providers to read.</param>
/// <param name="key">The key to read.</param>
/// <param name="value">
/// The text produced, which may be <see langword="null"/> for a key a provider holds as null. A read that
/// produced nothing leaves this <see langword="null"/> as well, so the two are told apart by the result.
/// </param>
/// <param name="providerIndex">
/// The position in <paramref name="providers"/> of the provider that declared the key, or -1 when none did.
/// A stage that rewrites a value reports the provider of the text it started from, since that is where the
/// key was declared, which is the question worth answering: a value built from several keys has no single
/// provider of its own.
/// </param>
internal virtual bool Get(IList<IConfigurationProvider> providers, string key, out string? value, out int providerIndex)
{
return Next.Get(providers, key, out value, out providerIndex);
}

/// <summary>
/// Produces the keys of the immediate children of <paramref name="parentPath"/>, or of the root when it is
/// <see langword="null"/>.
/// </summary>
internal virtual IEnumerable<string> GetChildKeys(IList<IConfigurationProvider> providers, string? parentPath)
{
return Next.GetChildKeys(providers, parentPath);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ public string? this[string key]
get
{
using ReferenceCountedProviders reference = _providerManager.GetReference();
return ConfigurationRoot.GetConfiguration(reference.Providers, key);
ConfigurationEngine.Default.Get(reference.Providers, key, out string? value, out _);
return value;
}
set
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,11 @@ public ConfigurationRoot(IList<IConfigurationProvider> providers)
/// <returns>The configuration value.</returns>
public string? this[string key]
{
get => GetConfiguration(_providers, key);
get
{
ConfigurationEngine.Default.Get(_providers, key, out string? value, out _);
return value;
}
set => SetConfiguration(_providers, key, value);
}

Expand Down Expand Up @@ -111,21 +115,6 @@ public void Dispose()
}
}

internal static string? GetConfiguration(IList<IConfigurationProvider> providers, string key)
{
for (int i = providers.Count - 1; i >= 0; i--)
{
IConfigurationProvider provider = providers[i];

if (provider.TryGet(key, out string? value))
{
return value;
}
}

return null;
}

internal static void SetConfiguration(IList<IConfigurationProvider> providers, string key, string? value)
{
if (providers.Count == 0)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.Linq;

Expand All @@ -21,61 +20,33 @@ internal static class InternalConfigurationRootExtensions
internal static IEnumerable<IConfigurationSection> GetChildrenImplementation(this IConfigurationRoot root, string? path)
{
using ReferenceCountedProviders? reference = (root as ConfigurationManager)?.GetProvidersReference();
IEnumerable<IConfigurationProvider> providers = reference?.Providers ?? root.Providers;
IList<IConfigurationProvider> providers = AsList(reference?.Providers ?? root.Providers);

IEnumerable<IConfigurationSection> children = providers
.Aggregate(Enumerable.Empty<string>(),
(seed, source) => source.GetChildKeys(seed, path))
.Distinct(StringComparer.OrdinalIgnoreCase)
.Select(key => root.GetSection(path == null ? key : path + ConfigurationPath.KeyDelimiter + key));

if (reference is null)
{
return children;
}
else
IEnumerable<string> keys = ConfigurationEngine.Default.GetChildKeys(providers, path);
if (reference is not null)
{
// Eagerly evaluate the IEnumerable before releasing the reference so we don't allow iteration over disposed providers.
return children.ToList();
keys = keys.ToList();
}

return keys.Select(key => root.GetSection(path == null ? key : path + ConfigurationPath.KeyDelimiter + key));
}

internal static bool TryGetConfiguration(this IConfigurationRoot root, string key, out string? value)
{
// common cases Providers is IList<IConfigurationProvider> in ConfigurationRoot
IList<IConfigurationProvider> providers = root.Providers is IList<IConfigurationProvider> list
? list
: root.Providers.ToList();

// ensure looping in the reverse order
for (int i = providers.Count - 1; i >= 0; i--)
if (root is ConfigurationManager manager)
{
IConfigurationProvider provider = providers[i];

try
{
if (provider.TryGet(key, out value))
{
return true;
}
}
catch (ObjectDisposedException)
{
// Skip disposed providers to avoid exceptions during access.
// This is especially relevant for cases like ConfigurationManager,
// which implements IConfigurationRoot and may dispose providers
// if configuration sources are concurrently modified. A new collection
// is created in this case, so it's still safe to iterate over it.
//
// If we want to avoid this possible exception altogether, we could update
// ConfigurationSection.TryGetValue to be virtual and have ConfigurationManager
// implement it with reference counting like it does for the indexer.
}
// Hold the reference for the whole read: resolving a reference reads several keys, and they all have to
// come from the same provider generation.
using ReferenceCountedProviders reference = manager.GetProvidersReference();
return ConfigurationEngine.Default.Get(reference.Providers, key, out value, out _);
}

value = null;
return false;
return ConfigurationEngine.Default.Get(AsList(root.Providers), key, out value, out _);
}

// Providers is IList<IConfigurationProvider> for both of the roots in this library, and for the pinned
// generation a ConfigurationManager hands out.
private static IList<IConfigurationProvider> AsList(IEnumerable<IConfigurationProvider> providers) =>
providers as IList<IConfigurationProvider> ?? providers.ToList();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@
Link="Common\src\Extensions\ChangeCallbackRegistrar.cs" />
<Compile Include="$(CommonPath)Extensions\EmptyDisposable.cs"
Link="Common\src\Extensions\EmptyDisposable.cs" />
<Compile Include="$(CommonPath)System\Text\ValueStringBuilder.cs"
Link="Common\src\System\Text\ValueStringBuilder.cs" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFrameworkIdentifier)' != '.NETCoreApp' or $([MSBuild]::VersionLessThan('$(TargetFrameworkVersion)', '9.0'))">
<Compile Include="$(CoreLibSharedDir)System\Diagnostics\CodeAnalysis\FeatureSwitchDefinitionAttribute.cs"
Link="Common\src\System\Diagnostics\CodeAnalysis\FeatureSwitchDefinitionAttribute.cs" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' != '$(NetCoreAppCurrent)'">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.Linq;

namespace Microsoft.Extensions.Configuration
{
/// <summary>
/// The stage at the end of the pipeline: the one that actually reads the providers. Everything a configuration root
/// reports comes from here, and every other stage is an interpretation of it.
/// </summary>
internal sealed class ProviderEngine : ConfigurationEngine
{
/// <summary>
/// The single instance. Reading providers depends on nothing but the providers handed to each call, so there is
/// never a reason for a second one.
/// </summary>
internal static ProviderEngine Instance { get; } = new ProviderEngine();

private ProviderEngine()
// Nothing follows this stage, and both methods that would read Next are overridden below.
: base(null!)
{
}

/// <summary>
/// Reads a key from the providers, highest precedence first, and takes the first answer.
/// </summary>
internal override bool Get(IList<IConfigurationProvider> providers, string key, out string? value, out int providerIndex)
{
for (int i = providers.Count - 1; i >= 0; i--)
{
try
{
if (providers[i].TryGet(key, out value))
{
providerIndex = i;
return true;
}
}
catch (ObjectDisposedException)
{
// ConfigurationManager disposes providers when its sources are modified, so a read running
// concurrently with a change can reach one that has already gone. It is reading a list that has
// been replaced, and the value it wants is in the new one, so passing over the dead provider is
// the whole of the fix.
}
}

value = null;
providerIndex = -1;
return false;
}

/// <summary>
/// Collects the child keys every provider declares under a path.
/// </summary>
/// <remarks>
/// Each provider is handed the keys gathered so far, which is how a provider that stores keys in a shape of its
/// own gets to reconcile them with the rest, so this accumulates rather than concatenates.
/// <para>
/// The providers are all consulted before this returns, since the fold is eager, but a provider is free to hand
/// back a sequence of its own that is not. So a caller that has borrowed its provider list has to gather the
/// result before letting go of it.
/// </para>
/// </remarks>
internal override IEnumerable<string> GetChildKeys(IList<IConfigurationProvider> providers, string? parentPath) =>
providers
.Aggregate(Enumerable.Empty<string>(), (seed, source) => source.GetChildKeys(seed, parentPath))
.Distinct(StringComparer.OrdinalIgnoreCase);
}
}
Loading
Loading