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

This is part five in a series of articles.

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.

SHARE:

Microsoft Feature Flags: Implementing Custom Feature Filters (Microsoft.FeatureManagement)

This is part four in a series of articles.

In part three I introduced the concept of feature filters. These allow features to be enabled/disabled based on more than a simple true/false configuration.

Currently there are 2 feature filters supplied out of the box, a percentage filter and a time window feature. You can also create you own custom feature filters.

Creating a Simple Custom Feature Filter

To create a custom feature filter, the first thing to do is create a new class, for example called “RandomFeatureFilter”. To create a feature filter you need to implement the IFeatureFilter interface from the Microsoft.FeatureManagement namespace. This interface has a single method called EvaluateAsync that returns a boolean result as a task. If this method returns true then the feature will be enabled. For example the following code implements a feature filter that will enable a feature at “random”:

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

namespace WebApplication1.CustomFeatures
{
    public class RandomFeatureFilter : IFeatureFilter
    {
        public Task<bool> EvaluateAsync(FeatureFilterEvaluationContext context)
        {
            // Simple/ naive "random" implementation
            bool isEnabled = DateTime.Now.Ticks % 2 == 0;

            return Task.FromResult(isEnabled);
        }
    }
}

The next thing do do is register this custom feature filter when the app starts up, for example in an ASP.NET Core app:

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

    services.AddFeatureManagement()
            .AddFeatureFilter<WebApplication1.CustomFeatures.RandomFeatureFilter>();
}

The next step is to configure one or more features to get their status from the custom filter. To do this in the appsettings.json:

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

Notice in the preceding config that the filter is referred to by name as “RandomFeature” even though the class is called “RandomFeatureFilter” – notice the “Filter” suffix is removed.

Also note that if you try and specify the fully qualified class name ”WebApplication1.CustomFeatures.RandomFeatureFilter” or ”WebApplication1.CustomFeatures.RandomFeature” you’ll get an error.

Specifying a Feature Filter Alias

Instead of using the class name (minus any “filter” suffix) you can also specify a custom name/alias by decorating the filter class with the [FilterAlias] attribute as follows:

[FilterAlias("RandomizeFeatureFilter")]
public class RandomFeatureFilter : IFeatureFilter
{
    public Task<bool> EvaluateAsync(FeatureFilterEvaluationContext context)
    {
        // Simple/ naive "random" implementation
        bool isEnabled = DateTime.Now.Ticks % 2 == 0;

        return Task.FromResult(isEnabled);
    }
}

Now the configuration can change to:

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

Notice in the preceding config that when using an alias that the “filter” suffix is not removed.

Custom Feature Filter Configuration Parameters

If you want to pass additional configuration information to a feature filter you can do this. For example take the following config that allows you to specify a method of “randomness” of odd or even:

"FeatureManagement": {
  "Printing": {
    "EnabledFor": [
      {
        "Name": "RandomizeFeatureFilter",
        "Parameters": {
          "Method":  "Even"
        }
      }
    ]
  }    
}

To access this in the feature filter evaluation logic, a strongly typed settings class can first be created to represent the settings:

public class RandomFeatureFilterSettings
{
    public string Method { get; set; }
}

Notice in the preceding code that the property Method maps to the “Method” parameter in the appsetting.json. Also note that the property has a setter, if you only add a get then the property will always be null.

To get access to the config you can access the Parameters property of the FeatureFilterEvaluationContext that’s passed into the EvaluateAsync method:

[FilterAlias("RandomizeFeatureFilter")]
public class RandomFeatureFilter : IFeatureFilter
{
    public Task<bool> EvaluateAsync(FeatureFilterEvaluationContext context)
    {
        RandomFeatureFilterSettings settings = context.Parameters.Get<RandomFeatureFilterSettings>();

        if (settings.Method == "Even")
        {
            return Task.FromResult(DateTime.Now.Ticks % 2 == 0);
        }

        if (settings.Method == "Odd")
        {
            return Task.FromResult(DateTime.Now.Ticks % 2 != 0);
        }

        throw new Exception($"Random feature filter configured value '{settings.Method}' is invalid, must be 'Even' or 'Odd'.");
    }
}

 

 

Custom Feature Filters Based On HttpContext

You can also create a feature filter that uses information about the current HttpContext to decide whether or not to enable a feature.

