慣性聚合 高效追蹤和閱讀你感興趣的部落格、新聞、科技資訊
閱讀原文 在慣性聚合中打開

推薦訂閱源

Google DeepMind News
Google DeepMind News
人人都是产品经理
人人都是产品经理
M
MIT News - Artificial intelligence
博客园 - 叶小钗
MyScale Blog
MyScale Blog
V
Visual Studio Blog
月光博客
月光博客
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
量子位
I
InfoQ
有赞技术团队
有赞技术团队
阮一峰的网络日志
阮一峰的网络日志
Jina AI
Jina AI
V
V2EX
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Blog — PlanetScale
Blog — PlanetScale
Last Week in AI
Last Week in AI
雷峰网
雷峰网
Stack Overflow Blog
Stack Overflow Blog
博客园 - Franky

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python)
.NET 8 中的功能旗標:ASP.NET Core、最小化 API、Blazor
Domenico Gio · 2026-05-25 · via DEV Community

這篇文章最初發表在 rollgate.io/blog/feature-flags-aspnet-core.

每一個 .NET團隊最終都會遇到同一個障礙:你有一個功能在測試環境中準備好了,但將其推送到生產環境意味著需要一次性為所有用戶切換開關。如果出現問題——錯誤假設、邊緣情況、性能回退——你唯一的恢復方法就是回滾並重新部署。

特性旗標透過將部署與發布分離來解決這個問題。您將程式碼背後加上旗標,然後從儀表板控制誰能看見它,而無需觸碰您的管線。從1%的使用者開始,觀察您的指標,擴展到100%。如果任何階段的錯誤突增,可在幾秒內禁用旗標.

快速啟動:.NET 8 中的特性旗標

從 NuGet 安裝 Rollgate SDK:

dotnet add package Rollgate.SDK

Enter fullscreen mode 退出全螢幕模式

應用程式啟動時初始化客戶端:

using Rollgate.SDK;

var client = new RollgateClient(new RollgateConfig
{
    ApiKey = Environment.GetEnvironmentVariable("ROLLGATE_API_KEY") ?? "",
});

await client.InitializeAsync();

if (client.IsEnabled("new-checkout", false))
{
    Console.WriteLine("New checkout enabled");
}

client.Dispose();

進入全螢幕模式 退出全螢幕模式

InitializeAsync()之後,每一個IsEnabled呼叫都從記憶體字典讀取 — 單位數微秒開銷.

註冊 Dependency Injection

在 ASP.NET Core 中,將客戶端註冊為單例,並加上一個小的 IFeatureFlags 抽象,以便控制項保持可測試:

// Program.cs
builder.Services.AddSingleton<RollgateClient>(sp =>
{
    var client = new RollgateClient(new RollgateConfig
    {
        ApiKey = builder.Configuration["Rollgate:ApiKey"] ?? "",
        RefreshInterval = TimeSpan.FromSeconds(30),
    });
    // Tutorial simplicity. In production, prefer IHostedService.
    client.InitializeAsync().GetAwaiter().GetResult();
    return client;
});

builder.Services.AddSingleton<IFeatureFlags, RollgateFeatureFlags>();

Enter fullscreen mode Exit fullscreen mode

public interface IFeatureFlags
{
    bool IsEnabled(string flagKey, bool defaultValue = false);
}

public sealed class RollgateFeatureFlags : IFeatureFlags
{
    private readonly RollgateClient _client;
    public RollgateFeatureFlags(RollgateClient client) => _client = client;
    public bool IsEnabled(string key, bool def = false) => _client.IsEnabled(key, def);
}

Enter fullscreen mode Exit fullscreen mode

ASP.NET Core 控制項中的功能旗標

注入 IFeatureFlags,而不是直接注入 SDK 類型:

[ApiController]
[Route("api/[controller]")]
public class CheckoutController : ControllerBase
{
    private readonly IFeatureFlags _flags;

    public CheckoutController(IFeatureFlags flags) => _flags = flags;

    [HttpPost]
    public async Task<IActionResult> CreateOrder([FromBody] OrderRequest request)
    {
        return _flags.IsEnabled("checkout-v2", false)
            ? Ok(await ProcessV2Async(request))
            : Ok(await ProcessV1Async(request));
    }
}

進入全螢幕模式 離開全螢幕模式

識別用戶 — 每次會話,而不是每次請求

RollgateClient.IdentifyAsync發送一個HTTP請求並觸發旗標刷新。不要在每次請求上調用它 — 這會為每個終端點增加一個網絡往返,並摧毀內存中的評估模型。

正確的位置是一個動作過濾器,每個用戶只執行一次,然後短路:

public class FeatureFlagIdentityFilter : IAsyncActionFilter
{
    private readonly RollgateClient _client;
    private static readonly HashSet<string> _identified = new();
    private static readonly SemaphoreSlim _gate = new(1, 1);

    public FeatureFlagIdentityFilter(RollgateClient client) => _client = client;

    public async Task OnActionExecutionAsync(ActionExecutingContext ctx, ActionExecutionDelegate next)
    {
        var userId = ctx.HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier);
        if (!string.IsNullOrEmpty(userId) && !_identified.Contains(userId))
        {
            await _gate.WaitAsync();
            try
            {
                if (!_identified.Contains(userId))
                {
                    await _client.IdentifyAsync(new UserContext { Id = userId });
                    _identified.Add(userId);
                }
            }
            finally { _gate.Release(); }
        }
        await next();
    }
}

進入全螢幕模式 退出全螢幕模式

生產環境的清理:在登入時呼叫IdentifyAsync一次,之後直到登出前都不再呼叫.

僅有 API 中的功能旗標

app.MapPost("/api/search", async (SearchRequest req, IFeatureFlags flags) =>
{
    return flags.IsEnabled("semantic-search", false)
        ? Results.Ok(await RunSemanticSearchAsync(req.Query))
        : Results.Ok(await RunKeywordSearchAsync(req.Query));
});

進入全螢幕模式 退出全螢幕模式

透過終點過濾器封鎖整條路徑,從請求服務中解析IFeatureFlags

public static class FeatureFlagEndpointExtensions
{
    public static TBuilder RequireFeature<TBuilder>(this TBuilder builder, string flagKey)
        where TBuilder : IEndpointConventionBuilder
    {
        return builder.AddEndpointFilter(async (context, next) =>
        {
            var flags = context.HttpContext.RequestServices.GetRequiredService<IFeatureFlags>();
            if (!flags.IsEnabled(flagKey, false)) return Results.NotFound();
            return await next(context);
        });
    }
}

app.MapGet("/api/v2/analytics", GetAnalyticsV2Handler)
   .RequireFeature("analytics-v2")
   .RequireAuthorization();

進入全螢幕模式 退出全螢幕模式

Blazor Server 中的功能旗標

@page "/checkout"
@inject IFeatureFlags Flags
@inject AuthenticationStateProvider AuthStateProvider

@if (_showNewCheckout) { <NewCheckoutFlow /> } else { <LegacyCheckoutFlow /> }

@code {
    private bool _showNewCheckout;

    protected override void OnInitialized()
    {
        // Runs once per circuit, not per render.
        _showNewCheckout = Flags.IsEnabled("checkout-v2", false);
    }
}

進入全螢幕模式 退出全螢幕模式

對於 Blazor WebAssembly,在Program.cs之前從您的伺服器中擷取旗標RunAsync():

var host = builder.Build();
await host.Services.GetRequiredService<FlagService>().LoadAsync();
await host.RunAsync();

進入全螢幕模式 離開全螢幕模式

在 C 語言中測試功能旗標

由於控制器依賴於 IFeatureFlags,因此不需要模擬框架:

public class FakeFeatureFlags : IFeatureFlags
{
    private readonly Dictionary<string, bool> _flags;
    public FakeFeatureFlags(Dictionary<string, bool>? f = null) => _flags = f ?? new();
    public bool IsEnabled(string key, bool def = false)
        => _flags.TryGetValue(key, out var v) ? v : def;
}

public class CheckoutControllerTests
{
    [Fact]
    public async Task Returns_V2_When_Flag_Enabled()
    {
        var flags = new FakeFeatureFlags(new() { ["checkout-v2"] = true });
        var controller = new CheckoutController(flags);
        var result = await controller.CreateOrder(new OrderRequest { Amount = 99 });
        // assert v2 path
    }
}

進入全螢幕模式 離開全螢幕模式

務必測試兩種旗標狀態.

逐步推出和用戶定位

識別時 (每個會話一次) 請傳入使用者屬性:

await _client.IdentifyAsync(new UserContext
{
    Id = userId,
    Email = userEmail,
    Attributes = new Dictionary<string, object?>
    {
        ["plan"] = user.SubscriptionPlan,
        ["country"] = user.Country,
    }
});

進入全螢幕模式 離開全螢幕模式

在儀表板中設定百分比推出和屬性針對 — SDK 在本地評估所有規則,無需每個評估的 API 呼叫.

常見問題解答

2026年採用哪種 .NET 功能旗標方法?

  • 簡單的開關,無需運行時變更 →Microsoft.FeatureManagement
  • 中立CNCF SDK → OpenFeature .NET
  • 儀表板 + 精準定位 + 漸進式發布 → 受管理服務 (Rollgate, LaunchDarkly 等)

它是否適用於 .NET 背景服務?
是。註冊 RollgateClient 為單例,注入至 BackgroundService,在 IsEnabled 中使用 ExecuteAsync

如果 InitializeAsync 在啟動時失敗怎麼辦?
若沒有快取,就會拋出錯誤。您可以捕捉它並使用預設值繼續,或者讓它顯現出來(通常快速失敗比較安全)。


閱讀完整版本 — 包括斷路器配置、針對關閉開關的 SSE 流式傳輸,以及 Blazor WebAssembly 模式 — 在 rollgate.io/blog/feature-flags-aspnet-core.

https://app.rollgate.io/register 註冊免費的 Rollgate 帳號。 — 不需要信用卡。