-
Notifications
You must be signed in to change notification settings - Fork 0
Home
- Introduction
- What are Feature Toggles?
- Benefits of Feature Toggles
- Getting Started
- Core Concepts
- Architecture Overview
- Installation
- Basic Usage
- Dependency Injection Integration ⭐ NEW v5.1.0
- Storage Providers
- Condition Types
- Advanced Configuration
- Extending FeatureOne
- Best Practices
- Troubleshooting
- API Reference
FeatureOne is a powerful .NET library designed to implement feature toggles (also known as feature flags) in your applications. With FeatureOne, you can control the visibility and behavior of application features at runtime without deploying new code, enabling safer releases, gradual rollouts, and better control over feature exposure.
This library supports .NET Framework 4.6.2, .NET Standard 2.1, and .NET 9.0, making it compatible with a wide range of .NET applications.
A feature toggle (or feature flag) is a software engineering technique that allows you to turn application features "on" or "off" remotely without requiring a code deployment. This is achieved by wrapping new functionality in conditional statements that check the status of a toggle at runtime.
var featureName = "dashboard_widget";
if(Features.Current.IsEnabled(featureName))
{
// New feature code here
ShowDashboardWidget();
}
else
{
// Fallback or existing behavior
ShowDefaultDashboard();
}The toggle status is determined by:
- Storage Provider: Retrieves toggle configurations from your chosen storage medium
- Conditions: Evaluate criteria based on user claims, environment, time, or custom logic
- Operators: Combine multiple conditions using logical AND/OR operations
- Instant Rollback: Disable problematic features immediately without code deployment
- Gradual Rollout: Release features to small user groups first
- A/B Testing: Compare different implementations with real users
- Continuous Integration: Merge incomplete features safely using toggles
- Decoupled Deployments: Deploy code and activate features independently
- Environment-Specific Features: Show different features in different environments
- Faster Time-to-Market: Release features when business is ready, not just when code is ready
- User Segmentation: Target features to specific user groups
- Operational Control: Non-technical team members can control feature visibility
- Production Testing: Test features with real data and real users safely
- Canary Releases: Monitor feature performance with limited exposure
- Blue-Green Deployments: Switch between different feature sets instantly
- .NET Framework 4.6.2+ or .NET Core 2.1+ or .NET 5.0+
- Basic understanding of dependency injection (recommended)
// 1. Install the package
// Install-Package FeatureOne.File
// 2. Create a feature file (Features.json)
{
"new_dashboard": {
"toggle": {
"conditions": [{
"type": "simple",
"isEnabled": true
}]
}
}
}
// 3. Initialize FeatureOne (Traditional approach)
var configuration = new FileConfiguration
{
FilePath = @"C:\path\to\Features.json"
};
var storageProvider = new FileStorageProvider(configuration);
Features.Initialize(() => new Features(new FeatureStore(storageProvider)));
// 4. Use in your code
if (Features.Current.IsEnabled("new_dashboard"))
{
// Show new dashboard
}A Feature represents a piece of functionality that can be toggled on or off. Each feature has:
- Name: Unique identifier for the feature
- Toggle: Configuration that determines when the feature is enabled
A Toggle contains the logic for determining feature enablement:
- Operator: How to combine multiple conditions (AND/OR)
- Conditions: Individual rules that evaluate to true/false
Conditions are the building blocks of toggle logic:
- SimpleCondition: Basic on/off switch
- RegexCondition: Evaluates user claims against regular expressions
-
Custom Conditions: Implement
IConditionfor specific needs
Storage Providers retrieve feature configurations from various sources:
- FileStorageProvider: JSON files on disk
- SQLStorageProvider: SQL databases
-
Custom Providers: Implement
IStorageProviderfor any data source
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ Application │───▶│ Features │───▶│ FeatureStore │
│ │ │ (Entry Point) │ │ │
└─────────────────┘ └──────────────────┘ └─────────────────┘
│
▼
┌─────────────────┐
│ StorageProvider │
│ │
└─────────────────┘
│
┌──────────────────────┼──────────────────────┐
▼ ▼ ▼
┌──────────────┐ ┌──────────────────┐ ┌──────────────┐
│ FileProvider │ │ SQLProvider │ │CustomProvider│
└──────────────┘ └──────────────────┘ └──────────────┘
- Features: Main entry point for feature checking
- FeatureStore: Manages feature retrieval and caching
- StorageProvider: Abstracts data access
- Toggle: Contains evaluation logic
- Conditions: Individual evaluation rules
- Cache: Optional performance optimization
FeatureOne offers three NuGet packages based on your storage needs:
Install-Package FeatureOneUse when implementing custom storage providers.
Install-Package FeatureOne.SQLIncludes support for:
- Microsoft SQL Server
- SQLite
- MySQL
- PostgreSQL
- ODBC/OleDB sources
Install-Package FeatureOne.FileUses JSON files for feature storage.
// Configuration
{
"user_dashboard": {
"toggle": {
"conditions": [{
"type": "simple",
"isEnabled": true
}]
}
}
}
// Usage
if (Features.Current.IsEnabled("user_dashboard"))
{
return View("NewDashboard");
}
return View("OldDashboard");// Configuration
{
"admin_panel": {
"toggle": {
"conditions": [{
"type": "regex",
"claim": "role",
"expression": "^administrator$"
}]
}
}
}
// Usage
var claims = new Dictionary<string, string>
{
["role"] = user.Role,
["email"] = user.Email
};
if (Features.Current.IsEnabled("admin_panel", claims))
{
ShowAdminPanel();
}// Configuration - Feature enabled for admins OR beta users
{
"beta_feature": {
"toggle": {
"operator": "any",
"conditions": [
{
"type": "regex",
"claim": "role",
"expression": "^administrator$"
},
{
"type": "regex",
"claim": "group",
"expression": "^beta_users$"
}
]
}
}
}FeatureOne v5.1.0 introduces comprehensive dependency injection support for seamless integration with modern .NET applications.
// In Program.cs or Startup.cs
using Microsoft.Extensions.DependencyInjection;
using FeatureOne;
public void ConfigureServices(IServiceCollection services)
{
// Register FeatureOne with your storage provider using factory method
services.AddFeatureOne(serviceProvider =>
{
// Create your storage provider implementation here
var storageProvider = new YourStorageProviderImplementation();
return storageProvider;
});
}// Register File Storage Provider with configuration
services.AddFeatureOneWithFileStorage(new FileConfiguration
{
FilePath = "features.json",
CacheSettings = new CacheSettings
{
EnableCache = true,
Expiry = new ExpiryPolicy { AbsoluteExpiration = TimeSpan.FromMinutes(5) }
}
});// Register SQL Storage Provider with configuration
services.AddFeatureOneWithSQLStorage(new SQLConfiguration
{
ConnectionSettings = new ConnectionSettings
{
ConnectionString = "Server=localhost;Database=FeatureToggles;Trusted_Connection=true;",
ProviderName = "System.Data.SqlClient"
},
CacheSettings = new CacheSettings
{
EnableCache = true,
Expiry = new ExpiryPolicy { AbsoluteExpiration = TimeSpan.FromMinutes(5) }
}
});// Register File Storage with custom components
services.AddFeatureOneWithFileStorage(
configuration: new FileConfiguration { FilePath = "features.json" },
deserializer: new CustomToggleDeserializer(), // Optional: Pass null to use default
cache: new CustomCache() // Optional: Pass null to use default
);
// Register SQL Storage with custom components
services.AddFeatureOneWithSQLStorage(
configuration: new SQLConfiguration
{
ConnectionSettings = new ConnectionSettings
{
ConnectionString = "connection_string",
ProviderName = "System.Data.SqlClient"
}
},
deserializer: new CustomToggleDeserializer(), // Optional: Pass null to use default
cache: new CustomCache() // Optional: Pass null to use default
);// Inject Features service directly
public class HomeController : Controller
{
private readonly Features _features;
public HomeController(Features features)
{
_features = features;
}
public IActionResult Index()
{
if (_features.IsEnabled("new_dashboard"))
{
return View("NewDashboard");
}
return View("OldDashboard");
}
}
// Or inject in services
public class UserService
{
private readonly Features _features;
public UserService(Features features)
{
_features = features;
}
public bool CanAccessPremiumFeatures(string userEmail)
{
var claims = new Dictionary<string, string>
{
["email"] = userEmail
};
return _features.IsEnabled("premium_features", claims);
}
}All FeatureOne services are registered with Singleton lifetime to ensure:
- Consistent behavior across the application
- Optimal performance with caching
- Proper resource management
Here's a complete example showing how to integrate FeatureOne v5.1.0 with ASP.NET Core using dependency injection:
// Program.cs (ASP.NET Core 6+)
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using FeatureOne;
using FeatureOne.File.StorageProvider;
var builder = WebApplication.CreateBuilder(args);
// Add FeatureOne with File Storage Provider
builder.Services.AddFeatureOneWithFileStorage(new FileConfiguration
{
FilePath = "appsettings/features.json",
CacheSettings = new CacheSettings
{
EnableCache = true,
Expiry = new ExpiryPolicy
{
AbsoluteExpiration = TimeSpan.FromMinutes(10)
}
}
});
// Add other services
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
// Configure pipeline
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();// appsettings/features.json
{
"premium_features": {
"toggle": {
"operator": "any",
"conditions": [
{
"type": "Regex",
"claim": "subscription",
"expression": "^(premium|enterprise)$"
},
{
"type": "DateRange",
"startDate": "2025-10-01",
"endDate": "2025-12-31"
}
]
}
},
"admin_dashboard": {
"toggle": {
"conditions": [
{
"type": "Regex",
"claim": "role",
"expression": "^administrator$"
}
]
}
},
"beta_feature": {
"toggle": {
"conditions": [
{
"type": "Simple",
"isEnabled": true
}
]
}
}
}// Controllers/DashboardController.cs
using Microsoft.AspNetCore.Mvc;
using FeatureOne;
[ApiController]
[Route("api/[controller]")]
public class DashboardController : ControllerBase
{
private readonly Features _features;
public DashboardController(Features features)
{
_features = features;
}
[HttpGet("overview")]
public IActionResult GetOverview()
{
var userClaims = GetUserClaims();
var viewModel = new DashboardViewModel
{
ShowPremiumWidgets = _features.IsEnabled("premium_features", userClaims),
ShowAdminPanel = _features.IsEnabled("admin_dashboard", userClaims),
ShowBetaFeatures = _features.IsEnabled("beta_feature", userClaims)
};
return Ok(viewModel);
}
private Dictionary<string, string> GetUserClaims()
{
return new Dictionary<string, string>
{
["role"] = User?.Identity?.Name ?? "guest",
["subscription"] = GetUserSubscription(),
["userId"] = User?.Identity?.Name?.GetHashCode().ToString() ?? "anonymous"
};
}
private string GetUserSubscription()
{
// In a real application, this would come from your auth system
return "premium"; // Example value
}
}
public class DashboardViewModel
{
public bool ShowPremiumWidgets { get; set; }
public bool ShowAdminPanel { get; set; }
public bool ShowBetaFeatures { get; set; }
}This example demonstrates:
- ✅ Modern ASP.NET Core integration with top-level statements
- ✅ File-based feature storage with caching configuration
- ✅ Multiple condition types including the new DateRangeCondition
- ✅ Dependency injection of Features service into controllers
- ✅ Real-world feature configuration with complex conditions
- ✅ Proper service lifetime (Singleton) for optimal performance
Perfect for smaller applications or when feature configurations change infrequently.
-
Create Feature File (
Features.json):
{
"feature_name": {
"toggle": {
"operator": "any",
"conditions": [
{
"type": "simple",
"isEnabled": true
}
]
}
}
}- Configure Provider:
var configuration = new FileConfiguration
{
FilePath = @"C:\path\to\Features.json",
CacheSettings = new CacheSettings
{
EnableCache = true,
Expiry = new CacheExpiry
{
InMinutes = 60,
Type = CacheExpiryType.Absolute
}
}
};
var storageProvider = new FileStorageProvider(configuration);
Features.Initialize(() => new Features(new FeatureStore(storageProvider)));Ideal for enterprise applications requiring centralized feature management.
- Create Feature Table:
CREATE TABLE TFeatures (
Id INT NOT NULL IDENTITY PRIMARY KEY,
Name VARCHAR(255) NOT NULL,
Toggle NVARCHAR(4000) NOT NULL,
Archived BIT DEFAULT (0)
);- Insert Feature Data:
INSERT INTO TFeatures (Name, Toggle, Archived) VALUES
(
'dashboard_widget',
'{ "conditions":[{ "type":"Simple", "isEnabled": true }] }',
0
);- Configure Provider:
// Register database provider
DbProviderFactories.RegisterFactory("System.Data.SqlClient", SqlClientFactory.Instance);
var sqlConfiguration = new SQLConfiguration
{
ConnectionSettings = new ConnectionSettings
{
ProviderName = "System.Data.SqlClient",
ConnectionString = "Data Source=server;Initial Catalog=Features;Integrated Security=SSPI;"
},
FeatureTable = new FeatureTable
{
TableName = "TFeatures",
NameColumn = "Name",
ToggleColumn = "Toggle",
ArchivedColumn = "Archived"
},
CacheSettings = new CacheSettings
{
EnableCache = true,
Expiry = new CacheExpiry { InMinutes = 60 }
}
};
var storageProvider = new SQLStorageProvider(sqlConfiguration);
Features.Initialize(() => new Features(new FeatureStore(storageProvider)));Implement IStorageProvider for integration with APIs, NoSQL databases, or other data sources:
public class ApiStorageProvider : IStorageProvider
{
private readonly HttpClient httpClient;
public ApiStorageProvider(HttpClient httpClient)
{
this.httpClient = httpClient;
}
public IFeature[] GetByName(string name)
{
// Fetch from REST API
var response = httpClient.GetAsync($"api/features/{name}").Result;
var json = response.Content.ReadAsStringAsync().Result;
// Deserialize and return features
// Implementation depends on your API structure
}
}Basic on/off switch, independent of user context.
{
"type": "simple",
"isEnabled": true
}new SimpleCondition { IsEnabled = true }Evaluates user claims against regular expressions.
{
"type": "regex",
"claim": "email",
"expression": "^[a-zA-Z0-9_.+-]+@company\\.com$"
}new RegexCondition
{
Claim = "email",
Expression = @"^[a-zA-Z0-9_.+-]+@company\.com$"
}Email Domain Matching:
{
"claim": "email",
"expression": "@(company|partner)\\.com$"
}Role-Based Access:
{
"claim": "role",
"expression": "^(admin|moderator)$"
}User ID Ranges:
{
"claim": "userId",
"expression": "^[1-9][0-9]{3}$"
}Create custom conditions by implementing ICondition. FeatureOne v5.1.0 also includes a built-in DateRangeCondition for time-based feature toggles.
public class TimeBasedCondition : ICondition
{
public int StartHour { get; set; } = 9;
public int EndHour { get; set; } = 17;
public bool Evaluate(IDictionary<string, string> claims)
{
var currentHour = DateTime.Now.Hour;
return currentHour >= StartHour && currentHour <= EndHour;
}
}JSON Configuration:
{
"type": "TimeBased",
"startHour": 9,
"endHour": 17
}Usage:
// Business hours feature
if (Features.Current.IsEnabled("business_hours_feature"))
{
// Only available during business hours
}Note: FeatureOne v5.1.0 includes a built-in
DateRangeConditionfor date-based feature toggles. See DateRangeCondition for more details.
Enable features based on date ranges with the new DateRangeCondition. Perfect for scheduled feature releases, temporary promotions, or trial periods.
{
"type": "DateRange",
"startDate": "2025-01-01",
"endDate": "2025-12-31"
}new DateRangeCondition
{
StartDate = new DateTime(2025, 1, 1),
EndDate = new DateTime(2025, 12, 31)
}Start Date Only (No end limit):
{
"type": "DateRange",
"startDate": "2025-06-01",
"endDate": null
}End Date Only (No start limit):
{
"type": "DateRange",
"startDate": null,
"endDate": "2025-08-31"
}Both Dates Null (Always enabled):
{
"type": "DateRange",
"startDate": null,
"endDate": null
}Seasonal Feature:
{
"holiday_promotion": {
"toggle": {
"conditions": [{
"type": "DateRange",
"startDate": "2025-11-01",
"endDate": "2025-12-31"
}]
}
}
}Beta Testing Period:
{
"beta_feature": {
"toggle": {
"conditions": [{
"type": "DateRange",
"startDate": "2025-10-01",
"endDate": "2025-12-31"
}]
}
}
}Usage in Code:
// Feature enabled during holiday season
if (Features.Current.IsEnabled("holiday_promotion"))
{
// Show special holiday offers
ShowHolidayPromotions();
}
// Feature enabled during beta testing
if (Features.Current.IsEnabled("beta_feature"))
{
// Show beta features to testers
ShowBetaFeatures();
}Optimize performance with intelligent caching:
var cacheSettings = new CacheSettings
{
EnableCache = true,
Expiry = new CacheExpiry
{
InMinutes = 30,
Type = CacheExpiryType.Sliding // Reset timer on access
}
};Cache Types:
- Absolute: Cache expires after fixed time
- Sliding: Cache expires after inactivity period
Monitor feature toggle behavior:
public class CustomLogger : IFeatureLogger
{
private readonly ILogger<CustomLogger> logger;
public CustomLogger(ILogger<CustomLogger> logger)
{
this.logger = logger;
}
public void Info(string message) => logger.LogInformation(message);
public void Debug(string message) => logger.LogDebug(message);
public void Warn(string message) => logger.LogWarning(message);
public void Error(string message, Exception ex) => logger.LogError(ex, message);
}
// Register with FeatureOne
var customLogger = new CustomLogger(serviceProvider.GetService<ILogger<CustomLogger>>());
Features.Initialize(() => new Features(new FeatureStore(storageProvider, customLogger), customLogger));FeatureOne supports various database providers:
// SQL Server
DbProviderFactories.RegisterFactory("System.Data.SqlClient", SqlClientFactory.Instance);
// MySQL
DbProviderFactories.RegisterFactory("MySql.Data.MySqlClient", MySqlClientFactory.Instance);
// PostgreSQL
DbProviderFactories.RegisterFactory("Npgsql", NpgsqlFactory.Instance);
// SQLite
DbProviderFactories.RegisterFactory("System.Data.SQLite", SQLiteFactory.Instance);public class PercentageRolloutCondition : ICondition
{
public int Percentage { get; set; }
public bool Evaluate(IDictionary<string, string> claims)
{
if (!claims.TryGetValue("userId", out string userIdStr))
return false;
if (!int.TryParse(userIdStr, out int userId))
return false;
// Use user ID to determine consistent rollout
var hash = userId.GetHashCode();
var bucket = Math.Abs(hash % 100);
return bucket < Percentage;
}
}Usage Example:
{
"new_checkout": {
"toggle": {
"conditions": [{
"type": "PercentageRollout",
"percentage": 25
}]
}
}
}public class RedisCacheProvider : ICache
{
private readonly IConnectionMultiplexer redis;
private readonly IDatabase database;
public RedisCacheProvider(IConnectionMultiplexer redis)
{
this.redis = redis;
this.database = redis.GetDatabase();
}
public void Add(string key, object value, CacheItemPolicy policy)
{
var json = JsonSerializer.Serialize(value);
var expiry = policy.AbsoluteExpiration.HasValue
? policy.AbsoluteExpiration.Value.TimeOfDay
: policy.SlidingExpiration;
database.StringSet(key, json, expiry);
}
public object Get(string key)
{
var json = database.StringGet(key);
return json.HasValue ? JsonSerializer.Deserialize<object>(json) : null;
}
}For complex toggle requirements:
public class CustomToggleDeserializer : IToggleDeserializer
{
public IToggle Deserialize(string toggle)
{
// Custom deserialization logic
// Handle special toggle formats
// Support additional operators
}
}Good Names:
user_dashboard_v2
checkout_flow_redesign
mobile_payment_integration
Avoid:
feature1
test_toggle
temp_fix
public class FeatureToggleAudit
{
// Track toggle creation date
public DateTime CreatedDate { get; set; }
// Review toggles older than 6 months
public bool RequiresReview =>
DateTime.Now.Subtract(CreatedDate).Days > 180;
}Unit Testing:
[Test]
public void Should_Enable_Feature_For_Admin_Users()
{
// Arrange
var claims = new Dictionary<string, string> { ["role"] = "administrator" };
// Act
var isEnabled = Features.Current.IsEnabled("admin_feature", claims);
// Assert
Assert.IsTrue(isEnabled);
}Integration Testing:
[Test]
public void Should_Load_Features_From_Database()
{
// Test storage provider integration
// Verify cache behavior
// Test error handling
}- Enable Caching: Always use caching for production
- Monitor Cache Hit Rates: Track cache effectiveness
- Optimize Database Queries: Index feature name columns
- Batch Feature Checks: Check multiple features in one call when possible
// Sanitize user inputs
public bool IsFeatureEnabledForUser(string featureName, ClaimsPrincipal user)
{
// Validate feature name
if (string.IsNullOrWhiteSpace(featureName))
return false;
// Extract only necessary claims
var safeClaims = ExtractSafeClaims(user);
return Features.Current.IsEnabled(featureName, safeClaims);
}public class FeatureToggleMetrics
{
public void RecordFeatureCheck(string featureName, bool isEnabled, TimeSpan duration)
{
// Log to metrics system
// Alert on performance issues
// Track feature usage
}
}Problem: Features.Current.IsEnabled() always returns false.
Solutions:
- Verify
Features.Initialize()was called - Check storage provider configuration
- Verify feature exists in storage
- Check condition syntax
Problem: SQL storage provider throws connection errors.
Solutions:
try
{
var features = storageProvider.GetByName("test_feature");
}
catch (Exception ex)
{
logger.Error("Storage provider error", ex);
// Handle gracefully - return default behavior
}Problem: Changes to feature configurations not reflected immediately.
Solutions:
- Verify cache settings
- Check file change monitoring (for file provider)
- Consider cache invalidation strategy
Problem: Regex conditions not evaluating correctly.
Solutions:
- Test regex patterns separately
- Use online regex testing tools
- Check claim values are correct
- Verify case sensitivity
// Enable detailed logging
var logger = new CustomLogger();
Features.Initialize(() => new Features(new FeatureStore(storageProvider, logger), logger));
// Test feature evaluation
var testClaims = new Dictionary<string, string>
{
["email"] = "test@company.com",
["role"] = "user"
};
var result = Features.Current.IsEnabled("test_feature", testClaims);
logger.Info($"Feature 'test_feature' evaluated to: {result}");public class PerformanceAwareFeatures
{
private readonly Stopwatch stopwatch = new Stopwatch();
public bool IsEnabled(string featureName, IDictionary<string, string> claims)
{
stopwatch.Restart();
var result = Features.Current.IsEnabled(featureName, claims);
stopwatch.Stop();
if (stopwatch.ElapsedMilliseconds > 100)
{
logger.Warn($"Slow feature check: {featureName} took {stopwatch.ElapsedMilliseconds}ms");
}
return result;
}
}public class Features
{
public static Features Current { get; private set; }
// Initialize FeatureOne
public static void Initialize(Func<Features> factory);
// Check if feature is enabled
public bool IsEnabled(string name);
public bool IsEnabled(string name, ClaimsPrincipal principal);
public bool IsEnabled(string name, IEnumerable<Claim> claims);
public bool IsEnabled(string name, IDictionary<string, string> claims);
}public interface IStorageProvider
{
IFeature[] GetByName(string name);
}public interface ICondition
{
bool Evaluate(IDictionary<string, string> claims);
}// File Storage
public class FileConfiguration
{
public string FilePath { get; set; }
public CacheSettings CacheSettings { get; set; }
}
// SQL Storage
public class SQLConfiguration
{
public ConnectionSettings ConnectionSettings { get; set; }
public FeatureTable FeatureTable { get; set; }
public CacheSettings CacheSettings { get; set; }
}
// Cache Settings
public class CacheSettings
{
public bool EnableCache { get; set; }
public CacheExpiry Expiry { get; set; }
}
// DateRangeCondition ⭐ NEW v5.1.0
public class DateRangeCondition : ICondition
{
public DateTime? StartDate { get; set; }
public DateTime? EndDate { get; set; }
public bool Evaluate(IDictionary<string, string> claims)
{
var now = DateTime.Now.Date;
if (StartDate.HasValue && now < StartDate.Value.Date)
return false;
if (EndDate.HasValue && now > EndDate.Value.Date)
return false;
return true;
}
}- ICondition: Custom toggle conditions (includes built-in DateRangeCondition ⭐ NEW v5.1.0)
- IStorageProvider: Custom storage backends
- IFeatureLogger: Custom logging implementations
- ICache: Custom caching providers
- IConditionDeserializer: Custom condition deserialization
- IToggleDeserializer: Custom toggle deserialization
public static class FeatureOneServiceCollectionExtensions
{
public static IServiceCollection AddFeatureOne(this IServiceCollection services, Func<IServiceProvider, IStorageProvider> storageProviderFactory);
}public static class FeatureOneFileExtensions
{
public static IServiceCollection AddFeatureOneWithFileStorage(this IServiceCollection services,
FileConfiguration configuration, IToggleDeserializer deserializer = null, ICache cache = null);
}public static class FeatureOneSQLExtensions
{
public static IServiceCollection AddFeatureOneWithSQLStorage(this IServiceCollection services,
SQLConfiguration configuration, IToggleDeserializer deserializer = null, ICache cache = null);
}- RegexCondition ReDoS Protection: Added timeout validation to prevent Regular Expression Denial of Service attacks
- Secure Dynamic Type Loading: Replaced assembly scanning with explicit safe type registry to prevent unsafe type loading
- Actual Prefix Matching: Fixed FindStartsWith implementation to properly support prefix matching instead of exact matching
- Dependency Injection Patterns: Implemented proper dependency injection patterns with constructors that accept dependencies explicitly
- DateRangeCondition: Added new condition type for time-based feature toggles
- Configuration Validation: Added configuration validation system for feature names and condition parameters
- Core Service Registration: Added AddFeatureOne extension method for registering FeatureOne services with Microsoft.Extensions.DependencyInjection using factory pattern
- File Storage Provider Registration: Added AddFeatureOneWithFileStorage extension method for easy FileStorageProvider registration
- SQL Storage Provider Registration: Added AddFeatureOneWithSQLStorage extension method for easy SQLStorageProvider registration
- Comprehensive Test Coverage: Achieved 90%+ code coverage across all critical components
- Integration Testing: Complete end-to-end validation of all components
- External Condition Types: External condition types from other assemblies may no longer be loadable (security enhancement)
- Configuration Validation: May detect previously undetected configuration errors
Here's a comprehensive example showcasing all the new features in FeatureOne v5.1.0:
// Program.cs - Modern ASP.NET Core integration with DI
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using FeatureOne;
using FeatureOne.File.StorageProvider;
var builder = WebApplication.CreateBuilder(args);
// Register FeatureOne with comprehensive configuration
builder.Services.AddFeatureOneWithFileStorage(new FileConfiguration
{
FilePath = "app_data/features.json",
CacheSettings = new CacheSettings
{
EnableCache = true,
Expiry = new ExpiryPolicy
{
AbsoluteExpiration = TimeSpan.FromMinutes(15),
SlidingExpiration = TimeSpan.FromMinutes(5)
}
}
});
var app = builder.Build();
// API endpoint demonstrating all new v5.1.0 features
app.MapGet("/api/features", (Features features, HttpContext context) =>
{
var userClaims = new Dictionary<string, string>
{
["role"] = context.User?.FindFirst("role")?.Value ?? "guest",
["email"] = context.User?.FindFirst("email")?.Value ?? "",
["subscription"] = GetUserSubscription(context), // premium, basic, trial, etc.
["userId"] = context.User?.FindFirst("sub")?.Value ?? ""
};
// Demonstrate all new v5.1.0 features
var featureStatus = new
{
// New DateRangeCondition
seasonalPromotion = features.IsEnabled("seasonal_promotion", userClaims),
// Security-enhanced RegexCondition with ReDoS protection
enterpriseEmail = features.IsEnabled("enterprise_email_access", userClaims),
// Traditional simple toggle
newDashboard = features.IsEnabled("new_dashboard", userClaims),
// Complex toggle with multiple conditions
premiumFeatures = features.IsEnabled("premium_features", userClaims)
};
return Results.Ok(featureStatus);
});
app.Run();
string GetUserSubscription(HttpContext context)
{
// Implementation would check user's subscription status
return "premium"; // Example
}// app_data/features.json - Feature configuration showcasing v5.1.0 features
{
"seasonal_promotion": {
"description": "Holiday promotion active Dec 1-31",
"toggle": {
"conditions": [{
"type": "DateRange",
"startDate": "2025-12-01",
"endDate": "2025-12-31"
}]
}
},
"enterprise_email_access": {
"description": "Enterprise email domain access with ReDoS protection",
"toggle": {
"conditions": [{
"type": "Regex",
"claim": "email",
"expression": "^[a-zA-Z0-9._%+-]+@(enterprise|company)\\.com$"
}]
}
},
"premium_features": {
"description": "Premium subscription features with beta access",
"toggle": {
"operator": "any",
"conditions": [
{
"type": "Regex",
"claim": "subscription",
"expression": "^(premium|enterprise)$"
},
{
"type": "DateRange",
"startDate": "2025-11-01",
"endDate": "2025-12-31"
}
]
}
},
"new_dashboard": {
"description": "New dashboard UI",
"toggle": {
"conditions": [{
"type": "Simple",
"isEnabled": true
}]
}
}
}This showcase demonstrates:
- ✅ DateRangeCondition: Time-based feature toggles for seasonal promotions
- ✅ Enhanced Security: ReDoS protection in RegexCondition
- ✅ Modern DI Integration: Seamless ASP.NET Core integration
- ✅ Comprehensive Caching: Advanced cache configuration
- ✅ Real-world Scenarios: Practical feature toggle implementations
- ✅ Backward Compatibility: Works with existing simple toggles
FeatureOne provides a robust, flexible foundation for implementing feature toggles in .NET applications. Whether you're building a simple web application or a complex enterprise system, FeatureOne's extensible architecture adapts to your needs.
Key Takeaways:
- Start simple with basic toggles, then add complexity as needed
- Choose the right storage provider for your infrastructure
- Implement proper monitoring and logging
- Plan for toggle lifecycle management
- Test your feature toggle logic thoroughly
For additional help and community support, visit the GitHub repository or review the unit tests for comprehensive usage examples.
Happy feature toggling! 🚀
MIT License - Copyright (c) 2024 Ninja Sha!4h