Kanject.Core.Packages.Annotations 3.13.1

Prefix Reserved
dotnet add package Kanject.Core.Packages.Annotations --version 3.13.1
                    
NuGet\Install-Package Kanject.Core.Packages.Annotations -Version 3.13.1
                    
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.Packages.Annotations" Version="3.13.1">
  <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.Packages.Annotations" Version="3.13.1" />
                    
Directory.Packages.props
<PackageReference Include="Kanject.Core.Packages.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.Packages.Annotations --version 3.13.1
                    
#r "nuget: Kanject.Core.Packages.Annotations, 3.13.1"
                    
#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.Packages.Annotations@3.13.1
                    
#: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.Packages.Annotations&version=3.13.1
                    
Install as a Cake Addin
#tool nuget:?package=Kanject.Core.Packages.Annotations&version=3.13.1
                    
Install as a Cake Tool

Kanject.Core.Packages.Annotations

Roslyn source generator and analyzers for the Kanject package manager. You mark a port interface with [Package] and its implementations with [PackageProvider], and the generator writes the rest: package managers, resolvers, keyed DI registrations, per-assembly provider manifests, scope-aware bare-port dispatch, and the startup graph-validation contributor. The analyzers catch miswiring (missing attributes, duplicate versions or defaults, captive dependencies) while you type.

Install it next to the runtime package, Kanject.Core.Packages. It is marked as a development dependency: the generator and analyzers run at compile time only.

Installation

dotnet add package Kanject.Core.Packages
dotnet add package Kanject.Core.Packages.Annotations

Then make sure the project file looks like this:

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

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.Packages.Annotations.Attributes can be stripped from compilation.

The attribute types live in Kanject.Core.Packages.Annotations.Attributes, which this package depends on, so you don't need to reference it yourself. Kanject.Core.Packages does not reference this generator; add it to every project that needs generated code:

  • the project that declares a [Package] interface (manager and resolver are generated there), and
  • every project that declares [PackageProvider] classes (DI registrations, manifests and key resolvers are generated there).

The analyzer assembly targets netstandard2.0, as Roslyn requires. The code it emits depends on Kanject.Core.Packages, so consuming projects target .NET 8, .NET 9 or .NET 10.

Quick start

using Kanject.Core.Packages.Annotations.Attributes;

[Package(id: "payment.gateway")]
[PackageDescription("Process order payments through third-party gateways")]
public interface IPaymentGateway
{
    Task<ChargeResult> ChargeAsync(ChargeRequest request, CancellationToken cancellationToken);
}

[Package<IPaymentGateway>]
[PackageProvider(id: "stripe.payment.gateway", version: 1)]
[PackageProviderOwner("Payments team")]
[PackageResolverKey("stripe")]
[DefaultPackageProvider]
public sealed partial class StripePaymentGateway : IPaymentGateway
{
    public Task<ChargeResult> ChargeAsync(ChargeRequest request, CancellationToken cancellationToken)
        => /* call Stripe */ throw new NotImplementedException();
}

[Package<IPaymentGateway>]
[PackageProvider(id: "paystack.payment.gateway", version: 1)]
[PackageProviderOwner("Payments team")]
[PackageResolverKey("paystack")]
public sealed partial class PaystackPaymentGateway : IPaymentGateway
{
    public Task<ChargeResult> ChargeAsync(ChargeRequest request, CancellationToken cancellationToken)
        => /* call Paystack */ throw new NotImplementedException();
}

Register everything the generator produced for this package, then dispatch explicitly through the manager:

builder.Services.AddPaymentGatewayPackages();

public sealed class CheckoutService(IPaymentGatewayPackageManager gateways)
{
    public Task<ChargeResult> ChargeAsync(string gatewayKey, ChargeRequest request, CancellationToken ct)
        => gateways.ResolvePackage(gatewayKey).ChargeAsync(request, ct);
}

AddPaymentGatewayPackages() is emitted into a namespace named after the provider project's assembly, so add a using for it. The runtime concepts behind this — resolver keys, versions, defaults, IPackageScope ambient routing, and startup graph validation — are documented in the Kanject.Core.Packages package.

What gets generated

Generated names come from the PascalCased package id, not the interface name: payment.gateway becomes PaymentGateway. Provider-specific names add the provider id and version, e.g. stripe.payment.gateway v1 becomes StripePaymentGatewayV1Package. KANPKG004 tells you when the package id and interface name diverge.

In the project that declares the [Package] interface

Generated Purpose
IPaymentGatewayPackageManager, PaymentGatewayPackageManager Scoped manager: exposes the port's methods and properties and delegates them to the active Package, keeping ref / out / in / ref readonly / params / scoped parameters and by-ref returns; ResolvePackage(key, version), ResolvePackageAsync(args), UsePackage(...).
IPaymentGatewayPackageManager<TPackage>, PaymentGatewayPackageManager<TPackage> Typed manager variant.
IPaymentGatewayResolver, AbstractPaymentGatewayResolver Resolver contract and base class for custom resolvers.
AddPaymentGatewayResolver<TResolver>(ServiceLifetime) Replace the resolver with your own (Scoped by default), regardless of registration order.
AddPaymentGatewayScopedDispatch() Registers IPaymentGateway itself (Scoped, TryAdd) so it resolves through the ambient IPackageScope, falling back to the resolver's default.
AddPaymentGatewayPackageManager(), GetPaymentGatewayPackage(...) Manager registration and keyed lookup helpers used by the provider-side code.

