skip to Main Content

I’m facing issues in reading Nested configuration values from local.settings.json in my Azure Function(v6) and the values always seem to be coming as null. Below are my code snippets:

local.settings.json:

{
  "IsEncrypted": false,
  "Values": {
    "AzureWebJobsStorage": "UseDevelopmentStorage=true",
    "FUNCTIONS_WORKER_RUNTIME": "dotnet",
    "EndpointDetails": {
      "GetAllTaskDetails": {
        "Url": "https://localhost:7000/api/Task/GetAllTaskDetails",
        "Method": "GET",
        "NotificationRecipients": "[email protected]"
      },
      "GetTaskDetails": {
        "Url": "https://localhost:7000/api/Task/GetTaskDetails",
        "Method": "GET",
        "NotificationRecipients": "[email protected]"
      }
    }
  }
}

Startup.cs:

using Microsoft.Azure.Functions.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;

[assembly: FunctionsStartup(typeof(HealthChecker.Startup))]
namespace HealthChecker
{
    public class Startup : FunctionsStartup
    {
        public override void Configure(IFunctionsHostBuilder builder)
        {
            // Getting the IConfiguration instance
            var configuration = builder.GetContext().Configuration;

            // Registering health checks
            builder.Services.AddHealthChecks();

            // Calling the method that defines health checks
            HealthCheckDefinitions.ConfigureHealthChecks(builder.Services.AddHealthChecks(), configuration);
        }
    }
}

HealthCheckDefinitions.cs:

public static class HealthCheckDefinitions
{
 private static Dictionary<string, List<string>> endpointDetails = new Dictionary<string, List<string>>();

 public static void ConfigureHealthChecks(IHealthChecksBuilder builder, IConfiguration configuration){
     private static IConfigurationSection endpointDetailsListConfig;
     private static string getAllTaskDetails;
     private static string getTaskDetails;
     List<string> endpoints = new List<string>();

     InitializeConfigValues(configuration);

     endpoints.AddRange(new List<string>
            {
                 getAllTaskDetails,
                 getTaskDetails
            });

     foreach (var endpoint in endpoints)
            {
                var endpointDetailsConfig 
                  = endpointDetailsListConfig.GetSection(endpoint);
                GetEndpointDetails(endpointDetailsConfig, endpoint);
               
                foreach (var endpointDetail in endpointDetails)
                {
                  builder.AddCheck(endpointDetail.Key, () =>
                  {
                    // lines of code
                    // .....
                  });
                }
            }
   }
 
 private static void InitializeConfigValues(IConfiguration configuration)
    {
        endpointDetailsListConfig = configuration.GetSection("EndpointDetails");
        getAllTaskDetails = endpointDetailsListConfig.GetSection("GetAllTaskDetails").Key;
        getTaskDetails = endpointDetailsListConfig.GetSection("GetTaskDetails").Key;
    }

   private static void GetEndpointDetails(IConfigurationSection endpointDetailsConfig, string endpoint)
    {
        if (!string.IsNullOrEmpty(endpointDetailsConfig["Url"]) && !string.IsNullOrEmpty(endpointDetailsConfig["Method"])
            && !string.IsNullOrEmpty(endpointDetailsConfig["NotificationRecipients"]))
            {
                endpointDetails.Add(endpoint, new List<string>
                {
                    endpointDetailsConfig["Url"],
                    endpointDetailsConfig["Method"],
                    endpointDetailsConfig["NotificationRecipients"]
                });
            }
    }
}

The values of the following are always null. Where am I going wrong here?

                endpointDetailsConfig["Url"],
                endpointDetailsConfig["Method"],
                endpointDetailsConfig["NotificationRecipients"]

2

