Feature Flags for ASP.Net Core Applications: Implementing Custom Feature Filters
Feature Flags for ASP.Net Core Applications Series
- Introduction to using Microsoft.FeatureManagement Library
- Combining Multiple Feature Flags to Control Feature Exposure
- Using Complex Feature Flags with Feature Filters
- Implementing Custom Feature Filters (This Article)
- Handling Action Disabled by Feature Flags
- Using Azure App Configuration for Feature Management
- Advanced Uses of Azure App Configuration for Feature Management
In this article we will look at how we can create a custom Feature Filter. In the previous article we learned about the useful built-in feature filters. We can provide an implementation for the IFeatureFilter
interface and register it during service registration, and we can use it the same way we use a built in Feature Filter. Let’s look at how to do that.
Implementing a Feature Filter to Enable Features Depending on the Browser
In our Music Store application, we have the Suggested Albums feature behind a feature flag. But the feature flag we were using is a simple on/off toggle. But we want to expose the feature only to specific set of browsers. This functionality is not available as a built-in feature filter. So, it’s a good candidate for us to implement our own.
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Microsoft.FeatureManagement;
using System;
using System.Linq;
using System.Threading.Tasks;
namespace MusicStore.Web.FeatureManagement.Filters
{
[FilterAlias(FilterAlias)]
public class BrowserFilter : IFeatureFilter
{
private const string FilterAlias = "MusicStore.Browser";
private const string Chrome = "Chrome";
private const string Firefox = "Firefox";
private const string IE = "MSIE";
private readonly ILogger _logger;
private readonly IHttpContextAccessor _httpContextAccessor;
public BrowserFilter(IHttpContextAccessor httpContextAccessor, ILoggerFactory loggerFactory)
{
_httpContextAccessor = httpContextAccessor;
_logger = loggerFactory.CreateLogger<BrowserFilter>();
}
public Task<bool> EvaluateAsync(FeatureFilterEvaluationContext context)
{
var settings = context.Parameters.Get<BrowserFilterSettings>() ?? new BrowserFilterSettings();
if (settings.AllowedBrowsers.Any(browser => browser.Contains(Chrome, StringComparison.OrdinalIgnoreCase)) && IsBrowser(Chrome))
{
return Task.FromResult(true);
}
if (settings.AllowedBrowsers.Any(browser => browser.Contains(Firefox, StringComparison.OrdinalIgnoreCase)) && IsBrowser(Firefox))
{
return Task.FromResult(true);
}
if (settings.AllowedBrowsers.Any(browser => browser.Contains(IE, StringComparison.OrdinalIgnoreCase)) && IsBrowser(IE))
{
return Task.FromResult(true);
}
_logger.LogWarning($"The AllowedBrowsers list is empty or the current browser is not enabled for this feature");
return Task.FromResult(false);
}
private string GetUserAgent()
{
var userAgent = _httpContextAccessor.HttpContext.Request.Headers["User-Agent"];
return userAgent;
}
private bool IsBrowser(string browser)
{
var userAgent = GetUserAgent();
return userAgent != null && userAgent.Contains(browser, StringComparison.OrdinalIgnoreCase);
}
}
}
In the BrowserFilter
class we are implementing IFeatureFilter
interface. It has a single method we need to implement. EvaluateAsync()
method takes a FeatureFilterEvaluationContext
as a parameter and returns a Boolean where it states if the feature flag is enabled or disabled. To implement the BrowserFilter
we need access to the HttpContext
so we can get the User-Agent string for the browser. For that we are constructor injecting in the HttpContextAccessor
. This also makes a requirement to register HttpContextAccessor
in the DI container in Startup.cs.
We also inject the ILoggerFactory
to log any information we need. Also, we can use the FilterAlias
attribute and supply a string as a parameter, this string will act as the Name of the filter when we use it. Otherwise the name of the filter will be the name of the class without the Filter word. Meaning our filter name would be Browser
. I wanted to put in a custom name. So, I provided MusicStore.Browser
as the alias for the filter.
We can use the FeatureFilterEvaluationContext
to access the parameters we pass into the filter. For our BrowserFilter
we need a list of browser names that we intend to enable access for the feature. So, once we define the parameters in the Configuration
we can access that in the filter using the FeatureFilterEvaluationContext
and bind it to a sternly typed configuration class. In our case its BrowserFilterSettings
class which has a List<string>
that includes the browser names. Then we need to register the new filter like this.
namespace MusicStore.Web
{
public class Startup
{
...
public void ConfigureServices(IServiceCollection services)
{
...
// Register the required IHttpContextAccessor
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
// Configure Feature Management
services
.AddFeatureManagement()
.AddFeatureFilter<BrowserFilter>();
...
}
}
}
Once registered we can update the Suggestion.User
feature flag to use the MusicStore.Browser
feature filter and supply the browsers we want to support.
namespace MusicStore.Web
{
public class Startup
{
...
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseAzureAppConfiguration();
...
}
}
}
Here we have defined only Chrome and Firefox as the supported browsers. So, if you run the application on for example Internet Explorer the Suggested Albums feature will not be visible.
MusicStore on Firefox - Suggested Albums are Visible
MusicStore on Internet Explorer 11 - Suggested Albums are Not Visible
Summary
Microsoft.FeatureManagement
library gives some useful feature filters out of the box. But you can easily implement IFeatureFilter
interface and create your own custom feature filters and register them. In the next article we’ll look at how to change the default behavior when a feature is disabled.
Tags:
You Might Also Like
← Previous Post
Next Post →