{"id":26271892,"url":"https://github.com/igouist/demo.dotnetcore.featuremanagement","last_synced_at":"2025-07-06T22:38:41.924Z","repository":{"id":253608870,"uuid":"844009349","full_name":"Igouist/Demo.DotnetCore.FeatureManagement","owner":"Igouist","description":"FeatureManagement 和 Feature Flag/Feature Toggle 的示範專案。請參見：https://igouist.github.io/post/2024/08/dotnet-feature-flag-and-feature-management/","archived":false,"fork":false,"pushed_at":"2024-08-18T05:23:57.000Z","size":17,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-06-16T04:28:41.933Z","etag":null,"topics":["feature-flags","featuremanagement"],"latest_commit_sha":null,"homepage":"https://igouist.github.io/post/2024/08/dotnet-feature-flag-and-feature-management/","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Igouist.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-08-18T05:13:42.000Z","updated_at":"2024-08-18T05:25:20.000Z","dependencies_parsed_at":"2024-08-18T06:32:52.705Z","dependency_job_id":"c865a419-30e5-4cfb-b455-96fc7831ec7d","html_url":"https://github.com/Igouist/Demo.DotnetCore.FeatureManagement","commit_stats":null,"previous_names":["igouist/demo.dotnetcore.featuremanagement"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/Igouist/Demo.DotnetCore.FeatureManagement","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Igouist%2FDemo.DotnetCore.FeatureManagement","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Igouist%2FDemo.DotnetCore.FeatureManagement/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Igouist%2FDemo.DotnetCore.FeatureManagement/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Igouist%2FDemo.DotnetCore.FeatureManagement/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Igouist","download_url":"https://codeload.github.com/Igouist/Demo.DotnetCore.FeatureManagement/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Igouist%2FDemo.DotnetCore.FeatureManagement/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":260891135,"owners_count":23077908,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["feature-flags","featuremanagement"],"created_at":"2025-03-14T07:15:07.197Z","updated_at":"2025-06-20T06:05:53.365Z","avatar_url":"https://github.com/Igouist.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"這個 Repo 是 FeatureManagement \u0026 Feature Flag 的測試用專案。以下將備份部落格文章內容。\n\n\u003e 筆記內容同步發布至 Blog 好讀版：[.Net: 使用 FeatureManagement 套件來實作 Feature Flag 功能切換吧 - 伊果的沒人看筆記本](https://igouist.github.io/post/2024/08/dotnet-feature-flag-and-feature-management/)\n\n---\n\n# .Net: 使用 FeatureManagement 套件來實作 Feature Flag 功能切換吧\n\n![Image](https://i.imgur.com/926KRip.png)\n\n在做上一篇 [IOptions](https://igouist.github.io/post/2024/08/dotnet-ioptions) 的筆記時，剛好看到 FeatureManagement 這香東西。\u003cbr/\u003e馬上來收錄一篇。順便也簡單整理一下 Feature Flag (= Feature Toggle) 的介紹。\n\n## 認識一下 Feature Flag\n\n\u003e 本部落格秉持著「簡單、友善、我好菜」的精神，按照慣例先簡單介紹一下\u003cbr/\u003e\n\u003e 已經知道的朋友就可以跳過這個小節，直接前往 #環境準備 囉。\n\n假設我們原本有 Old 邏輯：\n\n```csharp\nOld();\n```\n\n天庭傳來諭令，要我們改成 New 邏輯。這簡單，我們就把 Old 砍掉，換成 New。非常自然，改完就佈版\n\n```csharp\nNew();\n```\n\n隔天，天庭又傳來諭令，New 需要調整一下，先不要了現在我們又需要把 New 邏輯砍掉，讓 Old 邏輯回來。簡單，但看來我們得再上一版\n\n```csharp\nOld();\n// New();\n```\n\n再隔天，大家可能猜到天庭又要幹嘛了，總之又上了一版\n\n```csharp\n// Old();\nNew();\n```\n\n如此往復三萬八千次，工程師終於受不了了：「俺老孫每天在這切換 Old 跟 New，改完還得佈版，每天搞這些就飽了，我滴媽呀，不幹了」\n\n就在老孫關燈走人的那一瞬間，突然靈光一現：等等，**俺加個開關還不行嗎？**\n\n\u003c!--more--\u003e\n\n```csharp\nif (NewFunctionEnabled())\n{\n    New();\n}\nelse\n{\n    Old();\n}\n```\n\n現在只要把這個開關放到外面，別寫死在程式碼裡。丟去什麼 appsettings.json、Config、資料庫、ini、東海龍王廟，隨便\n\n總之丟去個不用佈板就能直接修改的地方，不就完事了嗎？\n\n現在程式碼就像盯著旗手的士兵們，旗手舉了藍色，他們就往左跑；旗手舉了紅色，他們就往右跑。老孫只要把設定給改了，就能一鍵切換邏輯，好不自在。\n\n**沒錯，老孫剛剛發現了 Feature Flag**。\n\n有了這個開關，除了開開關關(?)，現在也可以做到更多事情了。\n\n例如新功能想先隨機讓一些使用者踩看看，馬上在這個 `NewFunctionEnabled()` 開關後面加點料，讓它隨機打開、隨機關起來；\n\n想讓合作好的客戶搶先使用，馬上在這個 `NewFunctionEnabled()` 開關後面偷塞一張白名單：\u003cbr/\u003e你聽好了舉旗仔，看到這些人就幫我舉紅的旗子，門就會打開\n\n因為我們把開關抽出去了，代表判斷條件和執行邏輯之間被我們降低耦合了，彈性就變高了，我們也就可以針對開關內部的邏輯動手動腳了\n\n一時之間，花果山分部的工程師紛紛效仿老孫，\u003cbr/\u003e用這個**香香的 Feature Flag 來從外部動態切換程式碼邏輯**，好不爽快。\n\u003cbr/\u003e吱聲此起彼落，可謂是：\n\n\u003e 兩條邏輯改又改，改完還要等佈版\u003cbr/\u003e\n\u003e 有了 Feature Flag，切個開關就下班\n\n至於後來開關越埋越多，系統服務七十二變。工程師老孫被迫跟著唐經理去聽研討會，好好學習怎麼管理這些 Feature Flag。還弄了些儀表板、過期時間等等，那又是另一個故事了…\n\n故事說到這裡，雖然例子粗暴一點，但朋朋們應該對 Feature Flag 在幹嘛有點了解了（吧？）\n\n如果想要進一步認識 Feature Flag，這邊也準備了一些推薦閱讀。\n\n例如 Feature Flag 還可以用在哪、分成哪些種類；\u003cbr/\u003e\n兩個 Flag 共用到同一段 Code 怎辦；\u003cbr/\u003e\nPM 朋朋們又是怎麼運用 Feature Flag 的……\n\n都丟在下面這團，提供給有興趣的朋朋們：\n\n- [Feature Toggles (aka Feature Flags) - martinFowler](https://martinfowler.com/articles/feature-toggles.html)\n- [Feature Toggle 應用常見問題 | Miles' Blog](https://mileschou.me/blog/feature-toggle-faq/)\n- [產品上線規劃：Beta Release、Feature Flags 實作經驗與工具分享 | by Anne Hsiao | 3PM LAB 產品三眼怪實驗室 | Medium](https://medium.com/3pm-lab/product-beta-release-planning-and-feature-flags-implementation-1f7673007d23)\n- [Introduction to Feature Toggles and Implementation Best Practices - Darren Sim](https://darrensim.com/2018/introduction-to-feature-toggles-and-implementation-best-practices/)\n\n\u003e 補充：雖然 Feature Flag 這麼香，但如果開關沒有好好管理，\u003cbr/\u003e不小心開到不該開的程式碼邏輯會發生什麼事呢？\n\u003e\n\u003e 泡杯茶，看看實例吧：[Knightmare: A DevOps Cautionary Tale – Doug Seven](https://dougseven.com/2014/04/17/knightmare-a-devops-cautionary-tale/)\n\n\u003e 補充：Feature Toggle 常常跟主幹開發（Trunk-based Development）一起出現？\n\u003e \n\u003e 蠻常聽到這個問題的，這邊也簡單筆記一下，沒有採用主幹開發的朋朋可以跳過這小段\n\u003e \n\u003e （想認識 Trunk-based Development 的朋朋們，\u003cbr/\u003e　可以右轉去 [TrunkBasedDevelopment.com](https://tw.trunkbaseddevelopment.com/)，感謝協助中文化的大大們）\n\u003e \n\u003e 回到問題，我的理解是這樣：\n\u003e \n\u003e 即使我們採用了主幹開發，持續把變更交付到主幹分支上，把大家的 GAP 降到最低，\u003cbr/\u003e但一定還是會遇到需要做很久才做得完的功能\n\u003e \n\u003e 我們不想要拉一條 feature 分支，放著漸漸就跟主幹脫節，到時候合併又要大爆炸\u003cbr/\u003e\n\u003e 而是想要把這些程式碼持續地整合到我們的程式庫，這樣就能及早發現問題和解除衝突\u003cbr/\u003e\n\u003e 可是如果粗暴地合併進去，開發中的功能就會暴露出來…怎麼辦呢？\n\u003e \n\u003e 這種時候，如果有一個開關，來幫助我們關閉尚未完工的功能、打開即將交付的功能。我們就能整合現有的程式碼，也能提早知道合併會不會炸爛其他功能…\n\u003e \n\u003e 很方便對吧？ Feature Toggle 就跳出來啦。香。\n\n## 環境準備：Microsoft.FeatureManagement\n\n好，我們前面簡單介紹了一下 Feature Flag，接著就進入實作階段。\n\n要實現 Feature Flag 的方法有很多，例如我之前都是用[上一篇](https://igouist.github.io/post/2024/08/dotnet-ioptions)提到的 IOption 直接從 appsettings.json 把 true/false 讀出來用\n\n而這篇我們要來使用香香的 FeatureManagement 來幫我們實現 Feature Flag 的操作。\n\n首先，我們要先搞到 Microsoft.FeatureManagement.AspNetCore 這個套件：\u003cbr/\u003e[NuGet Gallery | Microsoft.FeatureManagement.AspNetCore](https://www.nuget.org/packages/Microsoft.FeatureManagement.AspNetCore)\n\n接著讓我們把它給註冊到我們的 Program.cs：\n\n```csharp\n// Program.cs\n\n// 環境準備：Microsoft.FeatureManagement\n// 加入 Feature Flag 功能，預設會抓 appsettings.json 中的 \"FeatureManagement\" 節點\nbuilder.Services.AddFeatureManagement();\n\n// 如果想要自訂 Feature Flag 在 appsettings.json 裡的位置，可以這樣寫\n//builder.Services.AddFeatureManagement(builder.Configuration.GetSection(\"CustomFeatureManagement\"));\n```\n\n然後為了後續的示範，先開好一組 Controller：\n```csharp\n// Controllers/DemoController.cs\n\n/// \u003csummary\u003e\n/// 示範 Feature Flag 的使用\n/// \u003c/summary\u003e\n[ApiController]\n[Route(\"[controller]\")]\npublic class DemoController : ControllerBase\n{\n    private readonly ILogger\u003cDemoController\u003e _logger;\n    private readonly IFeatureManager _featureManager;\n\n    public DemoController(\n        ILogger\u003cDemoController\u003e logger, \n        IFeatureManager featureManager)\n    {\n        _logger = logger;\n        _featureManager = featureManager;\n    }\n}\n```\n\n這樣環境就搞定了，讓我們開始第一組實作吧！\n\n## 實作：使用 IsEnabledAsync 拿到 Feature Flag 的值\n\n假設我們跟上面的老孫一樣：翻寫了一條新版邏輯。\u003cbr/\u003e如果開關被打開了，就走新版的路線；關著的話就維持舊版\n\n要達成上面的條件，我們會**需要直接取得某個 Flag 的開關狀態，\u003cbr/\u003e這時候就可以使用 `IsEnabledAsync`**\n\n先到 appsettings.json 加上我們的 Feature Flag，先叫做 `NewLogicEnabled`：\n\n```json\n// appsettings.json\n\n\"FeatureManagement\": {\n  \"NewLogicEnabled\": true\n}\n```\n\n![Image](https://i.imgur.com/FzIALd8.png)\n\n為了後面方便使用，我習慣先建一組簡單的 Const 來管理這些 Feature Flag：\n\n```csharp\n// Flags/FeatureFlags.cs\n\npublic class FeatureFlags\n{\n    // 這邊的名稱要和 appsettings.json 裡 FeatureManagement 底下的設定一樣\n    public const string NewLogicEnabled = \"NewLogicEnabled\"; \n}\n```\n\n讓我們加上這次測試用的 API，並且使用 IsEnabledAsync 來抓取指定的 Flag 狀態：\n```csharp\n// Controllers/DemoController.cs\n\n// Feature Flag 可以讓我們從設定檔中讀取特定的開關\n// 並且在程式中根據這些開關來決定是否啟用某些功能\n[HttpGet(\"IsEnabledAsync\")]\npublic async Task\u003cstring\u003e IsFeatureEnabled()\n{\n    // Demo 1: 使用 IsEnabledAsync 拿到 Feature Flag 的值 \n    var isEnabled = await _featureManager.IsEnabledAsync(FeatureFlags.NewLogicEnabled);\n    \n    return isEnabled \n        ? \"香香的新版邏輯已啟用。\"\n        : \"新版邏輯尚未啟用，繼續使用臭臭的舊版邏輯。\";\n}\n```\n\n![Image](https://i.imgur.com/O1uxn9W.png)\n\n現在讓我們直接把 appsettings.json 裡的 `NewLogicEnabled` 改成 false（只要存檔就好了，不需要重啟服務），並且再呼叫一次 API：\n\n![Image](https://i.imgur.com/WTgcapM.png)\n\n來張動圖看看成果：\n\n![Image](https://i.imgur.com/9elZfT7.gif)\n\n搞定，收工下班。\n\n## 實作：使用 FeatureGate 來啟用和停用 API\n\n除了根據開關決定要執行哪條路線的邏輯以外，我們也常常根據開關決定某些功能是不是要開放\n\n**如果我們需要根據 Feature Flag 來決定是否啟用某支 API，就可以使用 `FeatureGate`**\n\n首先讓我們加上第二組 Flag，這次叫做 `NiceApiEnabled`：\n\n```json\n// appsettings.json\n\n\"FeatureManagement\": {\n  \"NewLogicEnabled\": false,\n  \"NiceApiEnabled\": true\n}\n```\n\n```csharp\n// Flags/FeatureFlags.cs\n\npublic class FeatureFlags\n{\n    public const string NewLogicEnabled = \"NewLogicEnabled\"; \n\n      // 新增 NiceApiEnabled\n    public const string NiceApiEnabled = \"NiceApiEnabled\";\n}\n```\n\n接著就來替我們的 API 掛上 FeatureGate 試試：\n```csharp\n// Controllers/DemoController.cs\n\n[HttpGet(\"FeatureGate\")]\n[FeatureGate(FeatureFlags.NiceApiEnabled)] // 讓 FeatureGate 去檢查我們的 NiceApiEnabled\npublic string FeatureGate()\n{\n    // Demo 2: 使用 FeatureGate 來啟用和停用 API\n    // 如果 NiceApiEnabled 是關閉的，這個方法不會被執行，會直接回傳 404\n    return \"這是一個很棒的 API，只有你看得到。\";\n}\n```\n\n`NiceApiEnabled` 是 true 的時候，可以正常呼叫到 API：\n![Image](https://i.imgur.com/2el7jVT.png)\n\n而當 `NiceApiEnabled` 是 false 的時候，這支 API 就 不存在了…\n![Image](https://i.imgur.com/BV3aomM.png)\n\n## 實作：使用 FeatureFilter 自訂篩選條件、隨機抽取使用者\n\n前面我們示範了怎麼看開關狀態，以及根據開關把 API 給 Ban 了。接著來點場景應用吧。\n\n之前遇過需要隨機抽樣去分流的狀況（把 50% 使用者引導去翻寫好的新站台觀察狀況之類的）\n\n**像這種需要自己寫某些邏輯來決定開關是否開啟的場合，就可以使用 FeatureFilter 來實現**\n\n---\n\n跟前兩個範例一樣，我們需要先去 `appsettings.json` 掛上我們的 Feature Flag。既然是要隨機抽取幸運兒體驗我們的新功能，就叫做 `RandomTrial`\n\n但要注意，當我們要自訂 Flag 啟用的條件（也就是需要跑過某些邏輯才能決定是 true/false）的時候，就不能像前面兩個範例一樣簡單丟個 `\"RandomTrial\":true` 就搞定\n\n而是要使用 `EnabledFor`，並在裡面標示出我們要呼叫的 FeatureFilter 名稱、需要傳遞的參數，這樣 FeatureManagement 才知道它得依序跑過這些 FeatureFilter\n\n假設我們希望有 50% 的使用者會隨機抽中新功能。我們就可以先在 `EnabledFor` 裡掛一條叫做「Random」的 FeatureFilter，並且丟個 Percentage=50 的參數進去：\n```json\n// appsettings.json\n\n\"FeatureManagement\": {\n  \"NewLogicEnabled\": false,\n  \"NiceApiEnabled\": false,\n  \n  \"RandomTrial\": {\n    \"EnabledFor\": [\n      {\n        \"Name\": \"Random\",\n        \"Parameters\": {\n          \"Percentage\": 50\n        }\n      }\n    ]\n  }\n}\n```\n\n```csharp\n// Flags/FeatureFlags.cs\n\npublic class FeatureFlags\n{\n    public const string NewLogicEnabled = \"NewLogicEnabled\"; \n    public const string NiceApiEnabled = \"NiceApiEnabled\";\n\n    // 新增 RandomTrial\n    public const string RandomTrial = \"RandomTrial\";\n}\n```\n\n接著讓我們實作對應的 FeatureFilter，這邊作法就可以粗暴一點：\n\n- 從 `context.Parameters` 把前面設好的 Percentage 參數拉出來\n- 現場骰下去看有沒有過門檻，沒過就掰掰\n\n```csharp\n// Filters/RandomFilter.cs\n\n/// \u003csummary\u003e\n/// 隨機啟用 API 的 Filter\n/// \u003c/summary\u003e\n/// \u003csee ref=\"https://learn.microsoft.com/zh-tw/azure/azure-app-configuration/howto-feature-filters-aspnet-core\"/\u003e\n[FilterAlias(\"Random\")] // 設定 Filter 的別名，讓 appsettings.json 可以使用這個別名\npublic class RandomFilter : IFeatureFilter\n{\n    public Task\u003cbool\u003e EvaluateAsync(FeatureFilterEvaluationContext context)\n    {\n        var percentage = context.Parameters.GetValue\u003cint\u003e(\"Percentage\");\n        \n        var seed = Guid.NewGuid().GetHashCode();\n        var randomNumber = new Random(seed).Next(100);\n        \n        var isEnabled = randomNumber \u003c= percentage;\n        return Task.FromResult(isEnabled);\n    }\n}\n```\n\n接著我們要把這個 FeatureFilter 拿去註冊：\n```csharp\n// Program.cs\n\nbuilder.Services\n    .AddFeatureManagement()\n    .AddFeatureFilter\u003cRandomFilter\u003e() // 加入自訂的 Feature Filter. see: Filters/RandomFilter.cs\n    ;\n```\n\n接著就像前面的兩個範例一樣，直接拿來用就好嚕\n```csharp\n// Controllers/DemoController.cs\n\n[HttpGet(\"FeatureFilter/Random/\")]\npublic async Task\u003cstring\u003e FeatureFilterRandom()\n{\n    // Demo 3: 實作：使用 IFeatureFilter 來隨機啟用 API - 直接查詢 Feature Flag\n    var isEnabled = await _featureManager.IsEnabledAsync(FeatureFlags.RandomTrial);\n    return isEnabled \n        ? \"恭喜你！你抽中了香香新版邏輯的試用機會！\"\n        : \"什麼事也沒發生，又是臭臭邏輯的一天。\";\n}  \n\n[HttpGet(\"FeatureFilter/Random/FeatureGate\")]\n[FeatureGate(FeatureFlags.RandomTrial)]\npublic string FeatureFilterRandomWithFeatureGate()\n{\n    // Demo 3: 實作：使用 IFeatureFilter 來隨機啟用 API - 透過 FeatureGate 來啟用\n    return \"恭喜你！你抽中了很棒 API 的試用機會！\";\n}\n```\n\n來張動圖看看成果：\n\n![Image](https://i.imgur.com/9l8N7EE.gif)\n\n## 實作：使用 FeatureFilter 來根據白名單啟用 API\n\n接下來試試另一組場景，假設我們打算先讓十位合作客戶試用新功能，\u003cbr/\u003e這時候也可以靠自製 FeatureFilter 來處理。\n\n\u003e 備註：這一節會需要用到依賴注入相關的功能，還不熟的朋友可以參見[依賴注入的筆記](https://igouist.github.io/post/2021/11/newbie-6-dependency-injection/)\n\n首先按照慣例，先加上我們的 Feature Flag\u003cbr/\u003e\n這次就叫做 `Whitelist`，並且只指定 FeatureFilter 的名稱就好，不需要參數：\n\n```json\n// appsettings.json\n\n\"FeatureManagement\": {\n  \"NewLogicEnabled\": false,\n  \"NiceApiEnabled\": false,\n  \"RandomTrial\": {}, // 好長，省略\n  \n  \"Whitelist\": {\n    \"EnabledFor\": [\n      {\n        \"Name\": \"Whitelist\"\n      }\n    ]\n  }\n}\n```\n```csharp\n// Flags/FeatureFlags.cs\n\npublic class FeatureFlags\n{\n    public const string NewLogicEnabled = \"NewLogicEnabled\"; \n    public const string NiceApiEnabled = \"NiceApiEnabled\";\n    public const string RandomTrial = \"RandomTrial\";\n\n    // 新增 Whitelist\n    public const string Whitelist = \"Whitelist\";\n}\n```\n\n接著，假裝一下我們在某張資料表裏面有白名單資料：\n```csharp\n// Repositories/WhitelistRepository.cs\n\npublic interface IWhitelistRepository\n{\n    Task\u003cIEnumerable\u003cstring\u003e\u003e GetWhitelistAsync();\n}\n\npublic class WhitelistRepository : IWhitelistRepository\n{\n    public async Task\u003cIEnumerable\u003cstring\u003e\u003e GetWhitelistAsync()\n    {\n        // 假裝從資料庫中取得白名單 :)\n        return new List\u003cstring\u003e\n        {\n            \"Apple\",\n            \"Banana\",\n            \"Cat\"\n        };\n        // 小提醒：如果真的連線到資料庫，然後名單的改動頻率很低，可以考慮加個快取\n    }\n}\n```\n\n而且這個 Repo 也有好好註冊到 Program.cs。\u003cbr/\u003e\n同時，因為後續的示範一定也需要從 Request 拿到使用者的身分\u003cbr/\u003e\n這邊也註冊了 IHttpContextAccessor 來取得 Request 內容：\n```csharp\n// Program.cs\nbuilder.Services.AddHttpContextAccessor();\nbuilder.Services.AddSingleton\u003cIWhitelistRepository, WhitelistRepository\u003e();\n```\n\u003e 延伸閱讀：[存取 ASP.NET Core 中的 HttpContext](https://learn.microsoft.com/zh-tw/aspnet/core/fundamentals/http-context?view=aspnetcore-8.0#access-httpcontext-from-custom-components) 的「從自訂元件存取 HttpContext」小節\n\n\u003e 備註：如果 FeatureFilter 依賴對象的生命週期是 Scope 的，會需要調整一下 FeatureManagement 的註冊，請看這一小節最後的補充。\n\n上游的假資料準備好了，接著就讓我們來實作白名單 FeatureFilter 吧：\n```csharp\n// Filters/WhitelistFilter.cs\n\n/// \u003csummary\u003e\n/// 白名單啟用 API 的 Filter\n/// \u003c/summary\u003e\n[FilterAlias(\"Whitelist\")]\npublic class WhitelistFilter : IFeatureFilter\n{\n    private readonly IWhitelistRepository _whitelistRepository;\n    private readonly IHttpContextAccessor _httpContextAccessor;\n\n    public WhitelistFilter(\n        IWhitelistRepository whitelistRepository, \n        IHttpContextAccessor httpContextAccessor)\n    {\n        _whitelistRepository = whitelistRepository;\n        _httpContextAccessor = httpContextAccessor;\n    }\n\n    public async Task\u003cbool\u003e EvaluateAsync(FeatureFilterEvaluationContext context)\n    {\n        // 粗暴地從 Request 的 Header 取出使用者名稱\n        // 實作時記得換成真正的使用者資訊來源，例如 JWT Token、拆好的 User 資訊、擲筊的結果，之類的\n        var user = _httpContextAccessor.HttpContext?.Request.Headers[\"User\"]; \n        if (string.IsNullOrWhiteSpace(user))\n        {\n            return false;\n        }\n        \n        // 假裝從資料庫中取得白名單，檢查使用者是否在白名單中\n        // 實作時記得換成真正的白名單來源 \u0026 比對邏輯\n        var whitelist = await _whitelistRepository.GetWhitelistAsync();\n        return whitelist.Any(x =\u003e x.Equals(user, StringComparison.OrdinalIgnoreCase)); // 插個不分大小寫，避免我等等 Demo 手殘\n    }\n}\n```\n\n搞定，開開心心來註冊囉：\n```csharp\n// Program.cs\n\nbuilder.Services\n    .AddFeatureManagement()\n    .AddFeatureFilter\u003cRandomFilter\u003e()\n    .AddFeatureFilter\u003cWhitelistFilter\u003e(); // 把白名單的 FeatureFilter 掛上去\n```\n\n接著捏支 API 來測測看：\n```csharp\n\n// Controllers/DemoController.cs\n\n[HttpGet(\"FeatureFilter/Whitelist/\")]\npublic async Task\u003cstring\u003e FeatureFilterWhitelist(\n    [FromHeader(Name = \"User\")] string user)\n{\n    // Demo 4: 實作：使用 IFeatureFilter 來根據白名單啟用 API\n    var isEnabled = await _featureManager.IsEnabledAsync(FeatureFlags.Whitelist);\n    \n    return isEnabled \n        ? $\"{user} 先生/小姐，你好。歡迎使用本服務。您是我們第 9999 位 VIP 會員！\"\n        : $\"{user} 先生/小姐，你好。很抱歉，本服務尚未開放給您使用。請加入我們的 VIP 會員，取得優先體驗資格！\";\n}\n```\n\n來張動圖看看成果：\n\n![Image](https://i.imgur.com/r2gIrLw.gif)\n\n搞定，收工。又是美好的一天，準時下班囉\n\n\u003e 補充：由於 FeatureManager 是 Singleton 的，而 AddFeatureFilter 會根據 FeatureManager 的生命週期來註冊 FeatureFilter，因此如果依賴對象的生命週期是 Scope，注入的時候就會發生錯誤。\n\u003e \n\u003e 這時候請改用 AddScopedFeatureManagement：\n\u003e\n\u003e ```csharp\n\u003e // Program.cs\n\u003e \n\u003e // builder.Services.AddFeatureManagement();\n\u003e builder.Services.AddScopedFeatureManagement();\n\u003e ```\n\u003e \n\u003e 參考：[Unable to inject Scoped/Transient services into FeatureFilter · Issue #15 · microsoft/FeatureManagement-Dotnet](https://github.com/microsoft/FeatureManagement-Dotnet/issues/15)\n\u003e \n\u003e 延伸閱讀：[依賴注入的三種生命週期 Transient、Scoped、Singleton](https://igouist.github.io/post/2021/11/newbie-6-dependency-injection/#%E4%BE%9D%E8%B3%B4%E6%B3%A8%E5%85%A5%E7%9A%84%E4%B8%89%E7%A8%AE%E7%94%9F%E5%91%BD%E9%80%B1%E6%9C%9F-transientscopedsingleton)\n\n## 小結\n\n稍微玩了一下 FeatureManagement，並且拿了一些前陣子遇過的場景來實作看看\n\n原本以為只是簡單的去組態抓開關，想不到 FeatureFilter 提供的自訂條件還蠻彈性的，挺香\n\nFeatureGate 無腦噴 404 也挺爽的（警告：請跟呼叫端的朋朋喬好= = 不然很危險，生命危險）\n\n如果有想要搞個 Feature Flag 的場合，推薦可以玩玩看。\n\n那麼，今天的筆記就到這邊。我們下篇見～\n\n## 參考資料\n\n- [如何在 .NET 應用程式中使用功能旗標的教學課程 | Microsoft Learn](https://learn.microsoft.com/zh-tw/azure/azure-app-configuration/use-feature-flags-dotnet-core)\n- [[料理佳餚] ASP.NET Core 的 Feature Flags（Feature Toggle） | 軟體主廚的程式料理廚房](https://dotblogs.com.tw/supershowwei/2020/11/02/154134)\n- 延伸閱讀（Azure App Configuration）：[使用 App Configuration 的 Feature manager 功能來實做 Feature Toggle/Flag 機制 | 黯雲端記事錄](https://dotblogs.com.tw/anyun/2022/10/16/160158)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Figouist%2Fdemo.dotnetcore.featuremanagement","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Figouist%2Fdemo.dotnetcore.featuremanagement","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Figouist%2Fdemo.dotnetcore.featuremanagement/lists"}