Kanject.Core.Features.Annotations
3.12.7
Prefix Reserved
See the version list below for details.
dotnet add package Kanject.Core.Features.Annotations --version 3.12.7
NuGet\Install-Package Kanject.Core.Features.Annotations -Version 3.12.7
<PackageReference Include="Kanject.Core.Features.Annotations" Version="3.12.7"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference>
<PackageVersion Include="Kanject.Core.Features.Annotations" Version="3.12.7" />
<PackageReference Include="Kanject.Core.Features.Annotations"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference>
paket add Kanject.Core.Features.Annotations --version 3.12.7
#r "nuget: Kanject.Core.Features.Annotations, 3.12.7"
#:package Kanject.Core.Features.Annotations@3.12.7
#addin nuget:?package=Kanject.Core.Features.Annotations&version=3.12.7
#tool nuget:?package=Kanject.Core.Features.Annotations&version=3.12.7
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
partialmethod;{Method}Coremust be an instance method whose signature matches (return type, parameters,ref/in/out, generic arity, constraints) — otherwiseKANFL004. AFallbackmust be an instance method with the same signature (KANFL002).outparameters are rejected. - Edition prune only: the method must be
void, have nooutparameter, and carry no accessibility orvirtual/override/sealed/new/extern/async/abstractmodifier (KANFL007). A stateless seam tripsCA1822(on the partial declaration and{Method}Core) andIDE0051({Method}Coreis unreferenced below the lock) underTreatWarningsAsErrors; suppress those three on the seam. - Method guard only: the class (or a base) needs an
IFeatureGatefield 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 areKANFL004. - 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 typeint,long,bool,string, orImmutableArray<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 reportsKANFL004, the[PrunedBelow]manifest reportsKANFL001, an edition-locked service reportsKANFL001, and nothing is emitted for them. Keep prunable code in the ladder's assembly and gate other assemblies with the generatedEditions.HasXconstants (make the enumpublicand build every project with the same-p:KanjectEdition=...). String-flag andBinding = FeatureBinding.Runtimemethod locks don't need the ladder. - Exactly one
[EditionLadder]per assembly (KANFL005). VerifyEditionPruningis 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.
Related packages
| 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.
Learn more about Target Frameworks and .NET Standard.
-
.NETStandard 2.0
- Kanject.Core.Features.Annotations.Attributes (>= 3.12.7)
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 |