Conversation
There was a problem hiding this comment.
Pull request overview
This PR completes first-class support for interface-less handlers in the generated/intercepted registration paths, while explicitly failing fast for interface-less handlers registered via the reflection-based runtime path. It also preserves original handler identity for deduplication and OpenTelemetry tagging, and updates analyzers/fixers accordingly.
Changes:
- Allow
AddHandler<T>()registration APIs to accept interface-less handlers at compile time, with a runtime guard for the reflection-based path. - Extend source generation/interception to emit adapter handler types and register them while deduplicating and reporting the original handler type for diagnostics/tracing.
- Update analyzers/fixers and expand unit/approval/acceptance coverage for interface-less handler shapes and CancellationToken uplift behavior.
Reviewed changes
Copilot reviewed 36 out of 37 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| src/NServiceBus.Core/Unicast/MessageHandlerRegistry.cs | Removes generic constraint from AddHandler<T>(), adds reflection-path guard, and preserves original handler identity for dedup + tracing. |
| src/NServiceBus.Core/Unicast/Config/MessageHandlerRegistrationExtensions.cs | Updates EndpointConfiguration.AddHandler<T>() signature to allow interface-less handlers when interception/generation is active. |
| src/NServiceBus.Core/Pipeline/Incoming/MessageHandler.cs | Minor doc comment punctuation tweak. |
| src/NServiceBus.Core.Tests/Handlers/MessageHandlerRegistryTests.cs | Adds tests for rejecting interface-less handlers on reflection path and adapter dedup by original type. |
| src/NServiceBus.Core.Tests/ApprovalFiles/APIApprovals.ApproveNServiceBus.approved.txt | Updates approved public API surface for signature/overload changes. |
| src/NServiceBus.Core.Analyzer/SupressionIds.cs | Adds suppression id for interface-less handler CancellationToken binding scenario. |
| src/NServiceBus.Core.Analyzer/Handlers/InterfaceLessHandlerCancellationTokenSuppressor.cs | New diagnostic suppressor for CancellationToken parameters bound from handler context. |
| src/NServiceBus.Core.Analyzer/Handlers/InterfaceLessHandlerCancellationTokenBinding.cs | New helper to identify interface-less handlers with bound CancellationToken parameters. |
| src/NServiceBus.Core.Analyzer/Handlers/Handlers.Parser.cs | Parses interface-less handler method shapes, injected params, and generates deterministic adapter names. |
| src/NServiceBus.Core.Analyzer/Handlers/Handlers.Emitter.cs | Emits registrations for interface-less adapters and generates adapter types implementing IHandleMessages<T>. |
| src/NServiceBus.Core.Analyzer/Handlers/HandlerAttributeAnalyzer.cs | Extends HandlerAttribute analysis to support interface-less handlers and detects mixed-style handlers. |
| src/NServiceBus.Core.Analyzer/Handlers/AddHandlerInterceptor.Emitter.cs | Filters mixed-style handlers and emits adapter types for intercepted AddHandler<T>() calls. |
| src/NServiceBus.Core.Analyzer/Handlers/AddHandlerGenerator.Emitter.cs | Filters mixed-style handlers and emits adapter types alongside generated handler registries. |
| src/NServiceBus.Core.Analyzer/ForwardCancellationTokenAnalyzer.cs | Skips CancellationToken forwarding diagnostics for interface-less handlers with bound CancellationToken params. |
| src/NServiceBus.Core.Analyzer/DiagnosticIds.cs | Introduces diagnostic IDs for interface-less handler attribute scenarios and mixed-style errors. |
| src/NServiceBus.Core.Analyzer.Tests.Roslyn5/Handlers/HandlerAttributeFixerTests.cs | Adds fixer coverage for adding/moving [Handler] in interface-less and hybrid inheritance cases. |
| src/NServiceBus.Core.Analyzer.Tests.Roslyn5/Handlers/HandlerAttributeAnalyzerTests.cs | Adds analyzer coverage for interface-less handlers, ValueTask exclusion, and mixed-style detection. |
| src/NServiceBus.Core.Analyzer.Tests.Roslyn5/Handlers/AddHandlerInterceptorTests.cs | Adds approval-based coverage for interface-less interception, ctor/method injection collisions, inheritance, and mixed-style filtering. |
| src/NServiceBus.Core.Analyzer.Tests.Roslyn5/Handlers/AddHandlerGeneratorTests.cs | Adds approval-based coverage for interface-less generated registrations and mixed-style filtering. |
| src/NServiceBus.Core.Analyzer.Tests.Roslyn5/ForwardCancellationToken/ForwardCancellationTokenTests.cs | Adds tests ensuring no diagnostic when interface-less handlers expose a bound CancellationToken. |
| src/NServiceBus.Core.Analyzer.Tests.Roslyn5/ApprovalFiles/AddHandlerInterceptorTests.MixedStyleHandlerProducesNoInterception.approved.txt | Updates/introduces approved output for mixed-style handler interception behavior. |
| src/NServiceBus.Core.Analyzer.Tests.Roslyn5/ApprovalFiles/AddHandlerInterceptorTests.InterfaceLessHandlersCtorAndParameterInjection.approved.txt | Approved output for adapter emission with ctor+method injection. |
| src/NServiceBus.Core.Analyzer.Tests.Roslyn5/ApprovalFiles/AddHandlerInterceptorTests.InterfaceLessHandlers.approved.txt | Approved output for multiple interface-less handlers (static + instance). |
| src/NServiceBus.Core.Analyzer.Tests.Roslyn5/ApprovalFiles/AddHandlerInterceptorTests.InterfaceLessHandlerReturningValueTaskProducesNoInterception.approved.txt | Approved output verifying ValueTask handlers don’t intercept. |
| src/NServiceBus.Core.Analyzer.Tests.Roslyn5/ApprovalFiles/AddHandlerInterceptorTests.InterfaceLessHandlerInheritedFromBaseClass.approved.txt | Approved output for inherited interface-less Handle(...) methods. |
| src/NServiceBus.Core.Analyzer.Tests.Roslyn5/ApprovalFiles/AddHandlerInterceptorTests.InterfaceLessHandlerCtorAndMethodInjectionWithSameParameterName.approved.txt | Approved output for DI name-collision handling in adapters. |
| src/NServiceBus.Core.Analyzer.Tests.Roslyn5/ApprovalFiles/AddHandlerInterceptorTests.InterfaceLessHandler.approved.txt | Approved output for single interface-less handler interception. |
| src/NServiceBus.Core.Analyzer.Tests.Roslyn5/ApprovalFiles/AddHandlerGeneratorTests.MixedStyleHandlerProducesNoRegistration.approved.txt | Approved output confirming mixed-style handlers generate no registrations. |
| src/NServiceBus.Core.Analyzer.Tests.Roslyn5/ApprovalFiles/AddHandlerGeneratorTests.InterfaceLessHandlersCtorAndParameterInjection.approved.txt | Approved output for generated registry methods + adapter types. |
| src/NServiceBus.Core.Analyzer.Tests.Roslyn5/ApprovalFiles/AddHandlerGeneratorTests.InterfaceLessHandlers.approved.txt | Approved output for multiple interface-less handlers in generated registrations. |
| src/NServiceBus.Core.Analyzer.Tests.Roslyn5/ApprovalFiles/AddHandlerGeneratorTests.InterfaceLessHandlerReturningValueTaskProducesNoRegistration.approved.txt | Approved output verifying ValueTask handlers generate no registration. |
| src/NServiceBus.Core.Analyzer.Tests.Roslyn5/ApprovalFiles/AddHandlerGeneratorTests.InterfaceLessHandlerInheritedFromBaseClass.approved.txt | Approved output for inherited interface-less handler generation. |
| src/NServiceBus.Core.Analyzer.Tests.Roslyn5/ApprovalFiles/AddHandlerGeneratorTests.InterfaceLessHandlerCtorAndMethodInjectionWithSameParameterName.approved.txt | Approved output for generated adapter handling ctor/method param name collisions. |
| src/NServiceBus.Core.Analyzer.Fixes/Handlers/HandlerAttributeFixer.cs | Extends fixer logic to move/add [Handler] for interface-less handlers and relevant hybrids. |
| src/NServiceBus.AcceptanceTests/Registrations/Handlers/When_registering_interface_less_handlers.cs | New acceptance test covering interface-less registration, ctor/method DI, and CancellationToken uplift. |
| src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/Traces/When_processing_message_with_multiple_handlers.cs | Updates tracing acceptance test to use NonScanning + explicit registrations, and makes one handler interface-less. |
| src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/Traces/When_processing_message_with_interface_less_handler.cs | New acceptance test verifying OpenTelemetry tag reports original interface-less handler type. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
src/NServiceBus.Core.Analyzer/Handlers/HandlerAttributeAnalyzer.cs
Outdated
Show resolved
Hide resolved
src/NServiceBus.Core.Analyzer/Handlers/HandlerAttributeAnalyzer.cs
Outdated
Show resolved
Hide resolved
WilliamBZA
left a comment
There was a problem hiding this comment.
This change will mean we can change many of our docs samples and snippets way shorter and more concise. Can we track that in a follow-up issue?
| [Handler] | ||
| public class ParameterDiHandler | ||
| { | ||
| public static Task Handle(MyMessage message, IMessageHandlerContext context, Context testContext) |
...eneratorTests.ConventionBasedHandlerCtorAndMethodInjectionWithSameParameterName.approved.txt
Outdated
Show resolved
Hide resolved
src/NServiceBus.Core.Analyzer/Handlers/HandlerAttributeAnalyzer.cs
Outdated
Show resolved
Hide resolved
src/NServiceBus.Core.Analyzer/Handlers/InterfaceLessHandlerCancellationTokenSuppressor.cs
Outdated
Show resolved
Hide resolved
...eneratorTests.ConventionBasedHandlerCtorAndMethodInjectionWithSameParameterName.approved.txt
Outdated
Show resolved
Hide resolved
…ion in interceptor
…ctor and parameter injection
…ethod parameter names
…om base classes with source generation
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
…or interface-less handlers
…dler" across analyzers, fixers, tests, and related files.
…hod-level injections in handlers
Add `AddConventionBasedHandleMethodFixer` and `AddIHandleMessagesInterfaceFixer` to scaffold handlers for convention-based patterns. Updated relevant tests and adjusted `HandlerAttributeFixer` to delegate empty shell cases.
… from test cases
…ptsLocationAttribute`
…pe` using `GenericName` for improved clarity and consistency.
…ixer` and update tests to validate method scaffolding
…o ensure braces consistency; update tests for short class declarations.
…` to ensure braces consistency; add test for short class declarations.
…reuse across analyzers and fixers.
…sync methods in `ConventionBasedHandlerHelper`
…h private constructors
…logic in `ConventionBasedHandlerHelper`
…tralize marker interface handling logic.
…ructors; update tests and utilities
…duplication logic; add EditorBrowsable attribute to `AddMessageHandlerForMessage`.
…d handlers; update analyzer and tests
…nsolidate logic into `ConstructorAnalysis` struct; update diagnostics accordingly
…ved type discovery and propagation across handlers and sagas
This PR completes support for convention-based handlers across registration, source generation, analyzers, and OpenTelemetry tracing.
The result is that convention-based handlers are now treated as first-class handler definitions in the supported generated/intercepted registration paths, while the runtime reflection-based registration path remains intentionally limited and fails fast when used outside those supported scenarios.
convention-based handlers can be registered through generated/intercepted APIs
AddHandler<T>()now accepts convention-based handlers at compile time.When source generation/interception is active, convention-based handlers can be registered through:
config.AddHandler<THandler>()config.Handlers...Add...()methodsconfig.Handlers...AddAll()methodsThis support includes:
context.CancellationTokenHandle(...)methods on base classes for pure convention-based handlersThe reflection-based
MessageHandlerRegistry.AddHandler<T>()path still only supports types implementingIHandleMessagesand throws a clear exception for non-intercepted convention-based handlers.Generated registrations preserve original handler identity
Generated registrations and handler deduplication use the original handler type for convention-based handlers.
This means:
convention-based handler method shape is now explicit
A method is treated as a supported convention-based handler only when it matches the supported shape:
public Handle(...)IMessageHandlerContextTaskValueTaskis intentionally not treated as a supported convention-based handler return type.Generated adapters handle DI collisions correctly
Generated convention-based adapters keep constructor injection and method injection distinct even when the source parameter names collide.
A handler like:
Ctor(IServiceA service)Handle(..., IServiceB service)generates valid code and passes the correct dependency to each call site.
Analyzer and fixer behavior
HandlerAttribute analysis now supports both handler styles
HandlerAttributeAnalyzernow correctly recognizes pure convention-based handlers and their hierarchies.That means:
[Handler]is reported for valid convention-based handlers[Handler]is reported for convention-based abstract basesHandle(...)method are treated as convention-based handlers for attribute analysis/fixingHandlerAttribute diagnostics are split by handler style
The handler-attribute diagnostics are now separate for interface-based and convention-based handlers.
Interface-based handlers use:
NSB0022missing[Handler]NSB0023misplaced[Handler]convention-based handlers use:
NSB0034missing[Handler]NSB0035misplaced[Handler]This allows future policy and severity differences between the two styles without changing the analyzer model again.
The fixer works for both styles
HandlerAttributeFixernow supports both interface-based and convention-based handlers.It can:
[Handler]to concrete leaf handlers[Handler]from abstract bases to the concrete handler typeHybrid inheritance behavior is defined
A derived type that implements
IHandleMessages<T>is treated as interface-based even if a base class provides a virtualHandle(...)method.That prevents accidental reclassification of interface-based handlers as convention-based or mixed-style.
OpenTelemetry
OpenTelemetry handler spans now report the correct handler type for convention-based handlers.
In particular, the
nservicebus.handler.handler_typetag uses the original convention-based handler type, not the generated adapter type.Acceptance coverage now verifies this in:
Scope and boundaries
This PR does not broaden the runtime assembly-scanning/reflection model to fully support convention-based handlers.
The intended boundary remains: