From 93e0deb300499cdd9409ad220e334dd2c41ab491 Mon Sep 17 00:00:00 2001 From: Tim Bussmann Date: Tue, 3 Aug 2021 18:00:34 +0200 Subject: [PATCH 1/6] locate attribute to read AutoComplete value --- .../FunctionEndpoint.cs | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/FunctionEndpoint.cs b/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/FunctionEndpoint.cs index 859c0def..d1ecbf58 100644 --- a/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/FunctionEndpoint.cs +++ b/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/FunctionEndpoint.cs @@ -1,6 +1,7 @@ namespace NServiceBus { using System; + using System.Diagnostics; using System.IO; using System.Reflection; using System.Runtime.Loader; @@ -12,6 +13,7 @@ using Logging; using Microsoft.Azure.ServiceBus; using Microsoft.Azure.ServiceBus.Core; + using Microsoft.Azure.WebJobs; using Microsoft.Extensions.Logging; using Transport; using ExecutionContext = Microsoft.Azure.WebJobs.ExecutionContext; @@ -30,6 +32,42 @@ internal FunctionEndpoint(IStartableEndpointWithExternallyManagedContainer exter endpointFactory = _ => externallyManagedContainerEndpoint.Start(serviceProvider); } + /// + /// TODO. + /// + public Task AutoDetectProcess(Message message, ExecutionContext executionContext, IMessageReceiver messageReceiver, ILogger functionsLogger = null) + { + var st = new StackTrace(); + var frames = st.GetFrames(); + foreach (var frame in frames) + { + var method = frame.GetMethod(); + if (method?.GetCustomAttribute() != null) + { + foreach (var parameter in method.GetParameters()) + { + ServiceBusTriggerAttribute serviceBusTriggerAttribute; + if (parameter.ParameterType == typeof(Message) + && (serviceBusTriggerAttribute = parameter.GetCustomAttribute()) != null) + { + if (serviceBusTriggerAttribute.AutoComplete) + { + // Autocomplete enabled -> no transactions + return Process(message, executionContext, functionsLogger); + } + else + { + // Autocomplete disabled -> transactions + return ProcessTransactional(message, executionContext, messageReceiver, functionsLogger); + } + } + } + } + } + + throw new Exception($"Could not locate {nameof(ServiceBusTriggerAttribute)} to infer AutoComplete setting."); + } + /// /// Processes a message received from an AzureServiceBus trigger using the NServiceBus message pipeline. All messages are committed transactionally with the successful processing of the incoming message. /// From 082c06c31bd1b37b40336b3279a83ad26e1e5016 Mon Sep 17 00:00:00 2001 From: Tim Bussmann Date: Thu, 5 Aug 2021 11:43:56 +0200 Subject: [PATCH 2/6] rename public APIs --- .../FunctionEndpoint.cs | 22 ++++++++++++++----- .../IFunctionEndpoint.cs | 8 +++++-- ...vals2.Can_enable_transactions.approved.txt | 4 ++-- ...verride_trigger_function_name.approved.txt | 6 ++--- ...rApprovals2.NameIsStringValue.approved.txt | 6 ++--- ...s2.Two_optionals_out_of_order.approved.txt | 4 ++-- ...rApprovals2.Use_two_optionals.approved.txt | 4 ++-- ...ngFullyQualifiedAttributeName.approved.txt | 6 ++--- ...atorApprovals2.UsingNamespace.approved.txt | 6 ++--- .../TriggerFunctionGenerator2.cs | 6 ++--- .../APIApprovals.Approve.approved.txt | 8 ++++++- src/ServiceBus.Tests/FunctionEndpointTests.cs | 10 ++++----- 12 files changed, 55 insertions(+), 35 deletions(-) diff --git a/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/FunctionEndpoint.cs b/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/FunctionEndpoint.cs index d1ecbf58..6c36da64 100644 --- a/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/FunctionEndpoint.cs +++ b/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/FunctionEndpoint.cs @@ -33,15 +33,15 @@ internal FunctionEndpoint(IStartableEndpointWithExternallyManagedContainer exter } /// - /// TODO. + /// Processes a message received from an AzureServiceBus trigger using the NServiceBus message pipeline. This method will lookup the setting to determine whether to use transactional or non-transactional processing. /// - public Task AutoDetectProcess(Message message, ExecutionContext executionContext, IMessageReceiver messageReceiver, ILogger functionsLogger = null) + public Task Process(Message message, ExecutionContext executionContext, IMessageReceiver messageReceiver, ILogger functionsLogger = null) { var st = new StackTrace(); var frames = st.GetFrames(); foreach (var frame in frames) { - var method = frame.GetMethod(); + var method = frame?.GetMethod(); if (method?.GetCustomAttribute() != null) { foreach (var parameter in method.GetParameters()) @@ -70,6 +70,7 @@ public Task AutoDetectProcess(Message message, ExecutionContext executionContext /// /// Processes a message received from an AzureServiceBus trigger using the NServiceBus message pipeline. All messages are committed transactionally with the successful processing of the incoming message. + /// Requires to be set to false! /// public async Task ProcessTransactional(Message message, ExecutionContext executionContext, IMessageReceiver messageReceiver, ILogger functionsLogger = null) { @@ -82,7 +83,7 @@ public async Task ProcessTransactional(Message message, ExecutionContext executi await InitializeEndpointIfNecessary(functionExecutionContext, CancellationToken.None) .ConfigureAwait(false); - await Process(message, + await ProcessInternal(message, tx => messageReceiver.SafeCompleteAsync(message, tx), () => CreateTransaction(), tx => CreateTransportTransaction(tx), @@ -116,6 +117,15 @@ TransportTransaction CreateTransportTransaction(CommittableTransaction transacti /// /// Processes a message received from an AzureServiceBus trigger using the NServiceBus message pipeline. /// + public Task ProcessNonTransactional(Message message, ExecutionContext executionContext, IMessageReceiver messageReceiver, ILogger functionsLogger = null) => Process(message, executionContext, functionsLogger); + + /// + /// Processes a message received from an AzureServiceBus trigger using the NServiceBus message pipeline. + /// + [ObsoleteEx( + ReplacementTypeOrMember = "Process(Message, ExecutionContext, IMessageReceiver, ILogger)", + TreatAsErrorFromVersion = "2", + RemoveInVersion = "3")] public async Task Process(Message message, ExecutionContext executionContext, ILogger functionsLogger = null) { FunctionsLoggerFactory.Instance.SetCurrentLogger(functionsLogger); @@ -125,7 +135,7 @@ public async Task Process(Message message, ExecutionContext executionContext, IL await InitializeEndpointIfNecessary(functionExecutionContext, CancellationToken.None) .ConfigureAwait(false); - await Process(message, + await ProcessInternal(message, _ => Task.CompletedTask, () => null, _ => new TransportTransaction(), @@ -133,7 +143,7 @@ await Process(message, .ConfigureAwait(false); } - internal static async Task Process(Message message, Func onComplete, Func transactionFactory, Func transportTransactionFactory, PipelineInvoker pipeline) + internal static async Task ProcessInternal(Message message, Func onComplete, Func transactionFactory, Func transportTransactionFactory, PipelineInvoker pipeline) { var messageId = message.GetMessageId(); diff --git a/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/IFunctionEndpoint.cs b/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/IFunctionEndpoint.cs index 496d5f40..5b7de981 100644 --- a/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/IFunctionEndpoint.cs +++ b/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/IFunctionEndpoint.cs @@ -14,13 +14,17 @@ public interface IFunctionEndpoint { /// - /// Processes a message received from an AzureServiceBus trigger using the NServiceBus message pipeline. All messages are committed transactionally with the successful processing of the incoming message. + /// Processes a message received from an AzureServiceBus trigger using the NServiceBus message pipeline. This method will lookup the setting to determine whether to use transactional or non-transactional processing. /// - Task ProcessTransactional(Message message, ExecutionContext executionContext, IMessageReceiver messageReceiver, ILogger functionsLogger = null); + Task Process(Message message, ExecutionContext executionContext, IMessageReceiver messageReceiver, ILogger functionsLogger = null); /// /// Processes a message received from an AzureServiceBus trigger using the NServiceBus message pipeline. /// + [ObsoleteEx( + ReplacementTypeOrMember = "Process(Message, ExecutionContext, IMessageReceiver, ILogger)", + TreatAsErrorFromVersion = "2", + RemoveInVersion = "3")] Task Process(Message message, ExecutionContext executionContext, ILogger functionsLogger = null); /// diff --git a/src/NServiceBus.AzureFunctions.SourceGenerator.Tests/ApprovalFiles/SourceGeneratorApprovals2.Can_enable_transactions.approved.txt b/src/NServiceBus.AzureFunctions.SourceGenerator.Tests/ApprovalFiles/SourceGeneratorApprovals2.Can_enable_transactions.approved.txt index bfdbfd4a..4c8ea9d2 100644 --- a/src/NServiceBus.AzureFunctions.SourceGenerator.Tests/ApprovalFiles/SourceGeneratorApprovals2.Can_enable_transactions.approved.txt +++ b/src/NServiceBus.AzureFunctions.SourceGenerator.Tests/ApprovalFiles/SourceGeneratorApprovals2.Can_enable_transactions.approved.txt @@ -8,9 +8,9 @@ using NServiceBus; class FunctionEndpointTrigger { - readonly IFunctionEndpoint endpoint; + readonly FunctionEndpoint endpoint; - public FunctionEndpointTrigger(IFunctionEndpoint endpoint) + public FunctionEndpointTrigger(FunctionEndpoint endpoint) { this.endpoint = endpoint; } diff --git a/src/NServiceBus.AzureFunctions.SourceGenerator.Tests/ApprovalFiles/SourceGeneratorApprovals2.Can_override_trigger_function_name.approved.txt b/src/NServiceBus.AzureFunctions.SourceGenerator.Tests/ApprovalFiles/SourceGeneratorApprovals2.Can_override_trigger_function_name.approved.txt index efad0593..fd792927 100644 --- a/src/NServiceBus.AzureFunctions.SourceGenerator.Tests/ApprovalFiles/SourceGeneratorApprovals2.Can_override_trigger_function_name.approved.txt +++ b/src/NServiceBus.AzureFunctions.SourceGenerator.Tests/ApprovalFiles/SourceGeneratorApprovals2.Can_override_trigger_function_name.approved.txt @@ -8,9 +8,9 @@ using NServiceBus; class FunctionEndpointTrigger { - readonly IFunctionEndpoint endpoint; + readonly FunctionEndpoint endpoint; - public FunctionEndpointTrigger(IFunctionEndpoint endpoint) + public FunctionEndpointTrigger(FunctionEndpoint endpoint) { this.endpoint = endpoint; } @@ -23,6 +23,6 @@ class FunctionEndpointTrigger ILogger logger, ExecutionContext executionContext) { - await endpoint.Process(message, executionContext, logger); + await endpoint.ProcessNonTransactional(message, executionContext, messageReceiver, logger); } } \ No newline at end of file diff --git a/src/NServiceBus.AzureFunctions.SourceGenerator.Tests/ApprovalFiles/SourceGeneratorApprovals2.NameIsStringValue.approved.txt b/src/NServiceBus.AzureFunctions.SourceGenerator.Tests/ApprovalFiles/SourceGeneratorApprovals2.NameIsStringValue.approved.txt index 99e48622..e16a7f0f 100644 --- a/src/NServiceBus.AzureFunctions.SourceGenerator.Tests/ApprovalFiles/SourceGeneratorApprovals2.NameIsStringValue.approved.txt +++ b/src/NServiceBus.AzureFunctions.SourceGenerator.Tests/ApprovalFiles/SourceGeneratorApprovals2.NameIsStringValue.approved.txt @@ -8,9 +8,9 @@ using NServiceBus; class FunctionEndpointTrigger { - readonly IFunctionEndpoint endpoint; + readonly FunctionEndpoint endpoint; - public FunctionEndpointTrigger(IFunctionEndpoint endpoint) + public FunctionEndpointTrigger(FunctionEndpoint endpoint) { this.endpoint = endpoint; } @@ -23,6 +23,6 @@ class FunctionEndpointTrigger ILogger logger, ExecutionContext executionContext) { - await endpoint.Process(message, executionContext, logger); + await endpoint.ProcessNonTransactional(message, executionContext, messageReceiver, logger); } } \ No newline at end of file diff --git a/src/NServiceBus.AzureFunctions.SourceGenerator.Tests/ApprovalFiles/SourceGeneratorApprovals2.Two_optionals_out_of_order.approved.txt b/src/NServiceBus.AzureFunctions.SourceGenerator.Tests/ApprovalFiles/SourceGeneratorApprovals2.Two_optionals_out_of_order.approved.txt index bbb25c31..70cc6c8a 100644 --- a/src/NServiceBus.AzureFunctions.SourceGenerator.Tests/ApprovalFiles/SourceGeneratorApprovals2.Two_optionals_out_of_order.approved.txt +++ b/src/NServiceBus.AzureFunctions.SourceGenerator.Tests/ApprovalFiles/SourceGeneratorApprovals2.Two_optionals_out_of_order.approved.txt @@ -8,9 +8,9 @@ using NServiceBus; class FunctionEndpointTrigger { - readonly IFunctionEndpoint endpoint; + readonly FunctionEndpoint endpoint; - public FunctionEndpointTrigger(IFunctionEndpoint endpoint) + public FunctionEndpointTrigger(FunctionEndpoint endpoint) { this.endpoint = endpoint; } diff --git a/src/NServiceBus.AzureFunctions.SourceGenerator.Tests/ApprovalFiles/SourceGeneratorApprovals2.Use_two_optionals.approved.txt b/src/NServiceBus.AzureFunctions.SourceGenerator.Tests/ApprovalFiles/SourceGeneratorApprovals2.Use_two_optionals.approved.txt index bbb25c31..70cc6c8a 100644 --- a/src/NServiceBus.AzureFunctions.SourceGenerator.Tests/ApprovalFiles/SourceGeneratorApprovals2.Use_two_optionals.approved.txt +++ b/src/NServiceBus.AzureFunctions.SourceGenerator.Tests/ApprovalFiles/SourceGeneratorApprovals2.Use_two_optionals.approved.txt @@ -8,9 +8,9 @@ using NServiceBus; class FunctionEndpointTrigger { - readonly IFunctionEndpoint endpoint; + readonly FunctionEndpoint endpoint; - public FunctionEndpointTrigger(IFunctionEndpoint endpoint) + public FunctionEndpointTrigger(FunctionEndpoint endpoint) { this.endpoint = endpoint; } diff --git a/src/NServiceBus.AzureFunctions.SourceGenerator.Tests/ApprovalFiles/SourceGeneratorApprovals2.UsingFullyQualifiedAttributeName.approved.txt b/src/NServiceBus.AzureFunctions.SourceGenerator.Tests/ApprovalFiles/SourceGeneratorApprovals2.UsingFullyQualifiedAttributeName.approved.txt index 99e48622..e16a7f0f 100644 --- a/src/NServiceBus.AzureFunctions.SourceGenerator.Tests/ApprovalFiles/SourceGeneratorApprovals2.UsingFullyQualifiedAttributeName.approved.txt +++ b/src/NServiceBus.AzureFunctions.SourceGenerator.Tests/ApprovalFiles/SourceGeneratorApprovals2.UsingFullyQualifiedAttributeName.approved.txt @@ -8,9 +8,9 @@ using NServiceBus; class FunctionEndpointTrigger { - readonly IFunctionEndpoint endpoint; + readonly FunctionEndpoint endpoint; - public FunctionEndpointTrigger(IFunctionEndpoint endpoint) + public FunctionEndpointTrigger(FunctionEndpoint endpoint) { this.endpoint = endpoint; } @@ -23,6 +23,6 @@ class FunctionEndpointTrigger ILogger logger, ExecutionContext executionContext) { - await endpoint.Process(message, executionContext, logger); + await endpoint.ProcessNonTransactional(message, executionContext, messageReceiver, logger); } } \ No newline at end of file diff --git a/src/NServiceBus.AzureFunctions.SourceGenerator.Tests/ApprovalFiles/SourceGeneratorApprovals2.UsingNamespace.approved.txt b/src/NServiceBus.AzureFunctions.SourceGenerator.Tests/ApprovalFiles/SourceGeneratorApprovals2.UsingNamespace.approved.txt index 99e48622..e16a7f0f 100644 --- a/src/NServiceBus.AzureFunctions.SourceGenerator.Tests/ApprovalFiles/SourceGeneratorApprovals2.UsingNamespace.approved.txt +++ b/src/NServiceBus.AzureFunctions.SourceGenerator.Tests/ApprovalFiles/SourceGeneratorApprovals2.UsingNamespace.approved.txt @@ -8,9 +8,9 @@ using NServiceBus; class FunctionEndpointTrigger { - readonly IFunctionEndpoint endpoint; + readonly FunctionEndpoint endpoint; - public FunctionEndpointTrigger(IFunctionEndpoint endpoint) + public FunctionEndpointTrigger(FunctionEndpoint endpoint) { this.endpoint = endpoint; } @@ -23,6 +23,6 @@ class FunctionEndpointTrigger ILogger logger, ExecutionContext executionContext) { - await endpoint.Process(message, executionContext, logger); + await endpoint.ProcessNonTransactional(message, executionContext, messageReceiver, logger); } } \ No newline at end of file diff --git a/src/NServiceBus.AzureFunctions.SourceGenerator/TriggerFunctionGenerator2.cs b/src/NServiceBus.AzureFunctions.SourceGenerator/TriggerFunctionGenerator2.cs index 98ab503b..47805cb6 100644 --- a/src/NServiceBus.AzureFunctions.SourceGenerator/TriggerFunctionGenerator2.cs +++ b/src/NServiceBus.AzureFunctions.SourceGenerator/TriggerFunctionGenerator2.cs @@ -116,9 +116,9 @@ public void Execute(GeneratorExecutionContext context) class FunctionEndpointTrigger {{ - readonly IFunctionEndpoint endpoint; + readonly FunctionEndpoint endpoint; - public FunctionEndpointTrigger(IFunctionEndpoint endpoint) + public FunctionEndpointTrigger(FunctionEndpoint endpoint) {{ this.endpoint = endpoint; }} @@ -133,7 +133,7 @@ public async Task Run( {{ {(syntaxReceiver.enableCrossEntityTransactions ? "await endpoint.ProcessTransactional(message, executionContext, messageReceiver, logger);" - : "await endpoint.Process(message, executionContext, logger);")} + : "await endpoint.ProcessNonTransactional(message, executionContext, messageReceiver, logger);")} }} }}"; context.AddSource("NServiceBus__FunctionEndpointTrigger", SourceText.From(source, Encoding.UTF8)); diff --git a/src/ServiceBus.Tests/ApprovalFiles/APIApprovals.Approve.approved.txt b/src/ServiceBus.Tests/ApprovalFiles/APIApprovals.Approve.approved.txt index b7783608..d46ca1f2 100644 --- a/src/ServiceBus.Tests/ApprovalFiles/APIApprovals.Approve.approved.txt +++ b/src/ServiceBus.Tests/ApprovalFiles/APIApprovals.Approve.approved.txt @@ -3,7 +3,11 @@ namespace NServiceBus { public class FunctionEndpoint : NServiceBus.IFunctionEndpoint { + [System.Obsolete("Use `Process(Message, ExecutionContext, IMessageReceiver, ILogger)` instead. Will" + + " be treated as an error from version 2.0.0. Will be removed in version 3.0.0.", false)] public System.Threading.Tasks.Task Process(Microsoft.Azure.ServiceBus.Message message, Microsoft.Azure.WebJobs.ExecutionContext executionContext, Microsoft.Extensions.Logging.ILogger functionsLogger = null) { } + public System.Threading.Tasks.Task Process(Microsoft.Azure.ServiceBus.Message message, Microsoft.Azure.WebJobs.ExecutionContext executionContext, Microsoft.Azure.ServiceBus.Core.IMessageReceiver messageReceiver, Microsoft.Extensions.Logging.ILogger functionsLogger = null) { } + public System.Threading.Tasks.Task ProcessNonTransactional(Microsoft.Azure.ServiceBus.Message message, Microsoft.Azure.WebJobs.ExecutionContext executionContext, Microsoft.Azure.ServiceBus.Core.IMessageReceiver messageReceiver, Microsoft.Extensions.Logging.ILogger functionsLogger = null) { } public System.Threading.Tasks.Task ProcessTransactional(Microsoft.Azure.ServiceBus.Message message, Microsoft.Azure.WebJobs.ExecutionContext executionContext, Microsoft.Azure.ServiceBus.Core.IMessageReceiver messageReceiver, Microsoft.Extensions.Logging.ILogger functionsLogger = null) { } public System.Threading.Tasks.Task Publish(object message, Microsoft.Azure.WebJobs.ExecutionContext executionContext, Microsoft.Extensions.Logging.ILogger functionsLogger = null) { } public System.Threading.Tasks.Task Publish(object message, NServiceBus.PublishOptions options, Microsoft.Azure.WebJobs.ExecutionContext executionContext, Microsoft.Extensions.Logging.ILogger functionsLogger = null) { } @@ -30,8 +34,10 @@ namespace NServiceBus } public interface IFunctionEndpoint { + [System.Obsolete("Use `Process(Message, ExecutionContext, IMessageReceiver, ILogger)` instead. Will" + + " be treated as an error from version 2.0.0. Will be removed in version 3.0.0.", false)] System.Threading.Tasks.Task Process(Microsoft.Azure.ServiceBus.Message message, Microsoft.Azure.WebJobs.ExecutionContext executionContext, Microsoft.Extensions.Logging.ILogger functionsLogger = null); - System.Threading.Tasks.Task ProcessTransactional(Microsoft.Azure.ServiceBus.Message message, Microsoft.Azure.WebJobs.ExecutionContext executionContext, Microsoft.Azure.ServiceBus.Core.IMessageReceiver messageReceiver, Microsoft.Extensions.Logging.ILogger functionsLogger = null); + System.Threading.Tasks.Task Process(Microsoft.Azure.ServiceBus.Message message, Microsoft.Azure.WebJobs.ExecutionContext executionContext, Microsoft.Azure.ServiceBus.Core.IMessageReceiver messageReceiver, Microsoft.Extensions.Logging.ILogger functionsLogger = null); System.Threading.Tasks.Task Publish(object message, Microsoft.Azure.WebJobs.ExecutionContext executionContext, Microsoft.Extensions.Logging.ILogger functionsLogger = null); System.Threading.Tasks.Task Publish(object message, NServiceBus.PublishOptions options, Microsoft.Azure.WebJobs.ExecutionContext executionContext, Microsoft.Extensions.Logging.ILogger functionsLogger = null); System.Threading.Tasks.Task Publish(System.Action messageConstructor, Microsoft.Azure.WebJobs.ExecutionContext executionContext, Microsoft.Extensions.Logging.ILogger functionsLogger = null); diff --git a/src/ServiceBus.Tests/FunctionEndpointTests.cs b/src/ServiceBus.Tests/FunctionEndpointTests.cs index c1693989..1edaff22 100644 --- a/src/ServiceBus.Tests/FunctionEndpointTests.cs +++ b/src/ServiceBus.Tests/FunctionEndpointTests.cs @@ -27,7 +27,7 @@ public async Task When_processing_successful_should_complete_message() }); - await FunctionEndpoint.Process( + await FunctionEndpoint.ProcessInternal( message, tx => { @@ -59,7 +59,7 @@ public async Task When_processing_fails_should_provide_error_context() return Task.FromResult(ErrorHandleResult.Handled); }); - await FunctionEndpoint.Process( + await FunctionEndpoint.ProcessInternal( message, tx => Task.CompletedTask, () => null, @@ -89,7 +89,7 @@ public async Task When_error_pipeline_fails_should_throw() _ => throw errorPipelineException); var exception = Assert.ThrowsAsync(async () => - await FunctionEndpoint.Process( + await FunctionEndpoint.ProcessInternal( MessageHelper.GenerateMessage(new TestMessage()), tx => { @@ -112,7 +112,7 @@ public async Task When_error_pipeline_handles_error_should_complete_message() _ => throw new Exception("main pipeline failure"), _ => Task.FromResult(ErrorHandleResult.Handled)); - await FunctionEndpoint.Process( + await FunctionEndpoint.ProcessInternal( MessageHelper.GenerateMessage(new TestMessage()), tx => { @@ -136,7 +136,7 @@ public async Task When_error_pipeline_requires_retry_should_throw() _ => Task.FromResult(ErrorHandleResult.RetryRequired)); var exception = Assert.ThrowsAsync(async () => - await FunctionEndpoint.Process( + await FunctionEndpoint.ProcessInternal( MessageHelper.GenerateMessage(new TestMessage()), tx => { From a148188a9d38679b0e40a9fa841e96b4b9ebd46b Mon Sep 17 00:00:00 2001 From: Tim Bussmann Date: Thu, 5 Aug 2021 11:46:21 +0200 Subject: [PATCH 3/6] remove obsolete method usage --- src/ServiceBus.Tests/FunctionEndpointComponent.cs | 2 +- .../When_shipping_handlers_in_dedicated_assembly.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ServiceBus.Tests/FunctionEndpointComponent.cs b/src/ServiceBus.Tests/FunctionEndpointComponent.cs index 3aa61dbf..a5b5f21e 100644 --- a/src/ServiceBus.Tests/FunctionEndpointComponent.cs +++ b/src/ServiceBus.Tests/FunctionEndpointComponent.cs @@ -100,7 +100,7 @@ public override async Task ComponentsStarted(CancellationToken token) { var transportMessage = MessageHelper.GenerateMessage(message); var context = new ExecutionContext(); - await endpoint.Process(transportMessage, context); + await endpoint.ProcessNonTransactional(transportMessage, context, null); } } diff --git a/src/ServiceBus.Tests/When_shipping_handlers_in_dedicated_assembly.cs b/src/ServiceBus.Tests/When_shipping_handlers_in_dedicated_assembly.cs index da2e6ec5..23dd7501 100644 --- a/src/ServiceBus.Tests/When_shipping_handlers_in_dedicated_assembly.cs +++ b/src/ServiceBus.Tests/When_shipping_handlers_in_dedicated_assembly.cs @@ -42,7 +42,7 @@ public async Task Should_load_handlers_from_assembly_when_using_FunctionsHostBui // we need to process an actual message to have the endpoint being created - await endpoint.Process(GenerateMessage(), new ExecutionContext()); + await endpoint.ProcessNonTransactional(GenerateMessage(), new ExecutionContext(), null); // The message handler assembly should be loaded now because scanning should find and load the handler assembly Assert.True(AppDomain.CurrentDomain.GetAssemblies().Any(a => a.FullName == "Testing.Handlers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null")); From ade72e6be4f556e16aada80d67c08eced0e3e8fd Mon Sep 17 00:00:00 2001 From: Tim Bussmann Date: Thu, 5 Aug 2021 12:24:00 +0200 Subject: [PATCH 4/6] extract reflection code into helper --- .../FunctionEndpoint.cs | 37 +---- .../ReflectionHelper.cs | 35 +++++ src/ServiceBus.Tests/ReflectionHelperTests.cs | 148 ++++++++++++++++++ src/ServiceBus.Tests/ServiceBus.Tests.csproj | 1 + 4 files changed, 188 insertions(+), 33 deletions(-) create mode 100644 src/NServiceBus.AzureFunctions.InProcess.ServiceBus/ReflectionHelper.cs create mode 100644 src/ServiceBus.Tests/ReflectionHelperTests.cs diff --git a/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/FunctionEndpoint.cs b/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/FunctionEndpoint.cs index 6c36da64..cb82754b 100644 --- a/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/FunctionEndpoint.cs +++ b/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/FunctionEndpoint.cs @@ -1,7 +1,6 @@ namespace NServiceBus { using System; - using System.Diagnostics; using System.IO; using System.Reflection; using System.Runtime.Loader; @@ -35,38 +34,10 @@ internal FunctionEndpoint(IStartableEndpointWithExternallyManagedContainer exter /// /// Processes a message received from an AzureServiceBus trigger using the NServiceBus message pipeline. This method will lookup the setting to determine whether to use transactional or non-transactional processing. /// - public Task Process(Message message, ExecutionContext executionContext, IMessageReceiver messageReceiver, ILogger functionsLogger = null) - { - var st = new StackTrace(); - var frames = st.GetFrames(); - foreach (var frame in frames) - { - var method = frame?.GetMethod(); - if (method?.GetCustomAttribute() != null) - { - foreach (var parameter in method.GetParameters()) - { - ServiceBusTriggerAttribute serviceBusTriggerAttribute; - if (parameter.ParameterType == typeof(Message) - && (serviceBusTriggerAttribute = parameter.GetCustomAttribute()) != null) - { - if (serviceBusTriggerAttribute.AutoComplete) - { - // Autocomplete enabled -> no transactions - return Process(message, executionContext, functionsLogger); - } - else - { - // Autocomplete disabled -> transactions - return ProcessTransactional(message, executionContext, messageReceiver, functionsLogger); - } - } - } - } - } - - throw new Exception($"Could not locate {nameof(ServiceBusTriggerAttribute)} to infer AutoComplete setting."); - } + public Task Process(Message message, ExecutionContext executionContext, IMessageReceiver messageReceiver, ILogger functionsLogger = null) => + ReflectionHelper.GetAutoCompleteValue() + ? ProcessNonTransactional(message, executionContext, messageReceiver, functionsLogger) + : ProcessTransactional(message, executionContext, messageReceiver, functionsLogger); /// /// Processes a message received from an AzureServiceBus trigger using the NServiceBus message pipeline. All messages are committed transactionally with the successful processing of the incoming message. diff --git a/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/ReflectionHelper.cs b/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/ReflectionHelper.cs new file mode 100644 index 00000000..27d2898f --- /dev/null +++ b/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/ReflectionHelper.cs @@ -0,0 +1,35 @@ +namespace NServiceBus.AzureFunctions.InProcess.ServiceBus +{ + using System; + using System.Diagnostics; + using System.Reflection; + using Microsoft.Azure.ServiceBus; + using Microsoft.Azure.WebJobs; + + class ReflectionHelper + { + public static bool GetAutoCompleteValue() + { + var st = new StackTrace(skipFrames: 1); // skip first frame because it is this method + var frames = st.GetFrames(); + foreach (var frame in frames) + { + var method = frame?.GetMethod(); + if (method?.GetCustomAttribute() != null) + { + foreach (var parameter in method.GetParameters()) + { + ServiceBusTriggerAttribute serviceBusTriggerAttribute; + if (parameter.ParameterType == typeof(Message) + && (serviceBusTriggerAttribute = parameter.GetCustomAttribute()) != null) + { + return serviceBusTriggerAttribute.AutoComplete; + } + } + } + } + + throw new Exception($"Could not locate {nameof(ServiceBusTriggerAttribute)} to infer AutoComplete setting."); + } + } +} \ No newline at end of file diff --git a/src/ServiceBus.Tests/ReflectionHelperTests.cs b/src/ServiceBus.Tests/ReflectionHelperTests.cs new file mode 100644 index 00000000..aa88e485 --- /dev/null +++ b/src/ServiceBus.Tests/ReflectionHelperTests.cs @@ -0,0 +1,148 @@ +namespace ServiceBus.Tests +{ + using System; + using Microsoft.Azure.ServiceBus; + using Microsoft.Azure.WebJobs; + using NServiceBus.AzureFunctions.InProcess.ServiceBus; + using NUnit.Framework; + + [TestFixture] + public class ReflectionHelperTests + { + [Test] + public void When_no_attributes_defined_should_throw() + { + var exception = Assert.Throws(() => ReflectionHelper.GetAutoCompleteValue()); + + StringAssert.Contains($"Could not locate {nameof(ServiceBusTriggerAttribute)} to infer AutoComplete setting.", exception.Message); + } + + [Test] + public void When_no_function_name_attribute_defined_should_throw() + { + var exception = Assert.Throws(() => FunctionWithNoFunctionNameAttribute(null)); + + StringAssert.Contains($"Could not locate {nameof(ServiceBusTriggerAttribute)} to infer AutoComplete setting.", exception.Message); + + void FunctionWithNoFunctionNameAttribute( + [ServiceBusTrigger("queueName", "subscriptionname", AutoComplete = true)] Message _) + { + ReflectionHelper.GetAutoCompleteValue(); + } + } + + [Test] + public void When_no_trigger_attribute_defined_should_throw() + { + var exception = Assert.Throws(() => FunctionWithNoServiceBusTriggerAttribute(null)); + + StringAssert.Contains($"Could not locate {nameof(ServiceBusTriggerAttribute)} to infer AutoComplete setting.", exception.Message); + + [FunctionName("TestFunction")] + void FunctionWithNoServiceBusTriggerAttribute( + Message _) + { + ReflectionHelper.GetAutoCompleteValue(); + } + } + + [Test] + public void When_auto_complete_set_to_false_should_return_false() + { + FunctionTriggerWithAutoCompleteExplicitlySetToFalse(null); + + [FunctionName("TestFunction")] + void FunctionTriggerWithAutoCompleteExplicitlySetToFalse( + [ServiceBusTrigger("queueName", "subscriptionname", AutoComplete = false)] Message _) + { + Assert.IsFalse(ReflectionHelper.GetAutoCompleteValue()); + } + } + + [Test] + public void When_auto_complete_set_to_true_should_return_true() + { + FunctionTriggerWithAutoCompleteExplicitlySetToTrue(null); + + [FunctionName("TestFunction")] + void FunctionTriggerWithAutoCompleteExplicitlySetToTrue( + [ServiceBusTrigger("queueName", "subscriptionname", AutoComplete = true)] Message _) + { + Assert.IsTrue(ReflectionHelper.GetAutoCompleteValue()); + } + } + + [Test] + public void When_auto_complete_not_set_should_return_true() + { + FunctionTriggerWithoutAutoCompleteConfiguration(null); + + [FunctionName("TestFunction")] + void FunctionTriggerWithoutAutoCompleteConfiguration( + [ServiceBusTrigger("queueName", "subscriptionname")] Message _) + { + Assert.True(ReflectionHelper.GetAutoCompleteValue()); + } + } + + [Test] + public void When_helper_invoked_in_nested_methods() + { + NestedTrigger(null); + + [FunctionName("TestFunction")] + void NestedTrigger( + [ServiceBusTrigger("queueName", "subscriptionname", AutoComplete = false)] Message _) + { + One(); + } + + void One() + { + Two(); + } + + void Two() + { + Three(); + } + + void Three() + { + Assert.IsFalse(ReflectionHelper.GetAutoCompleteValue()); + } + } + + [Test] + public void When_helper_invoked_in_local_function() + { + LocalFunction(null); + + [FunctionName("TestFunction")] + void LocalFunction( + [ServiceBusTrigger("queueName", "subscriptionname", AutoComplete = false)] Message _) + { + LocalFunction(); + + void LocalFunction() + { + Assert.IsFalse(ReflectionHelper.GetAutoCompleteValue()); + } + } + } + + [Test] + public void When_helper_invoked_in_expression() + { + Expression(null); + + [FunctionName("TestFunction")] + void Expression( + [ServiceBusTrigger("queueName", "subscriptionname", AutoComplete = false)] Message _) + { + Func expression = () => ReflectionHelper.GetAutoCompleteValue(); + Assert.IsFalse(expression()); + } + } + } +} \ No newline at end of file diff --git a/src/ServiceBus.Tests/ServiceBus.Tests.csproj b/src/ServiceBus.Tests/ServiceBus.Tests.csproj index 22cc3d0f..dfe67d94 100644 --- a/src/ServiceBus.Tests/ServiceBus.Tests.csproj +++ b/src/ServiceBus.Tests/ServiceBus.Tests.csproj @@ -4,6 +4,7 @@ netcoreapp3.1;net5.0 true ..\Test.snk + 9 From 23f9d150a65d212ec8989510dfbe917f828654ca Mon Sep 17 00:00:00 2001 From: Tim Bussmann Date: Fri, 6 Aug 2021 10:47:02 +0200 Subject: [PATCH 5/6] add more details to the reflection helper exception message --- .../ReflectionHelper.cs | 2 +- src/ServiceBus.Tests/ReflectionHelperTests.cs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/ReflectionHelper.cs b/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/ReflectionHelper.cs index 27d2898f..a0583a2d 100644 --- a/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/ReflectionHelper.cs +++ b/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/ReflectionHelper.cs @@ -29,7 +29,7 @@ public static bool GetAutoCompleteValue() } } - throw new Exception($"Could not locate {nameof(ServiceBusTriggerAttribute)} to infer AutoComplete setting."); + throw new Exception($"Could not locate {nameof(ServiceBusTriggerAttribute)} to infer the AutoComplete setting. Make sure that the function trigger contains a parameter decorated with {nameof(ServiceBusTriggerAttribute)} or use the advanced APIs exposed via the {nameof(FunctionEndpoint)} type instead."); } } } \ No newline at end of file diff --git a/src/ServiceBus.Tests/ReflectionHelperTests.cs b/src/ServiceBus.Tests/ReflectionHelperTests.cs index aa88e485..bea7620f 100644 --- a/src/ServiceBus.Tests/ReflectionHelperTests.cs +++ b/src/ServiceBus.Tests/ReflectionHelperTests.cs @@ -14,7 +14,7 @@ public void When_no_attributes_defined_should_throw() { var exception = Assert.Throws(() => ReflectionHelper.GetAutoCompleteValue()); - StringAssert.Contains($"Could not locate {nameof(ServiceBusTriggerAttribute)} to infer AutoComplete setting.", exception.Message); + StringAssert.Contains($"Could not locate {nameof(ServiceBusTriggerAttribute)} to infer the AutoComplete setting.", exception.Message); } [Test] @@ -22,7 +22,7 @@ public void When_no_function_name_attribute_defined_should_throw() { var exception = Assert.Throws(() => FunctionWithNoFunctionNameAttribute(null)); - StringAssert.Contains($"Could not locate {nameof(ServiceBusTriggerAttribute)} to infer AutoComplete setting.", exception.Message); + StringAssert.Contains($"Could not locate {nameof(ServiceBusTriggerAttribute)} to infer the AutoComplete setting.", exception.Message); void FunctionWithNoFunctionNameAttribute( [ServiceBusTrigger("queueName", "subscriptionname", AutoComplete = true)] Message _) @@ -36,7 +36,7 @@ public void When_no_trigger_attribute_defined_should_throw() { var exception = Assert.Throws(() => FunctionWithNoServiceBusTriggerAttribute(null)); - StringAssert.Contains($"Could not locate {nameof(ServiceBusTriggerAttribute)} to infer AutoComplete setting.", exception.Message); + StringAssert.Contains($"Could not locate {nameof(ServiceBusTriggerAttribute)} to infer the AutoComplete setting.", exception.Message); [FunctionName("TestFunction")] void FunctionWithNoServiceBusTriggerAttribute( From 07081a9862468e6072581fc08f79fa44f1c3f52e Mon Sep 17 00:00:00 2001 From: Tim Bussmann Date: Fri, 6 Aug 2021 12:47:19 +0200 Subject: [PATCH 6/6] merge AttributeDiscoverer and ReflectionHelper methods --- .../AttributeDiscoverer.cs | 38 ------------------- .../ReflectionHelper.cs | 25 +++++++++--- ...erviceBusTriggeredEndpointConfiguration.cs | 7 ++-- 3 files changed, 23 insertions(+), 47 deletions(-) delete mode 100644 src/NServiceBus.AzureFunctions.InProcess.ServiceBus/AttributeDiscoverer.cs diff --git a/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/AttributeDiscoverer.cs b/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/AttributeDiscoverer.cs deleted file mode 100644 index 1a1434c7..00000000 --- a/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/AttributeDiscoverer.cs +++ /dev/null @@ -1,38 +0,0 @@ -namespace NServiceBus.AzureFunctions.InProcess.ServiceBus -{ - using System; - using System.Diagnostics; - using System.Reflection; - using Microsoft.Azure.WebJobs; - - static class TriggerDiscoverer - { - /// - /// Attempts to derive the required configuration information from the Azure Function and trigger attributes via reflection. - /// - public static TTransportTriggerAttribute TryGet() where TTransportTriggerAttribute : Attribute - { - var frames = new StackTrace().GetFrames(); - foreach (var stackFrame in frames) - { - var method = stackFrame.GetMethod(); - var functionAttribute = method.GetCustomAttribute(false); - if (functionAttribute != null) - { - foreach (var parameter in method.GetParameters()) - { - var triggerConfiguration = parameter.GetCustomAttribute(false); - if (triggerConfiguration != null) - { - return triggerConfiguration; - } - } - - return null; - } - } - - return null; - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/ReflectionHelper.cs b/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/ReflectionHelper.cs index a0583a2d..21d7bd08 100644 --- a/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/ReflectionHelper.cs +++ b/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/ReflectionHelper.cs @@ -10,26 +10,41 @@ class ReflectionHelper { public static bool GetAutoCompleteValue() { - var st = new StackTrace(skipFrames: 1); // skip first frame because it is this method + var triggerAttribute = FindTriggerAttributeInternal(); + if (triggerAttribute != null) + { + return triggerAttribute.AutoComplete; + } + + throw new Exception($"Could not locate {nameof(ServiceBusTriggerAttribute)} to infer the AutoComplete setting. Make sure that the function trigger contains a parameter decorated with {nameof(ServiceBusTriggerAttribute)} or use the advanced APIs exposed via the {nameof(FunctionEndpoint)} type instead."); + } + + public static ServiceBusTriggerAttribute FindBusTriggerAttribute() => FindTriggerAttributeInternal(); + + static ServiceBusTriggerAttribute FindTriggerAttributeInternal() + { + var st = new StackTrace(skipFrames: 2); // skip first two frames because it is this method + the public method var frames = st.GetFrames(); foreach (var frame in frames) { var method = frame?.GetMethod(); - if (method?.GetCustomAttribute() != null) + if (method?.GetCustomAttribute(false) != null) { foreach (var parameter in method.GetParameters()) { ServiceBusTriggerAttribute serviceBusTriggerAttribute; if (parameter.ParameterType == typeof(Message) - && (serviceBusTriggerAttribute = parameter.GetCustomAttribute()) != null) + && (serviceBusTriggerAttribute = parameter.GetCustomAttribute(false)) != null) { - return serviceBusTriggerAttribute.AutoComplete; + return serviceBusTriggerAttribute; } } + + return null; } } - throw new Exception($"Could not locate {nameof(ServiceBusTriggerAttribute)} to infer the AutoComplete setting. Make sure that the function trigger contains a parameter decorated with {nameof(ServiceBusTriggerAttribute)} or use the advanced APIs exposed via the {nameof(FunctionEndpoint)} type instead."); + return null; } } } \ 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 0b3a6cc0..3a2f544e 100644 --- a/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/ServiceBusTriggeredEndpointConfiguration.cs +++ b/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/ServiceBusTriggeredEndpointConfiguration.cs @@ -4,7 +4,6 @@ using System.Threading.Tasks; using AzureFunctions.InProcess.ServiceBus; using Logging; - using Microsoft.Azure.WebJobs; using Serialization; using Transport; @@ -77,10 +76,10 @@ public ServiceBusTriggeredEndpointConfiguration(string endpointName, string conn /// public static ServiceBusTriggeredEndpointConfiguration FromAttributes() { - var configuration = TriggerDiscoverer.TryGet(); - if (configuration != null) + var serviceBusTriggerAttribute = ReflectionHelper.FindBusTriggerAttribute(); + if (serviceBusTriggerAttribute != null) { - return new ServiceBusTriggeredEndpointConfiguration(configuration.QueueName, configuration.Connection); + return new ServiceBusTriggeredEndpointConfiguration(serviceBusTriggerAttribute.QueueName, serviceBusTriggerAttribute.Connection); } throw new Exception(