Kanject.Core.Features.Annotations 3.13.0

Prefix Reserved
There is a newer version of this package available.
See the version list below for details.
dotnet add package Kanject.Core.Features.Annotations --version 3.13.0
                    
NuGet\Install-Package Kanject.Core.Features.Annotations -Version 3.13.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Kanject.Core.Features.Annotations" Version="3.13.0">
  <PrivateAssets>all</PrivateAssets>
  <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Kanject.Core.Features.Annotations" Version="3.13.0" />
                    
Directory.Packages.props
<PackageReference Include="Kanject.Core.Features.Annotations">
  <PrivateAssets>all</PrivateAssets>
  <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Kanject.Core.Features.Annotations --version 3.13.0
                    
#r "nuget: Kanject.Core.Features.Annotations, 3.13.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Kanject.Core.Features.Annotations@3.13.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Kanject.Core.Features.Annotations&version=3.13.0
                    
Install as a Cake Addin
#tool nuget:?package=Kanject.Core.Features.Annotations&version=3.13.0
                    
Install as a Cake Tool

Kanject.Core.Features.Annotations

Roslyn source generators, analyzers, and MSBuild targets for Kanject edition-locking. From an [EditionLadder] enum and the <KanjectEdition> build property they emit compile-time Editions.Has* constants, an AddLockedFeatures() DI extension that omits locked services, prune points that vanish from lower editions, runtime IFeatureGate guards, and a post-publish check that proves locked code is absent from a trimmed or Native AOT artifact.

Install it next to the runtime package Kanject.Core.Features; the concepts and end-to-end guide live in that package's README.

Installation

<ItemGroup>
  <PackageReference Include="Kanject.Core.Features" />                       
  <PackageReference Include="Kanject.Core.Features.Annotations" PrivateAssets="all" />
</ItemGroup>

or:

dotnet add package Kanject.Core.Features
dotnet add package Kanject.Core.Features.Annotations

dotnet add package writes an IncludeAssets line for this package that omits compile. Delete that line (keep PrivateAssets="all") — otherwise the attribute types from Kanject.Core.Features.Annotations.Attributes can be stripped from compilation.

The package targets netstandard2.0 because it runs inside the C# compiler, and is marked as a development dependency. Code it generates calls into Kanject.Core.Features (.NET 8, .NET 9 and .NET 10), which does not reference this package — reference both. The attribute types come from Kanject.Core.Features.Annotations.Attributes, a dependency of this package.

Reference it as a package. The package's build/ props make <KanjectEdition> and the other switches visible to the compiler, and its targets implement locked references, the publish guard, and pruning verification. Wired any other way (a ProjectReference or a bare <Analyzer> item), those files aren't imported: <KanjectEdition> is never seen and the generators silently build the lowest edition. If you must, import the .props and .targets yourself.

Quick start

using Kanject.Core.Features.Annotations.Attributes;

[EditionLadder]
public enum Edition { Free, Pro, Enterprise }

[LockedBehind(Edition.Pro)]
public sealed class AdvancedExportEngine : IExportEngine
{
    public ExportResult Export(Project project) => /* premium implementation */;
}
services.AddLockedFeatures();   // generated into your project's root namespace

if (Editions.HasEnterprise)     // generated const; the false branch is removed by the trimmer
{
    services.AddSingleton<IAuditSink, SiemAuditSink>();
}
dotnet publish -p:KanjectEdition=Free -p:PublishTrimmed=true
dotnet publish -p:KanjectEdition=Pro  -p:PublishTrimmed=true

With KanjectEdition=Pro, the generator emits:

public static class Editions
{
    public const Edition Current = Edition.Pro;
    public const bool HasFree = true;
    public const bool HasPro = true;
    public const bool HasEnterprise = false;
}

and AddLockedFeatures() registers AdvancedExportEngine as itself and as IExportEngine. With KanjectEdition=Free, those registration lines are omitted.

What gets generated

Generator Trigger Output
Edition constants the [EditionLadder] enum {Enum}.Editions.g.cs — a static Editions class in the enum's namespace (with the enum's accessibility) holding const Current and one const bool Has{Member} per edition.
Service registration [LockedBehind(Edition.X)] on a class LockedFeatureServiceCollectionExtensions.AddLockedFeatures(this IServiceCollection) in the project's RootNamespace (fallback Kanject.Core.Features.Generated). Registers each unlocked service as itself and as each directly implemented non-System interface via TryAddScoped / TryAddSingleton / TryAddTransient (Lifetime, default Scoped). Locked services become a comment.
Edition prune [LockedBehind(Edition.X)] on an implicitly private partial void {Type}.{Method}.FeaturePrune.g.cs — the implementing partial that forwards to {Method}Core, emitted only in editions that unlock it. Below that, no implementation exists and the compiler elides every call site.
Method guard [LockedBehind("flag")], or [LockedBehind(Edition.X, Binding = FeatureBinding.Runtime)], on a partial method {Type}.{Method}.FeatureGuard.g.cs — the implementing partial: IFeatureGate.IsEnabled(...) ? {Method}Core(...) : the Fallback, or throw FeatureLockedException. A runtime-bound edition is keyed "{EnumName}.{Member}".
Interceptors (opt-in, experimental) call sites of a runtime-locked non-partial method, with EnableFeatureLockInterceptors=true LockedFeatureInterceptors.g.cs in namespace Kanject.Core.Features.Interceptors, gating each receiver.Method(...) call through the receiver's non-private IFeatureGate member.
Pruning manifest [PrunedBelow(Edition.X)] on a type or member PrunedBelowManifest.PrunedBelow.g.cs — an [assembly: AssemblyMetadata("KanjectPrunedBelow", ...)] manifest recording, for this edition, whether each marked element must be present or absent. The verification target reads it after publish.
Entitlement claims [EntitlementClaims] on a partial class/struct/record A public static T FromClaimsJson(string? claimsJson) factory that fills each [Claim] property with a reflection-free Utf8JsonReader walk.

For a runtime gate such as

public sealed partial class Dashboard
{
    private readonly IFeatureGate _gate;
    public Dashboard(IFeatureGate gate) => _gate = gate;

    [LockedBehind("beta.export", Fallback = nameof(Locked))]
    public partial string Render();

    private string RenderCore() => "high-res export";
    private string Locked() => "Upgrade to export.";
}

the guard generator emits (header comments omitted):

partial class Dashboard
{
    public partial string Render()
        => _gate.IsEnabled(new global::Kanject.Core.Features.Models.FeatureKey("beta.export"))
            ? RenderCore()
            : Locked();
}

For typed license claims:

using System.Collections.Immutable;
using Kanject.Core.Features.Annotations.Attributes;

[EntitlementClaims]
public sealed partial class LicenseClaims
{
    [Claim("seats")]   public int Seats { get; init; }
    [Claim("tier")]    public string Tier { get; init; } = "";
    [Claim("modules")] public ImmutableArray<string> Modules { get; init; }
}

// claimsJson: a JSON object such as {"seats":25,"tier":"gold","modules":["reports"]}
LicenseClaims claims = LicenseClaims.FromClaimsJson(claimsJson);

Shape rules the generators enforce

The generators report a diagnostic rather than silently skipping an input shape they can't handle.

  • Method guard / edition prune: the containing type must be a non-generic, non-nested, non-static class; the locked method must be an instance partial method; {Method}Core must be an instance method whose signature matches (return type, parameters, ref/in/out, generic arity, constraints) — otherwise KANFL004. A Fallback must be an instance method with the same signature (KANFL002). out parameters are rejected.
  • Edition prune only: the method must be void, have no out parameter, and carry no accessibility or virtual/override/sealed/new/extern/async/abstract modifier (KANFL007). A stateless seam trips CA1822 (on the partial declaration and {Method}Core) and IDE0051 ({Method}Core is unreferenced below the lock) under TreatWarningsAsErrors; suppress those three on the seam.
  • Method guard only: the class (or a base) needs an IFeatureGate field or property.
  • Interceptors: the locked method, gate member, and fallback must be non-private; generic or static methods, calls without an explicit receiver, ?. calls, and method-group conversions are KANFL004.
  • Entitlement claims: the claims type may be nested or generic. It and every containing type must be partial (KANFL010, KANFL015), and it must have no primary constructor (KANFL013); each [Claim] property must be a settable instance property (KANFL014) of type int, long, bool, string, or ImmutableArray<string> (KANFL011) with a unique key (KANFL012). Missing, wrong-typed, or malformed claims resolve to defaults (0, false, "", empty array) rather than throwing.

