Git Product home page Git Product logo

microsoft / featuremanagement-dotnet Goto Github PK

View Code? Open in Web Editor NEW
986.0 33.0 110.0 2.18 MB

Microsoft.FeatureManagement provides standardized APIs for enabling feature flags within applications. Utilize this library to secure a consistent experience when developing applications that use patterns such as beta access, rollout, dark deployments, and more.

License: MIT License

Batchfile 0.01% PowerShell 0.98% C# 98.94% HTML 0.07%

featuremanagement-dotnet's Introduction

.NET Feature Management

Microsoft.FeatureManagement Microsoft.FeatureManagement.AspNetCore

Feature flags provide a way for .NET and ASP.NET Core applications to turn features on or off dynamically. Developers can use feature flags in simple use cases like conditional statements to more advanced scenarios like conditionally adding routes or MVC filters. Feature flags are built on top of the .NET Core configuration system. Any .NET Core configuration provider is capable of acting as the backbone for feature flags.

Here are some of the benefits of using this library:

  • A common convention for feature management
  • Low barrier-to-entry
    • Built on IConfiguration
    • Supports JSON file feature flag setup
  • Feature Flag lifetime management
    • Configuration values can change in real-time, feature flags can be consistent across the entire request
  • Simple to Complex Scenarios Covered
    • Toggle on/off features through declarative configuration file
    • Dynamically evaluate state of feature based on call to server
  • API extensions for ASP.NET Core and MVC framework
    • Routing
    • Filters
    • Action Attributes

API Reference: https://go.microsoft.com/fwlink/?linkid=2091700

Index

Feature Flags

Feature flags are composed of two parts, a name and a list of feature-filters that are used to turn the feature on.

Feature Filters

Feature filters define a scenario for when a feature should be enabled. When a feature is evaluated for whether it is on or off, its list of feature filters is traversed until one of the filters decides the feature should be enabled. At this point the feature is considered enabled and traversal through the feature filters stops. If no feature filter indicates that the feature should be enabled, then it will be considered disabled.

As an example, a Microsoft Edge browser feature filter could be designed. This feature filter would activate any features it is attached to as long as an HTTP request is coming from Microsoft Edge.

Feature Flag Configuration

The .NET Core configuration system is used to determine the state of feature flags. The foundation of this system is IConfiguration. Any provider for IConfiguration can be used as the feature state provider for the feature flag library. This enables scenarios ranging from appsettings.json to Azure App Configuration and more.

Feature Flag Declaration

The feature management library supports appsettings.json as a feature flag source since it is a provider for .NET Core's IConfiguration system. Below we have an example of the format used to set up feature flags in a json file.

{
    "Logging": {
        "LogLevel": {
            "Default": "Warning"
        }
    },

    // Define feature flags in a json file
    "FeatureManagement": {
        "FeatureT": {
            "EnabledFor": [
                {
                    "Name": "AlwaysOn"
                }
            ]
        },
        "FeatureU": {
            "EnabledFor": []
        },
        "FeatureV": {
            "EnabledFor": [
                {
                    "Name": "TimeWindow",
                    "Parameters": {
                        "Start": "Wed, 01 May 2019 13:59:59 GMT",
                        "End": "Mon, 01 Jul 2019 00:00:00 GMT"
                    }
                }
            ]
        }
    }
}

The FeatureManagement section of the json document is used by convention to load feature flag settings. In the section above, we see that we have provided three different features. Features define their feature filters using the EnabledFor property. In the feature filters for FeatureT we see AlwaysOn. This feature filter is built-in and if specified will always enable the feature. The AlwaysOn feature filter does not require any configuration, so it only has the Name property. FeatureU has no filters in its EnabledFor property and thus will never be enabled. Any functionality that relies on this feature being enabled will not be accessible as long as the feature filters remain empty. However, as soon as a feature filter is added that enables the feature it can begin working. FeatureV specifies a feature filter named TimeWindow. This is an example of a configurable feature filter. We can see in the example that the filter has a Parameters property. This is used to configure the filter. In this case, the start and end times for the feature to be active are configured.

The detailed schema of the FeatureManagement section can be found here.

Advanced: The usage of colon ':' in feature flag names is forbidden.

On/Off Declaration

The following snippet demonstrates an alternative way to define a feature that can be used for on/off features.

{
    "Logging": {
        "LogLevel": {
            "Default": "Warning"
        }
    },

    // Define feature flags in config file
    "FeatureManagement": {
        "FeatureT": true, // On feature
        "FeatureX": false // Off feature
    }
}

RequirementType

The RequirementType property of a feature flag is used to determine if the filters should use Any or All logic when evaluating the state of a feature. If RequirementType is not specified, the default value is Any.

  • Any means only one filter needs to evaluate to true for the feature to be enabled.
  • All means every filter needs to evaluate to true for the feature to be enabled.

A RequirementType of All changes the traversal. First, if there are no filters, the feature will be disabled. Then, the feature-filters are traversed until one of the filters decides that the feature should be disabled. If no filter indicates that the feature should be disabled, then it will be considered enabled.

"FeatureW": {
    "RequirementType": "All",
    "EnabledFor": [
        {
            "Name": "TimeWindow",
            "Parameters": {
                "Start": "Mon, 01 May 2023 13:59:59 GMT",
                "End": "Sat, 01 Jul 2023 00:00:00 GMT"
            }
        },
        {
            "Name": "Percentage",
            "Parameters": {
                "Value": "50"
            }
        }
    ]
}

In the above example, FeatureW specifies a RequirementType of All, meaning all of its filters must evaluate to true for the feature to be enabled. In this case, the feature will be enabled for 50% of users during the specified time window.

Microsoft Feature Management Schema

The feature management library also supports the usage of the Microsoft Feature Management schema to declare feature flags. This schema is language agnostic in origin and is supported by all Microsoft feature management libraries.

{
    "feature_management": {
        "feature_flags": [
            {
                "id": "FeatureT",
                "enabled": true,
                "conditions": {
                    "client_filters": [
                        {  
                            "name": "Microsoft.TimeWindow",
                            "parameters": {
                                "Start": "Mon, 01 May 2023 13:59:59 GMT",
                                "End": "Sat, 01 Jul 2023 00:00:00 GMT"
                            }
                        }
                    ]
                }
            }
        ]
    }
}

Note: If the feature_management section can be found in the configuration, the FeatureManagement section will be ignored.

Consumption

The basic form of feature management is checking if a feature flag is enabled and then performing actions based on the result. This is done through the IFeatureManager's IsEnabledAsync method.

IFeatureManager featureManager;if (await featureManager.IsEnabledAsync("FeatureX"))
{
    // Do something
}

Service Registration

Feature management relies on .NET Core dependency injection. We can register the feature management services using standard conventions.

using Microsoft.FeatureManagement;

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddFeatureManagement();
    }
}

By default, the feature manager retrieves feature flag configuration from the "FeatureManagement" section of the .NET Core configuration data. If the "FeatureManagement" section does not exist, the configuration will be considered empty.

You can also specify that feature flag configuration should be retrieved from a different configuration section by passing the section to AddFeatureManagement. The following example tells the feature manager to read from a different section called "MyFeatureFlags" instead:

services.AddFeatureManagement(configuration.GetSection("MyFeatureFlags"));

Dependency Injection

When using the feature management library with MVC, the IFeatureManager can be obtained through dependency injection.

public class HomeController : Controller
{
    private readonly IFeatureManager _featureManager;
    
    public HomeController(IFeatureManager featureManager)
    {
        _featureManager = featureManager;
    }
}

Scoped Feature Management Services

The AddFeatureManagement method adds feature management services as singletons within the application, but there are scenarios where it may be necessary for feature management services to be added as scoped services instead. For example, users may want to use feature filters which consume scoped services for context information. In this case, the AddScopedFeatureManagement method should be used instead. This will ensure that feature management services, including feature filters, are added as scoped services.

services.AddScopedFeatureManagement();

ASP.NET Core Integration

The feature management library provides functionality in ASP.NET Core and MVC to enable common feature flag scenarios in web applications. These capabilities are available by referencing the Microsoft.FeatureManagement.AspNetCore NuGet package.

Controllers and Actions

MVC controller and actions can require that a given feature, or one of any list of features, be enabled in order to execute. This can be done by using a FeatureGateAttribute, which can be found in the Microsoft.FeatureManagement.Mvc namespace.

[FeatureGate("FeatureX")]
public class HomeController : Controller
{}

The HomeController above is gated by "FeatureX". "FeatureX" must be enabled before any action the HomeController contains can be executed.

[FeatureGate("FeatureX")]
public IActionResult Index()
{
    return View();
}

The Index MVC action above requires "FeatureX" to be enabled before it can be executed.

Disabled Action Handling

When an MVC controller or action is blocked because none of the features it specifies are enabled, a registered IDisabledFeaturesHandler will be invoked. By default, a minimalistic handler is registered which returns HTTP 404. This can be overridden using the IFeatureManagementBuilder when registering feature flags.

public interface IDisabledFeaturesHandler
{
    Task HandleDisabledFeature(IEnumerable<string> features, ActionExecutingContext context);
}

View

In MVC views <feature> tags can be used to conditionally render content based on whether a feature is enabled or not.

<feature name="FeatureX">
  <p>This can only be seen if 'FeatureX' is enabled.</p>
</feature>

You can also negate the tag helper evaluation to display content when a feature or set of features are disabled. By setting negate="true" in the example below, the content is only rendered if FeatureX is disabled.

<feature negate="true" name="FeatureX">
  <p>This can only be seen if 'FeatureX' is disabled.</p>
</feature>

The <feature> tag can reference multiple features by specifying a comma separated list of features in the name attribute.

<feature name="FeatureX,FeatureY">
  <p>This can only be seen if 'FeatureX' and 'FeatureY' are enabled.</p>
</feature>

By default, all listed features must be enabled for the feature tag to be rendered. This behavior can be overidden by adding the requirement attribute as seen in the example below.

<feature name="FeatureX,FeatureY" requirement="Any">
  <p>This can only be seen if either 'FeatureX' or 'FeatureY' or both are enabled.</p>
</feature>

The <feature> tag requires a tag helper to work. This can be done by adding the feature management tag helper to the ViewImports.cshtml file.

@addTagHelper *, Microsoft.FeatureManagement.AspNetCore

MVC Filters

MVC action filters can be set up to conditionally execute based on the state of a feature. This is done by registering MVC filters in a feature aware manner. The feature management pipeline supports async MVC Action filters, which implement IAsyncActionFilter.

services.AddMvc(o => 
{
    o.Filters.AddForFeature<SomeMvcFilter>("FeatureX");
});

The code above adds an MVC filter named SomeMvcFilter. This filter is only triggered within the MVC pipeline if the feature it specifies, "FeatureX", is enabled.

Razor Pages

MVC Razor pages can require that a given feature, or one of any list of features, be enabled in order to execute. This can be done by using a FeatureGateAttribute, which can be found in the Microsoft.FeatureManagement.Mvc namespace.

[FeatureGate("FeatureX")]
public class IndexModel : PageModel
{
    public void OnGet()
    {
    }
}

The code above sets up a Razor page to require the "FeatureX" to be enabled. If the feature is not enabled, the page will generate an HTTP 404 (NotFound) result.

When used on Razor pages, the FeatureGateAttribute must be placed on the page handler type. It cannot be placed on individual handler methods.

Application building

The feature management library can be used to add application branches and middleware that execute conditionally based on feature state.

app.UseMiddlewareForFeature<ThirdPartyMiddleware>("FeatureX");

With the above call, the application adds a middleware component that only appears in the request pipeline if the feature "FeatureX" is enabled. If the feature is enabled/disabled during runtime, the middleware pipeline can be changed dynamically.

This builds off the more generic capability to branch the entire application based on a feature.

app.UseForFeature(featureName, appBuilder => 
{
    appBuilder.UseMiddleware<T>();
});

Implementing a Feature Filter

Creating a feature filter provides a way to enable features based on criteria that you define. To implement a feature filter, the IFeatureFilter interface must be implemented. IFeatureFilter has a single method named EvaluateAsync. When a feature specifies that it can be enabled for a feature filter, the EvaluateAsync method is called. If EvaluateAsync returns true it means the feature should be enabled.

The following snippet demonstrates how to add a customized feature filter MyCriteriaFilter.

services.AddFeatureManagement()
        .AddFeatureFilter<MyCriteriaFilter>();

Feature filters are registered by calling AddFeatureFilter<T> on the IFeatureManagementBuilder returned from AddFeatureManagement. These feature filters have access to the services that exist within the service collection that was used to add feature flags. Dependency injection can be used to retrieve these services.

Note: When filters are referenced in feature flag settings (e.g. appsettings.json), the Filter part of the type name should be omitted. Please refer to the Filter Alias Attribute section below for more details.

Parameterized Feature Filters

Some feature filters require parameters to decide whether a feature should be turned on or not. For example, a browser feature filter may turn on a feature for a certain set of browsers. It may be desired that Edge and Chrome browsers enable a feature, while Firefox does not. To do this a feature filter can be designed to expect parameters. These parameters would be specified in the feature configuration, and in code would be accessible via the FeatureFilterEvaluationContext parameter of IFeatureFilter.EvaluateAsync.

public class FeatureFilterEvaluationContext
{
    /// <summary>
    /// The name of the feature being evaluated.
    /// </summary>
    public string FeatureName { get; set; }

    /// <summary>
    /// The settings provided for the feature filter to use when evaluating whether the feature should be enabled.
    /// </summary>
    public IConfiguration Parameters { get; set; }
}

FeatureFilterEvaluationContext has a property named Parameters. These parameters represent a raw configuration that the feature filter can use to decide how to evaluate whether the feature should be enabled or not. To use the browser feature filter as an example once again, the filter could use Parameters to extract a set of allowed browsers that would have been specified for the feature and then check if the request is being sent from one of those browsers.

[FilterAlias("Browser")]
public class BrowserFilter : IFeatureFilter
{public Task<bool> EvaluateAsync(FeatureFilterEvaluationContext context)
    {
        BrowserFilterSettings settings = context.Parameters.Get<BrowserFilterSettings>() ?? new BrowserFilterSettings();

        //
        // Here we would use the settings and see if the request was sent from any of BrowserFilterSettings.AllowedBrowsers
    }
}

Filter Alias Attribute

When a feature filter is registered to be used for a feature flag, the alias used in configuration is the name of the feature filter type with the Filter suffix, if any, removed. For example, MyCriteriaFilter would be referred to as MyCriteria in configuration.

"MyFeature": {
    "EnabledFor": [
        {
            "Name": "MyCriteria"
        }
    ]
}

This can be overridden through the use of the FilterAliasAttribute. A feature filter can be decorated with this attribute to declare the name that should be used in configuration to reference this feature filter within a feature flag.

Missing Feature Filters

If a feature is configured to be enabled for a specific feature filter and that feature filter hasn't been registered, then an exception will be thrown when the feature is evaluated. The exception can be disabled by using the feature management options.

services.Configure<FeatureManagementOptions>(options =>
{
    options.IgnoreMissingFeatureFilters = true;
});

Using HttpContext

Feature filters can evaluate whether a feature should be enabled based on the properties of an HTTP Request. This is performed by inspecting the HTTP Context. A feature filter can get a reference to the HTTP Context by obtaining an IHttpContextAccessor through dependency injection.

public class BrowserFilter : IFeatureFilter
{
    private readonly IHttpContextAccessor _httpContextAccessor;

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

The IHttpContextAccessor must be added to the dependency injection container on startup for it to be available. It can be registered in the IServiceCollection using the following method.

public void ConfigureServices(IServiceCollection services)
{
    …
    services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();}

Advanced: IHttpContextAccessor/HttpContext should not be used in the Razor components of server-side Blazor apps. The recommended approach for passing http context in Blazor apps is to copy the data into a scoped service. For Blazor apps, AddScopedFeatureManagement should be used to register the feature management services. Please refer to the Scoped Feature Management Services section for more details.

Providing a Context For Feature Evaluation

In console applications there is no ambient context such as HttpContext that feature filters can acquire and utilize to check if a feature should be on or off. In this case, applications need to provide an object representing a context into the feature management system for use by feature filters. This is done by using IFeatureManager.IsEnabledAsync<TContext>(string featureName, TContext appContext). The appContext object that is provided to the feature manager can be used by feature filters to evaluate the state of a feature.

MyAppContext context = new MyAppContext
{
    AccountId = current.Id;
}

if (await featureManager.IsEnabledAsync(feature, context))
{}

Contextual Feature Filters

Contextual feature filters implement the IContextualFeatureFilter<TContext> interface. These special feature filters can take advantage of the context that is passed in when IFeatureManager.IsEnabledAsync<TContext> is called. The TContext type parameter in IContextualFeatureFilter<TContext> describes what context type the filter is capable of handling. This allows the developer of a contextual feature filter to describe what is required of those who wish to utilize it. Since every type is a descendant of object, a filter that implements IContextualFeatureFilter<object> can be called for any provided context. To illustrate an example of a more specific contextual feature filter, consider a feature that is enabled if an account is in a configured list of enabled accounts.

public interface IAccountContext
{
    string AccountId { get; set; }
}

[FilterAlias("AccountId")]
class AccountIdFilter : IContextualFeatureFilter<IAccountContext>
{
    public Task<bool> EvaluateAsync(FeatureFilterEvaluationContext featureEvaluationContext, IAccountContext accountId)
    {
        //
        // Evaluate if the feature should be on with the help of the provided IAccountContext
    }
}

We can see that the AccountIdFilter requires an object that implements IAccountContext to be provided to be able to evalute the state of a feature. When using this feature filter, the caller needs to make sure that the passed in object implements IAccountContext.

Note: Only a single feature filter interface can be implemented by a single type. Trying to add a feature filter that implements more than a single feature filter interface will result in an ArgumentException.

Using Contextual and Non-contextual Filters With the Same Alias

Filters of IFeatureFilter and IContextualFeatureFilter can share the same alias. Specifically, you can have one filter alias shared by 0 or 1 IFeatureFilter and 0 or N IContextualFeatureFilter<ContextType>, so long as there is at most one applicable filter for ContextType.

The following passage describes the process of selecting a filter when contextual and non-contextual filters of the same name are registered in an application.

Let's say you have a non-contextual filter called FilterA and two contextual filters FilterB and FilterC which accept TypeB and TypeC contexts respectively. All three filters share the same alias SharedFilterName.

You also have a feature flag MyFeature which uses the feature filter SharedFilterName in its configuration.

If all of three filters are registered:

  • When you call IsEnabledAsync("MyFeature"), the FilterA will be used to evaluate the feature flag.
  • When you call IsEnabledAsync("MyFeature", context), if context's type is TypeB, FilterB will be used. If context's type is TypeC, FilterC will be used.
  • When you call IsEnabledAsync("MyFeature", context), if context's type is TypeF, FilterA will be used.

Built-In Feature Filters

There a few feature filters that come with the Microsoft.FeatureManagement package: PercentageFilter, TimeWindowFilter, ContextualTargetingFilter and TargetingFilter. All filters, except for the TargetingFilter, are added automatically when feature management is registered. The TargetingFilter is added with the WithTargeting method that is detailed in the Targeting section below.

Each of the built-in feature filters have their own parameters. Here is the list of feature filters along with examples.

Microsoft.Percentage

This filter provides the capability to enable a feature based on a set percentage.

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

Microsoft.TimeWindow

This filter provides the capability to enable a feature based on a time window. If only End is specified, the feature will be considered on until that time. If only Start is specified, the feature will be considered on at all points after that time.

"EnhancedPipeline": {
    "EnabledFor": [
        {
            "Name": "Microsoft.TimeWindow",
            "Parameters": {
                "Start": "Wed, 01 May 2019 13:59:59 GMT",
                "End": "Mon, 01 Jul 2019 00:00:00 GMT"
            }
        }
    ]
}

The time window can be configured to recur periodically. This can be useful for the scenarios where one may need to turn on a feature during a low or high traffic period of a day or certain days of a week. To expand the individual time window to recurring time windows, the recurrence rule should be specified in the Recurrence parameter.

Note: Start and End must be both specified to enable Recurrence.

"EnhancedPipeline": {
    "EnabledFor": [
        {
            "Name": "Microsoft.TimeWindow",
            "Parameters": {
                "Start": "Fri, 22 Mar 2024 20:00:00 GMT",
                "End": "Sat, 23 Mar 2024 02:00:00 GMT",
                "Recurrence": {
                    "Pattern": {
                        "Type": "Daily",
                        "Interval": 1
                    },
                    "Range": {
                        "Type": "NoEnd"
                    }
                }
            }
        }
    ]
}

The Recurrence settings is made up of two parts: Pattern (how often the time window will repeat) and Range (for how long the recurrence pattern will repeat).

Recurrence Pattern

There are two possible recurrence pattern types: Daily and Weekly. For example, a time window could repeat "every day", "every 3 days", "every Monday" or "on Friday per 2 weeks".

Depending on the type, certain fields of the Pattern are required, optional, or ignored.

  • Daily

    The daily recurrence pattern causes the time window to repeat based on a number of days between each occurrence.

    Property Relevance Description
    Type Required Must be set to Daily.
    Interval Optional Specifies the number of days between each occurrence. Default value is 1.
  • Weekly

    The weekly recurrence pattern causes the time window to repeat on the same day or days of the week, based on the number of weeks between each set of occurrences.

    Property Relevance Description
    Type Required Must be set to Weekly.
    DaysOfWeek Required Specifies on which day(s) of the week the event occurs.
    Interval Optional Specifies the number of weeks between each set of occurrences. Default value is 1.
    FirstDayOfWeek Optional Specifies which day is considered the first day of the week. Default value is Sunday.

    The following example will repeat the time window every other Monday and Tuesday

    "Pattern": {
        "Type": "Weekly",
        "Interval": 2,
        "DaysOfWeek": ["Monday", "Tuesday"]
    }

Note: Start must be a valid first occurrence which fits the recurrence pattern. Additionally, the duration of the time window cannot be longer than how frequently it occurs. For example, it is invalid to have a 25-hour time window recur every day.

Recurrence Range

There are three possible recurrence range type: NoEnd, EndDate and Numbered.

  • NoEnd

    The NoEnd range causes the recurrence to occur indefinitely.

    Property Relevance Description
    Type Required Must be set to NoEnd.
  • EndDate

    The EndDate range causes the time window to occur on all days that fit the applicable pattern until the end date.

    Property Relevance Description
    Type Required Must be set to EndDate.
    EndDate Required Specifies the date time to stop applying the pattern. Note that as long as the start time of the last occurrence falls before the end date, the end time of that occurrence is allowed to extend beyond it.

    The following example will repeat the time window every day until the last occurrence happens on April 1st, 2024.

    "Start": "Fri, 22 Mar 2024 18:00:00 GMT",
    "End": "Fri, 22 Mar 2024 20:00:00 GMT",
    "Recurrence":{
        "Pattern": {
            "Type": "Daily",
            "Interval": 1
        },
        "Range": {
            "Type": "EndDate",
            "EndDate": "Mon, 1 Apr 2024 20:00:00 GMT"
        }
    }
  • Numbered

    The Numbered range causes the time window to occur a fixed number of times (based on the pattern).

    Property Relevance Description
    Type Required Must be set to Numbered.
    NumberOfOccurrences Required Specifies the number of occurrences.

    The following example will repeat the time window on Monday and Tuesday until the there are 3 occurrences, which respectively happens on April 1st(Mon), April 2nd(Tue) and April 8th(Mon).

    "Start": "Mon, 1 Apr 2024 18:00:00 GMT",
    "End": "Mon, 1 Apr 2024 20:00:00 GMT",
    "Recurrence":{
        "Pattern": {
            "Type": "Weekly",
            "Interval": 1,
            "DaysOfWeek": ["Monday", "Tuesday"]
        },
        "Range": {
            "Type": "Numbered",
            "NumberOfOccurrences": 3
        }
    }

To create a recurrence rule, you must specify both Pattern and Range. Any pattern type can work with any range type.

Advanced: The time zone offset of the Start property will apply to the recurrence settings.

Microsoft.Targeting

This filter provides the capability to enable a feature for a target audience. An in-depth explanation of targeting is explained in the targeting section below. The filter parameters include an audience object which describes users, groups, excluded users/groups, and a default percentage of the user base that should have access to the feature. Each group object that is listed in the target audience must also specify what percentage of the group's members should have access. If a user is specified in the exclusion section, either directly or if the user is in an excluded group, the feature will be disabled. Otherwise, if a user is specified in the users section directly, or if the user is in the included percentage of any of the group rollouts, or if the user falls into the default rollout percentage then that user will have the feature enabled.

"EnhancedPipeline": {
    "EnabledFor": [
        {
            "Name": "Microsoft.Targeting",
            "Parameters": {
                "Audience": {
                    "Users": [
                        "Jeff",
                        "Alicia"
                    ],
                    "Groups": [
                        {
                            "Name": "Ring0",
                            "RolloutPercentage": 100
                        },
                        {
                            "Name": "Ring1",
                            "RolloutPercentage": 50
                        }
                    ],
                    "DefaultRolloutPercentage": 20,
                    "Exclusion": {
                        "Users": [
                            "Ross"
                        ],
                        "Groups": [
                            "Ring2"
                        ]
                    }
                }
            }
        }
    ]
}

Feature Filter Alias Namespaces

All of the built-in feature filter alias' are in the 'Microsoft' feature filter namespace. This is to prevent conflicts with other feature filters that may share the same simple alias. The segments of a feature filter namespace are split by the '.' character. A feature filter can be referenced by its fully qualified alias such as 'Microsoft.Percentage' or by the last segment which in the case of 'Microsoft.Percentage' is 'Percentage'.

Targeting

Targeting is a feature management strategy that enables developers to progressively roll out new features to their user base. The strategy is built on the concept of targeting a set of users known as the target audience. An audience is made up of specific users, groups, excluded users/groups, and a designated percentage of the entire user base. The groups that are included in the audience can be broken down further into percentages of their total members.

The following steps demonstrate an example of a progressive rollout for a new 'Beta' feature:

  1. Individual users Jeff and Alicia are granted access to the Beta
  2. Another user, Mark, asks to opt-in and is included.
  3. Twenty percent of a group known as "Ring1" users are included in the Beta.
  4. The number of "Ring1" users included in the beta is bumped up to 100 percent.
  5. Five percent of the user base is included in the beta.
  6. The rollout percentage is bumped up to 100 percent and the feature is completely rolled out.

This strategy for rolling out a feature is built-in to the library through the included Microsoft.Targeting feature filter.

Targeting in a Web Application

An example web application that uses the targeting feature filter is available in the FeatureFlagDemo example project.

To begin using the TargetingFilter in an application it must be added to the application's service collection just as any other feature filter. Unlike other built-in filters, the TargetingFilter relies on another service to be added to the application's service collection. That service is an ITargetingContextAccessor.

The implementation type used for the ITargetingContextAccessor service must be implemented by the application that is using the targeting filter. Here is an example setting up feature management in a web application to use the TargetingFilter with an implementation of ITargetingContextAccessor called HttpContextTargetingContextAccessor.

services.AddFeatureManagement()
        .WithTargeting<HttpContextTargetingContextAccessor>();

The targeting context accessor and TargetingFilter are registered by calling WithTargeting<T> on the IFeatureManagementBuilder.

ITargetingContextAccessor

To use the TargetingFilter in a web application, an implementation of ITargetingContextAccessor is required. This is because when a targeting evaluation is being performed, information such as what user is currently being evaluated is needed. This information is known as the targeting context. Different web applications may extract this information from different places. Some common examples of where an application may pull the targeting context are the request's HTTP context or a database.

An example that extracts targeting context information from the application's HTTP context is included in the FeatureFlagDemo example project. This method relies on the use of IHttpContextAccessor which is discussed here.

Targeting in a Console Application

The targeting filter relies on a targeting context to evaluate whether a feature should be turned on. This targeting context contains information such as what user is currently being evaluated, and what groups the user in. In console applications there is typically no ambient context available to flow this information into the targeting filter, thus it must be passed directly when FeatureManager.IsEnabledAsync is called. This is supported through the use of the ContextualTargetingFilter. Applications that need to float the targeting context into the feature manager should use this instead of the TargetingFilter.

Since ContextualTargetingFilter is an IContextualTargetingFilter<ITargetingContext>, an implementation of ITargetingContext must be passed in to IFeatureManager.IsEnabledAsync for it to be able to evaluate and turn a feature on.

IFeatureManager fm;// userId and groups defined somewhere earlier in application
TargetingContext targetingContext = new TargetingContext
{
   UserId = userId,
   Groups = groups
};

await fm.IsEnabledAsync(featureName, targetingContext);

The ContextualTargetingFilter still uses the feature filter alias Microsoft.Targeting, so the configuration for this filter is consistent with what is mentioned in that section.

An example that uses the ContextualTargetingFilter in a console application is available in the TargetingConsoleApp example project.

Targeting Evaluation Options

Options are available to customize how targeting evaluation is performed across all features. These options can be configured when setting up feature management.

services.Configure<TargetingEvaluationOptions>(options =>
{
    options.IgnoreCase = true;
});

Targeting Exclusion

When defining an Audience, users and groups can be excluded from the audience. This is useful when a feature is being rolled out to a group of users, but a few users or groups need to be excluded from the rollout. Exclusion is defined by adding a list of users and groups to the Exclusion property of the audience.

"Audience": {
    "Users": [
        "Jeff",
        "Alicia"
    ],
    "Groups": [
        {
            "Name": "Ring0",
            "RolloutPercentage": 100
        }
    ],
    "DefaultRolloutPercentage": 0
    "Exclusion": {
        "Users": [
            "Mark"
        ]
    }
}

In the above example, the feature will be enabled for users named Jeff and Alicia. It will also be enabled for users in the group named Ring0. However, if the user is named Mark, the feature will be disabled, regardless of if they are in the group Ring0 or not. Exclusions take priority over the rest of the targeting filter.

Caching

Feature state is provided by the IConfiguration system. Any caching and dynamic updating is expected to be handled by configuration providers. The feature manager asks IConfiguration for the latest value of a feature's state whenever a feature is checked to be enabled.

Snapshot

There are scenarios which require the state of a feature to remain consistent during the lifetime of a request. The values returned from the standard IFeatureManager may change if the IConfiguration source which it is pulling from is updated during the request. This can be prevented by using IFeatureManagerSnapshot. IFeatureManagerSnapshot can be retrieved in the same manner as IFeatureManager. IFeatureManagerSnapshot implements the interface of IFeatureManager, but it caches the first evaluated state of a feature during a request and will return the same state of a feature during its lifetime.

Custom Feature Providers

Implementing a custom feature provider enables developers to pull feature flags from sources such as a database or a feature management service. The included feature provider that is used by default pulls feature flags from .NET Core's configuration system. This allows for features to be defined in an appsettings.json file or in configuration providers like Azure App Configuration. This behavior can be substituted to provide complete control of where feature definitions are read from.

To customize the loading of feature definitions, one must implement the IFeatureDefinitionProvider interface.

public interface IFeatureDefinitionProvider
{
    Task<FeatureDefinition> GetFeatureDefinitionAsync(string featureName);

    IAsyncEnumerable<FeatureDefinition> GetAllFeatureDefinitionsAsync();
}

To use an implementation of IFeatureDefinitionProvider it must be added into the service collection before adding feature management. The following example adds an implementation of IFeatureDefinitionProvider named InMemoryFeatureDefinitionProvider.

services.AddSingleton<IFeatureDefinitionProvider, InMemoryFeatureDefinitionProvider>()
        .AddFeatureManagement()

Contributing

This project welcomes contributions and suggestions. Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us the rights to use your contribution. For details, visit https://cla.microsoft.com.

When you submit a pull request, a CLA-bot will automatically determine whether you need to provide a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions provided by the bot. You will only need to do this once across all repos using our CLA.

This project has adopted the Microsoft Open Source Code of Conduct. For more information see the Code of Conduct FAQ or contact [email protected] with any additional questions or comments.

featuremanagement-dotnet's People

Contributors

abhilasharora avatar amerjusupovic avatar andreytretyak avatar antmdvs avatar avanigupta avatar brental avatar davkean avatar jason-roberts avatar jimmyca15 avatar josephwoodward avatar kimsey0 avatar microsoft-github-policy-service[bot] avatar microsoftopensource avatar msftgits avatar neiljohari avatar rossgrambo avatar weihanli avatar zhiyuanliang-ms avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

featuremanagement-dotnet's Issues

Permanent caching?

Hi,

I have an issue where feature flags appear to be permanently cached throughout the lifetime of the ASP.NET Core session.

WebApi.cs file has:

 return new WebHostBuilder()
                                    .UseKestrel()
                                    .ConfigureAppConfiguration((builderContext, config) =>
                                    {
                                        config.AddJsonFile(environment, serviceContext);
                                        var settings = config.Build();
                                        AppConfigExists = !string.IsNullOrWhiteSpace(settings["VDSSettings:AppConfig"]);
                                        if (AppConfigExists)
                                        {
                                            config.AddAzureAppConfiguration(options => {
                                               options.Connect(settings["VDSSettings:AppConfig"])
                                                      .UseFeatureFlags();
                                            });
                                        }
                                    })

UseFeatureFlags had CacheExpirationTime set at one point (not above) but it didn't seem to make a difference.

The ASP.NET core web API is hosted in Service Fabric on Azure.

To reproduce:

  1. Make a request
  2. Feature flags are set as expected (in the App Configuration on Azure) - can see from a log in App Insights
  3. Change the flag
  4. Wait 30 seconds (or an age)
  5. Make a request
  6. Feature flag has not changed

From my machine (running Service Fabric locally), it's as above. However, I can get different behavior if I follow the steps below:

  1. Change the flag
  2. Stop debugging
  3. Start debugging again
  4. Make a request
  5. The flag will have the updated value

In the controller files I'm using IFeatureManagerSnapshot. I get a flag by using something like:

_enable = await _featureManagerSnapshot.IsEnabledAsync(FeatureFlagConstants.Enable);

So it appears as though the cache isn't getting released as I'd expect it to, but holds the values for the lifetime of the software being in memory.

Version info:

FeatureManagement 2.0.0
FeatureManagement.AspNetCore 2.0.0

Why Microsoft.FeatureManagement.FeatureFilters project is empty?

I guess, it would be nice if filters are moved into the Microsoft.FeatureManagement.FeatureFilters.

Any specific reason for that?

And also any guidance or ideas to have some base filter? I am using Time Window approach for all my other filters as all feature flags have time based life-time.

It would be nice to have these because I don't want to use modified version of this project any more...

Maybe I'll send some PR 🙃

Authorization/authentication errors thrown before feature flag is checked

If you have a ASP.Net Core app with a controller with the following method:

[HttpGet]
[FeatureGate(FeatureFlags.Foo)]
[Authorize]
public IEnumerable<string> Get()
        {
            return new string[] { "value1", "value2" };
        }

Currently if:

  1. A request is made with correct scopes and the feature is turned on -> 200 returned (expected)
  2. A request is made with incorrect scopes and the feature is turned on -> 401 returned (expected)
  3. A request is made with correct scopes and the feature is turned off -> 404 returned (expected)
  4. A request is made with incorrect scopes and the feature is turned off -> 401 returned (unexpected)

In scenario 4 (or any scenario when the feature is turned off) I would have imagined that it would always return a 404 so that a client wouldn't be made aware of new features it doesn't have access to.

Enable data to be passed to feature filters in console apps and frameworks that do not have a context.

At the moment console apps do not have a way to pass data to the feature management system to evaluate feature state based on contextual data. As an example, imagine I am processing notifications in a console app from a file. Part of the processing might trigger some additional functionality based off of criteria within the notification. We could assume that data is notificationId. We need to be able to propagate this data to feature filters to check if a feature is enabled.

This is available to frameworks like ASP.NET and ASP.NET Core by utilizing the HTTP Context which flows ambiently through the application. During the request, assorted items can be added to the context and accessed in feature filters.

To keep design and usage consistent across all types of applications I believe the best approach here would be to add a way to flow contextual data into the feature management pipeline to be utilized during evaluation.

Feature request: Set of IFeatureFilters applied to all features

Request

I have a custom IFeatureFilter I would like to apply automatically to all features in my application. I don't see a way to do this, short of building a custom IFeatureDefinitionProvider. No problem building an IFeatureDefinitionProvider, it's just unfortunate because the ConfigurationFeatureDefinitionProvider has everything I need besides that. Would it make sense to expose such a feature?

Proposal

Via configuration:

"FeatureManagement": {
   "Default": {
        "EnabledFor": [
            { "Name": "CustomFilter" },
            { "Name": "CustomFilterWithParameters", "Parameters": { "Param1": "Value1" } }
        ]
   }
}

How to combine FeatureFlags settings from multiple providers / sources?

Cross-post from Azure/AppConfiguration#228 since it is relevant to both Microsoft.FeatureManagement and Azure App Configuration.

Suppose that I want to do the following:

  • use Azure App Configuration to set Feature Flags
    • for the production environment
    • as a baseline for the development environment
  • have a way for locally overriding the Feature Flags in the development environment, e.g. using cookies

The usecase for this is e.g. for testers / product owners to switch on/off Features when checking their behavior or presenting them, without needing access to Azure and without impacting others members of the development team.
Another use case could be for users opting in to preview features.

The documentation explains how to add Azure App Configuration as a source for FeatureManagement. I assume I would need to implement an IFeatureFilter or an ISessionManager to provide the values from the cookies, but I don't see this documented.

I believe an IFeatureFilter should be able to independently provide a Feature value, so how can we set what source gets preference over the other? The comparison cannot be done inside the IFeatureFilter I believe.

The logic I want to achieve is this:

Azure App Configuration feature state Local (cookie-based?) feature value Calculated feature result
false null false
true null true
false false false
true false false
false true true
true true true

Should the single-value parameters (on Feature Filters) be serialized as arrays ?

In Azure App Configuration, when defining a feature with a feature filter that has one parameter then that parameter is serialized as single-value.

public class BrowserPreviewFeatureFilterParameters
{
        public IList<string> Allowed { get; set; } = new List<string>();
        public IList<string> Blocked { get; set; } = new List<string>();
}

In the below example if we use the above model for deserialization then the parameter "Allowed" is deserialized but "Blocked" is not.
image

Observations:

  • This means that if someone defines the same parameter twice in Azure portal then the developer must deserialize field as an array, if the parameter is defined only once then the developer needs to deserialize the value using a completely different model:
public class BrowserPreviewFeatureFilterParameters
{
        public IList<string> Allowed { get; set; } = new List<string>();
        public string Blocked { get; set; } ;
}

ISSUE: The developer has to implement a JSONConverter to handle both scenarios, which is not nice.
Is this something that can be improved from Azure App Configuration portal UI ?

Unable to inject Scoped/Transient services into FeatureFilter

In.NET Core 3.0 DI scope validation is enabled by default.
Application throws an exception that it is not possible to resolve service that is Scoped/Transient .

Cannot consume scoped service 'Common.Services.IRequestDataProvider' from singleton 'Microsoft.FeatureManagement.IFeatureFilter'

Custom FeatureDefinitionProvider still depends on IConfiguration

I've been look at the latest pre-release of this package (2.2.0-preview) - it is a great improvement in terms of migration from other feature management system 👍

However, when implementing a custom IFeatureDefinitionProvider I noticed that FeatureFilterConfiguration still expects its data to be modeled as IConfiguration. There are a few reasons why I think this is problematic:

  • Non-transparent API. It is not transparent what settings are mandatory or even expected, IConfiguration is very broad.
  • Cumbersome API. In my case, I have loaded feature toggle data that I want to map to percentage filter, groups etc. The best way I have found is to look at the source code for the filter classes and mimic the expected configuration in an in-memory configuration collection.
  • Surprising API. Configuration is a concept that is tied to application startup. Whenever code is reliant on configuration, the first step is usually to map it the configuration to a strongly typed class and let downstream classes rely on it.

It would be great if the API for implementing a custom data source/adapter for the feature management system would be strongly typed and not reliant on IConfiguration.

create sample for Blazor WASM usage

I'm trying to use the feature management within BlazorWASM, however have been unable to get this to work; I'm unable to see any exceptions of why this is the case; please consider providing support/examples of how to use this library from a BlazorWASM application

Forcing a toggle to be false in the middle of the `EnabledFor`

Hi,

I have the necessity to force a toggle to false in the middle of the EnabledFor for a particular filter.

For example
My feature flags

"FeatureManagement": {
    "FeatureA": {
      "EnabledFor": [
        {
          "Name": "QueryString",
        },
       {
          "Name": "AppSettings",
        }
      ]
    }
  }

In my Appsettings file the feature is enable but i need force disable feature by query string(https://www.microsoft.com/?FeatureA=false).

Proposed Snippet

on IFeatureFilter.cs Line 18

Task<bool?> EvaluateAsync(FeatureFilterEvaluationContext context);

on FeatureManager.cs Line 108

if (filter is IFeatureFilter featureFilter)
 {
    var result = await featureFilter.EvaluateAsync(context).ConfigureAwait(false);
    if(result.hasValue){
          enabled = result.Value;
           break;
    }
 }

Or you think it's better throw a exception from the filter?

What do you think about this?

Feature filters are combined with a logical OR

Hello,

While experimenting with the sdk, we noticed that when evaluating the filters and the contextual filters, the FeatureManager internal loop will break and return true for the first filter that returns true.

We are planning to use heavily the sdk, but we feel that our needs are mainly steering towards a logical AND.

For example, we will want to enable a feature for one specific country (contextual filter), but only for a defined percentage of them (filter). This would look like this in the app configuration store :

"conditions": {
    "client_filters": [
        {
            "name": "CountryContextual",
            "parameters": {
                "Country": [
                    "FR",
                    "IT"
                ]
            }
        },
        {
            "name": "Microsoft.Percentage",
            "parameters": {
                "Value": "50"
            }
        }
    ]
}

We imagine we might be able to create "meta" filters that would contain a logical operator and a list of sub filters, which can also be recursive. This would solve pretty much any use case we could imagine, but it seems to be a little over-complicated.

We could also create our own implementation of IFeatureManager, but the required dependency IFeatureSettingsProvider is currently internal.

In our opinion, the most sensible default combination operator should be an AND.

What's your opinion on that? Do you think of any built-in or alternative way of reaching our goals?

IFeatureManagerSnapshot one flag, many contexts.

Probably it's more a question than an issue (I hope), but is this expected behavior?

Context:

I'm evaluating one flag for two different contexts:

  • 123
  • 456

The flag should only return true for context 123.

var featureVFor123 = await _featureManagerSnapshot.IsEnabledAsync("FeatureV", "123");
var featureVFor456 = await _featureManagerSnapshot.IsEnabledAsync("FeatureV", "456");

return $"FeatureVFor123: {await featureVFor123}. FeatureVFor456: {await featureVFor456}.";

(where _featureManagerSnapshot is of type IFeatureManagerSnapshot)

returns:

FeatureVFor123: true. FeatureVFor456: true.

Which I would not expect (it's a different set of conditions for the flag). I would expect:

FeatureVFor123: true. FeatureVFor456: false.

It looks like the evaluated flag is getting cached and subsequent evaluations are ignoring different contexts. It does work correctly when I use IFeatureManager _featureManager.

Is this by design?

Recommended approach for automated testing of both code paths in ASP.NET Core?

Hi! I appreciate the work being done here.

One thing I haven't been able to find in the docs are examples of automated tests showing how to exercise both code paths (with a given feature both on & off).

Would the suggestion be to stub IFeatureManager or is there a more idiomatic approach the team recommends?

To illustrate what I'm asking with code, this is what I've got working so far:
(based on https://docs.microsoft.com/en-us/aspnet/core/test/integration-tests?view=aspnetcore-3.0#inject-mock-services)

[Fact]
public async Task FeatureA_ShouldBePresent_WhenConfiguredOn()
{
	// Arrange
	var client = _factory
		.WithWebHostBuilder(builder =>
		{
			builder.ConfigureServices(services =>
			{
				services.AddScoped<IFeatureManager, StubFeatureManagerWithFeatureAOn>();
			});
		})
		.CreateClient();

	// Act
	var response = await client.GetAsync("/Home/FeatureA");

	// Assert
	response.EnsureSuccessStatusCode(); // Status Code 200-299
	Assert.Equal("text/html; charset=utf-8",
			response.Content.Headers.ContentType.ToString());
}

[Fact]
public async Task FeatureA_ShouldNotBePresent_WhenConfiguredOff()
{
	// Arrange
	var client = _factory
		.WithWebHostBuilder(builder =>
		{
			builder.ConfigureServices(services =>
			{
				services.AddScoped<IFeatureManager, StubFeatureManagerWithFeatureAOff>();
			});
		})
		.CreateClient();

	// Act
	var response = await client.GetAsync("/Home/FeatureA");

	// Assert
	Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}

The salient lines in this snippet are:

services.AddScoped<IFeatureManager, StubFeatureManagerWithFeatureAOn>();
services.AddScoped<IFeatureManager, StubFeatureManagerWithFeatureAOff>();

The actual stub code is not shown (could use hand-rolled or Moq, etc.).

Does the team have a different recommendation for toggling a feature on a per-test basis?

Asp.net Core web app doesn't throw any exception when feature flag free quota limit is reached

This is something that we faced in production recently.
Accidentlally we were using Free tier of App Configuration in production workload for our web app.
We were deploying the new changes in the production environment and we were getting 500.30 service unavailable error, when we were trying to access the API's
We tried running the command *dotnet .dll from kudu using dotnet .dll command and it just hangs doesn't show any error in console.
At last we tested on local env and found it was due to feature flag free quota limit is reached application was not running and no exception was shown in console, making it hard to understand what is the root cause of the issue.

below is our code for program.cs

`{

                 var builtConfig = builder.Build();

                 var azureServiceTokenProvider = new AzureServiceTokenProvider();

                 var keyVaultClient = new KeyVaultClient(
                     new KeyVaultClient.AuthenticationCallback(
                         azureServiceTokenProvider.KeyVaultTokenCallback));

                 builder.AddAzureKeyVault(
                     $"https://{builtConfig["KeyVaultName"]}.vault.azure.net/",
                     keyVaultClient,
                     new DefaultKeyVaultSecretManager());

             })

            .ConfigureAppConfiguration((ctx, builder) =>
            {
                var builtConfig = builder.Build();
                builder.AddAzureAppConfiguration(options => {
                    options.Connect(builtConfig["feature-flag-connection-string"])
                        .UseFeatureFlags();
                });
            })`

Provide for Distributed Systems

Feature Request:
Based on documentation and blog articles I've read and from poking around some of the source code, the FeatureManagement system appears to be a wrapper on top of the Configuration System. The Configuration System doesn't easily provide for setting up a configuration source that is distributed - e.g. Database, Distributed Cache, etc.

Question:
Is there a recommendation for accomplishing this? I would expect this to be something that is easily configurable thru the Feature Management API but I must be missing something.

IFeatureManager.GetFeatureNamesAsync returning all configuration keys

I dont have a 'FeatureManagement' section in my appsettings.json, but when I call GetFeatureNamesAsync, it is returning key in IConfiguration.

I'd also try adding 'FeatureManagement' section without any feature set, but the result was the same.

the following are some of the names return from GetFeatureNamesAsync()

"AllowedHosts"
"ALLUSERSPROFILE"
"APPDATA"
"applicationName"
"APP_POOL_CONFIG"
"APP_POOL_ID"
"ASPNETCORE_ENVIRONMENT"
"ASPNETCORE_HOSTINGSTARTUPASSEMBLIES"
"ASPNETCORE_IIS_HTTPAUTH"
"ASPNETCORE_IIS_PHYSICAL_PATH"

Misconfigured Flags Should Error

I think that if a flag is used and that flag is not configured then the app should error and not just default the flag to false.

For example, if a configured flag is for some reason omitted in a production config (or mis-spelled) then the app will continue to run without error and the fault may not be noticed.

If a flag is being used programmability then this could cause data issues.

If a flag is being used to control UI elements such as some legal wording that needs to be shown, then by defaulting to false the company could be in breach of laws.

Any plans on creating an abstraction package?

Many of the Microsoft.Extensions packages comes with an abstraction package containing the fundamental interfaces for a component. This can be a lifesaver when trying to untangle dependency graphs/verison constraints in larger projects.

With this in mind - and in the light of the dependencies that the feature toggle package have taken - are there any plans to extract IFeatureManager, IFeatureFilter et. al. into a abstraction package?

async implementation of TryGetAsync on ISessionManager not possible?

The ISessionManager interface switched to using an async approach, which is good in itself.

However the TryGetAsync function uses an out parameter which forbids declaring the function as async - so you cannot await Tasks in the function body.

This feels a bit awkward - do you have any example on how to best tackle that?

Guidance for Azure Functions

Hi,

We are currently interested in using the Feature Management services in our Azure Functions. We've figured out how to register the service and can connect to the App Configuration instance to resolve the state of the FeatureFlag, however, we weren't sure how the 'refresh' of the feature flag is supposed to work?

My current tests show that toggling the feature flag from enabled/disabled doesn't affect the result of
await _featureManager.IsEnabledAsync("TestFlag") and only seems to take effect upon function restart.

cheers

Enable dynamic configuration values to be retrieved based on a feature.

Sometimes the addition of a new feature within an application might require altered/new configuration. This configuration is scoped to the feature itself and should not pollute the overall configuration of the application. A method should be established to allow applications to pull settings from the configuration system that may be altered based on a specific feature and whether it is enabled/disabled.

E.g. something like


{
  "MaxRequests": "100",
  "MaxRetries": "10",
  "FeatureManagement": {
    "Beta": {
      "EnabledFor": [
        {
          "Name": "Percentage",
          "Parameters": {
            "Value": "50"
          }
        }
      ],
      "Configuration': {
        "MaxRequests:" 500
      }
    }
}

Getting configuration normally would yield expected results

IConfiguration config;
config["MaxRequests"] // 100
config["MaxRetries"] // 10

Getting configuration with respect to the Beta feature would yield something like
IConfiguration betaAwareConfig
config["Maxrequests"] // 500
config["MaxRetries"] // 10

Settings for different environment don't transform correctly

I have a Development and Staging setup. For some business reasons, I'd like to disable certain features entirely, while enable them with predefined criteria for other environments.

For example, my appsettings.json file may have something like this

"FeatureManagement": {
    "FeatureA": {
      "EnabledFor": [
        {
          "Name": "ProductPlan",
          "Parameters": {
            "AllowedPlans": [
              "Developer",
              "Production",
              "Enterprise"
            ]
          }
        }
      ]
    }
  }

However, I'd like entirely disable this for my Staging configuration.
In my appsettings.Staging.json I would like to completely disable the feature

"FeatureManagement": {
    "FeatureA": false
  }

It appears that this is not possible. I also tried specifying different subset in the AllowedPlans, but that didn't work either - the environment specific settings file was never processed.
Other settings in it work fine.

Is this an expected behavior? Any help would be appreciated. Thanks

Consider making IFeatureSettingsProvider public

There are many system in the wild that uses other feature toggling frameworks that does not load its data from IConfiguration, nor has a data source that can be used as a ConfigurationProvider. The idea behind making IFeatureSettingsProvider public is that these systems could use a custom implementation as part of their migration story over to this toggle framework.

Dynamic change a Feature and apply it at runtime without restarting the application

Hi,

I can't find any answer on all docs I've read so far, so here is my point :

The context :
I feed the IConfiguration object using the IConfigurationBuilder with an external ressource (Hashicorp Vault here), I don't work with the Appsettings file but with a Inmemory configuration provider (builder.AddInMemoryCollection) feed by my Json from Hashicorp .
It works well, and the services.AddFeatureManagement() allows me to use the IFeatureManager as expected for example.

My point :
I would like to dynamically change some feature toogle:
for example enable FeatureA which was previously disabled, or disable FeatureB which was previously enabled. It's easy to change my Json in HaschiCorp and implement a kind of polling to Hashicorp to get keep my features activation up to date. But I don't want to have to restart the application to get the new changes.

Is there any way to achieve this ? ie force the FeatureManagement lib to "update its information" during runtime ?
Or could we update the IConfiguration at runtime ? If yes the FeatureManagement will be updated as expected ?

Thank you for your help.

Logging hook?

Hi, are there any hooks for logging?

It'd be super awesome if there was a to log all the features that are 'known about' and what they status is, including on a refresh.

Anyone know of a way?

Demo app failed with internal server error

I am trying to run the demo locally but with App Configure service using regular endpoint connection string instead of managed identity. Got an error below (screenshot attached):
An unhandled exception occurred while processing the request.
MissingMethodException: Method not found: 'Boolean Roslyn.Utilities.IObjectWritable.get_ShouldReuseInSerialization()'.
Microsoft.CodeAnalysis.CSharp.CSharpCompilation.Create(string assemblyName, CSharpCompilationOptions options, IEnumerable syntaxTrees,
IEnumerable references, CSharpCompilation previousSubmission, Type returnType, Type hostObjectType, bool isSubmission)

Stack trace:
MissingMethodException: Method not found: 'Boolean Roslyn.Utilities.IObjectWritable.get_ShouldReuseInSerialization()'.
Microsoft.CodeAnalysis.CSharp.CSharpCompilation.Create(string assemblyName, CSharpCompilationOptions options, IEnumerable syntaxTrees, IEnumerable references, CSharpCompilation previousSubmission, Type returnType, Type hostObjectType, bool isSubmission)
Microsoft.CodeAnalysis.Razor.CompilationTagHelperFeature.GetDescriptors()
Microsoft.AspNetCore.Razor.Language.DefaultRazorTagHelperBinderPhase.ExecuteCore(RazorCodeDocument codeDocument)
Microsoft.AspNetCore.Razor.Language.DefaultRazorEngine.Process(RazorCodeDocument document)
Microsoft.AspNetCore.Razor.Language.RazorProjectEngine.Process(RazorProjectItem projectItem)
Microsoft.AspNetCore.Mvc.Razor.Internal.RazorViewCompiler.CompileAndEmit(string relativePath)
Microsoft.AspNetCore.Mvc.Razor.Internal.RazorViewCompiler.OnCacheMiss(string normalizedPath)
System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
Microsoft.AspNetCore.Mvc.RazorPages.Internal.DefaultPageLoader.Load(PageActionDescriptor actionDescriptor)
Microsoft.AspNetCore.Mvc.RazorPages.Internal.PageActionInvokerProvider.OnProvidersExecuting(ActionInvokerProviderContext context)
Microsoft.AspNetCore.Mvc.Internal.ActionInvokerFactory.CreateInvoker(ActionContext actionContext)
Microsoft.AspNetCore.Mvc.Internal.MvcAttributeRouteHandler+<>c__DisplayClass12_0.b__0(HttpContext c)
Microsoft.AspNetCore.Builder.RouterMiddleware+d__4.MoveNext()
System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
Microsoft.Azure.AppConfiguration.AspNetCore.AzureAppConfigurationRefreshMiddleware+d__6.MoveNext()
System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware+d__7.MoveNext()
appconfigsettings
appconfigError

Unwanted feature values retrieved when no feature flags set within Azure App Configuration

I've been working with your library for some time now, and have got it working so that a list of feature flags is passed through an API of my own creation, via Azure App Configuration and the use of this package. However, is there a way to limit what feature store featureManager.GetFeatureNamesAsync() has access to? I only want to poll my AAC connection with this method, but currently, if no features are found within my instance of Azure App Configuration, it will fall back to using what appears to be environment variables of some kind?

TL:DR - Is there a way to limit the featureManager to access only one store/provider?

featurebits.core

There is any relation with the development of the package featurebits.core, nuget, github.
Both packages seems to have related goals, they work both on netcore.
It would be better, IMHO, to have a unified approach to feature flags.

Custom Feature Settings Providers

We really like what we're seeing here - great work for bringing something like this to the community!

We'd like to propose that some of the interfaces be made public so we can switch out their implementations to interface either with legacy (in-house) feature management solutions or external vendors (LaunchDarkly, split.io et al) to provide a consistent abstraction layer across multiple different backends.

We're happy to contribute code if you're accepting PR's too.

Add a result filter for razor pages

Right now, there's no nice declarative way (that I could find) to add a feature flag to a razor page.

[FeatureGate(FeatureFlags.FeatureA)]
public class IndexModel : PageModel {...}

Doesn't work because FeatureGateAttribute only overrides OnActionExecutionAsync. I haven't tested this myself, but I believe if you also implement OnResultExecutingAsync you'll cover Razor pages. At least according to the docs Filter methods for Razor Pages in ASP.NET Core

Consider moving to Microsoft.Extensions.FeatureManagement

Consider changing the namespaces and the assembly name to be Microsoft.Extensions.FeatureManagement. IMO, this makes the library look more like the rest of .NET Core "standard" libraries than a stand-alone thing.

Besides, it has the same "spirit" that other Microsoft.Extensions.* libraries have.

An exception should be thrown if a configured feature filter has not been registered.

If a feature is configured to be enabled for a certain feature filter, the feature management pipeline should throw if that feature is being evaluated and the feature filter can not be found. Without this behavior it is very hard to track down the fact that a feature filter has been excluded from being registered. For those who intend for this behavior and depend on an exception not being thrown, this could be configurable via feature management options.

Configure services depending on the enabled feature

Hi,

What's the correct way to checking whether feature enabled from Startup.ConfigureServices?

I'd expect something like
services.AddForFeature(MyFeatures.RedisCache, x => x.Decorate<IMyService, MyCachedService>());

or
if (configuration.IsFeatureEnabled(MyFeatures.RedisCache)) { services.Decorate<IMyService, MyCachedService>(); }

but have not found a nice way of doing it. Have I missed something existing for this? Or such usage breaks design somehow?

Using Label prevents cache expiration

I never see updates reflected if I try to apply a Label filter. Simply commenting out the setting of Label in the code below restores cache expiration and I see updates again but any time Label is set I only get the initial value.

var env = hostingContext.HostingEnvironment.EnvironmentName; config.AddAzureAppConfiguration(options => options .Connect(connectionString) .Select(KeyFilter.Any, LabelFilter.Null) .Select(KeyFilter.Any, env) .UseFeatureFlags(options => { options.CacheExpirationTime = TimeSpan.FromSeconds(5); options.Label = env; }) );

Turning off the internal cache within UseFeatureFlags(), or overarching AddAzureAppConfiguration method

Hello,

We're currently in the process of wrapping our feature flag code into an API, which surfaces the values stored in AAC out in an easy to read way for our clients/ui's to consume. We have written some Specflow API tests that covers all the scenarios that we may find our API's being used for, but we have come across a situation that seems to have us stumped.

What we do in said tests, is that we clear the AAC (Azure App Configuration) down, to remove any existing feature flags from any past test runs to avoid any interference between runs.

For example:

We create the following features Feature1-1, Feature2-1 using the AAC SDK.
We then poll our API, which calls the GetFeatureNamesAsync method. This returns:

Feature1-1
Feature2-1 

We loop through the features returned above, and check to see if they're enabled or not, via use of a custom feature filter, which when complete, is then sent as a response from the API.
We assert based on the response given in the test, and check that the values retrieved are correct.

However, if we run these tests as a group, one after another, we're finding that some of the tests fail, as they're getting some of the old test run features within their response.

From what I can gather, the API is updating its AAC configuration provider every 30 seconds or so, as that is what the default cache expiration value is. However, is there perhaps a way that we can get the provider to be updated/refreshed PER REQUEST? Or per call to GetFeaturesNamesAsync?

I know that we can update the cache value to within a second, as that's the minimum value. But is there a way that we can perhaps turn off the caching provided by the packages? As we're relying on using our own caching layer within the API for limiting calls to AAC where possible.

Should not depend on MS.Bcl.Interfaces for .NET Core 3.0+

The MS.FeatureManagement package depends on the MS.Bcl.Interfaces package:

<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="1.0.0" />

For .NET Core 3.0+ this seems to duplicate the IAsyncEnumerable<> and other async interfaces and creates compiler errors regarding those types being defined in both System.Runtime as well as System.Interactive.Async.

Basically, it wrecks the developer experience for IAsyncEnumerable and friends, unless you work around it by forcefully throwing out the duplicate with a bit of assembly aliasing magic in each affected project that transitively pulls in the dependency:

<Target Name="AddAssemblyAliasToReactiveAsync" AfterTargets="ResolveAssemblyReferences">
  <ItemGroup>
    <ReferencePath Condition=" '%(FileName)' == 'System.Interactive.Async' ">
      <Aliases>reactive</Aliases>
    </ReferencePath>
  </ItemGroup>
</Target>

Feature filters should match even if 'Filter' is included in the configuration.

Right now the current matching mechanism is a type named BrowserFilter would map to Browser in the configured feature filters for a feature. If the whole type name is specified including 'Filter', there is no match. We should enable this to match since there have been many cases where this behavior was expected.

Enable applications to report what features are being used, what were the states during a request, etc...

Currently feature management can be used to gate application functionality behind features. An important part of this flow is measuring how often these features are being used, and whether these features are healthy. The feature management interfaces lack the ability to extrapolate data such as what features were accessed during their request and what are their states. This should be added.

Feature names with colon in name are not parsed correctly

Given the following configuration:

"FeatureManagement": {
    "Feature1": true,
    "Prefix:Feature2": true
}

Querying the features was a bit surprising at first:

featureManager.IsEnabledAsync("Feature1") //returns true
featureManager.IsEnabledAsync("Prefix:Feature2") //returns false

Listing the feature names is also quite misleading:

featureManager.GetFeatureNamesAsync(); //returns "Feature1", "Prefix"

By taking a closer look, the colon is of course not only a gimmick, but more like a common separator for config sections. So when using the JSON file provider, the abovementioned configuration could also be semantically identical rephrased as

"FeatureManagement": {
    "Feature1": true,
    "Prefix": {
        "Feature2": true
    }
}

And that explains the weird behavior.

To solve that, would it be a possible to tokenize given feature names by : , treating them as config-sections and only the last segment as the name? Imho that would fix the "Prefix":Feature" : true case, as well as enable feature grouping (see above).

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.