Claude
Skills
Sign in
Back

feature-flags

Included with Lifetime
$97 forever

Microsoft.FeatureManagement patterns for feature toggles, gradual rollouts, and A/B testing in ASP.NET Core Razor Pages applications. Use when implementing feature toggles in ASP.NET Core applications, setting up gradual feature rollouts, or configuring A/B testing scenarios with feature flags.

General

What this skill does


## Rationale

Feature flags enable safe deployments, gradual rollouts, A/B testing, and quick rollback capabilities. Without proper feature flag patterns, teams risk deploying incomplete features or cannot respond quickly to production issues. These patterns provide a robust, maintainable approach to feature management in Razor Pages applications.

## Patterns

### Pattern 1: Configuration-Based Feature Flags

Use `appsettings.json` for simple feature toggles with environment-specific overrides.

```json
// appsettings.json
{
  "FeatureManagement": {
    "NewDashboard": false,
    "BetaFeature": false,
    "DarkMode": true,
    "PaymentV2": {
      "EnabledFor": [
        {
          "Name": "Microsoft.Targeting",
          "Parameters": {
            "Audience": {
              "Users": [ "[email protected]" ],
              "Groups": [ "BetaTesters" ],
              "DefaultRolloutPercentage": 0
            }
          }
        }
      ]
    }
  }
}

// appsettings.Production.json
{
  "FeatureManagement": {
    "NewDashboard": true,
    "PaymentV2": {
      "EnabledFor": [
        {
          "Name": "Microsoft.Targeting",
          "Parameters": {
            "Audience": {
              "Users": [ "[email protected]" ],
              "Groups": [ "BetaTesters" ],
              "DefaultRolloutPercentage": 25
            }
          }
        }
      ]
    }
  }
}
```

```csharp
// Program.cs - Basic setup
builder.Services.AddFeatureManagement();

// With custom configuration section
builder.Services.AddFeatureManagement(
    builder.Configuration.GetSection("FeatureManagement"));

// With feature filters
builder.Services.AddFeatureManagement()
    .AddFeatureFilter<TargetingFilter>()
    .AddFeatureFilter<PercentageFilter>()
    .AddFeatureFilter<TimeWindowFilter>();
```

### Pattern 2: Typed Feature Flags

Create strongly-typed feature flags for compile-time safety and discoverability.

```csharp
// Feature flag constants
public static class FeatureFlags
{
    public const string NewDashboard = "NewDashboard";
    public const string BetaFeature = "BetaFeature";
    public const string DarkMode = "DarkMode";
    public const string PaymentV2 = "PaymentV2";
    public const string ApiRateLimiting = "ApiRateLimiting";
    public const string AdvancedReporting = "AdvancedReporting";
}

// Feature-aware service interface
public interface IFeatureAwareService
{
    Task<bool> IsEnabledAsync(string featureName);
    Task<bool> IsEnabledAsync<TContext>(string featureName, TContext context);
}

public class FeatureService : IFeatureAwareService
{
    private readonly IFeatureManager _featureManager;

    public FeatureService(IFeatureManager featureManager)
    {
        _featureManager = featureManager;
    }

    public Task<bool> IsEnabledAsync(string featureName) =>
        _featureManager.IsEnabledAsync(featureName);

    public Task<bool> IsEnabledAsync<TContext>(string featureName, TContext context) =>
        _featureManager.IsEnabledAsync(featureName, context);
}

// Extension methods for easier usage
public static class FeatureManagerExtensions
{
    public static Task<bool> IsNewDashboardEnabledAsync(this IFeatureManager manager) =>
        manager.IsEnabledAsync(FeatureFlags.NewDashboard);

    public static Task<bool> IsPaymentV2EnabledAsync(this IFeatureManager manager, string userId) =>
        manager.IsEnabledAsync(FeatureFlags.PaymentV2, new TargetingContext { UserId = userId });
}
```

### Pattern 3: Razor Pages Integration

Use feature flags in Razor Pages for conditional UI rendering and routing.

```csharp
// PageModel with feature flag checks
public class DashboardModel : PageModel
{
    private readonly IFeatureManager _featureManager;

    public DashboardModel(IFeatureManager featureManager)
    {
        _featureManager = featureManager;
    }

    public bool UseNewDashboard { get; private set; }
    public bool IsDarkModeEnabled { get; private set; }

    public async Task OnGetAsync()
    {
        UseNewDashboard = await _featureManager.IsEnabledAsync(FeatureFlags.NewDashboard);
        IsDarkModeEnabled = await _featureManager.IsEnabledAsync(FeatureFlags.DarkMode);
    }
}

// View with conditional rendering
@page
@model DashboardModel
@inject IFeatureManager FeatureManager

@if (Model.UseNewDashboard)
{
    <partial name="_NewDashboard" model="Model" />
}
else
{
    <partial name="_LegacyDashboard" model="Model" />
}

@if (await FeatureManager.IsEnabledAsync(FeatureFlags.BetaFeature))
{
    <div class="alert alert-info">
        <strong>Beta:</strong> Try our new experimental features!
    </div>
}

@if (Model.IsDarkModeEnabled)
{
    <button id="theme-toggle" class="btn btn-outline-secondary">
        Toggle Dark Mode
    </button>
}
```

### Pattern 4: Feature Gate Action Filter

Use the built-in feature gate filter for controller/page-level feature control.

```csharp
// Controller/PageModel level feature gate
[FeatureGate(FeatureFlags.BetaFeature)]
public class BetaFeaturesModel : PageModel
{
    public void OnGet()
    {
        // This page is only accessible when BetaFeature is enabled
    }
}

// Alternative: Redirect to different page
[FeatureGate(FeatureFlags.NewDashboard, 
    RequirementType.All,  // All features must be enabled
    NoFeatureRedirect = "/Dashboard/Legacy")]
public class NewDashboardModel : PageModel
{
    // Redirects to legacy dashboard if NewDashboard is disabled
}

// Custom feature gate attribute for complex scenarios
public class PremiumFeatureAttribute : FeatureGateAttribute
{
    public PremiumFeatureAttribute() 
        : base(FeatureFlags.AdvancedReporting)
    {
    }
}

[PremiumFeature]
public class ReportsModel : PageModel
{
    // Premium feature page
}
```

### Pattern 5: Gradual Rollout with Targeting

Implement user-based and percentage-based rollouts safely.

```csharp
// Custom targeting context
public class FeatureTargetingContext : ITargetingContext
{
    public string? UserId { get; set; }
    public List<string> Groups { get; set; } = new();
}

// Targeting context accessor
public class HttpContextTargetingContextAccessor : ITargetingContextAccessor
{
    private readonly IHttpContextAccessor _httpContextAccessor;

    public HttpContextTargetingContextAccessor(IHttpContextAccessor httpContextAccessor)
    {
        _httpContextAccessor = httpContextAccessor;
    }

    public ValueTask<TargetingContext> GetContextAsync()
    {
        var httpContext = _httpContextAccessor.HttpContext;
        
        if (httpContext?.User?.Identity?.IsAuthenticated != true)
        {
            return ValueTask.FromResult(new TargetingContext());
        }

        var context = new TargetingContext
        {
            UserId = httpContext.User.FindFirst(ClaimTypes.NameIdentifier)?.Value,
            Groups = httpContext.User.FindAll(ClaimTypes.Role)
                .Select(c => c.Value)
                .ToList()
        };

        return ValueTask.FromResult(context);
    }
}

// Registration
builder.Services.AddHttpContextAccessor();
builder.Services.AddSingleton<ITargetingContextAccessor, HttpContextTargetingContextAccessor>();
builder.Services.AddFeatureManagement()
    .AddFeatureFilter<TargetingFilter>();

// Usage in PageModel
public class CheckoutModel : PageModel
{
    private readonly IFeatureManager _featureManager;

    public CheckoutModel(IFeatureManager featureManager)
    {
        _featureManager = featureManager;
    }

    public async Task<IActionResult> OnPostAsync()
    {
        var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? "anonymous";
        
        if (await _featureManager.IsEnabledAsync(FeatureFlags.PaymentV2, new 
        {
            UserId = userId,
            Groups = User.FindAll(ClaimTypes.Role).Select(c => c.Value).ToList()
        }))
        {
            return await ProcessPaymentV2Async();
        }

        return await ProcessLegacyPaymentAsync();
    }
}
```

### Pattern 6: Time-Based Feature Flags

Enable features
Files: 1
Size: 15.0 KB
Complexity: 20/100
Category: General

Related in General