AppSettings for AzureFunction on .NET 8 (Isolated)

833 views Asked by At

Context

I have an existing Linux Azure Function running on .Net 6 (In-process) v4. I have a lot of configuration coming from appsettings.json. Most of these configurations are objects with nested properties or arrays (nothing fancy, but common JSON). Some of these configurations are overridden by the Azure Environment Variable panel (former Configuration panel), especially for connectionStrings or some prod-related settings (expiry timer, etc), but most of the appsettings.json config is default settings to apply on each env.

The problem

I am trying to update the Azure Function to .NET 8. According to the documentation, I have to migrate to the isolated worker model. This is a heavy breaking change and causing a lot of pain, but I can manage that. The real problem is that the appsettings.json config is not loaded anymore (as it used to) when running in Azure. Everything is fine when running locally. I wasted a lot of time trying to get it to load without success. Config is coming nicely from the Azure Environement Variable panel though.

What I don't want to do

  • I don't want to rely solely on the Azure Environment Variable panel as I don't want to replicate and maintain all the default configs in all environments. We have a plan to deploy configuration through bicep templates, but that has much more implications on the overall project infrastructure.
  • I don't want to use the local.json as it is not able to deal with JSON objects or arrays. I would have to flatten all my configs and that would be a mess.

Code sample

I've reproduced the problem in a test project with a dummy setup and am experiencing the same behavior

Program.cs

var host = new HostBuilder()
    .ConfigureFunctionsWebApplication()
    .ConfigureServices((appBuilder, services) =>
    {
        var configuration = appBuilder.Configuration;

        services.AddApplicationInsightsTelemetryWorkerService();
        services.ConfigureFunctionsApplicationInsights();

        services.Configure<DummyOption>(configuration.GetSection(nameof(DummyOption)));
    })
    .ConfigureAppConfiguration((hostingContext, configBuilder) =>
    {
        var env = hostingContext.HostingEnvironment;

        configBuilder
            .AddJsonFile(Path.Combine(env.ContentRootPath, $"appsettings.json"), optional: true, reloadOnChange: true)
            .AddEnvironmentVariables();
    })
    .Build();

host.Run();

DummyOption

public class DummyOption
{
    public string Foo { get; set; }
}

DummyFunction.cs

    public class DummyFunction
    {
        private readonly ILogger<DummyFunction> _logger;
        private readonly DummyOption _options;

        public DummyFunction(ILogger<DummyFunction> logger, IOptions<DummyOption> options)
        {
            _logger = logger;
            _options = options.Value;
        }

        [Function("DummyConfig")]
        public IActionResult Run([HttpTrigger(AuthorizationLevel.Anonymous, "get")] HttpRequest req)
        {
            var json = JsonSerializer.Serialize(_options);
            return new OkObjectResult(json);
        }
    }

appsettings.json

{
  "DummyOption": {
    "Foo": "Test"
  }
}

The function returns {"Foo":"Test"} when running locally but {"Foo":null} when running in Azure. The function returns {"Foo":"yolo"} if I add a DummyOption__Foo = yolo in the Azure Envrionement panel, but again I dont want to do that. This would be a pain to maintain and scale.

Also, I have added the following csproj code to deploy the appsettings.json file with the build, and confirmed the file exist next to the DLL in the Function App files.

      <Content Include="appsettings.json">
          <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
          <ExcludeFromSingleFile>true</ExcludeFromSingleFile>
          <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
      </Content>

I even confirmed the file exists, and is accessible by adding some logs in ConfigureAppConfiguration.

Finally, by adding more logs in the ConfigureService, I can see the appsettings.json config is just not loaded in the configuration at all.

I am running out of options and would love some help.

Thanks

2

There are 2 answers

5
RithwikBojja On

It did worked for me by modifying the code like below and I followed Microsoft-Document:

Program.cs:

using FunctionApp132;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

var host = new HostBuilder()
               .ConfigureFunctionsWebApplication()
               .ConfigureAppConfiguration((context, builder) =>
               {
                   builder.SetBasePath(context.HostingEnvironment.ContentRootPath)
                          .AddJsonFile("appsettings.json", optional: true, reloadOnChange: false)
                          .AddEnvironmentVariables();
               })
               .ConfigureServices((context, services) =>
               {
                   var configuration = context.Configuration;

                   services.AddLogging();
                   services.Configure<DummyOption>(configuration.GetSection(nameof(DummyOption)));
               })
               .Build();

host.Run();

Added appsettings.Development.json in csproj:

Csproj:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <AzureFunctionsVersion>v4</AzureFunctionsVersion>
    <OutputType>Exe</OutputType>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>
  <ItemGroup>
    <FrameworkReference Include="Microsoft.AspNetCore.App" />
    <PackageReference Include="Microsoft.Azure.Functions.Worker" Version="1.20.1" />
    <PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http" Version="3.1.0" />
    <PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore" Version="1.2.0" />
    <PackageReference Include="Microsoft.Azure.Functions.Worker.Sdk" Version="1.16.4" />
    <PackageReference Include="Microsoft.ApplicationInsights.WorkerService" Version="2.21.0" />
    <PackageReference Include="Microsoft.Azure.Functions.Worker.ApplicationInsights" Version="1.1.0" />
  </ItemGroup>
  <ItemGroup>
    <None Update="host.json">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="local.settings.json">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
      <CopyToPublishDirectory>Never</CopyToPublishDirectory>
    </None>
      <None Update="appsettings.json">
          <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
      </None>
      <None Update="appsettings.Development.json">
          <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
          <CopyToPublishDirectory>Never</CopyToPublishDirectory>
  </None>
      </ItemGroup>
  <ItemGroup>
    <Using Include="System.Threading.ExecutionContext" Alias="ExecutionContext" />
  </ItemGroup>
</Project>

Change program.cs and csproj according to mine then it will work for sure.

Output:

enter image description here

0
Clement Germain On

Finally managed to get it working by adding this logic

if (context.HostingEnvironment.IsDevelopment() == false)
   builder.SetBasePath("/home/site/wwwroot");

So what is going on?

The application files (DLLs + appsettings.json) are deployed under "/home/site/wwwroot" BUT context.HostingEnvironment.ContentRootPath resolves to "/azure-functions-host" which contains the DLLs and appsettings.json of the Function Host (not the application itself). So appsettings.json was resolved to {"IncludeScopes":false,"LogLevel":{"Default":"Warning"}}

Directory.GetCurrentDirectory() resolves to "/tmp/functions\\standby\\wwwroot" which isn't helpful.

This is fine on windows but apparently off for Linux. To be honest, I'm not a fan of the solution, but I can't think of anything else for now.