.NET 功能管理

Microsoft.FeatureManagement
Microsoft.FeatureManagement.AspNetCore

Microsoft.FeatureManagement
Microsoft.FeatureManagement.AspNetCore
Microsoft.FeatureManagement.Telemetry.ApplicationInsights

.NET 功能管理库提供了基于功能标志开发和公开应用程序功能的方法。 开发新功能后,许多应用程序都有特殊要求,例如应何时启用此功能以及在哪些条件下启用该功能。 此库提供了一种方法来定义这些关系,并且已集成到常见的 .NET 代码模式中,使公开这些功能成为可能。

功能标志为 .NET 和 ASP.NET Core 应用程序提供了一种动态打开或关闭功能的方法。 开发人员可以在简单的用例(例如条件语句)中使用功能标志,也可以在更高级的方案(例如有条件地添加路由或 MVC 筛选器)中使用。 功能标志构建在 .NET Core 配置系统之上。 任何 .NET Core 配置提供程序都能够充当功能标志的主干。

下面是使用 .NET 功能管理库的一些优势:

  • 功能管理的常见约定

  • 低准入门槛

    • 基于 IConfiguration
    • 支持 JSON 文件功能标志设置
  • 功能标志生存期管理

    • 配置值可以实时更改;功能标志可以在整个请求中保持一致
  • 涵盖简单到复杂的方案

    • 通过声明性配置文件打开/关闭功能
    • 基于对服务器的调用来动态评估功能的状态
  • 适用于 ASP.NET Core 和 MVC 框架的 API 扩展

    • 路由
    • 筛选器
    • 操作属性

    .NET 功能管理库是开源的。 有关详细信息,请访问 GitHub 存储库

功能标志

功能标志由两个部分组成,即名称和用于打开功能的功能筛选器列表。

功能筛选器

功能筛选器定义应启用功能的场景。 当评估某项功能是应打开还是关闭时,将遍历其功能筛选器列表,直到其中一个筛选器确定应启用该功能。 此时,该功能被视为已启用,并停止遍历功能筛选器。 如果没有功能筛选器指示应启用该功能,则它被视为已禁用。

例如,可以设计 Microsoft Edge 浏览器功能筛选器。 只要 HTTP 请求来自 Microsoft Edge,此功能筛选器就会激活所附加的任何功能。

功能标志配置

.NET Core 配置系统用于确定功能标志的状态。 此系统的基础是 IConfigurationIConfiguration 的任何提供程序都可以用作功能标志库的功能状态提供程序。 此系统支持从 appsettings.json 到 Azure 应用程序配置等各种方案。

功能标志声明

功能管理库支持将 appsettings.json 用作功能标志源,因为它是 .NET Core IConfiguration 系统的提供程序。 下面是用于在 json 文件中设置功能标志的格式的示例。

{
    "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"
                    }
                }
            ]
        }
    }
}

按照约定,json 文档的 FeatureManagement 部分用于加载功能标志设置。 在上面的部分中,我们看到了三个不同的功能。 这些功能使用 EnabledFor 属性来定义其功能筛选器。 在 FeatureT 的功能筛选器中,我们看到了 AlwaysOn。 此功能筛选器是内置的,如果指定,则将始终启用该功能。 AlwaysOn 功能筛选器不需要任何配置,因此它仅具有 Name 属性。 FeatureU 在其 EnabledFor 属性中没有筛选器,因此永远不会启用。 只要功能筛选器保持为空,任何依赖于此功能的功能都将无法访问。 但是,只要添加了启用该功能的功能筛选器,它就可以开始工作。 FeatureV 指定名为 TimeWindow 的功能筛选器。 这是可配置的功能筛选器的示例。 在该示例中可以看到筛选器具有 Parameters 属性。 它用于配置筛选器。 在本例中,将配置功能处于活动状态的开始和结束时间。

可在此处找到 FeatureManagement 部分的详细架构。

高级:禁止在功能标志名称中使用冒号“:”。

开/关声明

以下代码片段演示了一种替代方法来定义可用于开/关功能的功能。

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

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

RequirementType

功能标志的 RequirementType 属性用于确定筛选器在评估功能状态时应使用 Any 还是 All 逻辑。 如果未指定 RequirementType,则默认值为 Any

  • Any 表示只需一个筛选器评估为 true 即可启用该功能。
  • All 表示每个筛选器都需要评估为 true 才能启用该功能。

如果 RequirementTypeAll,则会更改遍历。 首先,如果没有筛选器,则该功能处于禁用状态。 然后,将遍历功能筛选器,直到其中一个筛选器确定应禁用该功能。 如果没有筛选器指示应禁用该功能,则它被视为已启用。

"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"
            }
        }
    ]
}

在上面的示例中,FeatureW 指定 RequirementTypeAll,这意味着所有筛选器都必须评估为 true 才能启用该功能。 在本例中,在指定时间范围内为 50% 的用户启用了该功能。

Azure 功能管理架构

功能管理库还支持使用 Microsoft Feature Management schema 来声明功能标志。 此架构在源中与语言无关,并且受所有 Azure 功能管理库的支持。

{
    "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"
                            }
                        }
                    ]
                }
            }
        ]
    }
}

注意

如果可以在配置中找到 feature_management 部分,则会忽略 FeatureManagement 部分。

功能管理库支持将 appsettings.json 用作功能标志源,因为它是 .NET Core IConfiguration 系统的提供程序。 功能标志是使用 Microsoft Feature Management schema 声明的。 此架构在源中与语言无关,并且受所有 Azure 功能管理库的支持。

下面是在 json 文件中声明功能标志的示例。

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

    // Define feature flags in a json file
    "feature_management": {
        "feature_flags": [
            {
                "id": "FeatureT",
                "enabled": false
            },
            {
                "id": "FeatureU",
                "enabled": true,
                "conditions": {}
            },
            {
                "id": "FeatureV",
                "enabled": true,
                "conditions": {
                    "client_filters": [
                        {  
                            "name": "Microsoft.TimeWindow",
                            "parameters": {
                                "Start": "Mon, 01 May 2023 13:59:59 GMT",
                                "End": "Sat, 01 July 2023 00:00:00 GMT"
                            }
                        }
                    ]
                }
            }
        ]
    }
}

按照约定,json 文档的 feature_management 部分用于加载功能标志设置。 功能标志对象必须在 feature_management 部分下的 feature_flags 数组中列出。 在上面的部分中,我们看到提供了三个不同的功能。 功能标志具有 idenabled 属性。 id 是用于标识和引用功能标志的名称。 enabled 属性指定功能标志的已启用状态。 如果 enabled 为 false,则功能处于关闭状态。 如果 enabled 为 true,则功能的状态取决于 conditions。 如果没有 conditions,则功能处于打开状态。 如果存在 conditions 并且满足这些条件,则功能处于打开状态。 如果存在 conditions,但不满足这些条件,则功能处于关闭状态。 conditions 属性声明用于动态启用功能的条件。 这些功能在 client_filters 数组中定义其功能筛选器。 FeatureV 指定名为 Microsoft.TimeWindow 的功能筛选器。 这是可配置的功能筛选器的示例。 在该示例中可以看到筛选器具有 Parameters 属性。 它用于配置筛选器。 在本例中,将配置功能处于活动状态的开始和结束时间。

高级:禁止在功能标志名称中使用冒号“:”。

RequirementType

conditionsrequirement_type 属性用于确定筛选器在评估功能状态时应使用 Any 还是 All 逻辑。 如果未指定 requirement_type,则默认值为 Any

  • Any 表示只需一个筛选器评估为 true 即可启用该功能。
  • All 表示每个筛选器都需要评估为 true 才能启用该功能。

如果 requirement_typeAll,则会更改遍历。 首先,如果没有筛选器,将禁用该功能。 如果存在筛选器,则将遍历功能筛选器,直到其中一个筛选器确定应禁用该功能。 如果没有筛选器指示应禁用该功能,则它将被视为已启用。

{
    "id": "FeatureW",
    "enabled": true,
    "conditions": {
        "requirement_type": "All",
        "client_filters": [
            {
                "name": "Microsoft.TimeWindow",
                "parameters": {
                    "Start": "Mon, 01 May 2023 13:59:59 GMT",
                    "End": "Sat, 01 Jul 2023 00:00:00 GMT"
                }
            },
            {
                "name": "Microsoft.Percentage",
                "parameters": {
                    "Value": "50"
                }
            }
        ]
    }
}

在上面的示例中,FeatureW 指定 requirement_typeAll,这意味着所有筛选器都必须评估为 true 才能启用该功能。 在本例中,将在指定时间范围内为 50% 的用户启用该功能。

.NET 功能管理架构

在以前的版本中,功能管理库的主要架构是 .NET feature management schema。 从 v4.0.0 开始,.NET 功能管理架构不支持包括变体和遥测在内的新功能。

注意

如果有可在 feature_managementFeatureManagement 节中找到的功能标志声明,则会采用 feature_management 节中的功能标志声明。

消耗

功能管理的基本形式是检查是否启用了功能标志,然后根据结果执行操作。 这是通过 IFeatureManagerIsEnabledAsync 方法完成的。

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

服务注册

功能管理依赖于 .NET Core 依赖项注入。 我们可以使用标准约定来注册功能管理服务。

using Microsoft.FeatureManagement;

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

默认情况下,功能管理器从 .NET Core 配置数据的“FeatureManagement”部分检索功能标志配置。 如果“FeatureManagement”部分不存在,则配置被视为空。

注意

你还可以指定应从其他配置部分检索功能标志配置,方法是将该部分传递给 AddFeatureManagement。 以下示例告知功能管理器从名为“MyFeatureFlags”的另一部分读取数据:

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

Dependency Injection

将功能管理库与 MVC 配合使用时,可以通过依赖项注入来获取 IFeatureManager

public class HomeController : Controller
{
    private readonly IFeatureManager _featureManager;

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

作用域内功能管理服务

AddFeatureManagement 方法将功能管理服务添加为应用程序中的单一实例,但在某些情况下,可能需要将功能管理服务添加为作用域内服务。 例如,用户可能想要通过使用作用域内服务的功能筛选器来获取上下文信息。 在这种情况下,应改为使用 AddScopedFeatureManagement 方法。 这可确保将功能管理服务(包括功能筛选器)添加为作用域内服务。

services.AddScopedFeatureManagement();

ASP.NET Core 集成

功能管理库在 ASP.NET Core 和 MVC 中提供功能,可在 Web 应用程序中启用常见功能标志方案。 可以通过引用 Microsoft.FeatureManagement.AspNetCore NuGet 包来提供这些功能。

控制器和操作

MVC 控制器和操作可能要求启用给定功能或任何功能列表中的某项功能才能执行。 可以使用可在 Microsoft.FeatureManagement.Mvc 命名空间中找到的 FeatureGateAttribute 来完成此操作。

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

上面的 HomeController 由“FeatureX”选通。 必须先启用“FeatureX”,然后才能执行 HomeController 包含的任何操作。

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

上述 Index MVC 操作要求先启用“FeatureX”才能执行。

禁用的操作处理

当 MVC 控制器或操作由于未启用其指定的任何功能而被阻止时,将调用已注册的 IDisabledFeaturesHandler。 默认情况下,会注册一个返回 HTTP 404 的简易处理程序。 注册功能标志时,可以使用 IFeatureManagementBuilder 替代它。

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

视图

在 MVC 视图中,<feature> 标记可用于根据是否启用了某项功能来有条件地呈现内容。

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

你还可以否定标记帮助程序评估,以在禁用功能或功能集时显示内容。 通过在以下示例中设置 negate="true",仅当禁用 FeatureX 时,才会呈现内容。

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

通过在 name 属性中指定逗号分隔的功能列表,<feature> 标记可以引用多个功能。

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

默认情况下,必须为要呈现的功能标记启用所有列出的功能。 可以通过添加 requirement 属性来替代此行为,如以下示例所示。

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

在 MVC 视图中,根据是否启用了某项功能或是否分配了功能的特定变体,<feature> 标记可用于有条件地呈现内容。 有关详细信息,请参阅变体部分。

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

你还可以否定标记帮助程序评估,以在禁用功能或功能集时显示内容。 通过在以下示例中设置 negate="true",仅当禁用 FeatureX 时,才会呈现内容。

<feature negate="true" name="FeatureX">
  <p>This can only be seen if 'FeatureX' is disabled.</p>
</feature>
<feature negate="true" name="FeatureX" variant="Alpha">
  <p>This can only be seen if variant 'Alpha' of 'FeatureX' isn't assigned.</p>
</feature>

通过在 name/variant 属性中指定逗号分隔的功能/变体列表,<feature> 标记可以引用多个功能/变体。

<feature name="FeatureX,FeatureY">
  <p>This can only be seen if 'FeatureX' and 'FeatureY' are enabled.</p>
</feature>
<feature name="FeatureX" variant="Alpha,Beta">
  <p>This can only be seen if variant 'Alpha' or 'Beta' of 'FeatureX' is assigned.</p>
</feature>

注意

如果指定了 variant,则只应指定一个功能。

默认情况下,必须为要呈现的功能标记启用所有列出的功能。 可以通过添加 requirement 属性来替代此行为,如以下示例所示。

注意

如果在 requirementAnd 的情况下使用 variant,则将引发错误,因为永远无法分配多个变体。

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

<feature> 标记需要标记帮助程序才能工作。 这可以通过将功能管理标记帮助程序添加到 ViewImports.cshtml 文件来完成。

@addTagHelper *, Microsoft.FeatureManagement.AspNetCore

MVC 筛选器

可以将 MVC 操作筛选器设置为根据功能的状态来有条件地执行。 这是通过以功能感知的方式注册 MVC 筛选器来完成的。 功能管理管道支持实现 IAsyncActionFilter 的异步 MVC 操作筛选器。

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

上述代码添加名为 SomeMvcFilter 的 MVC 筛选器。 仅当已启用“FeatureX”时,才会在 MVC 管道内部触发此筛选器。

Razor 页面

MVC Razor 页面可能要求启用给定功能或任何功能列表中的某项功能才能执行。 可以使用可在 Microsoft.FeatureManagement.Mvc 命名空间中找到的 FeatureGateAttribute 来完成此操作。

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

上述代码将 Razor 页面设置为要求启用“FeatureX”。 如果未启用该功能,则该页面将生成 HTTP 404 (NotFound) 结果。

在 Razor 页面上使用时,必须将 FeatureGateAttribute 放在页面处理程序类型上。 不能将其放在单个处理程序方法上。

应用程序生成

功能管理库可用于添加根据功能状态有条件执行的应用程序分支和中间件。

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

通过上述调用,应用程序会添加一个中间件组件,该组件仅在启用“FeatureX”功能时才会显示在请求管道中。 如果在运行时启用/禁用该功能,则可以动态更改中间件管道。

此代码生成更通用的功能来根据功能分支整个应用程序。

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

实现功能筛选器

创建功能筛选器提供了一种基于所定义条件启用功能的方法。 若要实现功能筛选器,必须实现 IFeatureFilter 接口。 IFeatureFilter 有一个名为 EvaluateAsync 的方法。 当功能指定可为功能筛选器启用它时,将调用 EvaluateAsync 方法。 如果 EvaluateAsync 返回 true,则表示应启用该功能。

以下代码片段演示如何添加自定义功能筛选器 MyCriteriaFilter

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

通过对从 AddFeatureManagement 返回的 IFeatureManagementBuilder 调用 AddFeatureFilter<T>,注册功能筛选器。 这些功能筛选器可以访问用于添加功能标志的服务集合中存在的服务。 依赖项注入可用于检索这些服务。

注意

在功能标志设置(例如,appsettings.json)中引用筛选器时,应该省略类型名称的筛选器部分。 有关详细信息,请参阅 Filter Alias Attribute 部分。

参数化功能筛选器

某些功能筛选器需要参数来确定是否应打开某项功能。 例如,浏览器功能筛选器可能会为一组特定浏览器打开某项功能。 Edge 和 Chrome 浏览器可能需要启用某项功能,而 Firefox 则不需要。 为此,可以将功能筛选器设计为预期参数。 这些参数将在功能配置中指定,并且在代码中可以通过 IFeatureFilter.EvaluateAsyncFeatureFilterEvaluationContext 参数进行访问。

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 具有名为 Parameters 的属性。 这些参数表示原始配置,功能筛选器可以使用该配置来确定如何评估是否应启用该功能。 再次以浏览器功能筛选器为例,该筛选器可以使用 Parameters 提取一组为该功能指定的允许使用的浏览器,然后检查请求是否是从其中一个浏览器发送的。

[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
    }
}

筛选器别名属性

为功能标志注册功能筛选器时,配置中使用的别名是删除了筛选器后缀(如果有)的功能筛选器类型的名称。 例如,MyCriteriaFilter 在配置中称为 MyCriteria

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

可以通过使用 FilterAliasAttribute 来替代它。 可以使用此属性修饰功能筛选器,以声明应在配置中使用的名称,从而在功能标志中引用此功能筛选器。

缺少功能筛选器

如果将某项功能配置为针对特定功能筛选器启用,并且该功能筛选器未注册,则会在评估该功能时引发异常。 可以使用功能管理选项来禁用异常。

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

使用 HttpContext

功能筛选器可以根据 HTTP 请求的属性来评估是否应该启用某项功能。 这是通过检查 HTTP 上下文来执行的。 通过依赖项注入获取 IHttpContextAccessor,功能筛选器可以获取对 HTTP 上下文的引用。

public class BrowserFilter : IFeatureFilter
{
    private readonly IHttpContextAccessor _httpContextAccessor;

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

必须在启动时将 IHttpContextAccessor 添加到依赖项注入容器,才能使其可用。 可以使用以下方法在 IServiceCollection 中注册它。

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

高级:不应在服务器端 Blazor 应用的 Razor 组件中使用 IHttpContextAccessor/HttpContext。 在 Blazor 应用中传递 http 上下文的建议方法是将数据复制到作用域内服务中。 对于 Blazor 应用,应使用 AddScopedFeatureManagement 来注册功能管理服务。 有关详细信息,请参阅 Scoped Feature Management Services 部分。

为功能评估提供上下文

在控制台应用程序中,没有环境上下文(例如 HttpContext),功能筛选器需要获取并利用它来检查功能是应打开还是关闭。 在这种情况下,应用程序需要向功能管理系统提供表示上下文的对象,以供功能筛选器使用。 这是通过使用 IFeatureManager.IsEnabledAsync<TContext>(string featureName, TContext appContext) 完成的。 功能筛选器可以使用提供给功能管理器的 appContext 对象来评估功能的状态。

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

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

上下文功能筛选器

上下文功能筛选器可实现 IContextualFeatureFilter<TContext> 接口。 这些特殊功能筛选器可以利用调用 IFeatureManager.IsEnabledAsync<TContext> 时传入的上下文。 IContextualFeatureFilter<TContext> 中的 TContext 类型参数描述了筛选器能够处理的上下文类型。 这使上下文功能筛选器的开发人员能够描述其目标用户所需的内容。 由于每种类型都是对象的后代,因此可以为任何提供的上下文调用实现 IContextualFeatureFilter<object> 的筛选器。 若要演示更具体的上下文功能筛选器的示例,请考虑使用已启用的功能(如果帐户位于已启用帐户的配置列表中)。

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
    }
}

我们可以看到,AccountIdFilter 要求提供一个实现 IAccountContext 的对象才能评估功能的状态。 使用此功能筛选器时,调用方需要确保传入的对象可实现 IAccountContext

注意

单个类型只能实现一个功能筛选器接口。 尝试添加实现多个功能筛选器接口的功能筛选器会导致 ArgumentException

使用具有相同别名的上下文和非上下文筛选器

IFeatureFilterIContextualFeatureFilter 的筛选器可以共享相同的别名。 具体而言,可以有一个由 0 或 1 个 IFeatureFilter 和 0 或 N 个 IContextualFeatureFilter<ContextType> 共享的筛选器别名,只要 ContextType 最多有一个适用的筛选器即可。

以下文章介绍了在应用程序中注册相同名称的上下文和非上下文筛选器时选择筛选器的过程。

假设有一个名 FilterA 的非上下文筛选器和两个上下文筛选器(FilterB 和 FilterC),它们分别接受 TypeBTypeC 上下文。 所有三个筛选器共享相同的别名 SharedFilterName

你还有一个功能标志 MyFeature,它在其配置中使用功能筛选器 SharedFilterName

如果已注册所有三个筛选器:

  • 调用 IsEnabledAsync(“MyFeature”)时,FilterA 用于评估功能标志。
  • 调用 IsEnabledAsync(“MyFeature”上下文)时,如果上下文的类型为 TypeB,则使用 FilterB。 如果上下文的类型为 TypeC,则使用 FilterC
  • 调用 IsEnabledAsync(“MyFeature”上下文)时,如果上下文的类型为 TypeF,则使用 FilterA

内置功能筛选器

有一些功能筛选器附带 Microsoft.FeatureManagement 包:PercentageFilterTimeWindowFilterContextualTargetingFilterTargetingFilter。 当 AddFeatureManagement 方法注册功能管理时,所有筛选器(TargetingFilter 除外)都会自动添加。 TargetingFilter 随下面 Targeting 部分中详述的 WithTargeting 方法一起添加。

每个内置功能筛选器都有自己的参数。 以下是功能筛选器的列表和示例。

Microsoft.Percentage

此筛选器提供根据设置百分比来启用某项功能的功能。

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

Microsoft.TimeWindow

此筛选器提供根据时间窗口来启用某项功能的功能。 如果仅指定了 End,则在此时间之前该功能被视为已启用。 如果仅指定了 Start,则在该时间之后的所有时间点,该功能被视为已启用。

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

可以将时间窗口配置为定期重复出现。 对于可能需要在一天或一周中某些天的低流量或高流量期间启用功能的情况,这非常有用。 若要将单个时间窗口扩展为定期时间窗口,应在 Recurrence 参数中指定重复执行规则。

注意

必须同时指定 StartEnd 才能启用 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"
                    }
                }
            }
        }
    ]
}

Recurrence 设置由两个部分组成:Pattern(时间窗口重复的频率)和 Range(重复执行模式重复的时长)。

定期模式

有两种可能的重复执行模式类型:DailyWeekly。 例如,时间窗口可以“每天”、“每三天”、“每星期一”或“每隔一个星期五”重复。

根据类型,Pattern 的某些字段是必填、可选或可忽略的。

  • Daily

    每日重复执行模式会导致时间窗口根据每次出现间隔的天数进行重复。

    properties 相关性 说明
    类型 必须 必须设置为 Daily
    时间间隔 可选 指定每次出现之间的天数。 默认值为 1。
  • Weekly

    每周重复执行模式会导致时间窗口在一周中的同一天或几天重复,具体取决于每组重复间隔的周数。

    properties 相关性 说明
    类型 必须 必须设置为 Weekly
    DaysOfWeek 必须 指定事件发生在星期几。
    时间间隔 可选 指定每组重复间隔的周数。 默认值为 1。
    FirstDayOfWeek 可选 指定哪一天被视为一周的第一天。 默认值是 Sunday

    以下示例每隔一个星期一和星期二重复一次时间窗口

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

