dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
15.08k stars 4.69k forks source link

IOptionsMonitor doesn't work dinamically with Azure AppConfiguration #52485

Open jonnovaretti opened 3 years ago

jonnovaretti commented 3 years ago

I'm trying to use IOptionsMonitor for dinamic configuration with Azure AppConfiguration, the values are loaded correctly on starting the application. After started, when I update any settings on Azure App Configuration portal, the settings values keep the same.

I've already used App Configuration in a function and it works properly, but nowadays I'm using WebHostedService DotNetCore.

        public static void Main()
        {
            var host = CreateHostBuilder().Build();            
            host.Run();
        }

        public static IHostBuilder CreateHostBuilder()
        {
            var host = Host.CreateDefaultBuilder()
                 .ConfigureAppConfiguration((hostingContext, config) =>
                {
                    var configuration = config.Build();

                    config.AddAzureAppConfiguration(options =>
                    {
                        options.Connect(configuration["ConnectionStrings:AppConfiguration"])
                               .UseFeatureFlags()
                               .ConfigureRefresh(refresh =>
                               {
                                   refresh.Register("RetryPolicyConfig:IntervalSecs", true);
                                   refresh.Register("RetryPolicyConfig:RetryCount", true);
                                   refresh.SetCacheExpiration(new TimeSpan(30, 0, 0));
                               });

                        _refresher = options.GetRefresher();
                    });
                })
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                    webBuilder.ConfigureServices((w, s) => { s.AddSingleton(_refresher); });
                });

            return host;
        }

the IOptionsMonitor is inject in service class

        private readonly RetryPolicyConfig retryPolicyConfig;
        private readonly IConfigurationRefresher _refresher;

        public Service(IOptionsMonitor<RetryPolicyConfig> configuration, IConfigurationRefresher refresher)
        {
            this.configuration = configuration;
            retryPolicyConfig = configuration.CurrentValue;
        }

        public async Task Execute(Commmand command)
        {
            try
            {
                await _refresher.TryRefreshAsync();
                var retryPolicy = retryPolicyConfig.RetryCount;
                var secs = retryPolicyConfig.IntervalExponentialEachRetryInSecs; //nothing happens here after updating setting by AppConfiguration

Any idea?

dotnet-issue-labeler[bot] commented 3 years ago

I couldn't figure out the best area label to add to this issue. If you have write-permissions please help me learn by adding exactly one area label.

centreboard commented 3 years ago

What's the lifetime of your service? It's resolving the current configuration value when it's constructed rather than when it's executed. If you move retryPolicyConfig from a field to a variable does that help?

 public async Task Execute(Commmand command)
    {
        try
        {
            await _refresher.TryRefreshAsync();

            var retryPolicyConfig = this.configuration.CurrentValue;
            var retryPolicy = retryPolicyConfig.RetryCount;
            var secs = retryPolicyConfig.IntervalExponentialEachRetryInSecs;
        }
pinkfloydx33 commented 3 years ago

Are you actually updating the watched configuration settings? Configuration won't update at the client unless you explicitly update any of the "watched" settings and even then it can take a minimum of the refresh interval. You also need to set the updateAll parameter for it to trigger an update for everything and not just the Refresh triggers.

I use this mechanism and it works perfectly well so I can only imagine you are triggering the wrong settings or that labels are involved and you haven't set the LabelFilter in the refresher configuration.

BTW since you are using a WebHost, there's an Aspnet sibling package for that library which adds a Middleware that automatically does the refreshing for you on each request (so you don't need to track the refresher object yourself)

In any case I think this belongs in the Azure repos.

ghost commented 3 years ago

Tagging subscribers to this area: @maryamariyan See info in area-owners.md if you want to be subscribed.

Issue Details
I'm trying to use IOptionsMonitor for dinamic configuration with Azure AppConfiguration, the values are loaded correctly on starting the application. After started, when I update any settings on Azure App Configuration portal, the settings values keep the same. I've already used App Configuration in a function and it works properly, but nowadays I'm using WebHostedService DotNetCore. `public static void Main() { var host = CreateHostBuilder().Build(); host.Run(); } public static IHostBuilder CreateHostBuilder() { var host = Host.CreateDefaultBuilder() .ConfigureAppConfiguration((hostingContext, config) => { var configuration = config.Build(); config.AddAzureAppConfiguration(options => { options.Connect(configuration["ConnectionStrings:AppConfiguration"]) .UseFeatureFlags() .ConfigureRefresh(refresh => { refresh.Register("RetryPolicyConfig:IntervalSecs", true); refresh.Register("RetryPolicyConfig:RetryCount", true); refresh.SetCacheExpiration(new TimeSpan(30, 0, 0)); }); _refresher = options.GetRefresher(); }); }) .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); webBuilder.ConfigureServices((w, s) => { s.AddSingleton(_refresher); }); }); return host; } ` the IOptionsMonitor is inject in service class ` private readonly RetryPolicyConfig retryPolicyConfig; private readonly IConfigurationRefresher _refresher; public Service(IOptionsMonitor configuration, IConfigurationRefresher refresher) { this.configuration = configuration; retryPolicyConfig = configuration.CurrentValue; } public async Task Execute(Commmand command) { try { await _refresher.TryRefreshAsync(); var retryPolicy = retryPolicyConfig.RetryCount; var secs = retryPolicyConfig.IntervalExponentialEachRetryInSecs; //nothing happens here after updating setting by AppConfiguration` Any idea?
Author: jonnovaretti
Assignees: -
Labels: `area-Extensions-Options`, `untriaged`
Milestone: -
tommck commented 3 years ago

Wait.. seriously? This doesn't work? I was just wondering why it seemed like it wasn't and started googling. If this doesn't work, that's a major problem!

Do we at least have a workaround for this?

MRRQX commented 1 year ago

I was able to confirm the firing of IOptionsMontior.OnChange().

The 'gotcha' is you have to call IConfigurationRefresher.TryRefreshAsync() after the timespan passed to IConfigurationRefresher.ProcessPushNotification() has lapsed. The default value is a random time between 0-30 seconds.

_refresher.ProcessPushNotification(notification, TimeSpan.Zero);

await _refresher.TryRefreshAsync();
pinkfloydx33 commented 1 year ago

@MRRQX if this is a web app, the Middleware provided by this Azure maintained package will do that for you automatically, assuming you're receiving traffic