使用 .Net 6 在 .Net 核心 Web 应用程序中动态读取 Azure 应用程序配置

Dynamically Reading Azure App Configuration in .Net core Web App using .Net 6

我正在尝试做的事情:我正在尝试使用 .Net 6 web API 应用程序设置 Azure 应用程序配置,并在 Azure 应用程序配置中使用哨兵密钥,目标是能够在 azure 中更改密钥,并且 none 的密钥将在我的应用程序中更新,直到标记值发生更改。理论上,这应该允许我安全地热插拔配置。

我的问题是:当我执行此操作时,IOptionsSnapshot 无法从 Azure 应用程序配置中获取值。我得到的值是空的,即使是第一次。 当我们使用 IConfiguration 时,我第一次可以获得值。

我引用的文档: 我引用了来自 docs.microsoft

的文档
  1. https://docs.microsoft.com/en-us/azure/azure-app-configuration/quickstart-aspnet-core-app?tabs=core6x
  2. https://docs.microsoft.com/en-us/azure/azure-app-configuration/enable-dynamic-configuration-aspnet-core?tabs=core5x

对于动态更新,没有可用于 .Net 6 的参考,所以我在 .Net 5 中验证并更新了 .Net 6 的代码

我的环境: 使用来自 Visual Studio Enterprise 2022 的 运行 的 dot net 6,以及 Microsoft.Azure.AppConfiguration.AspNetCore[= 的最新 nuget 包17=]

我的代码: Program.cs 文件

using ReadingConfiguration.Model;

var builder = WebApplication.CreateBuilder(args);


#region Start reading AppSettings.Json file
//Reading appsettings.json Configuration file using
builder.Services.Configure<MySettingsConfiguration>(
    builder.Configuration.GetSection("MySettings"));
builder.Services.AddConfig(builder.Configuration);
#endregion
// Add services to the container.

#region Code start for connecting the Azure App Configuration
// Using to connect the azure App configuration
var connectionString = builder.Configuration.GetConnectionString("AppConfig");
builder.Host.ConfigureAppConfiguration((hostingContext, config) =>
{
    var settings = config.Build();
    config.AddAzureAppConfiguration(option =>
    {
        option.Connect(connectionString).ConfigureRefresh(refresh =>
        {
            refresh.Register("AAConfiguration:Sentinel", refreshAll:true).SetCacheExpiration(new TimeSpan(0, 0, 30));
        });
    });
})
.ConfigureServices(service =>
{    service.AddControllers();   
});
//Middleware for refreshing the azure App configuration
builder.Services.Configure<AAConfiguration>(builder.Configuration.GetSection("AAConfiguration"));
builder.Services.AddAzureAppConfiguration();
builder.Services.AddControllers();
#endregion

// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

// Configure the HTTP request pipeline.
// If statement can be removed if we need the swagger only in development
// if (app.Environment.IsDevelopment())
// {
    app.UseSwagger();
    app.UseSwaggerUI();
// }
//Middleware for refreshing the azure App configuration
app.UseAzureAppConfiguration();
app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

正在读取 AzureAppConfigurationController 文件

 using Microsoft.AspNetCore.Http;
 using Microsoft.AspNetCore.Mvc;
 using Microsoft.Extensions.Configuration;
 using Microsoft.Extensions.Options;
 using ReadingConfiguration.Model;
    
 namespace ReadingConfiguration
 {
     [Route("api/[controller]")]
     [ApiController]
     public class ReadingAzureAppConfigurationController : ControllerBase
     {
         private readonly AAConfiguration _aaConfiguration;
         private readonly IConfiguration _configuration; 
    
         public ReadingAzureAppConfigurationController(IOptionsSnapshot<AAConfiguration> optionsSnapshot,IConfiguration configuration)
         {
             _aaConfiguration = optionsSnapshot.Value;
             _configuration = configuration;
         }
    
         [HttpGet]
         public string ReadingDynamicAzureAppConfiguration()
         {
             return _aaConfiguration.Message;
         }
    
         [HttpGet]
         [Route("ReadingAppConfig")]
         public string ReadingAzureAppConfiguration()
         {
             return _configuration["Message"];
         }
     }
 }