For example we could create a feature filter to enable a feature or set of features when the cookie “beta” is present - for example to allow users to opt in to experimental features.

To do this, the first thing to do is create a feature filter class that has a constructor that takes an IHttpContextAccessor.This will be supplied in ASP.NET Core via dependency injection. This instance can be captured in a field and used in the EvaluateAsync method to access cookie information:

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

namespace WebApplication1.CustomFeatures
{
    [FilterAlias("BetaCookie")]
    public class BetaCookieFeatureFilter : IFeatureFilter
    {
        private readonly IHttpContextAccessor _httpContextAccessor;
        
        public BetaCookieFeatureFilter(IHttpContextAccessor httpContextAccessor)
        {
            _httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor));
        }

        public Task<bool> EvaluateAsync(FeatureFilterEvaluationContext context)
        {
            bool isEnabled = _httpContextAccessor.HttpContext.Request.Cookies.ContainsKey("beta");

            return Task.FromResult(isEnabled);
        }
    }
}

To register IHttpContextAccessor in the Startup.ConfigureServices method:

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

    services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();

    services.AddFeatureManagement()
            .AddFeatureFilter<WebApplication1.CustomFeatures.BetaCookieFeatureFilter>();
}

The final thing to do is configure a feature flag to use this new BetaCookie feature filter:

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

Now when a cookie called “beta” is present, the Printing feature will be enabled, otherwise it will be disabled.

Feature filters are a powerful way of providing custom logic that is also configurable. For example we could generalize the BetaCookie filter to be something like CookiePresentFeatureFilter and then add a configuration parameter that allows us to specify the cookie name.

SHARE:

Microsoft Feature Flags: Controlling Features with Feature Filters (Microsoft.FeatureManagement)

This is the third part in a series.

So far in this series, the configured feature flags have either been set to on (true) or off (false):

"FeatureManagement": {
  "Printing": true,
  "LiveChat": false
}

In addition to setting an absolute value, you can also make use of feature filters.

Essentially feature filters allow you to create conditional feature flags.

There are currently two feature filters built into the library and you can also create your own.

Percentage Feature Filter

If you want a feature enabled only for a percentage of users you can apply the Microsoft.Percentage feature filter.

To configure a feature filter in appsettings.json, instead of specifying a value of true/false, instead you add an EnabledFor section. For example to configure the Printing feature to be enabled 50% of the time:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*",
  "FeatureManagement": {
    "Printing": {
      "EnabledFor": [
        {
          "Name": "Microsoft.Percentage",
          "Parameters": {
            "Value": 50
          }
        }
      ]
    },
    "LiveChat": false
  }
}

If you run the app now  you’ll get an error because feature filters have to be registered. In an ASP.NET Core app this can be done by chaining on the AddFeatureFilter method, for example in the Startup.cs  as follows:

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

    services.AddFeatureManagement()
            .AddFeatureFilter<PercentageFilter>();
}

To get access to the PercentageFilter you’ll need to add a using directive for Microsoft.FeatureManagement.FeatureFilters.

Now if you run the app (e.g. an ASP.NET Core web app) the printing feature will be available for 50% of requests.

Time Window Feature Filter

The second built-in feature filter, Microsoft.TimeWindow, allows a feature to be enabled for a specified range of time.

The first thing is to register the feature filter:

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

    services.AddFeatureManagement()
            .AddFeatureFilter<TimeWindowFilter>();
}

Next the configuration can be changed.

The time window feature filter accepts two parameters in the config, a Start date/time and an End date/time, for example the following feature would be enabled at 1 second past midnight for New Year’s Day 2021 and be disable at the end of the 1st Jan:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*",
  "FeatureManagement": {
    "Printing": {
      "EnabledFor": [
        {
          "Name": "Microsoft.TimeWindow",
          "Parameters": {
            "Start": "Fri, 01 Jan 2021 00:00:01 GMT",
            "End": "Fri, 01 Jan 2021 23:59:59 GMT"
          }
        }
      ]
    },
    "LiveChat": false
  }
}

You can also specify that a feature should be enable after a specific date/time forever after by not specifying an end date, for example to enable a feature from the 1st may 2020, 1PM GMT:

