Click here to Skip to main content
15,867,308 members
Articles / Web Development / ASP.NET / ASP.NET Core

Featurify - User Specific Feature Flags in ASP.NET Core 2.0+ MVC Applications

Rate me:
Please Sign up or sign in to vote.
5.00/5 (6 votes)
17 Jul 2018CPOL8 min read 14.7K   7   2
User specific implementation of feature flags, which can be used to roll out features targeting specific users without doing multiple releases

What is Featurify?

Featurify is an implementation of feature flags that's super easy to setup and use. It intends to solve one and only one issue where feature flags are user specific. Given this, there is absolutely no dependence on the application settings for the MVC application in question.

Background

Featurify is designed to be light weight and easily integrate with .NET Core 2.0+ web applications. This is intended to be one part of a two piece puzzle. If you are interested in simple application settings based feature flags, this library is not for you! There are a number of other excellent options for that purpose and they are listed in the last section of this article.

First part of this puzzle is to implement the user specific features at the application level. Second part of the puzzle is to design a user interface that would help administrators to define various features and also tie them to users.

The need for this came up for one of the projects I am currently working on to selectively roll out features. There is already a service offering that provides this (feature flags as a service) called launch darkly (docs). But there is one catch - it's not free! The "not free" part led to the creation of this package where the same could be achieved with minimal effort. Of course, it's not as extensive as launch darkly, in order to satisfy the criteria of "being simple".

First Part of the Puzzle

The first part of the puzzle almost exclusively deals with how featurely is put into use. So the feature flag settings for users are dynamically generated based on a few simple steps. But in the next section, I will delve into replacing this simplistic implementation of meta data generation with a more real life approach. So let's get started!

Setting up the Project

In order to demonstrate featurify, I am going to use .NET identity framework template. To create a project that uses the identity framework, follow the steps given below:

  1. Choose ASP.NET Core Web Application

    Image 1

  2. Click the "Change Authentication" button:

    Image 2

  3. Choose the "Individual User Accounts" option:

    Image 3

    Image 4

  4. Open the command prompt in the root folder for the project, run the following command:

    Image 5

    With the project created, the first step in adding feature flags to your application is to add the Featurify nuget package using the nuget package manager or the package manager console.

The snippets given below are from one of the sample projects for featurify and you can view it online here.

> Install-Package Featurify

This command when entered in the package manager console would add the latest version (1.0.1) to the current project.

Infrastructure Classes

In order to implement feature flags in your project, all you need is two classes that implement certain interfaces. With this done, you can use one of the IServiceCollection based extensions to put them into use.

The first class that you have to define is something that will help featurify identify the id for the user currently logged in. This can be literally anything, a guid, an email address or anything that would uniquely identify the logged in user. This class has to implement the IUserInfoStrategy interface. For a project based on the .NET identity framework, the user identifying information is part of the logged in user's claims. And more specifically, the claim with type ClaimTyes.Name contains the email address for the logged in user. For the sake of simplicity, we will use this as the user's unique id. Here is the implementation of this class:

C#
public class IdentityUserInfoStrategy : IUserInfoStrategy
{
   private readonly IHttpContextAccessor accessor;

   public IdentityUserInfoStrategy(IHttpContextAccessor accessor)
   {
       this.accessor = accessor;
   }

   public async Task<string> GetCurrentUserId()
   {
       var claims = accessor.HttpContext.User.Claims.ToList();
       var claim = claims.Single(c => c.Type == ClaimTypes.Name);
       await Task.CompletedTask;
       return claim.Value;
    }
}

The next step in defining the classes as part of the infrastructure requirement is to define a class that would act as the one that would provide the feature flag metadata based on the user id and the feature name. The only requirement is that it should implement the IToggleMetadata interface, as shown below:

C#
public class ToggleMetadata : IToggleMetadata
{
    public string Name { get; set; }
    public bool Value { get; set; }
    public string UserId { get; set; }
}

