Describe the bug
Description
We're building a Feature that relies on the results of assembly scanning to set up the TopicTopology for the Azure Service Bus transport. However, we are running into an issue where our messagebus contracts assemblies are not being scanned properly. This results in exceptions being thrown down the line due to ThrowIfUnmappedEventTypes = true.
Our contracts libs use custom defined unobtrusive mode marker interfaces, with a convention configured in a separate assembly. As a result, our contracts don't have any reference to NServiceBus.Core or NServiceBus.MessageInterfaces.
As an aside, our unobtrusive mode interfaces narrowly predate the NServiceBus.MessageInterfaces package, but the biggest reason we haven't switched to that package (yet) is the fact that TimeToBeReceivedAttribute is still defined in NServiceBus. We pulled its functionality to our unobtrusive mode package.
Our EndpointConfiguration then sets up a feature, which resolves MessageMetadataRegistry from the settings and loops over GetAllMessages().
Expected behavior
The contracts libs are scanned and the contained messages are registered in MessageMetadataRegistry.
OR
The assembly is listed in .diagnostics/<endpointname>-configuration.txt output with the reason why it was skipped (does not reference NServiceBus.Core or NServiceBus.MessageInterfaces.
Actual behavior
The messages are not in the registry. The containing assembly is not mentioned in the diagnostics log.
Versions
Steps to reproduce
- Project `MessageAbstractions:
MessageAbstractions.csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
IEvent.cs
namespace MessageAbstractions;
public interface IEvent;
- Project
MessageContracts:
MessageContracts.csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\MessageAbstractions\MessageAbstractions.csproj" />
</ItemGroup>
</Project>
MyEvent.cs
using MessageAbstractions;
namespace MessageContracts;
public class MyEvent : IEvent;
- Project
AssemblyScanningRepro:
AssemblyScanningRepro.csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.10" />
<PackageReference Include="NServiceBus" Version="10.2.7" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MessageAbstractions\MessageAbstractions.csproj" />
<ProjectReference Include="..\MessageContracts\MessageContracts.csproj" />
</ItemGroup>
</Project>
Program.cs
using AssemblyScanningRepro;
using Microsoft.Extensions.Hosting;
var builder = Host.CreateApplicationBuilder(args);
var endpointConfiguration = new EndpointConfiguration("AssemblyScanningRepro");
endpointConfiguration.Conventions().DefiningEventsAs(type => typeof(MessageAbstractions.IEvent).IsAssignableTo(type));
endpointConfiguration.UseTransport<LearningTransport>();
endpointConfiguration.UseSerialization<SystemJsonSerializer>();
endpointConfiguration.EnableFeature<ConventionsReadingFeature>();
builder.Services.AddNServiceBusEndpoint(endpointConfiguration);
var host = builder.Build();
await host.RunAsync();
ConventionsReadingFeature.cs
using NServiceBus.Features;
using NServiceBus.Unicast.Messages;
namespace AssemblyScanningRepro;
internal class ConventionsReadingFeature : Feature {
protected override void Setup(FeatureConfigurationContext context) {
var registry = context.Settings.Get<MessageMetadataRegistry>();
var messages = registry.GetAllMessages();
if (messages.Length == 0) {
Console.WriteLine("MyEvent was not found!");
}
}
}
Relevant log output
Additional Information
Workarounds
A quick-fix workaround for us right now is to manually enumerate the types into the MessageMetadataRegistry:
var registry = endpointConfiguration.GetSettings().GetOrCreate<MessageMetadataRegistry>();
registry.RegisterMessageTypes([typeof(Message1), typeof(Message2), ...]);
We could also move our contracts to NServiceBus.MessageInterfaces, but that would still leave the TimeToBeReceivedAttribute wrinkle.
Possible solutions
I'm not quite sure whether this is a bug/oversight or intended behaviour. But in the latter case, I would like to suggest a few improvements in visibility of this behaviour:
- Neither the docs on Unobtrusive Mode nor the docs on Assembly Scanning, mention this incompatibility. Especially the latter might benefit with a mention that only assemblies directly or indirectly referencing
NServiceBus.Core or NServiceBus.MessageInterfaces are actually scanned.
- There is currently no diagnostic output on assemblies where
bool ScanAssembly(Assembly assembly, Dictionary<AssemblyIdentity, bool> processed) returns false. Scanned assemblies are mentioned, excluded Particular or .Net runtime assemblies are mentioned, but the remainder are excluded.
As a potential fix for our problem:
- The
AssemblyScanner configuration currently already allows us to exclude certain assemblies from being scanned. Would it make sense to add a configuration for including assemblies into the dictionary that seeds processedAssemblies with true values?. Then we could bundle that into the extension methods setting up the conventions related to our contracts interfaces.
Additional information
...
Describe the bug
Description
We're building a
Featurethat relies on the results of assembly scanning to set up theTopicTopologyfor the Azure Service Bus transport. However, we are running into an issue where our messagebus contracts assemblies are not being scanned properly. This results in exceptions being thrown down the line due toThrowIfUnmappedEventTypes = true.Our contracts libs use custom defined unobtrusive mode marker interfaces, with a convention configured in a separate assembly. As a result, our contracts don't have any reference to
NServiceBus.CoreorNServiceBus.MessageInterfaces.As an aside, our unobtrusive mode interfaces narrowly predate the
NServiceBus.MessageInterfacespackage, but the biggest reason we haven't switched to that package (yet) is the fact thatTimeToBeReceivedAttributeis still defined inNServiceBus. We pulled its functionality to our unobtrusive mode package.Our
EndpointConfigurationthen sets up a feature, which resolvesMessageMetadataRegistryfrom the settings and loops overGetAllMessages().Expected behavior
The contracts libs are scanned and the contained messages are registered in
MessageMetadataRegistry.OR
The assembly is listed in
.diagnostics/<endpointname>-configuration.txtoutput with the reason why it was skipped (does not referenceNServiceBus.CoreorNServiceBus.MessageInterfaces.Actual behavior
The messages are not in the registry. The containing assembly is not mentioned in the diagnostics log.
Versions
NServiceBus10.2.7Steps to reproduce
MessageAbstractions.csproj
IEvent.cs
MessageContracts:MessageContracts.csproj
MyEvent.cs
AssemblyScanningRepro:AssemblyScanningRepro.csproj
Program.cs
ConventionsReadingFeature.cs
Relevant log output
Additional Information
Workarounds
A quick-fix workaround for us right now is to manually enumerate the types into the
MessageMetadataRegistry:We could also move our contracts to
NServiceBus.MessageInterfaces, but that would still leave theTimeToBeReceivedAttributewrinkle.Possible solutions
I'm not quite sure whether this is a bug/oversight or intended behaviour. But in the latter case, I would like to suggest a few improvements in visibility of this behaviour:
NServiceBus.CoreorNServiceBus.MessageInterfacesare actually scanned.bool ScanAssembly(Assembly assembly, Dictionary<AssemblyIdentity, bool> processed)returnsfalse. Scanned assemblies are mentioned, excluded Particular or .Net runtime assemblies are mentioned, but the remainder are excluded.As a potential fix for our problem:
AssemblyScannerconfiguration currently already allows us to exclude certain assemblies from being scanned. Would it make sense to add a configuration for including assemblies into the dictionary that seedsprocessedAssemblieswith true values?. Then we could bundle that into the extension methods setting up the conventions related to our contracts interfaces.Additional information
...