Feature Flags for ASP.Net Core Applications: Implementing Custom Feature Filters

Feature Flags for ASP.Net Core Applications: Implementing Custom Feature Filters

Feature Flags for ASP.Net Core Applications Series

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.

1 MusicStore home page on Firefox

MusicStore on Firefox - Suggested Albums are Visible

2 MusicStore home page on IE

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.

You Might Also Like
Comments