The new Azure Function 3.0 SDK provides a way to implement a Startup class. It gives access to the collection of services that are available by dependency injection, where I
After some research, I came across this thread on Githib
using appsettings.json + IConfiguration in Function App
From which I crafted the following extension based on the comments and suggestions that showed to have worked.
public static class FunctionHostBuilderExtensions {
/// <summary>
/// Set up the configuration for the builder itself. This replaces the
/// currently registered configuration with additional custom configuration.
/// This can be called multiple times and the results will be additive.
/// </summary>
public static IFunctionsHostBuilder ConfigureHostConfiguration (
this IFunctionsHostBuilder builder,
Action<IServiceProvider, IConfigurationBuilder> configureDelegate) {
IServiceCollection services = builder.Services;
var providers = new List<IConfigurationProvider>();
//Cache all current configuration provider
foreach (var descriptor in services.Where(d => d.ServiceType == typeof(IConfiguration)).ToList()) {
var existingConfiguration = descriptor.ImplementationInstance as IConfigurationRoot;
if (existingConfiguration is null) {
continue;
}
providers.AddRange(existingConfiguration.Providers);
services.Remove(descriptor);
}
//add new configuration based on original and newly added configuration
services.AddSingleton<IConfiguration>(sp => {
var configurationBuilder = new ConfigurationBuilder();
//call custom configuration
configureDelegate?.Invoke(sp, configurationBuilder);
providers.AddRange(configurationBuilder.Build().Providers);
return new ConfigurationRoot(providers);
});
return builder;
}
}
The main idea is to extract all the currently registered configuration related types, create a new builder, apply custom configuration and build a new configuration with the original and custom configuration details merged into one.
It would then be used in Startup
public class Startup : FunctionsStartup {
public override void Configure(IFunctionsHostBuilder builder) {
builder.ConfigureHostConfiguration((sp, config) => {
var executioncontextoptions = sp.GetService<IOptions<ExecutionContextOptions>>().Value;
var currentDirectory = executioncontextoptions.AppDirectory;
config
.SetBasePath(currentDirectory)
.AddJsonFile("appSettings.json", optional: true, reloadOnChange: true)
.AddEnvironmentVariables();
//if there are multiple settings files, consider extracting the list,
//enumerating it and adding them to the configuration builder.
});
builder.Services
.AddOptions<MachineLearningConfig>()
.Configure<IConfiguration>((settings, configuration) => {
configuration.GetSection("MachineLearningConfig").Bind(settings);
});
}
}
The above should now be able to get the settings from your custom configuration.
In the startup class:
IConfigurationRoot config = new ConfigurationBuilder()
.SetBasePath(Environment.CurrentDirectory)
.AddJsonFile("someSettings.json", optional: true, reloadOnChange: true)
.AddEnvironmentVariables()
.Build();
Add a json file to you project that holds the settings. Note that local.settings.json is ignored/removed during deployment. (Name the file something else.)
Nkosi's solution works pretty well, but it does update the way the azure function runtime loads settings for itself, by replacing IConfiguration singleton: services.AddSingleton<IConfiguration>
.
I prefer having another IConfigurationRoot that is not injected. I just need to inject my settings IOption<MachineLearningSettings>
that are linked to my own IConfigurationRoot.
I build another IConfigurationRoot that is member of the Startup class:
public class Startup : FunctionsStartup
{
private IConfigurationRoot _functionConfig = null;
private IConfigurationRoot FunctionConfig( string appDir ) =>
_functionConfig ??= new ConfigurationBuilder()
.AddJsonFile(Path.Combine(appDir, "appsettings.json"), optional: true, reloadOnChange: true)
.Build();
public override void Configure(IFunctionsHostBuilder builder)
{
builder.Services.AddOptions<MachineLearningSettings>()
.Configure<IOptions<ExecutionContextOptions>>((mlSettings, exeContext) =>
FunctionConfig(exeContext.Value.AppDirectory).GetSection("MachineLearningSettings").Bind(mlSettings) );
}
}
Note: connection strings must remain in the application settings, because it is required by triggers to create an instance of the the function app that is not started (in a consumption service plan).
In Azure Functions v3 you can use the appsettings.json
configuration pattern from ASP.NET-Core with the ConfigureAppConfiguration
call below (reference).
Additionally, change the way you add your options by using the code within the Configure
method below. You should not be passing IConfiguration
to IServiceProvider.Configure<>()
. This will allow you to use an injected IOptions<MachineLearningConfig>
object.
using Microsoft.Azure.Functions.Extensions.DependencyInjection;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.IO;
[assembly: FunctionsStartup(typeof(Startup))]
namespace MyAzureFunction
{
public class Startup : FunctionsStartup
{
public override void ConfigureAppConfiguration(IFunctionsConfigurationBuilder builder)
{
if (builder == null) throw new ArgumentNullException(nameof(builder));
var context = builder.GetContext();
builder.ConfigurationBuilder
.AddAppsettingsFile(context)
.AddAppsettingsFile(context, useEnvironment: true)
.AddEnvironmentVariables();
}
public override void Configure(IFunctionsHostBuilder builder)
{
if (builder == null) throw new ArgumentNullException(nameof(builder));
var configuration = builder.GetContext().Configuration;
builder.Services.Configure<MachineLearningConfig>(options =>
{
configuration.GetSection("MachineLearningConfig").bind(options);
});
}
}
public static class ConfigurationBuilderExtensions
{
public static IConfigurationBuilder AddAppsettingsFile(
this IConfigurationBuilder configurationBuilder,
FunctionsHostBuilderContext context,
bool useEnvironment = false
)
{
if (context == null) throw new ArgumentNullException(nameof(context));
var environmentSection = string.Empty;
if (useEnvironment)
{
environmentSection = $".{context.EnvironmentName}";
}
configurationBuilder.AddJsonFile(
path: Path.Combine(context.ApplicationRootPath, $"appsettings{environmentSection}.json"),
optional: true,
reloadOnChange: false);
return configurationBuilder;
}
}
}
When you develop a function app locally, you must maintain local copies of these values in the local.settings.json
project file. To learn more, see Local settings file.
The easiest way to upload the required settings to your function app in Azure is to use the Manage Application Settings... link that is displayed after you successfully publish your project.
Refer this example on how to get those setting values.
var config = new ConfigurationBuilder()
.SetBasePath(currentDirectory)
.AddJsonFile("local.settings.json", optional: true, reloadOnChange: true)
.AddEnvironmentVariables()
.Build();
here is a sample project
With this .NET Core 3.1 and Azure Function 3. Spent a hours days. Here is what I came up with.
[assembly: FunctionsStartup(typeof(Ugly.AzureFunctions.Startup))]
namespace Ugly.AzureFunctions
{
class Startup : FunctionsStartup
{
public override void ConfigureAppConfiguration(IFunctionsConfigurationBuilder builder)
{
try
{
// On Azure, we need to get where the app is.
// If you use Directory.GetCurrentDirectory(), you will get something like D:\Program Files (x86)\SiteExtensions\Functions\3.0.14785\32bit
var basePath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "..");
var environmentName = builder.GetContext().EnvironmentName;
builder.ConfigurationBuilder
.SetBasePath(basePath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{environmentName}.json", optional: true, reloadOnChange: true)
.AddEnvironmentVariables();
}
catch (Exception ex)
{
// Handle exceptions about this. Which __should__ never ever happen.
// The previous comment is sarcastic.
throw;
}
}
public override void Configure(IFunctionsHostBuilder builder)
{
try
{
// DO NOT add the configuration as Singleton.
// If you need the IConfiguration:
//var configuration = builder.GetContext().Configuration;
builder.Services
.AddOptions<MachineLearningConfig>()
.Configure<IConfiguration>((settings, configuration) => {
configuration.GetSection("MachineLearningConfig").Bind(settings);
});
}
catch (Exception ex)
{
// Handle or not handle? That's the question.
throw;
}
}
}
}