Skip to content
Najaf Shaikh edited this page Aug 11, 2025 · 8 revisions

FeatureOne - Complete Guide

Table of Contents

  1. Introduction
  2. What are Feature Toggles?
  3. Benefits of Feature Toggles
  4. Getting Started
  5. Core Concepts
  6. Architecture Overview
  7. Installation
  8. Basic Usage
  9. Storage Providers
  10. Condition Types
  11. Advanced Configuration
  12. Extending FeatureOne
  13. Best Practices
  14. Troubleshooting
  15. API Reference

Introduction

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.

What are Feature Toggles?

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.

How Feature Toggles Work

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

Benefits of Feature Toggles

1. Risk Mitigation

  • 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

2. Development Flexibility

  • 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

3. Business Value

  • 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

4. Quality Assurance

  • 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

Getting Started

Prerequisites

  • .NET Framework 4.6.2+ or .NET Core 2.1+ or .NET 5.0+
  • Basic understanding of dependency injection (recommended)

Quick Start Example

// 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
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
}

Core Concepts

Features

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

Toggles

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

Conditions are the building blocks of toggle logic:

  • SimpleCondition: Basic on/off switch
  • RegexCondition: Evaluates user claims against regular expressions
  • Custom Conditions: Implement ICondition for specific needs

Storage Providers

Storage Providers retrieve feature configurations from various sources:

  • FileStorageProvider: JSON files on disk
  • SQLStorageProvider: SQL databases
  • Custom Providers: Implement IStorageProvider for any data source

Architecture Overview

┌─────────────────┐    ┌──────────────────┐    ┌─────────────────┐
│   Application   │───▶│   Features       │───▶│  FeatureStore   │
│                 │    │   (Entry Point)  │    │                 │
└─────────────────┘    └──────────────────┘    └─────────────────┘
                                                         │
                                                         ▼
                                                ┌─────────────────┐
                                                │ StorageProvider │
                                                │                 │
                                                └─────────────────┘
                                                         │
                                  ┌──────────────────────┼──────────────────────┐
                                  ▼                      ▼                      ▼
                          ┌──────────────┐    ┌──────────────────┐    ┌──────────────┐
                          │ FileProvider │    │   SQLProvider    │    │CustomProvider│
                          └──────────────┘    └──────────────────┘    └──────────────┘

Key Components

  1. Features: Main entry point for feature checking
  2. FeatureStore: Manages feature retrieval and caching
  3. StorageProvider: Abstracts data access
  4. Toggle: Contains evaluation logic
  5. Conditions: Individual evaluation rules
  6. Cache: Optional performance optimization

Installation

FeatureOne offers three NuGet packages based on your storage needs:

Core Package (Custom Storage)

Install-Package FeatureOne

Use when implementing custom storage providers.

SQL Storage Provider

Install-Package FeatureOne.SQL

Includes support for:

  • Microsoft SQL Server
  • SQLite
  • MySQL
  • PostgreSQL
  • ODBC/OleDB sources

File Storage Provider

Install-Package FeatureOne.File

Uses JSON files for feature storage.

Basic Usage

1. Simple Feature Toggle

// Configuration
{
    "user_dashboard": {
        "toggle": {
            "conditions": [{
                "type": "simple",
                "isEnabled": true
            }]
        }
    }
}

// Usage
if (Features.Current.IsEnabled("user_dashboard"))
{
    return View("NewDashboard");
}
return View("OldDashboard");

2. User-Based Toggle

// 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();
}

3. Complex Toggle Logic

// 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$"
                }
            ]
        }
    }
}

Storage Providers

File Storage Provider

Perfect for smaller applications or when feature configurations change infrequently.

Setup

  1. Create Feature File (Features.json):
{
    "feature_name": {
        "toggle": {
            "operator": "any",
            "conditions": [
                {
                    "type": "simple",
                    "isEnabled": true
                }
            ]
        }
    }
}
  1. 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)));

SQL Storage Provider

Ideal for enterprise applications requiring centralized feature management.

Database Setup

  1. 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)
);
  1. Insert Feature Data:
INSERT INTO TFeatures (Name, Toggle, Archived) VALUES 
(
    'dashboard_widget',
    '{ "conditions":[{ "type":"Simple", "isEnabled": true }] }',
    0
);
  1. 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)));

Custom Storage Provider

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
    }
}

Condition Types

Simple Condition

Basic on/off switch, independent of user context.

{
    "type": "simple",
    "isEnabled": true
}
new SimpleCondition { IsEnabled = true }

Regex Condition

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$" 
}

Common Regex Patterns

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}$"
}

Custom Conditions

Create custom conditions by implementing ICondition:

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
}

Advanced Configuration

Caching Configuration

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

Logging Configuration

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));

Multiple Database Providers

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);

Extending FeatureOne

Custom Condition Implementation

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
            }]
        }
    }
}

Custom Cache Implementation

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;
    }
}

Custom Toggle Deserializer

For complex toggle requirements:

public class CustomToggleDeserializer : IToggleDeserializer
{
    public IToggle Deserialize(string toggle)
    {
        // Custom deserialization logic
        // Handle special toggle formats
        // Support additional operators
    }
}

Best Practices

1. Feature Toggle Naming

Good Names:

user_dashboard_v2
checkout_flow_redesign
mobile_payment_integration

Avoid:

feature1
test_toggle
temp_fix

2. Toggle Lifecycle Management

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;
}

3. Testing Strategies

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
}

4. Performance Considerations

  • 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

5. Security Considerations

// 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);
}

6. Monitoring and Alerting

public class FeatureToggleMetrics
{
    public void RecordFeatureCheck(string featureName, bool isEnabled, TimeSpan duration)
    {
        // Log to metrics system
        // Alert on performance issues
        // Track feature usage
    }
}

Troubleshooting

Common Issues

1. Features Always Return False

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

2. Database Connection Issues

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
}

3. Cache Not Working

Problem: Changes to feature configurations not reflected immediately.

Solutions:

  • Verify cache settings
  • Check file change monitoring (for file provider)
  • Consider cache invalidation strategy

4. Regex Conditions Not Matching

Problem: Regex conditions not evaluating correctly.

Solutions:

  • Test regex patterns separately
  • Use online regex testing tools
  • Check claim values are correct
  • Verify case sensitivity

Debugging Tips

// 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}");

Performance Debugging

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;
    }
}

API Reference

Core Classes

Features Class

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);
}

IStorageProvider Interface

public interface IStorageProvider
{
    IFeature[] GetByName(string name);
}

ICondition Interface

public interface ICondition
{
    bool Evaluate(IDictionary<string, string> claims);
}

Configuration Classes

// 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; }
}

Extension Points

  • ICondition: Custom toggle conditions
  • IStorageProvider: Custom storage backends
  • IFeatureLogger: Custom logging implementations
  • ICache: Custom caching providers
  • IConditionDeserializer: Custom condition deserialization
  • IToggleDeserializer: Custom toggle deserialization

Conclusion

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! 🚀

Clone this wiki locally