Created
July 14, 2022 19:19
-
-
Save asimmon/9d0563d9b421ede6215e05cd0cf50435 to your computer and use it in GitHub Desktop.
Azure App Configuration with .NET console app
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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