Skip to content

Instantly share code, notes, and snippets.

@asimmon
Created July 14, 2022 19:19
Show Gist options
  • Save asimmon/9d0563d9b421ede6215e05cd0cf50435 to your computer and use it in GitHub Desktop.
Save asimmon/9d0563d9b421ede6215e05cd0cf50435 to your computer and use it in GitHub Desktop.
Azure App Configuration with .NET console app
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Configuration.AzureAppConfiguration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.FeatureManagement;
namespace HelloAppConfig;
// Nuget packages:
// <PackageReference Include="Microsoft.Extensions.Hosting" Version="6.0.0" />
// <PackageReference Include="Microsoft.Extensions.Configuration.AzureAppConfiguration" Version="5.0.0" />
// <PackageReference Include="Microsoft.FeatureManagement" Version="2.5.1" />
//
// Note that we use Microsoft.Extensions.Configuration.AzureAppConfiguration instead of Microsoft.Azure.AppConfiguration.AspNetCore
// because we're in a console app, not a web app
// appsettings.json:
// {
// "Azure": {
// "AppConfiguration": "<connectionstring>"
// }
// }
public static class Program
{
public static void Main(string[] args) => Host
.CreateDefaultBuilder(args)
.ConfigureAppConfiguration(builder =>
{
var config = builder.Build();
builder.AddAzureAppConfiguration(options =>
{
options.Connect(config["Azure:AppConfiguration"]);
options.UseFeatureFlags(featureFlagOptions =>
{
// Cache all feature flags for a long time as we rely on a sentinel key
// https://docs.microsoft.com/en-us/azure/azure-app-configuration/enable-dynamic-configuration-aspnet-core?tabs=core5x#add-a-sentinel-key
featureFlagOptions.CacheExpirationInterval = TimeSpan.FromHours(1);
});
options.ConfigureRefresh(refreshOptions =>
{
// When the sentinel key is changed, it reloads the whole configuration including the feature flags
refreshOptions.Register(key: "SentinelKey", refreshAll: true);
refreshOptions.SetCacheExpiration(TimeSpan.FromSeconds(5));
});
});
})
.ConfigureServices(services =>
{
services.AddAzureAppConfiguration();
services.AddFeatureManagement();
services.AddHostedService<MyService>();
})
.ConfigureLogging(builder =>
{
builder.ClearProviders();
builder.AddSimpleConsole(options =>
{
options.SingleLine = true;
options.TimestampFormat = "HH:mm:ss ";
});
})
.Build()
.Run();
}
public static class MyFeatureFlags
{
public const string FeatureA = "FeatureA";
public const string FeatureB = "FeatureB";
}
public sealed class MyService : BackgroundService
{
private readonly IHostApplicationLifetime _hostLifetime;
private readonly IFeatureManager _featureManager;
private readonly IConfigurationRefresher[] _configurationRefreshers;
private readonly ILogger<MyService> _logger;
public MyService(IHostApplicationLifetime hostLifetime, IFeatureManager featureManager, IConfigurationRefresherProvider configurationRefresherProvider, ILogger<MyService> logger)
{
this._hostLifetime = hostLifetime;
this._featureManager = featureManager;
this._configurationRefreshers = configurationRefresherProvider.Refreshers.ToArray();
this._logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
try
{
await this.ExecuteInternalAsync(stoppingToken);
}
catch (OperationCanceledException ex) when (ex.CancellationToken == stoppingToken)
{
// swallow exception as we're stopping the app
}
finally
{
this._hostLifetime.StopApplication();
}
}
private async Task ExecuteInternalAsync(CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
// This has to be done in a console application. If we were in an ASP.NET Core application, the nuget package
// "Microsoft.Azure.AppConfiguration.AspNetCore" would have done that for us automatically
await Task.WhenAll(this._configurationRefreshers.Select(x => x.TryRefreshAsync(cancellationToken)));
var isFeatureAEnabled = await this._featureManager.IsEnabledAsync(MyFeatureFlags.FeatureA);
var isFeatureBEnabled = await this._featureManager.IsEnabledAsync(MyFeatureFlags.FeatureB);
this._logger.LogInformation("Feature A is enabled: {FeatureAEnabled}. Feature B is enabled: {FeatureBEnabled}", isFeatureAEnabled, isFeatureBEnabled);
await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment