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
<PackageReference Include="Kanject.Core.Packages.Annotations" Version="3.13.1"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference>
<PackageVersion Include="Kanject.Core.Packages.Annotations" Version="3.13.1" />
<PackageReference Include="Kanject.Core.Packages.Annotations"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference>
paket add Kanject.Core.Packages.Annotations --version 3.13.1
#r "nuget: Kanject.Core.Packages.Annotations, 3.13.1"
#:package Kanject.Core.Packages.Annotations@3.13.1
#addin nuget:?package=Kanject.Core.Packages.Annotations&version=3.13.1
#tool nuget:?package=Kanject.Core.Packages.Annotations&version=3.13.1
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>whereTis a collection the aggregator can rebuild: the results are flattened into oneT. That means an array;IEnumerable<E>,IReadOnlyCollection<E>,IReadOnlyList<E>,ICollection<E>, orIList<E>; a type with[CollectionBuilder], such asImmutableArray<E>; or a class or struct with a public parameterless constructor and a publicAdd(E), such asList<E>orHashSet<E>; - several providers, any other
Task<T>: returnsTask<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 asList<E>?(so a provider'snullis kept), and enumerables nothing can rebuild, such asQueue<E>orISet<E>; - several providers,
Task: awaits all providers withTask.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 ofIDisposable/IAsyncDisposableare skipped silently, because the DI container owns disposal. - Make providers
partialto 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 injectIXxxPackageManagerinstead. - Runtime wiring is checked at startup, not compile time. Call
ValidatePackageGraphOrThrow()fromKanject.Core.Packagesto 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.
Related packages
| 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.
Learn more about Target Frameworks and .NET Standard.
-
.NETStandard 2.0
- Kanject.Core.Packages.Annotations.Attributes (>= 3.13.1)
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.