23
votes

Is it possible to use an appsettings.json file in Azure Functions?

There is documentation for environment variables here..

https://docs.microsoft.com/en-us/azure/azure-functions/functions-reference-csharp#environment-variables

..however we use Octopus for deployments and would really like to have appsettings version controlled.

We have tried using

{
  "frameworks": {
    "net46": {
      "dependencies": {
        "Microsoft.Extensions.Configuration": "1.0.0",
        "Microsoft.Extensions.Configuration.Json": "1.0.0"
      }
    }
  }
}

but constantly get

2016-11-23T15:27:03.811 (12,16): error CS0012: The type 'Object' is defined in an assembly that is not referenced. You must add a reference to assembly 'System.Runtime, Version=4.0.0.0

Even being able to supply/update environment variables via Octopus would be sufficient for our needs.

Please advise.

7

7 Answers

7
votes

Only environment variables are supported for app settings and connection strings. appsettings.json is not supported.

However, you can use Azure Resource Manager (ARM) Templates to configure the settings for your Function App. Here's a blog post that describe this in more detail.

21
votes

For your needs the answer is YES! Azure Functions can use appsettings.json for your configurations. But there are some ordering sequence that Azure will do when a Function is requested.

1º) Azure will look for that KEYS that you used on .GetEnvironmentVariables("[KEY]") method, through Keys that were configured on Application Settings blade in Azure Functions settings

2º) If Azure wasn't able to find out that configurations through Application Settings keys, then Azure will try looking for after appsettings.json file into your root folder of the Function that you working on.

3º) Finally, if Azure wasn't able to find out this keys either on Application Settings neither on appsettings.json file, then Azure will do the last attempt to find out web.config for looking for into this file appsettings section keys.

For your appreciation, you'll able to find out these configurations by the sample on my github repo: here and here

I hope that these information help you.

13
votes

According to the changes made to the configuration files, you should only use local.settings.json since the appsettings.json was renamed to local.settings.json

Reference to the change: azure-functions-cli

4
votes

Configuration source customization is available beginning in Azure Functions host versions 2.0.14192.0 and 3.0.14191.0.

To specify additional configuration sources, override the ConfigureAppConfiguration method in your function app's StartUp class.

using Microsoft.Azure.Functions.Extensions.DependencyInjection;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
[assembly: FunctionsStartup(typeof(MyNamespace.Startup))]
namespace MyNamespace
{
    public class Startup : FunctionsStartup
    {
        public override void ConfigureAppConfiguration(IFunctionsConfigurationBuilder 
builder)
        {
            FunctionsHostBuilderContext context = builder.GetContext();

            builder.ConfigurationBuilder
                .AddJsonFile(Path.Combine(context.ApplicationRootPath, 
"appsettings.json"), optional: true, reloadOnChange: false)
                .AddJsonFile(Path.Combine(context.ApplicationRootPath, $"appsettings. 
{context.EnvironmentName}.json"), optional: true, reloadOnChange: false)
                .AddEnvironmentVariables();
        }
    }
}

// update configuration in csproject

<None Update="appsettings.json">
  <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>      
</None>

<None Update="appsettings">
    <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    <CopyToPublishDirectory>Never</CopyToPublishDirectory>
</None>

Instead of everytime using configuration, you can inject option class whenever required as below. From inside the Startup.Configure method, you can extract values from the IConfiguration instance into your custom type using the following code:

builder.Services.AddOptions<MyOptions>()
    .Configure<IConfiguration>((settings, configuration) =>
    {
        configuration.GetSection("MyOptions").Bind(settings);
    });

Function class: using System; using Microsoft.Extensions.Options;

public class HttpTrigger
{
    private readonly MyOptions _settings;

    public HttpTrigger(IOptions<MyOptions> options)
    {
        _settings = options.Value;
    }
}

Refer: https://docs.microsoft.com/en-us/azure/azure-functions/functions-dotnet-dependency-injection#customizing-configuration-sources

3
votes

In Azure Functions, setting are stored in local.settings.json (create this file if doesn't exist in your solution/ name should be exact as mentioned).

once you add setting file, you have to configure it under your Run() method as mentioned bellow,

enter image description here

When Accessing setting, use below

IConfigurationRoot config;
config["fromEmail"];

use below command to publish settings

func azure functionapp publish *YourAppName* --publish-local-settings -i

enter image description here

1
votes

Tried and test approach where we can:

  1. Create an appsettings.json within your function app project.
  2. You would need add a Startup.cs class within your project inheriting from FunctionsStartup class. This would expose a method Configure(IFunctionsHostBuilder builder) for overriding.
  3. For control on level of customisation, I would say to extend IFunctionsHostBuilder class lets say it is (IFunctionsHostBuilderExtensions.cs) and add an extension method to add a new configuration builder to the same. This gives us the benefit of controlling how we want to configure the appsettings setup at runtime.
  4. Once, done, you can call the newly created extension method by either passing the entire file path for the appsettings or just the name.( You can configure base path for you appsettings in the extension method to avoid hastiness within the code).
  5. Once you build the extension method, you will get a service ready for injection i.e. IConfiguration, which can be used as anywhere within the codebase.
  6. You can also add multiple providers for the appsettings, like Azure Key Vault, AWS Secret Manager etc. For the same , all you need to do is add an extension method within the IFunctionsHostBuilderExtensions class & call them within your startup class.
  7. If you want to keep things more neat, you can implement a wrapper around the IConfiguration service to expose a single GetSettings(string key) method which would return the settings that you want from a central collection of providers within IConfiguration.

Some code snips below:

/// <summary>
///     Represents the startup class of the function app.
/// </summary>
public class Startup : FunctionsStartup
{
    private const string LocalSettingFileGenericName = "appsettings";
    private const string LocalSettingFileExtension = "json";

    /// <summary>
    ///     Configures the host builder for the function app.
    /// </summary>
    /// <param name="builder">The function app host builder.</param>
    public override void Configure(IFunctionsHostBuilder builder)
    {
        try
        {
            builder.AddConfiguration((configurationBuilder) =>
            {
                var configuration = typeof(Startup).Assembly.GetCustomAttribute<AssemblyConfigurationAttribute>().Configuration;

                var configurationFileName = !string.Equals(configuration, "Release")
                    ? $"{LocalSettingFileGenericName}.{configuration.ToLowerInvariant()}.{LocalSettingFileExtension}"
                    : $"{LocalSettingFileGenericName}.{LocalSettingFileExtension}";

                var configurationSource = configurationBuilder
                    .AddJsonFile(configurationFileName, false, true)
                    .AddEnvironmentVariables();

                var partialConfigurationBuilder = configurationSource.Build();

                var keyVaultName = partialConfigurationBuilder.GetSection(ConfigurationKeys.KeyvaultName)?.Value;

                return configurationSource
                       .AddKeyVaultWithManagedIdentity(keyVaultName)
                       .Build();
            });

IFunctionBuilderExtensions.cs

 internal static class IFunctionsHostBuilderConfigurationsExtensions
{
    private const string keyVaultGenericUri = "https://{0}.vault.azure.net/";

    /// <summary>
    ///     Provides an extension method to add configuration provider.
    /// </summary>
    /// <param name="builder">The function app host builder.</param>
    /// <param name="configBuilderFunc">The delegate to pointing to configuration builder.</param>
    /// <returns>The function app host builder</returns>
    public static IFunctionsHostBuilder AddConfiguration(
        this IFunctionsHostBuilder builder,
        Func<IConfigurationBuilder, IConfiguration> configBuilderFunc)
    {
        var configurationBuilder = builder.GetBaseConfigurationBuilder();

        var configuration = configBuilderFunc(configurationBuilder);

        builder.Services.Replace(ServiceDescriptor.Singleton(typeof(IConfiguration), configuration));

        return builder;
    }

    /// <summary>
    ///     Provides an extension method to add Azure Key Vault as a configuration provider.
    /// </summary>
    /// <param name="builder">The configuration builder.</param>
    /// <param name="keyvaultName">The azure key vault name.</param>
    /// <param name="authenticationClientId">The AAD application clientId.</param>
    /// <param name="authenticationClientSecret">The AAD application clientSecret.</param>
    /// <returns>The configuration builder.</returns>
    public static IConfigurationBuilder AddKeyVaultWithManagedIdentity(
        this IConfigurationBuilder builder,
        string keyvaultName)
    {
        if (string.IsNullOrWhiteSpace(keyvaultName))
        {
            return builder;
        }

        var serviceTokenProvider = new AzureServiceTokenProvider();
        var keyVaultClient = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(serviceTokenProvider.KeyVaultTokenCallback));

        var keyVaultUri = string.Format(keyVaultGenericUri, keyvaultName);

        builder.AddAzureKeyVault(
            keyVaultUri,
            keyVaultClient,
            new DefaultKeyVaultSecretManager());

        return builder;
    }

    private static IConfigurationBuilder GetBaseConfigurationBuilder(this IFunctionsHostBuilder builder)
    {
        var configurationBuilder = new ConfigurationBuilder();

        var descriptor = builder.Services.FirstOrDefault(
            service => service.ServiceType == typeof(IConfiguration));

        if (descriptor?.ImplementationInstance is IConfiguration configRoot)
        {
            configurationBuilder.AddConfiguration(configRoot);
        }

        var rootConfigurationBuilder = configurationBuilder.SetBasePath(GetCurrentDirectory());

        return rootConfigurationBuilder;
    }

    private static string GetCurrentDirectory()
    {
        var currentDirectory = Path.GetDirectoryName(
            Assembly.GetExecutingAssembly().Location);

        return currentDirectory.Replace("bin", "{Your settings directory}");
    }

Example for wrapper implementation:

  /// <summary>
///     Represents the configuration settings provider class.
/// </summary>
public class ConfigurationSettings : IConfigurationSettings
{
    private readonly IConfiguration configurationSource;

    /// <summary>
    ///     Initializes the class of type <see cref="ConfigurationSettings"/>.
    /// </summary>
    /// <param name="configurationSource">The configuration source.</param>
    public ConfigurationSettings(
        IConfiguration configurationSource)
    {
        this.configurationSource = configurationSource;
    }

    ///<inheritdoc/>
    public T GetSetting<T>(string key)
    {
        try
        {
            if (!configurationSource.GetSection(key).Exists())
            {
                throw new ConfigurationDoesNotExistException(
                    $"The configuration with key {key} does not exist in appsetting or key vault.");
            }

            return (T)Convert.ChangeType(configurationSource.GetSection(key)?.Value, typeof(T));
        }
        catch (InvalidCastException)
        {
            throw;
        }
        catch (Exception)
        {
            throw;
        }
    }
}
-2
votes

For dependencies you should use/create the project.json inside your function. There you can specify your dependencies. Please check: https://docs.microsoft.com/en-us/azure/azure-functions/functions-reference-csharp#package-management

For example:

{
  "frameworks": {
    "net46":{
      "dependencies": {
        "Microsoft.ProjectOxford.Face": "1.1.0"
      }
    }
   }
}