The next step is to define the class that provides method(s) required to take in the feature name and the user id to return the corresponding metadata. This class should implement the IToggleMetadataFinder interface as shown below. Before that, you might wonder how the feature name is going to be passed, we will get to that soon!

C#
public class UserBasedToggleMetadataFinder : IToggleMetadataFinder
{
    public async Task<IToggleMetadata> FindToggleStatus(string featureName, string userId)
    {
       var required = toggleMetadatas.SingleOrDefault(t => t.Name == featureName && t.UserId == userId);
       await Task.CompletedTask;
       return required;
    }

    private static List<ToggleMetadata> toggleMetadatas = new List<ToggleMetadata>
    {
        new ToggleMetadata { Name = "Featurify.ImportFeature", Value = true, 
                             UserId = "john.doe@company.com" },
        new ToggleMetadata { Name = "Featurify.ExportFeature", Value = false, 
                             UserId = "john.doe@company.com" },
        new ToggleMetadata { Name = "Featurify.EmailFeature", Value = true, 
                             UserId = "john.doe@company.com" }
    };
}

If you notice, a local List<ToggleMetadata> is being used as the source for the metadata, since the emphasis on this article is to demonstrate featurify. In a real life implementation, you can provide a page where an administrator can go and choose the user and a feature to decide the Value. Let me take this opportunity to point out the Name property in the entries listed in the snippet above - Featurify.{feature-name}. Soon, I will explain how these things come together!

With this class, all the necessary infrastructure classes are ready. We can now use the AddFeaturify fluent extension to add featurify to our .NET Core MVC application.

C#
public void ConfigureServices(IServiceCollection services)
{
    // ...
            
    services.AddFeaturify<UserBasedToggleMetadataFinder, IdentityUserInfoStrategy>(options =>
    {
        options.AnyUserVerifier = "*";
        options.UseStrict = false;
     });

     services.AddMvc();
}

Again, it's pretty straight forward - I am using the AddFeaturify extension on IServiceCollection by passing the types for the metadata finder and user identification strategy. You also have to pass an options object that has 2 properties. AnyUserVerifier property identifies the string that will be used to identify if a certain feature is enabled for all the users (irrespective of the id returned by the IdentityUserInfoStrategy). The second property is UseStrict which indicates if an exception should be thrown if the feature metadata is not found for a user. So, if it is set to true and no metadata has been identified for the logged in user, an exception gets thrown.

With the last step, we are all set to use featurify to implement feature flags! I am going to demonstrate this with the help of 3 features we want to expose selectively, based on the logged in user - an import feature, an export feature and an email feature. In order to define these features, all you have to do is create the following classes. Note that all of these classes implement the IFeatureToggle interface.

C#
public class ImportFeature : IFeatureToggle
{
}

public class ExportFeature : IFeatureToggle
{
}

public class EmailFeature : IFeatureToggle
{
}

Let's now see how all of these things tie together! There are two possible ways to put featurify into use. I am going to elaborate them one by one:

Straight from the View

The first step is to add the using statements required. The next step is to inject the IFeaturifyServer in the view. Finally, use the class that corresponds to the feature to selectively expose it to the logged in user. The code snippet given below illustrates the same:

HTML
@using Featurify
@using Featurify.Contracts
@using AspNetIdentityWithFeaturify.Features

@inject IFeaturifyServer Featurify

<strong>ImportFeature</strong>: Enabled

@if (await Featurify.Is<ImportFeature>().Enabled())
{
   <button class="btn btn-success"> Import Users</button>
}
else
{
  <button class="btn btn-danger" disabled="disabled"> Import Users</button>
}

You can also move the using statements to your _ViewImports.cshtml file so that it does not have to be present in every view. After the using statements is the @inject line where I inject the featurify server that will let me verify if a feature is enabled for the user currently logged in. There are a couple of ways the injected IFeaturifyServer can be used. If you do not mind verbosity, you can use the first method shown above. Here, I am using the Is<IFeatureToggle>() extension to indicate the feature I am querying for after which I call the Enabled() method to get the state based on the logged in user.