Assembly boundaries

  • Edition generators are assembly-local to the [EditionLadder]. They only see a ladder declared in the same compilation. With no ladder, the partial-void prune reports KANFL004, the [PrunedBelow] manifest reports KANFL001, an edition-locked service reports KANFL001, and nothing is emitted for them. Keep prunable code in the ladder's assembly and gate other assemblies with the generated Editions.HasX constants (make the enum public and build every project with the same -p:KanjectEdition=...). String-flag and Binding = FeatureBinding.Runtime method locks don't need the ladder.
  • Exactly one [EditionLadder] per assembly (KANFL005).
  • VerifyEditionPruning is single-project. It reads the manifest generated in the publishing project and inspects that project's own published assembly (or .mstat), so the ladder, markers, generator, and trimmed/AOT publish must share one project.

MSBuild properties and items

Property / item Default Effect
KanjectEdition (unset → lowest edition) The edition this build is. Matched case-insensitively against the ladder; an unknown value is KANFL006 and falls back to the lowest edition.
EnableFeatureLockGenerator true false turns off the edition-constants, service-registration, prune, guard, and manifest generators. Locked partial methods then get no implementation.
EnableFeatureLockInterceptors false Enables interceptor mode and registers Kanject.Core.Features.Interceptors in InterceptorsNamespaces / InterceptorsPreviewNamespaces.
RequireExplicitEditionOnPublish false true fails dotnet publish with KANFL910 unless the edition came from the command line (-p:KanjectEdition=), a global/environment property, or Directory.Build.props. A value set in the project file body doesn't count — it is captured too late to be "explicit".
KanjectEditionLadder (unset) The ladder restated as a ;-list in declaration order (e.g. Free;Pro;Enterprise). Required by <LockedReference>; KANFL008 if it disagrees with the enum.
<LockedReference Include="…" MinEdition="…" /> — Becomes a ProjectReference only when KanjectEdition reaches MinEdition. Add ReferenceType="Package" and Version="…" to make it a PackageReference. KANFL911 without KanjectEditionLadder; KANFL912 for an off-ladder MinEdition.
VerifyEditionPruning false true runs the post-publish absence check (KANFL921) after a trimmed or Native AOT publish, and turns on EmitCompilerGeneratedFiles if you haven't set it. For Native AOT also set IlcGenerateMstatFile=true.
KanjectRequirePrunedBelowMarkers true With verification on, fail when no [PrunedBelow] markers are found.
KanjectPruningArtifactPath (derived) Overrides the artifact to inspect (default: the published $(TargetName).dll, or the .mstat for Native AOT).
<PropertyGroup>
  <KanjectEditionLadder>Free;Pro;Enterprise</KanjectEditionLadder>
  <RequireExplicitEditionOnPublish>true</RequireExplicitEditionOnPublish>
  <VerifyEditionPruning>true</VerifyEditionPruning>
</PropertyGroup>

<ItemGroup>
  <LockedReference Include="..\App.Pro\App.Pro.csproj" MinEdition="Pro" />
</ItemGroup>

Pruning verification notes: it fails closed (a missing or unreadable artifact is an error), and it self-checks its detector on every run. The Native AOT (.mstat) backend verifies non-generic types only; verify member-level and generic [PrunedBelow] markers against a trimmed (PublishTrimmed) artifact.

Analyzers

Analyzer Reports
Edition graph KANFL003 — a class available at edition E constructor-injects a type provided only by [LockedBehind] services above E (resolution would fail in an E build). Runtime-bound services are exempt.
Method group KANFL004 — with interceptors on, a runtime-locked non-partial method converted to a delegate would run ungated.
Single ladder KANFL005 — more than one [EditionLadder] enum.
Ladder manifest KANFL008 — <KanjectEditionLadder> lists different editions, or a different order, than the enum.
Unreachable-code suppressor Suppresses CS0162 (id KANFL900) only inside the then/else branch of an if whose condition is a generated Editions.Has{Member} constant, so the prune idiom works under TreatWarningsAsErrors.

