Skip to content

Repository files navigation

ManagedCode.FeatureChecker

CINuGet

ManagedCode.FeatureChecker is a provider-agnostic .NET feature flag SDK for feature management, feature toggles, targeting, segmentation, percentage rollouts, variants, remote configuration values, and deterministic local evaluation.

It is built for .NET developers who want a clean in-process API first: no dashboard dependency, no cloud SDK dependency in the core package, and no hidden runtime fallback path. Feature definitions can be built in code, bound from Microsoft.Extensions configuration, loaded from JSON snapshots, or supplied by your own storage/provider adapter.

Why FeatureChecker

  • .NET-native feature flag evaluator with explicit interfaces for application code.
  • Target features by user, tenant, session, role, plan, region, country, environment, app version, device, or any custom attribute.
  • Use individual targets, reusable segments, ordered targeting rules, dependencies, kill switches, and deterministic percentage rollouts.
  • Return evaluation reasons, matching rules, selected variants, variation indexes, and typed remote-config values.
  • Keep the core package storage-neutral through FeatureSnapshot and IFeatureDefinitionProvider.
  • Integrate directly with Microsoft.Extensions.DependencyInjection, Microsoft.Extensions.Configuration, and options binding.
  • Ship through CI with format, build, analyzer, test, coverage, pack, NuGet, and GitHub Release gates.

Keywords

feature flags, feature toggles, feature management, .NET SDK, C#, targeting, segmentation, percentage rollout, gradual rollout, variants, remote config, kill switch, A/B testing, experiments, permissions, migrations, JSON snapshots, Microsoft.Extensions, deterministic local evaluation.

Install

dotnet add package ManagedCode.FeatureChecker

Quick Start