A number of things come together in order to make this work. They are:

  1. The logged in user id is returned by the IdentityUserInfoStrategy.GetCurrentUserId method
  2. Using the static class used during the Is<T> method call, the feature name is derived as Featurify.ImportFeature (In the next section, I will explain how this can be customized
  3. Using the user id and the feature name from step (1) and (2), UserBasedToggleMetadataFinder.FindToggleStatus method is used to identify the metadata.

Using all of this information (and the options passed during the initial setup), the featurify server deduces the final status of the feature for the user and returns it to the view.

You can also keep things simple by calling the Enabled<T>() method of the featurify server, as shown below:

HTML
@using Featurify
@using Featurify.Contracts
@using AspNetIdentityWithFeaturify.Features

@inject IFeaturifyServer Featurify

<strong>ImportFeature</strong>: Enabled

@if (await Featurify.Enabled<ImportFeature>())
{
   <button class="btn btn-success"> Import Users</button>
}
else
{
   <button class="btn btn-danger" disabled="disabled"> Import Users</button>
}

From the Controller

You can also inject the featurify server in a controller (obviously!) to achieve the same effect as shown below:

C#
[Authorize]
public class HomeController : Controller
{
    private readonly IFeaturifyServer server;

    public HomeController(IFeaturifyServer server)
    {
        this.server = server;
    }

    public async Task<IActionResult> Contact()
    {
        var model = new ContactViewModel
        {
             CanImport = await server.Is<ImportFeature>().Enabled()
        };
        return View(model);
    }
}

In this case, I am using the IFeaturifyServer injected in the constructor to identify if the ImportFeature is enabled and set the result in a model property. This property can now be used in the view to display the feature selectively.

Customizing Feature Name Generation

If you recall the UserBasedToggleMetadataFinder class has a private List<ToggleMetadata> to provide the metadata information for various users. In this, the feature name specified follows the format Featurify.{feature-name}. This is the default and can be customized to match your requirements. To do this, you have to create a class that implements the IFeatureNameTransformer interface as part of the infrastructure classes and specify this during the call to AddFeaturify, as illustrated below:

C#
public class CustomNameTransformer : IFeatureNameTransformer
{
    public string TransformFeatureName(string featureName)
    {
       return $"MyProduct.{featureName}";
    }
}

Consider the CustomNameTransformer. Here, the TransformFeatureName method returns the customized feature name. Now that this class is available, I am going to specify this as part of the AddFeaturify call.

C#
public void ConfigureServices(IServiceCollection services)
{
    // ...
            
    services.AddFeaturify<UserBasedToggleMetadataFinder, 
                          IdentityUserInfoStrategy, 
                          CustomNameTransformer>(options =>
    {
        options.AnyUserVerifier = "*";
        options.UseStrict = false;
     });

     services.AddMvc();
}

Implementations Based on Application Settings

  • This site lists various implementations of feature flags for various languages
  • This is the implementation that is most active (FeatureToggle library)
  • This repository has an implementation of feature flags and also lists various other implementations of feature flags in .NET.

Credits

Conclusion

The "second" part of the puzzle, designing the user interface is not covered in this article since it's kind of out of scope for featurify. But it should be simple straight forward, since all that is needed is a page to manage features and a page to link a feature to a user and a page to list them. Any feedback/ comments are welcome. Thanks for reading!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United States United States
Just another passionate software developer!

Some of the contributions to the open source world - a blog engine written in MVC 4 - sBlog.Net. Check it out here. For the codeproject article regarding sBlog.Net click here!

(Figuring out this section!)

Comments and Discussions

 
QuestionElectronic component? Pin
Member 1390489817-Jul-18 19:32
Member 1390489817-Jul-18 19:32 
AnswerRe: Electronic component? Pin
Karthik. A18-Jul-18 2:51
Karthik. A18-Jul-18 2:51 
GeneralMessage Closed Pin
18-Jul-18 18:04
Member 1390489818-Jul-18 18:04 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.