注意

Start 必须是符合重复执行模式的第一个有效匹配项。 此外,时间窗口的持续时间不能超过它的发生频率。 例如,每天重复 25 小时的时间窗口无效。

重复执行范围

有三种可能的重复执行范围类型:NoEndEndDateNumbered

  • NoEnd

    NoEnd 范围会导致重复执行无限期地发生。

    properties 相关性 说明
    类型 必须 必须设置为 NoEnd
  • EndDate

    EndDate 范围会导致时间窗口在符合适用模式的所有日期发生,直到结束日期为止。

    properties 相关性 说明
    类型 必须 必须设置为 EndDate
    EndDate 必须 指定停止应用模式的日期时间。 只要最后一次发生的开始时间在结束日期之前,就允许该次发生的结束时间超出该日期。

    以下示例将每天重复时间窗口,直到最后一次事件发生在 2024 年 4 月 1 日。

    "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

    Numbered 范围会导致时间窗口出现固定次数(基于模式)。

    properties 相关性 说明
    类型 必须 必须设置为 Numbered
    NumberOfOccurrences 必须 指定发生次数。

    以下示例将在星期一和星期二重复时间窗口,直到出现三次,分别发生在 4 月 1 日(星期一)、4 月 2 日(星期二)和 4 月 8 日(星期一)。

    "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
        }
    }
    

若要创建重复执行规则,必须同时指定 PatternRange。 任何模式类型都可以与任何范围类型一起使用。

高级:Start 属性的时区偏移量将应用于重复执行设置。

Microsoft.Targeting

此筛选器提供为目标受众启用某项功能的功能。 以下目标部分对此进行了深入说明。 筛选器参数包括一个 Audience 对象,该对象描述用户、组、已排除的用户/组,以及应该有权访问该功能的用户群的默认百分比。 Groups 部分中列出的每个组对象还必须指定应具有访问权限的组成员的百分比。 如果直接在 Exclusion 部分中指定了用户,或者该用户在排除的组中,则该功能将被禁用。 否则,如果直接在 Users 部分中指定了用户,或者如果用户处于任何组推出包含的百分比中,或者处于默认推出百分比中,则此用户将启用该功能。

"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"
                        ]
                    }
                }
            }
        }
    ]
}

功能筛选器别名命名空间

所有内置功能筛选器别名都位于 Microsoft 功能筛选器命名空间中。 这是为了防止与其他可能共享同一别名的功能筛选器发生冲突。 功能筛选器命名空间的各个段由“.”字符分隔。 功能筛选器可以由其完全限定的别名(如 Microsoft.Percentage)引用,也可以由最后一段引用(如果 Microsoft.PercentagePercentage)。

目标设定

目标定位是一种功能管理策略,使开发人员能够逐步向其用户群推出新功能。 该策略建立在面向一组称为目标受众的用户的概念之上。 受众由特定用户、组、已排除的用户/组和占整个用户群的指定百分比的人数组成。 受众中包含的组可以进一步细分为其成员总数的百分比。

以下步骤演示了新“Beta 版”功能的渐进式推出示例:

  1. 个人用户 Jeff 和 Alicia 有权访问 Beta 版
  2. 另一个用户 Mark 要求加入并包含在内。
  3. Beta 版中包含 20% 的名为“Ring1”用户的组。
  4. Beta 版中包含的“Ring1”用户数量增加到 100%。
  5. Beta 版中包含 5% 的用户群。
  6. 推出百分比将提升到 100%,该功能已完全推出。

此功能推出策略通过随附的 Microsoft.Targeting 功能筛选器内置到库中。

Web 应用程序中的目标

FeatureFlagDemo 示例项目提供了一个使用目标功能筛选器的示例 Web 应用程序。

若要开始在应用程序中使用 TargetingFilter,必须将它添加到应用程序的服务集合中,就像任何其他功能筛选器一样。 与其他内置筛选器不同,TargetingFilter 依赖于要添加到应用程序服务集合的另一项服务。 该服务是一个 ITargetingContextAccessor

Microsoft.FeatureManagement.AspNetCore 提供 ITargetingContextAccessor默认实现,它将从请求的 HttpContext 中提取目标信息。 通过在 IFeatureManagementBuilder 上使用非泛型 WithTargeting 重载设置目标时,可以使用默认的目标上下文访问器。

通过对 IFeatureManagementBuilder 调用 WithTargeting 来注册默认目标上下文访问器和 TargetingFilter

services.AddFeatureManagement()
        .WithTargeting();

还可以通过调用 WithTargeting<T>ITargetingContextAccessorTargetingFilter 来注册自定义实现。 以下是在 Web 应用程序中设置功能管理以将 TargetingFilter 与称为 ExampleTargetingContextAccessorITargetingContextAccessor 实现配合使用的示例。

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

ITargetingContextAccessor

若要在 Web 应用程序中使用 TargetingFilter,需要实现 ITargetingContextAccessor。 这是因为在执行目标评估时,需要诸如当前正在评估的用户之类的上下文信息。 此信息称即称为 TargetingContext。 不同的应用程序可能会从不同的位置提取此信息。 应用程序可以拉取目标上下文的一些常见示例是请求的 HTTP 上下文或数据库。

从应用程序的 HTTP 上下文中提取目标上下文信息的示例是 Microsoft.FeatureManagement.AspNetCore 包提供的 DefaultHttpTargetingContextAccessor。 它将从 HttpContext.User 中提取目标信息。 UserId 信息将从 Identity.Name 字段中提取,Groups 信息将从类型 Role 的声明中提取。 此实现依赖于使用 IHttpContextAccessor,在此处进行了讨论。

控制台应用程序中的目标

目标筛选器依赖于目标上下文来评估是否应启用某项功能。 此目标上下文包含的信息,例如当前正在评估的用户,以及用户所在的组。 在控制台应用程序中,通常没有环境上下文可用于将此信息传入目标筛选器,因此在调用 FeatureManager.IsEnabledAsync 时必须直接传递此信息。 可使用 ContextualTargetingFilter 支持此操作。 需要将目标上下文传入功能管理器的应用程序应该使用此方法,而不是 TargetingFilter.

由于 ContextualTargetingFilter 是一个 IContextualTargetingFilter<ITargetingContext>,因此必须将 ITargetingContext 的实现传入 IFeatureManager.IsEnabledAsync 才能评估和启用功能。

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

await fm.IsEnabledAsync(featureName, targetingContext);

ContextualTargetingFilter 仍使用功能筛选器别名 Microsoft.Targeting,因此,此筛选器的配置与该部分中提到的配置一致。

TargetingConsoleApp 示例项目提供了一个在控制台应用程序中使用 ContextualTargetingFilter 的示例。

目标评估选项

这些选项可用于自定义跨所有功能执行目标评估的方式。 设置功能管理时,可以配置这些选项。

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

目标排除

定义受众时,可以从该受众中排除用户和组。 如果要向一组用户推出某项功能,但需要从推出中排除一些用户或组,则此功能非常有用。 通过将用户和组列表添加到受众的 Exclusion 属性来定义排除。

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

在上面的示例中,为名为 JeffAlicia 的用户启用功能。 它还为名为 Ring0 的组中的用户启用该功能。 但是,如果用户命名为 Mark,则会禁用该功能,而不考虑其是否位于组 Ring0 中。 排除的优先级高于目标筛选器的其余部分。

变量

将新功能添加到应用程序时,有时某项功能具有多个不同的建议设计选项。 决定设计的一个常见解决方案是某种形式的 A/B 测试,它涉及向不同的用户群体提供不同的功能版本,并根据用户交互选择版本。 在此库中,通过用变体表示功能的不同配置来启用此功能。

变体使功能标志变得不仅仅是一个简单的开/关标志。 变体表示功能标志的值,可以是字符串、数字、布尔值,甚至是配置对象。 用于声明变体的功能标志应定义每个变体的使用条件,这将在分配变体部分中进行更详细的介绍。

public class Variant
{
    /// <summary>
    /// The name of the variant.
    /// </summary>
    public string Name { get; set; }

    /// <summary>
    /// The configuration of the variant.
    /// </summary>
    public IConfigurationSection Configuration { get; set; }
}

获取变体

对于每个功能,可以使用 IVariantFeatureManagerGetVariantAsync 方法检索变体。

…
IVariantFeatureManager featureManager;
…
Variant variant = await featureManager.GetVariantAsync(MyFeatureFlags.FeatureU, CancellationToken.None);

IConfigurationSection variantConfiguration = variant.Configuration;

// Do something with the resulting variant and its configuration

一旦检索到变体,可以直接将变体的配置用作变体 Configuration 属性中的 IConfigurationSection。 另一种选择是使用 .NET 的配置绑定模式将配置绑定到对象。

IConfigurationSection variantConfiguration = variant.Configuration;

MyFeatureSettings settings = new MyFeatureSettings();

variantConfiguration.Bind(settings);

返回的变体取决于当前正在评估的用户,并且该信息是从 TargetingContext 的实例中获取的。 调用 GetVariantAsync 时,可以传入此上下文,也可以从 ITargetingContextAccessor(如果已注册)的实现中自动检索该上下文。

变体功能标志声明

与普通功能标志相比,变体功能标志具有两个附加属性:variantsallocationvariants 属性是一个数组,其中包含为此功能定义的变体。 allocation 属性定义为该功能分配这些变体的方式。 与声明普通功能标志一样,可以在 json 文件中设置变体功能标志。 以下是变体功能标志的示例。

{
    "feature_management": {
        "feature_flags": [
            {
                "id": "MyVariantFeatureFlag",
                "enabled": true,
                "allocation": {
                    "default_when_enabled": "Small",
                    "group": [
                        {
                            "variant": "Big",
                            "groups": [
                                "Ring1"
                            ]
                        }
                    ]
                },
                "variants": [
                    { 
                        "name": "Big"
                    },  
                    { 
                        "name": "Small"
                    } 
                ]
            }
        ]
    }
}

定义变体

每个变体都有两个属性:名称和配置。 名称用于引用特定变体,而配置则是该变体的值。 可以使用 configuration_value 属性设置配置。 configuration_value 是一个内联配置,它可以是字符串、数字、布尔值或配置对象。 如果未指定 configuration_value,则返回的变体的 Configuration 属性将为 null。

已为 variants 属性下的每个功能定义所有可能的变体的列表。

{
    "feature_management": {
        "feature_flags": [
            {
                "id": "MyVariantFeatureFlag",
                "variants": [
                    { 
                        "name": "Big", 
                        "configuration_value": {
                            "Size": 500
                        }
                    },  
                    { 
                        "name": "Small", 
                        "configuration_value": {
                            "Size": 300
                        }
                    } 
                ]
            }
        ]
    }
}

分配变体

分配功能变体的过程由该功能的 allocation 属性确定。

