Application configuration
.NET .NET Core ASP.NET Core Azure C# Dependency Injection DevOps PaaS Patterns Visual Studio

How to use Azure App Configuration Service Features within Web Apps

Welcome to today’s post.

In today’s post I will discuss how to apply the usage of an Azure App Configuration Features within an ASP.NET Core application.

In previous posts I showed how to use configuration key-value pairs setup within an Azure App Configuration service within an ASP.NET Core application and how to refresh remote configuration settings within the application when their values are altered.   

Before I show how to use Features, I will explain what a Feature is. A Feature is a part of an application that is enabled as part of a release. This might a release that is for enhancements, preview features, beta version. In each of these types of releases we might want to restrict the releases to a particular group of users (if the application has authenticated access), to a particular date range, or an application running metrics. When a feature or set of features is enabled, users evaluate them and provide feedback to the application owner. If a feature is considered as a successful enhancement to the application, then it is included in a future official release of the application. When the feature release is complete, the feature flag is switched off either locally or remotely.

In the context of DevOps pipelines, the use of features can be used as part of an environment release, where the application configuration features are filtered by the environment label in the remote app configuration settings.

Locally configured features can be defined in an appSettings.json file. With locally configured application settings, amending settings requires the application to be restarted. In addition, changes to local configuration settings require a change control on the settings file. With remotely configured settings, they can be controlled with the advantage of refreshing the configuration settings within a running application instance.

As I have shown in one of my previous posts, previously, remote settings defined within an Azure App Configuration Service can be managed and refreshed within a running application instance of an ASP.NET Core web application. In addition, Features can also be managed and refreshed within a running application instance of an ASP.NET Core web application.  

In the next section I will first give an overview on how to create Features within an Azure App Configuration Service.

Creation of Azure App Configuration Feature Flags in the Azure Portal

First, we select an existing Azure App Configuration resource. In the side menu of the overview of the resource we select the Feature manager menu as shown:

In the Feature Manager screen, you will see an empty grid which will contain any created feature flags. To create a new feature flag, we click on the Create button in the top left corner.

The Create Feature Flag dialog includes then following fields:

Enabled

Name

Key

Label

Description

Feature filter

Enter a name and description. The key is auto filled. The label is used in the same way as for the key-value pair configurations.

After applying changes, the new feature flag Enhancements shows in the Feature manager grid.

After creating our feature flag, we can access it from our web client application.

In the next section, I will show how to retrieve the feature flags from an ASP.NET Core web application.

Retrieval of Azure App Configuration Feature Flags within an ASP.NET Core Web Application

To use features within an existing ASP.NET Core web application, we can follow the guidelines in this section.

We will need to first add the following NuGet package to our project:

Microsoft.FeatureManagement.AspNetCore

I will now give an overview of how I use the feature flag, Enhancements to control visibility of a search feature in a web application.

In the Program.cs file, to ensure that the configuration settings for the Feature flags are included in the application’s remote key-value configurations are read in from the Azure App Configuration Service, we use the following extension method to enable this:

UseFeatureFlags()

The CreateHostBuilder() method to enable both Feature and non-Feature key-value pairs from the Azure App Configuration Service is implemented as shown:

public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
    .ConfigureWebHostDefaults(webBuilder =>
    {
        webBuilder.ConfigureAppConfiguration(config =>
        {
            IConfiguration settings = config.Build();
            string connectionString = 
            settings.GetValue<string>("AppSettings:AppConfigConnection");

            string environmentString = 
            settings.GetValue<string>("AppSettings:AppEnvironment");

            string appLocationString = settings.GetValue<string>("AppSettings:AppLocation");

            if (appLocationString == "Remote")
            {
                config.AddAzureAppConfiguration(options =>
                {
                    options.Connect(connectionString)
                        .Select("BookLoanApp:*", LabelFilter.Null)
                        .Select("BookLoanApp:*",environmentString)
                        .ConfigureRefresh(refreshOptions =>
                            refreshOptions.Register(
                                "BookLoanApp:Settings:Sentinel", 
                                refreshAll: true
                            )
                            .SetCacheExpiration(new System.TimeSpan(0, 5, 0)));
                    options.UseFeatureFlags();                                
                });
            }
        });
        webBuilder.UseStartup<Startup>();
});

The UseFeatureFlags() extension method call from the configuration provider by default selected all the Feature flags including those with or without labels.

One other option we can configure with UseFeatureFlags() extension method is to enable a cache expiration duration. The cache expiration duration for features allows an application to store the configuration value of the feature after retrieval from the remote configuration service, then make it visible to the application through the FeatureManager service (which I will explain later). 

Below is a cache expiration duration defined with as one minute interval:

options.UseFeatureFlags(featureOptions =>
    featureOptions.CacheExpirationInterval = TimeSpan.FromMinutes(1)
);                                

In the Startup class, we will need to add the feature management services to the service collection. We do this by adding the feature management extension service AddFeatureManagement() to the service collection. After we added AddAzureAppConfiguration() to the service collection and before adding the feature management extension, all configuration key-value pairs including the feature configuration key-value pairs were added to the configuration key-value pairs for the application.

Before we can add any Feature services to our application, we add the namespace as shown:

using Microsoft.FeatureManagement;

In the ConfigureServices() method we enable the FeatureManager service with AddFeatureManagement() as shown:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews();
    services.AddRazorPages().AddRazorRuntimeCompilation();

    if (Configuration.GetValue<string>("AppSettings:AppLocation") == "Remote")
        services.AddAzureAppConfiguration();

    services.AddFeatureManagement();

    services.Configure<AppConfiguration>(Configuration.GetSection("BookLoanApp:Settings"));
}

There is no need to add any additional middleware through the Configure() method. Only the UseAzureAppConfiguration() middleware for the remote configuration service is sufficient.

In our model for the Index view, I include an additional property for the enhancement flag Boolean switch as shown:

namespace BookLoanWebApp.Models
{
    public class IndexModel
    {
        public string HeadingColour { get; set; }
        public string EnvironmentName { get; set; }
        public string WelcomeMessage { get; set; }
        public string HeadingMessage { get; set; }
        public bool AreEnhancementsEnabled { get; set; }
    }
}

In the Home controller, we inject the FeatureManager service through the constructor as shown:

using Microsoft.FeatureManagement;
…
public class HomeController : Controller
{
    private readonly ILogger<HomeController> _logger;
    private readonly IOptionsSnapshot<AppConfiguration> _appConfiguration;
    private readonly IFeatureManager _featureManager;

    public HomeController(ILogger<HomeController> logger, 
        IOptionsSnapshot<AppConfiguration> options,
        IFeatureManager featureManager)
    {
        _logger = logger;
        _appConfiguration = options;
        _featureManager = featureManager;
    }
…

Within the Index() action we access the feature flag we created, Enhancements using IsEnabledAsync() from the IFeatureManager service as shown:

public IActionResult Index()
{
    IndexModel indexModel = new IndexModel() {
        HeadingColour = _appConfiguration.Value.HeadingColour == null ? 
            "" : _appConfiguration.Value.HeadingColour,
        EnvironmentName = _appConfiguration.Value.EnvironmentName == null ?  
            "" : "Current Environment is " + 
            _appConfiguration.Value.EnvironmentName,
        HeadingMessage = _appConfiguration.Value.LandingPageHeading == null ? 
            "Welcome Reader!" : _appConfiguration.Value.LandingPageHeading, 
        WelcomeMessage = _appConfiguration.Value.LandingPageMessage == null ? 
            "Welcome to our collection of Books!" : _appConfiguration.Value.LandingPageMessage,
        AreEnhancementsEnabled = _featureManager.IsEnabledAsync("Enhancements")
            .GetAwaiter().GetResult();
    };
    return View(indexModel);
}

In the Index markup, we add a condition to control visibility of the Search enhancement:

@model IndexModel;

@{
    ViewData["Title"] = "Landing Page!!";
}

<div class="text-center">
    <h1 class="display-4">@Model.HeadingMessage</h1>
    <p>@Model.WelcomeMessage</p>
    @if (Model.EnvironmentName.Length > 0)
    {
        <p>@Model.EnvironmentName</p>
    }
</div>

<br />

@if (Model.HeadingColour == "Blue")
{
    <h2 style="color:blue">Main Menu</h2>
}
else
@if (Model.HeadingColour == "Green")
{
    <h2 style="color:green">Main Menu</h2>
}
else
@if (Model.HeadingColour == "Red")
{
    <h2 style="color:red">Main Menu</h2>
}
else
{
    <h2>Main Menu</h2>
}

<br />

<div>
    @Html.ActionLink("View Books", "List", "Book", null, new { id="viewBookLink" })
</div>

@if (Model.AreEnhancementsEnabled)
{
    <div>
        @Html.ActionLink("Search Books", "Search", "Book", null, new { id="searchBookLink" })
    </div>
}

When we run the application, with a breakpoint on the Configuration member of the Startup class, the configuration key-values, including the key-value for the feature flag loads with a False value as shown:

When we get to the landing page, we will see the options in the menu without the Search feature link, as it is not an enabled feature:

Next, we will enable the feature flag within the Feature Explorer in the Azure Portal.

During the re-running of the application, a breakpoint on the Configuration member of the Startup class, the configuration key-values, including the key-value for the feature flag, set to a True value are visible as shown:

Within our controller, before loading the Index view, we will notice the property in our Index model is this time is also set to True.

When we get to the landing page, we will see the options in the menu include the Search feature link, which is an enabled feature:

I will now show how to use refreshes of remotely altered feature flags within our application. The method of refreshing configurations remotely is to poll the changes.

In our controller, we declare a IFeatureManagerSnapshot member and inject the instance into the controller through the constructor as shown:

public class HomeController : Controller
{
    private readonly ILogger<HomeController> _logger;
    private readonly IOptionsSnapshot<AppConfiguration> _appConfiguration;
    private readonly IFeatureManagerSnapshot _featureManagerSnapshot;
    private readonly IFeatureManager _featureManager;

    public HomeController(ILogger<HomeController> logger, 
        IOptionsSnapshot<AppConfiguration> options,
        IFeatureManagerSnapshot featureManagerSnapshot, 
        IFeatureManager featureManager)
    {
        _logger = logger;
        _appConfiguration = options;
        _featureManager = featureManager;
        _featureManagerSnapshot = featureManagerSnapshot;
    }
    ..

Within the Index() action we access the feature flag we created, Enhancements using IsEnabledAsync() from the IFeatureManagerSnapShot service as shown:

public IActionResult Index()
{
    IndexModel indexModel = new IndexModel() {
        HeadingColour = _appConfiguration.Value.HeadingColour == null ?
            "" : _appConfiguration.Value.HeadingColour,
        EnvironmentName = _appConfiguration.Value.EnvironmentName == null ?
            "" : "Current Environment is " + _appConfiguration.Value.EnvironmentName,
        HeadingMessage = _appConfiguration.Value.LandingPageHeading == null ?
            "Welcome Reader!" : _appConfiguration.Value.LandingPageHeading, 
        WelcomeMessage = _appConfiguration.Value.LandingPageMessage == null ?
            "Welcome to our collection of Books!" : _appConfiguration.Value.LandingPageMessage,
 		AreEnhancementsEnabled = _featureManagerSnapshot
 				.IsEnabledAsync("Enhancements")
 				.GetAwaiter()
 				.GetResult()
 		};
 		return View(indexModel);
    }
}

Next, we re-run or re-start the application.

Return to the Feature Explorer in the Azure Portal and uncheck the Enabled property of the Enhancements feature flag.

As the refresh cache is set to one minute, you may have to wait until you refresh the application.

On hitting a breakpoint in the controller, you will see the property for the feature flag within the Index model set to false:

When we get to the landing page, we will see the options in the menu without the Search feature link, as the feature flag has refreshed with without being enabled:

We have learnt the following in relation to being able to use feature flags:

  • Creation, configuration, enabling, and disabling feature flags within an Azure App Configuration service.
  • Setup and configuration of an ASP.NET Core web application to retrieve feature flags.
  • Amending an ASP.NET Core web application to provide refreshing of feature flags.

That is all for today’s post.

I hope you have found this post useful and informative.

Social media & sharing icons powered by UltimatelySocial