asp.net core override connection strings via ENV variables - c#

I have an asp.net core API 2.2 implemented. I have created a docker image. I would like to override connection strings in appsettings.json file.
Is there any way to do it? I tried via environment variables when I start the container with command docker container run -e "ConnectionStrings:DefaultConnection={...here goes the connection string}"
I have also builder.AddEnvironmentVariables(); in my Startup.cs but connection string in my appsetting.json is not replaced.
I checked it inside the container, appsetting.json is there but the values are not replaced.
Any other way how to do such cases? Thx.

For -e, it will override the system environment which will change the connectionstring when you retrive from code, it will not affect the content in appsettings.json.
For example
Assume you have a appsettings.json like
{
"ConnectionStrings": {
"DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=aspnet-TestDockerConfiguration-416C6FD2-3531-42D6-9EDE-18AC45901208;Trusted_Connection=True;MultipleActiveResultSets=true"
},
"Logging": {
"LogLevel": {
"Default": "Warning"
}
},
"AllowedHosts": "*"
}
Retrive the connectionstring by _configuration.GetConnectionString("DefaultConnection") like
public class HomeController : Controller
{
private readonly IConfiguration _configuration;
public HomeController(IConfiguration configuration)
{
_configuration = configuration;
}
public IActionResult Index()
{
return Ok(_configuration.GetConnectionString("DefaultConnection"));
//return View();
}
}
For docker run -it -p 8888:80 dockerconfiguration, it will return "Server=(localdb)\\mssqllocaldb;Database=aspnet-TestDockerConfiguration-416C6FD2-3531-42D6-9EDE-18AC45901208;Trusted_Connection=True;MultipleActiveResultSets=true" for Index Action
For docker run -it -p 8888:80 dockerconfiguration -e "ConnectionStrings:DefaultConnection"="testsqlstring", it will return testsqlstring
So, if you only want to override the value in appsettings.json during runtime, you just need to specify like Step 4
If you prefer change the appsettings.json file in docker container, you could follow steps below
Install vi command with apt-get install vim -y
Run docker exec -it 43ea835776dd /bin/bash to go into container
Run ls to list files and find the appsettings.json
Run vi appsettings.json to modify the content
Run cat appsettings.json to check the content whether it is changed
Run exit and access the Home/Index to check the result.

appsetting.json never gets changed, however based on the hierarchy, variables values get overridden. Take a look at the examples at https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-2.2#conventions . It works pretty well. Perhaps just to be sure that you didn't break something because of command line execution, you can try to assign environment variables via docker-compose. The rule of thumb is that the pattern that you use for the "key" must match (with the help of __ instead of :)what you have in the json file, so that it gets overridden.

Tried without the quotes for the ConnectionStrings section and it worked (on a PowerShell console.
docker run -d -p 8080:80 --name letscosmosweb -e ConnectionStrings__ProductsDbContext=$key letscosmosweb:v1
$key variable contains the connection string

If you're using docker-compose
Besides specifying the variable in .env, don't forget to also set it in environments section of the docker-compose.yml. It got me :)

Related

cannot read values for few keys from local.settings.json file

I am unable to read the few of the keys from local.settings.json file. Below is the file content.
{
"IsEncrypted": false,
"Values": {
"KeyVaultUrl": "https://mykeyvault.azure.net/",
"SecretKey": "myconnectionstring",
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"FUNCTIONS_WORKER_RUNTIME": "dotnet"
}
}
I used the below code to read these values
var keyVaultUrl = Environment.GetEnvironmentVariable("KeyVaultUrl"); // returns null
var secretKey = Environment.GetEnvironmentVariable("SecretKey"); // returns null
var sample = Environment.GetEnvironmentVariable("AzureWebJobsStorage"); // returns "UseDevelopmentStorage=true"
I am not sure why it returns null for the key I have added. I have set Copy Always in Copy to Output Directory and Build Action to None.
Please assist.
Values you are trying to read, are not written in environment variables. To use config files, use IConfiguration. It can contain environment variables, multiple config files and much more.
It depends on type of your application, if you are using simple console application without hosts and builders, use this, but if you are using some kind of framework use this approach.
An environment variable is a variable whose value is set outside the program, typically through functionality built into the operating system!
Your data in appsettings is just key-value pairs of configuration settings for your application (from it's name - appsettings).
To access data in your appsettings you need to inject IConfiguration to your service (or controller or whatever)
private readonly IConfiguration _configuration;
public ServiceConstructor(public IConfiguration)
=> _configuration = configuration;
and then you can read values from there like this:
_configuration["IsEncrypted"]
and
_configuration["Values:KeyVaultUrl"]

Override appsettings.json Array With Env Variable

I have this array in my appsettings.json:
"ServiceDefinitions": [
{
"Name": "encryption-api",
"Url": "http://localhost:5032",
"ApiKey": "",
"ExposedEndpoints": [
"encrypt",
"decrypt"
]
}
],
I want to be able to override this with an environment variable. I've added the env vars to the configuration builder like this, which seems to work for other values:
builder.Configuration.AddEnvironmentVariables();
And then I've attempted to set this in my Docker Compose file with:
environment:
- ASPNETCORE_URLS=http://gateway:8080/
- ServiceDefinitions="{\"Name\":\"encryption-api\",\"Url\":\"http://encryption-api:80\",\"ApiKey\":\"\",\"ExposedEndpoints\":[\"encrypt\",\"decrypt\"]}"
However, it's still picking up the value from the json file rather than the env var. I've also tried setting it inside [] but that makes no difference.
How can I do this?
Microsoft docs regarding app configuration.
You are going to want to make sure that your Json provider is registered before your environment variables provider. Then you'll want to setup your environment variables in your Docker file as follows:
environment:
- ASPNETCORE_URLS=http://gateway:8080/
- ServiceDefinitions__0__Name="encryption-api"
- ServiceDefinitions__0__Url="http://encryption-api:80"
- ServiceDefinitions__0__ApiKey=""
- ServiceDefinitions__0__ExposedEndpoints__0="encrypt"
- ServiceDefinitions__0__ExposedEndpoints__1="decrypt"

A named connection string was used, but the name 'DefaultConnection' was not found in the application's configuration

I have a DbContext named FileManagerContext in my DOTNET 6 API:
public class FileManagerContext : DbContext {
public FileManagerContext(DbContextOptions<FileManagerContext> options) : base(options) { }
protected override void OnModelCreating(ModelBuilder modelBuilder) {
base.OnModelCreating(modelBuilder);
modelBuilder.ApplyConfigurationsFromAssembly(GetType().Assembly);
}
}
It's a pretty simple DbContext with a simple Entity in it. Anyway, I have this appsettings.json too:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"ConnectionStrings": {
"DefaultConnection": "Server=localhost;Database=FM;User=SA;Password=1234;"
},
"AllowedHosts": "*"
}
And here is the startup snippet in Program.cs's top level statement:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<FileManagerContext>(
opt => opt.UseSqlServer("name=DefaultConnection"));
I could use migrations in the case. All thing goes good. I can add migrations and I can update database successfully. But when I run the application and try to use DbContext I get this error:
System.InvalidOperationException: A named connection string was used,
but the name 'DefaultConnection' was not found in the application's
configuration. Note that named connection strings are only supported
when using 'IConfiguration' and a service provider, such as in a
typical ASP.NET Core application. See
https://go.microsoft.com/fwlink/?linkid=850912 for more information.
I've also tried to get the connection string like this:
var cs = builder.Configuration.GetConnectionString("DefaultConnection");
But it returns null. Can anybody help me through please?
From the sounds of the error, your configuration might not be getting picked up.
How is the configuration being created?
Do you see AddConfiguration extension method being called on the Webhost? the contents of the method should look something like the following:
IConfiguration config = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", false, false)
.AddJsonFile($"appsettings.{envLower}.json", true, true)
.AddEnvironmentVariables()
.Build();
The the call to build may or may not exist. We typically manually create the configuration object because we need to construct the Loggers and then use AddConfiguration extension method to add that object to the host.
If you don't see that, please take a look at the documentation from Microsoft for guidance on how to set it up. Configuration Documentation
Alternatively, you can get the connection string via the Configuration Object and pass it to the UseSqlServer method.
services.AddDbContext<FileManagerContext>((provider, options) => {
IConfiguration config = provider.GetRequiredService<IConfiguration>();
string connectionString = config.GetConnectionString("DefaultConnection");
options.UseSqlServer(connectionString);
});
in the alternate method, a service provider is passed to the action. You can use the provider to fetch the IConfiguration object from the DI container.
use builder.Configuration
builder.Services.AddDbContext<FileManagerContext>(options =>
{
options.UseSqlServer(builder.Configuration["ConnectionStrings:DefaultConnection"]);
});
or builder.Configuration.GetConnectionString as #Serge mentions
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"));
Don't use "name=DefaultConnection" since it is treated as connection string name including "name="
I am using this syntax in program.cs and everything is working properly
var builder = WebApplication.CreateBuilder(args);
.......
builder.Services.AddDbContext<FileManagerContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
and since you are using Ms Sql Server , the connection string for sql server is usually like this
"DefaultConnection": "Data Source=xxxx;Initial catalog=xxx;Integrated Security=False;User ID=xxxx;Password=xxxx;"
and my OnConfiguring code looks like this
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
...your code
}
}
Thanks To #Neil I figured it out. The configurations were not loaded into app. But, since I'm in dotnet 6's top level statements, adding configurations seems a little bit different from #Neil's advice. So here is the working solution:
builder.Services.AddControllers();
var currentDirectory = AppDomain.CurrentDomain.BaseDirectory;
var environmentName = builder.Environment.EnvironmentName;
builder.Configuration
.SetBasePath(currentDirectory)
.AddJsonFile("appsettings.json", false, true)
.AddJsonFile($"appsettings.{environmentName}.json", true, true)
.AddEnvironmentVariables();
// blah blah
builder.Services.AddDbContext<FileManagerContext>(
opt => opt.UseSqlServer("name=DefaultConnection"));
I was trying to scaffold the DB changes by this command :
Scaffold-DbContext -Connection name=YourDBContextObjectName -Provider
Microsoft.EntityFrameworkCore.SqlServer -project Destination_NameSpace
-context YourDBContextObjectName -OutputDir Entities -force
But the issue is the same in the code-first approach as well, In my scenario, I had different appsettings for different ENVs.
The appSettings.json was like so (as you can see, there is connectionStrings information in this file:
Also, my appsettings.dev.json was like so :
The problem is being occurred because when I tried to scaffold the DB changes by command, Firstly it tries to build your project, and because the Redis and RabbitMQ connection information is missed in the appsettings.json file, It cannot connect to the dataSources. so It returns the error.
An error occurred while accessing the Microsoft.Extensions.Hosting services. Continuing without the application service provider. Error: Object reference not set to an instance of an object.
System.InvalidOperationException: A named connection string was used, but the name 'DBContextName' was not found in the application's configuration. Note that named connection strings are only supported when using 'IConfiguration' and a service provider...
Solution :
Set The correct Project as the Startup project
Add missing information about other connections to the appsettings.json
As you see, regardless of the error, It's not always related to the Database connection string.
I had the same problem but the error occurs when the application was published on the server. My string connection is in the user's secret, what I did was the following:
In program.cs
// Add services to the container.
builder.Services.AddDbContext<DataContext>(options =>
{
options.UseSqlServer("Name=dbconnection");
});
builder.Services.AddDbContext<DataContext>();
And before publish, select the string connection in data base options and make sure it is the correct:
Publish Options
It works for me in dot 6 API
Hello, the highlighted code worked for

How do environment variables know what environment I am running on and how do I set them in C#?

I am reading a selenium guidebook for c# and they show this:
class BaseTest
{
private static string VendorDirectory = System.IO.Directory.GetParent(
System.AppContext.BaseDirectory).
Parent.Parent.Parent.FullName
+ #"/vendor";
protected IWebDriver Driver;
public static string BaseUrl;
[SetUp]
protected void SetUp()
{
BaseUrl = System.Environment.GetEnvironmentVariable("BASE_URL") ??
"http://the-internet.herokuapp.com";
But it doesn't show how they are actually setting the environment variables. Is BASE_URL coming from appsettings.json? I'm not sure where they are getting it from. Right now, I have a class with all of the urls I am using throughout my tests like this:
public static class Urls
{
public static readonly string baseUrl = "https://localhost:5001/";
public static readonly string aboutUrl = $"{baseUrl}about";
public static readonly string citiesUrl = $"{baseUrl}cities";
public static readonly string countriesUrl = $"{baseUrl}countries";
}
I don't think this is the best way to do it and want to try to use environment variables instead but I am not sure how to do that. When I change from localhost to a production environment how I have it now will obviously break. How can I set the baseUrl so it knows what environment I am in?
EDITED
My test solution is in a separate repo from my project solution. My test solution is a c# xunit test project. I added an appsettings.json file to my solution. It looks like this
{
"Base_Url": "https://testurl/",
"AllowedHosts": "*"
}
Inside one of my tests that uses the url, I am doing this
public static IConfiguration InitConfiguration()
{
var config = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.Build();
return config;
}
[Fact]
public void LoadFaqs()
{
using IWebDriver driver = new ChromeDriver();
var config = InitConfiguration();
var faqurl = config["Base_Url"] + "faqs";
driver.Navigate().GoToUrl(faqurl);
}
When I run my test, it is failing because it cannot find my appsettings.json file. I also tried putting it inside my test folder and it still couldn't find it. I'm not sure what I am doing wrong.
Locally, they must be setting them up by using either the project configuration (this is for Visual Studio):
Project properties -> Debug tab -> Environment variables
or by running some MSBuild scripts/tasks on build events that ensure the environment variables are added. (quick search on SO). Or by adding them manually on the code when the test host is being built somehow (I'm guessing this is for some functional or integration testing)
In CI/CD pipelines they are set up depending on the platform you're using (Github, Gitlab, Azure DevOps etc)
The downside of using hard-coded classes for configuration is that whenever the configuration changes (like when you change your environment and instead of pointing to a local api you need to point to the production one), you need to change/recompile your code, like you said.
So yeah, I'd say have a look and explore those options. Configuration can use several configuration providers. On asp.net core, by default, they use one called ChainedConfigurationSource, which includes reading from the appsettings.json files, environment variables etc.
Environment variable are set by the Windows system with predefined values
the most common ENV variable is PATH
you can display env variables on cmd with: echo %ENV_NAME%
for example echo %PATH% gives you:
C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;
Env variables is a way to pass parameters between a parent process to a child process ( in the cases where args on command lines are a problem)
Then env context is only known by the child process and disappear once the process is killed.
Your selenium launcher might set additional variable with win api like:
public static void SetEnvironmentVariable (string variable, string? value);
SetEnvironmentVariable
In your example I would say: do not care about how selenium set this env variable , just take the content of BASE_URL ( hoping it is not empty....)

How can I use config.json to set environment-specific variables for a Visual Studio 2015/DNX project?

I have a Visual Studio 2015 solution made up of projects targeting DNX framework. I have been working locally but I plan to deploy to Azure environments (dev/test/prod). Naturally, the solution uses different database connection strings and other variables dependent on the environment. In the past I made use of cloud configuration files to set these variables, reading them with the CloudConfigurationManager.
I am given to understand that I need to use a config.json file. I have the following code in my Startup.cs file:
public Startup(IHostingEnvironment env, IApplicationEnvironment app)
{
Configuration = new ConfigurationBuilder(app.ApplicationBasePath)
.AddJsonFile("config.json")
.AddJsonFile($"config.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables()
.Build();
Configuration.Set("ASPNET_ENV", "Development");
}
My config.json file is currently an empty object { }. How do I add variables to this file (syntax?), and how do I access them from code?
Note this line in your startup code:
.AddJsonFile($"config.{env.EnvironmentName}.json", optional: true)
This adds an additional JSON config file which is named depending on your environment name. So add a new file to your project called:
config.<environment-name>.json
And set up the details in there, such as:
{
"AppSettings": {
"SomeVariable": "Blah"
},
"Data": {
"YourConnectionString": {
"ConnectionString": "<connection string here>"
}
}
}
For reading the configuration, there's a good answer here: Using IConfiguration globally in mvc6
You can use plain old JSON for that. For example, here's how to define a connection string:
{
"Foo": "Bar"
}
To access the configuration value, you do:
config.Get("username")
or:
config["username"];
You can find more information here: http://docs.asp.net/en/latest/fundamentals/configuration.html

Categories