Skip to content

Commit ce4b019

Browse files
DavidBoikedanielmarbachandreasohlund
authored
Explicit AddHandler registration API (#7439)
* Add IHandleMessages and config extension * Make RegisterHandler generic * Move IsMessageHandler * Don't deprecate RegisterHandler(Type) with error, use reflection to call generic * Remove separate timeout handler registration method * Replace GetMessageTypesBeingHandledBy implementation with more efficient one that doesn't use MakeGenericType (which is not AOT friendly) * Bring the generics further down * Change runtime check for injecting IMessageSession into compile-time analyzer * Handler creation is ugly but might work * Error in nullability test was excluding types with get-only properties that did have nullability enabled * Not needed due to the new analyzer * Can have IHandleMessages & IHandleTimeouts of same type * Registration of both handler and timeout only when necessary, using reflection but I don't think anything AOT-averse * Also not needed due to new analyzer * This is wrong, this is done at a level up - at this level it could be both timeout/handler * Parameterless IHandleTimeouts doesn't need to exist with the updated implementation * RegisterHandler interceptor * Made the first stage very memoizable * Memoizable at all 3 levels * Ability to output stage results * Create method name from stable hash of file path + source span * Generator test will automatically account for platform-unique data on interceptor attributes * Remove test for unsupported handler registration in container * Hash only handler type and dedupe calls * Change RegisterHandler to AddHandler * See if we can achieve something different * Strong typing through * Simplify * Deprecate the removed MessageHandler constructor with error * Easier to read without one inverted, and must be able to register for Handle and Timeout * Fix tests * Split add methods for handler and timeout with stronger generic constraints * Fix interceptor with longer method name * Add saga to interceptor test * Just messing with the spacing/indent in the tests * Fix after rebase * Approve * Variable naming * Sealing * Variable naming * Sealed * Rename to factories for now * Cleanup test * Add multi interface test * Correctly cache factory for now * Faster casting * Dedup * Add additional test * Fix and use slight redundancy for better clarity * Use built-in API * Move handler related stuff * Use non-cryptographic hash * Fix spacin * Cleanup * InterceptDetails is too large to be a struct * Simplify generator * Remove leftover struct * Remove using * Stable ordering of method names * Specify types explicitely to avoid nullable ref types * Fast path first * Simplify slightly * Switch to symbol based analysis (#7483) * Switch to symbol based analysis * Static delegate * No need for generics * Use GetTypeSymbolOrDefault * Be more defensive and avoid closure * Inline directly * Cleanup using --------- Co-authored-by: Daniel Marbach <danielmarbach@users.noreply.github.com> * Extracing non-cryptographic hash that will be useful in other source generators as well * Span based * For loop * Manual unrolling and unsafe BenchmarkDotNet v0.13.8, macOS 26.1 (25B78) [Darwin 25.1.0] Apple M4 Max, 1 CPU, 16 logical and 16 physical cores .NET SDK 10.0.100 [Host] : .NET 10.0.0 (10.0.25.52411), Arm64 RyuJIT AdvSIMD DefaultJob : .NET 10.0.0 (10.0.25.52411), Arm64 RyuJIT AdvSIMD | Method | Mean | Error | StdDev | Ratio | RatioSD | Allocated | Alloc Ratio | |----------------- |---------:|----------:|----------:|------:|--------:|----------:|------------:| | HashBaseline | 8.429 ns | 0.1537 ns | 0.1438 ns | 1.00 | 0.00 | - | NA | | HashUnroll | 7.728 ns | 0.0663 ns | 0.0620 ns | 0.92 | 0.02 | - | NA | | HashUnsafe | 8.213 ns | 0.1716 ns | 0.1605 ns | 0.97 | 0.03 | - | NA | | HashUnsafeUnroll | 7.311 ns | 0.0792 ns | 0.0702 ns | 0.87 | 0.02 | - | NA | * Use AddHandler api to register handlers in ATTs (#7482) * Use AddHandler api to register handlers in ATTs * Test cleanup * Fix conventions in test * Test cleanup * Use asm qualified name * Test cleanup * Naive fix to the handler ordering * Test cleanup * Register messages handled by the endpoint as messages * Rollback unneded test change * Rollback unneded test change * Uneeded test change * Cleanup * Remove usings * Use AddHandler to control handler ordering * Revert handler registry changes * Switch to add handler instead of ExecuteTheseHandlersFirst * Initialize handler registry earlier * Obsolete ExecuteTheseHandlersFirst with error * Approvals * Move diagnostic entry to a better place * Cleanup test * Test cleanup * Remove handler detection code from the MessageMetadataRegistry * Refactor * Add test that ensures that we can types for messages * Cleanup * Add test that scans handlers * Make message public * Simplify * Move obsoletes --------- Co-authored-by: Daniel Marbach <daniel.marbach@openplace.net> Co-authored-by: Daniel Marbach <danielmarbach@users.noreply.github.com> * Fix after rebase * Get rid of scanning in ATTs * Cleanup att server * Remove uneeded config * Better test name * Remove EditorBrowsable attributes on AddMessage/TimeoutHandlerForMessage methods --------- Co-authored-by: Daniel Marbach <danielmarbach@users.noreply.github.com> Co-authored-by: Andreas Öhlund <andreas.ohlund@particular.net> Co-authored-by: Daniel Marbach <daniel.marbach@openplace.net>
1 parent c120b15 commit ce4b019

File tree

66 files changed

+1478
-927
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

66 files changed

+1478
-927
lines changed

src/NServiceBus.AcceptanceTesting/Customization/EndpointConfigurationExtensions.cs

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ namespace NServiceBus.AcceptanceTesting.Customization;
44
using System.Collections.Generic;
55
using System.Linq;
66
using System.Reflection;
7+
using Hosting.Helpers;
78
using Sagas;
89
using Support;
910

@@ -22,15 +23,25 @@ public static class EndpointConfigurationExtensions
2223
public static void ScanTypesForTest(this EndpointConfiguration config,
2324
EndpointCustomizationConfiguration customizationConfiguration)
2425
{
25-
var typesToIncludeInScanning = GetNestedTypeRecursive(customizationConfiguration.BuilderType.DeclaringType, customizationConfiguration.BuilderType)
26-
.Where(t => t.IsAssignableTo(typeof(IHandleMessages))
27-
|| t.IsAssignableTo(typeof(IFinder))
26+
var testTypes = GetNestedTypeRecursive(customizationConfiguration.BuilderType.DeclaringType, customizationConfiguration.BuilderType).ToList();
27+
28+
var typesToIncludeInScanning = testTypes
29+
.Where(t => t.IsAssignableTo(typeof(IFinder))
2830
|| t.IsAssignableTo(typeof(IHandleSagaNotFound))
2931
|| t.IsAssignableTo(typeof(Saga)))
3032
.Union(customizationConfiguration.TypesToInclude);
3133

3234
config.TypesToIncludeInScan(typesToIncludeInScanning);
3335

36+
//auto-register handlers for now
37+
if (customizationConfiguration.AutoRegisterHandlers)
38+
{
39+
foreach (var messageHandler in testTypes.Where(t => t.IsAssignableTo(typeof(IHandleMessages))))
40+
{
41+
AddHandlerWithReflection(messageHandler, config);
42+
}
43+
}
44+
3445
IEnumerable<Type> GetNestedTypeRecursive(Type rootType, Type builderType)
3546
{
3647
if (rootType == null)
@@ -77,4 +88,10 @@ public static void EnforcePublisherMetadataRegistration(this EndpointConfigurati
7788
config.Pipeline.Register(new EnforceSubscriptionPublisherMetadataBehavior(endpointName, publisherMetadata),
7889
"Enforces all subscribed events have corresponding mappings in the PublisherMetadata");
7990
}
91+
92+
static void AddHandlerWithReflection(Type handlerType, EndpointConfiguration endpointConfiguration) =>
93+
typeof(MessageHandlerRegistrationExtensions)
94+
.GetMethod("AddHandler", BindingFlags.Public | BindingFlags.Static)!
95+
.MakeGenericMethod(handlerType)
96+
.Invoke(null, [endpointConfiguration]);
8097
}

src/NServiceBus.AcceptanceTesting/EndpointConfigurationBuilder.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,4 +115,11 @@ public EndpointConfigurationBuilder IncludeType<T>()
115115

116116
return this;
117117
}
118+
119+
public EndpointConfigurationBuilder DoNotAutoRegisterHandlers()
120+
{
121+
configuration.AutoRegisterHandlers = false;
122+
123+
return this;
124+
}
118125
}

src/NServiceBus.AcceptanceTesting/Support/EndpointCustomizationConfiguration.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,6 @@ public string EndpointName
2525
public string CustomEndpointName { get; set; }
2626

2727
public bool DisableStartupDiagnostics { get; set; } = true;
28+
29+
public bool AutoRegisterHandlers { get; set; } = true;
2830
}

src/NServiceBus.AcceptanceTesting/Support/IEndpointSetupTemplate.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,5 @@
55

66
public interface IEndpointSetupTemplate
77
{
8-
Task<EndpointConfiguration> GetConfiguration(RunDescriptor runDescriptor, EndpointCustomizationConfiguration endpointConfiguration, Func<EndpointConfiguration, Task> configurationBuilderCustomization);
8+
Task<EndpointConfiguration> GetConfiguration(RunDescriptor runDescriptor, EndpointCustomizationConfiguration endpointCustomizationConfiguration, Func<EndpointConfiguration, Task> configurationBuilderCustomization);
99
}

src/NServiceBus.AcceptanceTests/Core/BestPractices/When_injecting_message_session_into_handlers.cs

Lines changed: 0 additions & 44 deletions
This file was deleted.

src/NServiceBus.AcceptanceTests/Core/Conventions/When_receiving_non_matching_message_with_handler.cs

Lines changed: 4 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -27,36 +27,21 @@ public class Context : ScenarioContext
2727

2828
public class Sender : EndpointConfigurationBuilder
2929
{
30-
public Sender()
31-
{
32-
EndpointSetup<DefaultServer>(c =>
33-
{
34-
c.ConfigureRouting().RouteToEndpoint(typeof(NonMatchingMessageWithHandler), typeof(Receiver));
35-
});
36-
}
30+
public Sender() =>
31+
EndpointSetup<DefaultServer>(c => c.ConfigureRouting().RouteToEndpoint(typeof(NonMatchingMessageWithHandler), typeof(Receiver)));
3732
}
3833

3934
public class Receiver : EndpointConfigurationBuilder
4035
{
41-
public Receiver()
42-
{
43-
EndpointSetup<DefaultServer>(c => c.Conventions().DefiningMessagesAs(t => false));
44-
}
36+
public Receiver() => EndpointSetup<DefaultServer>(c => c.Conventions().DefiningMessagesAs(t => false));
4537

46-
class MyHandler : IHandleMessages<NonMatchingMessageWithHandler>
38+
class MyHandler(Context testContext) : IHandleMessages<NonMatchingMessageWithHandler>
4739
{
48-
public MyHandler(Context context)
49-
{
50-
testContext = context;
51-
}
52-
5340
public Task Handle(NonMatchingMessageWithHandler message, IMessageHandlerContext context)
5441
{
5542
testContext.GotTheMessage = true;
5643
return Task.CompletedTask;
5744
}
58-
59-
readonly Context testContext;
6045
}
6146
}
6247

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
namespace NServiceBus.AcceptanceTests.Core.Hosting;
2+
3+
using System.Threading.Tasks;
4+
using AcceptanceTesting;
5+
using EndpointTemplates;
6+
using NUnit.Framework;
7+
8+
public class When_scanning_is_enabled : NServiceBusAcceptanceTest
9+
{
10+
[Test]
11+
public async Task Should_discover_handlers()
12+
{
13+
var context = await Scenario.Define<Context>()
14+
.WithEndpoint<MyEndpoint>(c => c
15+
.When(b => b.SendLocal(new MyMessage())))
16+
.Done(c => c.GotTheMessage)
17+
.Run();
18+
19+
Assert.That(context.GotTheMessage, Is.True);
20+
}
21+
22+
class Context : ScenarioContext
23+
{
24+
public bool GotTheMessage { get; set; }
25+
}
26+
27+
class MyEndpoint : EndpointConfigurationBuilder
28+
{
29+
public MyEndpoint() =>
30+
EndpointSetup<DefaultServer>()
31+
.DoNotAutoRegisterHandlers()
32+
.IncludeType<MyMessageHandler>();
33+
34+
public class MyMessageHandler(Context testContext) : IHandleMessages<MyMessage>
35+
{
36+
public Task Handle(MyMessage message, IMessageHandlerContext context)
37+
{
38+
testContext.GotTheMessage = true;
39+
return Task.CompletedTask;
40+
}
41+
}
42+
}
43+
44+
public class MyMessage : IMessage;
45+
}

src/NServiceBus.AcceptanceTests/Core/Mutators/When_using_outgoing_tm_mutator.cs

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,12 @@ public class Context : ScenarioContext
3333

3434
public class Endpoint : EndpointConfigurationBuilder
3535
{
36-
public Endpoint()
37-
{
36+
public Endpoint() =>
3837
EndpointSetup<DefaultServer>(c =>
3938
{
4039
c.UseSerialization<XmlSerializer>();
4140
c.RegisterMessageMutator(new MyTransportMessageMutator());
4241
});
43-
}
4442

4543
class MyTransportMessageMutator : IMutateOutgoingTransportMessages
4644
{
@@ -53,22 +51,15 @@ public Task MutateOutgoing(MutateOutgoingTransportMessageContext context)
5351
}
5452
}
5553

56-
class MessageToBeMutatedHandler : IHandleMessages<MessageThatMutatorChangesTo>
54+
class MessageToBeMutatedHandler(Context testContext) : IHandleMessages<MessageThatMutatorChangesTo>
5755
{
58-
public MessageToBeMutatedHandler(Context testContext)
59-
{
60-
this.testContext = testContext;
61-
}
62-
6356
public Task Handle(MessageThatMutatorChangesTo message, IMessageHandlerContext context)
6457
{
6558
testContext.CanAddHeaders = context.MessageHeaders.ContainsKey("HeaderSetByMutator");
6659
testContext.MutatedPropertyValue = message.SomeProperty;
6760
testContext.MessageProcessed = true;
6861
return Task.CompletedTask;
6962
}
70-
71-
Context testContext;
7263
}
7364
}
7465

src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/OpenTelemetryEnabledEndpoint.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@
88
public class OpenTelemetryEnabledEndpoint : DefaultServer
99
{
1010
public override Task<EndpointConfiguration> GetConfiguration(RunDescriptor runDescriptor,
11-
EndpointCustomizationConfiguration endpointConfiguration,
11+
EndpointCustomizationConfiguration endpointCustomizationConfiguration,
1212
Func<EndpointConfiguration, Task> configurationBuilderCustomization) =>
13-
base.GetConfiguration(runDescriptor, endpointConfiguration, configuration =>
13+
base.GetConfiguration(runDescriptor, endpointCustomizationConfiguration, configuration =>
1414
{
1515
configuration.EnableOpenTelemetry();
1616
return configurationBuilderCustomization(configuration);

src/NServiceBus.AcceptanceTests/Core/Pipeline/When_aborting_the_behavior_chain.cs

Lines changed: 8 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -28,24 +28,16 @@ public class Context : ScenarioContext
2828
public bool SecondHandlerInvoked { get; set; }
2929
}
3030

31-
public class SomeMessage : IMessage
32-
{
33-
}
34-
3531
public class MyEndpoint : EndpointConfigurationBuilder
3632
{
37-
public MyEndpoint()
33+
public MyEndpoint() => EndpointSetup<DefaultServer>(c =>
3834
{
39-
EndpointSetup<DefaultServer>(c => c.ExecuteTheseHandlersFirst(typeof(FirstHandler), typeof(SecondHandler)));
40-
}
35+
c.AddHandler<FirstHandler>();
36+
c.AddHandler<SecondHandler>();
37+
});
4138

42-
class FirstHandler : IHandleMessages<SomeMessage>
39+
class FirstHandler(Context testContext) : IHandleMessages<SomeMessage>
4340
{
44-
public FirstHandler(Context context)
45-
{
46-
testContext = context;
47-
}
48-
4941
public Task Handle(SomeMessage message, IMessageHandlerContext context)
5042
{
5143
testContext.FirstHandlerInvoked = true;
@@ -54,27 +46,18 @@ public Task Handle(SomeMessage message, IMessageHandlerContext context)
5446

5547
return Task.CompletedTask;
5648
}
57-
58-
Context testContext;
59-
6049
}
6150

62-
class SecondHandler : IHandleMessages<SomeMessage>
51+
class SecondHandler(Context testContext) : IHandleMessages<SomeMessage>
6352
{
64-
public SecondHandler(Context context)
65-
{
66-
testContext = context;
67-
}
68-
6953
public Task Handle(SomeMessage message, IMessageHandlerContext context)
7054
{
7155
testContext.SecondHandlerInvoked = true;
7256

7357
return Task.CompletedTask;
7458
}
75-
76-
Context testContext;
77-
7859
}
7960
}
61+
62+
public class SomeMessage : IMessage;
8063
}

0 commit comments

Comments
 (0)