dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
15.07k stars 4.69k forks source link

[API Proposal]: Add environment capability to UserSecrets (secrets.json) #99565

Open vdailly opened 6 months ago

vdailly commented 6 months ago

Background and motivation

It is not uncommon for some clients I'm working with to have applications deployed on several environments for which I need to debug an application on different environments. Data is often different on each environment which is great to catch issues before going to production. Secrets (like API Keys, connections strings ...) cannot be stored in regular "appsettings.json". It is adviced to store them with the "secrets.json" file for development-only purposes. Nevertheless with several non-production environments switching easily from an environment (or secrets of an environment) to another is not straightforward.

I think it would be great if we could, like with standard appsettings.json and appsettings.Debug.json use environment secrets files like:

It's done the same way with appsettings.json in method ApplyDefaultAppConfiguration(HostBuilderContext hostingContext, IConfigurationBuilder appConfigBuilder, string[]? args) of file \src\libraries\Microsoft.Extensions.Hosting\src\HostingHostBuilderExtensions.cs.

Due to an issue regarding a circular dependency build issue when adding these new extensions methods I don't have a PR to submit (See Risks). But preview of the code (and the changes to bring to Microsoft.Extensions.Configuration.UserSecrets) are available as a standalone Nuget package Chrysalit.Extensions.UserSecrets and the source code available on Github.

While Visual Studio does fully integrate the secrets.json file (Manage User Secrets on the project menu in Solution Explorer) opening the default secrets.json file, adding environment secrets may break this integration, or may require some updates accordingly.

An MSBuild solution is proposed as well in the documentation of the Nuget Package Chrysalit.Extensions.UserSecrets to easily access the various secrets for the project directly from the Solution Explorer.

API Proposal

Adding the following extensions methods may help to either:

namespace Microsoft.Extensions.Configuration.UserSecrets {

public static IConfigurationBuilder AddUserSecrets<T>(this IConfigurationBuilder configuration, HostBuilderContext hostBuilderContext) {}

public static IConfigurationBuilder AddUserSecrets<T>(this IConfigurationBuilder configuration, IHostEnvironment hostEnvironment) {}

public static IConfigurationBuilder AddUserSecrets<T>(this IConfigurationBuilder configuration, Func<string> environmentDelegate) {}
}

API Usage

// With the default builder and the .UseEnvironment extension method.
var host = Host.CreateDefaultBuilder(args)
    .UseEnvironment("dev")
    .ConfigureAppConfiguration((context, config) =>
    {
        config.AddUserSecrets<Program>(context);
    })
    .Build();

// with an environment variable DOTNET_ENVIRONMENT = "dev"
var host2 = new HostBuilder()
    .ConfigureHostConfiguration(config =>
    {
        config.AddEnvironmentVariables("DOTNET_");
    })
    .ConfigureAppConfiguration((context, config) =>
    {
        config.AddUserSecrets<Program>(context.HostingEnvironment);
    })
    .Build()

/* with a custom key defined in any configuration file (or elsewhere) and a factory to build the environement value.
appsettings.json
{
   "customkey": "dev"
}
*/
var host3 = new HostBuilder()
    .ConfigureHostConfiguration(config =>
    {
        config.AddJsonFile("appsettings.json");
    })
    .ConfigureAppConfiguration(config) =>
    {
        confbuilder.AddUserSecrets<Program>(() => config["customkey"]);
    })
    .Build()

By setting explicitely the environment to "dev", the API would load in order:

Alternative Designs

The dotnet tool user-secrets is misleading. The tool accepts a --configuration parameter but doesn't use it anywhere and update only the file secrets.json. It would be interesting if the tool, when providing a non null configuration, could write to a secrets.{configuration}.json file, while still writing to secrets.json file when the parameter is not explicitely defined on the command-line. Using this parameter would be fully aligned with the current API proposal.

Risks

While these changes are easy enough to implement, there's a problem on the where should these methods being implemented:

This leads to a circular dependency build issue.

I don't know where these methods should be implemented. But Microsoft.Extensions.Hosting uses only one method AddUserSecrets() in file src\libraries\Microsoft.Extensions.Hosting\src\HostingHostBuilderExtensions.cs. But I don't see how this dependency could be reversed.

Propositions regarding this dependency issue;

dotnet-policy-service[bot] commented 6 months ago

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