"allocation": { 
    "default_when_enabled": "Small", 
    "default_when_disabled": "Small",  
    "user": [ 
        { 
            "variant": "Big", 
            "users": [ 
                "Marsha" 
            ] 
        } 
    ], 
    "group": [ 
        { 
            "variant": "Big", 
            "groups": [ 
                "Ring1" 
            ] 
        } 
    ],
    "percentile": [ 
        { 
            "variant": "Big", 
            "from": 0, 
            "to": 10 
        } 
    ], 
    "seed": "13973240" 
},
"variants": [
    { 
        "name": "Big", 
        "configuration_value": "500px"
    },  
    { 
        "name": "Small", 
        "configuration_value": "300px"
    } 
]

功能的 allocation 设置具有以下属性:

properties 说明
default_when_disabled 指定在该功能被视为已禁用的情况下请求变体时应使用哪个变体。
default_when_enabled 指定在以下情况下请求变体时应使用哪个变体,即如果该功能被视为已启用,并且没有向用户分配其他变体。
user 指定变体以及应将该变体分配到的用户列表。
group 指定变体和组列表。 如果用户至少位于其中一个组,则会分配该变体。
percentile 指定一个变体和一个百分比范围,计算出的用户百分比必须处于此范围内才能分配该变体。
seed percentile 的百分比计算所基于的值。 如果使用相同的 seed 值,则特定用户的百分比计算在所有功能中都是相同的。 如果未指定 seed,则会根据功能名称创建默认种子。

在上面的示例中,如果未启用该功能,则功能管理器会将标记为 default_when_disabled 的变体分配给当前用户,在本例中为 Small

如果已启用该功能,则功能管理器将按该顺序检查 usergrouppercentile 分配,以分配变体。 对于此特定示例,如果接受评估的用户在名为 Ring1 的组中命名为 Marsha,或者该用户恰好位于 0 和第 10 个百分位数之间,则会将指定的变体分配给该用户。 在这种情况下,所有这些都会返回 Big 变体。 如果这些分配都不匹配,则会为用户分配 default_when_enabled 变体,它是 Small

分配逻辑类似于 Microsoft.Targeting 功能筛选器,但有一些存在于目标中的参数不在分配中,反之亦然。 目标和分配的结果不相关。

注意

若要允许分配功能变体,需要注册 ITargetingContextAccessor。 这可以通过调用 WithTargeting<T> 方法来完成。

使用变体替代已启用状态

可以使用变体来替代功能标志的已启用状态。 这为变体提供了扩展功能标志评估的机会。 使用变体对标志调用 IsEnabled 时,功能管理器将检查分配给当前用户的变体是否配置为替代结果。 这是使用可选变量属性 status_override 完成的。 默认情况下,此属性设置为 None,这意味着变体不会影响标志是被视为已启用还是已禁用。 将 status_override 设置为 Enabled 允许所选变体替代要启用的标志。 将 status_override 设置为 Disabled 会提供相反的功能,因此在选择变体时会禁用标志。 无法替代 enabled 状态为 false 的功能。

如果使用具有二进制变体的功能标志,则 status_override 属性非常有用。 它可让你继续在应用程序中使用 IsEnabledAsyncFeatureGateAttribute 等 API,同时受益于变体附带的新功能,例如百分位数分配和种子。

{
    "id": "MyVariantFeatureFlag",
    "enabled": true,
    "allocation": {
        "percentile": [
            {
                "variant": "On",
                "from": 10,
                "to": 20
            }
        ],
        "default_when_enabled":  "Off",
        "seed": "Enhanced-Feature-Group"
    },
    "variants": [
        {
            "name": "On"
        },
        {
            "name": "Off",
            "status_override": "Disabled"
        }
    ]
}

在上面的示例中,始终启用该功能。 如果当前用户在计算出的第 10 到第 20 百分位数范围内,则会返回 On 变体。 否则,将返回 Off 变体,因为 status_override 等于 Disabled,因此该功能现在被视为已禁用。

依赖项注入中的变体

变体功能标志可与依赖项注入结合使用,以便为不同的用户展示服务的不同实现。 这是通过使用 IVariantServiceProvider<TService> 接口实现的。

IVariantServiceProvider<IAlgorithm> algorithmServiceProvider;
...

IAlgorithm forecastAlgorithm = await algorithmServiceProvider.GetServiceAsync(cancellationToken); 

在上面的代码片段中,IVariantServiceProvider<IAlgorithm> 从依赖项注入容器中检索 IAlgorithm 的实现。 所选的实现取决于:

  • IAlgorithm 服务注册的功能标志。
  • 已为该功能分配的变体。

通过调用 IFeatureManagementBuilder.WithVariantService<T>(string featureName),应用程序可以使用 IVariantServiceProvider<T>。 请参阅下文查看示例。

services.AddFeatureManagement() 
        .WithVariantService<IAlgorithm>("ForecastAlgorithm");

上述调用使 IVariantServiceProvider<IAlgorithm> 在服务集合中可用。 必须通过 add 方法(如 services.AddSingleton<IAlgorithm, SomeImplementation>())单独添加 IAlgorithm 的实现。 IVariantServiceProvider 使用的 IAlgorithm 的实现取决于 ForecastAlgorithm 变体功能标志。 如果未将 IAlgorithm 的实现添加到服务集合,则 IVariantServiceProvider<IAlgorithm>.GetServiceAsync() 将返回结果为 null 的任务。

{
    // The example variant feature flag
    "id": "ForecastAlgorithm",
    "enabled": true,
    "variants": [
        { 
            "Name": "AlgorithmBeta" 
        },
        ...
    ] 
}

变体服务别名属性

[VariantServiceAlias("Beta")]
public class AlgorithmBeta : IAlgorithm
{
    ...
}

变体服务提供程序将使用实现的类型名称来匹配分配的变体。 如果已使用 VariantServiceAliasAttribute 修饰变体服务,则应在配置中使用此属性中声明的名称来引用该变体服务。

遥测

部署功能标志更改时,分析其对应用程序的影响通常很重要。 例如,可能会出现以下几个问题:

  • 我的标志是否按预期启用/禁用?
  • 目标用户是否按预期获得对特定功能的访问权限?
  • 特定用户看到的是哪个变体?