AA配置文件

 namespace ReadingConfiguration.Model
 {
     public class AAConfiguration
     {
         public string? Message { get; set; }
         public int Sentinel { get; set; }
     }
 }

Appsettings.Json

{
   "Logging": {
     "LogLevel": {
       "Default": "Information",
       "Microsoft.AspNetCore": "Warning"
     }
   },
   "MySettings": {
     "Log": true,
     "ConnectionStringId": "Default",
     "Parameters": {
       "IsProduction": false
     }
   },
   "Trading": {
     "ChartType": "Monthly",
     "Function": "Pivot",
     "RSI": true
   },
   "Car": {
     "Manufacturer": "Fiat",
     "Model": "Punto",
     "Year": 2013
   },
   "AllowedHosts": "*"
 }

MyConfigServiceCollectionExtensions 文件

using Microsoft.Extensions.Configuration;
using ReadingConfiguration.Model;

namespace Microsoft.Extensions.DependencyInjection
{
    public static class MyConfigServiceCollectionExtensions
    {
        public static IServiceCollection AddConfig(this IServiceCollection services, IConfiguration configuration)
        {
            #region This will read the configuration from appsettings.json
            services.Configure<TradingConfiguration>(
                configuration.GetSection("Trading")
                );
            services.Configure<CarConfiguration>(
                configuration.GetSection("Car")
                );
            #endregion
            // This will read the configuration azure app configuration
            services.Configure<AAConfiguration>(
                configuration.GetSection("AAConfiguration")
                );
            return services;
        }
    }
}

使用用户机密从本地计算机连接到应用程序配置。

  1. 创建秘密管理器密钥以在开发时连接到本地计算机中的 Azure 应用程序配置 dotnet 用户机密 init

  2. 在 Secret Manager 中配置 Azure App 配置的连接字符串。

dotnet user-secrets set ConnectionStrings:AppConfig“使用您的应用配置主连接字符串”

  1. 从 Nuget 添加包 Microsoft.Azure.AppConfiguration.AspNetCore

请参考我的代码示例,它在我身边工作。我创建了一个新的 .net 6 api 项目。 运行 应用程序后,您可以更改 azure 门户中的值以测试刷新。

将连接字符串添加到 appsetting.json:

"ConnectionStrings": {
    "AppConfig": "Endpoint=https://xxxx.azconfig.io;Id=yaFxxSgH;Secret=5MYxxxs="
  }

我的program.cs,请不要忘记添加服务和中间件,中间件用于监控sentinel key。

using WebApiNet6AzureAppConfig.Models;

var builder = WebApplication.CreateBuilder(args);

ConfigurationManager configuration = builder.Configuration;
var connectionString = builder.Configuration.GetConnectionString("AppConfig");
builder.Host.ConfigureAppConfiguration((hostingContext, config) =>
{
    var settings = config.Build();
    config.AddAzureAppConfiguration(options =>
    {
        options.Connect(connectionString)
               .ConfigureRefresh(refresh =>
               {
                   refresh.Register("TestApp:Settings:FontColor", refreshAll: true)
                                      .SetCacheExpiration(new TimeSpan(0, 0, 30));
               });
    });
}).ConfigureServices(services =>
    {
        services.AddControllers();
    });
builder.Services.Configure<AppSettings>(configuration.GetSection("TestApp:Settings"));
builder.Services.AddAzureAppConfiguration();

builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseAzureAppConfiguration();

app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();

我的测试api:

using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using WebApiNet6AzureAppConfig.Models;

namespace WebApiNet6AzureAppConfig.Controllers
{
    [ApiController]
    [Route("[controller]")]
    public class WeatherForecastController : ControllerBase
    {
        private readonly AppSettings _settings;

        public WeatherForecastController(IOptionsSnapshot<AppSettings> settings)
        {
            _settings = settings.Value;
        }

        [HttpGet(Name = "GetWeatherForecast")]
        public string Get()
        {
            var res = _settings.Message;
            return res;
        }

    }
}