.NET 6 进程内Function App部署Azure后无法读取应用设置值
我使用Visual Studio 2022创建了全新的.NET 6进程内(in-process)Function App,项目包含3个计时器(timer)函数。我定义了自定义配置类用于绑定配置项,在本地Visual Studio开发环境运行时功能完全正常。
问题出现在应用部署到Azure之后:由于自定义配置类无法从Function App的应用程序设置中正确加载值,Startup类中的依赖注入(Dependency Injection)流程失败。
相关代码
自定义配置类
public class Settings { public ConnectionStrings ConnectionStrings { get; set; } public TwilioSettings TwilioSettings { get; set; } } public class TwilioSettings { public string AccountSid { get; set; } public string AuthToken { get; set; } public string From { get; set; } } public class ConnectionStrings { public string ZZZZZZZ { get; set; } }
本地local.settings.json配置
{ "IsEncrypted": false, "Logging": { "LogLevel": { "Default": "Information", "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information" } }, "Values": { "AzureWebJobsStorage": "UseDevelopmentStorage=true", "FUNCTIONS_WORKER_RUNTIME": "dotnet" }, "AzureWebJobsStorage": "UseDevelopmentStorage=true", "FUNCTIONS_WORKER_RUNTIME": "dotnet", "ConnectionStrings": { "ZZZZZZZ": "xxxxxxxxxxxxxxxxxxxxxxxx" }, "TwilioSettings": { "AccountSid": "xxxxxxxxxxxxxxxx", "AuthToken": "xxxxxxxxxxxxxxxxx", "From": "xxxxxxxxxxxxxxxxxxxxxxxxxxx" } }
现有Startup.cs配置绑定代码
public override void Configure(IFunctionsHostBuilder builder) { var settings = new Settings(); var basePath = $"{Directory.GetParent(Assembly.GetExecutingAssembly().Location).FullName}\\"; var jsonPath = $"{basePath.Substring(0, basePath.LastIndexOf('\\'))}\\local.settings.json"; var config = new ConfigurationBuilder() .SetBasePath(basePath) .AddJsonFile(jsonPath, true) .AddEnvironmentVariables() .Build(); config.Bind(settings); builder.Services.AddSingleton(settings); builder.Services.AddSingleton(settings.TwilioSettings); var cs = settings.ConnectionStrings.ZZZZZ; builder.Services.AddDbContextFactory<JTMContext>(opt => opt.UseSqlServer(cs)); builder.Services.AddDbContext<JTMContext>(opt => opt.UseSqlServer(cs)); }
Azure端配置
部署前已提前创建Azure Function App资源,在配置页的应用程序设置(Application Settings)和连接字符串(Connection Strings)中添加了所需键值对:
错误现象
从Visual Studio发布Function App后,应用初始状态正常,但任意计时器函数触发时会抛出如下错误:
Microsoft.Extensions.DependencyInjection.Abstractions: Value cannot be null. (Parameter 'implementationInstance').
错误出现在Startup.cs的Configure()方法注册自定义Settings单例的环节,排查发现手动实例化的IConfigurationRoot对象config没有加载Azure端的应用程序设置。
你现在手动new ConfigurationBuilder从零构建配置的写法,从根上就不符合Azure Functions进程内模型的配置加载规则,两个核心问题直接导致Azure上配置读不到:
- 本地能跑纯粹是因为你本地有local.settings.json文件,部署到Azure后生产环境根本不会带这个文件。而且你加的
AddEnvironmentVariables()也读不到Azure Function的应用设置——Functions的应用设置加载逻辑宿主已经做了特殊处理,不是简单读普通环境变量就能拿到的。 - 你自己写的路径拼接逻辑在Azure托管环境完全不生效,Azure Functions运行时的目录结构和本地调试的目录结构不一样,就算文件存在你也找不到路径。
正确做法是直接用Functions宿主已经构建完成的IConfiguration实例,完全没必要自己从头搭配置管道:
public override void Configure(IFunctionsHostBuilder builder) { // 直接拿宿主构建好的配置,已经自动加载了本地local.settings.json、Azure应用设置、连接字符串等所有配置源 var config = builder.GetContext().Configuration; var settings = new Settings(); config.Bind(settings); // 提前做空校验,比等DI注入时报null错误好排查 if (settings.ConnectionStrings == null || string.IsNullOrWhiteSpace(settings.ConnectionStrings.ZZZZZZZ)) { throw new InvalidOperationException("数据库连接字符串未正确配置"); } if (settings.TwilioSettings == null) { throw new InvalidOperationException("Twilio相关配置未正确加载"); } builder.Services.AddSingleton(settings); builder.Services.AddSingleton(settings.TwilioSettings); // 注意你原代码这里写的是ZZZZZ,和配置类定义的ZZZZZZZ键名不一致,这也是个会触发空引用的坑 var cs = settings.ConnectionStrings.ZZZZZZZ; builder.Services.AddDbContextFactory<JTMContext>(opt => opt.UseSqlServer(cs)); builder.Services.AddDbContext<JTMContext>(opt => opt.UseSqlServer(cs)); }
另外要注意Azure端配置嵌套选项的规则:Azure应用设置是平的键值对结构,不支持像json那样建嵌套节,配置层级用:分隔即可。比如Twilio相关配置不需要建TwilioSettings的嵌套节点,直接在应用设置里添加三个键:
TwilioSettings:AccountSid填对应AccountSid值TwilioSettings:AuthToken填对应AuthToken值TwilioSettings:From填对应发信号码值
连接字符串直接在Azure门户的「连接字符串」配置块里添加名称为ZZZZZZZ的项即可,宿主会自动将其绑定到ConnectionStrings:ZZZZZZZ配置路径,不需要额外处理。
原来代码里拼接路径、读取local.settings.json、手动初始化ConfigurationBuilder的逻辑全部删掉就行,宿主已经把所有配置源都加载好了,直接复用即可。
内容的提问来源于stack exchange,提问作者DFaucher

