How can I use EF Core without appsettings.json - c#

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.

Related

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.

How to update a single value in Application settings at runtime in Azure Function App

I'm building my first Function App so if I'm asking a silly question, please keep that in mind :)
I have a need to store some settings at runtime.
In standard desktop apps I can easily update app.config
by using:
System.Configuration.Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
config.AppSettings.Settings["UserId"].Value = "myUserId";
config.Save(ConfigurationSaveMode.Modified);
but in Function App I load config using ConfigurationBuilder:
var config = new ConfigurationBuilder()
.SetBasePath(context.FunctionAppDirectory)
.AddJsonFile("local.settings.json", optional: true, reloadOnChange: true)
.AddEnvironmentVariables()
.Build();
and there is no way to update that config object.
When developing locally I can update local.settings.json, but when running on Azure everything is loaded from Application settings (Environment variables).
My question is: how can I update a single value in Application settings that are associated with my Function App?
I found a similar question but the solution there requires registering app in AAD and it is updating all settings, not just one value.
Take a look at the appsettings.json based approach.
Note that in-built support for appsettings.json files is not supported in Azure functions (yet). There is a github issue that tracks this.
There are few ways to do things at present. An example answer here:
Azure Functions, how to have multiple .json config files
There is also a similar solution described at github:
https://github.com/Azure/azure-functions-host/issues/4464#issuecomment-494367524

Two connection strings for local and hosting server

I would like to ask if there is a way to tell ASP.NET Core 2 to choose different connection strings.
It is quite annoying to keep changing the connection string in the appsettings.json file every time I publish my website to the hosting server..
I am using this code to get connection string.
services.AddDbContext<AppIdentityDbContext>(options =>
options.UseSqlServer(Configuration["Data:WebDataBase:ConnectionString"]));
Maybe there is an easy way but I am thinking of using an if statement in my Startup.cs:
if (local) {
services.AddDbContext<AppIdentityDbContext>(options =>
options.UseSqlServer(Configuration["Data:WebDataBase1:ConnectionString"]));
}
else {
services.AddDbContext<AppIdentityDbContext>(options =>
options.UseSqlServer(Configuration["Data:WebDataBase2:ConnectionString"]));
}
But how can I set this local variable whether the server is my local computer or a live hosting server?
"Data": {
"WebDataBase1": {
"ConnectionString": "Data Source=DatasoruceName;Initial Catalog=DBname;Trusted_Connection=True;Integrated Security=True;"
},
"WebDataBase2": {
"ConnectionString": "Data Source=DatasoruceName;Initial Catalog=DatabaseName;Trusted_Connection=True;Integrated Security=True;"
}
}
Environment specific configuration is not supposed to be specified within code. ASP.NET Core has a mechanism for that which allows you to swap out the configuration dependending on what environment you run on.
While developing your application, you usually run in the Development environment. When you are deploying your application for production, the Production environment is used by default. But if you have other environments, you can totally make up new names for those too and have specific configurations for them. That all is explained in the Environments chapter of the documentation
What environments allow you to do is create multiple appsettings.json files. The ASP.NET Core by default comes with two files: appsettings.json and appsettings.Development.json.
The former is supposed to contain environment unspecific configuration; things that apply to all environments. The latter file contains development-specific configuration and for example adjusts the logging level so that you get more information during development. You can also use these files to specify a default connection string within appsettings.json and overwrite that for development within appsettings.Development.json.
Furthermore, the configuration file follows a very simple pattern: appsettings.<Environment>.json. So if you run in the Production environment, a file named appsettings.Production.json will be loaded if it exists. This mechanism allows you to configure all your environments differently without having to resort to detections within your code.
In addition, there is also the concept of user secrets during development. These are for development-specific configurations that do only apply to yourself but not for example to other members of your team. This is basically a per-machine configuration that allows you to overwrite both the appsettings.json and the appsettings.Development.json. This is described in the user secrets chapter.
Best practice is to avoid connection strings altogether within configuration files though, since you want to avoid that people that happen to have access to your configuration files (for example through your source code) can know the passwords to your database. In that case you can use other mechanisms, for example environment variables that are local to the process.
In your case, where you are just using integrated security, and as such rely on the credentials of the current user, this is not that much of a problem. The only thing you are leaking is the database name. – So you can definitely start out with putting the connection strings into the appsettings files.
So for example, this is how you would configure your database context within your Startup’s ConfigureServices:
services.AddDbContext<AppIdentityDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("WebDataBase"));
Your appsettings.Development.json would look like this:
{
"ConnectionStrings": {
"WebDataBase": "Data Source=DatasourceName;Initial Catalog=DBname;Trusted_Connection=True;Integrated Security=True;"
}
}
And your appsettings.Production.json would look like this:
{
"ConnectionStrings": {
"WebDataBase": "Data Source=DatasourceName;Initial Catalog=DatabaseName;Trusted_Connection=True;Integrated Security=True;"
}
}