可以通过功能标志评估事件的发出和分析来回答这类问题。 此库使用 System.Diagnostics.Activity API 在功能标志评估期间生成跟踪遥测数据。

启用遥测

默认情况下,功能标志不发出遥测。 若要发布给定功能标志的遥测,该标志必须声明已启用遥测发出。

对于在 appsettings.json 中定义的功能标志,这是通过使用 telemetry 属性完成的。

{
    "feature_management": {
        "feature_flags": [
            {
                "id": "MyFeatureFlag",
                "enabled": true,
                "telemetry": {
                    "enabled": true
                }
            }
        ]
    }
}

上面的 appsettings 代码片段定义了一个名为 MyFeatureFlag 的功能标志,该标志已启用遥测。 它由 telemetry 对象指示,该对象将 enabled 设置为 true。 enabled 属性的值必须为 true 才能发布标志的遥测。

功能标志的 telemetry 部分具有以下属性:

properties 说明
enabled 指定是否应为功能标志发布遥测。
metadata 键值对的集合(建模为字典),可用于将有关功能标志的自定义元数据附加到评估事件。

自定义遥测发布

功能管理器有自己的名为“Microsoft.FeatureManagement”的 ActivitySource。 如果为功能标志启用了 telemetry,则每当功能标志评估启动时,功能管理器将启动 Activity。 功能标志评估完成后,功能管理器会将名为 FeatureFlagActivityEvent 添加到当前活动。 FeatureFlag 事件将包含提供功能标志评估相关信息的标记,遵循 FeatureEvaluationEvent 架构中定义的字段。

注意

在功能标志中指定的 telemetry.metadata 所有键值对也将包含在标记中。

若要启用自定义遥测发布,可以创建 ActivityListener 并侦听 Microsoft.FeatureManagement 活动源。 下面是一个示例,演示如何侦听功能管理活动源,并在评估功能时添加回调。

ActivitySource.AddActivityListener(new ActivityListener()
{
    ShouldListenTo = (activitySource) => activitySource.Name == "Microsoft.FeatureManagement",
    Sample = (ref ActivityCreationOptions<ActivityContext> options) => ActivitySamplingResult.AllData,
    ActivityStopped = (activity) =>
    {
        ActivityEvent? evaluationEvent = activity.Events.FirstOrDefault((activityEvent) => activityEvent.Name == "FeatureFlag");

        if (evaluationEvent.HasValue && evaluationEvent.Value.Tags.Any())
        {
            // Do something.
        }
    }
});

有关详细信息,请转到收集分布式跟踪

Application Insights 遥测发布服务器

Microsoft.FeatureManagement.Telemetry.ApplicationInsights 包提供内置的遥测发布者服务器,用于将功能标志评估数据发送到 Application Insights。 若要利用它,请添加对包的引用并注册 Application Insights 遥测发布服务器,如下所示。

builder.services
    .AddFeatureManagement()
    .AddApplicationInsightsTelemetryPublisher();

Microsoft.FeatureManagement.Telemetry.ApplicationInsights 包提供遥测初始值设定项,该初始值设定项会自动标记具有 TargetingId 的所有事件,以便事件可以链接到标志评估。 若要使用遥测初始值设定项 TargetingTelemetryInitializer,请将其添加到应用程序的服务集合中。

builder.Services.AddSingleton<ITelemetryInitializer, TargetingTelemetryInitializer>();

注意

为了确保 TargetingTelemetryInitializer 按预期工作,应使用下面所述的 TargetingHttpContextMiddleware

若要在当前活动中启用目标上下文的持久化,可以使用 TargetingHttpContextMiddleware

app.UseMiddleware<TargetingHttpContextMiddleware>();

可以在 EvaluationDataToApplicationInsights 示例中找到其用法示例。

先决条件

此遥测发布服务器依赖于已经设置并注册为应用程序服务的 Application Insights。 例如,在示例应用程序中,这是在此处完成的。

缓存

功能状态由 IConfiguration 系统提供。 任何缓存和动态更新应由配置提供程序进行处理。 每当检查某项功能是否已启用时,功能管理器会要求 IConfiguration 提供功能状态的最新值。

快照

有些方案要求功能的状态在请求的生存期内保持一致。 如果在请求期间更新了从中提取的 IConfiguration 源,则从标准 IFeatureManager 返回的值可能会发生更改。 可以使用 IFeatureManagerSnapshot 来防止这种情况。 IFeatureManagerSnapshot 的检索方式与 IFeatureManager 相同。 IFeatureManagerSnapshot 实现 IFeatureManager 的接口,但它在请求期间会缓存功能的第一个评估状态,并在其生存期内返回功能的相同状态。

自定义功能提供程序

通过实现自定义功能提供程序,开发人员可以从数据库或功能管理服务等源拉取功能标志。 默认情况下使用的附带功能提供程序从 .NET Core 的配置系统中拉取功能标志。 这允许在 appsettings.json 文件或配置提供程序(如 Azure 应用程序配置)中定义功能。 可以替换此行为,以完全控制从中读取功能定义的位置。

若要自定义功能定义的加载,必须实现 IFeatureDefinitionProvider 接口。

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

    IAsyncEnumerable<FeatureDefinition> GetAllFeatureDefinitionsAsync();
}

若要使用 IFeatureDefinitionProvider 的实现,必须先将其添加到服务集合中,然后才能添加功能管理。 以下示例添加名为 InMemoryFeatureDefinitionProviderIFeatureDefinitionProvider 实现。

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

后续步骤

若要了解如何在应用程序中使用功能标志,请继续阅读以下快速入门。

若要了解如何使用功能筛选器的信息,请继续学习以下教程。

若要了解如何使用变体功能标志运行试验,请继续学习以下教程。