Answers


  1. Chosen as BEST ANSWER

    So, after spending a day breaking my head on this issue, I finally came to the solution thanks to this post which was an eye-opener for me.

    Basically you should not add nested configuration in your local.settings.json file, it creates all sorts of other issues(see below issue) when you do so. Hence, I moved my nested configuration into another file like so:

    Missing value for AzureWebJobsStorage in local.settings.json. This is required for all triggers other than httptrigger, kafkatrigger, rabbitmqtrigger, orchestrationTrigger, activityTrigger, entityTrigger. You can run 'func azure functionapp fetch-app-settings ', specify a connection string in local.settings.json, or use managed identity to authenticate.

    Configuration.json:

       {
         "EndpointDetails": {
              "GetAllTaskDetails": {
                "Url": "https://localhost:7000/api/Task/GetAllTaskDetails",
                "Method": "GET",
                "NotificationRecipients": "[email protected]"
              },
              "GetTaskDetails": {
                "Url": "https://localhost:7000/api/Task/GetTaskDetails",
                "Method": "GET",
                "NotificationRecipients": "[email protected]"
              }
            }
        }
    

    And my Startup.cs class after necessary changes:

    using Microsoft.Azure.Functions.Extensions.DependencyInjection;
    using Microsoft.Extensions.Configuration;
    using Microsoft.Extensions.DependencyInjection;
    
    [assembly: FunctionsStartup(typeof(HealthChecker.Startup))]
    namespace HealthChecker
    {
        public class Startup : FunctionsStartup
        {
            public override void Configure(IFunctionsHostBuilder builder)
            {
                var context = builder.GetContext();
    
                // Load configuration from local.settings.json
                var localConfig = new ConfigurationBuilder()
                    .SetBasePath(context.ApplicationRootPath)
                    .AddJsonFile("local.settings.json", optional: true, reloadOnChange: true)
                    .AddEnvironmentVariables()
                    .Build();
    
                // Load configuration from config.json
                var config = new ConfigurationBuilder()
                    .SetBasePath(context.ApplicationRootPath)
                    .AddJsonFile("configuration.json", optional: true, reloadOnChange: true)
                    .AddEnvironmentVariables()
                    .Build();
    
                // Register the configurations
                builder.Services.AddSingleton(localConfig);
                builder.Services.AddSingleton(config);
    
                // Registering health checks
                builder.Services.AddHealthChecks();
    
                // Calling the method that defines health checks
                HealthCheckDefinitions.ConfigureHealthChecks(builder.Services.AddHealthChecks(), localConfig, config);
            }
        }
    }
    

    And that's pretty much it. You can access these values easily wherever you want to, viz:

    HealthCheckDefinitions.cs

    public static class HealthCheckDefinitions
        {
         private static Dictionary<string, List<string>> endpointDetails = new Dictionary<string, List<string>>();
    
         public static void ConfigureHealthChecks(IHealthChecksBuilder builder, IConfiguration configuration){
           private static IConfigurationSection endpointDetailsListConfig;
           private static string getAllTaskDetails;
           private static string getTaskDetails;
           List<string> endpoints = new List<string>();
       
           var x = config["EndpointDetails:GetAllTaskDetails:Url"];
           //lines of code
           //...
     }
    
     //lines of code
     //...
    }
    

    Edit:

    One more thing, please make sure that CopyToOutputDirectory is correctly set. Refer this if required. Below is my .csproj file:

    <Project Sdk="Microsoft.NET.Sdk">
      <PropertyGroup>
        <TargetFramework>net6.0</TargetFramework>
        <AzureFunctionsVersion>v4</AzureFunctionsVersion>
      </PropertyGroup>
      <ItemGroup>
        <PackageReference Include="AzureFunctions.Extensions.DependencyInjection" Version="1.1.3" />
        <PackageReference Include="Microsoft.Azure.Functions.Extensions" Version="1.1.0" />
        <PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="6.0.0" />
        <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.0" />
        <PackageReference Include="Microsoft.NET.Sdk.Functions" Version="4.1.1" />
      </ItemGroup>
      <ItemGroup>
        <None Update="configuration.json">
            <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
            <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
        </None>
        <None Update="host.json">
          <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
        </None>
        <None Update="local.settings.json">
            <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
            <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
        </None>
      </ItemGroup>
    </Project>
    

    I tried to give all the steps that I followed to fix this issue, hope it helps!


  2. In your local.settings.json file, "GetAllTaskDetails" and "GetTaskDetails" exist here:

    {
      "Values": {
        "EndpointDetails": {
          "GetAllTaskDetails": {
            ...
          },
          "GetTaskDetails": {
            ...
          }
        }
      }
    }
    

    I.e. at these paths:

    "Values:EndpointDetails:GetAllTaskDetails"
    "Values:EndpointDetails:GetTaskDetails"

    By setting

    endpointDetailsListConfig = configuration.GetSection("EndpointDetails");
    

    , you try to target a section that is not at the config root level. Hence, the return value of GetSection() should not be an actual configuration section.

    As per the .GetSection() documentation:

    This method will never return null. If no matching sub-section is found with the specified key, an empty IConfigurationSection will be returned.

    Side note:
    To verify whether a section (or value) exists or not, call .Exists() on .GetSection()‘s return value.


    If you rather target "Values:EndpointDetails", you should be able to traverse the tree correctly from there:

    endpointDetailsListConfig = configuration.GetSection("Values:EndpointDetails");
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search