Reload configuration when env variable has changed

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

Publishing a shared appsettings file with .net core

I have a .net core solution that contains a database project (class library) and a console application. The database project contains EF Migrations and to do Add-Migration, most methods use a hard-coded connection string in one place or the other.
To avoid hard-coding (and/or duplication) I have created a shared appsettings.json file in the solution root and I use it in my Main method and the class library
In the console application
static void Main(string[] args)
{
var settingPath = Path.GetFullPath(Path.Combine(#"../appsettings.json"));
var builder = new ConfigurationBuilder()
.AddJsonFile(settingPath, false);
var configuration = builder.Build();
var services = new ServiceCollection()
.AddDbContext<MyContext>(options => options.UseSqlServer(configuration["ConnectionStrings:MyDatabase"]))
.BuildServiceProvider();
}
And in the class library to use migrations
public class DesignTimeDbContextFactory : IDesignTimeDbContextFactory<MyContext>
{
public MyContext CreateDbContext(string[] args)
{
var settingPath = Path.GetFullPath(Path.Combine(#"../appsettings.json"));
var builder = new ConfigurationBuilder()
.AddJsonFile(settingPath, false);
var configuration = builder.Build();
var optionsBuilder = new DbContextOptionsBuilder<MyContext>()
.UseSqlServer(configuration["ConnectionStrings:MyDatabase"]);
return new MyContext(optionsBuilder.Options);
}
}
This is working well for development purposes when I use dotnet run but when I publish the console application, it doesn't include the appsettings file. Other than running a powershell script as part of dotnet publish, is there any other cleaner way of including this file when the project is published?
IDesignTimeDbContextFactory is exactly for the purpose its name describes. You shouldn't be running migrations against your production database in the first place, and if you do, you should be generating specific migrations for production into your app (instead of the class library) and using your app for the migrations. See the docs on using a separate project for migrations. That, then, negates the need to share your appsettings.json. Just leave the connection string hard-coded in your factory, since it's only for development anyways.
Now, you might have an issue I suppose in a team environment. However, even if you're using something like SQLite, you can use project-relative paths that won't be developer-specific, and with LocalDB, you can use a normal SQL Server connection string to the MSSQLLocalDB instance, which will be same for every developer using Visual Studio. Regardless, even if you do need to specify the connection specifically by developer, at that point it would make more sense to use user secrets, anyways, since you wouldn't want that info be committed to source control. Otherwise, each developer would end up clobbering the other's copy of appsettings.json, and you'd have a mess on your hands.
Long and short, just hard-code the connection string in your factory, or if you can't or won't, use user secrets for the connection string. In either case, you do not need to share appsettings.json.
The way I've done this before is to specify the startup project when you run dotnet ef (with the -s switch - the options are at https://learn.microsoft.com/en-us/ef/core/miscellaneous/cli/dotnet#common-options)
It gets messy quickly, and it's probably easiest to write some wrapper scripts for the project that deal with this kind of thing.

Categories