Reload configuration when env variable has changed - c#

In Startup.cs file I have
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
}
There is appsettings.json file with configurations. Like :
{
"Log" : {
"Type" : "value from appsettings.json"
}
}
reloadOnChange set to true, so, when I change appsettings.json then I get immediately a new value of log type in my program.
But I'm using Docker with docker-compose and pass a value of setting by env variables. My docker-compose.override.yml file is:
version: '3.7'
services:
myservice:
environment:
ASPNETCORE_ENVIRONMENT: Development
Log__Type: "value from docker-compose"
To run I use `docker-compose up. Now my app has value "value from docker-compose" for log type.
Question: Are there any ways to change a value of env variable (Log__Type) at runtime (without restarting docker container) and reload configuration in my app as it has done with reloadOnChange and appsettings.json?
I tried to connect to the container (docker exec) and set a new value of env variable
printenv Log__Type // -> value from docker-compose
export Log__Type=new value
printenv Log__Type // -> new value
but my app didn't reload configuration and still shows log type "value from docker-compose".
Could you please advise how to change settings at runtime using docker? Or explain why there is only reload when the file has changed but not env variable.

Are there any ways to change a value of env variable at runtime (without restarting docker container)
No. (And even a restart isn't enough: you need to delete and recreate the container.)
This follows the ordinary Unix model. A process can set the initial environment for its child process, but once it's exec'd the child, it has no more control over the environment any more. docker exec launches a new process in the container namespace and so if you change an environment variable there it will only affect that process and not the main container process.
There are a significant number of options that can only be set during the initial docker run command. This includes environment variables, and also includes volume mounts and published ports. Critically, it also includes the underlying image: if you ever have a new build of your application, or need to update the underlying OS distribution for a security issue, you will be forced to delete and recreate your container. In my experience docker rm is extremely routine, and you should plan for it to happen regularly.

so what you want to do is read weather a file has changed or not during runtime? you can use a FileSystemWatcher to detect when the file has been changed or edited, so in a sense you can, but detecting weather or not a specific variable has changed is a bit more messy, because you'd only be able to detect when the entire file has had a change to it, and then check if the wanted var has changed yourself.
FileSystemWatcher MSD
but if we're talking about the app reciving a new environment change during runtime, you can use a hacky solution, using a NamedPipeStream you could send environment changes by either starting another process to tell the main one to update Var X to Y or by like mentioning earlier but instead having a sub process detecting it and sending it back up the chute.
NamedPipeStream Server MSD
NamedPipeStream Client MSD

Related

Set Environment Variable for .NET 5 Azure Function ServiceBusTrigger connection string programmatically?

To preface, I know that the connection string comes from the Configuration on the Azure Portal / local.settings.json, and that it's an environment variable you can access with the following:
Environment.GetEnvironmentVariable("Name", EnvironmentVariableTarget.Process);
I am trying to set the ServiceBusTrigger connection string from the appsettings.json.
I have added an appsettings.json file following this blog on how to do it. I am able to access settings from the appsettings.json file, however in the Program.cs when I try to use the following to set the environment variable:
.ConfigureAppConfiguration(c =>
{
var config = c.SetBasePath(Environment.CurrentDirectory)
.AddJsonFile("appsettings.json", true, true)
.AddEnvironmentVariables()
.Build();
// Setting the Environment Variable for the connection string
Environment.SetEnvironmentVariable(config["ConnectionString:Name"], config["ConnectionString:Value"]);
})
where "Name" is what's passed to the Connection in the function, and "Value" is the connection string, I get the following error:
Microsoft.Azure.WebJobs.ServiceBus: Microsoft Azure WebJobs SDK ServiceBus connection string 'Name' is missing or empty.
The Connection I'm talking about:
public async Task Run(ServiceBusTrigger("myqueue", Connection = "Name")] string myQueueItem, FunctionContext) { // Some Implementation }
I am trying to avoid setting the connection string "Name" in the App Configuration on the Azure portal. Is there a way to do it programmatically?
When you set the variable by Environment.SetEnvironmentVariable, it will not show in application setting. But we can use it by Environment.GetEnvironmentVariable as expected.
SetEnvironmentVariable(String,String) Creates, modifies, or deletes an environment variable stored in the current process.
Although the solution you mentioned is not so good, but it can implement your requirement. The adverse effect is when you restart the function app, the variables will be lost.
For more information on SetEnvironmentVariable, please refer this MSFT documentation

How can I use EF Core without appsettings.json

I need to use the dotnet ef tools utility, but I need to set a connection string in appsettings.json.
I don't want to store it in appsettings.json, because project is stored in the git repository.
At the moment, I keep launchSettings.json in the EnvironmentVariables section, but this does not help me because dotnet ef tools simply does not see these values. I tried to implement IDesignTimeDbContextFactory but everything is the same. He just doesn't see these values.
I found a solution that requires you to explicitly enter the value of the connection string, but this option also does not work.
The thing is that you should not store any secret value in git/repository at all.
Ideally this connnection string should be in a vault/Secret place which you will have access using another password, so eventually you need to "store" a password somewhere.
There is differnt approaches for this, as mention the most common one will be something like _secret.GetConnectionString() -> this calls to another service which will return the connectionstring.
Other solution you can do (probably better in your case) is to "setup" a default/plain appsettings.json just store the value like connection: xxxxxx and then use environment variables to replace that value. The configuration builder is already built with this in mind.
var configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.AddEnvironmentVariables() //This line here
.Build();
Then you can just set up the value in your own machine.
For deploying to production it will depends which software you use to deploy the app, but it will have some way to allow you to add environment variables.
Finally, as an extension of the previous code you can add your "own" json
var configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.AddJsonFile("appsettings.private.json")
.Build();
So doing this, in appsettings.private.json you will only need to write the connectionstring property. You will not need to write the entire document.
and then you can just add a rule on .gitignore to ignore it.

IDataProtector unable to decrypt when application runs as a service

I'm using ASP.NET Core data protection with default DI behavior. That works fine when my ASP.NET application is hosted on IIS. Now I have an application that needs to run as a service. So I'm using Microsoft.Extensions.Hosting.WindowsServices to do the windows service part with our standard
Host.CreateDefaultBuilder(args)
.UseWindowsService()
The BackgroundService then hosts ASP.NET Core with your standard
var builder = Host.CreateDefaultBuilder()
.ConfigureAppConfiguration((hostingContext, config) =>
{
config.AddJsonFile("secrets.json", optional: true, reloadOnChange: true);
}
.ConfigureWebHostDefaults(....)
inside the background service, I can then resolve an instance of IDataProtectionProvider, create a protector, and use it to unprotect my secrets
var dataProtectionProvider = Container.Resolve<Microsoft.AspNetCore.DataProtection.IDataProtectionProvider>();
var protector = dataProtectionProvider.CreateProtector(appName);
var decryptedSecret = protector.Unprocect(some secret)
Now that all works fine as long as I run my application from the CLI. But running it as a service (same file, same location, and of course under the same account), I get an 'invalid payload' exception when I call Unprotect.
I know same path and same account is important, so that's taken care of. I also know that the application can find secrets.json as I wrote some probing code that checks if the file is present and can be read before I even try to unprotect. I'm even checking if the string I'm trying to unprotect is null/empty (which it isn't).
I finally installed a debug build as a service and attached the debugger, and when I look at IDataProtectionProvider, it has a Purpose.. and when running as a service, that's c:\windows\system32. When my app runs from the CLI, it's the path to the exe. So, is there a way to specify the purpose on my own so things behave the same regardless of CLI/Service?
So how can I control the purpose?
So having noted the difference in purpose of the IDataProtectionProvider, I was well on my way of solving this. The solution was to set a static purpose as explained here

.NET Core 3.1 application not finding environment variable

I cannot get my .NET Core 3.1 console application to recognize my custom system environment variables. I can pull and print other system variables such as username just fine. But if I set a custom one like 'TestKey' in the example below, they are always null within the application.
Here is my code:
static void Main()
{
var config = new ConfigurationBuilder().AddEnvironmentVariables().Build();
var value = config.GetValue<string>("TestKey");
var envValue = Environment.GetEnvironmentVariable("TestKey");
Console.WriteLine($"Config Variable is: {value}");
Console.WriteLine($"Environment Variable: {envValue}");
}
This is the output:
and system variables clearly show the variable has been set:
Likewise, when I do an echo from the command prompt for the TestKey variable, it correctly returns the associated value.
This is a Windows 10 Pro computer and it is not part of a domain. This behavior is truly puzzling. I have rebooted the computer and the system variable persist but still will not appear in the application.
There are two major points here. First, according to Configuration in ASP.NET Core, environment variables are retrieved only for current process
Are only set in processes launched from the command window they were
set in
So, you should set them in the same command window before launch the app or add to the launchsettings.json file
"environmentVariables": {
"Test": "Test",
"ASPNETCORE_ENVIRONMENT": "Development"
}
and restart Visual Studio for that. Or just change it in Debug properties of your project
Second, to be able to get a system environment variable in GetEnvironmentVariable method, you should specify the EnvironmentVariableTarget.Machine parameter
var envValue = Environment.GetEnvironmentVariable("Test", EnvironmentVariableTarget.Machine);
By default
The GetEnvironmentVariable(String) method retrieves an environment
variable from the environment block of the current process only
Or, if you'll add the variable to launchsettings.json in previous point, setting the target isn't needed.
However, if you will run your project in command line via dotnet run, you should be able to access the system environment variable as well, because
On Windows systems, the environment block of the current process
includes:
All environment variables that are provided to it by the parent process that created it. For example, a .NET application launched from
a console window inherits all of the console window's environment
variables.
If there is no parent process, per-machine and per-user environment variables are used instead. For example, a new console
window has all per-machine and per-user environment variables defined
at the time it was launched.
Bottom Line: I had to reboot my web server.
I had this very same problem, in a razor view i had this...
#inject Microsoft.AspNetCore.Hosting.IWebHostEnvironment env
.
.
<div>
Environment is #env.EnvironmentName
</div>
<environment include="Development">
<strong>You are in the Development environment</strong>
</environment>
and in ASPNETCORE_ENVIRONMENT variable i had 'Development'
The page never saw 'Development' it always defaulted to 'Production' (which it's supposed to). Rebooting the web server 'fixed' it for me. I'm not sure if it was the reboot or I just could have recycled the app pool. I'm using .net core 3.1
use the following command to set the environment variable permanently setx key value /M
If you use set command then it will accessible in that cmd.exe itself.
Event after using setx command if the env variable gets null, do a quick restart and it will work.

Azure functions - getting configuration recommended practices

I have created an Azure Function v2. In most articles I people suggest following:
var config = new ConfigurationBuilder()
.SetBasePath(context.FunctionAppDirectory)
.AddJsonFile("local.settings.json", optional: true, reloadOnChange: true)
.AddEnvironmentVariables()
.Build();
Does a function hosted on azure also have a local.settings.json file? Will this code snippet get the app settings defined in Azure? Or do you need to change local.settings.json before publish? I thought local.settings.json was a local developer thing?
Is there any recommended practices using above snippet vs
Environment.GetEnvironmentVariable("xx")?
The first way you posted works just fine, both locally and in the cloud.
Note: In Azure, there is no local.settings.json (or should never be). This is only being used when debugging locally. There it is being picked up by the Functions runtime the same way it picks up AppSettings when running in Azure - and the settings are injected into your app as env variables. I have been using this for most of the time.
However, I recently started to switch to the second way, using Environment variables (Environment.GetEnvironmentVariable("xx")). Why? Simply because it is shorter and requires not extra usings etc. Apart from that there is no difference that I am aware of in using either of those ways.

Categories