Diagnostics

ID Severity What it means
KANFL001 Error A [LockedBehind] service or [PrunedBelow] element uses an edition that can't be ranked (no ladder in the assembly, another enum, unknown member); it is not registered / verified.
KANFL002 Error A [LockedBehind] Fallback is missing or its signature doesn't match the locked method.
KANFL003 Warning A consumer at a lower edition injects a capability only provided at a higher edition.
KANFL004 Error A [LockedBehind] method can't be guarded, pruned, or intercepted (shape, {Method}Core, gate member, no ladder, call form, method group).
KANFL005 Error More than one [EditionLadder] enum in the assembly.
KANFL006 Error <KanjectEdition> isn't a member of the ladder; the lowest edition is used.
KANFL007 Error An edition-locked partial method can't be elided (non-void, out parameter, or an explicit modifier).
KANFL008 Error <KanjectEditionLadder> disagrees with the [EditionLadder] enum.
KANFL009 Error [LockedBehind] used in an unsupported way, e.g. a string flag on a class.
KANFL010 Error An [EntitlementClaims] type isn't partial.
KANFL011 Error A [Claim] property has an unsupported type.
KANFL012 Error Two [Claim] properties share a key.
KANFL013 Error An [EntitlementClaims] type has a primary constructor.
KANFL014 Error A [Claim] property is get-only or static.
KANFL015 Error An [EntitlementClaims] type is nested in a type that isn't partial.
KANFL900 Suppression Suppresses CS0162 on a folded Editions.Has* branch.
KANFL910 Error (MSBuild) RequireExplicitEditionOnPublish is set but no edition was chosen for the publish.
KANFL911 Error (MSBuild) A <LockedReference> is declared but <KanjectEditionLadder> isn't set.
KANFL912 Error (MSBuild) A <LockedReference>'s MinEdition isn't on <KanjectEditionLadder>.
KANFL921 Error (MSBuild) Pruning verification failed: a marked element is present/absent contrary to the edition, or the artifact couldn't be read.
KANJECTGEN001 Error A generator threw an unexpected exception (a bug — please report it with the message).

KANFL050 is the runtime code carried by FeatureLockedException in Kanject.Core.Features.

Package Role Availability
Kanject.Core.Features Runtime: IFeatureGate, stock gates, FeatureKey, FeatureLockedException, DI helpers. nuget.org
Kanject.Core.Features.Annotations.Attributes The attributes and enums these generators read. nuget.org
Kanject.Core.Features.Entitlements Signed entitlement tokens and EntitlementFeatureGate; its token claims feed FromClaimsJson. Commercial license (not on nuget.org)
Kanject.Core.Features.TestKit Edition-aware xUnit [EditionFact] / [EditionTheory]. Commercial license (not on nuget.org)

License

Licensed under the Kanject Code Libraries License Agreement (KCLLA); the full text ships in this package as LICENSE.md. Organizations whose trailing-twelve-month gross revenue and total funding raised are each below US$250,000 may use it at no cost under the Free Tier. At or above either threshold a commercial license is required — contact commercial@kanjectbusiness.com.

There are no supported framework assets in this package.

Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
3.13.1 120 9/27/2026
3.13.0 91 9/27/2026
3.12.7 91 9/26/2026
3.12.6 131 9/7/2026
3.12.5 106 8/27/2026
3.12.4 107 8/22/2026
3.12.3 120 8/10/2026
3.12.2 108 8/9/2026
3.12.1 107 8/5/2026
3.12.0 109 8/5/2026
3.11.0 114 8/3/2026
3.10.5 127 7/30/2026
3.10.4 127 7/18/2026
3.10.3 127 7/13/2026
3.10.2 116 7/11/2026
3.10.1 121 7/11/2026
3.10.0 123 7/9/2026
3.9.1 117 7/9/2026