Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

When using a FunctionsStartup class, settings are not read from the correct directory #4517

Closed
logiclrd opened this issue May 31, 2019 · 6 comments

Comments

@logiclrd
Copy link

logiclrd commented May 31, 2019

In creating a simple Azure Function using D.I. (a FunctionsStartup class) and ASP.NET Core 2 style configuration, I have run into issues with it not reading configuration from the correct directory.

In my Startup.Configure method, I tried writing code like this:

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

However, all of the configuration properties are always null. Drilling into it a bit with the debugger, it looks like the registered IConfiguration does in fact have a JsonConfigurationProvider, but it is watching the wrong directory. It looks like the JsonConfigurationProvider assumes that AppContext.BaseDirectory will be the bin directory of the web app, but when the Functions app is starting up, AppContext.BaseDirectory points at the directory containing the Functions framework bits (e.g. C:\Users\Jonathan Gilbert\AppData\Local\AzureFunctionsTools\Releases\2.22.0\cli\).

I have not found a non-ugly work-around for this yet.

Repro steps

  • Create a new Azure Functions project, HTTP trigger.
  • Create a class AppSettings.cs with a dummy setting in it:
    public class AppSettings
    {
      public string DummySetting { get; set; }
    }
  • Create a file appsettings.json with a value for this setting:
    {
      "AppSettings":
      {
        "DummySetting": "foobar"
      }
    }
  • Configure appsettings.json to be copied to the build output if newer.
  • Add a NuGet reference to Microsoft.Azure.Functions.Extensions. (At the time of writing, only version 1.0.0 is published.)
  • Create a class Startup.cs with code to register IOptions<AppSettings> via the configuration subsystem:
    using Microsoft.Azure.Functions.Extensions.DependencyInjection;
    using Microsoft.Extensions.Configuration;
    using Microsoft.Extensions.DependencyInjection;

    [assembly: FunctionsStartup(typeof(Startup))]
    public class Startup : FunctionsStartup
    {
      public override void Configure(IFunctionsHostBuilder builder)
      {
        builder.Services.AddOptions<AppSettings>().Configure<IConfiguration>((settings, configuration) => configuration.GetSection("AppSettings").Bind(settings));
      }
    }
  • Create a simple function to consume the settings:
    using Microsoft.AspNetCore.Http;
    using Microsoft.Azure.WebJobs;
    using Microsoft.Azure.WebJobs.Extensions.Http;

    public class TestFunction
    {
      AppSettings _settings;

      public TestFunction(IOptions<AppSettings> settings)
      {
        _settings = settings.Value;
      }

      [FunctionName("test")]
      public string Test([HttpTrigger(AuthorizationLevel.Anonymous, "get")] HttpRequest req)
      {
        return _settings.DummySetting;
      }
    }
  • Run the function host locally and call its /api/test endpoint.

Expected behavior

The function should return the "foobar" value assigned to DummySetting in appsettings.json.

Actual behavior

When the function is called, _settings.DummySetting is null.

Place a breakpoint on the configuration callback in the Configure method, and observe that configuration has no JSON configuration loaded, and that its FileProvider is looking at the wrong directory.

image

Known workarounds

The closest thing I have found to a work-around is to obtain a reference to the ScriptApplicationHostOptions object used to initialize the parent ScriptHost. I haven't located a "proper" way to do this, so my "workaround" involves using reflection to extract the value of a private member (in this case, the _hostOptions member of the HostJsonFileConfigurationProvider configuration provider). This then provides a ScriptPath value that can be used to override the FileProvider for a ConfigurationBuilder and thereby explicitly load the correct appsettings.json file. I then use this IConfiguration object instead of the registered one when resolving IOptions<AppSettings>:

    var appSettingsConfig = new ConfigurationBuilder()
      .SetFileProvider(new PhysicalFileProvider(scriptApplicationHostOptions.ScriptPath))
      .AddJsonFile("appsettings.json")
      .Build();
    
    builder.Services.AddOptions<SlackSettings>().Configure<IConfiguration>(
      (settings, configuration) => appSettingsConfig.GetSection("AppSettings").Bind(settings));
    //                             ^^^^^^^^^^^^^^^^^ NB: I am ignoring the `configuration` supplied by the callback.

This is an extremely poor "workaround", owing to the dependency on a private member.

@espray
Copy link

espray commented Jun 1, 2019

See #4464 and Azure/azure-functions-core-tools#122
I believe this is by design

@ahmelsayed can probably give more background.

You can use this to find the correct root

var actual_root = Environment.GetEnvironmentVariable("AzureWebJobsScriptRoot")  // local_root
                    ?? (Environment.GetEnvironmentVariable("HOME") == null
                        ? Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)
                        : $"{Environment.GetEnvironmentVariable("HOME")}/site/wwwroot"); // azure_root

@fabiocav
Copy link
Member

fabiocav commented Jun 3, 2019

@espray is correct. The host sets up the configuration provider at the runtime location, to read its configuration, which differs from the function root. The approach mentioned above should work in the meantime, but we do have plans to support application level configuration files out of the box, so this setup wouldn't be required.

@logiclrd
Copy link
Author

logiclrd commented Jun 4, 2019

Thanks :-)

@KalyanChanumolu-MSFT
Copy link

KalyanChanumolu-MSFT commented Jun 18, 2019

@fabiocav So what does that mean?
How do we read configuration from host.json/local.settings.json?
When can we expect a Visual Studio project template with a proper startup.cs and all the plumbing put together?

@espray
Copy link

espray commented Jun 18, 2019

@KalyanChanumolu-MSFT
I would recomend following #4577 for that information

@KalyanChanumolu-MSFT
Copy link

#4464

@ghost ghost locked as resolved and limited conversation to collaborators Dec 31, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants