NetEvolve.Pulse.SourceGeneration
0.67.36
Prefix Reserved
dotnet add package NetEvolve.Pulse.SourceGeneration --version 0.67.36
NuGet\Install-Package NetEvolve.Pulse.SourceGeneration -Version 0.67.36
<PackageReference Include="NetEvolve.Pulse.SourceGeneration" Version="0.67.36"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference>
<PackageVersion Include="NetEvolve.Pulse.SourceGeneration" Version="0.67.36" />
<PackageReference Include="NetEvolve.Pulse.SourceGeneration"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference>
paket add NetEvolve.Pulse.SourceGeneration --version 0.67.36
#r "nuget: NetEvolve.Pulse.SourceGeneration, 0.67.36"
#:package NetEvolve.Pulse.SourceGeneration@0.67.36
#addin nuget:?package=NetEvolve.Pulse.SourceGeneration&version=0.67.36
#tool nuget:?package=NetEvolve.Pulse.SourceGeneration&version=0.67.36
NetEvolve.Pulse.SourceGeneration
NetEvolve.Pulse.SourceGeneration is a Roslyn source generator for the Pulse CQRS mediator library. It automatically generates DI registration code for handler classes annotated with [PulseHandler], [PulseHandler<TMessage>], or [PulseGenericHandler], eliminating manual service registrations and catching missing or duplicate registrations at compile time.
Features
- Compile-Time Code Generation: Emits
IServiceCollectionextension methods withTryAdd*registrations for all annotated handlers - Closed Open-Generic Handler Support:
[PulseHandler<TMessage>]closes open-generic handler classes for specific message types at compile time; multiple attributes on the same class register it for multiple message types - Pure Open-Generic Handler Support:
[PulseGenericHandler]registers an open-generic handler class directly as an open-generic DI service (e.g.services.TryAddScoped(typeof(ICommandHandler<,>), typeof(MyHandler<,>))), allowing the DI container to resolve any closed variant at runtime - Incremental Generator: Uses
ForAttributeWithMetadataNamefor fast, IDE-friendly discovery - Configurable Lifetimes: Supports
Singleton,Scoped(default), andTransientviaPulseServiceLifetimeenum - Assembly-Derived Method Name: Generated method name is derived from
AssemblyNamewith dots removed andPulseHandlersappended (e.g.,MyProject→AddMyProjectPulseHandlers) - Root Namespace Support: Generated namespace uses the consuming project's
RootNamespace - Multi-Interface Instance Sharing: Handlers implementing multiple interfaces are registered as the concrete type once; each interface resolves via a factory delegate so all share the same instance within the configured lifetime
- Diagnostics: PULSE001–PULSE006 covering missing handler interfaces, duplicate registrations, open-generic type annotations, and invalid or incompatible explicit message type arguments
- Fully Qualified Names: All generated code uses
global::prefixed type names to avoid namespace conflicts
Installation
NuGet Package Manager
Install-Package NetEvolve.Pulse.SourceGeneration
.NET CLI
dotnet add package NetEvolve.Pulse.SourceGeneration
PackageReference
<PackageReference Include="NetEvolve.Pulse.SourceGeneration" Version="x.x.x" />
Quick Start
using NetEvolve.Pulse.Attributes;
using NetEvolve.Pulse.Extensibility;
// 1. Annotate your handler classes
[PulseHandler]
public class CreateOrderHandler
: ICommandHandler<CreateOrderCommand, OrderResult>
{
public Task<OrderResult> HandleAsync(
CreateOrderCommand command,
CancellationToken cancellationToken) =>
Task.FromResult(new OrderResult(Guid.NewGuid()));
}
// 2. Call the generated extension method in your startup code
// Method name is derived from your assembly name
services.AddMyProjectPulseHandlers();
Usage
Handler Registration
Annotate handler classes with [PulseHandler] and the generator emits TryAddScoped, TryAddSingleton, or TryAddTransient calls based on the configured lifetime:
[PulseHandler] // Scoped (default)
public class CreateOrderHandler : ICommandHandler<CreateOrderCommand, OrderResult> { ... }
[PulseHandler(Lifetime = PulseServiceLifetime.Singleton)]
public class GetCachedDataHandler : IQueryHandler<GetCachedDataQuery, CachedData> { ... }
[PulseHandler(Lifetime = PulseServiceLifetime.Transient)]
public class NotificationHandler : IEventHandler<OrderCreatedEvent> { ... }
Closed Open-Generic Handler Registration
Use [PulseHandler<TMessage>] to close an open-generic handler class for a specific message type. Apply the attribute multiple times to register the same class for several message types:
// Register the generic handler for two concrete command types
[PulseHandler<CreateOrderCommand>]
[PulseHandler<CancelOrderCommand>]
public class GenericCommandHandler<TCmd, TResult> : ICommandHandler<TCmd, TResult>
where TCmd : ICommand<TResult>
{
public Task<TResult> HandleAsync(TCmd command, CancellationToken cancellationToken) =>
Task.FromResult(default(TResult)!);
}
// Register with a non-default lifetime
[PulseHandler<OrderShippedEvent>(Lifetime = PulseServiceLifetime.Singleton)]
public class GenericAuditEventHandler<TEvent> : IEventHandler<TEvent>
where TEvent : IEvent
{
public Task HandleAsync(TEvent message, CancellationToken cancellationToken) =>
Task.CompletedTask;
}
Pure Open-Generic Handler Registration
Use [PulseGenericHandler] when you want a single open-generic class to handle any closed variant of a message type, resolved by the DI container at runtime. The generator emits a typeof()-based registration instead of a closed-type one:
// Handles ICommandHandler<TCommand, TResult> for any TCommand : ICommand<TResult>
[PulseGenericHandler]
public class GenericCommandHandler<TCommand, TResult> : ICommandHandler<TCommand, TResult>
where TCommand : ICommand<TResult>
{
public Task<TResult> HandleAsync(TCommand command, CancellationToken cancellationToken) =>
Task.FromResult(default(TResult)!);
}
// Generated: services.TryAddScoped(
// typeof(ICommandHandler<,>), typeof(GenericCommandHandler<,>));
// Handles IEventHandler<TEvent> for any TEvent : IEvent, registered as Singleton
[PulseGenericHandler(Lifetime = PulseServiceLifetime.Singleton)]
public class GenericAuditEventHandler<TEvent> : IEventHandler<TEvent>
where TEvent : IEvent
{
public Task HandleAsync(TEvent message, CancellationToken cancellationToken) =>
Task.CompletedTask;
}
// Generated: services.TryAddSingleton(
// typeof(IEventHandler<>), typeof(GenericAuditEventHandler<>));
Note:
[PulseHandler]on an open-generic class produces a PULSE004 error — use[PulseGenericHandler]instead when you need a true open-generic DI registration.
Supported Handler Interfaces
| Interface | Description |
|---|---|
ICommandHandler<TCommand> |
Void command handler (single type parameter) |
ICommandHandler<TCommand, TResponse> |
Command handler with response |
IQueryHandler<TQuery, TResponse> |
Query handler |
IEventHandler<TEvent> |
Event handler (multiple handlers per event are valid) |
IStreamQueryHandler<TQuery, TResponse> |
Streaming query handler |
Diagnostics
| Id | Severity | Description |
|---|---|---|
| PULSE001 | Error | Type is annotated with [PulseHandler] but does not implement any known Pulse handler interface. |
| PULSE002 | Warning | Multiple [PulseHandler] types implement the same command or query handler contract. Events are excluded — multiple event handlers are valid. |
| PULSE004 | Error | Type annotated with [PulseHandler] is an open generic type and cannot be automatically registered. Use [PulseHandler<TMessage>] for closed registrations or [PulseGenericHandler] for open-generic DI registrations. |
| PULSE005 | Error | The type argument T passed to [PulseHandler<T>] does not implement any known Pulse message interface (ICommand, ICommand<T>, IQuery<T>, IEvent, or IStreamQuery<T>). |
| PULSE006 | Error | A closed registration for the given message type cannot be constructed because the handler does not implement a compatible handler interface or not all type parameters can be inferred from the message type. |
Requirements
- .NET 8.0, .NET 9.0, or .NET 10.0
NetEvolve.Pulse.Attributespackage for the[PulseHandler]attributeNetEvolve.Pulse.Extensibilitypackage for handler interfaces
Related Packages
- NetEvolve.Pulse.Attributes -
[PulseHandler]attribute andPulseServiceLifetimeenum - NetEvolve.Pulse - Core CQRS mediator
- NetEvolve.Pulse.Extensibility - Handler and interceptor contracts
Documentation
For complete documentation, please visit the official documentation.
Contributing
Contributions are welcome! Please read the Contributing Guidelines before submitting a pull request.
Support
- Issues: Report bugs or request features on GitHub Issues
- Documentation: Read the full documentation at https://github.com/dailydevops/pulse
License
This project is licensed under the MIT License - see the LICENSE file for details.
Made with ❤️ by the NetEvolve Team
Learn more about Target Frameworks and .NET Standard.
This package has no dependencies.
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 |
|---|---|---|
| 0.67.36 | 105 | 5/10/2026 |