From 85313715c898fb79d7304a5d5e8a0de8fe6faf73 Mon Sep 17 00:00:00 2001 From: danielmarbach Date: Wed, 12 Mar 2025 17:54:49 +0100 Subject: [PATCH 1/8] Bump Azure Service Bus --- .../NServiceBus.AzureFunctions.InProcess.ServiceBus.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/NServiceBus.AzureFunctions.InProcess.ServiceBus.csproj b/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/NServiceBus.AzureFunctions.InProcess.ServiceBus.csproj index 7fcfa8a1..733ddb9a 100644 --- a/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/NServiceBus.AzureFunctions.InProcess.ServiceBus.csproj +++ b/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/NServiceBus.AzureFunctions.InProcess.ServiceBus.csproj @@ -15,7 +15,7 @@ - + From 2809824de0c822ef43b8aa4ac87815f6611f55a4 Mon Sep 17 00:00:00 2001 From: danielmarbach Date: Wed, 12 Mar 2025 17:55:06 +0100 Subject: [PATCH 2/8] Add hashing package --- src/ServiceBus.AcceptanceTests/ServiceBus.AcceptanceTests.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ServiceBus.AcceptanceTests/ServiceBus.AcceptanceTests.csproj b/src/ServiceBus.AcceptanceTests/ServiceBus.AcceptanceTests.csproj index afda94e9..0d213340 100644 --- a/src/ServiceBus.AcceptanceTests/ServiceBus.AcceptanceTests.csproj +++ b/src/ServiceBus.AcceptanceTests/ServiceBus.AcceptanceTests.csproj @@ -22,6 +22,7 @@ + From 6538ad393dd39f595f8aa97008bafada0d39b63d Mon Sep 17 00:00:00 2001 From: danielmarbach Date: Wed, 12 Mar 2025 17:55:30 +0100 Subject: [PATCH 3/8] Acceptance test support --- .../AcceptanceTestExtensions.cs | 39 ++++++++ .../DefaultEndpoint.cs | 25 +++-- .../FunctionEndpointComponent.cs | 94 +++++++++---------- 3 files changed, 95 insertions(+), 63 deletions(-) create mode 100644 src/ServiceBus.AcceptanceTests/AcceptanceTestExtensions.cs diff --git a/src/ServiceBus.AcceptanceTests/AcceptanceTestExtensions.cs b/src/ServiceBus.AcceptanceTests/AcceptanceTestExtensions.cs new file mode 100644 index 00000000..e5369c15 --- /dev/null +++ b/src/ServiceBus.AcceptanceTests/AcceptanceTestExtensions.cs @@ -0,0 +1,39 @@ +namespace ServiceBus.Tests; + +using System; +using System.IO.Hashing; +using System.Text; + +public static class AcceptanceTestExtensions +{ + public static string ToTopicName(this Type eventType) => + eventType.FullName.Replace("+", ".").Shorten(maxLength: 260); + + // The idea here is to preserve part of the text and append a non-cryptographic hash to it. + // This way, we can have a deterministic and unique names without harming much the readability. + // The chance of collisions should be very low but definitely not zero. We can always switch to + // using more bits in the hash or even back to a cryptographic hash if needed. + public static string Shorten(this string name, int maxLength = 50) + { + if (name.Length <= maxLength) + { + return name; + } + + var nameBytes = Encoding.UTF8.GetBytes(name); + var hashValue = XxHash32.Hash(nameBytes); + string hashHex = Convert.ToHexString(hashValue); + + int prefixLength = maxLength - hashHex.Length; + + if (prefixLength < 0) + { + return hashHex.Length > maxLength + ? hashHex[..maxLength] // in case even the hash is too long + : hashHex; + } + + string prefix = name[..Math.Min(prefixLength, name.Length)]; + return $"{prefix}{hashHex}"; + } +} \ No newline at end of file diff --git a/src/ServiceBus.AcceptanceTests/DefaultEndpoint.cs b/src/ServiceBus.AcceptanceTests/DefaultEndpoint.cs index 578d35e0..42e8b343 100644 --- a/src/ServiceBus.AcceptanceTests/DefaultEndpoint.cs +++ b/src/ServiceBus.AcceptanceTests/DefaultEndpoint.cs @@ -1,6 +1,7 @@ namespace ServiceBus.Tests { using System; + using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using NServiceBus; @@ -28,23 +29,21 @@ public async Task GetConfiguration( recoverability.Immediate(immediate => immediate.NumberOfRetries(0)); configuration.SendFailedMessagesTo("error"); + configuration.EnforcePublisherMetadataRegistration(endpointConfiguration.EndpointName, endpointConfiguration.PublisherMetadata); + var connectionString = Environment.GetEnvironmentVariable(ServerlessTransport.DefaultServiceBusConnectionName); - var azureServiceBusTransport = new AzureServiceBusTransport(connectionString) + var topology = TopicTopology.Default; + topology.OverrideSubscriptionNameFor(endpointConfiguration.EndpointName, endpointConfiguration.EndpointName.Shorten()); + foreach (var eventType in endpointConfiguration.PublisherMetadata.Publishers.SelectMany(p => p.Events)) { - SubscriptionRuleNamingConvention = type => - { - if (type.FullName.Length <= 50) - { - return type.FullName; - } - - return type.Name; - } - }; - - var transport = configuration.UseTransport(azureServiceBusTransport); + topology.PublishTo(eventType, eventType.ToTopicName()); + topology.SubscribeTo(eventType, eventType.ToTopicName()); + } + var azureServiceBusTransport = new AzureServiceBusTransport(connectionString, topology); + + _ = configuration.UseTransport(azureServiceBusTransport); configuration.Pipeline.Register("TestIndependenceBehavior", b => new TestIndependenceSkipBehavior(runDescriptor.ScenarioContext), "Skips messages not created during the current test."); diff --git a/src/ServiceBus.AcceptanceTests/FunctionEndpointComponent.cs b/src/ServiceBus.AcceptanceTests/FunctionEndpointComponent.cs index f7bda9f8..ce13c2fa 100644 --- a/src/ServiceBus.AcceptanceTests/FunctionEndpointComponent.cs +++ b/src/ServiceBus.AcceptanceTests/FunctionEndpointComponent.cs @@ -19,6 +19,7 @@ using NServiceBus.AcceptanceTesting.Support; using NServiceBus.AzureFunctions.InProcess.ServiceBus; using NServiceBus.MessageMutator; + using NServiceBus.Transport.AzureServiceBus; using Conventions = NServiceBus.AcceptanceTesting.Customization.Conventions; using ExecutionContext = Microsoft.Azure.WebJobs.ExecutionContext; @@ -39,8 +40,10 @@ public Task CreateRunner(RunDescriptor runDescriptor) => new FunctionRunner( Messages, CustomizeConfiguration, + HostConfigurationCustomization, OnStartCore, runDescriptor.ScenarioContext, + PublisherMetadata, GetType(), DoNotFailOnErrorMessages, TypesScopedByTestClassAssemblyScanningEnabled, @@ -53,46 +56,37 @@ public Task CreateRunner(RunDescriptor runDescriptor) => protected bool TypesScopedByTestClassAssemblyScanningEnabled { get; init; } = true; - protected Func ServiceBusMessageActionsFactory { get; set; } = (r, _) => new TestableServiceBusMessageActions(r); + protected Func ServiceBusMessageActionsFactory { get; init; } = (r, _) => new TestableServiceBusMessageActions(r); protected Action CustomizeConfiguration { private get; init; } = _ => { }; + protected Action HostConfigurationCustomization { private get; init; } = _ => { }; + + protected PublisherMetadata PublisherMetadata { get; } = new PublisherMetadata(); + protected virtual Task OnStart(IFunctionEndpoint functionEndpoint, ExecutionContext executionContext) => Task.CompletedTask; Task OnStartCore(IFunctionEndpoint functionEndpoint, ExecutionContext executionContext) => OnStart(functionEndpoint, executionContext); readonly bool sendsAtomicWithReceive; - class FunctionRunner : ComponentRunner + class FunctionRunner(IList messages, + Action configurationCustomization, + Action hostConfigurationCustomization, + Func onStart, + ScenarioContext scenarioContext, + PublisherMetadata publisherMetadata, + Type functionComponentType, + bool doNotFailOnErrorMessages, + bool typesScopedByTestClassAssemblyScanningEnabled, + bool sendsAtomicWithReceive, + Func serviceBusMessageActionsFactory) : ComponentRunner { - public FunctionRunner(IList messages, - Action configurationCustomization, - Func onStart, - ScenarioContext scenarioContext, - Type functionComponentType, - bool doNotFailOnErrorMessages, - bool typesScopedByTestClassAssemblyScanningEnabled, - bool sendsAtomicWithReceive, - Func serviceBusMessageActionsFactory) - { - this.messages = messages; - this.configurationCustomization = configurationCustomization; - this.onStart = onStart; - this.scenarioContext = scenarioContext; - this.functionComponentType = functionComponentType; - this.typesScopedByTestClassAssemblyScanningEnabled = typesScopedByTestClassAssemblyScanningEnabled; - this.doNotFailOnErrorMessages = doNotFailOnErrorMessages; - this.sendsAtomicWithReceive = sendsAtomicWithReceive; - this.serviceBusMessageActionsFactory = serviceBusMessageActionsFactory; - - Name = Conventions.EndpointNamingConvention(functionComponentType); - } - - public override string Name { get; } + public override string Name { get; } = Conventions.EndpointNamingConvention(functionComponentType); public override async Task Start(CancellationToken cancellationToken = default) { - var hostBuilder = new FunctionHostBuilder(); + var hostBuilder = new FunctionHostBuilder(hostConfigurationCustomization); hostBuilder.UseNServiceBus(Name, triggerConfiguration => { var endpointConfiguration = triggerConfiguration.AdvancedConfiguration; @@ -102,21 +96,34 @@ public override async Task Start(CancellationToken cancellationToken = default) endpointConfiguration.TypesToIncludeInScan(functionComponentType.GetTypesScopedByTestClass()); } + if (triggerConfiguration.Transport.Topology is TopicPerEventTopology topology) + { + topology.OverrideSubscriptionNameFor(Name, Name.Shorten()); + + foreach (var eventType in publisherMetadata.Publishers.SelectMany(p => p.Events)) + { + topology.PublishTo(eventType, eventType.ToTopicName()); + topology.SubscribeTo(eventType, eventType.ToTopicName()); + } + } + + endpointConfiguration.EnforcePublisherMetadataRegistration(Name, publisherMetadata); + endpointConfiguration.Recoverability() .Immediate(i => i.NumberOfRetries(0)) .Delayed(d => d.NumberOfRetries(0)) .Failed(c => c // track messages sent to the error queue to fail the test - .OnMessageSentToErrorQueue((failedMessage, _) => + .OnMessageSentToErrorQueue((failedMessage, ct) => { - scenarioContext.FailedMessages.AddOrUpdate( + _ = scenarioContext.FailedMessages.AddOrUpdate( Name, - new[] { failedMessage }, + [failedMessage], (_, fm) => { - var messages = fm.ToList(); - messages.Add(failedMessage); - return messages; + var failedMessages = fm.ToList(); + failedMessages.Add(failedMessage); + return failedMessages; }); return Task.CompletedTask; })); @@ -253,7 +260,7 @@ public override async Task Stop(CancellationToken cancellationToken = default) // but the host builder used by functions is still using the lambda based approach. To work around this we // have to forward the service registrations to the host builder and some other things manually. This is not // great but once the functions host moved to the new host builder this can be simplified. - sealed class FunctionHostBuilder : IFunctionsHostBuilder, IFunctionsHostBuilderExt + sealed class FunctionHostBuilder(Action configurationCustomization) : IFunctionsHostBuilder, IFunctionsHostBuilderExt { HostBuilderContext context; readonly HostBuilder hostBuilder = new(); @@ -271,6 +278,7 @@ public FunctionsHostBuilderContext Context var configurationBuilder = new ConfigurationBuilder(); configurationBuilder.AddEnvironmentVariables(); + configurationCustomization(configurationBuilder); var configurationRoot = configurationBuilder.Build(); context = new HostBuilderContext(new WebJobsBuilderContext { Configuration = configurationRoot, ApplicationRootPath = AppDomain.CurrentDomain.BaseDirectory }); return context; @@ -296,26 +304,12 @@ public IHost Build() return hostBuilder.Build(); } - sealed class HostBuilderContext : FunctionsHostBuilderContext - { - public HostBuilderContext(WebJobsBuilderContext webJobsBuilderContext) : base(webJobsBuilderContext) - { - } - } + sealed class HostBuilderContext(WebJobsBuilderContext webJobsBuilderContext) + : FunctionsHostBuilderContext(webJobsBuilderContext); } - IList messages; IHost host; IFunctionEndpoint endpoint; - - readonly Action configurationCustomization; - readonly Func onStart; - readonly ScenarioContext scenarioContext; - readonly Type functionComponentType; - readonly bool typesScopedByTestClassAssemblyScanningEnabled; - readonly bool doNotFailOnErrorMessages; - readonly bool sendsAtomicWithReceive; - readonly Func serviceBusMessageActionsFactory; } } } \ No newline at end of file From 6789f67c082a980be83a522ddf61df9a222750af Mon Sep 17 00:00:00 2001 From: danielmarbach Date: Wed, 12 Mar 2025 17:55:41 +0100 Subject: [PATCH 4/8] Adjust prod code --- .../FunctionsHostBuilderExtensions.cs | 25 +++++++-- .../TransportWrapper/ServerlessTransport.cs | 56 ++++++++++--------- ...erviceBusTriggeredEndpointConfiguration.cs | 37 ++++++++---- 3 files changed, 74 insertions(+), 44 deletions(-) diff --git a/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/FunctionsHostBuilderExtensions.cs b/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/FunctionsHostBuilderExtensions.cs index 9f03c5ba..52b33d4f 100644 --- a/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/FunctionsHostBuilderExtensions.cs +++ b/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/FunctionsHostBuilderExtensions.cs @@ -9,6 +9,8 @@ using Microsoft.Extensions.Azure; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; + using Microsoft.Extensions.Options; + using Transport.AzureServiceBus; /// /// Provides extension methods to configure a using . @@ -112,11 +114,24 @@ static void ConfigureEndpointFactory(IServiceCollection services, FunctionsHostB services, Path.Combine(functionsHostBuilderContext.ApplicationRootPath, assemblyDirectoryName)); - services.AddSingleton(serviceBusTriggeredEndpointConfiguration); - services.AddSingleton(startableEndpoint); - services.AddSingleton(serverless); - services.AddSingleton(); - services.AddSingleton(sp => sp.GetRequiredService()); + _ = services.AddSingleton(serviceBusTriggeredEndpointConfiguration); + _ = services.AddSingleton(startableEndpoint); + _ = services.AddSingleton(serverless); + _ = services.AddSingleton(); + _ = services.AddSingleton(sp => sp.GetRequiredService()); + +#pragma warning disable CS0618 // Type or member is obsolete + // Validator is registered here in case the user wants to use the options directly. This makes sure that the options are validated. + // The transport still has to validate the options because options validators are only executed when the options are resolved. + _ = services.AddSingleton, MigrationTopologyOptionsValidator>(); + _ = services.AddOptions() +#pragma warning restore CS0618 // Type or member is obsolete + .BindConfiguration("AzureServiceBus:MigrationTopologyOptions"); + + // Validator is registered here in case the user wants to use the options directly. This makes sure that the options are validated. + // The transport still has to validate the options because options validators are only executed when the options are resolved. + _ = services.AddSingleton, TopologyOptionsValidator>(); + _ = services.AddOptions().BindConfiguration("AzureServiceBus:TopologyOptions"); } static FunctionsHostBuilderContext GetContextInternal(this IFunctionsHostBuilder functionsHostBuilder) diff --git a/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/Serverless/TransportWrapper/ServerlessTransport.cs b/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/Serverless/TransportWrapper/ServerlessTransport.cs index 11442fc9..92ada331 100644 --- a/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/Serverless/TransportWrapper/ServerlessTransport.cs +++ b/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/Serverless/TransportWrapper/ServerlessTransport.cs @@ -2,14 +2,20 @@ { using System; using System.Collections.Generic; + using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; + using Azure.Core; using Microsoft.Extensions.Azure; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Transport; - class ServerlessTransport : TransportDefinition + class ServerlessTransport(AzureServiceBusTransport transport, string connectionString, string connectionName) : TransportDefinition( + TransportTransactionMode.ReceiveOnly, + transport.SupportsDelayedDelivery, + transport.SupportsPublishSubscribe, + transport.SupportsTTBR) { // HINT: This constant is defined in NServiceBus but is not exposed const string MainReceiverId = "Main"; @@ -19,22 +25,11 @@ class ServerlessTransport : TransportDefinition public IServiceProvider ServiceProvider { get; set; } - public ServerlessTransport(TransportExtensions transportExtensions, string connectionString, string connectionName) : base( - transportExtensions.Transport.TransportTransactionMode, - transportExtensions.Transport.SupportsDelayedDelivery, - transportExtensions.Transport.SupportsPublishSubscribe, - transportExtensions.Transport.SupportsTTBR) - { - this.transportExtensions = transportExtensions; - this.connectionString = connectionString; - this.connectionName = connectionName; - } - public override async Task Initialize(HostSettings hostSettings, ReceiveSettings[] receivers, string[] sendingAddresses, CancellationToken cancellationToken = default) { - var configuredTransport = ConfigureTransportConnection(connectionString, connectionName, ServiceProvider.GetRequiredService(), transportExtensions, + var configuredTransport = ConfigureTransportConnection(connectionString, connectionName, ServiceProvider.GetRequiredService(), transport, ServiceProvider.GetRequiredService()); var baseTransportInfrastructure = await configuredTransport.Initialize( @@ -58,16 +53,12 @@ public override async Task Initialize(HostSettings host public override IReadOnlyCollection GetSupportedTransactionModes() => supportedTransactionModes; - // We are deliberately using the old way of configuring a transport here because it allows us configuring - // the uninitialized transport with a connection string or a fully qualified name and a token provider. - // Once we deprecate the old way we can for example add make the internal ConnectionString, FQDN or - // TokenProvider properties visible to functions or the code base has already moved into a different direction. static AzureServiceBusTransport ConfigureTransportConnection(string connectionString, string connectionName, IConfiguration configuration, - TransportExtensions transportExtensions, AzureComponentFactory azureComponentFactory) + AzureServiceBusTransport transport, AzureComponentFactory azureComponentFactory) { if (connectionString != null) { - _ = transportExtensions.ConnectionString(connectionString); + GetConnectionStringRef(transport) = connectionString; } else { @@ -80,7 +71,7 @@ static AzureServiceBusTransport ConfigureTransportConnection(string connectionSt if (!string.IsNullOrWhiteSpace(connectionSection.Value)) { - _ = transportExtensions.ConnectionString(connectionSection.Value); + GetConnectionStringRef(transport) = connectionSection.Value; } else { @@ -91,22 +82,33 @@ static AzureServiceBusTransport ConfigureTransportConnection(string connectionSt } var credential = azureComponentFactory.CreateTokenCredential(connectionSection); - _ = transportExtensions.CustomTokenCredential(fullyQualifiedNamespace, credential); + GetFullyQualifiedNamespaceRef(transport) = fullyQualifiedNamespace; + GetTokenCredentialRef(transport) = credential; } } - return transportExtensions.Transport; + return transport; } + // As a temporary workaround we are accessing the properties of the AzureServiceBusTransport using UnsafeAccessor + // This is another blocker to AoT but we are already using the execution assembly in the code base anyway + // Furthermore this allows us to still comply with initializing the transport as late as possible without having to + // expose the properties on the transport itself which would pollute the public API for not much added value. + [UnsafeAccessor(UnsafeAccessorKind.Field, Name = "k__BackingField")] + static extern ref string GetConnectionStringRef(AzureServiceBusTransport transport); + + [UnsafeAccessor(UnsafeAccessorKind.Field, Name = "k__BackingField")] + static extern ref string GetFullyQualifiedNamespaceRef(AzureServiceBusTransport transport); + + [UnsafeAccessor(UnsafeAccessorKind.Field, Name = "k__BackingField")] + static extern ref TokenCredential GetTokenCredentialRef(AzureServiceBusTransport transport); + internal const string DefaultServiceBusConnectionName = "AzureWebJobsServiceBus"; readonly TransportTransactionMode[] supportedTransactionModes = - { + [ TransportTransactionMode.ReceiveOnly, TransportTransactionMode.SendsAtomicWithReceive - }; - readonly TransportExtensions transportExtensions; - readonly string connectionString; - readonly string connectionName; + ]; } } \ No newline at end of file diff --git a/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/ServiceBusTriggeredEndpointConfiguration.cs b/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/ServiceBusTriggeredEndpointConfiguration.cs index 9ee0fdf8..fae5acb7 100644 --- a/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/ServiceBusTriggeredEndpointConfiguration.cs +++ b/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/ServiceBusTriggeredEndpointConfiguration.cs @@ -5,11 +5,13 @@ using System.Threading.Tasks; using AzureFunctions.InProcess.ServiceBus; using AzureFunctions.InProcess.ServiceBus.Serverless; + using Configuration.AdvancedExtensibility; using Logging; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Serialization; using Settings; + using Transport.AzureServiceBus; /// /// Represents a serverless NServiceBus endpoint. @@ -68,16 +70,28 @@ internal ServiceBusTriggeredEndpointConfiguration(string endpointName, IConfigur endpointConfiguration.License(licenseText); } - // We are deliberately using the old way of creating a transport here because it allows us to create an - // uninitialized transport that can later be configured with a connection string or a fully qualified name and - // a token provider. Once we deprecate the old way we can for example add make the internal constructor - // visible to functions or the code base has already moved into a different direction. - transportExtensions = endpointConfiguration.UseTransport(); - // This is required for the Outbox validation to work in NServiceBus 8. It does not affect the actual consistency mode because it is controlled by the functions - // endpoint API (calling ProcessAtomic vs ProcessNonAtomic). - transportExtensions.Transactions(TransportTransactionMode.ReceiveOnly); - Transport = transportExtensions.Transport; - Routing = transportExtensions.Routing(); + TopicTopology topicTopology = TopicTopology.Default; + var topologyOptionsSection = configuration?.GetSection("AzureServiceBus:TopologyOptions"); + if (topologyOptionsSection.Exists()) + { + topicTopology = TopicTopology.FromOptions(topologyOptionsSection.Get()); + } + // Migration options take precedence over topology options. We are not doing additional checks here for now. + var migrationOptionsSection = configuration?.GetSection("AzureServiceBus:MigrationTopologyOptions"); + if (migrationOptionsSection.Exists()) + { +#pragma warning disable CS0618 // Type or member is obsolete + topicTopology = TopicTopology.FromOptions(migrationOptionsSection.Get()); +#pragma warning restore CS0618 // Type or member is obsolete + } + + Transport = new AzureServiceBusTransport("TransportWillBeInitializedCorrectlyLater", topicTopology) + { + // This is required for the Outbox validation to work in NServiceBus 8. It does not affect the actual consistency mode because it is controlled by the functions + // endpoint API (calling ProcessAtomic vs ProcessNonAtomic). + TransportTransactionMode = TransportTransactionMode.ReceiveOnly + }; + Routing = new RoutingSettings(endpointConfiguration.GetSettings()); endpointConfiguration.UseSerialization(); @@ -86,7 +100,7 @@ internal ServiceBusTriggeredEndpointConfiguration(string endpointName, IConfigur internal ServerlessTransport InitializeTransport() { - var serverlessTransport = new ServerlessTransport(transportExtensions, connectionString, connectionName); + var serverlessTransport = new ServerlessTransport(Transport, connectionString, connectionName); AdvancedConfiguration.UseTransport(serverlessTransport); return serverlessTransport; } @@ -117,6 +131,5 @@ public void LogDiagnostics() => readonly ServerlessRecoverabilityPolicy recoverabilityPolicy = new ServerlessRecoverabilityPolicy(); readonly string connectionString; readonly string connectionName; - readonly TransportExtensions transportExtensions; } } From 384320565cf65384eeb67753cee63c430f198aa4 Mon Sep 17 00:00:00 2001 From: danielmarbach Date: Wed, 12 Mar 2025 17:55:50 +0100 Subject: [PATCH 5/8] Cleanup and add tests --- .../When_failing_to_process_message.cs | 74 ++++++------------- ...en_incoming_message_is_not_acknowledged.cs | 31 ++------ ...message_fails_with_disabled_error_queue.cs | 4 +- ...sage_is_failing_all_processing_attempts.cs | 18 +---- .../When_message_is_received.cs | 18 +---- .../When_no_connection_string_is_provided.cs | 4 +- .../When_overriding_the_topology.cs | 74 +++++++++++++++++++ .../When_publishing_event_from_function.cs | 28 ++----- .../When_receiving_with_sendonly.cs | 4 +- .../When_sending_message.cs | 26 ++----- .../When_sending_with_sendonly.cs | 10 +-- .../When_using_processatomic_with_outbox.cs | 30 ++------ .../When_using_sagas.cs | 20 ++--- 13 files changed, 133 insertions(+), 208 deletions(-) create mode 100644 src/ServiceBus.AcceptanceTests/When_overriding_the_topology.cs diff --git a/src/ServiceBus.AcceptanceTests/When_failing_to_process_message.cs b/src/ServiceBus.AcceptanceTests/When_failing_to_process_message.cs index ce768a70..23eb1a1e 100644 --- a/src/ServiceBus.AcceptanceTests/When_failing_to_process_message.cs +++ b/src/ServiceBus.AcceptanceTests/When_failing_to_process_message.cs @@ -53,20 +53,15 @@ class Context : ScenarioContext class InsideEndpoint : EndpointConfigurationBuilder { - public InsideEndpoint() - { - EndpointSetup(cfg => cfg.LimitMessageProcessingConcurrencyTo(1)); - } - - public class AbortedEventHandler : IHandleMessages - { - Context testContext; - - public AbortedEventHandler(Context testContext) + public InsideEndpoint() => EndpointSetup(cfg => cfg.LimitMessageProcessingConcurrencyTo(1), + metadata => { - this.testContext = testContext; - } + metadata.RegisterPublisherFor(typeof(PublishingFunction)); + metadata.RegisterPublisherFor(typeof(PublishingFunction)); + }); + public class AbortedEventHandler(Context testContext) : IHandleMessages + { public Task Handle(AbortedEvent message, IMessageHandlerContext context) { testContext.AbortedEventReceived = true; @@ -74,15 +69,8 @@ public Task Handle(AbortedEvent message, IMessageHandlerContext context) } } - public class TerminatingEventHandler : IHandleMessages + public class TerminatingEventHandler(Context testContext) : IHandleMessages { - Context testContext; - - public TerminatingEventHandler(Context testContext) - { - this.testContext = testContext; - } - public Task Handle(TerminatingEvent message, IMessageHandlerContext context) { testContext.TerminatingEventReceived = true; @@ -95,6 +83,8 @@ class PublishingFunction : FunctionEndpointComponent { public PublishingFunction(TransportTransactionMode transportTransactionMode) : base(transportTransactionMode) { + PublisherMetadata.RegisterPublisherFor(typeof(PublishingFunction)); + PublisherMetadata.RegisterPublisherFor(typeof(PublishingFunction)); Messages.Add(new TriggerMessage()); Messages.Add(new TerminatingMessage()); DoNotFailOnErrorMessages = true; @@ -103,31 +93,21 @@ public PublishingFunction(TransportTransactionMode transportTransactionMode) : b public class PublishingHandler : IHandleMessages { - public Task Handle(TriggerMessage message, IMessageHandlerContext context) - { - return context.Publish(new AbortedEvent()); - } + public Task Handle(TriggerMessage message, IMessageHandlerContext context) => context.Publish(new AbortedEvent()); } public class TerminatingMessageHandler : IHandleMessages { - public Task Handle(TerminatingMessage message, IMessageHandlerContext context) - { - return context.Publish(new TerminatingEvent()); - } + public Task Handle(TerminatingMessage message, IMessageHandlerContext context) => context.Publish(new TerminatingEvent()); } } - class FirstCompleteFailingServiceBusMessageActions : ServiceBusMessageActions + class FirstCompleteFailingServiceBusMessageActions( + ServiceBusReceiver serviceBusReceiver, + ScenarioContext scenarioContext) + : ServiceBusMessageActions { - readonly ServiceBusReceiver serviceBusReceiver; - readonly Context scenarioContext; - - public FirstCompleteFailingServiceBusMessageActions(ServiceBusReceiver serviceBusReceiver, ScenarioContext scenarioContext) - { - this.serviceBusReceiver = serviceBusReceiver; - this.scenarioContext = (Context)scenarioContext; - } + readonly Context scenarioContext = (Context)scenarioContext; public override async Task CompleteMessageAsync(ServiceBusReceivedMessage message, CancellationToken cancellationToken = default) { @@ -142,25 +122,15 @@ public override async Task CompleteMessageAsync(ServiceBusReceivedMessage messag } public override Task AbandonMessageAsync(ServiceBusReceivedMessage message, IDictionary propertiesToModify = null, CancellationToken cancellationToken = default) - { - return serviceBusReceiver.AbandonMessageAsync(message, propertiesToModify, cancellationToken); - } + => serviceBusReceiver.AbandonMessageAsync(message, propertiesToModify, cancellationToken); } - class TriggerMessage : IMessage - { - } + class TriggerMessage : IMessage; - class TerminatingMessage : IMessage - { - } + class TerminatingMessage : IMessage; - class AbortedEvent : IEvent - { - } + class AbortedEvent : IEvent; - class TerminatingEvent : IEvent - { - } + class TerminatingEvent : IEvent; } } \ No newline at end of file diff --git a/src/ServiceBus.AcceptanceTests/When_incoming_message_is_not_acknowledged.cs b/src/ServiceBus.AcceptanceTests/When_incoming_message_is_not_acknowledged.cs index bebf11a9..0681f413 100644 --- a/src/ServiceBus.AcceptanceTests/When_incoming_message_is_not_acknowledged.cs +++ b/src/ServiceBus.AcceptanceTests/When_incoming_message_is_not_acknowledged.cs @@ -62,20 +62,10 @@ public Task Handle(HappyDayMessage message, IMessageHandlerContext context) class SpyEndpoint : EndpointConfigurationBuilder { - public SpyEndpoint() - { - EndpointSetup(); - } + public SpyEndpoint() => EndpointSetup(); - public class EventHandler : IHandleMessages + public class EventHandler(Context testContext) : IHandleMessages { - Context testContext; - - public EventHandler(Context testContext) - { - this.testContext = testContext; - } - public Task Handle(FollowUpMessage message, IMessageHandlerContext context) { testContext.MessageReceived = true; @@ -84,15 +74,9 @@ public Task Handle(FollowUpMessage message, IMessageHandlerContext context) } } - class FailBeforeAckBehavior : Behavior + class FailBeforeAckBehavior(Context testContext) : Behavior { bool failed; - Context testContext; - - public FailBeforeAckBehavior(Context testContext) - { - this.testContext = testContext; - } public override async Task Invoke(ITransportReceiveContext context, Func next) { @@ -110,13 +94,8 @@ public override async Task Invoke(ITransportReceiveContext context, Func n } } + class HappyDayMessage : IMessage; - class HappyDayMessage : IMessage - { - } - - class FollowUpMessage : IMessage - { - } + class FollowUpMessage : IMessage; } } \ No newline at end of file diff --git a/src/ServiceBus.AcceptanceTests/When_message_fails_with_disabled_error_queue.cs b/src/ServiceBus.AcceptanceTests/When_message_fails_with_disabled_error_queue.cs index f544b99b..aa4e39d1 100644 --- a/src/ServiceBus.AcceptanceTests/When_message_fails_with_disabled_error_queue.cs +++ b/src/ServiceBus.AcceptanceTests/When_message_fails_with_disabled_error_queue.cs @@ -42,8 +42,6 @@ public Task Handle(TriggerMessage message, IMessageHandlerContext context) } } - class TriggerMessage : IMessage - { - } + class TriggerMessage : IMessage; } } \ No newline at end of file diff --git a/src/ServiceBus.AcceptanceTests/When_message_is_failing_all_processing_attempts.cs b/src/ServiceBus.AcceptanceTests/When_message_is_failing_all_processing_attempts.cs index 2045b6af..ac775afa 100644 --- a/src/ServiceBus.AcceptanceTests/When_message_is_failing_all_processing_attempts.cs +++ b/src/ServiceBus.AcceptanceTests/When_message_is_failing_all_processing_attempts.cs @@ -37,20 +37,10 @@ class Context : ScenarioContext class MoveToErrorQueueFunction : FunctionEndpointComponent { - public MoveToErrorQueueFunction(TransportTransactionMode transactionMode) : base(transactionMode) - { - Messages.Add(new TriggerMessage()); - } + public MoveToErrorQueueFunction(TransportTransactionMode transactionMode) : base(transactionMode) => Messages.Add(new TriggerMessage()); - public class FailingHandler : IHandleMessages + public class FailingHandler(Context testContext) : IHandleMessages { - Context testContext; - - public FailingHandler(Context testContext) - { - this.testContext = testContext; - } - public Task Handle(TriggerMessage message, IMessageHandlerContext context) { testContext.HandlerInvocations++; @@ -59,8 +49,6 @@ public Task Handle(TriggerMessage message, IMessageHandlerContext context) } } - public class TriggerMessage : IMessage - { - } + public class TriggerMessage : IMessage; } } \ No newline at end of file diff --git a/src/ServiceBus.AcceptanceTests/When_message_is_received.cs b/src/ServiceBus.AcceptanceTests/When_message_is_received.cs index 1d5d51e6..ec2f17ca 100644 --- a/src/ServiceBus.AcceptanceTests/When_message_is_received.cs +++ b/src/ServiceBus.AcceptanceTests/When_message_is_received.cs @@ -31,20 +31,10 @@ public class Context : ScenarioContext class FunctionHandler : FunctionEndpointComponent { - public FunctionHandler(TransportTransactionMode transactionMode) : base(transactionMode) - { - Messages.Add(new HappyDayMessage()); - } + public FunctionHandler(TransportTransactionMode transactionMode) : base(transactionMode) => Messages.Add(new HappyDayMessage()); - public class HappyDayMessageHandler : IHandleMessages + public class HappyDayMessageHandler(Context testContext) : IHandleMessages { - Context testContext; - - public HappyDayMessageHandler(Context testContext) - { - this.testContext = testContext; - } - public Task Handle(HappyDayMessage message, IMessageHandlerContext context) { testContext.HandlerInvoked(); @@ -53,8 +43,6 @@ public Task Handle(HappyDayMessage message, IMessageHandlerContext context) } } - class HappyDayMessage : IMessage - { - } + class HappyDayMessage : IMessage; } } \ No newline at end of file diff --git a/src/ServiceBus.AcceptanceTests/When_no_connection_string_is_provided.cs b/src/ServiceBus.AcceptanceTests/When_no_connection_string_is_provided.cs index bc165776..2bc651a4 100644 --- a/src/ServiceBus.AcceptanceTests/When_no_connection_string_is_provided.cs +++ b/src/ServiceBus.AcceptanceTests/When_no_connection_string_is_provided.cs @@ -39,8 +39,6 @@ public void TearDown() => string originalConnectionString; - class FunctionWithoutConnectionString : FunctionEndpointComponent - { - } + class FunctionWithoutConnectionString : FunctionEndpointComponent; } } \ No newline at end of file diff --git a/src/ServiceBus.AcceptanceTests/When_overriding_the_topology.cs b/src/ServiceBus.AcceptanceTests/When_overriding_the_topology.cs new file mode 100644 index 00000000..18b04cfd --- /dev/null +++ b/src/ServiceBus.AcceptanceTests/When_overriding_the_topology.cs @@ -0,0 +1,74 @@ +namespace ServiceBus.Tests +{ + using System.Collections.Generic; + using System.Threading.Tasks; + using Microsoft.Extensions.Configuration; + using NServiceBus; + using NServiceBus.AcceptanceTesting; + using NUnit.Framework; + + public class When_overriding_the_topology + { + [Test] + public async Task Should_publish_to_subscribers() + { + var context = await Scenario.Define() + .WithEndpoint() + .WithComponent(new PublishingFunction()) + .Done(c => c.EventReceived) + .Run(); + + Assert.That(context.EventReceived, Is.True); + } + + class Context : ScenarioContext + { + public bool EventReceived { get; set; } + } + + class InsideSubscriber : EndpointConfigurationBuilder + { + public InsideSubscriber() => EndpointSetup(_ => { }, + metadata => metadata.RegisterPublisherFor(typeof(PublishingFunction))); + + class EventHandler(Context testContext) : IHandleMessages + { + public Task Handle(MyEvent message, IMessageHandlerContext context) + { + testContext.EventReceived = true; + return Task.CompletedTask; + } + } + } + + class PublishingFunction : FunctionEndpointComponent + { + public PublishingFunction() + { + PublisherMetadata.RegisterPublisherFor(typeof(PublishingFunction)); + HostConfigurationCustomization = builder => + { + var customSettings = new Dictionary + { + { "AzureServiceBus:MigrationTopologyOptions:TopicToPublishTo", "bundle-1" }, + { "AzureServiceBus:MigrationTopologyOptions:TopicToSubscribeOn", "bundle-1" }, + { $"AzureServiceBus:MigrationTopologyOptions:PublishedEventToTopicsMap:{typeof(MyEvent).FullName}", $"{typeof(MyEvent).ToTopicName()}" + }, + }; + _ = builder.AddInMemoryCollection(customSettings); + }; + Messages.Add(new TriggerMessage()); + } + + class PublishingHandler : IHandleMessages + { + public Task Handle(TriggerMessage message, IMessageHandlerContext context) => + context.Publish(new MyEvent()); + } + } + + class TriggerMessage : IMessage; + + class MyEvent : IEvent; + } +} \ No newline at end of file diff --git a/src/ServiceBus.AcceptanceTests/When_publishing_event_from_function.cs b/src/ServiceBus.AcceptanceTests/When_publishing_event_from_function.cs index 4cb12421..a5480db5 100644 --- a/src/ServiceBus.AcceptanceTests/When_publishing_event_from_function.cs +++ b/src/ServiceBus.AcceptanceTests/When_publishing_event_from_function.cs @@ -27,20 +27,10 @@ class Context : ScenarioContext class InsideEndpoint : EndpointConfigurationBuilder { - public InsideEndpoint() - { - EndpointSetup(); - } + public InsideEndpoint() => EndpointSetup(_ => { }, metadata => metadata.RegisterPublisherFor(typeof(PublishingFunction))); - public class EventHandler : IHandleMessages + public class EventHandler(Context testContext) : IHandleMessages { - Context testContext; - - public EventHandler(Context testContext) - { - this.testContext = testContext; - } - public Task Handle(InsideEvent message, IMessageHandlerContext context) { testContext.EventReceived = true; @@ -53,24 +43,18 @@ class PublishingFunction : FunctionEndpointComponent { public PublishingFunction(TransportTransactionMode transactionMode) : base(transactionMode) { + PublisherMetadata.RegisterPublisherFor(typeof(PublishingFunction)); Messages.Add(new TriggerMessage()); } public class PublishingHandler : IHandleMessages { - public Task Handle(TriggerMessage message, IMessageHandlerContext context) - { - return context.Publish(new InsideEvent()); - } + public Task Handle(TriggerMessage message, IMessageHandlerContext context) => context.Publish(new InsideEvent()); } } - class TriggerMessage : IMessage - { - } + class TriggerMessage : IMessage; - class InsideEvent : IEvent - { - } + class InsideEvent : IEvent; } } \ No newline at end of file diff --git a/src/ServiceBus.AcceptanceTests/When_receiving_with_sendonly.cs b/src/ServiceBus.AcceptanceTests/When_receiving_with_sendonly.cs index f1f87c15..e9fc9a4c 100644 --- a/src/ServiceBus.AcceptanceTests/When_receiving_with_sendonly.cs +++ b/src/ServiceBus.AcceptanceTests/When_receiving_with_sendonly.cs @@ -36,8 +36,6 @@ public class TestMessageHandler : IHandleMessages } } - class TestMessage : IMessage - { - } + class TestMessage : IMessage; } } \ No newline at end of file diff --git a/src/ServiceBus.AcceptanceTests/When_sending_message.cs b/src/ServiceBus.AcceptanceTests/When_sending_message.cs index 3e36ef09..bf68208c 100644 --- a/src/ServiceBus.AcceptanceTests/When_sending_message.cs +++ b/src/ServiceBus.AcceptanceTests/When_sending_message.cs @@ -26,20 +26,10 @@ class Context : ScenarioContext public class ReceivingEndpoint : EndpointConfigurationBuilder { - public ReceivingEndpoint() - { - EndpointSetup(); - } + public ReceivingEndpoint() => EndpointSetup(); - class OutgoingMessageHandler : IHandleMessages + class OutgoingMessageHandler(Context testContext) : IHandleMessages { - Context testContext; - - public OutgoingMessageHandler(Context testContext) - { - this.testContext = testContext; - } - public Task Handle(FollowupMessage message, IMessageHandlerContext context) { testContext.HandlerReceivedMessage = true; @@ -58,18 +48,12 @@ public SendingFunction(TransportTransactionMode transactionMode) : base(transact public class TriggerMessageHandler : IHandleMessages { public Task Handle(TriggerMessage message, IMessageHandlerContext context) - { - return context.Send(Conventions.EndpointNamingConvention(typeof(ReceivingEndpoint)), new FollowupMessage()); - } + => context.Send(Conventions.EndpointNamingConvention(typeof(ReceivingEndpoint)), new FollowupMessage()); } } - class TriggerMessage : IMessage - { - } + class TriggerMessage : IMessage; - class FollowupMessage : IMessage - { - } + class FollowupMessage : IMessage; } } \ No newline at end of file diff --git a/src/ServiceBus.AcceptanceTests/When_sending_with_sendonly.cs b/src/ServiceBus.AcceptanceTests/When_sending_with_sendonly.cs index 416b1fb8..ac11c87c 100644 --- a/src/ServiceBus.AcceptanceTests/When_sending_with_sendonly.cs +++ b/src/ServiceBus.AcceptanceTests/When_sending_with_sendonly.cs @@ -28,12 +28,8 @@ class ReceivingEndpoint : EndpointConfigurationBuilder { public ReceivingEndpoint() => EndpointSetup(); - public class TestMessageHandler : IHandleMessages + public class TestMessageHandler(Context testContext) : IHandleMessages { - readonly Context testContext; - - public TestMessageHandler(Context testContext) => this.testContext = testContext; - public Task Handle(TestMessage message, IMessageHandlerContext context) { testContext.HandlerReceivedMessage = true; @@ -56,8 +52,6 @@ protected override Task OnStart(IFunctionEndpoint endpoint, ExecutionContext exe => endpoint.Send(new TestMessage(), executionContext); } - class TestMessage : IMessage - { - } + class TestMessage : IMessage; } } \ No newline at end of file diff --git a/src/ServiceBus.AcceptanceTests/When_using_processatomic_with_outbox.cs b/src/ServiceBus.AcceptanceTests/When_using_processatomic_with_outbox.cs index a1ffb685..ab27dbea 100644 --- a/src/ServiceBus.AcceptanceTests/When_using_processatomic_with_outbox.cs +++ b/src/ServiceBus.AcceptanceTests/When_using_processatomic_with_outbox.cs @@ -60,20 +60,10 @@ public Task Handle(HappyDayMessage message, IMessageHandlerContext context) class SpyEndpoint : EndpointConfigurationBuilder { - public SpyEndpoint() - { - EndpointSetup(); - } + public SpyEndpoint() => EndpointSetup(); - public class EventHandler : IHandleMessages + public class EventHandler(Context testContext) : IHandleMessages { - Context testContext; - - public EventHandler(Context testContext) - { - this.testContext = testContext; - } - public Task Handle(FollowUpMessage message, IMessageHandlerContext context) { testContext.MessageReceived = true; @@ -82,15 +72,9 @@ public Task Handle(FollowUpMessage message, IMessageHandlerContext context) } } - class FailBeforeAckBehavior : Behavior + class FailBeforeAckBehavior(Context testContext) : Behavior { bool failed; - Context testContext; - - public FailBeforeAckBehavior(Context testContext) - { - this.testContext = testContext; - } public override async Task Invoke(ITransportReceiveContext context, Func next) { @@ -109,12 +93,8 @@ public override async Task Invoke(ITransportReceiveContext context, Func n } - class HappyDayMessage : IMessage - { - } + class HappyDayMessage : IMessage; - class FollowUpMessage : IMessage - { - } + class FollowUpMessage : IMessage; } } \ No newline at end of file diff --git a/src/ServiceBus.AcceptanceTests/When_using_sagas.cs b/src/ServiceBus.AcceptanceTests/When_using_sagas.cs index d017223d..83458a1e 100644 --- a/src/ServiceBus.AcceptanceTests/When_using_sagas.cs +++ b/src/ServiceBus.AcceptanceTests/When_using_sagas.cs @@ -38,26 +38,16 @@ public SagaFunction(TransportTransactionMode transportTransactionMode) : base(tr Messages.Add(new ReadSagaDataValueMessage { CorrelationProperty = correlationProperty }); } - public class DemoSaga : Saga, + public class DemoSaga(Context testContext) : Saga, IAmStartedByMessages, IHandleMessages, IHandleMessages { - Context testContext; - - public DemoSaga(Context testContext) - { - this.testContext = testContext; - } - - protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) - { + protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) => mapper.MapSaga(saga => saga.CorrelationProperty) - .ToMessage(m => m.CorrelationProperty) - .ToMessage(m => m.CorrelationProperty) - .ToMessage(m => m.CorrelationProperty); - - } + .ToMessage(m => m.CorrelationProperty) + .ToMessage(m => m.CorrelationProperty) + .ToMessage(m => m.CorrelationProperty); public Task Handle(StartSagaMessage message, IMessageHandlerContext context) { From 3549ec35ce5f6681c74cef0fb96cff435e500c73 Mon Sep 17 00:00:00 2001 From: danielmarbach Date: Wed, 12 Mar 2025 18:01:22 +0100 Subject: [PATCH 6/8] Slightly smarter configuration forwarding --- .../FunctionEndpointComponent.cs | 8 ++++---- src/ServiceBus.AcceptanceTests/InitializationHost.cs | 6 +----- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/src/ServiceBus.AcceptanceTests/FunctionEndpointComponent.cs b/src/ServiceBus.AcceptanceTests/FunctionEndpointComponent.cs index ce13c2fa..a09c9a96 100644 --- a/src/ServiceBus.AcceptanceTests/FunctionEndpointComponent.cs +++ b/src/ServiceBus.AcceptanceTests/FunctionEndpointComponent.cs @@ -287,14 +287,14 @@ public FunctionsHostBuilderContext Context public IHost Build() { - hostBuilder.ConfigureHostConfiguration(configuration => + _ = hostBuilder.ConfigureHostConfiguration(configuration => { - configuration.AddEnvironmentVariables(); + configuration.AddConfiguration(Context.Configuration); }); // Forwarding all the service registrations to the host builder - hostBuilder.ConfigureServices(services => + _ = hostBuilder.ConfigureServices(services => { - services.AddHostedService(); + _ = services.AddHostedService(); foreach (var service in Services) { services.Add(service); diff --git a/src/ServiceBus.AcceptanceTests/InitializationHost.cs b/src/ServiceBus.AcceptanceTests/InitializationHost.cs index 2ef8df94..8eaeabaf 100644 --- a/src/ServiceBus.AcceptanceTests/InitializationHost.cs +++ b/src/ServiceBus.AcceptanceTests/InitializationHost.cs @@ -4,12 +4,8 @@ namespace NServiceBus.AzureFunctions.InProcess.ServiceBus using System.Threading.Tasks; using Microsoft.Extensions.Hosting; - class InitializationHost : IHostedService + class InitializationHost(InProcessFunctionEndpoint functionEndpoint) : IHostedService { - readonly InProcessFunctionEndpoint functionEndpoint; - - public InitializationHost(InProcessFunctionEndpoint functionEndpoint) => this.functionEndpoint = functionEndpoint; - public Task StartAsync(CancellationToken cancellationToken = default) => functionEndpoint.InitializeEndpointIfNecessary(cancellationToken); public Task StopAsync(CancellationToken cancellationToken = default) => Task.CompletedTask; From 7a3928541d4d34c0374990bc42536336c422af8d Mon Sep 17 00:00:00 2001 From: danielmarbach Date: Wed, 12 Mar 2025 18:03:19 +0100 Subject: [PATCH 7/8] Cleanup startup --- src/IntegrationTests.HostV4/Startup.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/IntegrationTests.HostV4/Startup.cs b/src/IntegrationTests.HostV4/Startup.cs index e4397b49..13c28c8b 100644 --- a/src/IntegrationTests.HostV4/Startup.cs +++ b/src/IntegrationTests.HostV4/Startup.cs @@ -7,7 +7,5 @@ public class Startup : FunctionsStartup { public override void Configure(IFunctionsHostBuilder builder) - { - builder.UseNServiceBus(c => c.AdvancedConfiguration.EnableInstallers()); - } + => builder.UseNServiceBus(c => c.AdvancedConfiguration.EnableInstallers()); } \ No newline at end of file From 05883906a86a57cc66961c1ac1d0b2a55df66a1a Mon Sep 17 00:00:00 2001 From: danielmarbach Date: Wed, 12 Mar 2025 18:17:44 +0100 Subject: [PATCH 8/8] Fix analyzer tests --- .../AnalyzerTestFixture.cs | 2 ++ .../ConfigurationAnalyzerTests.cs | 4 ++-- .../ConfigurationAnalyzerTestsCSharp8.cs | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/NServiceBus.AzureFunctions.InProcess.Analyzer.Tests/AnalyzerTestFixture.cs b/src/NServiceBus.AzureFunctions.InProcess.Analyzer.Tests/AnalyzerTestFixture.cs index 3ee6b02c..61a7dc87 100644 --- a/src/NServiceBus.AzureFunctions.InProcess.Analyzer.Tests/AnalyzerTestFixture.cs +++ b/src/NServiceBus.AzureFunctions.InProcess.Analyzer.Tests/AnalyzerTestFixture.cs @@ -9,6 +9,7 @@ using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; + using Azure.Core; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Diagnostics; @@ -111,6 +112,7 @@ static AnalyzerTestFixture() MetadataReference.CreateFromFile(typeof(System.Linq.Expressions.Expression).GetTypeInfo().Assembly.Location), MetadataReference.CreateFromFile(Assembly.Load("System.Runtime").Location), MetadataReference.CreateFromFile(typeof(IFunctionEndpoint).GetTypeInfo().Assembly.Location), + MetadataReference.CreateFromFile(typeof(TokenCredential).GetTypeInfo().Assembly.Location), MetadataReference.CreateFromFile(typeof(EndpointConfiguration).GetTypeInfo().Assembly.Location), MetadataReference.CreateFromFile(typeof(AzureServiceBusTransport).GetTypeInfo().Assembly.Location), ]; diff --git a/src/NServiceBus.AzureFunctions.InProcess.Analyzer.Tests/ConfigurationAnalyzerTests.cs b/src/NServiceBus.AzureFunctions.InProcess.Analyzer.Tests/ConfigurationAnalyzerTests.cs index 37687f1e..3e1938d5 100644 --- a/src/NServiceBus.AzureFunctions.InProcess.Analyzer.Tests/ConfigurationAnalyzerTests.cs +++ b/src/NServiceBus.AzureFunctions.InProcess.Analyzer.Tests/ConfigurationAnalyzerTests.cs @@ -13,7 +13,7 @@ public class ConfigurationAnalyzerTests : AnalyzerTestFixture { // HINT: In C# 7 this call is ambiguous with the LearningTransport version as the compiler cannot differentiate method calls via generic type constraints - [TestCase("UseTransport()", UseTransportNotAllowedId)] + [TestCase("UseTransport(null)", UseTransportNotAllowedId)] public Task DiagnosticIsReportedForEndpointConfiguration(string configuration, string diagnosticId) { var source =