Skip to main content

A/B testing in .NET using Microsoft Feature Management

I’ m currently working on a new feature in one of our microservices. As this new feature could have a large performance impact, we did some performance benchmarks up front through BenchMarkDotNet. The results looked promising but we were not 100% confident that these results are representative for real life usage. Therefore we decided to implement A/B testing.

In this post, we'll explore what A/B testing is, why it matters, and how to implement it effectively using .NET Feature Management.

What is A/B testing?

A/B testing, also known as split testing, is a method of comparing two versions of a web page, application feature, or user experience to determine which one performs better. In its simplest form, you show version A to half your users and version B to the other half, then measure which version achieves your desired outcome more effectively.

The beauty of A/B testing lies in its scientific approach. Rather than making changes based on opinions or assumptions, you're letting actual user behavior guide your decisions. This leads to improvements backed by real data rather than guesswork.

Common A/B testing scenarios

A/B testing can be applied to virtually any aspect of your application. Here are some popular use cases:

  • User Interface Changes: Testing different button colors, layouts, or navigation structures to improve user engagement and conversion rates.
  • Feature Variations: Comparing different implementations of the same feature to see which approach users prefer or find more effective.
  • Algorithmic Changes: Testing different recommendation algorithms, search ranking methods, or personalization approaches.
  • Pricing and Messaging: Experimenting with different pricing models, promotional offers, or marketing copy to optimize conversion rates.
  • Performance Optimizations: Comparing different technical implementations to see which provides better user experience while maintaining functionality. (This our scenario in this case)

Introduction to .NET Feature Management

There are multiple ways to implement A/B testing in .NET, but we decided to use the Microsoft's Feature Management library. This library provides a robust foundation for implementing feature flags and A/B testing in .NET applications. It integrates seamlessly with ASP.NET Core's dependency injection system and configuration providers, making it easy to manage features across different environments.

The Feature Management library supports several key concepts that make A/B testing straightforward. Feature flags allow you to toggle functionality on and off without code deployments. Feature filters provide sophisticated logic for determining when features should be enabled, including percentage-based rollouts perfect for A/B testing. The library also includes built-in support for configuration providers, meaning you can manage your feature flags through the appsettings.json, or other configuration sources.

Setup feature management in your ASP.NET Core project

Let's start by setting up a new ASP.NET Core project with Feature Management. First, we need to install the necessary NuGet packages:

dotnet add package Microsoft.FeatureManagement.AspNetCore

Next, configure Feature Management in your Program.cs file:

Configure a feature flag

Feature flags are configured through your application's configuration system. Add the following to your appsettings.json:

This configuration creates one feature flag. The NewFunctionality feature will be enabled for 50% of users. The percentage filter ensures consistent assignment—the same user will always see the same variation.

Integrate the feature flag in your controllers

In your controllers, inject the IFeatureManager service to check feature flag status:

That’s it!

Best practices for A/B testing

Successful A/B testing requires more than just technical implementation. Here are key practices to follow:

  • Statistical Significance: Don't end tests too early. Ensure you have enough data to make statistically significant conclusions. Tools like online sample size calculators can help determine how long to run your tests.
  • Single Variable Testing: Test one change at a time. If you test multiple changes simultaneously, you won't know which change caused any observed differences in behavior.
  • Consistent User Experience: Ensure users see the same variation throughout their session or longer. Inconsistent experiences can confuse users and skew results.
  • Meaningful Metrics: Choose metrics that truly matter to your business goals. High-level metrics like conversion rate or user engagement are often more valuable than vanity metrics.
  • Test Documentation: Document your hypotheses, test parameters, and results. This creates institutional knowledge and helps inform future testing strategies.
  • Gradual Rollouts: Start with small percentages and gradually increase traffic to winning variations. This approach minimizes risk while maximizing learning.

More information

.NET feature flag management - Azure App Configuration | Microsoft Learn

Feature Flags in .NET, from simple to more advanced

Home | BenchmarkDotNet

Popular posts from this blog

Podman– Command execution failed with exit code 125

After updating WSL on one of the developer machines, Podman failed to work. When we took a look through Podman Desktop, we noticed that Podman had stopped running and returned the following error message: Error: Command execution failed with exit code 125 Here are the steps we tried to fix the issue: We started by running podman info to get some extra details on what could be wrong: >podman info OS: windows/amd64 provider: wsl version: 5.3.1 Cannot connect to Podman. Please verify your connection to the Linux system using `podman system connection list`, or try `podman machine init` and `podman machine start` to manage a new Linux VM Error: unable to connect to Podman socket: failed to connect: dial tcp 127.0.0.1:2655: connectex: No connection could be made because the target machine actively refused it. That makes sense as the podman VM was not running. Let’s check the VM: >podman machine list NAME         ...

Cache stampede: when our cache turned against us

While investigating some performance issues, we ran into an ASP.NET Core API that cached a fairly expensive aggregation query for 60 seconds. Under normal load, that was fine: one request rebuilds the cache, everyone else reads from it. Under peak load, dozens of requests would arrive in that same expiry window, all see a cache miss, and all fire the same expensive query in parallel. The database didn't like that. That was the moment when our caching layer stopped helping and started hurting. A burst of requests comes in at the same time, all miss the cache, and all go hammer the database or the downstream API at once. That's a cache stampede . The cache was supposed to protect our backend, and for a few hundred milliseconds it did the opposite. Why this happens IMemoryCache.GetOrCreate (and its async sibling) looks like it protects you, but it doesn't add any locking on its own. Look at the naive version: public async Task<Report> GetReportAsync(string key) ...

A complex system designed from scratch never works

A few years ago, I worked as an architect on a big mainframe rewrite. I still count it as one of my failures. Not because the technology was wrong, but because I couldn't convince the management team to simplify the approach. Years later, the organization is still struggling to get the new system up and running. I left the project at the time, because I couldn't put my name behind an approach that would take very long and cost a lot of money without a working system to show for it along the way. Gall’s Law That memory keeps coming back to me, because it's a textbook case of Gall's Law playing out in real life. Gall's Law , from John Gall's Systemantics , states it plainly: A complex system that works is invariably found to have evolved from a simple system that worked. A complex system designed from scratch never works, and it cannot be patched to make it work. You have to start over with a simple system that works. What does that mean in practice,...