2

What I am trying to do: I am attempting to setup Azure App Configuration with a .Net 6 web API application with a sentinel key in Azure App Configuration, with the goal of being able to change keys in azure, and none of the keys will update in my apps until the sentinel value has changed. In theory, this should allow me to safely hot swap configs.

What my issue is: When I do this IOptionsSnapshot couldn't get the value from Azure App Configuration. I am getting values are null even for the first time. When we use IConfiguration I could get the values for the first time.

Documentation I am referencing: I referred the documents from docs.microsoft

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

For dynamic update there is no reference available for .Net 6 so I verified in .Net 5 and updated the code for .Net 6

My Environment: Using dot net 6 being run from Visual Studio Enterprise 2022, with the latest nuget package for Microsoft.Azure.AppConfiguration.AspNetCore

My Code : Program.cs File

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();

ReadingAzureAppConfigurationController File

 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"];
         }
     }
 }

AAConfiguration File

 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 File

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;
        }
    }
}

Used user-secrets for connecting to App Configuration from local machine.

  1. Create secret manager key for connecting to Azure App Configuration in local machine while development dotnet user-secrets init

  2. Configure connection string of Azure App configuration in secret manager.

dotnet user-secrets set ConnectionStrings:AppConfig "Use your app configure primary connection string"

  1. Add package from Nuget Microsoft.Azure.AppConfiguration.AspNetCore
Sreenivasan
  • 23
  • 1
  • 5

1 Answers1

2

enter image description here

Pls refer to my code sample, it worked in my side. I created a new .net 6 api project. After running the app, you can change the value in azure portal fot testing the refresh.

enter image description here

Add connection string to appsetting.json:

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

My program.cs, pls don't forget to add service and middleware, the middleware is used to monitors the 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();

My test 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;
        }

    }
}
Tiny Wang
  • 10,423
  • 1
  • 11
  • 29
  • Thanks Tiny .. based on your Inputs I made two changes with that I could get the values from Azure App configuration for the first time ,but it couldn't get the modified value. Changes What I Made : 1. In azure app configuration I created the key as AAConfiguration:Message and AAConfiguration.Sentinel. – Sreenivasan Feb 23 '22 at 08:44
  • 2. Then in the extension class I read the configuration from app configuration [I included the extension method also in the question section code ] . But as I said these changes couldn't refresh the configuration if any changes happening in Sentinel value in azure App configuration.Then I made changes in the Program.cs file in the region "Code start for connecting the Azure App Configuration" as you gave, but that also couldn't get the refreshed value from azure app configuration. – Sreenivasan Feb 23 '22 at 08:52
  • At first I can get the value from azure app configuration but can't get the refreshed value if I edit the Sentinel key's value. Then I found it results from missing middleware in Program.cs, then I added `builder.Services.AddAzureAppConfiguration();` and `app.UseAzureAppConfiguration();` . And pls note all my codes are referred from the documents you provided in the question, so the keys added in azure are the same as well. – Tiny Wang Feb 24 '22 at 01:28
  • My idea on troubleshooting is make sure you can read all the value of the keys first, then adding the middleware to troubleshoot the refreshing value. @Sreenivasan – Tiny Wang Feb 24 '22 at 01:29
  • 2
    Thanks @Tiny after including the middleware it started working. Updated the Program.cs file. – Sreenivasan Feb 24 '22 at 16:56
  • 1
    @Sreenivasan If you have the working code can you send me a link or code snippet that I can refer to... I am struggling with the same problem. – Dutt93 Mar 21 '22 at 06:44