In each project that declares providers

Generated Purpose
AddPaymentGatewayPackages() Registers every provider in this assembly as a keyed service, plus the manager, the key resolver (when present), scope-aware dispatch, the provider catalog + manifests, and the graph contributor.
AddStripePaymentGatewayV1Package() Registers a single provider/version.
GetStripePaymentGatewayV1Package() IServiceProvider lookup of one provider/version.
UseStripePaymentGatewayV1Package() On the manager: switch the active provider. On the resolver: return that provider's (packageId, version).
PaymentGatewayKeyResolver, AddPaymentGatewayKeyResolver(ServiceLifetime) Catalog-backed resolver generated when any provider has [PackageResolverKey] or [DefaultPackageProvider].
ResolvePackageByKey(key, version) Local (key, version) switch used when the key resolver is constructed without a catalog.
Provider manifests Internal IPackageProviderManifest per provider, so sibling assemblies compose into one catalog without reflection.
Graph contributor Internal IPackageGraphContributor that ValidatePackageGraph() runs at startup.
Metadata partials PackageIdentifier, PackageName, PackageProviderName, PackageProviderVersion on each partial provider.

Provider keyed registrations are Scoped unless the provider sets [PackageProvider(..., Lifetime = PackageLifetime.Singleton)] (or Transient). The manager and scope-aware dispatch stay Scoped.

When the [Package] interface comes from an assembly without the generator

If a provider's [Package<T>] interface is declared in a referenced assembly that doesn't run this generator, there is no generated IPaymentGatewayPackageManager to build on. The provider project gets catalog-only output instead:

Generated Purpose
AddPaymentGatewayPackages() Registers this assembly's providers as keyed services, plus their manifests, the provider catalog, and the graph contributor.
AddStripePaymentGatewayV1Package(), GetStripePaymentGatewayV1Package() Register or resolve one provider/version.
ResolvePaymentGatewayProvider(key, version) Resolves an exact (key, version) pair through the catalog, including providers registered by sibling assemblies.

These methods are all internal, so expose your own registration method from the provider assembly. There is no manager, resolver, bare-port bridge, default or latest-version fallback, or metadata partial. A version is always required: ValidatePackageGraph() reports an IPackageScope selection without one as KANPKG053. Keyed-service keys come from PackageProviderServiceKeys in Kanject.Core.Packages, so provider assemblies built with different generator versions still resolve one another's providers.

Provider aggregators

An aggregator calls every listed provider instead of one:

using Kanject.Core.Packages.Annotations.Attributes;

[ProviderAggregator(typeof(IShippingQuotes))]
[PackageProvider<EvriShippingQuotes>]
[PackageProvider<VoilaShippingQuotes>]
public partial class ShippingQuoteAggregator
{
}

