We have multiple pipeline steps that are added depending on certain criteria. It might happen that the exact same behavior has already been registered (because of some shared logic). Therefore, I wanted to use the RegisterOrReplace hook. My understanding was that it would either replace an existing step of the same identifier, or do its actual registration.
When I do the following:
pipeline.RegisterOrReplace(
stepId: nameof(DemoPipelineStep),
behavior: new DemoPipelineStep(),
description: "test");
// somewhere later on
pipeline.RegisterOrReplace(
stepId: nameof(DemoPipelineStep),
behavior: new DemoPipelineStep(),
description: "test");
This crashes with System.Exception: Step registration with id 'DemoPipelineStep' is already registered for 'Demo.DemoPipelineStep'.
It only works if one of the two registration steps uses the Register hook, the actual order of registration has no impact.
My understanding of RegisterOrReplace would have been that it simply does not matter how often I call RegisterOrReplace with the same step identifier, the last registration that is called would win and do the replacement. Is this intended behavior?
I have drafted a simple example that reproduces the issue. It uses NServiceBus version 9.2.7 and NServiceBus.Extensions.Hosting version 3.0.1.
It assumes we have a step identifier "same-step-id" and then we try to RegisterOrReplace this step twice. It does not matter whether I try to replace it with the same type (DemoPipelineStep and/or DemoPipelineStep2) or not.
using NServiceBus.Pipeline;
namespace PipelineBehavior;
internal static class Program
{
private static async Task Main(string[] args)
{
var builder = Host.CreateApplicationBuilder(args);
var endpointConfiguration = new EndpointConfiguration("SomeEndpoint");
endpointConfiguration.UseSerialization<SystemJsonSerializer>();
var pipeline = endpointConfiguration.Pipeline;
pipeline.RegisterOrReplace(
stepId: "same-step-id",
behavior: new DemoPipelineStep(),
description: "test");
pipeline.RegisterOrReplace(
stepId: "same-step-id",
behavior: new DemoPipelineStep2(),
description: "test");
endpointConfiguration.UseTransport(new LearningTransport());
builder.UseNServiceBus(endpointConfiguration);
var app = builder.Build();
await app.RunAsync();
}
}
internal sealed class DemoPipelineStep : Behavior<IInvokeHandlerContext>
{
public override async Task Invoke(IInvokeHandlerContext context, Func<Task> next)
{
await next().ConfigureAwait(false);
}
}
internal sealed class DemoPipelineStep2 : Behavior<IInvokeHandlerContext>
{
public override async Task Invoke(IInvokeHandlerContext context, Func<Task> next)
{
await next().ConfigureAwait(false);
}
}
System.Exception: Step registration with id 'same-step-id' is already registered for 'PipelineBehavior.DemoPipelineStep'.