-
Notifications
You must be signed in to change notification settings - Fork 463
Description
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 registerIOptions<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.
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.