usingManagedCode.FeatureChecker.Definitions;usingManagedCode.FeatureChecker.Evaluation;usingManagedCode.FeatureChecker.Targeting;varfeatures=newFeatureSetBuilder();features.Feature("checkout.new-flow").Disabled().WhenAll([FeatureCondition.Equals("plan","enterprise")],percentage:25);IFeatureEvaluatorchecker=features.ToChecker();varcontext=FeatureEvaluationContext.ForTargetingKey("user-123").With("plan","enterprise").With("region","eu");if(checker.IsEnabled("checkout.new-flow",context)){// Run the new checkout flow.}varevaluation=checker.Evaluate("checkout.new-flow",context);Console.WriteLine($"{evaluation.Status}{evaluation.ReasonKind}{evaluation.Reason}");

Percentage rollout is deterministic and sticky for the same feature key and targeting key. If no targeting key is supplied, percentage rules do not match.

Core Concepts

ConceptPurpose
FeatureDefinitionA feature flag, toggle, experiment, permission, operational switch, migration flag, or remote-config value.
FeatureEvaluationContextThe user, tenant, session, application, device, or custom context used for targeting.
FeatureConditionAttribute checks such as equals, not equals, contains, starts with, ends with, in, not in, exists, not exists, and minimum version.
FeatureTargetingRuleOrdered rule with conditions, include/exclude segments, percentage rollout, status, variant, and value.
FeatureSegmentReusable audience definition with included keys, excluded keys, and segment rules.
FeatureVariantWeighted variant with status and optional string value for remote configuration.
FeatureEvaluationDecision result with status, reason, selected rule, selected variant, variation index, value, and existence flag.
IFeatureEvaluatorMain application-facing evaluation contract.
IFeatureCheckerFactoryCreates fresh evaluators or scoped checkers from a definition provider.
IFeatureDefinitionProviderStorage boundary for JSON files, databases, object storage, cloud adapters, or custom providers.
IFeatureSnapshotSourceFull-snapshot backend boundary when features and segments are loaded together.

Feature Modes

Use modes to document intent and keep flags easier to operate:

  • Release - gradual feature delivery.
  • Experiment - A/B tests and variant selection.
  • Operational - kill switches and runtime controls.
  • Permission - plan, role, group, or tenant gates.
  • Migration - staged infrastructure or data migrations.
  • Maintenance - temporary maintenance controls.

Targeting

Rules are evaluated in order after dependencies and individual targets. The first matching rule wins.

usingManagedCode.FeatureChecker.Definitions;usingManagedCode.FeatureChecker.Evaluation;usingManagedCode.FeatureChecker.Storage;usingManagedCode.FeatureChecker.Targeting;varsnapshot=FeatureSnapshot.FromDefinitions([newFeatureDefinition{Key="reports.experimental",Status=FeatureStatus.Disabled,Rules=[newFeatureTargetingRule{Status=FeatureStatus.Enabled,Percentage=10,Conditions=[FeatureCondition.Equals("plan","enterprise"),FeatureCondition.In("region","eu","us"),FeatureCondition.VersionAtLeast("applicationVersion","2.3.0")]}]}]);varchecker=newFeatureChecker(snapshot);varcontext=FeatureEvaluationContext.ForTargetingKey("user-123").With("plan","enterprise").With("region","eu").With("applicationVersion","2.4.1");varenabled=checker.IsEnabled("reports.experimental",context);

Users, Tenants, Sessions, And Scoped Access

Use FeatureCheckerFactory when controllers, endpoints, background jobs, or services need a context-bound checker for a request.

usingManagedCode.FeatureChecker.Access;usingManagedCode.FeatureChecker.Storage;varfactory=newFeatureCheckerFactory(newFeatureFileProvider("features.json"));varuserFeatures=factory.ForUser(userId:"user-123",
context =>context.WithTenantId("tenant-456").WithSessionId("session-789").WithPlan("enterprise").WithRole("admin").WithRegion("eu"));if(userFeatures.IsEnabled("checkout.new-flow")){// Run the user-scoped feature.}vartemplate=userFeatures.GetStringValue("checkout.template","default");varmaxItems=userFeatures.GetInt32Value("checkout.max-items",10);

For application code, depend on interfaces:

usingManagedCode.FeatureChecker.Access;publicsealedclassCheckoutController(IFeatureCheckerFactoryfeatureCheckers){publicboolCanUseNewCheckout(stringuserId,stringtenantId){varfeatures=featureCheckers.ForUser(userId, context =>context.WithTenantId(tenantId));returnfeatures.IsEnabled("checkout.new-flow");}}

Segments

Segments define reusable audiences. They can include or exclude targeting keys directly and can also contain attribute rules.

usingManagedCode.FeatureChecker.Definitions;usingManagedCode.FeatureChecker.Targeting;varbuilder=newFeatureSetBuilder();builder.Segment("beta-enterprise-users").Include("user-123").WhenAll([FeatureCondition.Equals("plan","enterprise")]);builder.Feature("checkout.new-flow").Mode(FeatureMode.Release).Disabled().WhenSegment("beta-enterprise-users",variant:"treatment",featureValue:"new-checkout").FallthroughVariation("control","classic-checkout").Variant("control",50,FeatureStatus.Disabled,"classic-checkout").Variant("treatment",50,FeatureStatus.Enabled,"new-checkout");

Individual Targets And Dependencies

Individual targets are evaluated before general rules. Dependencies let one feature require another feature status and, optionally, a selected variant.

varbuilder=newFeatureSetBuilder();builder.Feature("billing.enabled").Enabled();builder.Feature("checkout.new-flow").Enabled().Target("blocked-user",FeatureStatus.Disabled,"control","classic-checkout").Require("billing.enabled");

If a dependency is missing, disabled, cyclic, or does not match the required variant, the dependent feature evaluates as disabled with reason Dependency or DependencyCycle.

Variants And Remote Config Values

Variants support deterministic weighted selection and can carry string values for remote configuration.

usingManagedCode.FeatureChecker.Definitions;usingManagedCode.FeatureChecker.Evaluation;usingManagedCode.FeatureChecker.Targeting;varbuilder=newFeatureSetBuilder();builder.Feature("pricing.card").Mode(FeatureMode.Experiment).Disabled("control-config").Variant("control",50,FeatureStatus.Disabled,"control-config").Variant("treatment",50,FeatureStatus.Enabled,"treatment-config");varchecker=builder.ToChecker();varcontext=FeatureEvaluationContext.ForTargetingKey("user-123");FeatureVariationDetail<string>detail=checker.StringVariationDetail("pricing.card","default-config",context);Console.WriteLine($"{detail.Value}{detail.ReasonKind}{detail.VariationIndex}");

Typed helpers are available for strings, booleans, integers, long integers, and doubles.

Microsoft.Extensions Integration

Register FeatureChecker in any Microsoft.Extensions host:

usingManagedCode.FeatureChecker.DependencyInjection;builder.Services.AddFeatureChecker(builder.Configuration.GetSection("FeatureChecker"));

Inject the contracts you need:

usingManagedCode.FeatureChecker.Access;usingManagedCode.FeatureChecker.Evaluation;usingManagedCode.FeatureChecker.Storage;publicsealedclassMyService(IFeatureEvaluatorevaluator,IFeatureCheckerFactoryfactory,IFeatureDefinitionProviderprovider){}

Configuration shape:

{
"FeatureChecker": {
"snapshot": {
"features": [
{
"key": "checkout.new-flow",
"status": "Disabled",
"rules": [
{
"status": "Enabled",
"percentage": 25,
"conditions": [
{ "attribute": "plan", "operator": "Equals", "values": [ "enterprise" ] }
]
}
]
}
],
"segments": []
}
}
}

You can also configure definitions in code:

builder.Services.AddFeatureChecker(options =>{varfeatures=newFeatureSetBuilder();features.Feature("ops.kill-switch").Enabled();options.Snapshot=features.Build();});

Register a backend snapshot source when feature configuration is assembled outside the application:

usingManagedCode.FeatureChecker.DependencyInjection;usingManagedCode.FeatureChecker.Storage;builder.Services.AddFeatureCheckerSnapshotSource<DatabaseFeatureSnapshotSource>();publicsealedclassDatabaseFeatureSnapshotSource:IFeatureSnapshotSource{publicFeatureSnapshotGetSnapshot(){// Load the latest snapshot from a table, API, object store, cache, or control-plane export.returnFeatureSnapshotSerializer.Load("features.json");}}

For runtime freshness, inject IFeatureCheckerFactory and create a new evaluator or scope after the source has refreshed its cache:

varscope=factory.ForUser(userId, context =>context.WithPlan(plan));if(scope.IsEnabled("checkout.new-flow")){// Run the enabled path.}

JSON Snapshots And Storage

The core package deliberately avoids cloud SDK dependencies. Storage integration lives behind FeatureSnapshot, FeatureSnapshotSerializer, IFeatureSnapshotSource, and IFeatureDefinitionProvider.

usingManagedCode.FeatureChecker.Definitions;usingManagedCode.FeatureChecker.Evaluation;usingManagedCode.FeatureChecker.Storage;varbuilder=newFeatureSetBuilder();builder.Feature("checkout.new-flow").Enabled();varsnapshot=builder.Build();FeatureSnapshotSerializer.Save("features.json",snapshot);varloaded=FeatureSnapshotSerializer.Load("features.json");varchecker=newFeatureChecker(loaded);

Reload from a JSON file:

varchecker=newFeatureChecker(newFeatureFileProvider("features.json"));

Implement your own provider for ManagedCode.Storage, object storage, databases, edge config, or other backends:

usingManagedCode.FeatureChecker.Definitions;usingManagedCode.FeatureChecker.Storage;publicsealedclassMyFeatureProvider:IFeatureDefinitionProvider{publicIReadOnlyCollection<FeatureDefinition>GetFeatureDefinitions(){returnFeatureSnapshotSerializer.Load("features.json").Features;}}

Prefer IFeatureSnapshotSource when your backend returns a complete snapshot including segments. Use IFeatureDefinitionProvider directly only when a feature-only provider is enough.

Evaluation Reasons

Every evaluation returns a reason so callers can inspect and log decisions:

  • Missing
  • Off
  • TargetMatch
  • RuleMatch
  • Fallthrough
  • Variant
  • Dependency
  • DependencyCycle
  • Default
  • Error

Use FeatureEvaluation.ReasonKind, Reason, Rule, Variant, VariationIndex, Value, and Exists for diagnostics, audit logging, and tests.

Project Structure

The repository uses vertical slices:

  • ManagedCode.FeatureChecker/Access
  • ManagedCode.FeatureChecker/Definitions
  • ManagedCode.FeatureChecker/DependencyInjection
  • ManagedCode.FeatureChecker/Evaluation
  • ManagedCode.FeatureChecker/Segments
  • ManagedCode.FeatureChecker/Storage
  • ManagedCode.FeatureChecker/Targeting
  • ManagedCode.FeatureChecker.Tests/*

The solution file is ManagedCode.FeatureChecker.slnx.

Development

dotnet restore ManagedCode.FeatureChecker.slnx
dotnet format ManagedCode.FeatureChecker.slnx --verify-no-changes
dotnet build ManagedCode.FeatureChecker.slnx --configuration Release --no-restore
dotnet build ManagedCode.FeatureChecker.slnx --configuration Release --no-restore -p:RunAnalyzers=true
dotnet test --solution ManagedCode.FeatureChecker.slnx --configuration Release --no-restore --verbosity normal
dotnet test --solution ManagedCode.FeatureChecker.slnx --configuration Release --no-build --verbosity normal -- --coverage --coverage-output-format cobertura
dotnet pack ManagedCode.FeatureChecker/ManagedCode.FeatureChecker.csproj --configuration Release --no-build --output ./artifacts

Release Flow

CI runs restore, format, build, analyzer, test, and coverage gates. The release workflow runs on v* tags or manual dispatch, validates the version from Directory.Build.props, packs the NuGet package, publishes to NuGet, uploads package artifacts, and creates a GitHub Release.

Package Metadata

Design Principles

  • Core evaluation is deterministic and side-effect-free.
  • Public APIs prefer explicit caller-provided defaults over hidden fallback behavior.
  • The core package stays provider-agnostic; storage and cloud integrations should sit behind provider interfaces.
  • Tests exercise caller-visible behavior through public contracts.

About

Provider-agnostic .NET feature flag SDK for targeting, segments, rollouts, variants, remote config, JSON snapshots, and Microsoft.Extensions.

Topics

Resources

Stars

8 stars

Watchers

1 watching

Forks

Releases

Used by

Contributors

Languages