凡.NET之众,终遇同障:既成之功能,或立於试场,然推之於实境,则须一转其钥,使众皆受之。倘有倾覆——或因谬识,或缘奇境,或致迟滞——则唯可回撤重布,以图补救。
特性标志以分离部署与发布而解此困。将代码隐于旗之后,自仪表盘控其见者,不触管廊。初试百分之一之用,察其度数,渐扩至百。若误骤增于任阶,秒息可禁其旗。
快启:.NET 8 之特性旗
自 NuGet 安 Rollgate SDK:
dotnet add package Rollgate.SDK
于应用启动时初始化客户端:
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调用皆自内存字典读取——仅毫秒之微耗。
注册于依赖注入
于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>();
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);
}
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();
鲍尔佐服务器之特徵旗
@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);
}
}
鲍尔佐网页组件,于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
}
}
:
__JHSNS_SEG_1a03b836_48__:__JHSNS_SEG_1a03b836_49__出全屏模式__JHSNS_SEG_1a03b836_50__:__JHSNS_SEG_1a03b836_51__ 渐进式发布与用户定向
识人之际(每会一次),当传用户之属。
await _client.IdentifyAsync(new UserContext
{
Id = userId,
Email = userEmail,
Attributes = new Dictionary<string, object?>
{
["plan"] = user.SubscriptionPlan,
["country"] = user.Country,
}
});
于仪表盘设百分比铺展与属之靶向——SDK于本地评诸规,无每评之API呼。
诸问
二零二六,.NET之特征旗何术?
- 简易之切换,无运行时之变——
Microsoft.FeatureManagement - CNCF中立厂商SDK→
OpenFeature .NET - 仪表盘+目标定位+渐进式发布→管理服务(Rollgate, LaunchDarkly等)
.NET 背景服务可与之相合乎?
然。注册RollgateClient为独体,注入之BackgroundService,用IsEnabled在ExecuteAsync.
何若InitializeAsync启动之际,其功不就。
若无缓存,则抛出异常。或捕获之,依默认值继续;或任其显现(速败通常更安全)。
览全文——含断路器配置、杀软开关之SSE流式传输、及Blazor WebAssembly模式——于rollgate.io/blog/功能标志-ASP.NET Core。
创 Rollgate 免费账户于https://app.rollgate.io/register — 无需信用卡。