"FeatureManagement": {
"Printing": {
  "EnabledFor": [
    {
      "Name": "Microsoft.TimeWindow",
      "Parameters": {
        "Start": "Fri, 01 May 2020 13:00:00 GMT"            
      }
    }
  ]
},

If you want a feature to be on until a date/time and then be off after that then you can just specify the end date, for example to have a feature enabled and then automatically turn off 5:10 AM on 15th may 2020:

"FeatureManagement": {
"Printing": {
  "EnabledFor": [
    {
      "Name": "Microsoft.TimeWindow",
      "Parameters": {
        "End": "Fri, 15 May 2020 05:10:00 GMT"
      }
    }
  ]
},

There is another advanced type of feature filtering called “targeting” that can be used to roll out new features based on a complex set of rules – I’ll cover this in a future part of this series.

You can also define your own feature filters which we’ll look at in the next part in this series.

SHARE:

Using C# Source Generators with Microsoft Feature Management Feature Flags

C# Source Generators allow you to generate and compile source code dynamically. You can read more about them in this previous article.

In my series on Microsoft Feature Management, part 2 showed how to reduce magic strings by using an enum.

Using this approach still requires that the enum and the configuration file be manually kept in sync.

One use case for C# Source Generators (that are currently in preview) is to generate code from a file in the project, for example generating a class from an XML settings file.

We can process any external file in a source generator, including json files such as appsettings.json.

This means that if the feature flags are defined in the appsettings.json file, we can create a source generator to read this file and output an enum containing all the features that have been defined in the appsettings.json. This means that the feature flags in the app will always be in sync with configuration file. It also means that if you were to remove a flag from the appsettings.json then compilation would break if you were still referencing the feature flag in the app.

Creating a C# Source Generator

To get this all to work in Visual Studio 2019 Preview first create a new .NET Standard 2.0 class library project called FeatureFlagGenerator.

Modify the FeatureFlagGenerator.csproj to the following:

<Project Sdk="Microsoft.NET.Sdk">
    <PropertyGroup>
        <TargetFramework>netstandard2.0</TargetFramework>
        <LangVersion>preview</LangVersion>
    </PropertyGroup>
    
    <PropertyGroup>
        <RestoreAdditionalProjectSources>https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet5/nuget/v3/index.json ;$(RestoreAdditionalProjectSources)</RestoreAdditionalProjectSources>
    </PropertyGroup>
    
    <ItemGroup>
        <PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="3.6.0-3.20207.2" PrivateAssets="all" />
        <PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.0.0-beta2.final" PrivateAssets="all" />
    </ItemGroup>
</Project>

Next add a new class called FeatureFlagEnumGenerator with the following code:

using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;

namespace FeatureFlagGenerator
{
    [Generator]
    public class FeatureFlagEnumGenerator : ISourceGenerator
    {
        public void Execute(SourceGeneratorContext context)
        {
            IEnumerable<AdditionalText> files = context.AdditionalFiles.Where(at => at.Path.EndsWith("appsettings.json"));

            AdditionalText appsettings = files.First();

            StringBuilder enumString = new StringBuilder();

            enumString.AppendLine("namespace GeneratedCode");
            enumString.AppendLine("{");

            enumString.AppendLine("   public enum GeneratedFeatureFlags");
            enumString.AppendLine("   {");

            // Doing this manually because referencing JsonDocument etc causes unknown errors
            // Not very robust code, POC only
            bool isInFeatureManagamentSection = false;
            foreach (var line in appsettings.GetText(context.CancellationToken).Lines)
            {               
                if (isInFeatureManagamentSection && line.ToString().Contains("}"))
                {
                    isInFeatureManagamentSection = false;
                }

                if (isInFeatureManagamentSection)
                {
                    int featureNameStartIndex = line.ToString().IndexOf('"')+1;
                    int featureNameEndIndex = line.ToString().IndexOf(':')-1;
                    enumString.AppendLine(line.ToString().Substring(featureNameStartIndex, featureNameEndIndex - featureNameStartIndex) + ",");
                }

                if (line.ToString().Contains("FeatureManagement"))
                {
                    isInFeatureManagamentSection = true;
                }
            }

            enumString.AppendLine("   }");
            enumString.AppendLine("}");

            SourceText sourceText = SourceText.From(enumString.ToString(), Encoding.UTF8);
            const string desiredFileName = "GeneratedFeaturesEnum.cs";
            context.AddSource(desiredFileName, sourceText);
        }

        public void Initialize(InitializationContext context)
        {
            // Advanced usage
            
        }
    }
}

Essentially the preceding code will search for the appsettings.json file in the project where the source generator is being used and then find all the configured feature flag names - for some reason trying to use System.Text.Json was causing errors so I implemented a very clunky solution using string manipulation.

Using a C# Source Generator

Now the generator is defined, it can be used in another project.

Go and add a new ASP.NET Core MVC project to the solution and modify the .csproj file to the following:

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
      <TargetFramework>net5.0</TargetFramework>
      <LangVersion>preview</LangVersion>
  </PropertyGroup>

  <ItemGroup>
    <AdditionalFiles Include="appsettings.json">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </AdditionalFiles>
  </ItemGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.FeatureManagement.AspNetCore" Version="2.0.0" />      
  </ItemGroup>

  <ItemGroup>
    <ProjectReference Include="..\FeatureFlagGenerator\FeatureFlagGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
  </ItemGroup>

</Project>

There is a secret ingredient in the preceding project file and that is the AdditionalFiles Include="appsettings.json" part. This makes the file available to C# Source Generators and took me a while to work out.

Now when the web application is built, the source generator will run and create an enum with every configured flag name.

For example given the following appsettings.json:

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

This will generate and compile the following into the web application dll:

namespace GeneratedCode
{
  public enum GeneratedFeatureFlags
  {
    Printing,
    LiveChat,
  }
}

Notice the Printing and LiveChat enum values come from the appsettings FeatureManagement section.

Now the feature flags can be referenced in code:

if (await _featureManager.IsEnabledAsync(nameof(GeneratedCode.GeneratedFeatureFlags.Printing)))
{
    ViewData["PrintMessage"] = "On";
}
else
{
    ViewData["PrintMessage"] = "Off";
}

Or in the UI:

<feature name="@nameof(GeneratedCode.GeneratedFeatureFlags.Printing)">
    <li class="nav-item">
        <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Print">Print Preview</a>
    </li>
</feature>

It is these kinds of use cases, where there would otherwise be manual boilerplate code work, that C# Source Generators will be really useful.

SHARE:

Reducing Magic Strings with Microsoft Feature Toggles (Microsoft.FeatureManagement)

This is the second part in a series.

One of the downsides of Microsoft’s feature flags/feature toggle implementation is the reliance on magic strings in code to query the status of features.

For example, the following code checks for the state of the Printing feature:

if (await _featureManager.IsEnabledAsync("Printing"))
{
    ViewData["PrintMessage"] = "On";
}
else
{
    ViewData["PrintMessage"] = "Off";
}

Notice in the preceding code that the feature is referenced with the string “Printing”. These kind of magic strings in code can be problematic because they have no sematic compiler meaning, for example if the feature is mentioned in multiple places and the name of the feature changes in configuration you would need to update all the strings in the codebase. It is easy to miss one and then end up with errors in the app.

In the documentation, the recommended approach is to define an enum containing the feature you want to control, for example:

public enum Features
{
    Printing,
    QuickQuotes,
    OnlineChat
}

Now the code can be refactored to make use of the enum in conjunction with a nameof expression:

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

There are a number of things that can (and should) happen when using feature toggles/flags. One of which is that when a feature becomes permanent then the toggle and associated code should be removed. First this means removing it from configuration, which as I mentioned in part 1 will not actually break anything. The next step will be to remove it from the enum, doing this will actually break compilation (which is a good thing) if the enum value is referenced in a nameof expression. The next step is to fix compilation errors by removing the conditional code that relies on the flag/toggle.

In part 1 I mentioned the <feature> tag helper that can be used to enable/disable UI elements based on a 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>

Notice in the preceding HTML that the name of the feature is still represented as a magic string “Printing”. If the enum value and config are removed, compilation will not beak if this HTML still exists. We can fix this by one again using nameof in the view:

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

Now if we removed Printing from the enum we get a compilation error and we won’t accidentally release an app to production with incorrect UI elements.

SHARE:

Using the Microsoft Feature Toggle Library in ASP.NET Core (Microsoft.FeatureManagement)

This is the first part in a series.

As the creator of the open source FeatureToggle library for .NET it was with some interest that I learned about Microsoft’s own offering to the feature toggle/feature flags landscape.

Microsoft’s offering is know as “.NET Core Feature Management” and they use the term “feature flag” in place of other terms such as “feature toggle”, “feature flappers”, etc. The root namespace is “Microsoft.FeatureManagement”.

Getting Started with Microsoft Feature Management in ASP.NET Core

As a simple example, first create a new ASP.NET Core 3.1 web app project with MVC support and then install the Microsoft.FeatureManagement.AspNetCore NuGet package.

Next, open the Startup.cs class, add a using directive for Microsoft.FeatureManagement and opt in to feature management:

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

    services.AddFeatureManagement();
}

Feature flags can be configured in a number of ways, such as in appsettings.json, environment variables, and Azure App Configuration.

Open up the appsettings.json file and add a feature to be managed in a new FeatureManagement section, for example the following defines a feature called “Printing”:

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

In the preceding json, the Printing feature is currently enabled.

Now we have a configured feature flag/toggle we can make use of it.

Programmatically Querying Feature Flags in Controllers

If you want to execute conditional logic in a controller based on a feature flag you can do this by first getting a reference to an IFeatureManager. To do this, it can be injected as a dependency into the controller via its constructor:

private readonly IFeatureManager _featureManager;

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

Now the IFeatureManager instance can be used to make decisions by calling the IsEnabledAsync method and providing the string of the feature that was configured in the appsettings.json – in this case “Printing”:

public async Task<IActionResult> Index()
{
    if (await _featureManager.IsEnabledAsync("Printing"))
    {
        ViewData["PrintMessage"] = "On";
    }
    else
    {
        ViewData["PrintMessage"] = "Off";
    }
    
    return View();
}

In the view you could have some HTML to output the contents of the PrintMessage viewdata:

@{
    ViewData["Title"] = "Home Page";
}

<div class="text-center">
    <h1 class="display-4">Welcome</h1>
    <p>Printing is currently @ViewData["PrintMessage"]</p>
</div>

In the controller you could of course do more complex logic such as calling additional services or swapping out different algorithms based on flags.

One thing I don’t like about the FeatureManagement library is that if a flag is not configured or has a typo in the configuration, then the app still runs with the feature disabled. By default I think that the app should error if  a flag is used but not defined, otherwise you may have important features/logic that does not get executed; I feel that the sooner you know about these kind of problems the better. For example some important legal/compliance text might not be shown when it should.

Managing Controllers and Actions Based On Feature Flags

Controller actions (and also entire controllers) can be enabled/disabled based on a feature flag. For example an action could be based on the printing flag by decorating the action method with the [FeatureGate] attribute as follows:

[FeatureGate("Printing")]
public IActionResult Print()
{
    return View();
}

If you try and navigate to the Print page when the flag is disabled then you’ll get a 404.

You can also apply the [FeatureGate] attribute at the controller class level and this will affect all actions contained therein.

Modifying The Generated HTML View Based On Flags

Sometimes you will have UI elements that should be shown or hidden based on a flag.

To control an entire block of HTML you can surround it with the <feature> tag. To enable this you should first modify _ViewImports.cshtml and add the tag helper as shown below:

@using WebApplication1
@using WebApplication1.Models
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, Microsoft.FeatureManagement.AspNetCore

As an example, suppose we have disabled the Print page/action – we would also not want it to be displayed in the menu. We could modify the HTML to make the menu item display conditional on the Printing flag:

<nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
    <div class="container">
        <a class="navbar-brand" asp-area="" asp-controller="Home" asp-action="Index">WebApplication1</a>
        <button class="navbar-toggler" type="button" data-toggle="collapse" data-target=".navbar-collapse" aria-controls="navbarSupportedContent"
                aria-expanded="false" aria-label="Toggle navigation">
            <span class="navbar-toggler-icon"></span>
        </button>
        <div class="navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse">
            <ul class="navbar-nav flex-grow-1">
                <li class="nav-item">
                    <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Index">Home</a>
                </li>
                <li class="nav-item">
                    <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
                </li>
                <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>
            </ul>
        </div>
    </div>
</nav>

Notice in the preceding HTML that the <feature name=”Printing”> element is wrapped around the printing menu option <li>.

Now if the Printing flag is set to true the menu item will be shown, otherwise it won’t appear in the rendered HTML.

SHARE:

C# Source Generators: Less Boilerplate Code, More Productivity

One exciting feature of the upcoming .NET 5 are Source Generators.

Source Generators as the name suggests generate C# source code as part of the compilation process. Code generation is not a new concept in Visual Studio and .NET – for example T4 templates have been around for a while now and enable you to programmatically generate/transform content that can be compiled. There are also techniques such as IL Weaving that tools such as Fody use to manipulate the assembly that is produced from the compilation process.

Source Generators essentially enable you to add new code dynamically as part of the build process, for example adding new classes based on the hand written code in the project.

One thing to note is that Source Generators are designed to add additional generated code and not modify the code you have already written.

Source Generators can examine the existing code you have written and make decisions about what new code to generate, they can also access other files to determine what to generate.

When using Source Generators the sequence looks like this: Begin Compilation –> Any Source Generators Being Used? –> Yes –> Analyse Source Code –> Generate New Source Code –> Add Generated Source Code to Compilation –> Compile Hand-Written and Generated Source Into Output Assembly.

Creating a Simple C# Source Generator

Step 1: Creating the Source Generator

The first step is to actually define the Source Generator, this is done by creating a separate project and once it’s created, referencing it in the project you want to add generated source to.

First off you will need Visual Studio Preview and .NET 5 Preview installed.

Once installed, open VS Preview and create a new C# .NET Standard 2.0 Class Library Project project called “CheeseSourceGenerator”.

Once the project is created, you’ll need to modify the project file by double clicking on it. Source Generators are currently in preview so we can expect better tooling support in the final versions. Change the project file to the following:

<Project Sdk="Microsoft.NET.Sdk">
    <PropertyGroup>
        <TargetFramework>netstandard2.0</TargetFramework>
        <LangVersion>preview</LangVersion>
    </PropertyGroup>
    
    <PropertyGroup>
        <RestoreAdditionalProjectSources>https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet5/nuget/v3/index.json ;$(RestoreAdditionalProjectSources)</RestoreAdditionalProjectSources>
    </PropertyGroup>
    
    <ItemGroup>
        <PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="3.6.0-3.20207.2" PrivateAssets="all" />
        <PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.0.0-beta2.final" PrivateAssets="all" />
    </ItemGroup>
</Project>

Save the project file and build the project to check there are no errors.

The next thing to do is to actually define a Source Generator. To do this add a new class to hold the generator called “Generator” and implement the ISourceGenerator interface and decorate the class with the [Generator] attribute – both of these are from the Microsoft.CodeAnalysis namespace:

using System;
using Microsoft.CodeAnalysis;


namespace CheeseSourceGenerator
{
    [Generator]
    public class Generator : ISourceGenerator
    {
        public void Execute(SourceGeneratorContext context)
        {
            throw new NotImplementedException();
        }

        public void Initialize(InitializationContext context)
        {
            throw new NotImplementedException();
        }
    }
}

The Execute method is where the actual source code generation takes place and the Initialize method allows for some more complex scenarios. In this simple example we’ll just add code to the Generate method and leave the method empty.

In this simple example we’ll just add a new class to the compilation – in this simple example there is no logic involved in the generation:

using System;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;

namespace CheeseSourceGenerator
{
    [Generator]
    public class Generator : ISourceGenerator
    {
        public void Execute(SourceGeneratorContext context)
        {
            const string source = @"
namespace GeneratedCheese
{
    public class CheeseChooser
    {
        public string BestCheeseForPasta => ""Parmigiano-Reggiano"";
        public string BestCheeseForBakedPotato => ""Mature Cheddar"";
    }
}
";
            const string desiredFileName = "CheeseChooser.cs";
            
            SourceText sourceText = SourceText.From(source, Encoding.UTF8); // If no encoding specified then SourceText is not debugable

            // Add the "generated" source to the compilation
            context.AddSource(desiredFileName, sourceText);
        }

        public void Initialize(InitializationContext context)
        {
            // Advanced usage
        }
    }
}

Notice in the preceding code that the SourceGeneratorContext passed to the Execute method is the object that allows us to add the source to the compilation.

Build the project. At this point no source code generation has taken place, we’ve just compiled the generator into an assembly.

Step 2: Register the C# Source Generator in a Project

Add a new .NET Core Console project to the solution called “CheeseConsole”.

Once created add a project reference to the CheeseSourceGenerator project. This will allow the console app to generate source code as part of its compilation.

The project file will now look like:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net5.0</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <ProjectReference Include="..\Cheese\CheeseSourceGenerator.csproj" />
  </ItemGroup>

</Project>

To actually opt-in to the code generation, the CheeseConsole project file needs to modified to add <LangVersion>preview</LangVersion> and change the reference to the generator project to be an analyser reference:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net5.0</TargetFramework>
    <LangVersion>preview</LangVersion>
  </PropertyGroup>

  <ItemGroup>
      <ProjectReference Include="..\Cheese\CheeseSourceGenerator.csproj" 
                        OutputItemType="Analyzer"
                        ReferenceOutputAssembly="false"/>
  </ItemGroup>

</Project>

If you build everything now you should see no errors.

Step 3: Use the Generated Code

In the console app Program.cs add a using directive to the namespace that was used in the source code string, namely using GeneratedCheese;

In the Main method we can now create an instance of a CheeseChooser and make use of it. Add the following code and notice that you get Intellisense support when referencing the  BestCheeseForPasta and BestCheeseForBakedPotato properties.

The Program.cs file should look like:

using System;
using GeneratedCheese;

namespace CheeseConsole
{
    class Program
    {
        static void Main(string[] args)
        {
            var cheeseChooser = new CheeseChooser();

            Console.WriteLine($"The best cheese for pasta is: {cheeseChooser.BestCheeseForPasta}");
            Console.WriteLine($"The best cheese for potato is: {cheeseChooser.BestCheeseForBakedPotato}");

            Console.ReadLine();
        }
    }
}

If you run the console app you should see the following:

The best cheese for pasta is: Parmigiano-Reggiano
The best cheese for potato is: Mature Cheddar

This example is very simplistic but there are a  number of other use cases that I’ll cover in future posts such as:

  • Augmenting existing code
  • Auto-implementing boilerplate code (such as INotifyPropertyChanged)
  • Generation from (non C#) external file
  • Generation from database contents
  • Serialization without reflection
  • etc.

SHARE:

Pretty Method Display in xUnit.net

One little-known feature of the xUnit.net testing framework is the ability to write test method names in a specific way and then have them converted to a ‘pretty’ version for example in Visual Studio Test Explorer.

Take the following test method:

using ClassLibrary1;
using Xunit;

namespace XUnitTestProject2
{
    public class CalculatorShould
    {
        [Fact]
        public void Add2PositiveNumbers()
        {
            var sut = new Calculator();

            sut.Add(1);
            sut.Add(1);

            Assert.Equal(2, sut.Value);
        }
    }
}

By default, this will look like the following screenshot in Visual Studio Test Explorer:

Default xUnit.net Test Method Name Display

The first thing that can be modified to to simplify the test method name display to only display the test method name and not the preceding namespace and class name, for example “XUnitTestProject2.CalculatorShould.Add2PositiveNumbers” becomes more simply “Add2PositiveNumbers” by making a simple configuration change.

Displaying Only Test Method Names in xUnit.net Tests

To control the rendering of method names in xUnit.net, the first thing to do is add a new file called “xunit.runner.json” to the root of the test project and set the Copy To Output Directory property to Copy if newer. This will make this file copy to the output bin directory. Once this is done, if you open the project file you should see something like:

<ItemGroup>
  <None Update="xunit.runner.json">
    <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
  </None>
</ItemGroup>

Next, modify the json file to the following:

{
  "$schema": "https://xunit.net/schema/current/xunit.runner.schema.json",
   "methodDisplay": "method"
}

Notice in the preceding json configuration the methodDisplay has been set to “method”, this will prevent the namespace and class being prepended to the method name in Test Explorer.

Now if you head back to Test Explorer you should see the following:

Method name only display in xUnit.net tests

Enabling Pretty Method Names in xUnit.net

In addition to shortening test method name display we can also make use of xUnit.net’s “pretty method display”.

To enable this feature, modify the json configuration file and add the "methodDisplayOptions": "all" configuration as follows:

{
  "$schema": "https://xunit.net/schema/current/xunit.runner.schema.json",
  "methodDisplay": "method",
  "methodDisplayOptions": "all"
}

Now the previous test can be renamed to “Add_2_positive_numbers” as follows:

[Fact]
public void Add_2_positive_numbers()
{
    var sut = new Calculator();

    sut.Add(1);
    sut.Add(1);

    Assert.Equal(2, sut.Value);
}

In test explorer this test method will show up as “Add 2 positive numbers” as the following screenshot shows:

xUnit.net pretty method display names

You can use other items in the test method name, for example you can use the monikers eq, ne, lt, le, gt, ge that get replaced with =, !=, <, <=, >, >= respectively, for example a test name of “Have_a_value_eq_0_when_multiplied_by_zero” would be displayed as “Have a value = 0 when multiplied by zero”. Here the eq has been replaced with =.

You can also use ASCII or Unicode escape sequences, for example the test name “Divide_by_U00BD” gets displayed as “Divide by ½” and the test “Email_address_should_only_contain_a_single_U0040” gets displayed as “Email address should only contain a single @”, or “The_U2211_of_1U002C_2_and_3_should_be_6” becomes “The ∑ of 1, 2 and 3 should be 6”:

xUnit Pretty methods

You could also combine the "methodDisplay": "classAndMethod" to create something like  and the following:

namespace Given_a_cleared_calculator
{
    public class when_a_number_gt_0_is_added
    {
        [Fact]
        public void then_the_value_should_be_gt_0()
        {
            // etc.
        }

        [Fact]
        public void then_the_value_should_eq_the_one_added()
        {
            // etc.
        }
    }
}

This would produce the following tests in Test Explorer:

xUnit.net Pretty Display Names

If you want to learn more about writing tests with xUnit.net check out my Pluralsight course today.

SHARE:

Simplifying Parameter Null and Other Checks with the Pitcher Library

In a previous post I looked at the GaurdClauses library that can simplify the usual guard checks we sometimes need to write. In the comments someone mentioned the Pitcher library that accomplishes the same thing so I thought I’d check it out here.

First, the NuGet package needs to be installed and a using Pitcher; directive added, then we can make use of the library.

As an example, without the library you might end up with some code like the following:

public static void AddNewPerson(string name, int ageInYears)
{
    if (string.IsNullOrWhiteSpace(name))
    {
        throw new ArgumentException($"Cannot be null, empty, or contain only whitespace.", nameof(name));
    }

    if (ageInYears < 1)
    {
        throw new ArgumentOutOfRangeException(nameof(ageInYears), "Must be greater than zero.");
    }

    // Add to database etc.
}

This is just some boilerplate type code to check for null/empty strings and that the age that’s passed in is positive.

With the Pitcher library this could be refactored to the following (I’ve left the GaurdClauses code commented as a comparison):

public static void AddNewPerson(string name, int ageInYears)
{
    // GuardClauses version:
    // Guard.Against.NullOrWhiteSpace(name, nameof(name));
    // Guard.Against.NegativeOrZero(ageInYears, nameof(ageInYears));


    // Pitcher version:
    Throw.ArgumentNull.WhenNullOrWhiteSpace(name, nameof(name));

    Throw.ArgumentOutOfRange.WhenLessThan(ageInYears, mustBeMoreThan:0, nameof(ageInYears));
    // or more simply:
    Throw.ArgumentOutOfRange.WhenNegativeNumber(ageInYears, nameof(ageInYears));            
}

Pitcher allows you to throw either an ArgumentOutOfRangeException or an ArgumentNullException when using this syntax and also provides ways to throw other exception types when a given condition is true, for example:

Throw.When(ageInYears == 42, new InvalidOperationException("This age has no meaning"));

You can find the source code and more examples on GitHub and don’t forget to say hi/thanks to the project maintainer Alex Kamsteeg and tell him you heard about Pitcher here :)

SHARE:

You Can Watch All My Pluralsight Training Videos for Free This April

No credit card needed, sign up for free now and start watching all my Pluralsight training courses for free.

Some suggestions:

C#

Testing Frameworks:

Testing Tools You May Not Know About:

Expand Your Software Development Horizons:

The following are some suggested courses on topics that may not be on your radar but that your may find interesting.

Skills Paths Featuring My and Other Author’s Courses:

If you want a ready made “curriculum” in the form of a skills path check out the follow paths that feature some of my courses and courses by fellow Pluralsight authors:

P.s. Remember to take care of yourself physically, mentally, and emotionally during these trying times.

From the Pluralsight website: "Free April is open to anyone who is not a current, active subscriber."

SHARE: