Skip to content

Add configurable VariableNameTransformation to EnvironmentVariables config#127503

Draft
svick wants to merge 4 commits intodotnet:mainfrom
svick:env-var-name-transformation
Draft

Add configurable VariableNameTransformation to EnvironmentVariables config#127503
svick wants to merge 4 commits intodotnet:mainfrom
svick:env-var-name-transformation

Conversation

@svick
Copy link
Copy Markdown
Member

@svick svick commented Apr 28, 2026

Implements #125137.

API additions

  • IConfigurationBuilder.AddEnvironmentVariables(string? prefix, Func<string, string>? variableNameTransformation) overload.
  • EnvironmentVariablesConfigurationSource.VariableNameTransformation -- a settable Func<string, string>? that, when non-null, completely replaces the default transformation.
  • EnvironmentVariablesConfigurationSource.DefaultTransformation -- the existing default that replaces __ with the configuration key delimiter (:).
  • EnvironmentVariablesConfigurationSource.ColonAndDotTransformation -- replaces ___ with . and __ with :, so configuration keys containing dots can be set from environment variables.

API shape matches @bartonjs's approval comment on the issue.

Implementation notes

  • ColonAndDotTransformation uses ValueStringBuilder + stackalloc char[256] on .NET, StringBuilder on .NET Framework, kept under a single small #if NET region. No new package references.
  • Prefix is itself passed through the active transformation before comparison, so it should be specified in pre-transformation form (e.g. "Logging__" rather than "Logging:"). Documented in <remarks> on the Prefix property and the matching overloads.

Tests

13 new tests in EnvironmentVariablesTest cover:

  • The two built-in transformations behaviorally ([Theory] tables for double/triple/4/5/6 underscores, single-underscore preservation, no-underscore early return).
  • VariableNameTransformation replacing the default, identity transformation disabling replacement, transformation applied to prefix, source/builder wiring, and a behavioral check that ColonAndDotTransformation is not the default.

Note

This PR description and the implementation were drafted with assistance from GitHub Copilot.

…onfig (dotnet#125137)

Adds:

- AddEnvironmentVariables(prefix, variableNameTransformation) overload on IConfigurationBuilder.
- EnvironmentVariablesConfigurationSource.VariableNameTransformation: a settable property that, when non-null, replaces the default transformation behavior.
- EnvironmentVariablesConfigurationSource.DefaultTransformation: the existing default that replaces __ with the configuration key delimiter (:).
- EnvironmentVariablesConfigurationSource.ColonAndDotTransformation: replaces ___ with . and __ with :, allowing dots in configuration keys without the override-by-environment ergonomics being limited to colons.

ColonAndDotTransformation is implemented with ValueStringBuilder on .NET and StringBuilder on .NET Framework.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@dotnet-policy-service
Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/area-extensions-configuration
See info in area-owners.md if you want to be subscribed.

/// <param name="prefix">A prefix used to filter the environment variables.</param>
/// <param name="variableNameTransformation">A function that transforms environment variable names.
/// When <see langword="null"/>, <see cref="EnvironmentVariablesConfigurationSource.DefaultTransformation"/> is used.</param>
internal EnvironmentVariablesConfigurationProvider(string? prefix, Func<string, string>? variableNameTransformation)
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I think this constructor should actually be public. I should take it back to API review, right?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Yes.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@svick It’s up to you whether to wait and add the new public constructor after the design review, or proceed with the current change and track the constructor addition as a complementary issue.

var builder = new StringBuilder(name.Length);
#endif

// For short strings, this seems to be faster than Append(name.Slice(0, first)).
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

did you measure that? note the builder with every insrt has to check the buffer size before adding it.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I did arrive at this code by measuring various approaches, but did not recheck. When I measured it now, builder.Append(name.AsSpan(0, first)) ended up being better, so I'm going to change it.

builder.Append(name[j]);
}

int i = first;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit: why not using first instead of adding a new variable? you can rename first to something like index or just i

<ProjectReference Include="$(LibrariesProjectRoot)Microsoft.Extensions.Configuration.Abstractions\src\Microsoft.Extensions.Configuration.Abstractions.csproj" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFrameworkIdentifier)' == '.NETCoreApp'">
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why adding it conditionally. shouldn't ValueStringBuilder work on non .NETCoreApp platforms too?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I didn't want to add extra dependencies just for this. But I didn't realize that System.Memory is already transitively referenced by this package. So yes, we can use VSB everywhere.

Copy link
Copy Markdown
Member

@tarekgh tarekgh left a comment

Choose a reason for hiding this comment

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

LGTM.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[API Proposal]: Replacement of forbidden characters in variable names in EnvironmentVariablesConfigurationProvider

3 participants