Skip to content

Features

SaaS feature management with three value types — toggle (boolean), numeric (bounded), and selection (enum). Features cascade through a Tenant → Plan → Default chain and support gating via attributes, middleware, and imperative checks.

  • DirectoryGranit.Features/ SaaS feature management (Toggle/Numeric/Selection)
    • Granit.Features.EntityFrameworkCore Isolated FeaturesDbContext
PackageRoleDepends on
Granit.FeaturesIFeatureChecker, IFeatureLimitGuard, plan-based cascadeGranit.Caching, Granit.Localization
Granit.Features.EntityFrameworkCoreFeaturesDbContext (isolated)Granit.Features, Granit.Persistence
graph TD
    F[Granit.Features] --> CA[Granit.Caching]
    F --> L[Granit.Localization]
    FEF[Granit.Features.EntityFrameworkCore] --> F
    FEF --> P[Granit.Persistence]
[DependsOn(typeof(GranitFeaturesModule))]
public class AppModule : GranitModule { }

Uses InMemoryFeatureStore by default (no persistence).

Implement IFeatureDefinitionProvider to declare features. Features are grouped for admin UI organization.

public sealed class AcmeFeatureDefinitionProvider : FeatureDefinitionProvider
{
public override void Define(IFeatureDefinitionContext context)
{
FeatureGroupDefinition group = context.AddGroup("Acme", "Acme Application");
group.AddToggle("Acme.VideoConsultation",
defaultValue: "false",
displayName: "Video Consultation");
group.AddNumeric("Acme.MaxUsersCount",
defaultValue: "50",
displayName: "Maximum Users",
numericConstraint: new NumericConstraint(Min: 1, Max: 10_000));
group.AddSelection("Acme.StorageTier",
defaultValue: "standard",
displayName: "Storage Tier",
selectionValues: new SelectionValues("standard", "premium", "enterprise"));
}
}
PropertyDescription
NameUnique feature name (convention: Module.FeatureName)
DefaultValueFallback string value
ValueTypeToggle, Numeric, or Selection
NumericConstraintMin/Max bounds for Numeric features
SelectionValuesAllowed values for Selection features

Features are resolved through a Tenant → Plan → Default cascade:

Tenant override → Plan value → Default (code)

The application must implement IPlanIdProvider and IPlanFeatureStore to activate plan-level resolution. Without these, features fall back to their default values.

The main abstraction for querying feature state at runtime:

public class ConsultationService(IFeatureChecker features)
{
public async Task StartAsync(CancellationToken ct)
{
// Gate: throws FeatureNotEnabledException (HTTP 403) if disabled
await features
.RequireEnabledAsync("Acme.VideoConsultation", ct)
.ConfigureAwait(false);
// Conditional logic
bool isEnabled = await features
.IsEnabledAsync("Acme.VideoConsultation", ct)
.ConfigureAwait(false);
// Numeric value
long maxUsers = await features
.GetNumericAsync("Acme.MaxUsersCount", ct)
.ConfigureAwait(false);
}
}

Enforces numeric feature limits before mutating operations. Throws FeatureLimitExceededException (HTTP 403) when the limit is reached.

public sealed class CreatePatientHandler(
IFeatureLimitGuard limitGuard,
IPatientRepository patients)
{
public async Task HandleAsync(CreatePatientCommand cmd, CancellationToken ct)
{
long current = await patients.CountAsync(ct).ConfigureAwait(false);
await limitGuard
.CheckAsync("Acme.MaxUsersCount", current, ct)
.ConfigureAwait(false);
// ... proceed with creation
}
}
app.MapPost("/consultations", handler)
.RequiresFeature("Acme.VideoConsultation");

When the feature is disabled, FeatureNotEnabledException is thrown and mapped to HTTP 403 with errorCode: "Features:NotEnabled".

public interface IFeatureStoreReader
{
Task<string?> GetOrNullAsync(
string featureName, string? tenantId,
CancellationToken cancellationToken = default);
}
public interface IFeatureStoreWriter
{
Task SetAsync(
string featureName, string? tenantId, string value,
CancellationToken cancellationToken = default);
Task DeleteAsync(
string featureName, string? tenantId,
CancellationToken cancellationToken = default);
}
CategoryKey typesPackage
ModulesGranitFeaturesModule, GranitFeaturesEntityFrameworkCoreModule
AbstractionsIFeatureChecker, IFeatureLimitGuard, IFeatureStoreReader, IFeatureStoreWriterGranit.Features
DefinitionsFeatureDefinition, FeatureDefinitionProvider, FeatureValueType, NumericConstraint, SelectionValuesGranit.Features
Gating[RequiresFeature], .RequiresFeature(), RequiresFeatureMiddlewareGranit.Features
  • Settings — cascading key-value settings
  • Multi-tenancy — tenant context for feature resolution
  • Caching — used for feature value caching