前言
.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
物件 - 第三方提供程式
.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 中有如下方案來啟用功能的開關,不過多介紹大家可以官方瀏覽學習。
FeatureManagement-Dotnet
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
本文測試完整原始碼