From 2647f1f951c8d79ea2d05eea2a36d82bec88a46d Mon Sep 17 00:00:00 2001 From: JoshLove-msft <54595583+JoshLove-msft@users.noreply.github.com> Date: Mon, 6 Feb 2023 10:54:32 -0800 Subject: [PATCH 01/13] Add ability to bind to SBReceivedMessage --- .../src/Properties/AssemblyInfo.cs | 2 +- .../src/ServiceBusExtensionStartup.cs | 28 +++++++++++ .../src/ServiceBusReceivedMessageConverter.cs | 41 +++++++++++++++++ .../src/ServiceBusTriggerAttribute.cs | 1 + .../src/Worker.Extensions.ServiceBus.csproj | 14 +++++- test/E2ETests/E2EApps/E2EApp/E2EApp.csproj | 1 + .../FunctionMetadataGeneratorTests.cs | 46 +++++++++++++++++++ .../SdkTests.csproj | 1 + 8 files changed, 131 insertions(+), 3 deletions(-) create mode 100644 extensions/Worker.Extensions.ServiceBus/src/ServiceBusExtensionStartup.cs create mode 100644 extensions/Worker.Extensions.ServiceBus/src/ServiceBusReceivedMessageConverter.cs diff --git a/extensions/Worker.Extensions.ServiceBus/src/Properties/AssemblyInfo.cs b/extensions/Worker.Extensions.ServiceBus/src/Properties/AssemblyInfo.cs index 88b2ff13b..f3d09d159 100644 --- a/extensions/Worker.Extensions.ServiceBus/src/Properties/AssemblyInfo.cs +++ b/extensions/Worker.Extensions.ServiceBus/src/Properties/AssemblyInfo.cs @@ -3,4 +3,4 @@ using Microsoft.Azure.Functions.Worker.Extensions.Abstractions; -[assembly: ExtensionInformation("Microsoft.Azure.WebJobs.Extensions.ServiceBus", "5.7.0")] +[assembly: ExtensionInformation("Microsoft.Azure.WebJobs.Extensions.ServiceBus", "5.9.0-alpha.20230203.1")] diff --git a/extensions/Worker.Extensions.ServiceBus/src/ServiceBusExtensionStartup.cs b/extensions/Worker.Extensions.ServiceBus/src/ServiceBusExtensionStartup.cs new file mode 100644 index 000000000..aff8ba1c6 --- /dev/null +++ b/extensions/Worker.Extensions.ServiceBus/src/ServiceBusExtensionStartup.cs @@ -0,0 +1,28 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.IO; +using Microsoft.Azure.Functions.Worker.Core; +using Microsoft.Extensions.Azure; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.Azure.Functions.Worker; + +public class ServiceBusExtensionStartup : WorkerExtensionStartup +{ + public override void Configure(IFunctionsWorkerApplicationBuilder applicationBuilder) + { + if (applicationBuilder == null) + { + throw new ArgumentNullException(nameof(applicationBuilder)); + } + + applicationBuilder.Services.AddAzureClientsCore(); // Adds AzureComponentFactory + + applicationBuilder.Services.Configure((workerOption) => + { + workerOption.InputConverters.RegisterAt(0); + }); + } +} \ No newline at end of file diff --git a/extensions/Worker.Extensions.ServiceBus/src/ServiceBusReceivedMessageConverter.cs b/extensions/Worker.Extensions.ServiceBus/src/ServiceBusReceivedMessageConverter.cs new file mode 100644 index 000000000..17836e45e --- /dev/null +++ b/extensions/Worker.Extensions.ServiceBus/src/ServiceBusReceivedMessageConverter.cs @@ -0,0 +1,41 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.IO; +using System.Threading.Tasks; +using Azure.Messaging.ServiceBus; +using Azure.Messaging.ServiceBus.Primitives; +using Microsoft.Azure.Functions.Worker.Converters; +using Microsoft.Azure.Functions.Worker.Core; + +namespace Microsoft.Azure.Functions.Worker; + +internal class ServiceBusReceivedMessageConverter : IInputConverter +{ + public ValueTask ConvertAsync(ConverterContext context) + { + if (context is null) + { + return new ValueTask(ConversionResult.Unhandled()); + } + + return context.Source switch + { + ModelBindingData binding => ConvertToServiceBusReceivedMessage(context, binding), + _ => new ValueTask(ConversionResult.Unhandled()), + }; + } + + private ValueTask ConvertToServiceBusReceivedMessage(ConverterContext context, ModelBindingData binding) + { + // The lock token is a 16 byte GUID + const int lockTokenLength = 16; + + ReadOnlyMemory bytes = binding.Content.ToMemory(); + ReadOnlyMemory lockTokenBytes = bytes.Slice(0, lockTokenLength); + ReadOnlyMemory messageBytes = bytes.Slice(lockTokenLength, bytes.Length - lockTokenLength); + ServiceBusReceivedMessage message = ServiceBusAmqpExtensions.FromAmqpBytes(BinaryData.FromBytes(messageBytes), BinaryData.FromBytes(lockTokenBytes)); + return new ValueTask(ConversionResult.Success(message)); + } +} \ No newline at end of file diff --git a/extensions/Worker.Extensions.ServiceBus/src/ServiceBusTriggerAttribute.cs b/extensions/Worker.Extensions.ServiceBus/src/ServiceBusTriggerAttribute.cs index 35ef6f461..2172e0ff3 100644 --- a/extensions/Worker.Extensions.ServiceBus/src/ServiceBusTriggerAttribute.cs +++ b/extensions/Worker.Extensions.ServiceBus/src/ServiceBusTriggerAttribute.cs @@ -5,6 +5,7 @@ namespace Microsoft.Azure.Functions.Worker { + [SupportsDeferredBinding] public sealed class ServiceBusTriggerAttribute : TriggerBindingAttribute, ISupportCardinality { private bool _isBatched = false; diff --git a/extensions/Worker.Extensions.ServiceBus/src/Worker.Extensions.ServiceBus.csproj b/extensions/Worker.Extensions.ServiceBus/src/Worker.Extensions.ServiceBus.csproj index 8a15ad193..ff9eda14c 100644 --- a/extensions/Worker.Extensions.ServiceBus/src/Worker.Extensions.ServiceBus.csproj +++ b/extensions/Worker.Extensions.ServiceBus/src/Worker.Extensions.ServiceBus.csproj @@ -6,16 +6,26 @@ Azure Service Bus extensions for .NET isolated functions - 5.7.0 + 5.9.0 false - + + + + + + + + + + + diff --git a/test/E2ETests/E2EApps/E2EApp/E2EApp.csproj b/test/E2ETests/E2EApps/E2EApp/E2EApp.csproj index f6fd3de04..42065957e 100644 --- a/test/E2ETests/E2EApps/E2EApp/E2EApp.csproj +++ b/test/E2ETests/E2EApps/E2EApp/E2EApp.csproj @@ -18,6 +18,7 @@ + diff --git a/test/FunctionMetadataGeneratorTests/FunctionMetadataGeneratorTests.cs b/test/FunctionMetadataGeneratorTests/FunctionMetadataGeneratorTests.cs index bd7cf1140..a6b46a665 100644 --- a/test/FunctionMetadataGeneratorTests/FunctionMetadataGeneratorTests.cs +++ b/test/FunctionMetadataGeneratorTests/FunctionMetadataGeneratorTests.cs @@ -9,6 +9,7 @@ using System.Linq; using System.Reflection; using System.Threading.Tasks; +using Azure.Messaging.ServiceBus; using Microsoft.Azure.Functions.Tests; using Microsoft.Azure.Functions.Worker; using Microsoft.Azure.Functions.Worker.Http; @@ -244,6 +245,41 @@ void ValidateQueueOutput(ExpandoObject b) } } + [Fact] + public void ServiceBusTriggerFunction_SDKTypeBindings() + { + var generator = new FunctionMetadataGenerator(); + var module = ModuleDefinition.ReadModule(_thisAssembly.Location); + var typeDef = TestUtility.GetTypeDefinition(typeof(ServiceBusSDKBindings)); + var functions = generator.GenerateFunctionMetadata(typeDef); + var extensions = generator.Extensions; + + Assert.Equal(1, functions.Count()); + + var serviceBusTrigger = functions.Single(p => p.Name == "ServiceBusTriggerFunction"); + + ValidateFunction(serviceBusTrigger, "ServiceBusTriggerFunction", + GetEntryPoint(nameof(ServiceBusSDKBindings), nameof(ServiceBusSDKBindings.ServiceBusFunction)), + b => ValidateServiceBusTrigger(b)); + + AssertDictionary(extensions, new Dictionary + { + { "Microsoft.Azure.WebJobs.Extensions.ServiceBus", "5.9.0-alpha.20230203.1" }, + }); + + void ValidateServiceBusTrigger(ExpandoObject b) + { + AssertExpandoObject(b, new Dictionary + { + { "Name", "message" }, + { "Type", "serviceBusTrigger" }, + { "Direction", "In" }, + { "queueName", "queue" }, + { "Properties", new Dictionary() { { "SupportsDeferredBinding", "True" } } } + }); + } + } + [Fact] public void TimerFunction() { @@ -750,6 +786,16 @@ public object BlobToBlobs( } } + private class ServiceBusSDKBindings + { + [Function("ServiceBusTriggerFunction")] + public object ServiceBusFunction( + [ServiceBusTrigger("queue")] ServiceBusReceivedMessage message) + { + throw new NotImplementedException(); + } + } + private class ExternalType_Return { public const string FunctionName = "BasicHttpWithExternalTypeReturn"; diff --git a/test/FunctionMetadataGeneratorTests/SdkTests.csproj b/test/FunctionMetadataGeneratorTests/SdkTests.csproj index afde18e26..4fb4cbe92 100644 --- a/test/FunctionMetadataGeneratorTests/SdkTests.csproj +++ b/test/FunctionMetadataGeneratorTests/SdkTests.csproj @@ -34,6 +34,7 @@ + From f00735c8fb405a4a8f245da44b85ace038783429 Mon Sep 17 00:00:00 2001 From: JoshLove-msft <54595583+JoshLove-msft@users.noreply.github.com> Date: Mon, 6 Feb 2023 18:16:59 -0800 Subject: [PATCH 02/13] Add assembly attribute --- .../src/ServiceBusExtensionStartup.cs | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/extensions/Worker.Extensions.ServiceBus/src/ServiceBusExtensionStartup.cs b/extensions/Worker.Extensions.ServiceBus/src/ServiceBusExtensionStartup.cs index aff8ba1c6..00414e28f 100644 --- a/extensions/Worker.Extensions.ServiceBus/src/ServiceBusExtensionStartup.cs +++ b/extensions/Worker.Extensions.ServiceBus/src/ServiceBusExtensionStartup.cs @@ -2,27 +2,30 @@ // Licensed under the MIT License. See License.txt in the project root for license information. using System; -using System.IO; +using Microsoft.Azure.Functions.Worker; using Microsoft.Azure.Functions.Worker.Core; using Microsoft.Extensions.Azure; using Microsoft.Extensions.DependencyInjection; -namespace Microsoft.Azure.Functions.Worker; +[assembly: WorkerExtensionStartup(typeof(ServiceBusExtensionStartup))] -public class ServiceBusExtensionStartup : WorkerExtensionStartup +namespace Microsoft.Azure.Functions.Worker { - public override void Configure(IFunctionsWorkerApplicationBuilder applicationBuilder) + public class ServiceBusExtensionStartup : WorkerExtensionStartup { - if (applicationBuilder == null) + public override void Configure(IFunctionsWorkerApplicationBuilder applicationBuilder) { - throw new ArgumentNullException(nameof(applicationBuilder)); - } + if (applicationBuilder == null) + { + throw new ArgumentNullException(nameof(applicationBuilder)); + } - applicationBuilder.Services.AddAzureClientsCore(); // Adds AzureComponentFactory + applicationBuilder.Services.AddAzureClientsCore(); // Adds AzureComponentFactory - applicationBuilder.Services.Configure((workerOption) => - { - workerOption.InputConverters.RegisterAt(0); - }); + applicationBuilder.Services.Configure((workerOption) => + { + workerOption.InputConverters.RegisterAt(0); + }); + } } } \ No newline at end of file From 54784132e8f05973b2b1f825674f47f6675e636c Mon Sep 17 00:00:00 2001 From: JoshLove-msft <54595583+JoshLove-msft@users.noreply.github.com> Date: Wed, 15 Feb 2023 21:47:08 -0800 Subject: [PATCH 03/13] PR fb --- .../src/ServiceBusReceivedMessageConverter.cs | 27 ++++++++++--------- .../src/Worker.Extensions.ServiceBus.csproj | 2 +- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/extensions/Worker.Extensions.ServiceBus/src/ServiceBusReceivedMessageConverter.cs b/extensions/Worker.Extensions.ServiceBus/src/ServiceBusReceivedMessageConverter.cs index 17836e45e..2efcc19c0 100644 --- a/extensions/Worker.Extensions.ServiceBus/src/ServiceBusReceivedMessageConverter.cs +++ b/extensions/Worker.Extensions.ServiceBus/src/ServiceBusReceivedMessageConverter.cs @@ -2,7 +2,8 @@ // Licensed under the MIT License. See License.txt in the project root for license information. using System; -using System.IO; +using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using Azure.Messaging.ServiceBus; using Azure.Messaging.ServiceBus.Primitives; @@ -15,27 +16,29 @@ internal class ServiceBusReceivedMessageConverter : IInputConverter { public ValueTask ConvertAsync(ConverterContext context) { - if (context is null) + ConversionResult result = context?.Source switch { - return new ValueTask(ConversionResult.Unhandled()); - } - - return context.Source switch - { - ModelBindingData binding => ConvertToServiceBusReceivedMessage(context, binding), - _ => new ValueTask(ConversionResult.Unhandled()), + ModelBindingData binding => ConversionResult.Success(ConvertToServiceBusReceivedMessage(binding)), + CollectionModelBindingData collection when context.TargetType.IsArray => ConversionResult.Success(collection.ModelBindingDataArray.Select(ConvertToServiceBusReceivedMessage).ToArray()), + CollectionModelBindingData collection => ConversionResult.Success(collection.ModelBindingDataArray.Select(ConvertToServiceBusReceivedMessage).ToList()), + _ => ConversionResult.Unhandled() }; + return new ValueTask(result); } - private ValueTask ConvertToServiceBusReceivedMessage(ConverterContext context, ModelBindingData binding) + private ServiceBusReceivedMessage ConvertToServiceBusReceivedMessage(ModelBindingData binding) { // The lock token is a 16 byte GUID const int lockTokenLength = 16; + if (binding.ContentType != "application/octet-stream") + { + throw new InvalidOperationException("Only binary data is supported."); + } + ReadOnlyMemory bytes = binding.Content.ToMemory(); ReadOnlyMemory lockTokenBytes = bytes.Slice(0, lockTokenLength); ReadOnlyMemory messageBytes = bytes.Slice(lockTokenLength, bytes.Length - lockTokenLength); - ServiceBusReceivedMessage message = ServiceBusAmqpExtensions.FromAmqpBytes(BinaryData.FromBytes(messageBytes), BinaryData.FromBytes(lockTokenBytes)); - return new ValueTask(ConversionResult.Success(message)); + return ServiceBusAmqpExtensions.FromAmqpBytes(BinaryData.FromBytes(messageBytes), BinaryData.FromBytes(lockTokenBytes)); } } \ No newline at end of file diff --git a/extensions/Worker.Extensions.ServiceBus/src/Worker.Extensions.ServiceBus.csproj b/extensions/Worker.Extensions.ServiceBus/src/Worker.Extensions.ServiceBus.csproj index ff9eda14c..04bb94e64 100644 --- a/extensions/Worker.Extensions.ServiceBus/src/Worker.Extensions.ServiceBus.csproj +++ b/extensions/Worker.Extensions.ServiceBus/src/Worker.Extensions.ServiceBus.csproj @@ -25,7 +25,7 @@ - + From 2a41aed0055dcc5fce2d0e5def1e93ec9ff98ca4 Mon Sep 17 00:00:00 2001 From: JoshLove-msft <54595583+JoshLove-msft@users.noreply.github.com> Date: Tue, 16 May 2023 15:07:03 -0700 Subject: [PATCH 04/13] save --- NuGet.Config | 1 + .../src/Properties/AssemblyInfo.cs | 2 +- .../src/ServiceBusExtensionStartup.cs | 1 + .../src/ServiceBusReceivedMessageConverter.cs | 3 +- .../src/Worker.Extensions.ServiceBus.csproj | 2 +- .../E2EApps/E2EApp/Blob/BlobTestFunctions.cs | 39 +++++++++++++++++++ test/E2ETests/E2EApps/E2EApp/E2EApp.csproj | 1 + test/E2ETests/E2ETests/HttpEndToEndTests.cs | 9 +++++ 8 files changed, 55 insertions(+), 3 deletions(-) diff --git a/NuGet.Config b/NuGet.Config index 0df60b34c..3ffd14193 100644 --- a/NuGet.Config +++ b/NuGet.Config @@ -4,5 +4,6 @@ + \ No newline at end of file diff --git a/extensions/Worker.Extensions.ServiceBus/src/Properties/AssemblyInfo.cs b/extensions/Worker.Extensions.ServiceBus/src/Properties/AssemblyInfo.cs index f3d09d159..b57ca0dc4 100644 --- a/extensions/Worker.Extensions.ServiceBus/src/Properties/AssemblyInfo.cs +++ b/extensions/Worker.Extensions.ServiceBus/src/Properties/AssemblyInfo.cs @@ -3,4 +3,4 @@ using Microsoft.Azure.Functions.Worker.Extensions.Abstractions; -[assembly: ExtensionInformation("Microsoft.Azure.WebJobs.Extensions.ServiceBus", "5.9.0-alpha.20230203.1")] +[assembly: ExtensionInformation("Microsoft.Azure.WebJobs.Extensions.ServiceBus", "5.9.0-alpha.20230216.1")] diff --git a/extensions/Worker.Extensions.ServiceBus/src/ServiceBusExtensionStartup.cs b/extensions/Worker.Extensions.ServiceBus/src/ServiceBusExtensionStartup.cs index 00414e28f..1cb4a9eee 100644 --- a/extensions/Worker.Extensions.ServiceBus/src/ServiceBusExtensionStartup.cs +++ b/extensions/Worker.Extensions.ServiceBus/src/ServiceBusExtensionStartup.cs @@ -6,6 +6,7 @@ using Microsoft.Azure.Functions.Worker.Core; using Microsoft.Extensions.Azure; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; [assembly: WorkerExtensionStartup(typeof(ServiceBusExtensionStartup))] diff --git a/extensions/Worker.Extensions.ServiceBus/src/ServiceBusReceivedMessageConverter.cs b/extensions/Worker.Extensions.ServiceBus/src/ServiceBusReceivedMessageConverter.cs index 2efcc19c0..c47c7ded2 100644 --- a/extensions/Worker.Extensions.ServiceBus/src/ServiceBusReceivedMessageConverter.cs +++ b/extensions/Worker.Extensions.ServiceBus/src/ServiceBusReceivedMessageConverter.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Azure.Core.Amqp; using Azure.Messaging.ServiceBus; using Azure.Messaging.ServiceBus.Primitives; using Microsoft.Azure.Functions.Worker.Converters; @@ -39,6 +40,6 @@ private ServiceBusReceivedMessage ConvertToServiceBusReceivedMessage(ModelBindin ReadOnlyMemory bytes = binding.Content.ToMemory(); ReadOnlyMemory lockTokenBytes = bytes.Slice(0, lockTokenLength); ReadOnlyMemory messageBytes = bytes.Slice(lockTokenLength, bytes.Length - lockTokenLength); - return ServiceBusAmqpExtensions.FromAmqpBytes(BinaryData.FromBytes(messageBytes), BinaryData.FromBytes(lockTokenBytes)); + return ServiceBusReceivedMessage.FromAmqpMessage(AmqpAnnotatedMessage.FromBytes(BinaryData.FromBytes(messageBytes)), BinaryData.FromBytes(lockTokenBytes)); } } \ No newline at end of file diff --git a/extensions/Worker.Extensions.ServiceBus/src/Worker.Extensions.ServiceBus.csproj b/extensions/Worker.Extensions.ServiceBus/src/Worker.Extensions.ServiceBus.csproj index 04bb94e64..949c653c2 100644 --- a/extensions/Worker.Extensions.ServiceBus/src/Worker.Extensions.ServiceBus.csproj +++ b/extensions/Worker.Extensions.ServiceBus/src/Worker.Extensions.ServiceBus.csproj @@ -15,7 +15,7 @@ - + diff --git a/test/E2ETests/E2EApps/E2EApp/Blob/BlobTestFunctions.cs b/test/E2ETests/E2EApps/E2EApp/Blob/BlobTestFunctions.cs index b375e044a..fe1b97372 100644 --- a/test/E2ETests/E2EApps/E2EApp/Blob/BlobTestFunctions.cs +++ b/test/E2ETests/E2EApps/E2EApp/Blob/BlobTestFunctions.cs @@ -1,8 +1,13 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. +using System; +using System.Collections; +using System.Collections.Generic; using System.Text.Json.Serialization; +using Azure.Data.Tables; using Microsoft.Azure.Functions.Worker; +using Microsoft.Azure.Functions.Worker.Http; using Microsoft.Extensions.Logging; namespace Microsoft.Azure.Functions.Worker.E2EApp.Blob @@ -38,6 +43,32 @@ public TestBlobData BlobTriggerPocoTest( return triggerBlob; } + [Function(nameof(TablesOutputBinding))] + [TableOutput("TablesPoco")] + public TablesPoco TablesOutputBinding( + [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequestData req, + FunctionContext context) + { + // _logger.LogInformation(".NET Blob trigger function processed a blob.\n Name: " + name + "\n Content: " + triggerBlob.BlobText); + return new TablesPoco + { + PartitionKey = "foo", + RowKey = Guid.NewGuid().ToString(), + YearlySetupId = "bar", + TenantId = Guid.NewGuid(), + }; + } + + [Function(nameof(TablesInputBinding))] + public void TablesInputBinding( + [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequestData req, + [TableInput(nameof(TablesPoco), "foo", Take = 1)] IEnumerable entity, + FunctionContext context) + { + // _logger.LogInformation(".NET Blob trigger function processed a blob.\n Name: " + name + "\n Content: " + triggerBlob.BlobText); + _logger.LogInformation(entity.ToString()); + } + [Function(nameof(BlobTriggerStringTest))] [BlobOutput("test-outputstring-dotnet-isolated/{name}")] public string BlobTriggerStringTest( @@ -53,5 +84,13 @@ public class TestBlobData [JsonPropertyName("text")] public string BlobText { get; set; } } + + public class TablesPoco + { + public string PartitionKey { get; set; } + public string RowKey { get; set; } + public string YearlySetupId { get; set; } + public Guid TenantId { get; set; } + } } } diff --git a/test/E2ETests/E2EApps/E2EApp/E2EApp.csproj b/test/E2ETests/E2EApps/E2EApp/E2EApp.csproj index 42065957e..7eefb1683 100644 --- a/test/E2ETests/E2EApps/E2EApp/E2EApp.csproj +++ b/test/E2ETests/E2EApps/E2EApp/E2EApp.csproj @@ -24,6 +24,7 @@ + diff --git a/test/E2ETests/E2ETests/HttpEndToEndTests.cs b/test/E2ETests/E2ETests/HttpEndToEndTests.cs index 0d95a45c3..248384434 100644 --- a/test/E2ETests/E2ETests/HttpEndToEndTests.cs +++ b/test/E2ETests/E2ETests/HttpEndToEndTests.cs @@ -41,6 +41,15 @@ public async Task HttpTriggerTests(string functionName, string queryString, Http } } + [Fact] + public async Task TablesOutputTest() + { + HttpResponseMessage response = await HttpHelpers.InvokeHttpTrigger("TablesOutputBinding"); + string actualMessage = await response.Content.ReadAsStringAsync(); + await HttpHelpers.InvokeHttpTrigger("TablesInputBinding"); + + } + [Theory] [InlineData("HelloFromJsonBody", "{\"Name\": \"Whitney\"}", "application/json", HttpStatusCode.OK, "Hello Whitney")] [InlineData("HelloFromJsonBody", "{\"Name\": \"麵🍜\"}", "application/json", HttpStatusCode.OK, "Hello 麵🍜")] From a552f69087dc0888992237536db1cb1ced2122a5 Mon Sep 17 00:00:00 2001 From: JoshLove-msft <54595583+JoshLove-msft@users.noreply.github.com> Date: Tue, 16 May 2023 17:18:04 -0700 Subject: [PATCH 05/13] Use new attributes --- .../src/Properties/AssemblyInfo.cs | 2 +- .../src/ServiceBusReceivedMessageConverter.cs | 4 ++++ .../src/ServiceBusTriggerAttribute.cs | 8 ++++++-- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/extensions/Worker.Extensions.ServiceBus/src/Properties/AssemblyInfo.cs b/extensions/Worker.Extensions.ServiceBus/src/Properties/AssemblyInfo.cs index b57ca0dc4..32604c234 100644 --- a/extensions/Worker.Extensions.ServiceBus/src/Properties/AssemblyInfo.cs +++ b/extensions/Worker.Extensions.ServiceBus/src/Properties/AssemblyInfo.cs @@ -3,4 +3,4 @@ using Microsoft.Azure.Functions.Worker.Extensions.Abstractions; -[assembly: ExtensionInformation("Microsoft.Azure.WebJobs.Extensions.ServiceBus", "5.9.0-alpha.20230216.1")] +[assembly: ExtensionInformation("Microsoft.Azure.WebJobs.Extensions.ServiceBus", "5.10.0")] diff --git a/extensions/Worker.Extensions.ServiceBus/src/ServiceBusReceivedMessageConverter.cs b/extensions/Worker.Extensions.ServiceBus/src/ServiceBusReceivedMessageConverter.cs index c47c7ded2..b23a6a750 100644 --- a/extensions/Worker.Extensions.ServiceBus/src/ServiceBusReceivedMessageConverter.cs +++ b/extensions/Worker.Extensions.ServiceBus/src/ServiceBusReceivedMessageConverter.cs @@ -10,9 +10,13 @@ using Azure.Messaging.ServiceBus.Primitives; using Microsoft.Azure.Functions.Worker.Converters; using Microsoft.Azure.Functions.Worker.Core; +using Microsoft.Azure.Functions.Worker.Extensions.Abstractions; namespace Microsoft.Azure.Functions.Worker; +[SupportsDeferredBinding] +[SupportedConverterType(typeof(ServiceBusReceivedMessage))] +[SupportedConverterType(typeof(IList))] internal class ServiceBusReceivedMessageConverter : IInputConverter { public ValueTask ConvertAsync(ConverterContext context) diff --git a/extensions/Worker.Extensions.ServiceBus/src/ServiceBusTriggerAttribute.cs b/extensions/Worker.Extensions.ServiceBus/src/ServiceBusTriggerAttribute.cs index 2172e0ff3..b01a7dd53 100644 --- a/extensions/Worker.Extensions.ServiceBus/src/ServiceBusTriggerAttribute.cs +++ b/extensions/Worker.Extensions.ServiceBus/src/ServiceBusTriggerAttribute.cs @@ -1,11 +1,15 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. -using Microsoft.Azure.Functions.Worker.Extensions.Abstractions; +using System.Collections.Generic; +using Azure.Messaging.ServiceBus; +using Microsoft.Azure.Functions.Worker.Converters; +using Microsoft.Azure.Functions.Worker.Extensions.Abstractions; namespace Microsoft.Azure.Functions.Worker { - [SupportsDeferredBinding] + [AllowConverterFallback(true)] + [InputConverter(typeof(ServiceBusReceivedMessageConverter))] public sealed class ServiceBusTriggerAttribute : TriggerBindingAttribute, ISupportCardinality { private bool _isBatched = false; From ab0d1eef9ca3697feeb81477f47fa91114f1a0ec Mon Sep 17 00:00:00 2001 From: JoshLove-msft <54595583+JoshLove-msft@users.noreply.github.com> Date: Wed, 17 May 2023 12:30:01 -0700 Subject: [PATCH 06/13] Remove unintended changes and get ready for preview --- NuGet.Config | 1 - .../src/Worker.Extensions.ServiceBus.csproj | 5 +++-- test/E2ETests/E2EApps/E2EApp/Blob/BlobTestFunctions.cs | 1 - test/E2ETests/E2EApps/E2EApp/E2EApp.csproj | 1 - test/E2ETests/E2ETests/HttpEndToEndTests.cs | 9 --------- 5 files changed, 3 insertions(+), 14 deletions(-) delete mode 100644 test/E2ETests/E2EApps/E2EApp/Blob/BlobTestFunctions.cs diff --git a/NuGet.Config b/NuGet.Config index 3ffd14193..0df60b34c 100644 --- a/NuGet.Config +++ b/NuGet.Config @@ -4,6 +4,5 @@ - \ No newline at end of file diff --git a/extensions/Worker.Extensions.ServiceBus/src/Worker.Extensions.ServiceBus.csproj b/extensions/Worker.Extensions.ServiceBus/src/Worker.Extensions.ServiceBus.csproj index 949c653c2..855ed40b6 100644 --- a/extensions/Worker.Extensions.ServiceBus/src/Worker.Extensions.ServiceBus.csproj +++ b/extensions/Worker.Extensions.ServiceBus/src/Worker.Extensions.ServiceBus.csproj @@ -6,7 +6,8 @@ Azure Service Bus extensions for .NET isolated functions - 5.9.0 + 5.10.0 + -preview1 false @@ -16,7 +17,7 @@ - + diff --git a/test/E2ETests/E2EApps/E2EApp/Blob/BlobTestFunctions.cs b/test/E2ETests/E2EApps/E2EApp/Blob/BlobTestFunctions.cs deleted file mode 100644 index 5f282702b..000000000 --- a/test/E2ETests/E2EApps/E2EApp/Blob/BlobTestFunctions.cs +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/test/E2ETests/E2EApps/E2EApp/E2EApp.csproj b/test/E2ETests/E2EApps/E2EApp/E2EApp.csproj index 3151b631a..efcb5a6fa 100644 --- a/test/E2ETests/E2EApps/E2EApp/E2EApp.csproj +++ b/test/E2ETests/E2EApps/E2EApp/E2EApp.csproj @@ -24,7 +24,6 @@ - diff --git a/test/E2ETests/E2ETests/HttpEndToEndTests.cs b/test/E2ETests/E2ETests/HttpEndToEndTests.cs index 248384434..0d95a45c3 100644 --- a/test/E2ETests/E2ETests/HttpEndToEndTests.cs +++ b/test/E2ETests/E2ETests/HttpEndToEndTests.cs @@ -41,15 +41,6 @@ public async Task HttpTriggerTests(string functionName, string queryString, Http } } - [Fact] - public async Task TablesOutputTest() - { - HttpResponseMessage response = await HttpHelpers.InvokeHttpTrigger("TablesOutputBinding"); - string actualMessage = await response.Content.ReadAsStringAsync(); - await HttpHelpers.InvokeHttpTrigger("TablesInputBinding"); - - } - [Theory] [InlineData("HelloFromJsonBody", "{\"Name\": \"Whitney\"}", "application/json", HttpStatusCode.OK, "Hello Whitney")] [InlineData("HelloFromJsonBody", "{\"Name\": \"麵🍜\"}", "application/json", HttpStatusCode.OK, "Hello 麵🍜")] From 28147e5e524529c254244d0c316324f73036a943 Mon Sep 17 00:00:00 2001 From: JoshLove-msft <54595583+JoshLove-msft@users.noreply.github.com> Date: Wed, 17 May 2023 12:42:17 -0700 Subject: [PATCH 07/13] Add sample --- samples/Extensions/Extensions.csproj | 1 + samples/Extensions/ServiceBus/ServiceBusFunction.cs | 13 +++++++++++++ 2 files changed, 14 insertions(+) diff --git a/samples/Extensions/Extensions.csproj b/samples/Extensions/Extensions.csproj index cc8b7e690..c0ee4d9b5 100644 --- a/samples/Extensions/Extensions.csproj +++ b/samples/Extensions/Extensions.csproj @@ -6,6 +6,7 @@ <_FunctionsSkipCleanOutput>true + diff --git a/samples/Extensions/ServiceBus/ServiceBusFunction.cs b/samples/Extensions/ServiceBus/ServiceBusFunction.cs index 6cd003d66..60f3caac1 100644 --- a/samples/Extensions/ServiceBus/ServiceBusFunction.cs +++ b/samples/Extensions/ServiceBus/ServiceBusFunction.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. See License.txt in the project root for license information. using System; +using Azure.Messaging.ServiceBus; using Microsoft.Azure.Functions.Worker; using Microsoft.Extensions.Logging; @@ -21,5 +22,17 @@ public static string Run([ServiceBusTrigger("queue", Connection = "ServiceBusCon var message = $"Output message created at {DateTime.Now}"; return message; } + + [Function("ServiceBusSdkTypeBindingFunction")] + [ServiceBusOutput("outputQueue", Connection = "ServiceBusConnection")] + public static string Run([ServiceBusTrigger("queue", Connection = "ServiceBusConnection")] ServiceBusReceivedMessage message, FunctionContext context) + { + var logger = context.GetLogger("ServiceBusFunction"); + + logger.LogInformation($"Message body: {message.Body}"); + logger.LogInformation($"Message ID: {message.MessageId}"); + + return $"Output message created at {DateTime.Now}"; + } } } From 0b2a21080868fb96dddda48f2713a79f8c25331e Mon Sep 17 00:00:00 2001 From: JoshLove-msft <54595583+JoshLove-msft@users.noreply.github.com> Date: Wed, 17 May 2023 16:17:14 -0700 Subject: [PATCH 08/13] Advertise more collection types --- .../src/ServiceBusReceivedMessageConverter.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/extensions/Worker.Extensions.ServiceBus/src/ServiceBusReceivedMessageConverter.cs b/extensions/Worker.Extensions.ServiceBus/src/ServiceBusReceivedMessageConverter.cs index b23a6a750..004a75ccf 100644 --- a/extensions/Worker.Extensions.ServiceBus/src/ServiceBusReceivedMessageConverter.cs +++ b/extensions/Worker.Extensions.ServiceBus/src/ServiceBusReceivedMessageConverter.cs @@ -16,6 +16,8 @@ namespace Microsoft.Azure.Functions.Worker; [SupportsDeferredBinding] [SupportedConverterType(typeof(ServiceBusReceivedMessage))] +[SupportedConverterType(typeof(ServiceBusReceivedMessage[]))] +[SupportedConverterType(typeof(IEnumerable))] [SupportedConverterType(typeof(IList))] internal class ServiceBusReceivedMessageConverter : IInputConverter { From a38cd494f664a283cd9d80482c8878efbb9fc187 Mon Sep 17 00:00:00 2001 From: JoshLove-msft <54595583+JoshLove-msft@users.noreply.github.com> Date: Wed, 17 May 2023 17:00:31 -0700 Subject: [PATCH 09/13] Add samples and remove IEnumerable support since it is not supported in in-proc. --- .../src/ServiceBusReceivedMessageConverter.cs | 8 +-- .../ServiceBus/ServiceBusFunction.cs | 12 ---- ...ServiceBusReceivedMessageBindingSamples.cs | 67 +++++++++++++++++++ .../WorkerBindingSamples.csproj | 2 + 4 files changed, 71 insertions(+), 18 deletions(-) create mode 100644 samples/WorkerBindingSamples/ServiceBus/ServiceBusReceivedMessageBindingSamples.cs diff --git a/extensions/Worker.Extensions.ServiceBus/src/ServiceBusReceivedMessageConverter.cs b/extensions/Worker.Extensions.ServiceBus/src/ServiceBusReceivedMessageConverter.cs index 004a75ccf..a35cc18c0 100644 --- a/extensions/Worker.Extensions.ServiceBus/src/ServiceBusReceivedMessageConverter.cs +++ b/extensions/Worker.Extensions.ServiceBus/src/ServiceBusReceivedMessageConverter.cs @@ -2,12 +2,10 @@ // Licensed under the MIT License. See License.txt in the project root for license information. using System; -using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Azure.Core.Amqp; using Azure.Messaging.ServiceBus; -using Azure.Messaging.ServiceBus.Primitives; using Microsoft.Azure.Functions.Worker.Converters; using Microsoft.Azure.Functions.Worker.Core; using Microsoft.Azure.Functions.Worker.Extensions.Abstractions; @@ -17,8 +15,6 @@ namespace Microsoft.Azure.Functions.Worker; [SupportsDeferredBinding] [SupportedConverterType(typeof(ServiceBusReceivedMessage))] [SupportedConverterType(typeof(ServiceBusReceivedMessage[]))] -[SupportedConverterType(typeof(IEnumerable))] -[SupportedConverterType(typeof(IList))] internal class ServiceBusReceivedMessageConverter : IInputConverter { public ValueTask ConvertAsync(ConverterContext context) @@ -26,8 +22,8 @@ public ValueTask ConvertAsync(ConverterContext context) ConversionResult result = context?.Source switch { ModelBindingData binding => ConversionResult.Success(ConvertToServiceBusReceivedMessage(binding)), - CollectionModelBindingData collection when context.TargetType.IsArray => ConversionResult.Success(collection.ModelBindingDataArray.Select(ConvertToServiceBusReceivedMessage).ToArray()), - CollectionModelBindingData collection => ConversionResult.Success(collection.ModelBindingDataArray.Select(ConvertToServiceBusReceivedMessage).ToList()), + // Only array collections are currently supported, which matches the behavior of the in-proc extension. + CollectionModelBindingData collection => ConversionResult.Success(collection.ModelBindingDataArray.Select(ConvertToServiceBusReceivedMessage).ToArray()), _ => ConversionResult.Unhandled() }; return new ValueTask(result); diff --git a/samples/Extensions/ServiceBus/ServiceBusFunction.cs b/samples/Extensions/ServiceBus/ServiceBusFunction.cs index 60f3caac1..362b34d64 100644 --- a/samples/Extensions/ServiceBus/ServiceBusFunction.cs +++ b/samples/Extensions/ServiceBus/ServiceBusFunction.cs @@ -22,17 +22,5 @@ public static string Run([ServiceBusTrigger("queue", Connection = "ServiceBusCon var message = $"Output message created at {DateTime.Now}"; return message; } - - [Function("ServiceBusSdkTypeBindingFunction")] - [ServiceBusOutput("outputQueue", Connection = "ServiceBusConnection")] - public static string Run([ServiceBusTrigger("queue", Connection = "ServiceBusConnection")] ServiceBusReceivedMessage message, FunctionContext context) - { - var logger = context.GetLogger("ServiceBusFunction"); - - logger.LogInformation($"Message body: {message.Body}"); - logger.LogInformation($"Message ID: {message.MessageId}"); - - return $"Output message created at {DateTime.Now}"; - } } } diff --git a/samples/WorkerBindingSamples/ServiceBus/ServiceBusReceivedMessageBindingSamples.cs b/samples/WorkerBindingSamples/ServiceBus/ServiceBusReceivedMessageBindingSamples.cs new file mode 100644 index 000000000..3135112e1 --- /dev/null +++ b/samples/WorkerBindingSamples/ServiceBus/ServiceBusReceivedMessageBindingSamples.cs @@ -0,0 +1,67 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Azure.Messaging.ServiceBus; +using Microsoft.Azure.Functions.Worker; +using Microsoft.Extensions.Logging; + +namespace SampleApp +{ + /// + /// Samples demonstrating binding to the type. + /// + public class ServiceBusReceivedMessageBindingSamples + { + private readonly ILogger _logger; + + public ServiceBusReceivedMessageBindingSamples(ILogger logger) + { + _logger = logger; + } + + /// + /// This function demonstrates binding to a single . + /// + [Function(nameof(ServiceBusReceivedMessageFunction))] + public void ServiceBusReceivedMessageFunction( + [ServiceBusTrigger("queue", Connection = "ServiceBusConnection")] ServiceBusReceivedMessage message) + { + _logger.LogInformation("Message ID: {id}", message.MessageId); + _logger.LogInformation("Message Body: {body}", message.Body); + _logger.LogInformation("Message Content-Type: {contentType}", message.ContentType); + } + + /// + /// This function demonstrates binding to an array of . + /// Note that when doing so, you must also set the property + /// to true. + /// + [Function(nameof(ServiceBusReceivedMessageBatchFunction))] + public void ServiceBusReceivedMessageBatchFunction( + [ServiceBusTrigger("queue", Connection = "ServiceBusConnection", IsBatched = true)] ServiceBusReceivedMessage[] messages) + { + foreach (ServiceBusReceivedMessage message in messages) + { + _logger.LogInformation("Message ID: {id}", message.MessageId); + _logger.LogInformation("Message Body: {body}", message.Body); + _logger.LogInformation("Message Content-Type: {contentType}", message.ContentType); + } + } + + /// + /// This functions demonstrates that it is possible to bind to both the ServiceBusReceivedMessage and any of the supported binding contract + /// properties at the same time. If attempting this, the ServiceBusReceivedMessage must be the first parameter. There is not + /// much benefit to doing this as all of the binding contract properties are available as properties on the ServiceBusReceivedMessage. + /// + [Function(nameof(ServiceBusReceivedMessageWithStringProperties))] + public void ServiceBusReceivedMessageWithStringProperties( + [ServiceBusTrigger("queue", Connection = "ServiceBusConnection")] + ServiceBusReceivedMessage message, string messageId, int deliveryCount) + { + // The MessageId property and the messageId parameter are the same. + _logger.LogInformation("Message ID: {id}", message.MessageId); + _logger.LogInformation("Message ID: {id}", message.MessageId); + + } + } +} \ No newline at end of file diff --git a/samples/WorkerBindingSamples/WorkerBindingSamples.csproj b/samples/WorkerBindingSamples/WorkerBindingSamples.csproj index 3342d74a2..826c34de3 100644 --- a/samples/WorkerBindingSamples/WorkerBindingSamples.csproj +++ b/samples/WorkerBindingSamples/WorkerBindingSamples.csproj @@ -7,12 +7,14 @@ enable + + From 3385c3214b352119f5d65a42463b81dd5e16410c Mon Sep 17 00:00:00 2001 From: JoshLove-msft <54595583+JoshLove-msft@users.noreply.github.com> Date: Wed, 17 May 2023 19:45:43 -0700 Subject: [PATCH 10/13] PR fb --- release_notes.md | 3 +- samples/Extensions/Extensions.csproj | 1 - .../ServiceBus/ServiceBusFunction.cs | 1 - ...ServiceBusReceivedMessageBindingSamples.cs | 5 +- .../FunctionMetadataGeneratorTests.cs | 65 ++++++++++++++++++- 5 files changed, 68 insertions(+), 7 deletions(-) diff --git a/release_notes.md b/release_notes.md index 46d9b33b6..d5f5fe518 100644 --- a/release_notes.md +++ b/release_notes.md @@ -3,4 +3,5 @@ - My change description (#PR/#issue) --> -- Implementation for bypass deferred binding (#1462/#1495) \ No newline at end of file +- Implementation for bypass deferred binding (#1462/#1495) +- Support binding to ServiceBusReceivedMessage (#1313) \ No newline at end of file diff --git a/samples/Extensions/Extensions.csproj b/samples/Extensions/Extensions.csproj index c0ee4d9b5..cc8b7e690 100644 --- a/samples/Extensions/Extensions.csproj +++ b/samples/Extensions/Extensions.csproj @@ -6,7 +6,6 @@ <_FunctionsSkipCleanOutput>true - diff --git a/samples/Extensions/ServiceBus/ServiceBusFunction.cs b/samples/Extensions/ServiceBus/ServiceBusFunction.cs index 362b34d64..6cd003d66 100644 --- a/samples/Extensions/ServiceBus/ServiceBusFunction.cs +++ b/samples/Extensions/ServiceBus/ServiceBusFunction.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. See License.txt in the project root for license information. using System; -using Azure.Messaging.ServiceBus; using Microsoft.Azure.Functions.Worker; using Microsoft.Extensions.Logging; diff --git a/samples/WorkerBindingSamples/ServiceBus/ServiceBusReceivedMessageBindingSamples.cs b/samples/WorkerBindingSamples/ServiceBus/ServiceBusReceivedMessageBindingSamples.cs index 3135112e1..fd7b19437 100644 --- a/samples/WorkerBindingSamples/ServiceBus/ServiceBusReceivedMessageBindingSamples.cs +++ b/samples/WorkerBindingSamples/ServiceBus/ServiceBusReceivedMessageBindingSamples.cs @@ -60,8 +60,11 @@ public void ServiceBusReceivedMessageWithStringProperties( { // The MessageId property and the messageId parameter are the same. _logger.LogInformation("Message ID: {id}", message.MessageId); - _logger.LogInformation("Message ID: {id}", message.MessageId); + _logger.LogInformation("Message ID: {id}", messageId); + // Similarly the DeliveryCount property and the deliveryCount parameter are the same. + _logger.LogInformation("Delivery Count: {count}", message.DeliveryCount); + _logger.LogInformation("Delivery Count: {count}", deliveryCount); } } } \ No newline at end of file diff --git a/test/FunctionMetadataGeneratorTests/FunctionMetadataGeneratorTests.cs b/test/FunctionMetadataGeneratorTests/FunctionMetadataGeneratorTests.cs index 9451dfa93..ec98fac77 100644 --- a/test/FunctionMetadataGeneratorTests/FunctionMetadataGeneratorTests.cs +++ b/test/FunctionMetadataGeneratorTests/FunctionMetadataGeneratorTests.cs @@ -849,6 +849,58 @@ public void FunctionWithRetryPolicyWithInvalidIntervals() Assert.Throws(() => new ExponentialBackoffRetryAttribute(5, "something_bad", "00:01:00")); } + [Fact] + public void ServiceBus_SDKTypeBindings() + { + var generator = new FunctionMetadataGenerator(); + var module = ModuleDefinition.ReadModule(_thisAssembly.Location); + var typeDef = TestUtility.GetTypeDefinition(typeof(SDKTypeBindings_ServiceBus)); + var functions = generator.GenerateFunctionMetadata(typeDef); + var extensions = generator.Extensions; + + Assert.Equal(2, functions.Count()); + + AssertDictionary(extensions, new Dictionary + { + { "Microsoft.Azure.WebJobs.Extensions.ServiceBus", "5.10.0" }, + }); + + var serviceBusTriggerFunction = functions.Single(p => p.Name == nameof(SDKTypeBindings_ServiceBus.ServiceBusTriggerFunction)); + + ValidateFunction(serviceBusTriggerFunction, nameof(SDKTypeBindings_ServiceBus.ServiceBusTriggerFunction), GetEntryPoint(nameof(SDKTypeBindings_ServiceBus), nameof(SDKTypeBindings_ServiceBus.ServiceBusTriggerFunction)), + ValidateServiceBusTrigger); + + var serviceBusBatchTriggerFunction = functions.Single(p => p.Name == nameof(SDKTypeBindings_ServiceBus.ServiceBusBatchTriggerFunction)); + + ValidateFunction(serviceBusBatchTriggerFunction, nameof(SDKTypeBindings_ServiceBus.ServiceBusBatchTriggerFunction), GetEntryPoint(nameof(SDKTypeBindings_ServiceBus), nameof(SDKTypeBindings_ServiceBus.ServiceBusBatchTriggerFunction)), + ValidateServiceBusBatchTrigger); + + void ValidateServiceBusTrigger(ExpandoObject b) + { + AssertExpandoObject(b, new Dictionary + { + { "Name", "message" }, + { "Type", "serviceBusTrigger" }, + { "Direction", "In" }, + { "queueName", "queue" }, + { "Properties", new Dictionary( ) { { "SupportsDeferredBinding" , "True"} } } + }); + } + + void ValidateServiceBusBatchTrigger(ExpandoObject b) + { + AssertExpandoObject(b, new Dictionary + { + { "Name", "messages" }, + { "Type", "serviceBusTrigger" }, + { "Direction", "In" }, + { "queueName", "queue" }, + { "Cardinality", "Many" }, + { "Properties", new Dictionary( ) { { "SupportsDeferredBinding" , "True"} } } + }); + } + } + private class EventHubNotBatched { [Function("EventHubTrigger")] @@ -1035,14 +1087,21 @@ public object BlobStringToBlobPocoArray( } } - private class ServiceBusSDKBindings + private class SDKTypeBindings_ServiceBus { - [Function("ServiceBusTriggerFunction")] - public object ServiceBusFunction( + [Function(nameof(ServiceBusTriggerFunction))] + public static void ServiceBusTriggerFunction( [ServiceBusTrigger("queue")] ServiceBusReceivedMessage message) { throw new NotImplementedException(); } + + [Function(nameof(ServiceBusBatchTriggerFunction))] + public static void ServiceBusBatchTriggerFunction( + [ServiceBusTrigger("queue", IsBatched = true)] ServiceBusReceivedMessage[] messages) + { + throw new NotImplementedException(); + } } private class ExternalType_Return From 54efd0ebd1a0fead8b42fb9064f9a5448b642766 Mon Sep 17 00:00:00 2001 From: JoshLove-msft <54595583+JoshLove-msft@users.noreply.github.com> Date: Thu, 18 May 2023 14:08:34 -0700 Subject: [PATCH 11/13] PR fb --- .../release_notes.md | 4 +- .../src/Constants.cs | 11 ++ .../src/Properties/AssemblyInfo.cs | 5 +- .../src/ServiceBusReceivedMessageConverter.cs | 60 ++++---- release_notes.md | 3 +- .../WorkerBindingSamples.csproj | 1 - ...ServiceBusReceivedMessageConverterTests.cs | 129 ++++++++++++++++++ .../WorkerExtensionTests.csproj | 1 + 8 files changed, 182 insertions(+), 32 deletions(-) create mode 100644 extensions/Worker.Extensions.ServiceBus/src/Constants.cs create mode 100644 test/WorkerExtensionTests/ServiceBus/ServiceBusReceivedMessageConverterTests.cs diff --git a/extensions/Worker.Extensions.ServiceBus/release_notes.md b/extensions/Worker.Extensions.ServiceBus/release_notes.md index b38bd27d3..a39714534 100644 --- a/extensions/Worker.Extensions.ServiceBus/release_notes.md +++ b/extensions/Worker.Extensions.ServiceBus/release_notes.md @@ -1,4 +1,6 @@ ## Release notes \ No newline at end of file +--> + +- Support binding to ServiceBusReceivedMessage (#1313) \ No newline at end of file diff --git a/extensions/Worker.Extensions.ServiceBus/src/Constants.cs b/extensions/Worker.Extensions.ServiceBus/src/Constants.cs new file mode 100644 index 000000000..6912e94f6 --- /dev/null +++ b/extensions/Worker.Extensions.ServiceBus/src/Constants.cs @@ -0,0 +1,11 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + + +namespace Microsoft.Azure.Functions.Worker.Extensions.ServiceBus +{ + public static class Constants + { + internal const string BinaryContentType = "application/octet-stream"; + } +} \ No newline at end of file diff --git a/extensions/Worker.Extensions.ServiceBus/src/Properties/AssemblyInfo.cs b/extensions/Worker.Extensions.ServiceBus/src/Properties/AssemblyInfo.cs index 32604c234..75d427695 100644 --- a/extensions/Worker.Extensions.ServiceBus/src/Properties/AssemblyInfo.cs +++ b/extensions/Worker.Extensions.ServiceBus/src/Properties/AssemblyInfo.cs @@ -1,6 +1,9 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. -using Microsoft.Azure.Functions.Worker.Extensions.Abstractions; +using System.Runtime.CompilerServices; +using Microsoft.Azure.Functions.Worker.Extensions.Abstractions; [assembly: ExtensionInformation("Microsoft.Azure.WebJobs.Extensions.ServiceBus", "5.10.0")] +[assembly: InternalsVisibleTo("Microsoft.Azure.Functions.WorkerExtension.Tests, PublicKey=00240000048000009400000006020000002400005253413100040000010001005148be37ac1d9f58bd40a2e472c9d380d635b6048278f7d47480b08c928858f0f7fe17a6e4ce98da0e7a7f0b8c308aecd9e9b02d7e9680a5b5b75ac7773cec096fbbc64aebd429e77cb5f89a569a79b28e9c76426783f624b6b70327eb37341eb498a2c3918af97c4860db6cdca4732787150841e395a29cfacb959c1fd971c1")] + diff --git a/extensions/Worker.Extensions.ServiceBus/src/ServiceBusReceivedMessageConverter.cs b/extensions/Worker.Extensions.ServiceBus/src/ServiceBusReceivedMessageConverter.cs index a35cc18c0..19e257aa5 100644 --- a/extensions/Worker.Extensions.ServiceBus/src/ServiceBusReceivedMessageConverter.cs +++ b/extensions/Worker.Extensions.ServiceBus/src/ServiceBusReceivedMessageConverter.cs @@ -9,39 +9,45 @@ using Microsoft.Azure.Functions.Worker.Converters; using Microsoft.Azure.Functions.Worker.Core; using Microsoft.Azure.Functions.Worker.Extensions.Abstractions; +using Microsoft.Azure.Functions.Worker.Extensions.ServiceBus; -namespace Microsoft.Azure.Functions.Worker; - -[SupportsDeferredBinding] -[SupportedConverterType(typeof(ServiceBusReceivedMessage))] -[SupportedConverterType(typeof(ServiceBusReceivedMessage[]))] -internal class ServiceBusReceivedMessageConverter : IInputConverter +namespace Microsoft.Azure.Functions.Worker { - public ValueTask ConvertAsync(ConverterContext context) - { - ConversionResult result = context?.Source switch - { - ModelBindingData binding => ConversionResult.Success(ConvertToServiceBusReceivedMessage(binding)), - // Only array collections are currently supported, which matches the behavior of the in-proc extension. - CollectionModelBindingData collection => ConversionResult.Success(collection.ModelBindingDataArray.Select(ConvertToServiceBusReceivedMessage).ToArray()), - _ => ConversionResult.Unhandled() - }; - return new ValueTask(result); - } - private ServiceBusReceivedMessage ConvertToServiceBusReceivedMessage(ModelBindingData binding) + [SupportsDeferredBinding] + [SupportedConverterType(typeof(ServiceBusReceivedMessage))] + [SupportedConverterType(typeof(ServiceBusReceivedMessage[]))] + internal class ServiceBusReceivedMessageConverter : IInputConverter { - // The lock token is a 16 byte GUID - const int lockTokenLength = 16; - - if (binding.ContentType != "application/octet-stream") + public ValueTask ConvertAsync(ConverterContext context) { - throw new InvalidOperationException("Only binary data is supported."); + ConversionResult result = context?.Source switch + { + ModelBindingData binding => ConversionResult.Success(ConvertToServiceBusReceivedMessage(binding)), + // Only array collections are currently supported, which matches the behavior of the in-proc extension. + CollectionModelBindingData collection => ConversionResult.Success(collection.ModelBindingDataArray + .Select(ConvertToServiceBusReceivedMessage).ToArray()), + _ => ConversionResult.Unhandled() + }; + return new ValueTask(result); } - ReadOnlyMemory bytes = binding.Content.ToMemory(); - ReadOnlyMemory lockTokenBytes = bytes.Slice(0, lockTokenLength); - ReadOnlyMemory messageBytes = bytes.Slice(lockTokenLength, bytes.Length - lockTokenLength); - return ServiceBusReceivedMessage.FromAmqpMessage(AmqpAnnotatedMessage.FromBytes(BinaryData.FromBytes(messageBytes)), BinaryData.FromBytes(lockTokenBytes)); + private ServiceBusReceivedMessage ConvertToServiceBusReceivedMessage(ModelBindingData binding) + { + // The lock token is a 16 byte GUID + const int lockTokenLength = 16; + + if (binding.ContentType != Constants.BinaryContentType) + { + throw new InvalidOperationException( + $"Unexpected content-type. Only '{Constants.BinaryContentType}' is supported."); + } + + ReadOnlyMemory bytes = binding.Content.ToMemory(); + ReadOnlyMemory lockTokenBytes = bytes.Slice(0, lockTokenLength); + ReadOnlyMemory messageBytes = bytes.Slice(lockTokenLength, bytes.Length - lockTokenLength); + return ServiceBusReceivedMessage.FromAmqpMessage(AmqpAnnotatedMessage.FromBytes(BinaryData.FromBytes(messageBytes)), + BinaryData.FromBytes(lockTokenBytes)); + } } } \ No newline at end of file diff --git a/release_notes.md b/release_notes.md index d5f5fe518..46d9b33b6 100644 --- a/release_notes.md +++ b/release_notes.md @@ -3,5 +3,4 @@ - My change description (#PR/#issue) --> -- Implementation for bypass deferred binding (#1462/#1495) -- Support binding to ServiceBusReceivedMessage (#1313) \ No newline at end of file +- Implementation for bypass deferred binding (#1462/#1495) \ No newline at end of file diff --git a/samples/WorkerBindingSamples/WorkerBindingSamples.csproj b/samples/WorkerBindingSamples/WorkerBindingSamples.csproj index 826c34de3..cd69ed1c4 100644 --- a/samples/WorkerBindingSamples/WorkerBindingSamples.csproj +++ b/samples/WorkerBindingSamples/WorkerBindingSamples.csproj @@ -7,7 +7,6 @@ enable - diff --git a/test/WorkerExtensionTests/ServiceBus/ServiceBusReceivedMessageConverterTests.cs b/test/WorkerExtensionTests/ServiceBus/ServiceBusReceivedMessageConverterTests.cs new file mode 100644 index 000000000..82783b44b --- /dev/null +++ b/test/WorkerExtensionTests/ServiceBus/ServiceBusReceivedMessageConverterTests.cs @@ -0,0 +1,129 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Threading.Tasks; +using Azure.Messaging.ServiceBus; +using Google.Protobuf; +using Microsoft.Azure.Functions.Worker; +using Microsoft.Azure.Functions.Worker.Converters; +using Microsoft.Azure.Functions.Worker.Extensions.ServiceBus; +using Microsoft.Azure.Functions.Worker.Grpc.Messages; +using Microsoft.Azure.Functions.Worker.Tests.Converters; +using Xunit; + +namespace Microsoft.Azure.Functions.WorkerExtension.Tests +{ + public class ServiceBusReceivedMessageConverterTests + { + [Fact] + public async Task ConvertAsync_ReturnsSuccess() + { + var lockToken = Guid.NewGuid(); + var message = CreateReceivedMessage(lockToken); + + var data = new GrpcModelBindingData(new ModelBindingData() + { + Version = "1.0", + Source = "AzureServiceBusReceivedMessage", + Content = ByteString.CopyFrom(ConvertReceivedMessageToBinaryData(message)), + ContentType = Constants.BinaryContentType + }); + var context = new TestConverterContext(typeof(string), data); + var converter = new ServiceBusReceivedMessageConverter(); + var result = await converter.ConvertAsync(context); + + Assert.Equal(ConversionStatus.Succeeded, result.Status); + var output = result.Value as ServiceBusReceivedMessage; + Assert.NotNull(output); + AssertReceivedMessage(output, lockToken); + } + + [Fact] + public async Task ConvertAsync_Batch_ReturnsSuccess() + { + var lockToken = Guid.NewGuid(); + var message = CreateReceivedMessage(lockToken); + + var data = new ModelBindingData + { + Version = "1.0", + Source = "AzureStorageBlobs", + Content = ByteString.CopyFrom(ConvertReceivedMessageToBinaryData(message)), + ContentType = Constants.BinaryContentType + }; + + var array = new CollectionModelBindingData(); + array.ModelBindingData.Add(data); + array.ModelBindingData.Add(data); + + var context = new TestConverterContext(typeof(string), new GrpcCollectionModelBindingData(array)); + var converter = new ServiceBusReceivedMessageConverter(); + var result = await converter.ConvertAsync(context); + + Assert.Equal(ConversionStatus.Succeeded, result.Status); + var output = result.Value as ServiceBusReceivedMessage[]; + Assert.NotNull(output); + Assert.Equal(2, output.Length); + AssertReceivedMessage(output[0], lockToken); + AssertReceivedMessage(output[1], lockToken); + } + + private static void AssertReceivedMessage(ServiceBusReceivedMessage output, Guid lockToken) + { + Assert.Equal("body", output.Body.ToString()); + Assert.Equal("messageId", output.MessageId); + Assert.Equal("correlationId", output.CorrelationId); + Assert.Equal("sessionId", output.SessionId); + Assert.Equal("replyTo", output.ReplyTo); + Assert.Equal("replyToSessionId", output.ReplyToSessionId); + Assert.Equal("contentType", output.ContentType); + Assert.Equal("label", output.Subject); + Assert.Equal("to", output.To); + Assert.Equal("partitionKey", output.PartitionKey); + Assert.Equal("viaPartitionKey", output.TransactionPartitionKey); + Assert.Equal("deadLetterSource", output.DeadLetterSource); + Assert.Equal(1, output.EnqueuedSequenceNumber); + Assert.Equal(lockToken.ToString(), output.LockToken); + } + + private static ServiceBusReceivedMessage CreateReceivedMessage(Guid lockToken) + { + return ServiceBusModelFactory.ServiceBusReceivedMessage( + body: BinaryData.FromString("body"), + messageId: "messageId", + correlationId: "correlationId", + sessionId: "sessionId", + replyTo: "replyTo", + replyToSessionId: "replyToSessionId", + contentType: "contentType", + subject: "label", + to: "to", + partitionKey: "partitionKey", + viaPartitionKey: "viaPartitionKey", + deadLetterSource: "deadLetterSource", + enqueuedSequenceNumber: 1, + lockTokenGuid: lockToken); + } + + private static BinaryData ConvertReceivedMessageToBinaryData(ServiceBusReceivedMessage message) + { + ReadOnlyMemory messageBytes = message.GetRawAmqpMessage().ToBytes().ToMemory(); + + byte[] lockTokenBytes = Guid.Parse(message.LockToken).ToByteArray(); + + // The lock token is a 16 byte GUID + const int lockTokenLength = 16; + + byte[] combinedBytes = new byte[messageBytes.Length + lockTokenLength]; + + // The 16 lock token bytes go in the beginning + lockTokenBytes.CopyTo(combinedBytes.AsSpan()); + + // The AMQP message bytes go after the lock token bytes + messageBytes.CopyTo(combinedBytes.AsMemory(lockTokenLength)); + + return new BinaryData(combinedBytes); + } + } +} \ No newline at end of file diff --git a/test/WorkerExtensionTests/WorkerExtensionTests.csproj b/test/WorkerExtensionTests/WorkerExtensionTests.csproj index 8122e45b2..ac8f753ff 100644 --- a/test/WorkerExtensionTests/WorkerExtensionTests.csproj +++ b/test/WorkerExtensionTests/WorkerExtensionTests.csproj @@ -22,6 +22,7 @@ + From 41f51ba8ea3c171b983e9f5e80f34eb75d3e1d90 Mon Sep 17 00:00:00 2001 From: JoshLove-msft <54595583+JoshLove-msft@users.noreply.github.com> Date: Thu, 18 May 2023 14:14:16 -0700 Subject: [PATCH 12/13] fix --- .../ServiceBus/ServiceBusReceivedMessageConverterTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/WorkerExtensionTests/ServiceBus/ServiceBusReceivedMessageConverterTests.cs b/test/WorkerExtensionTests/ServiceBus/ServiceBusReceivedMessageConverterTests.cs index 82783b44b..77275aceb 100644 --- a/test/WorkerExtensionTests/ServiceBus/ServiceBusReceivedMessageConverterTests.cs +++ b/test/WorkerExtensionTests/ServiceBus/ServiceBusReceivedMessageConverterTests.cs @@ -48,7 +48,7 @@ public async Task ConvertAsync_Batch_ReturnsSuccess() var data = new ModelBindingData { Version = "1.0", - Source = "AzureStorageBlobs", + Source = "AzureServiceBusReceivedMessage", Content = ByteString.CopyFrom(ConvertReceivedMessageToBinaryData(message)), ContentType = Constants.BinaryContentType }; From 7e57462deaa9f3051bc94c5fa6d93cc7a5f549f8 Mon Sep 17 00:00:00 2001 From: JoshLove-msft <54595583+JoshLove-msft@users.noreply.github.com> Date: Fri, 19 May 2023 12:18:31 -0700 Subject: [PATCH 13/13] Remove unnecessary registration --- .../src/ServiceBusExtensionStartup.cs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/extensions/Worker.Extensions.ServiceBus/src/ServiceBusExtensionStartup.cs b/extensions/Worker.Extensions.ServiceBus/src/ServiceBusExtensionStartup.cs index 1cb4a9eee..eab4334b3 100644 --- a/extensions/Worker.Extensions.ServiceBus/src/ServiceBusExtensionStartup.cs +++ b/extensions/Worker.Extensions.ServiceBus/src/ServiceBusExtensionStartup.cs @@ -5,8 +5,6 @@ using Microsoft.Azure.Functions.Worker; using Microsoft.Azure.Functions.Worker.Core; using Microsoft.Extensions.Azure; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; [assembly: WorkerExtensionStartup(typeof(ServiceBusExtensionStartup))] @@ -22,11 +20,6 @@ public override void Configure(IFunctionsWorkerApplicationBuilder applicationBui } applicationBuilder.Services.AddAzureClientsCore(); // Adds AzureComponentFactory - - applicationBuilder.Services.Configure((workerOption) => - { - workerOption.InputConverters.RegisterAt(0); - }); } } } \ No newline at end of file