Kanject.Core.Channels.Annotations
3.14.0
Prefix Reserved
dotnet add package Kanject.Core.Channels.Annotations --version 3.14.0
NuGet\Install-Package Kanject.Core.Channels.Annotations -Version 3.14.0
<PackageReference Include="Kanject.Core.Channels.Annotations" Version="3.14.0"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference>
<PackageVersion Include="Kanject.Core.Channels.Annotations" Version="3.14.0" />
<PackageReference Include="Kanject.Core.Channels.Annotations"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference>
paket add Kanject.Core.Channels.Annotations --version 3.14.0
#r "nuget: Kanject.Core.Channels.Annotations, 3.14.0"
#:package Kanject.Core.Channels.Annotations@3.14.0
#addin nuget:?package=Kanject.Core.Channels.Annotations&version=3.14.0
#tool nuget:?package=Kanject.Core.Channels.Annotations&version=3.14.0
Kanject.Core.Channels.Annotations
Roslyn source generator, analyzer, and code fix for Kanject channels. Annotate a batch handler with [Channel] and this package generates, at compile time, a typed writer interface, a DI registration extension, and either a hosted BackgroundService consumer or an AWS Lambda invocation-local drain helper — all running on the Kanject.Core.Channels engine. The analyzer reports handler-shape and configuration mistakes as build diagnostics (KANCHN…).
Installation
<ItemGroup>
<PackageReference Include="Kanject.Core.Channels" />
<PackageReference Include="Kanject.Core.Channels.Annotations" PrivateAssets="all" />
</ItemGroup>
dotnet add package Kanject.Core.Channels.Annotations writes an IncludeAssets line for this package that omits compile. Delete that line (keep PrivateAssets="all") — otherwise the attribute types from Kanject.Core.Channels.Annotations.Attributes can be stripped from compilation.
This is a development-only package (DevelopmentDependency): it ships the generator under analyzers/ and adds no runtime assembly to your output. Kanject.Core.Channels does not reference it, so add it explicitly to every project that declares [Channel] handlers. The runtime does reference Kanject.Core.Channels.Annotations.Attributes, which supplies the attribute types.
The generator targets netstandard2.0 and runs inside the compiler. The code it emits uses Kanject.Core.Channels, which targets .NET 8, .NET 9 and .NET 10.
Quick start
using Kanject.Core.Channels.Annotations.Attributes;
namespace Shop.Orders;
public sealed record OrderPlaced(string OrderId, decimal Total);
public sealed partial class OrderProjector(IOrderReadModel readModel)
{
[Channel(Capacity = 5000, BatchSize = 25, FlushIntervalMs = 200)]
public async ValueTask ProjectAsync(ReadOnlyMemory<OrderPlaced> batch, CancellationToken ct)
{
await readModel.UpsertAsync(batch.ToArray(), ct);
}
}
// Composition root
builder.Services.AddOrderProjectorProjectAsyncChannel();
// Producer
public sealed class CheckoutService(IOrderProjectorProjectAsyncChannelWriter orders)
{
public ValueTask OnOrderPlacedAsync(OrderPlaced order, CancellationToken ct) =>
orders.WriteAsync(order, ct);
}
The handler is an ordinary method with a body; only its containing type must be partial. The generated files fully qualify every type and DI extension call, so they compile without any using in your project — including a plain Microsoft.NET.Sdk project with ImplicitUsings off.
What gets generated
For a [Channel] method M on type T with item type TItem (hint names are T_M.*.g.cs):
| Output | Generated code | When |
|---|---|---|
ChannelWriter.g.cs |
public interface IT{M}ChannelWriter : IChannelWriter<TItem> — WriteAsync(item), WriteAsync(ReadOnlyMemory<TItem>), WriteAllAsync(IAsyncEnumerable<TItem>), TryWrite, CompleteAsync, ChannelName. Emitted internal when TItem is not public. |
Always |
DiExtension.g.cs |
AddT{M}Channel(this IServiceCollection services, Action<ChannelOptions>? configure = null) and const string ServiceKey in T{M}ChannelServiceCollectionExtensions, plus an internal in-process writer |
Always |
HostedService.g.cs |
internal sealed class T{M}ChannelService : ChannelHostedServiceBase<TItem> |
Resolved mode InProcess |
InvocationDrainer.g.cs |
public static class T{M}ChannelDrain with DrainAsync(IServiceProvider serviceProvider, TimeSpan remainingTime, CancellationToken cancellationToken = default) |
Resolved mode InvocationLocal |
Everything is emitted into the handler's namespace. ChannelName defaults to T_M and can be overridden with [Channel(ChannelName = "…")]; it does not change the generated type names.
The DI extension copies the attribute values into a ChannelOptions (Capacity, FullMode, BatchSize, FlushInterval, RestartOnFault, MaxRestartAttempts, SafetyMargin), runs your configure callback, and creates the bounded channel. FullMode is mapped to the BoundedChannelFullMode member of the same name. The extension then registers:
ChannelOptions,Channel<TItem>,ChannelReader<TItem>andChannelWriter<TItem>as keyed singletons underT{M}ChannelServiceCollectionExtensions.ServiceKey(the handler's fully qualifiedNamespace.Type.Method). Several channels — including several over the same item type — therefore coexist in one container. Resolve them withGetRequiredKeyedService<ChannelWriter<TItem>>(T{M}ChannelServiceCollectionExtensions.ServiceKey)or[FromKeyedServices(…)]. Nothing is registered unkeyed.- The writer interface
IT{M}ChannelWriteras a singleton bound to this channel. - In
InProcessmode, the hosted consumer (built over this channel's reader and options) and the handler type with the lifetime fromLifetime(Scopedby default, which gives each batch its own async DI scope).
Calling the same AddT{M}Channel twice on one IServiceCollection throws InvalidOperationException: the host de-duplicates the hosted consumer by type, so a second registration would leave a writer feeding a channel nobody drains.
Mode resolution
ChannelMode.Default is resolved when the project compiles: InvocationLocal if the compilation references Kanject.Core.CloudFunction.Provider.AwsLambda, otherwise InProcess. Distributed emits only the writer interface and the DI extension — no consumer.
Handler shape the generator expects
- An ordinary method (not a local function, lambda, operator, accessor or explicit interface implementation —
KANCHN024), non-generic and on a non-generic type (KANCHN019). public,internalorprotected internal, on containing types that are too (KANCHN022): the generated consumer is a separate type that calls it.- Returns
TaskorValueTask(KANCHN003;async voidincluded). - The partition is the first parameter that isn't a
CancellationToken:TItemfor per-item handlers, orReadOnlyMemory<TItem>/IReadOnlyList<TItem>for batched ones (KANCHN004).ReadOnlyMemory<TItem>is passed through without copying; anIReadOnlyList<TItem>handler receives a copy of each batch as an array, in every mode.TItemcan't be a ref struct or pointer (KANCHN023). - A
CancellationTokenis optional and may appear in any position; the consumer forwards its token to the first one. - Generated consumers bind arguments by name, so any other parameter must be optional or
paramsand keeps its default (KANCHN021). No parameter may beref,out,inorref readonly(KANCHN020).
The generator emits nothing for a method that breaks any of these rules; the analyzer reports why.
Things the generated wiring does not do
- In
InvocationLocalmode the DI extension does not register the handler type — register it yourself. [ChannelProducer]generates nothing; every use is reported asKANCHN017. Use a[Channel(Mode = ChannelMode.Distributed)]handler for a producer-only port.
See the Kanject.Core.Channels package for the drain contracts, the Lambda guidance, and ChannelOptions.
Diagnostics
| ID | Severity | What it means |
|---|---|---|
KANCHN002 |
Error | The type containing an instance [Channel] method is not partial. Code fix: "Make containing type partial". |
KANCHN003 |
Error | The method does not return Task or ValueTask. |
KANCHN004 |
Error | No item/batch parameter (T, ReadOnlyMemory<T>, IReadOnlyList<T>) besides the CancellationToken. |
KANCHN005 |
Error | BatchSize > 1 but the parameter is a single item. |
KANCHN006 |
Warning | Batch-shaped parameter with BatchSize = 1; every batch holds one item. |
KANCHN007 |
Error | Capacity is not > 0. |
KANCHN010 |
Warning | Mode = InvocationLocal but the project does not reference Kanject.Core.CloudFunction.Provider.AwsLambda. |
KANCHN011 |
Error | [Channel] and [Parallel] on the same method. |
KANCHN013 |
Warning | FullMode = DropOldest, DropWrite or DropNewest drops items silently on overflow. |
KANCHN014 |
Error | MaxRestartAttempts = 0; use -1, a positive value, or RestartOnFault = false. |
KANCHN015 |
Warning | [Channel] on a static method; no DI resolution, the method is called directly. |
KANCHN017 |
Error | [ChannelProducer] is not supported; no code is generated for it. |
KANCHN018 |
Error | FullMode is not a defined ChannelFullMode member (e.g. (ChannelFullMode)42); no wiring is generated for the method. |
KANCHN019 |
Error | The method is generic, or is declared on a generic type; no wiring is generated. |
KANCHN020 |
Error | A parameter is ref, out, in or ref readonly; the consumer passes arguments by value. |
KANCHN021 |
Error | A required parameter other than the item/batch and the first CancellationToken; the consumer can't supply it. Make it optional. |
KANCHN022 |
Error | The method, or a type containing it, is private, protected, private protected or file-local; the generated consumer can't call it. |
KANCHN023 |
Error | The item type is a ref struct or pointer and can't be a Channel<T> element. |
KANCHN024 |
Error | [Channel] is on a local function, lambda, operator, accessor or explicit interface implementation; only ordinary methods are supported. |
If the generator itself throws while emitting code, it reports KANJECTGEN001 (Error) with the exception message instead of failing silently.
Related packages
| Package | Role | Availability |
|---|---|---|
Kanject.Core.Channels |
Runtime engine the generated code calls | nuget.org |
Kanject.Core.Channels.Annotations.Attributes |
[Channel], [ChannelProducer], ChannelMode, ChannelFullMode, ChannelLifetime |
nuget.org |
Kanject.Core.CloudFunction.Provider.AwsLambda |
Lambda hosting; its reference selects InvocationLocal for ChannelMode.Default |
nuget.org |
Kanject.Core.Channels.Provider.AwsSqs.Annotations |
Generator for [SqsChannel] SQS Lambda entry points |
Commercial license (not on nuget.org) |
Kanject.Core.Channels.Provider.AwsKinesis.Annotations |
Generator for [KinesisChannel] Kinesis Lambda entry points |
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.Channels.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.14.0 | 45 | 10/5/2026 |
| 3.13.1 | 126 | 9/27/2026 |
| 3.13.0 | 85 | 9/27/2026 |
| 3.12.7 | 94 | 9/26/2026 |
| 3.12.6 | 118 | 9/7/2026 |
| 3.12.5 | 100 | 8/27/2026 |
| 3.12.4 | 107 | 8/22/2026 |
| 3.12.3 | 132 | 8/10/2026 |
| 3.12.2 | 112 | 8/9/2026 |
| 3.12.1 | 122 | 8/5/2026 |
| 3.12.0 | 127 | 8/5/2026 |
| 3.11.0 | 121 | 8/3/2026 |
| 3.10.5 | 123 | 7/30/2026 |
| 3.10.4 | 131 | 7/18/2026 |
| 3.10.3 | 133 | 7/13/2026 |
| 3.10.2 | 122 | 7/11/2026 |
| 3.10.1 | 126 | 7/11/2026 |
| 3.10.0 | 120 | 7/9/2026 |
| 3.9.1 | 123 | 7/9/2026 |