在FunctionsStartup中使用ConfigurationBuilder

时间:2019-08-05 14:04:17

标签: azure azure-functions

我具有Azure功能,并且我正在使用DI系统注册某些类型;例如:

public class Startup : FunctionsStartup
{
    public override void Configure(IFunctionsHostBuilder builder)
    {
        builder.Services
            .AddTransient<IMyInterface, MyClass>()
    . . . etc

但是,我也要从我的环境设置中注册一些数据。在函数本身内部,我可以获得ExecutionContext,所以我可以这样做:

IConfiguration config = new ConfigurationBuilder()
   .SetBasePath(context.FunctionAppDirectory)
   .AddJsonFile("local.settings.json", optional: true, reloadOnChange: true)
   .AddEnvironmentVariables()
   .Build();

但是,在FunctionsStartup中,我无权访问ExecutionContext。有没有一种方法可以从FunctionsStartup类中获取ExecutionContext,或者可以选择另一种方法来确定当前的运行目录,以便设置基本路径?

5 个答案:

答案 0 :(得分:1)

有一个可靠的方法可以做到这一点,在这里回答: Get root directory of Azure Function App v2

Function Apps使用环境变量作为获取配置的典型方法的事实是事实,但这并不是最佳的IMO。除了应该成为环境变量的项目之外,还可以获取appsettings.json文件。

通过DevOps任务设置的环境变量的数量:“ Azure App Service Deploy”选项“应用程序和配置设置”>“应用程序设置”完全失控。

这是我在撰写本文时的实现:

ExecutionContextOptions executionContextOptions = builder.Services.BuildServiceProvider().GetService<IOptions<ExecutionContextOptions>>().Value;

IConfigurationBuilder configurationBuilder = new ConfigurationBuilder()
    .SetBasePath(executionContextOptions.AppDirectory)
    .AddEnvironmentVariables()
    .AddJsonFile("appsettings.json", false)
    .AddJsonFile("local.settings.json", true)
    .AddUserSecrets(Assembly.GetExecutingAssembly(), true);

这使我可以利用发布过程变量对我的大部分配置进行特定于环境的“ JSON变量替换”,该配置位于结构良好的appsettings.json中,该设置被设置为Copy Always。注意,将appsettings.json的加载设置为非可选(错误设置),而我将本地设置和秘密设置为可选以适应本地开发。

然后可以将

appsettings.json格式化为类似格式的结构。释放正确命名的变量,例如如果将发行版设置为执行JSON变量替换,则“ MyConfig.Setting”将正确替换该值。

{
  "Environment": "dev",
  "APPINSIGHTS_INSTRUMENTATIONKEY": "<guid>",
  "MyConfig": {
    "Setting": "foo-bar-baz"
  }
}

而local.settings.json保留为平面样式:

{
  "IsEncrypted": false,
  "Values": {
    "AzureWebJobsStorage": "DefaultEndpointsProtocol=https;AccountName=<accountname>;AccountKey=<accountkey>;EndpointSuffix=core.windows.net",
    "Strickland:Propane:Config": "Dammit, Bobby!"
  }
}

此外,我在发布过程中为Azure KeyVault引用设置了一些应用程序设置(env vars),以及Azure Function运行时正确启动并与应用程序见解实时指标正常通信所需的最低设置。 / p>

希望这可以帮助像我一样讨厌讨厌-Variable.Name应用程序设置中的“ $(ReleaseVariableName)”项的用户。 :)

答案 1 :(得分:1)

虽然对这个问题的正确回答是正确的,但我认为它对为什么还缺乏深度。您应该知道的第一件事是,Azure功能在幕后使用与ASP.NET Core应用程序中相同的 ConfigurationBuilder ,但具有一组不同的提供程序。与ASP.NET Core的文档非常详细(https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/)不同,Azure Functions并非如此。

要了解这组提供程序,可以将以下代码行放在 FunctionStartup 类的 Configure(IFunctionsHostBuilder构建器)方法中,

var configuration = builder.Services.BuildServiceProvider().GetService<IConfiguration>();

在命令后放置调试断点,以调试模式执行功能,并对配置变量进行快速监视(右键单击变量名称以选择快速监视...)。

下面是提供者的清单。

Microsoft.Extensions.Configuration.ChainedConfigurationProvider
MemoryConfigurationProvider
HostJsonFileConfigurationProvider
JsonConfigurationProvider for 'appsettings.json' (Optional)
EnvironmentVariablesConfigurationProvider
MemoryConfigurationProvider

ChainedConfigurationProvider 添加现有的 IConfiguration 作为源。在默认配置的情况下,添加主机配置并将其设置为应用程序配置的第一个来源。

第一个 MemoryConfigurationProvider 添加键/值 {[AzureWebJobsConfigurationSection,AzureFunctionsJobHost]} 。至少它是在开发环境中执行的。在撰写本文时,找不到关于此 MemoryConfigurationProvider AzureWebJobsConfigurationSection 的文档。

HostJsonFileConfigurationProvider 是另一个未提供文档的提供程序,但是在查看 host.json https://docs.microsoft.com/en-us/azure/azure-functions/functions-host-json)上的文档时,似乎是负责提取此元数据。

appsettings.json JsonConfigurationProvider 似乎与ASP.NET Core对 appsettings.json 的使用有着明显的关联。这是行不通的。经过一番调查,我发现 Source FileProvider Root 并未设置为文件所在的应用程序根文件夹,而是某个晦涩的AppData文件夹(C:\ Users%USERNANE%\ AppData \ Local \ AzureFunctionsTools \ Releases \ 3.15.0 \ cli_x64)。 去钓鱼。

EnvironmentVariablesConfigurationProvider 加载环境变量键值对。

第二个 MemoryConfigurationProvider 添加键/值 {[AzureFunctionsJobHost:logging:console:isEnabled,false]} 。至少它是在开发环境中执行的。同样,在撰写本文时,找不到关于此 MemoryConfigurationProvider AzureFunctionsJobHost 的文档。

现在需要指出的有趣一点是,在配置中的任何地方都没有提到 local.settings.json 。这是因为 local.settings.json 不在 ConfigurationBuilder 过程中。相反, local.settings.json 是Azure Functions核心工具的一部分,可让您在本地计算机(https://docs.microsoft.com/en-us/azure/azure-functions/functions-run-local)上开发和测试功能。 Azure Function核心工具仅关注特定部分和键/值,例如 IsEncrypted ConnectionString 部分,等等。文档(https://docs.microsoft.com/en-us/azure/azure-functions/functions-run-local#local-settings-file)。这些键/值发生的事情也很独特。例如,部分中的键/值作为变量插入到环境中。大多数开发人员甚至都没有注意到,Git默认将local.settings.json设置为忽略,这也使您有问题,如果您从开发环境中删除存储库,以仅在将来还原它。 ASP.NET Core已修复了应用程序机密(https://docs.microsoft.com/en-us/aspnet/core/security/app-secrets)的某些问题。

因此,如果我们按照原始问题中的建议使用 ConfigurationBuilder 创建自己的配置,会发生什么情况

IConfiguration config = new ConfigurationBuilder()
   .SetBasePath(context.FunctionAppDirectory)
   .AddJsonFile("local.settings.json", optional: true, reloadOnChange: true)
   .AddEnvironmentVariables()
   .Build();

还是使用其他答案之一中显示的示例?

ExecutionContextOptions executionContextOptions = builder.Services.BuildServiceProvider().GetService<IOptions<ExecutionContextOptions>>().Value;

IConfigurationBuilder configurationBuilder = new ConfigurationBuilder()
    .SetBasePath(executionContextOptions.AppDirectory)
    .AddEnvironmentVariables()
    .AddJsonFile("appsettings.json", false)
    .AddJsonFile("local.settings.json", true)
    .AddUserSecrets(Assembly.GetExecutingAssembly(), true);

以下只是两个示例中的几个问题。

  • 第二个示例的顺序错误,因为AddEnvironmentVariables应该排在最后。

  • 两个示例都没有提到需要以下代码行。 列表项

    builder.Services.AddSingleton<IConfiguration>(configurationBuilder.Build());

    没有这一行,配置仅存在于 FunctionStartup 类的 Configure(IFunctionsHostBuilder构建器)方法中。但是,用该行替换在后台隐藏的Azure Function构建的配置。这不一定是一件好事,因为您无法替换 HostJsonFileConfigurationProvider 之类的提供程序。

  • 读取 local.settings.json 文件( .AddJsonFile(“ appsettings.json”))不会将键/值对放在<将strong>值部分作为预期的单个键/值对进入配置,而是将其分组在部分下。换句话说,例如,如果您要访问 Values 中的 {[“ AzureWebJobsStorage”:“”]} ,则可以使用命令 configuration.GetValue(“值:AzureWebJobsStorage“)。问题是Azure希望通过键名“ AzureWebJobsStorage” 访问它。更有趣的是,由于local.settings.json从来都不是 ConfigurationBuilder 进程的一部分,因此这是多余的,因为Azure Functions核心工具已将这些值放入环境中。唯一要做的就是允许您访问未定义为 local.settings.json https://docs.microsoft.com/en-us/azure/azure-functions/functions-run-local#local-settings-file)一部分的节和键/值。但是,为什么要从不会复制到生产代码中的文件中提取配置值呢?

所有这些使我们找到了一种更好的方式来影响配置更改,而不会破坏Azure Function构建的默认配置,该默认配置将覆盖 FunctionStartup 中的 ConfigureAppConfiguration 方法>类(https://docs.microsoft.com/en-us/azure/azure-functions/functions-dotnet-dependency-injection#customizing-configuration-sources)。

以下示例通过添加用户机密使文档中提供的示例更进一步。

public override void ConfigureAppConfiguration(IFunctionsConfigurationBuilder builder)
{
    FunctionsHostBuilderContext context = builder.GetContext();

    builder.ConfigurationBuilder
        .SetBasePath(context.ApplicationRootPath)
        .AddJsonFile("appsettings.json", optional: true, reloadOnChange: false)
        .AddJsonFile($"appsettings.{context.EnvironmentName}.json", optional: true, reloadOnChange: false)
        .AddUserSecrets(Assembly.GetExecutingAssembly(), true, true)
        .AddEnvironmentVariables();
}

默认情况下,诸如 appsettings.json 之类的配置文件不会自动复制到Azure Function输出文件夹中。请务必查看文档(https://docs.microsoft.com/en-us/azure/azure-functions/functions-dotnet-dependency-injection#customizing-configuration-sources),以对您的 .csproj 文件进行修改。还要注意,由于该方法附加了现有提供程序的方式,因此必须始终以 .AddEnvironmentVariables()结尾。

答案 2 :(得分:0)

Azure函数(v2)中不需要任何配置对象。所有应用程序设置都将作为环境变量注入。因此,您只需做一个简单的Environment.GetEnvironmentVariable()

在本地运行时,以相同的方式读取local.settings.json

请参阅此处:https://docs.microsoft.com/en-us/sandbox/functions-recipes/environment-variables?tabs=csharp

答案 3 :(得分:0)

不幸的是,目前尚没有获取本地运行目录的标准方法。最好是ExecutionContext或类似的东西暴露出来。

在没有标准方法的情况下,我使用AzureWebJobsScriptRoot环境变量来获取当前的工作目录,但它仅在本地工作。在蔚蓝的环境中,我正在使用Environment.GetEnvironmentVariable("HOME")}\\site\\wwwroot

为此,我在此处发布了一个代码,以回应类似的问题: Azure Functions, how to have multiple .json config files

github issue也有类似的解决方案。

答案 4 :(得分:0)

您可能想要使用 GetContext().ApplicationRootPath,例如:

[assembly:FunctionsStartup(typeof(SampleFunction.FunctionsAppStartup))]

namespace SampleFunction
{
  public class FunctionsAppStartup : FunctionsStartup
  {
    public override void Configure(IFunctionsHostBuilder builder)
    {
      string appRootPath = builder.GetContext().ApplicationRootPath;

      // ...
    }
  }
}