The generator adds a constructor taking IServiceProvider and one method per Task or Task<T> method of the port, its own and those inherited from its base interfaces:

  • one provider: the call is forwarded unchanged;
  • several providers, Task<T> where T is a collection the aggregator can rebuild: the results are flattened into one T. That means an array; IEnumerable<E>, IReadOnlyCollection<E>, IReadOnlyList<E>, ICollection<E>, or IList<E>; a type with [CollectionBuilder], such as ImmutableArray<E>; or a class or struct with a public parameterless constructor and a public Add(E), such as List<E> or HashSet<E>;
  • several providers, any other Task<T>: returns Task<IList<T>> with one result per provider. Enumerable values count as scalars here: string, dictionaries and other keyed maps, JSON nodes (JsonArray, JsonObject), nullable collections such as List<E>? (so a provider's null is kept), and enumerables nothing can rebuild, such as Queue<E> or ISet<E>;
  • several providers, Task: awaits all providers with Task.WhenAll.

Whether a type can be rebuilt depends on the target framework: .NET 10 adds [CollectionBuilder] to ReadOnlyCollection<T>, ReadOnlySet<T>, and FrozenSet<T>, so those are flattened on .NET 10 and later and returned one per provider before that. A project that multi-targets across .NET 10 gets a different return type on each side.

Providers are deduplicated by provider id, keeping the highest version, so v1 and v2 of the same provider are never both called. It also emits AddShippingQuoteAggregator(), which registers the aggregator class Scoped. The optional name argument, [ProviderAggregator(typeof(IShippingQuotes), "QuoteFanout")], renames only the registration helper (AddQuoteFanoutAggregator()); it still registers ShippingQuoteAggregator. The aggregator resolves each provider through its generated Get…Package() helper, so the providers must be registered.

A fan-out can't express every member. Properties, methods that don't return Task / Task<T> (including ValueTask), generic methods, and methods with ref / out / in or ref-struct parameters are left out of the aggregator and reported as KANPKG018. params parameters are kept.

Package schema document

On every build the package writes <AssemblyName>.packageschema.md next to the project file. It catalogs the generated package interfaces, providers, aggregators, contracts, DI registrations, and usage examples, and is useful as a reference for developers and AI assistants. [PackageDescription] and [PackageProviderOwner] feed into it (hence KANPKG001 and KANPKG008).

MSBuild property Default Effect
GeneratePackageSchema true Set to false to skip writing the file.
PackageSchemaOutputDir (project directory) Relative (to the project) or absolute directory for the file.

To extract the document, the build sets EmitCompilerGeneratedFiles to true unless you have already set it. dotnet clean deletes the file.

Things to know

  • Managers and aggregators forward the port's ordinary methods and properties, including those inherited from its base interfaces — a marker port that only inherits a third-party contract (IStudioChatClient : IChatClient) gets the whole contract on its manager. Same-signature members resolve the way C# member lookup on the port resolves them. Indexers, events, and a member declared by more than one base interface (an ambiguous forwarding call) are skipped, and KANPKG017 (Info) notes each one. Members of IDisposable / IAsyncDisposable are skipped silently, because the DI container owns disposal.
  • Make providers partial to get the metadata properties. Non-partial providers still register.
  • Aggregators must be partial (KANPKG009). Their contract type must be a [Package] interface (KANPKG010).
  • KANPKG002 fires on every bare-port injection of a [Package] interface, because an analyzer can't see your DI registrations. AddXxxPackages() already wires the scope-aware bridge. Suppress the warning where that's true, or inject IXxxPackageManager instead.
  • Runtime wiring is checked at startup, not compile time. Call ValidatePackageGraphOrThrow() from Kanject.Core.Packages to catch missing registrations, unresolved keys, and cross-assembly key or default conflicts.

Diagnostics

ID Severity What it means
KANPKG001 Warning A [Package] interface has no [PackageDescription] (code fix available).
KANPKG002 Warning A [Package] interface is injected directly; confirm scope-aware dispatch is registered, or inject the manager.
KANPKG003 Error A [PackageProvider] class is missing [Package<T>] (code fix available).
KANPKG004 Info The package id's PascalCase form differs from the interface name, so generated names won't match the interface.
KANPKG005 Error A provider does not implement the interface named in its [Package<T>].
KANPKG007 Error Two providers in one package share the same (providerId, version).
KANPKG008 Warning A provider has no [PackageProviderOwner] (code fix available).
KANPKG009 Error A [ProviderAggregator] class is not partial (code fix available).
KANPKG010 Error The [ProviderAggregator] contract type is not a [Package] interface.
KANPKG011 Error More than one provider in a package is marked [DefaultPackageProvider].
KANPKG012 Error Two providers in one package share the same (resolver key, version) pair.
KANPKG014 Info A generated key resolver will be auto-registered as the Scoped fallback resolver.
KANPKG015 Info A Singleton provider injects a Kanject service that is Scoped by default (captive dependency).
KANPKG016 Info A custom resolver registered Singleton injects a Kanject service that is Scoped by default (captive dependency).
KANPKG017 Info A [Package] interface member (indexer, event, or member declared by more than one base interface) is not forwarded by the generated manager or aggregator.
KANPKG018 Error A [ProviderAggregator] can't fan out a contract member (property, non-Task return, generic method, by-reference or ref-struct parameter), so it is left out.
KANJECTPKGSCHEMA001 Warning Generating the .packageschema.md document failed.
KANJECTGEN001 Error The generator hit an unhandled exception; the message carries the exception and stack trace.
KANJECTGEN002 Error The generator skipped an input, e.g. a provider whose [Package] interface could not be read.

The runtime counterparts (KANPKG050–KANPKG057, reported by ValidatePackageGraph()) are documented in the Kanject.Core.Packages package.

Package Role Availability
Kanject.Core.Packages Runtime: IPackageScope, PackageScopeBuilder, provider catalog, graph validator nuget.org
Kanject.Core.Packages.Annotations.Attributes The attribute types this generator reads (dependency of this package) 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 122 9/27/2026
3.13.0 87 9/27/2026
3.12.7 98 9/26/2026
3.12.6 127 9/7/2026
3.12.5 107 8/27/2026
3.12.4 114 8/22/2026
3.12.3 126 8/10/2026
3.12.2 117 8/9/2026
3.12.1 112 8/5/2026
3.12.0 129 8/5/2026
3.11.0 119 8/3/2026
3.10.5 124 7/30/2026
3.10.4 132 7/18/2026
3.10.3 133 7/13/2026
3.10.2 125 7/11/2026
3.10.1 123 7/11/2026
3.10.0 135 7/9/2026
3.9.1 123 7/9/2026

Generated provider aggregators emit their using directives in a stable, sorted order. They were enumerated from a hash set whose order changes with every compiler process, so identical source could compile to different assemblies.