Approval Tests: Assert With Human Intelligence

In the previous article I described how the Approval Tests library can help reduce the amount of assert code that needs to be written. The second benefit of using Approval Tests is the ability to use innate human intelligence to decide if the result of the test is correct.

Imagine a scenario where you need to assert that a text-to-speech generator has generated the correct output. In this example the output could be a byte array representing a .WAV or .MP3 sound file. How would you write traditional asserts to test this output?

As another example, suppose you had to test code that applied a creative filter to an input photograph, this could be some sort of “make skin tones look nice” filter, the output in this case would be a modified image file. How would you assert that the output photo looked “nice”?

In cases like these using traditional asserts may be impossible or very time consuming to implement, there is no Assert.Speech(…) or Assert.LooksNice(…).

This is where the Approval Tests library offers great benefits. You could simply write Approvals.Verify(speechWavBytes); or Approvals.Verify(processedImageBytes); In the case of the sound file you could listen to it and decide if it sounds correct. In the case of the processed photo, you could look at it on screen and use human intelligence to decide if it “looks nice”.

Once you are happy you can approve the results and then in future tests runs if the output accidentally changes due to a bug the tests will fail.

If you want to see Approval Tests in action and learn more about how they can make your testing life easier check out my Approval Tests for .NET Pluralsight course which you can currently start watching for free today with a Pluralsight free trial.

SHARE:

Approval Tests: Write Tests More Quickly

Sometimes assert code in tests gets big and messy and complicated when the output we’re testing is complex.

Approval Tests is a library that can help simplify assert code. The library has other benefits/use cases which I’ll cover in future posts such as using human intelligence to judge if the output is correct; providing a safety net when refactoring legacy code that has no tests; and even testing view rendering.

In the following test code, notice the assert phase:

[Fact]
public void TraditionalAsserts()
{
    var lines = new List<string>
    {
        "Widget sales: 42",
        "Losses: 99",
        "Coffee overheads: 9,999,999"
    };

    var sut = new ReportGenerator(title: "Annual Report",
                                    footer: "Copyright 2020",
                                    lines);

    string report = sut.Generate();

    // Notice the complexity of the asserts below
    Assert.Equal("Annual Report", report.Split(Environment.NewLine)[0]);
    Assert.Equal("Widget sales: 42", report.Split(Environment.NewLine)[2]);
    Assert.Equal("Losses: 99", report.Split(Environment.NewLine)[3]);
    Assert.Equal("Coffee overheads: 9,999,999", report.Split(Environment.NewLine)[4]);
    Assert.Equal("Total lines: 3", report.Split(Environment.NewLine)[6]);
    Assert.Equal("Copyright 2020", report.Split(Environment.NewLine)[8]);
            

    // We could also have just asserted using a long expected string rather than individual line asserts
}

And for reference the ReportGenerator class looks like the following:

public class ReportGenerator
{
    public string Title { get; }
    public List<string> Lines { get; }
    public string Footer { get; }

    public ReportGenerator(string title, string footer, List<string> lines)
    {
        Title = title;
        Footer = footer;
        Lines = lines;
    }

    public string Generate()
    {
        var report = new StringBuilder();

        report.AppendLine(Title);
        report.AppendLine();

        foreach (var line in Lines)
        {
            report.AppendLine(line);
        }


        report.AppendLine();
        report.AppendLine($"Total lines: {Lines.Count}");

        report.AppendLine();
        report.AppendLine(Footer);

        return report.ToString();
    }
}

So in the test there are 6 lines of assert code:

Assert.Equal("Annual Report", report.Split(Environment.NewLine)[0]);
Assert.Equal("Widget sales: 42", report.Split(Environment.NewLine)[2]);
Assert.Equal("Losses: 99", report.Split(Environment.NewLine)[3]);
Assert.Equal("Coffee overheads: 9,999,999", report.Split(Environment.NewLine)[4]);
Assert.Equal("Total lines: 3", report.Split(Environment.NewLine)[6]);
Assert.Equal("Copyright 2020", report.Split(Environment.NewLine)[8]);

If the output was more complex or bigger (for example 100’s or 1000’s of lines of text) then the assert code would get even more messy and harder to maintain. Or what if the output was some binary representation such as an array of bytes representing a generated image or text to speech sound file?

It’s in these cases when dealing with complex output that Approval Tests can help to simplify the assert code as shown in the following test:

[Fact]
[UseReporter(typeof(DiffReporter))]
public void ApprovalTestsVersion()
{
    var lines = new List<string>
    {
        "Widget sales: 42",
        "Losses: 99",
        "Coffee overheads: 9,999,999"
    };

    var sut = new ReportGenerator(title: "Annual Report",
                                    footer: "Copyright 2020",
                                    lines);

    string report = sut.Generate();

    Approvals.Verify(report);
}

Notice in the preceding code the line: Approvals.Verify(report); This line calls Approval Tests and will create a new  “received” .txt file in the test project. You can examine this text file and if it is correct rename it to be an “approved” file. When the test runs in the future, Approval Tests will use the approved file (which should be added to source control) and if the generated report is the same then the test will pass, otherwise the test will fail and a new received file will be output. The [UseReporter] attribute lets you specify how to visualize approval failures, in this example by using a diff tool, and there’s a number of other reporters that come out of the box that you can use.

If you want to see Approval Tests in action and learn more about how they can make your testing life easier check out my Approval Tests for .NET Pluralsight course which you can currently start watching for free today with a Pluralsight free trial.

SHARE:

Simplify and Reduce Test Code with AutoFixture

AutoFixture is a library that you can use alongside your testing framework to reduce the amount of boilerplate test code you need to write and thus improve your productivity.

At its core, AutoFixture helps you setup your tests by generating anonymous test data for you. This anonymous test data can be used to fulfil non-important boilerplate test data; this is test data that is required for the test to execute but whose value is unimportant.

Take the follow abbreviated test:

[Fact]
public void ManualCreation()
{
    // arrange

    Customer customer = new Customer()
    {
        CustomerName = "Amrit"
    };

    Order order = new Order(customer)
    {
        Id = 42,
        OrderDate = DateTime.Now,
        Items =
                      {
                          new OrderItem
                          {
                              ProductName = "Rubber ducks",
                              Quantity = 2
                          }
                      }
    };


    // act and assert phases...
}

Suppose the previous test code was only creating an Order (with associated Customer) just to fulfil some dependency and the actual Customer and OrderItems did not matter. In this case we could use AutoFixture to generate them for us.

AutoFixture can be installed via NuGet and once installed allows a Fixture instance to be instantiated. This Fixture object can then be used to generate anonymous test data and greatly simplify the arrange phase, as the following test shows:

[Fact]
public void AutoCreation()
{
    // arrange

    var fixture = new Fixture();

    Order order = fixture.Create<Order>();

    // act and assert phases...
}

If we were to debug this test we’d see the following values:

AutoFixture anonymous test data generation for complex object graphs

Notice in the preceding screenshot that AutoFixture has created the object graph for us, including the Customer and 3 OrderItem instances.

There’s a lot more to AutoFixture than just this simple example, for example you can combine with the AutoFixture.Xunit2 package to further reduce code:

[Theory, AutoData]
public void SubtractWhenZeroTest(int aPositiveNumber, Calculator sut)
{
    // Act
    sut.Subtract(aPositiveNumber);

    // Assert
    Assert.True(sut.Value < 0);
}

If you want to learn more about how AutoFixture can improve your productivity check out the docs or start watching for free  my AutoFixture Pluralsight course with a free trial:

SHARE:

Running xUnit.net Tests on Specific Threads for WPF and Other UI Tests

Sometimes when you write a test with xUnit.net (or other testing frameworks) you may run into problems if UI technologies are involved. This usually relates to the fact that the test must execute using a specific threading model such as single-threaded apartment (STA).

For example suppose you had a WPF app that you wanted to add tests for.

The XAML looks like:

<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp1"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <TextBlock FontSize="42" Text="{Binding Path=Greeting}" />
    </Grid>
</Window>

And the simple quick and dirty view model class looks like:

namespace WpfApp1
{
    public class MainWindowViewModel
    {
        public string Greeting { get; set; }
    }
}

And  in the MainWindow constructor we set the data context:

public MainWindow()
{
    InitializeComponent();

    var vm = new MainWindowViewModel { Greeting = "Hi there!" };
    DataContext = vm;
}

(This is a very simple demo code with no change notifications etc.)

If you wanted to write an xUnit.net test that instantiates an instance of MainWindow, such as:

[Fact]
[UseReporter(typeof(DiffReporter))]
public void RenderWithViewModel()
{
    var sut = new MainWindow();
    var vm = new MainWindowViewModel { Greeting = "Good day!" };
    sut.DataContext = vm;

    // Test rendering, e.g. using Approval Tests
    WpfApprovals.Verify(sut);
}

If you run this, the test will fail with: System.InvalidOperationException : The calling thread must be STA, because many UI components require this.

Note: this test is using Approval Tests (e.g. [UseReporter(typeof(DiffReporter))]) to render the UI into an image file for approval, you can learn more about Approval Tests with my Pluralsight course. Approval Tests is no related to the threading model requirements.

To enable this test to run you need to instruct xUnit to run the test using an apartment model process (“STA thread”).

Luckily Andrew Arnott has done all the hard work for us and created some custom xUnit.net attributes that allow us to specify what thread/synchronization context to use for a test.

Once the Xunit.StaFact NuGet package has been installed into the test project you can replace the standard [Fact] attribute with [StaFact]. The test will now execute without error:

using ApprovalTests.Reporters;
using ApprovalTests.Wpf;
using Xunit;

namespace WpfApp1.Tests
{
    public class MainWindowShould
    {
        [StaFact]
        [UseReporter(typeof(DiffReporter))]
        public void RenderWithViewModel()
        {
            var sut = new MainWindow();
            var vm = new MainWindowViewModel { Greeting = "Good day!" };
            sut.DataContext = vm;

            // Test rendering, e.g. using Approval Tests
            WpfApprovals.Verify(sut);
        }
    }
}

There are also a number of other attributes such as [WinFormsFact] for use with Windows Forms apps, check out the entire list of attributes in the docs.

If you use this library make sure to say a thankyou to Andrew on Twitter  :)

Also check out my xUnit.net Pluralsight training course or get started watching with a free trial.

SHARE:

Configuring Custom Feature Filters with Azure App Configuration (Microsoft.FeatureManagement)

This is part ten in a series of articles.

EDIT: my Feature Management Pluralsight training course is now available.

In part 4 we looked at creating custom feature filters and in part 5 we looked at configuring features with Azure App Configuration. We can combine these techniques to create a custom feature filter that we can configure remotely in Azure.

For example suppose we have the following class representing a custom feature filter that  enables a feature if a query string field is present:

[FilterAlias("BetaQueryString")]
public class BetaQueryStringFeatureFilter : IFeatureFilter
{
    private readonly IHttpContextAccessor _httpContextAccessor;
    
    public BetaQueryStringFeatureFilter(IHttpContextAccessor httpContextAccessor)
    {
        _httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor));
    }

    public Task<bool> EvaluateAsync(FeatureFilterEvaluationContext context)
    {
        BetaQueryStringFeatureFilterSettings settings = context.Parameters.Get<BetaQueryStringFeatureFilterSettings>();           

        bool isEnabled = _httpContextAccessor.HttpContext.Request.Query.ContainsKey(settings.QueryStringFieldName);

        return Task.FromResult(isEnabled);
    }
}

The configurable parameters for this feature filter are represented by the following class:

public class BetaQueryStringFeatureFilterSettings
{
    public string QueryStringFieldName { get; set; }
}

In the appsettings.json we could configure a feature called “printing” to use this custom feature filter:

"FeatureManagement": {
  "Printing": {
    "EnabledFor": [
      {
        "Name": "BetaQueryString",
        "Parameters": {
          "QueryStringFieldName": "beta"
        }
      }
    ]
  }
}

Notice in the preceding config that the query string field that needs to be present in the request URL is the string “beta”. This means if the URL was something like “”http://localhost:5607/?beta” the printing feature would be enabled.

If we wanted to enable the filter when the URL query string contained a field called “earlyaccess” we could change the appsettings.config to:

"FeatureManagement": {
  "Printing": {
    "EnabledFor": [
      {
        "Name": "BetaQueryString",
        "Parameters": {
          "QueryStringFieldName": "earlyaccess"
        }
      }
    ]
  }
}

Configuring Custom Feature Filters with Azure App Config

Instead of having the query string field defined in the appsettings.config we could instead hold this value in Azure. Check out part 5 for more info on setting this up.

After configuring the web app to use Azure App Configuration for feature flag settings, we can modify the appsettings.json to remove the QueryStringFieldName parameter because this will now be coming from Azure:

"FeatureManagement": {
  "Printing": {
    "EnabledFor": [
      {
        "Name": "BetaQueryString"          
      }
    ]
  }
}

Setting up a custom/conditional feature filter in Azure App Configuration is a little unintuitive at the moment. After clicking Add and specifying the feature name (in this case “Printing”) you then need to click the On toggle and then click the  Add filter button.

Adding a new feature flag in Azure App Configuration

Next, enter a key that matches the name of the custom feature filter, in this case “BetaQueryString” and then click the ellipses and choose Edit parameters:

Configuring a custom feature filter in Azure App Configuration

 

The name of the parameter should match what settings value the custom feature filter is looking for, in this case “QueryStringFieldName” and in the Value box enter the configured value you want, for example “beta” :

image

Click Apply and then Apply again and you should now see the Printing feature marked as conditional:

image

Now you can run the web app and remotely configure what query string parameter will enable the printing feature.

Be sure to check out my Microsoft Feature Management Pluralsight course get started watching with a free trial.

SHARE:

Gradually Rollout New Features with Targeting Feature Flags (Microsoft.FeatureManagement)

This is part nine in a series of articles.

EDIT: my Feature Management Pluralsight training course is now available.

One of the feature filters that Microsoft Provides is the targeting feature filter. This allows you to gradually rollout a feature to make sure it’s working for a small subset of users before rolling it out to everyone. This approach can help to find bugs that might have slipped into the feature during development. By not just turning on the feature for all users, you can shield them from any problems, only a few users will see the error.

At first glance the targeting feature filter might seem a bit confusing but essentially it allows you to expose the new feature to your “audience”. The “audience” are your users.

Your audience can be specified in 3 ways:

  1. By specific user(s)
  2. By which group(s) the current user belongs to
  3. By a percentage of all users, regardless of 1 & 2 above

For example, you could release a new feature and only have it enabled for 1 or 2 specific users (e.g. Sarah and Amrit). These users could be part of the development team that have accounts setup in the production system. Sarah and Amrit will see the new feature and be able to check it is working, everyone else won’t see it.

Once Sarah and Amrit have used the new feature and are sure that there are no errors, the feature can be rolled out to a small subset of users. This subset is represented as a group. For example you could have a subset of users (that all belong to a group called “earlyadopters”) who have opted in to get access to “beta” features before other users. These users would be made aware at the time they opted in that they might experience some occasional errors. You could  enable the new feature for all “earlyadopters” or for a subset of them based on a percentage. For example you could start with 10% of early adopters and if there are no errors, gradually increase it to 100% of early adopters.

Once all early adopters are using the feature and there is confidence that the new feature is working correctly, the feature can be rolled out to all the other users. You could do this all at once, or once again start by rolling it out to 50% of them and gradually increase to 100%, or just go straight to 100% and enable for everyone.

Essentially, feature targeting gives you a great amount of control on how you roll out new features. Contrast this with just releasing a new feature to production with no feature flags and all users starting to use it at the same time. If there is a problem with the new feature, all users will will see it and be affected by it.

Using Feature Targeting

Create an ASP.NET Core 3.1 MVC app and create it with “individual user accounts” authentication. This will enable you to add users to local SQL database rather that relying on Windows auth for example.

The first thing to do is map users and groups in whatever authentication method is being used to users/groups in the Microsoft Feature Management world. Specifically to create TargetingContext instances for users. A TargetingContext contains the user id and list of groups to which the user belongs. This information is used to position the current user in the audience that has been configured.

One way to build a TargetingContext is to get it from the current HTTP context by getting the HttpContext.User property.

To create TargetingContexts, you can create a class that implements the Microsoft.FeatureManagement.FeatureFilters.ITargetingContextAccessor interface. This interface has a method GetContextAsync inside which you create a TargetingContext for the current user:

using System;
using Microsoft.AspNetCore.Http;
using Microsoft.FeatureManagement.FeatureFilters;
using System.Collections.Generic;
using System.Security.Claims;
using System.Threading.Tasks;

namespace WebApplication2.Models
{
    /// <summary>
    /// Based on https://github.com/microsoft/FeatureManagement-Dotnet/blob/master/examples/FeatureFlagDemo/HttpContextTargetingContextAccessor.cs
    /// </summary>
    public class HttpTargetingContextAccessor : ITargetingContextAccessor
    {
        private const string CacheKey = "HttpContextTargetingContextAccessor.TargetingContext";
        private readonly IHttpContextAccessor _httpContextAccessor;

        public HttpTargetingContextAccessor(IHttpContextAccessor httpContextAccessor)
        {
            _httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor));
        }

        public ValueTask<TargetingContext> GetContextAsync()
        {
            HttpContext httpContext = _httpContextAccessor.HttpContext;

            if (ACachedTargetingContextExists())
            {
                return CachedTargetingContext();
            }

            ClaimsPrincipal user = httpContext.User;
            TargetingContext targetingContext = new TargetingContext
            {
                UserId = user.Identity.Name,
                Groups = GetGroupsFromClaims()
            };

            CacheTargetingContextForFutureLookups();

            return new ValueTask<TargetingContext>(targetingContext);

            // Local functions could be moved to class level functions
            bool ACachedTargetingContextExists() => httpContext.Items.ContainsKey(CacheKey);
           
            ValueTask<TargetingContext> CachedTargetingContext() =>  new ValueTask<TargetingContext>((TargetingContext)httpContext.Items[CacheKey]);
           
            IEnumerable<string> GetGroupsFromClaims()
            {                               
                // In this implementation groups/roles are specified using claims (ClaimTypes.Role)
                foreach (Claim claim in user.Claims)
                {
                    if (claim.Type == ClaimTypes.Role) 
                    {
                        yield return claim.Value;
                    }
                }
            }
          
            void CacheTargetingContextForFutureLookups() => httpContext.Items[CacheKey] = targetingContext;
        }
    }
}

In the preceding code, we are basically getting the user id of the current request and also any roles/groups to which that user belongs. This is in the form of a TargetingContext instance. This TargetingContext instance will be used by the targeting feature filter to decide whether or not to give the user access to the feature.

Configuring Targeting

To configure the targeting filter, you specify the users and/or groups, and the percentage of all users:

"FeatureManagement": {
  "Printing": {
    "EnabledFor": [
      {
        "Name": "Microsoft.Targeting",
        "Parameters": {
          "Audience": {
            "Users": [
              "Sarah",
              "Amrit"
            ],
            "Groups": [
              {
                "Name": "earlyadopters",
                "RolloutPercentage": 10
              }
            ],
            "DefaultRolloutPercentage": 0
          }
        }
      }
    ]
  }
}

In the preceding config, the Printing feature will be enabled for:

  • Sarah and Amrit
  • 10% of all users in the earlyadopters role/group
  • 0% of all users

Once the feature has been running in production without error this could be expanded to all the early adopters:

"FeatureManagement": {
  "Printing": {
    "EnabledFor": [
      {
        "Name": "Microsoft.Targeting",
        "Parameters": {
          "Audience": {
            "Users": [
              "Sarah",
              "Amrit"
            ],
            "Groups": [
              {
                "Name": "earlyadopters",
                "RolloutPercentage": 100
              }
            ],
            "DefaultRolloutPercentage": 0
          }
        }
      }
    ]
  }
}

And then at a later point, 50% of non early adopters (i.e. 50% of the user base):

"FeatureManagement": {
  "Printing": {
    "EnabledFor": [
      {
        "Name": "Microsoft.Targeting",
        "Parameters": {
          "Audience": {
            "Users": [
              "Sarah",
              "Amrit"
            ],
            "Groups": [
              {
                "Name": "earlyadopters",
                "RolloutPercentage": 100
              }
            ],
            "DefaultRolloutPercentage": 50
          }
        }
      }
    ]
  }
}

And then to everyone:

"FeatureManagement": {
  "Printing": {
    "EnabledFor": [
      {
        "Name": "Microsoft.Targeting",
        "Parameters": {
          "Audience": {
            "Users": [
              "Sarah",
              "Amrit"
            ],
            "Groups": [
              {
                "Name": "earlyadopters",
                "RolloutPercentage": 100
              }
            ],
            "DefaultRolloutPercentage": 100
          }
        }
      }
    ]
  }
}

Now once the feature has been used for enough time and is considered stable, in a future release the Printing feature flag and associated code can be removed from the app.

Be sure to check out my Microsoft Feature Management Pluralsight course get started watching with a free trial.

SHARE:

Maintaining Feature Flag Values Across Multiple Requests (Microsoft.FeatureManagement)

This is part eight in a series of articles.

EDIT: my Feature Management Pluralsight training course is now available.

In part six of this series, we saw how to prevent a feature flag from changing during processing of a single request.

In this article we’re going to look at how to maintain consistency across multiple requests for the same user/session.

The Problem

Consider the following scenario that introduces a feature flag MustCaptureAge. For example suppose new legal regulations require that from a certain date or time that the application must now capture the age of a customer for all purchases:

  1. New age functionality is deployed to production with MustCaptureAge flag set to false
  2. User 42 navigates to web app
  3. User 42 adds an item to their cart
  4. User 42 navigates to checkout
  5. At this point MustCaptureAge=false so no Age input is displayed
  6. User 42 starts typing information onto checkout page
  7. Whilst User 42 is typing, MustCaptureAge flags gets set to true
  8. User 42 clicks submit
  9. What happens now? Age was not captured…

In the preceding scenario the MustCaptureAge feature flag changed between requests. At the start it was set to false but at some point during the user’s interaction it got set to true. This type of scenario could cause unpredictable results, legal breaches, data corruption, missed sales, annoyed customers, etc.

One way to fix this is to maintain the feature flag value across all requests for a given session, essentially taking the initial value of the feature flag and caching it for all subsequent requests in that session.

Preserving Feature Flag Values Across Multiple ASP.NET Core Requests

The ISessionManager interface in the Microsoft.FeatureManagement namespace allows the implementer to store the state of feature flags for a session. This interface is not specific to ASP.NET and can be used in non web-applications. It also has no dependency on HTTP context. We can however implement a version that can be used in an ASP.NET Core web app.

The interface has 2 methods to get and set the value of a feature flag. Inside these methods we can make use of the current HTTP session context to store the feature flag value. Essentially the first time the feature flag is looked up for a session, its value will be persisted in session state for the remainder of the session, even across multiple pages/requests.

As an example, the following code shows a basic implementation that uses an IHttpContextAccessor to get access to the HttpContext of the current request and then store or retrieve the value in ASP.NET session state:

using Microsoft.AspNetCore.Http;
using Microsoft.FeatureManagement;
using System;
using System.Threading.Tasks;

namespace WebApplication1.Models
{
    /// <summary>
    /// Based on https://andrewlock.net/keeping-consistent-feature-flags-across-requests-adding-feature-flags-to-an-asp-net-core-app-part-5/
    /// </summary>
    public class HttpContextFeatureSessionManager : ISessionManager
    {
        private readonly IHttpContextAccessor _contextAccessor;
        private const string SessionKeyPrefix = "feature_flag_";
       
        public HttpContextFeatureSessionManager(IHttpContextAccessor contextAccessor)
        {
            _contextAccessor = contextAccessor;
        }

        public Task<bool?> GetAsync(string featureName)
        {
            bool keyExistsInHttpSession = _contextAccessor.HttpContext
                                                          .Session
                                                          .TryGetValue(key: $"{SessionKeyPrefix}{featureName}",
                                                                       value: out byte[] bytes);

            if (keyExistsInHttpSession)
            {
                return Task.FromResult((bool?)BitConverter.ToBoolean(bytes));
            }

            return Task.FromResult<bool?>(null);
        }

        public Task SetAsync(string featureName, bool enabled)
        {            
            _contextAccessor.HttpContext
                            .Session
                            .Set(key: $"{SessionKeyPrefix}{featureName}", 
                                 value: BitConverter.GetBytes(enabled));
            
            return Task.CompletedTask;
        }
    }
}

To plug the above HttpContextFeatureSessionManager into the ASP.NET Core app, modify the Startup.ConfigureServices method and add services.AddTransient<ISessionManager, HttpContextFeatureSessionManager>(); and to enable session state: services.AddSession(); You’ll also need to add app.UseSession(); in the Configure method:

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.FeatureManagement;
using WebApplication1.Models;

namespace WebApplication1
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddSession();
            services.AddTransient<ISessionManager, HttpContextFeatureSessionManager>();
            services.AddControllersWithViews();            
            services.AddHttpContextAccessor();
            services.AddFeatureManagement();
        }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            app.UseSession();

            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");
            }
            app.UseStaticFiles();

            app.UseRouting();

            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllerRoute(
                    name: "default",
                    pattern: "{controller=Home}/{action=Index}/{id?}");
            });            
        }
    }
}

In the appsettings.json, we can configure the MustCaptureAge feature:

"FeatureManagement": {
  "MustCaptureAge": true
}

And then for example use it in the UI:

<div class="text-center">
    <h1 class="display-4">Welcome</h1>
    <feature name="MustCaptureAge">
        <label for="age">Please enter your age</label>
        <input name="age" type="number"/>
    </feature>    
</div>

Now if we run the app, the page will load and show the age input because MustCaptureAge is set to true. However,  if we now modify the appsettings.json and change MustCaptureAge to “false” while the web app is still running,  and save the file, the existing session that’s open in the browser will still show the age input. If however we open another session (e.g. in another browser) the age input will not be shown.

There are however a number of problems with the HttpContextFeatureSessionManager implementation. We might want only some features to be preserved across requests but other features to be updated immediately regardless of if a session is currently underway.

One way to opt in, if using strongly typed feature names via an enum (see part two) is to define a custom attribute:

using System;

namespace WebApplication1.Models
{
    public sealed class PreserveFeatureAttribute : Attribute { }
}

And then decorate any enum value feature flags that you want to preserve across requests:

public enum Features
{
    Printing,
    [PreserveFeature]
    MustCaptureAge
}

In the preceding enum, the Printing feature will not be preserved for a session, but the MustCaptureAge feature will be consistent for requests in a single session.

To make use of this attribute, the HttpContextFeatureSessionManager can be modified. Using some reflection code (which may not be the fastest as it will be performed on every request) we can examine whether or not the enum value has the attribute and only set the session item if it does:

using Microsoft.AspNetCore.Http;
using Microsoft.FeatureManagement;
using System;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;

namespace WebApplication1.Models
{
    /// <summary>
    /// Based on https://andrewlock.net/keeping-consistent-feature-flags-across-requests-adding-feature-flags-to-an-asp-net-core-app-part-5/
    /// </summary>
    public class HttpContextFeatureSessionManager : ISessionManager
    {
        private readonly IHttpContextAccessor _contextAccessor;
        private const string SessionKeyPrefix = "feature_flag_";
       
        public HttpContextFeatureSessionManager(IHttpContextAccessor contextAccessor)
        {
            _contextAccessor = contextAccessor;
        }

        public Task<bool?> GetAsync(string featureName)
        {
            bool keyExistsInHttpSession = _contextAccessor.HttpContext
                                                          .Session
                                                          .TryGetValue(key: $"{SessionKeyPrefix}{featureName}",
                                                                       value: out byte[] bytes);

            if (keyExistsInHttpSession)
            {
                return Task.FromResult((bool?)BitConverter.ToBoolean(bytes));
            }

            return Task.FromResult<bool?>(null);
        }

        public Task SetAsync(string featureName, bool enabled)
        {
            if (!ShouldPreserveAccrossRequests(featureName))
            {
                return Task.CompletedTask;
            }

            _contextAccessor.HttpContext
                            .Session
                            .Set(key: $"{SessionKeyPrefix}{featureName}", 
                                 value: BitConverter.GetBytes(enabled));
            
            return Task.CompletedTask;
        }

        private static bool ShouldPreserveAccrossRequests(string featureName)
        {        
            MemberInfo enumFieldInfo = typeof(Features).GetMember(featureName).First();            
                        
            if (enumFieldInfo.GetCustomAttributes(typeof(PreserveFeatureAttribute), false).Any())
            {
                return true;
            }

            return false;
        }
    }
}

Now if we have the following in the view:

<div class="text-center">
    <h1 class="display-4">Welcome</h1>
    <feature name="MustCaptureAge">
        <label for="age">Please enter your age</label>
        <input name="age" type="number" />
    </feature>
    <feature name="Printing">        
        <button>Print</button>
    </feature>
</div>

The print button can change for different requests in a single session, whereas the age input will never change within a single session.

This reflection based approach also means that the configuring is hard coded into the enum. You could however read session preservation settings from configuration.

Another disadvantage of this approach is when you are using feature filters. A simple true/false feature flag is less complex than a percentage filter or a date range based feature. For example what if a feature is set to turn on automatically by using the Time Window Feature Filter, the feature could automatically turn on during a session, should the current date/time be honoured or should the date/time from the first initial session request be honoured? This is as much a business consideration as it is a technical one and there is no one right answer.

Be sure to check out my Microsoft Feature Management Pluralsight course get started watching with a free trial.

SHARE:

Conditional HTML Rendering with Microsoft Feature Flags (Microsoft.FeatureManagement)

This is part seven in a series of articles.

EDIT: my Feature Management Pluralsight training course is now available.

You can render HTML in your views based on whether or not a feature flag is enable or disabled. To do this you can make use of the FeatureTagHelper.

For example suppose the following Printing feature is configured to be off in the appsettings.json:

"FeatureManagement": {
  "Printing": false    
}

Now in the view (or _ViewImports.cshtml) you can add the tag helper and then use the <feature> tag:

@addTagHelper *, Microsoft.FeatureManagement.AspNetCore

<div class="text-center">
    <h1 class="display-4">Welcome</h1>
    <feature name="Printing">
        <button>Print</button>
    </feature>
</div>

Notice in the preceding view that the <feature> tag has the  name property set to the feature set in config, i.e. “Printing”.

If you have defined your features in an enum as described earlier in this series, you could use a nameof expression instead:

@addTagHelper *, Microsoft.FeatureManagement.AspNetCore

<div class="text-center">
    <h1 class="display-4">Welcome</h1>
    <feature name="@nameof(Features.Printing)">
        <button>Print</button>
    </feature>
</div>

The FeatureTagHelper has a number of ways it can be used. For example you can invert the configured feature value by setting the negate property to true:

@addTagHelper *, Microsoft.FeatureManagement.AspNetCore

    <div class="text-center">
        <h1 class="display-4">Welcome</h1>
        <feature name="@nameof(Features.Printing)">
            <button>Print</button>
        </feature>
        <feature name="@nameof(Features.Printing)" negate="true">
            <button disabled>Printing is disabled</button>
        </feature>
    </div>

The preceding view will show a disabled button when the printing feature is not available.

You can also specify multiple features in a single <feature> name:

<feature name="Printing, PrintPreview">
    <button>Print</button>
</feature>

Notice in the preceding view that both the Printing and PrintPreview features are used (comma separated) in the name attribute. By default this is a logical AND so both features need to be enabled for the contents of the <feature> tag to be rendered, e.g.:

"FeatureManagement": {
  "Printing": true,
  "PrintPreview": true
}

If you want to use a logical OR and render the HTML when any of the specified features are enabled you can set the requirement attribute to “Any”:

@addTagHelper *, Microsoft.FeatureManagement.AspNetCore

<div class="text-center">
    <h1 class="display-4">Welcome</h1>
    <feature name="Printing, PrintPreview" requirement="Any">
        <button>Print</button>
    </feature>
</div>

Now if either (or both) Printing or PrintPreview is enabled the button will be rendered.

You could also implement a more strongly typed tag helper that uses a Features enum to represent the features:

public enum Features
{
    Printing,
    PrintPreview,
    QuickQuotes,
    OnlineChat
}
using Microsoft.AspNetCore.Razor.TagHelpers;
using Microsoft.FeatureManagement;
using System.Threading.Tasks;

namespace WebApplication1.Models
{
    /// <summary>
    /// Based on https://github.com/microsoft/FeatureManagement-Dotnet/blob/master/src/Microsoft.FeatureManagement.AspNetCore/TagHelpers/FeatureTagHelper.cs
    /// </summary>    
    public class FeatureTagHelper : TagHelper
    {
        private readonly IFeatureManager _featureManager;

        public Features Feature { get; set; }
        
        public FeatureTagHelper(IFeatureManagerSnapshot featureManager)
        {
            _featureManager = featureManager;
        }

        public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
        {
            DontOutputTagName();

            bool isFeatureEnabled = await _featureManager.IsEnabledAsync(Feature.ToString()).ConfigureAwait(false);

            if (!isFeatureEnabled)
            {
                output.SuppressOutput();
            }

            void DontOutputTagName() => output.TagName = null;
        }
    }
}

@addTagHelper *, WebApplication1

<div class="text-center">
    <h1 class="display-4">Welcome</h1>
    <feature feature="@Features.Printing">
        <button>Print</button>
    </feature>
</div>

You could also go one step further and define a tag helper for every feature:

using Microsoft.AspNetCore.Razor.TagHelpers;
using Microsoft.FeatureManagement;
using System.Threading.Tasks;

namespace WebApplication1.Models
{
    /// <summary>
    /// Based on https://github.com/microsoft/FeatureManagement-Dotnet/blob/master/src/Microsoft.FeatureManagement.AspNetCore/TagHelpers/FeatureTagHelper.cs
    /// </summary>    
    public abstract class FeatureTagHelper : TagHelper
    {
        private readonly IFeatureManager _featureManager;

        protected Features _feature;
        
        public FeatureTagHelper(IFeatureManagerSnapshot featureManager, Features feature)
        {
            _featureManager = featureManager;
            _feature = feature;
        }

        public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
        {
            DontOutputTagName();

            bool isFeatureEnabled = await _featureManager.IsEnabledAsync(_feature.ToString()).ConfigureAwait(false);

            if (!isFeatureEnabled)
            {
                output.SuppressOutput();
            }

            void DontOutputTagName() => output.TagName = null;
        }
    }

    public class PrintingTagHelper : FeatureTagHelper
    {
        public PrintingTagHelper(IFeatureManagerSnapshot featureManager) : base(featureManager, Features.Printing) {}        
    }

    public class OnlineChatTagHelper : FeatureTagHelper
    {
        public OnlineChatTagHelper(IFeatureManagerSnapshot featureManager) : base(featureManager, Features.OnlineChat) {}
    }
}

@addTagHelper *, WebApplication1

<div class="text-center">
    <h1 class="display-4">Welcome</h1>
    <printing>
        <button>Print</button>
    </printing>
    <online-chat>
        <button>chat</button>
    </online-chat>
</div>

Be sure to check out my Microsoft Feature Management Pluralsight course get started watching with a free trial.

SHARE:

Preventing Inconsistent Feature Flag Values During a Single Request (Microsoft.FeatureManagement)

This is part six in a series of articles.

EDIT: my Feature Management Pluralsight training course is now available.

If you check a feature flag multiple times during a single HTTP request in ASP.NET Core, the feature can return a different results for each check.

For example, the Microsoft.Percentage feature filter which we looked at in part three enables a feature for a specified percentage of feature checks:

"FeatureManagement": {
  "Printing": {
    "EnabledFor": [
      {
        "Name": "Microsoft.Percentage",
        "Parameters": {
          "Value": 50
        }
      }
    ]
  }
}

If, within a single HTTP request, the printing feature is queried you will get different results. The following is a simple demo of this:

public async Task<IActionResult> Index()
{
    CheckPrintingFeature();
    CheckPrintingFeature();
    CheckPrintingFeature();

    return View();            

    async void CheckPrintingFeature()
    {
        if (await _featureManager.IsEnabledAsync(nameof(Features.Printing)))
        {
            ViewData["PrintMessage"] += "On, ";
        }
        else
        {
            ViewData["PrintMessage"] += "Off, ";
        }
    }
}

If we run the web app, the ViewData could be set to “Off, Off, On,” for example. This means if you are controlling which version of an algorithm gets used based on a feature flag and you are doing this in multiple places within a single HTTP request you may get different versions of the algorithm used, which is probably not what you want. This behaviour is not limited to the Percentage feature filter.

Using Feature Flag Snapshots During a Single Request

Luckily the FeatureManagement team has thought of this and provides an alternative in the form of the IFeatureManagerSnapshot interface. This interface represents a “a snapshot of feature state to ensure consistency across a given request” [Microsoft]. This means that within a single request, the first time a feature is checked, the result will be remembered for all subsequent checks within that HTTP request.

To use this, instead of injecting an IFeatureManager into a controller, you can instead inject an IFeatureManagerSnapshot:

private IFeatureManagerSnapshot _featureManager;

public HomeController(IFeatureManagerSnapshot featureManager)
{
    _featureManager = featureManager;
}

If you run the app now, you’ll get a message of either “Off, Off, Off,” or “On, On, On,”, you won’t get mixed results within a single request. You may still get different results across multiple requests, even for the same user/session. In a future post we’ll look at preserving values across requests.

Interestingly the <feature> tag helper internally uses IFeatureManagerSnapshot – this means that if you are controlling UI elements with the tag helper then you will get consistent results within a single request, for example:

<ul>
    <feature name="Printing">
        <li>Tag helper check 1</li>
    </feature>
    <feature name="Printing">
        <li>Tag helper check 2</li>
    </feature>
    <feature name="Printing">
        <li>Tag helper check 3</li>
    </feature>
    <feature name="Printing">
        <li>Tag helper check 4</li>
    </feature>
    <feature name="Printing">
        <li>Tag helper check 5</li>
    </feature>
    <feature name="Printing">
        <li>Tag helper check 6</li>
    </feature>
    <feature name="Printing">
        <li>Tag helper check 7</li>
    </feature>
</ul>

The above will either show all <li>s or none at all.

Be sure to check out my Microsoft Feature Management Pluralsight course get started watching with a free trial.

SHARE:

Managing Microsoft Feature Flags with Azure App Configuration (Microsoft.FeatureManagement)

This is part five in a series of articles.

EDIT: my Feature Management Pluralsight training course is now available.

So far in this series the feature flags in the application have been managed via the appsettings.json file, for example:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*",
  "FeatureManagement": {
    "Printing": true
    }      
}

This means that if you want to manually enable a feature you need to update the config file manually in production or release a new version to production with the changed config file. If you want to manage feature flags without needing to modify the running production app/config one thing you could do is to create a custom feature filter to read from a database or API that’s external to your app.

As an alternative you could use Azure App Configuration to manage your features. This enables you to turn features on or off whenever you want.

Creating an Azure App Configuration Store

The first thing to do is sign in to Azure or create a free account.

Once in the Azure Portal, click the Create a resource button, and search for App Configuration:

Creating a new Azure App Configuration store

Click the Create button.

Next, configure your App Configuration store, for example:

Configuring a new Azure App Configuration store

Notice in the preceding screenshot that I’ve selected the Pricing tier as Free which comes with a number of limitations such as no SLA, etc. – there is also the Standard tier which comes with 99.9% availability SLA, more requests, etc.

Once you’ve configured the new store, click Create. This will submit the deployments, you may have to wait a short while and then if you head to All Resources in the portal you should see your new store listed - click on it an then choose Feature manager:

Feature Manager

It’s here you can add the names of your feature flags.

For example let’s add a feature flag to control printing in the app. Click the Add button, choose Off or On, give the feature a name (key) of “Printing”, and a description of “Print preview features”:

Adding a new feature flag in Feature Manager

Click Apply.

You now have a Printing feature flag, the  next thing is to use it in an app.

Using Azure App Configuration Feature Flags in ASP.NET Core

In an ASP.NET Core app, add the following NuGet packages:

  • Microsoft.FeatureManagement.AspNetCore
  • Microsoft.Azure.AppConfiguration.AspNetCore

Next add a new connection string in the appsettings.json file called “AppConfig”. The connection can be found in the Azure portal by clicking on Access Keys and then clicking the copy to clipboard button next to the connection string:

How to get Azure App Configuration connection string

Paste this connection string into the appsettings.json, for example:

{
  "ConnectionStrings": {
    "AppConfig": "Endpoint=https://msfeatureflagdemo.azconfig.io;Id=REDACTED"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*"
}

Note: you should be careful when adding secrets to config files that they are not accidentally checked in to source control or otherwise compromised. You should not have production passwords, secrets, etc checked into source control. One way to manage secrets in development to use the Secret Manager.

The next step is to modify the ASP.NET Core app to look for feature flags in Azure App Configuration.

First, modify the program.cs CreateHostBuilder method to add Azure App Configuration support:

public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
    .ConfigureWebHostDefaults(webBuilder =>
    {
        webBuilder.ConfigureAppConfiguration((hostingContext, config) =>
        {
            var settings = config.Build();
            config.AddAzureAppConfiguration(options =>
            {
                options.Connect(settings["ConnectionStrings:AppConfig"])
                    .UseFeatureFlags();
            });
        });

        webBuilder.UseStartup<Startup>();
    });
}

Notice in the preceding code that we are calling AddAzureAppConfiguration and then making sure that .UseFeatureFlags() is called.

Next in the Startup.Configuration method, add feature management:

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

    services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();

    services.AddFeatureManagement();
}

Turning Feature Flags Off and On with Azure App Configuration

Now in the app we can reference the Printing feature. In part one of this series we saw the feature tag helper, for example to hide a menu item based on a Printing feature flag:

<feature name="Printing">
    <li class="nav-item">
        <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Print">Print Preview</a>
    </li>
</feature>

Note: You should not rely solely on UI elements to “hide” features, you should also control them at the controller/action level with the [FeatureGate] attribute otherwise the action/URL will still be accessible even if the UI is not showing the menu item for example..

If you run the app now the printing menu item will not be shown because the Printing feature is currently off in Azure App Configuration:

Disabled feature flag

If you click the On toggle, the feature will now be set to enabled. If you refresh the browser window you will notice however that the printing menu item is still not shown. However if you stop the web app and restart it the printing menu will now be shown.

To enable “live” updating of feature flags in ASP.NET Core you need to add an extra piece of middleware, to do this modify the Startup.Configure method and add app.UseAzureAppConfiguration();

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app.UseAzureAppConfiguration();

    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Home/Error");
    }
    app.UseStaticFiles();

    app.UseRouting();

    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllerRoute(
            name: "default",
            pattern: "{controller=Home}/{action=Index}/{id?}");
    });            
}

If you run the web app now and turn the feature off and on, if you refresh the browser a few times you should see the print menu item appear/disappear without needing to stop and start the web app. You may need to refresh several times before the feature flag setting is updated, this means that if you need to turn a feature off in an emergency situation it may not take effect immediately and you may still get users using the feature for a short while after making the change.

Be sure to check out my Microsoft Feature Management Pluralsight course get started watching with a free trial.

SHARE: