官术网_书友最值得收藏!

Understanding feature toggling

.NET Core 3 introduced the Microsoft.FeatureManagement.AspNetCore library, which is very handy for doing feature toggling. In a nutshell, a feature is either enabled or not, and this is configured through the configuration (any source) by a Boolean switch.

For more complex scenarios, you can define a configuration to be made available for a particular feature; this can be taken into consideration to determine whether or not it is enabled.

Feature toggling can be applied to an action method by applying the [FeatureGate] attribute with any number of feature names, as follows:

[FeatureGate("MyFeature1", "MyFeature2")]
public IActionResult FeactureEnabledAction() { ... }

When the[FeatureGate]attribute is applied to an action method and the feature is disabled, any attempts to access it will result in an HTTP 404 Not Found result. It can take any number of feature names and as well as an optional requirement type, which can be eitherAllorAny, meaning that either all features need to be enabled or at least one has to be enabled. This is illustrated in the following code snippet:

[FeatureGate(RequirementType.All, "MyFeature1", "MyFeature2")]
public IActionResult FeactureEnabledAction() { ... }

Alternatively, this can be asked for explicitly, through an instance of an injected IFeatureManager, as follows:

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

public async Task<IActionResult> Index()
{
var isEnabled = await _featureManager.IsEnabledAsync("MyFeature");
}

Of course, you can inject IFeatureManager anywhere. An example of this can be seen in the following code snippet:

@inject IFeatureManager FeatureManager

@if (await FeatureManager.IsEnabledAsync("MyFeature")) {
<p>MyFeature is enabled!</p>
}

But another option, on a view, would be to use the <feature> tag helper, like this:

<feature name="MyFeature">
<p>MyFeature is enabled!</p>
</feature>

Similar to the [FeatureGate] attribute, you can specify multiple feature names in the name attribute, and you can also specify one of Any or All in requirement. You can also negate the value, as follows:

<feature name="MyFeature">
<p>MyFeature is enabled!</p>
</feature>
<feature name="MyFeature" negate="true">
<p>MyFeature is disabled!</p>
</feature>

This is useful—as you can see—because you can provide content for both when the feature is enabled and when it is not.

Tag helpers need to be registered—this normally happens on the _ViewImports.cshtml file, as follows:

@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

At the very least, we need to have the following configuration—for example—on an appsettings.json file, for a feature named MyFeature:

{
"FeatureManagement": {
"MyFeature": true
}
}

The default is always false, meaning that the feature is disabled. Any changes done at runtime to the configuration file are detected by the feature management library.

Setup is pretty straightforward—in theConfigureServicesmethod, just call theAddFeatureManagementextension method. This is what registers theIFeatureManagerinterface (plus a few others that we will see later), as follows:

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

And there is another overload of AddFeatureManagement that takes as a parameter an IConfiguration object, should you wish to build your own. Next, you need to register as many feature filters as you want to use, with consecutive calls to AddFeatureFilter.

Included feature filters

The feature filters package includes the following filters:

  • PercentageFilter: This allows a certain defined percentage of items to pass.
  • TimeWindowFilter: A feature is enabled only during the defined date-and-time window.

Each of these filters has its own configuration schema—let's have a look at each.

Percentage filter

The percentage filter takes as its sole parameter—well, the percentage we're interested in. Every time it is invoked, it will return enabled approximately that percentage of times. The configuration in the appsettings.json file should look like this:

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

You can see that you declare the name of the feature gate, "HalfTime", and the percentage parameter—50, in this example.

You also declare the attribute, as follows:

[FeatureGate("HalfTime")]
public IActionResult Action() { ... }
Time window filter

This one allows a feature to be made available automatically when a certain date and time comes. A configuration for Christmas Day looks like this:

"FeatureManagement": {
"Christmas": {
"EnabledFor": [
{
"Name": "Microsoft.TimeWindow",
"Parameters": {
"Start": "25 Dec 2019 00:00:00 +00:00",
"End": "26 Dec 2019 00:00:00 +00:00"
}
}
]
}
}

Notice the format of the date and time—this is culture-agnostic. You need to declare both the start and end time, together with the name of the feature gate: "Christmas".

The feature gate declaration is illustrated in the following code snippet:

[FeatureGate("Christmas")]
public IActionResult Action() { ... }

Custom feature filters

Building a simple feature filter is straightforward—just implement IFeatureFilter, which only has a single method, as follows:

[FilterAlias("MyFeature")]
public class MyFeatureFilter : IFeatureFilter
{
public bool Evaluate(FeatureFilterEvaluationContext context)
{
//return true or false
}
}

Then, register it on ConfigureServices, like this:

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

The FeatureFilterEvaluationContext class provides only two properties, as follows:

  • FeatureName (string): The name of the current feature
  • Parameters (IConfiguration): The configuration object that is used to feed the feature filter

However, we can leverage the built-in dependency injection mechanism of .NET Core and have it inject into our feature filter something such as IHttpContextAccessor, from which we can gain access to the current HTTP context, and from it to pretty much anything you need. This can be achieved as follows:

private readonly HttpContext _httpContext;

public MyFeatureFilter(IHttpContextAccessor httpContextAccessor)
{
this._httpContext = httpContextAccessor.HttpContext;
}

You are also not limited to a yes/no value from the configuration—you can have rich configuration settings. For example, let's see how we can have our own model in the configuration file— although, for the sake of simplicity, we will make this a simple one. Imagine the following simple class:

public class MySettings
{
public string A { get; set; }
public int B { get; set; }
}

We want to persist this class in a configuration file, like this:

{
"FeatureManagement":{
"MyFeature":{
"EnabledFor":[
{
"Name":"MyFeature",
"Parameters":{
"A":"AAAAA",
"B":10
}
}
]
}
}

This configuration can be read from a custom feature inside the Evaluate method, like this:

var settings = context.Parameters.Get<MySettings>();

The MySettings class is automatically deserialized from the configuration setting and made available to a .NET class.

Consistency between checks

You may notice that for some features—such as the percentage feature—if you call it twice during the same request, you may get different values, as illustrated in the following code snippet:

var isEnabled1 = await _featureManager.IsEnabledAsync("HalfTime");
var isEnabled2 = await _featureManager.IsEnabledAsync("Halftime");

In general, you want to avoid this whenever your feature either does complex calculations or some random operations, and you want to get consistent results for the duration of a request. In this case, you want to use IFeatureManagerSnapshot instead of IFeatureManager. IFeatureManagerSnapshot inherits from IFeatureManager but its implementations cache the results in the request, which means that you always get the same result. And IFeatureManagerSnapshot is also registered on the dependency injection framework, so you can use it whenever you would use IFeatureManager.

Disabled features handler

When you try to access an action method that is decorated with a feature gate that targets a feature (or features) that is disabled, then the action method is not reachable and, by default, we will get an HTTP 403 Forbidden error. However, this can be changed by applying a custom disabled features handler.

A disabled features handler is a concrete class that implements IDisabledFeaturesHandler, such as this one:

public sealed class RedirectDisabledFeatureHandler : IDisabledFeaturesHandler
{
public RedirectDisabledFeatureHandler(string url)
{
this.Url = url;
}

public string Url { get; }

public Task HandleDisabledFeatures(IEnumerable<string> features,
ActionExecutingContext context)
{
context.Result = new RedirectResult(this.Url);
return Task.CompletedTask;
}
}

This class redirects to a Uniform Resource Locator (URL) that is passed as a parameter. You register it through a call to UseDisabledFeaturesHandler, as follows:

services
.AddFeatureManagement()
.AddFeatureFilter<MyFeatureFilter>()
.UseDisabledFeaturesHandler(new
RedirectDisabledFeatureHandler("/Home/FeatureDisabled"));

You can only register one handler, and that's all it takes. Whenever we try to access an action method for which there is a feature gate defined that evaluates to false, it will be called, and the most obvious response will be to redirect to some page, as we can see in the example I gave.

In this section, we learned about a new feature of ASP.NET Core: feature toggling. This is a streamlined version of configuration that is more suitable for on/off switches and has some nice functionality associated. May you find it useful!

主站蜘蛛池模板: 临武县| 皮山县| 定州市| 西充县| 阿尔山市| 监利县| 海城市| 棋牌| 旬邑县| 永寿县| 赣州市| 乾安县| 阿坝县| 大名县| 韩城市| 响水县| 无极县| 三门峡市| 肃南| 灵武市| 临邑县| 扎兰屯市| 兴业县| 安平县| 蕲春县| 新源县| 西乌珠穆沁旗| 通江县| 朝阳县| 泰州市| 肇源县| 义马市| 潜山县| 兰考县| 忻州市| 杭锦后旗| 广西| 隆尧县| 巴楚县| 瑞丽市| 九龙城区|