深度探索.NET Feature Management功能开关的魔法
前言
.NET Feature Management
是一个用于管理应用程序功能的库,它可以帮助开发人员在应用程序中轻松地添加、移除和管理功能。使用 Feature Management
,开发人员可以根据不同用户、环境或其他条件来动态地控制应用程序中的功能。这使得开发人员可以更灵活地管理应用程序的功能,并根据需要快速调整和部署新功能。 Feature Management
还提供了一些方便的工具和 API
,帮助开发人员更轻松地实现功能管理和控制。
安装
.Net CLI
dotnet add package Microsoft.FeatureManagement.AspNetCore --version 4.0.0-preview2
Package Manager
NuGet\Install-Package Microsoft.FeatureManagement.AspNetCore -Version 4.0.0-preview2
或者 Vs
Nuget
包管理 管理工具安装等
依赖注入
.Net
功能管理器是通过框架的本机配置系统配置的,简单来说只要是.Net 的配置系统支持的数据源都可以用做功能管理(FeatureManagement
)的配置源
.NET
中的配置是使用一个或多个配置提供程序执行的。 配置提供程序使用各种配置源从键值对读取配置数据:
- 设置文件,例如
appsettings.json
- 环境变量
Azure Key Vault
Azure
应用配置- 命令行参数
- 已安装或已创建的自定义提供程序
- 目录文件
- 内存中的
.NET
对象 - 第三方提供程序
依赖注入:
service.AddFeatureManagement();
默认情况下,功能管理器从 .NET
appsettings.json
配置数据的 FeatureManagement
Section
来获取数据
// Define feature flags in config file
"FeatureManagement": {
"sayHello": true, // On feature
"todo": false // Off feature
}
当然也可以自定义 Section
service.AddFeatureManagement(builder.Configuration.GetSection("CustomFeatureManagement"));
// Define feature flags in config file
"CustomFeatureManagement": {
"sayHello": true, // On feature
"todo": false // Off feature
}
功能开关注册成 Scoped
AddFeatureManagement
方法将特性管理服务作为单例添加到应用程序中,但有些情况下可能需要将特性管理服务添加为Scoped
(作用域服务)。例如,我们可能希望使用 Scoped
以获取上下文信息的功能过滤器。在这种情况下,应该使用 AddScopedFeatureManagement
方法, 这将确保功能管理服务(包括功能过滤器)被添加为 Scoped
服务。
//功能管理注册 Scoped 作用域
service.AddScopedFeatureManagement();
功能管理的基本形式是检查功能标志是否已启用,然后根据结果执行操作。这通过
IFeatureManager
的IsEnabledAsync
方法来实现。
对我们上面的 FeatureManager
的配置来做一个验证
- sayhello 功能开关标志测试
app.MapGet("/sayHello", async Task<IResult> ([FromServices] IFeatureManager manager, string name) =>
{
if (await manager.IsEnabledAsync("sayHello"))
{
return TypedResults.Ok($"hello {name}");
}
return TypedResults.NotFound();
}).WithSummary("sayHello");
调用接口查看一下结果,在配置中我们的sayHello
设置为true
状态码为 200,返回信息"hello Ruipeng",符合预期,功能开启正常。
- todo 功能开关标志测试
app.MapGet("/todo", async Task<IResult> ([FromServices] IFeatureManager manager) =>
{
if (await manager.IsEnabledAsync("todo"))
{
return TypedResults.Ok($"todo is enabled !");
}
return TypedResults.NotFound();
}).WithSummary("todo");
调用接口查看一下结果,状态码 404,返回信息 Not Found,符合预期,功能未开启。
上面的示例简单讲解了一下功能开关的使用,接下来深入了解功能开关的配置
功能开关的定义
功能开关的标志由两部分组成:名称和用于启用功能的过滤器列表。
功能过滤器(Feature filters
)定义了功能应何时启用的场景。在评估特性是开启还是关闭时,会遍历其功能过滤器列表,直到其中一个过滤器决定启用该特性。如果一个过滤器都没有标识改功能应该开启,那此功能标志是关闭的状态。
内置过滤器
AlwaysOn
: 总是开启PercentageFilter
:根据百分比随机启用/禁用功能。这个过滤器允许您基于一个百分比值来决定功能被启用的概率,提供了一种简单而灵活的机制来控制特性的曝光范围。TimeWindowFilter
:在预定义的时间窗口内启用特性。这个过滤器允许您指定特性的开始和结束时间,确保特性只在特定的时间段内可用。这对于限时活动或测试场景非常有用。TargetingFilter
:(这个主要是在Azure
用为目标受众启用功能的分阶段推出针对特定用户或用户组启用特性。这个过滤器允许您根据用户属性或标识来启用特性,例如基于用户 ID、角色、地区等。此外,对于此过滤器,您还可以设置一个百分比值,以进一步控制特性在目标用户中的启用概率。
详细信息可以参考注册功能筛选器 Docs
过滤器的配置指南
需要注意的是在功能标志名称中禁止使用冒号
:
,这是为了遵循一定的命名规范,避免与现有的或未来的功能管理系统产生冲突或造成解析错误。在定义功能标志名称时,请确保使用合法和合适的字符组合,以确保系统的稳定性和可维护性。
功能使用EnabledFor
属性来定义它们的功能过滤器
AlwaysOn 过滤器
// Define feature flags in config file
"FeatureManagement": {
//始终启用该功能
"featureAlwaysOn": {
"EnabledFor": [
{
"Name": "AlwaysOn"
}
]
}
}
app.MapGet("/featureAlwaysOn", async Task<IResult> (IFeatureManager manager) =>
{
if (await manager.IsEnabledAsync("featureAlwaysOn"))
{
return TypedResults.Ok($"featureAlwaysOn is enabled !");
}
return TypedResults.NotFound();
}).WithSummary("featureAlwaysOn");
调用接口查看测试结果,返回 200,符合预期
TimeWindow 过滤器
"FeatureManagement": {
"featureTimeWindow": {
"EnabledFor": [
{
"Name": "TimeWindow",
"Parameters": {
"Start": "2024-03-26 13:30:00",
"End": "2024-03-27 13:30:00"
}
}
]
}
}
指定了一个名为 TimeWindow
的功能过滤器。这是一个可配置的功能过滤,具有 Parameters
属性,配置了功能活动的开始和结束时间 。
app.MapGet("/featureTimeWindow", async Task<IResult> (IFeatureManager manager) =>
{
if (await manager.IsEnabledAsync("featureTimeWindow"))
{
return TypedResults.Ok($"featureTimeWindow is enabled !");
}
return TypedResults.NotFound();
}).WithSummary("TimeWindow 过滤器测试");
调用接口测试:返回 200 符合预期
Percentage 过滤器
百分比过滤器(Percentage Filter)它根据指定的百分比值随机启用或禁用某个特性。这种过滤器允许您控制特性的曝光率,以便在不同的用户群体中测试特性的效果,或者在逐步推广新特性时控制其影响范围。
"FeatureManagement": {
"featurePercentage": {
"EnabledFor": [
{
"Name": "Percentage",
"Parameters": {
"Value": "50"
}
}
]
}
},
app.MapGet("/featurePercentage", async Task<IResult> (IFeatureManager manager) =>
{
if (await manager.IsEnabledAsync("featurePercentage"))
{
return TypedResults.Ok($"featurePercentage is enabled !");
}
return TypedResults.NotFound();
}).WithSummary("Percentage 过滤器测试");
连续测两次
第一次测试结果: 返回 200
第二次测试结果:返回 404
通过测试结果可以看出有百分之五十的几率成功,符合预期。
RequirementType
功能标志的 RequirementType
属性用于确定在评估功能状态时,过滤器应该使用任何(Any
)还是全部(All
)逻辑。如果未指定 RequirementType
,则默认值为 Any
。
Any
表示只需一个过滤器评估为true
,特性就会被启用。All
表示每个过滤器都必须评估为true
,特性才会被启用。
RequirementType
为All
会改变遍历方式。首先,如果没有过滤器,则功能将被禁用。然后,遍历特性过滤器,直到其中一个过滤器决定应将功能禁用。如果没有过滤器指示应禁用功能,则该功能将被视为已启用。
"FeatureManagement": {
"featureRequirementTypeAll": {
"RequirementType": "All",
"EnabledFor": [
{
"Name": "TimeWindow",
"Parameters": {
"Start": "2024-03-27 13:00:00",
"End": "2024-05-01 13:00:00"
}
},
{
"Name": "Percentage",
"Parameters": {
"Value": "50"
}
}
]
}
},
app.MapGet("/featureRequirementTypeAll", async Task<IResult> (IFeatureManager manager) =>
{
if (await manager.IsEnabledAsync("featureRequirementTypeAll"))
{
return TypedResults.Ok($"featureRequirementTypeAll is enabled !");
}
return TypedResults.NotFound();
}).WithSummary("RequirementTypeAll 多过滤器测试");
上面的实例设置为 all
之后此功能标志的过滤器列表必须全部符合要求才能调用成功。
比如上面我设置的开始日期是2024-03-27 13:00:00
当前时间小于这个日期
无论调用几次还是还是 404,结果符合我们的预期。
自定义过滤器
要实现一个功能过滤器,必须要实现的是一个IFeatureFilter
接口,接口包含了一个EvaluateAsync
的方法。当功能标志指定启用该过滤器时,将调用 EvaluateAsync
方法,如果方法返回的是true
,则表示应该启用功能。
定义一个中间件接口只对某个用户组做开放,这个场景在 C 端的产品上比较常见,比如说部分功能的内测。
[FilterAlias("AuthenticatedGroup")]
public class AuthenticatedGroupFilter : IFeatureFilter, IFeatureFilterMetadata, IFilterParametersBinder
{
public object BindParameters(IConfiguration parameters)
{
return parameters.Get<GroupSetting>() ?? new GroupSetting();
}
public Task<bool> EvaluateAsync(FeatureFilterEvaluationContext featureFilterContext)
{
GroupSetting filterSettings = ((GroupSetting)featureFilterContext.Settings) ?? ((GroupSetting)BindParameters(featureFilterContext.Parameters));
// 假设您有一个方法来检查用户是否已通过身份验证
// 例如,这可能是一个从身份验证服务或中间件中获得的属性或方法
bool isAuthenticated = IsGroupAuthenticated(filterSettings);
return Task.FromResult(isAuthenticated);
}
private bool IsGroupAuthenticated(GroupSetting groupSetting)
{
// 在这里编写您的身份验证检查逻辑
// 这可能涉及到检查HTTP请求的上下文、会话状态、令牌等
// 具体的实现将取决于您使用的身份验证机制
// 示例:返回一个硬编码的值,表示用户是否已通过身份验证
// 在实际应用中,您应该实现实际的检查逻辑
return true; // 或者 false,取决于用户是否已通过身份验证
}
}
FilterAlias
是定义过滤器的别名,我们在配置文件中指定时需要用别名,IFeatureFilter
接口返回的信息决定功能是否启用,IFeatureFilterMetadata
是一个空的标记接口,用于评估功能状态的特征过滤器的标记接口,IFilterParametersBinder
接口用于参数绑定。
- json 配置
"FeatureManagement": {
"featureAuthencatedGroup": {
"EnabledFor": [
{
"Name": "AuthenticatedGroup",
"Parameters": {
"Groups": [ "AdminGroup", "GroupOne" ]
}
}
]
}
}
- 依赖注入
services.AddFeatureManagement()
.AddFeatureFilter<AuthenticatedGroupFilter>();
调用 AddFeatureFilter
方法可把自定义的过滤器注册到功能管理器中。
app.MapGet("/featureAuthencatedGroup", async Task<IResult> (IFeatureManager manager) =>
{
if (await manager.IsEnabledAsync("featureAuthencatedGroup"))
{
return TypedResults.Ok($"featureAuthencatedGroup is enabled !");
}
return TypedResults.NotFound();
}).WithSummary("AuthencatedGroup 自定义过滤器测试");
测试一下,返回 200 ,符合预期
一个小 tips;如果多个过滤器有同一个别名是,可以用命名空间加别名的方式来定义唯一一个过滤器,例如,Microsoft.Percentage
是一个完全限定的别名,它明确指出了 Percentage
过滤器位于 Microsoft
命名空间下
自定义开启中间件
"FeatureManagement": {
"featureMiddleWare": {
"EnabledFor": [
{
"Name": "Percentage",
"Parameters": {
"Value": "50"
}
}
]
}
}
自定义中间件
public class FeatureMiddleWare(RequestDelegate next)
{
public async Task Invoke(HttpContext context)
{
Console.WriteLine("FeatureMiddleWare管道执行之前~");
await next(context);
Console.WriteLine("FeatureMiddleWare管道执行之后~");
}
}
添加扩展方法
//测试中间件的功能开启
app.UseMiddlewareForFeature<FeatureMiddleWare>("featureMiddleWare");
随便调用一个接口测试一下,可以看到管道根据百分比触发成功
通过上述调用,应用程序添加了一个中间件组件,只有在特性“featureMiddleWare”被启用时才会出现在请求管道中。如果在运行时启用/禁用特性,中间件管道可以动态更改。
这是建立在基于特性对整个应用程序进行分支的更通用能力之上。
app.UseForFeature(featureName, appBuilder =>
{
appBuilder.UseMiddleware<T>();
});
MinimalApis 集成
在我们的 MVC 或者 Razor Pages 中有如下方案来启用工农的开关,不过多介绍大家可以官方浏览学习。
services.AddMvc(o =>
{
o.Filters.AddForFeature<SomeMvcFilter>("FeatureX");
});
[FeatureGate("FeatureX")]
public class IndexModel : PageModel
{
public void OnGet()
{
}
}
在 MinimalAps
中可以利用 endpoint filter
来简化公功能的开关,
- 第一步创建最小 Api 的基类,所有的 MinimalApis 过滤器都要继承它
public abstract class FeatureFlagEndpointFilter(IFeatureManager featureManager) : IEndpointFilter
{
protected abstract string FeatureFlag { get; }
private readonly IFeatureManager _featureManager = featureManager;
public async ValueTask<object?> InvokeAsync(EndpointFilterInvocationContext context,
EndpointFilterDelegate next)
{
var isEnabled = await _featureManager.IsEnabledAsync(FeatureFlag);
if (!isEnabled)
{
return TypedResults.NotFound();
}
return await next(context);
}
}
- 定义目标 Json 配置
"FeatureManagement": {
"featureUserApi": {
"EnabledFor": [
{
"Name": "Percentage",
"Parameters": {
"Value": "50"
}
}
]
}
- 定义最小 Api 过滤器
public class UserApiFeatureFilter(IFeatureManager featureManager) : FeatureFlagEndpointFilter(featureManager)
{
protected override string FeatureFlag => "featureUserApi";
}
- 定义 Api 接口测试
//最小Api分组功能添加
{
var userGroup = app.MapGroup("User").WithTags("User").AddEndpointFilter<UserApiFeatureFilter>(); ;
userGroup.MapGet("/featureUserApi", IResult (IFeatureManager manager) =>
{
return TypedResults.Ok($"featureUserApi is enabled !");
}).WithSummary("featureUserApi 最小Api过滤器测试");
}
调用测试,可以看出我们配置的百分比过滤器成功。
通过对 IEndpointFilter
的封装借助最小 Api
的 MapGroup
可以对一组相关的 Api 进行功能管理,简化了我们一个个 Api 注册。
最后
在本文中,我们深入探讨了.NET Feature Management
库的安装、配置和使用方法,以及如何利用功能开关来动态管理应用程序的功能。以下是关键点的总结和提炼:
安装与依赖注入:通过
.NET CLI
或NuGet Package Manager
安装等方式Microsoft.FeatureManagement.AspNetCore
库,并在应用程序中添加功能管理服务的依赖注入。功能定义与配置:通过
.NET
的配置系统,在appsettings.json
中定义功能标志,指定功能的启用和禁用状态,以及可选的功能过滤器配置。自定义功能过滤器:实现
IFeatureFilter
接口来定义自定义功能过滤器,根据特定条件决定功能是否启用,例如基于用户组、时间窗口或百分比等条件。功能开关的使用:利用
IFeatureManager
的IsEnabledAsync
方法检查功能是否启用,根据不同的功能状态执行相应的逻辑,实现功能的动态控制。RequirementType
设置:可以通过RequirementType
属性指定功能过滤器的逻辑要求,是Any
还是All
,决定多个过滤器的组合逻辑。自定义中间件的动态切换:通过自定义功能过滤器和中间件,可以根据功能状态动态调整请求管道,实现功能开关对中间件的控制。
最小 API 集成:在
Minimal APIs
中,利用IEndpointFilter
接口来简化功能开关的应用,将功能管理应用到最小 API 的端点上,实现对一组相关API
的功能管理。
通过以上总结和提炼,您可以更好地了解和应用.NET Feature Management
库,实现灵活的功能管理和动态控制应用程序的功能。
有条件的富哥可以体验一下在 Azure 应用程序配置中管理功能标志
更多详细的内容请浏览FeatureManagement-Dotnet
本文测试完整源代码
深度探索.NET Feature Management功能开关的魔法的更多相关文章
- 微软Azure配置中心 App Configuration (二):Feature Flag 功能开关特性
写在前面 Web服务开发过程中我们经常有这样的需求: 某些功能我必须我修改了配置才启用,比如新用户注册送券等: 某个功能需到特定的时间才启用,过后就失效,比如春节活动等: 某些功能,我想先对10%的用 ...
- 如何在 Blazor WebAssembly中 使用 功能开关
微软Azure 团队开发的 功能管理 (Feature Management) 包 Microsoft.FeatureManagement可用于实现 功能开关,可以通过 功能开关 特性动态的改变应用程 ...
- Delphi深度探索-CodeSite应用指南
Delphi深度探索-CodeSite应用指南 Delphi虽然为我们提供极其强大的调试功能,查找Bug仍然是一项艰巨的工作,通常我们写代码和调试代码的所消耗的时间是大致相同的,甚至有可能更多.为了减 ...
- Kibana5 数据探索使用(Discover功能)
认识Kibana Kibana 是一个为 Logstash 和 ElasticSearch 提供的日志分析的 Web 接口.可使用它对日志进行高效的搜索.可视化.分析等各种操作.Kibana的使用场景 ...
- Android深度探索总结
Android深度探索前四章总结 通过这几章的学习真实体会到“移植”的概念:为特定设备定制Android的过程,但是移植的过程中开发最多的就是支持各种硬件设备的Linux驱动程序,本章对Android ...
- 深度探索MySQL主从复制原理
深度探索MySQL主从复制原理 一 .概要 MySQL Replication (MySQL 主从复制) 是什么? 为什么要主从复制以及它的实现原理是什么? 1.1 MySQL 主从复制概念 MySQ ...
- 读书笔记《深度探索c++对象模型》 概述
<深度探索c++对象模型>这本书是我工作一段时间后想更深入了解C++的底层实现知识,如内存布局.模型.内存大小.继承.虚函数表等而阅读的:此外在很多面试或者工作中,对底层的知识的足够了解也 ...
- 柔性数组-读《深度探索C++对象模型》有感 (转载)
最近在看<深度探索C++对象模型>,对于Struct的用法中,发现有一些地方值得我们借鉴的地方,特此和大家分享一下,此间内容包含了网上搜集的一些资料,同时感谢提供这些信息的作者. 原文如下 ...
- SharePoint solution and feature management with PowerShell
/* Author: Jiangong SUN */ Hello, I want to introduce SharePoint solution and feature management usi ...
- 柔性数组-读《深度探索C++对象模型》有感
最近在看<深度探索C++对象模型>,对于Struct的用法中,发现有一些地方值得我们借鉴的地方,特此和大家分享一下,此间内容包含了网上搜集的一些资料,同时感谢提供这些信息的作者. 原文如下 ...
随机推荐
- OneCloud记录
配置信息 S805, 1G RAM, 8G ROM, USB2.0 * 2, 1GB LAN, SD Cardreader S805参数: 32-bit, ARMv7-A, Cortex-A5, 1. ...
- 【Unity3D】GUI控件
1 前言 Unity 3D 提供了 GUI.NGUI.UGUI 等图形系统,以增强玩家与游戏的交互性.GUI 在编译时不能可视化,在运行时才能可视化.GUI 代码需要在 OnGUI 函数中调用才能 ...
- 【Android】截图案例
1 工作空间 2 代码 MainActivity.java package com.zhyan8.demo; import android.graphics.Bitmap; import andr ...
- eslint+prettier 统一代码风格
1.实现效果 Eslint校验代码语法,prettier统一格式化代码,按下保存自动修复eslint错误,自动格式化代码. 2.安装vscode插件 Vetur ESLint Prettier - C ...
- python3调用nmap封装
python3调用nmap封装; 外部处理好参数后直接调用; #!/usr/bin/env python # -*- coding: utf-8 -*- """ 代码修改 ...
- win32 - 基于hwnd获取进程名字(GetModuleFileNameEx)
#include <Windows.h> #include <psapi.h> int main() { DWORD process_ID = 0; WCHAR process ...
- npm代理 -- 解决在公司内网如何装包的问题
什么是Npm代理 npm代理指的是npm包管理器在使用时通过代理访问npm服务器获取依赖包的过程.在某些情况下,我们需要npm走代理才能访问到npm服务器,否则会出现timeout的错误.那下面我们就 ...
- 文件IO操作开发笔记(一):使用Qt的QFile对磁盘文件存储进行性能测试以及测试工具
前言 在做到个别项目对日志要求较高,要求并行写入的数据较多,尽管写入数据的线程放在子线程,仍然会造成界面程序的假死(实际上Qt还是在跑,只是磁盘消耗超过瓶颈,造成假死(注意:控制台还能看到打印输出 ...
- chrome浏览器配置自定义搜索引擎
chrome谷歌浏览器配置自定义搜索引擎 放弃百度搜索已经酝酿许久,现在搜索结果简直不忍直视.如果你想放弃使用百度搜索,并转向其他搜索引擎,头条搜索可能是一个不错的选择. 使用以下方式可以丝滑的使用其 ...
- Java 多线程----- 解决线程安全问题的 方式三:Lock锁 --------jdk 5.0 新增
1 package bytezero.deadlock; 2 3 import java.util.concurrent.locks.ReentrantLock; 4 5 /** 6 * 解决线程安全 ...