NServiceBus
NServiceBus copied to clipboard
Altering NSB components within a Feature not resolving proper dependency
While trying to work around #6058 we created a Feature to replace the DefaultDataBusSerializer.
internal class BsonDataBusSerializerFeature : Feature
{
public BsonDataBusSerializerFeature()
{
DependsOn<NServiceBus.Features.DataBus>();
EnableByDefault();
}
protected override void Setup(FeatureConfigurationContext context)
{
context.Container.ConfigureComponent<IDataBusSerializer>(_ => new BsonDataBusSerializer(), DependencyLifecycle.SingleInstance);
}
}
We have added a reference to the project containing this feature to three different types of projects.
- .NET Core Console App
- Azure Function App
- ASP.NET Core Web API
Using #1 we can successfully publish our message. #2 will successfully handle the message.
For #3, the Feature is executing Setup when the application starts up, however trying to send a message with a DataBusProperty fails with "BinaryFormatter serialization and deserialization are disabled within this application...".
If we explicitly register the custom serializer like this in Program.cs
var endpointConfiguration = new EndpointConfiguration("API");
endpointConfiguration.RegisterComponents(configur => configur.RegisterSingleton<IDataBusSerializer>(new BsonDataBusSerializer()));
Our web app project will publish the message successfully.
This feels like an issue with NSB with Features and dependency resolution.
@seankearney How are you integrating NServiceBus with the ASP.NET Core Web API host? Are they sharing a container?
Using DependsOn should guarantee that the original DataBus feature Setup is run first, which will register the default serializer in the container. Then the BsonDataBusSerializerFeature.Setup is run which adds a second implementation. It looks like the container is getting the first registration instead of the last.
I'm guessing that scenarios (1) and (2) are using the NServiceBus internal container which is returning the latest registration.
Part of the reason that explicitly registering the serializer works is because the DataBus feature explicitly checks for an existing registration and only adds the default one if there isn't a custom one registered.
@mikeminutillo so despite overriding the component registration from a custom feature by calling
context.Container.ConfigureComponent<IDataBusSerializer>(_ =>
new BsonDataBusSerializer(), DependencyLifecycle.SingleInstance);
The DefaultDataBusSerializer is not replaced. Is there a way to remove the registered default implementation prior to adding a new one from within the Feature's Setup?
Our container abstraction doesn't expose a way to remove a registration but the underlying container might. ConfigureComponent is just an abstraction over whatever container is being used under the covers. What it does with multiple registrations is not under the control of NServiceBus. That's why I was asking about how the endpoint is integrated into the framework and which container is being used.
So in the case of Azure Functions, it's a bit more complicated than just IServiceCollection. NSB adapts a type that is already an adapter for Functions. I don't see any other way of accessing the underlying service collection other than going into nasty reflection to access the member field NSB holds on to.
Azure Functions appears to use a last-registration-in-wins strategy so it works. It's only scenario 3 that is having an issue and that is ASP.NET Core Web API. I would have thought that would be using the default Microsoft container as well. Until I know more about that third (non-working scenario), I can't tell why it might not be working.
Ironically, it appears that Functions also has an issue with this. The only way to address it is to remove the default IDataBusSerializer found in the IServiceCollection passed into the adapter, ServiceCollectionAdapter.
As ServiceCollectionAdapter doesn't expose any way to remove registrations, reflection is required.
In the future, it would be nice to expose IServiceCollection or remove the adapter altogether.
@SeanFeldman another approach could be to use INeedInitialization to hook into the endpoint creation process. This would allow you to register the bson databus serializer before the databus feature is set up. Something like this:
public class ReplaceDefaultDataBusSerializer : INeedInitialization
{
public void Customize(EndpointConfiguration endpointConfiguration)
{
endpointConfiguration.RegisterComponents(components =>
components.RegisterSingleton<IDataBusSerializer>(new BsonDataBusSerializer()));
}
}
This class should get picked up by the type scanner and applied very early in the endpoint creation pipeline.