Kanject.Core.Features

View .md

One source tree, several product artifacts — Free, Pro, Enterprise, a customer build, an offline variant. Declare the lock once, on the capability, and Features wires the edition constants, the service registrations, the runtime gates, and the publish checks. Edition locks decide what ships. Runtime flags decide what runs. The runtime and generator are free under the Community licence.

Install

bash
dotnet add package Kanject.Core.Featuresdotnet add package Kanject.Core.Features.Annotations
xml
<ItemGroup>  <PackageReference Include="Kanject.Core.Features" />  <PackageReference Include="Kanject.Core.Features.Annotations" PrivateAssets="all" /></ItemGroup>

Define the ladder, lock a service

Mark one enum as the edition ladder, then put [LockedBehind] on anything that should only exist from a given edition upward:

csharp
using Kanject.Core.Features.Annotations.Attributes;​// Declaration order is unlock order: Pro includes everything in Free.[EditionLadder]public enum Edition{    Free,    Pro,    Enterprise,}​// A whole capability that only ships from Pro upward.[LockedBehind(Edition.Pro)]public sealed class AdvancedExportEngine : IExportEngine{    public ExportResult Export(Project project) => /* premium implementation */;}
csharp
// Generated. Registers the services this edition unlocks; the rest are omitted.services.AddLockedFeatures();

In a Pro build, AdvancedExportEngine is registered as itself and as IExportEngine. In a Free build those registration lines are not emitted at all.

Publish an edition

bash
dotnet publish -p:KanjectEdition=Free -p:PublishTrimmed=true -o ./dist/freedotnet publish -p:KanjectEdition=Pro  -p:PublishTrimmed=true -o ./dist/pro

The edition is a build-time fact. When KanjectEdition is not set the lowest edition is selected, so the safe default never unlocks anything by accident. Branch on the generated constants for edition-aware code that is not a service:

csharp
// Generated beside the enum. A false branch is dead code the trimmer removes.if (Editions.HasPro){    menu.Add(new AdvancedExportCommand());}

Gate at runtime

For code that ships in every artifact but should not always run — a beta, a gradual rollout, a kill switch — lock a partial method behind a string flag. The generated guard lives in the method body, so every route into the method passes through it:

csharp
using Kanject.Core.Features.Annotations.Attributes;using Kanject.Core.Features.Interfaces;​public sealed partial class Dashboard{    private readonly IFeatureGate _gate;​    public Dashboard(IFeatureGate gate) => _gate = gate;​    // A string means "decide at runtime". The real logic lives in {Method}Core.    [LockedBehind("beta.export", Fallback = nameof(Locked))]    public partial string Render();​    private string RenderCore() => "high-res export";    private string Locked() => "Upgrade to export.";}

Then register a gate. Each helper registers a singleton IFeatureGate, and the first registration wins:

csharp
using Kanject.Core.Features.Extensions;​services.AddInMemoryFeatureGate("beta.export");// or: services.AddFeatureGate(key => myRemoteFlags.IsOn(key.Name));// or: services.AddFeatureGate<MyEntitlementGate>();

With the feature off and no Fallback declared, the guard throws FeatureLockedException.

Which lock to reach for

  • A whole capability with its own service — [LockedBehind(Edition.Pro)] on the class, then AddLockedFeatures()
  • A small premium contribution inside shared code — an edition-locked partial void method the compiler elides in lower editions
  • The strongest boundary — move the code to its own project and include it with an edition-conditional <LockedReference>
  • Shipped code that should not always run — [LockedBehind("flag")] on a partial method, backed by IFeatureGate
  • Present in every edition but gated — [LockedBehind(Edition.Pro, Binding = FeatureBinding.Runtime)], for an in-app upsell
  • Proof that code is absent — [PrunedBelow(…)] with <VerifyEditionPruning> checking the published artifact

What ships with it

  • [EditionLadder] and the KanjectEdition build property — one ladder, one artifact per edition
  • Generated Editions.Has… compile-time constants and AddLockedFeatures() service registration
  • [LockedBehind] on classes (edition) and partial methods (edition or runtime flag), with an optional Fallback
  • IFeatureGate with in-memory and delegate gates — AddInMemoryFeatureGate, AddFeatureGate
  • <LockedReference> for edition-conditional project and package references
  • [PrunedBelow] and post-publish pruning verification — absence as a CI-checkable property
  • Compile-time KANFL diagnostics, including a check for lower-edition code that injects a higher-edition service
Was this page helpful?