Let's say I have connection string for Development environment specified in appsettings.Development.json
and connection string for the Staging environment specified in appsettings.Staging.json
All I need to do to switch between Development and Staging is to navigate to Visual Studio Debug tab in project properties and change the value for ASPNETCORE_ENVIRONMENT
environment variable.
Now, of course I don't want to have connection string in appsettings.*.json
for security reasons. So I move it to User Secrets.
Problem is - it seems there is just one secrets.json
file that is used by all the environments. There are no secrets.Development.json
or secrets.Staging.json
. This means after I switch from Development to Staging environment via Visual Studio Debug tab I then also need to change connection strings manually in secrets.json
which kind of defeats the purpose of having built-in support for the environments.
Is this correct that User Secrets are not supported on per-environment basis? If so - is there another approach that would avoid having to modify Secret connection string manually when switching environments?
The user secrets configuration provider registers the appropriate configuration source with the . NET Configuration API. ASP.NET Core web apps created with dotnet new or Visual Studio generate the following code: C# Copy.
In a Windows machine, they are stored in the %APPDATA%\Microsoft\UserSecrets\<user_secrets_id>\secrets. json file. In a Linux/macOS machine, they are stored in the ~/. microsoft/usersecrets/<user_secrets_id>/secrets.
ASP.NET Core uses an environment variable called ASPNETCORE_ENVIRONMENT to indicate the runtime environment. The value of this variable can be anything as per your need but typically it can be Development, Staging, or Production. The value is case insensitive in Windows and Mac OS but it is case sensitive on Linux.
Creating User Secrets via Visual Studio By far the easiest way to use User Secrets is via Visual Studio. Right click your entry project and select “Manage User Secrets”. Visual Studio will then work out the rest, installing any packages you require and setting up the secrets file! Easy!
If you check the tool's parameters with dotnet user-secrets --help
you'll see you can specify different secrets per configuration (Debug, Release, any other you want) but not per environment. Which is not a bad decision if you think about it.
The ASPNETCORE_ENVIRONMENT
environment variable is meant to tell your application whether the current machine or container is a Development, Production or other environment, so it can pick the appropriate settings file. This environment variable isn't expected to change from one application execution to the next. Even when using containers, the environment variables are passed from the host to the container and aren't expected to change during the container's lifetime.
The secrets
files are supposed to be per machine, for development purposes, so there's no need to keep separate files per environment. It makes much more sense to use separate files for configuration, allowing developers to simply change from Dev to Release or Testing or any other custom configuration they may have.
Specifying secrets per configuration
The dotnet user-secrets
tool works by reading the UserSecretsId
value from the project file and storing the secrets in a JSON file with the same name, eg c952ecfc-344e-43e1-bb67-1ac05973d6c6.json
. It's possible to store a UserSecretsId
for each configuration.
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<UserSecretsId>c952ecfc-344e-43e1-bb67-1ac05973d6c6</UserSecretsId>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<UserSecretsId>7D104000-2230-4EDE-8AE6-63BDDA0BD0C5</UserSecretsId>
</PropertyGroup>
When the -c
parameter is used to specify a configuration, the user-secrets
tool will read the UserSecretsId
value from the corresponding section and use it to store or read secrets.
The dotnet user-secrets init
command doesn't recognize the -c
parameter, so the csproj
file needs to be modified directly.
Once that's done, one can set and read secrets by specifying the configuration, eg :
❯ dotnet user-secrets set -c Debug Key1 Value1
Successfully saved Key1 = Value1 to the secret store.
❯ dotnet user-secrets set -c Release Key1 Value2
Successfully saved Key1 = Value2 to the secret store.
❯ dotnet user-secrets list -c Debug
Key1 = Value1
❯ dotnet user-secrets list -c Release
Key1 = Value2
I also needed this and I think I've come up with an elegant solution.
secrets.json
file is shared among all environments you are using, what you can do is add the environment
parent to each node in the file and then do the little trick (last 2 code fragments).
Suppose you have a configuration, e.g. in appsettings.json
or appsettings.{environment}.json
:
{
"Key1": "value1",
"Secret1": "<set yourself>"
}
and then you have the secret part in secrets.json
:
{
"Secret1": "my secret value"
}
you can get and bind the whole section easily:
IConfiguration configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.AddJsonFile($"appsettings.{environment}.json")
.AddUserSecrets<Program>()
.Build();
var myConfiguration = configuration.Get<MyConfiguration>();
Now comes the problem, I want to have multiple environments in the secrets.json
. I personally used to have secrets for all environments I needed and just comment/uncomment what I wanted, however, it's manual work. So I will prefix them with environment
name instead.
{
"Development:Secret1": "my secret development value",
"Staging:Secret1": "my secret staging value"
}
You have to load the environment-specific configuration(s) from the IConfiguration
instance and override the existing myConfiguration
values using:
configuration.GetSection(environment).Bind(myConfiguration);
\\ or
configurationRoot.Bind(environment, configuration);
And that's it.
If you run it using environment="Development"
, you will have "my secret development value"
loaded. If you run it using environment="Staging"
, you will have "my secret staging value"
loaded.
The double dot character (:
) acts as a new section, so if you write
{
"Development:Secret1": "my secret development value",
"Staging:Secret1": "my secret staging value"
}
it's the same as
{
"Development":
{
"Secret1": "my secret development value"
},
"Staging":
{
"Secret1": "my secret staging value"
}
}
the trick is about loading just the environment-specific part and binding it to the myConfiguration
instance.
I must mention that no matter what environment you are using, all the secrets are actually loaded in the memory.
The Secret Manager (https://docs.microsoft.com/en-us/aspnet/core/security/app-secrets?view=aspnetcore-3.1) is designed strictly for development, not any other stage (environment), since it is inherently insecure (local dev secrets are not encrypted). See the warning on the page linked. So there is no need to have per environment secrets storage vis-a-vis that tool. For other environments (staging, prod, etc), Microsoft would likely steer you toward their secure secrets storage service -- Key Vault. You can use the Secret Manager for dev secrets and then store the other environments in Key Vault. I have done this in many Asp.Net Core apps and it works well. For Key Vault info, see this: https://docs.microsoft.com/en-us/aspnet/core/security/key-vault-configuration?view=aspnetcore-3.1
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With