I made an Azure Functions app that connects to a CosmosDB. I made the following class to retrieve a CosmosClient instance:
public static class CosmosClientContext
{
private static readonly CosmosClient CosmosClient = GetCosmosClient();
public static CosmosClient GetCosmosClient()
{
return CosmosClient
//?? new CosmosClient("AccountEndpoint=https://mycosmosdb.documents.azure.com:443/;AccountKey=JkLv....etc;");
?? new CosmosClient("AccountEndpoint=https://localhost:8081/;AccountKey=C2y6...etc");
}
}
So as you can see I'm currently hard coding the ConnectionString in the class, which is obviously not optimal.
I noticed that I have a local.settings.json file in my project. Is that the place to store to local connection string in? And if so, do I have to use a specific key name for this? Or how do I read from it?
And how does this then work when I publish my Azure Functions app?
So how can I make it so that it locally uses my local ConnectionString, and when published it automatically uses the remote ConnectionString?
You store these as environment variables. Locally these will be in the local.settings.json and on Azure they will be in the Application Settings tab under the Configuration blade of your function app in the Azure Portal.
The name of your variable is arbitrary. Your local.settings.json will look something like this:
{
"IsEncrypted": false,
"Values": {
"CosmosDbConnectionString": "[CONNECTION STRING HERE]"
"FUNCTIONS_WORKER_RUNTIME": "dotnet"
}
}
Be sure it goes inside of the "Values" section. The you simply access it using the GetEnvironmentVariable method:
Environment.GetEnvironmentVariable("CosmosDbConnectionString");
So something like:
return new CosmosClient(Environment.GetEnvironmentVariable("CosmosDbConnectionString"));
You don't need any sort of logic to switch between dev and prod. Since the environment variables are different if each place, it will automatically pick up the correct connection string.
Note, in the Portal, make sure you use the "Application Settings" section and NOT the "Connection Strings" section. It's confusing, but the Connections String section is only used for Entity Framework on Functions.
If you prefer to access configuration in the idiomatic ASP.NET Core manner with dependency injection, you can use a Startup.cs that looks something like the below. The values are stored in local.settings.json or in hosted App Settings.
[assembly: FunctionsStartup(typeof(MyApp.Functions.Startup))]
namespace MyApp.Functions
{
public class Startup : FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
IServiceCollection services = builder.Services;
// Read configuration
var config = new ConfigurationBuilder().SetBasePath(Directory.GetCurrentDirectory()).AddEnvironmentVariables().Build();
// Pass configuration values to a custom IServiceCollection extension
services.AddCosmosDb(new CosmosDbOptions
{
ConnectionString = config["CosmosDb:ConnectionString"],
DatabaseId = config["CosmosDb:DatabaseId"]
});
// Configuration access
services.Configure<AzureStorageOptions>(config.GetSection("AzureStorage"));
// Other setup, add more things to services, etc
services.AddMemoryCache();
}
}
}
Then in any function, you can constructor-inject any services configured at Startup, including your Cosmos service instance.
Related
Is there a special way to define a Key Value setting for ConnectionStrings in Azure App Configuration?
I have tried using:
ConnectionStrings:DatabaseKeyName
ConnectionStrings\DatabaseKeyName
Using the standard builder.Configuration.GetConnectionString("DatabaseKeyName") always results in a null value. Using builder.Configuration["ConnectionStrings:DatabaseKeyName"] also results in null, however if I use a keyname that does not start with ConnectionStrings (e.g. Test:ConnectionStrings:DatabaseKeyName it works as an app setting via builder.Configuration["Test:ConnectionStrings:DatabaseKeyName"]
The Null value for ConnectionStrings:DatabaseKeyName indicates there is some special handling for ConnectionStrings in Azure App Config, but I don't know where I am going wrong. The Microsoft example pages don't seem to cover ConnectionStrings (except via KeyVault).
Basically I do not want to have to change this:
services.AddDbContext<IciContext>(o =>
{
o.UseSqlServer(Configuration.GetConnectionString("DatabaseKeyName"));
});
To this:
services.AddDbContext<IciContext>(o =>
{
o.UseSqlServer(builder.Configuration["DatabaseKeyName"]);
});
Standard app config connection string setting I need to simulate from Azure App Config:
{
"ConnectionStrings": {
"DatabaseKeyName": "Data Source=localhost;Initial Catalog=xxxx;Integrated Security=True"
},
In my secrets file it is in this format (which does not work with Azure App Config):
{
"ConnectionStrings:DatabaseKeyName": "Server=xxxx;Database=xxxx;User ID=xxxx;Password=xxxx"
}
To get the Connection String from Azure App Configuration, please check the below process.
Install the NuGet Package Microsoft.Azure.AppConfiguration.AspNetCore latest version to add the AddAzureAppConfiguration and read the key values.
To read Azure App Configuration locally, we need to set the secret manager to store the connection string.
dotnet user-secrets init
The above command enables the secret storage and sets the secret ID in .csproj of your application.
In Program.cs, add the below code
var myAppConn= builder.Configuration.GetConnectionString("AppConfig");
Output:
As mentioned in the MSDoc, For the Apps deployed in Azure App Service it is recommended to store Connection String in Configuration Section => Application Settings => Connection Strings of the deployed App.
Is there a special way to define a Key Value setting for ConnectionStrings in Azure App Configuration?
In Azure App Configuration => *YourAppConfiguration* => click on Configuration explorer Under Operations => click on Create => Key-value
In Program.cs, add the below code
var myconn = builder.Configuration.GetConnectionString("AppConfig");
builder.Host.ConfigureAppConfiguration(builder =>
{
builder.AddAzureAppConfiguration(myconn);
})
.ConfigureServices(services =>
{
services.AddControllersWithViews();
});
In any of the cshtml file, add the below code
#using Microsoft.Extensions.Configuration
#inject IConfiguration Configuration
<h1>#Configuration["MyConnection"]</h1>
Output for Key-Value from AppConfiguration:
I have a React SPA with .Net Core WebAPI backend being deployed to Azure App Service. When developing locally i use a appsettings.json file to configure the WebAPI backend. I am setting a key/value pair for an external api called "XyzApi".
{
"XyzApi": "https://somedomain/api"
}
In a WebAPI controller I access this config setting using the Configuration provider.
var xyzApiUrl = Configuration["XyzApi"]
This works fine.
When I deploy the app to the App Service I need to change the the value of "XyzApi" to override the value from the appsettings.json with a new value. I have added a key/value pair named "XyzApi" in the App Service Application Settings section in Azure.
In the App Service documentation for Application Settings it says the value will be exposed to the configuration as an Environment Variable with the name "APPSETTING_XyzApi". It also says that it is supposed to override the value from the appsettings.json file for the key "XyzApi".
When the app spins up. The issue is that the value for "XyzApi" is still set to the value from the appsettings.json file. It is NOT being overridden. And as expected there is now a new key/value pair for "APPSETTING_XyzApi".
Why is the Azure App Service not overriding the key/value pair from the appsettings.json file with the new value configured in the Azure AppService Application Settings.
Here is the code that configures the Configuration provider.
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
})
.ConfigureAppConfiguration((hostingContext, config) =>
{
var env = hostingContext.HostingEnvironment;
config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json",
optional: true, reloadOnChange: true)
.AddEnvironmentVariables();
});
}
All the steps you did is correct, except the appsettings' name in portal. You add "APPSETTING_XyzApi" in Configuration, which is different with "XyzApi" in appsettings.json. It would override only if the name fit.
Here is an example:
1. I set "myKey": "value1" in appsettings.json in local, And show the value on my index page.
2. After publish, I set the value to 123123:
3. The value override:
Alright, so we have multiple signalR services and what we want to do is when our code is deployed, we want the connection string to be picked from our custom configuration file instead of the function App settings.
This is the negotiate function. See the "SignalRConnectionInfo" attribute.
[FunctionName("negotiate")]
public IActionResult negotiate(
[HttpTrigger(AuthorizationLevel.Function, "post")]
HttpRequest req,
[SignalRConnectionInfo(HubName = HubName, ConnectionStringSetting = **"Cannot pass dynamic connection string here as it requies a constant"**)]
SignalRConnectionInfo connectionInfo )
{
}
we tried adding it in Startup.cs
public class Startup : FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
IConfigurationRoot config;
builder.Services.AddSignalR().AddAzureSignalR(config["SignalrConnectionString"]);
}
}
and it does not work this way. as it gives an error
Invalid host services. Microsoft.Azure.WebJobs.Script.WebHost: The following service registrations did not match the expected services:
[Invalid] ServiceType: Microsoft.Extensions.Hosting.IHostedService, Lifetime: Singleton, ImplementationType: Microsoft.Azure.SignalR.HeartBeat
Value cannot be null.
Parameter name: provider
So, is there any other way to use it in the function?
If you want to pick connection strings from custom configuration files, you should first add the configuration files as configuration sources.
See
https://learn.microsoft.com/en-us/azure/azure-functions/functions-dotnet-dependency-injection#customizing-configuration-sources
The SignalR Function extension would pick the connection from a fixed location, by default AzureSignalRConnectionString, and you can customize it via ConnectionStringSetting.
If you have multiple SignalR service instances, you might want to use multiple endpoints support. Currently only Persistent service transport type supports multiple SignalR instances, that is, function would establish WebSocket connections with all the SignalR instances and you could customize the routing logic. This behaviour is different to picking one instance at the function startup. See https://github.com/Azure/azure-functions-signalrservice-extension/blob/dev/docs/sharding.md .
If you don't want to connect to all the SignalR instances, you can configure each connection string in one file, and pick one file as the configuration source that would be added at function startup.
My question has two parts but they are both about secure credential storage. The background is that I am using a basic hosting service with ftp to deploy my .net core 3.1 site. I don't have directory access beyond the root folder of where I deploy my files. I am using sql server and a few other third party services that require me to use API keys.
For development, all the documentation from the different third parties have me either use the windows credential storage or put a file somewhere on the file system outside of the solution folder. But that is not an option for deployment.
So my questions are, For deployment to this hosting service...
1. Is it safe to use appsettings.json to store my API keys?
2. Also, is it save to just put a flat text file in the root of my site so it's not reachable by the public and then just pull that in at runtime?
1.Is it safe to use appsettings.json to store my API keys?
NO
If you are using third party hosting service using FTP to a folder, it not safe to assume only you can see your deployment files.
Also, is it save to just put a flat text file in the root of my site so it's not reachable by the public and then just pull that in at runtime?
It is same as using appsettings.json to store API keys since appsettings files should not be exposed publicly too.
What I can suggest is to either use some third party secure tool like Azure Vault or Hashi Vault to store secrets(they are encrypted there) & consume in your application.
If you don't want to use third party service, you can create custom Configuration Builder where you can encrypt the secrets before saving & decrypt before using.
public class CustomConfigProvider : ConfigurationProvider
{
public CustomConfigProvider() { }
public override void Load()
{
Data = MyEncryptUtils.DecryptConfiguration();
}
}
public class CustomConfigurationSource : IConfigurationSource
{
public CustomConfigurationSource() { }
public IConfigurationProvider Build(IConfigurationBuilder builder)
{
return new CustomConfigProvider();
}
}
public class CustomConfigurationSource : IConfigurationSource
{
public CustomConfigurationSource() { }
public IConfigurationProvider Build(IConfigurationBuilder builder)
{
return new CustomConfigProvider();
}
}
Call in startup
var builder = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.AddCustomConfiguration()
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true);
Source: https://stormpath.com/blog/store-protect-sensitive-data-dotnet-core
Also there is alternate approach using bridge pattern & encryption by Steve here:
https://stevetalkscode.co.uk/configuration-bridging-part-4
I have an Azure web app with several web jobs in the back-end. Up until now, I have been using traditional .Net to create these web jobs. One of the core aspects of these web jobs is that they all access global configuration properties defined in the Azure portal, as shown below:
And I reference these settings in my traditional .Net app like so:
var appKey = ConfigurationManager.AppSettings["RingCentral_AppKey"];
I am now starting to transition my web jobs to .Net Core. However, the "best practice" for for managing/retrieving app settings like this seems to be in an appsettings.json file within each web job:
And I reference my settings in my .Net Core app like so:
class Program
{
public static IConfiguration StaticConfig { get; set; }
static void Main(string[] args)
{
var builder = new HostBuilder();
builder.ConfigureWebJobs(b =>
{
b.AddAzureStorageCoreServices();
b.AddAzureStorage();
});
builder.ConfigureAppConfiguration((hostContext, config) =>
{
var conf = new ConfigurationBuilder()
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true).Build();
config.AddConfiguration(conf);
});
var host = builder.Build();
using (host)
{
host.Run();
}
}
}
public class Functions
{
IConfiguration configuration;
public Functions(IConfiguration _configuration)
{
configuration = _configuration;
}
[NoAutomaticTrigger]
public void DoTrigger(ILogger logger, [Queue("messagestarterqueue")] ICollector<string> outputQueueMessage)
{
var appKey = configuration["RingCentral_AppKey"];
}
}
That's all good, but I have to have a separate appsettings.config file in each separate project, each with the same settings repeated. Is there a way to "centralize" theses settings for my .Net Core web jobs? Either create a "global" appsettings.config file that my Azure web app can reference? Or, is there some way that each .Net Core app can reference the "old style" application settings that are currently being used by the traditional .Net web jobs?
Allow me to suggest a bit different approach.
IMHO, the best way you can do it is by utilizing the Azure App Configuration service.
(Check it on GitHub)
It will allow you to call an API to get the configured values, while you can manage it conveniently through the portal.
Actually you could use Environment.GetEnvironmentVariable(your settings key) to get the settings.
After you set the settings in Configuration, they will be saved as environment variables, you could check them in https://yousitename.scm.azurewebsites.net/Env.cshtml.
So of course yo could get them GetEnvironmentVariable, it will show as below pic.