├── sign.snk ├── webjobs.png ├── test └── Microsoft.Azure.WebJobs.Extensions.ServiceBus.Tests │ ├── Properties │ └── AssemblyInfo.cs │ ├── ServiceBusAttributeTests.cs │ ├── PublicSurfaceTests.cs │ ├── Bindings │ ├── BoundServiceBusTests.cs │ ├── ParameterizedServiceBusPathTests.cs │ ├── BindableServiceBusPathTests.cs │ ├── ServiceBusAttributeBindingProviderTests.cs │ └── ServiceBusTriggerAttributeBindingProviderTests.cs │ ├── MessageToByteArrayConverterTests.cs │ ├── ServiceBusTriggerAttributeTests.cs │ ├── WebJobs.Extensions.ServiceBus.Tests.csproj │ ├── README.md │ ├── MessageProcessorTests.cs │ ├── ServiceBusAccountTests.cs │ ├── MessagingProviderTests.cs │ ├── MessageToStringConverterTests.cs │ ├── Config │ ├── ServiceBusHostBuilderExtensionsTests.cs │ └── ServiceBusOptionsTests.cs │ └── Listeners │ └── ServiceBusListenerTests.cs ├── NuGet.Config ├── src └── Microsoft.Azure.WebJobs.Extensions.ServiceBus │ ├── Bindings │ ├── IQueueArgumentBindingProvider.cs │ ├── ServiceBusParameterDescriptor.cs │ ├── MessageArgumentBindingProvider.cs │ ├── ServiceBusEntity.cs │ ├── UserTypeToBrokeredMessageConverter.cs │ ├── ByteArrayToBrokeredMessageConverter.cs │ ├── StringToBrokeredMessageConverter.cs │ ├── MessageSenderExtensions.cs │ ├── IBindableServiceBusPath.cs │ ├── CompositeArgumentBindingProvider.cs │ ├── BoundServiceBusPath.cs │ ├── CollectorValueProvider.cs │ ├── OutputConverter.cs │ ├── MessageConverterFactory.cs │ ├── MessageSenderCollector.cs │ ├── BindableServiceBusPath.cs │ ├── ConverterValueBinder.cs │ ├── ParameterizedServiceBusPath.cs │ ├── MessageSenderAsyncCollector.cs │ ├── StringToServiceBusEntityConverter.cs │ ├── NonNullConverterValueBinder.cs │ ├── StringArgumentBindingProvider.cs │ ├── ByteArrayArgumentBindingProvider.cs │ ├── UserTypeArgumentBindingProvider.cs │ ├── MessageSenderArgumentBindingProvider.cs │ ├── CollectorArgumentBindingProvider.cs │ ├── AsyncCollectorArgumentBindingProvider.cs │ ├── MessageArgumentBinding.cs │ ├── ServiceBusBinding.cs │ └── ServiceBusAttributeBindingProvider.cs │ ├── ContentTypes.cs │ ├── EntityType.cs │ ├── StrictEncodings.cs │ ├── ServiceBusWebJobsStartup.cs │ ├── Utility.cs │ ├── Triggers │ ├── MessageToByteArrayConverter.cs │ ├── OutputConverter.cs │ ├── MessageToStringConverter.cs │ ├── MessageToPocoConverter.cs │ ├── ServiceBusTriggerInput.cs │ └── ServiceBusTriggerAttributeBindingProvider.cs │ ├── Config │ ├── BatchOptions.cs │ ├── ServiceBusWebJobsBuilderExtensions.cs │ ├── ServiceBusExtensionConfigProvider.cs │ └── ServiceBusOptions.cs │ ├── Listeners │ ├── ServiceBusTriggerMetrics.cs │ ├── ServiceBusEntityPathHelper.cs │ ├── ServiceBusCausalityHelper.cs │ └── ServiceBusListenerFactory.cs │ ├── Properties │ └── AssemblyInfo.cs │ ├── GlobalSuppressions.cs │ ├── Constants.cs │ ├── ServiceBusAccount.cs │ ├── ServiceBusAccountAttribute.cs │ ├── WebJobs.Extensions.ServiceBus.csproj │ ├── ServiceBusAttribute.cs │ ├── SessionMessageProcessor.cs │ ├── MessageProcessor.cs │ └── ServiceBusTriggerAttribute.cs ├── release_notes.md ├── .github └── pull_request_template.md ├── stylecop.json ├── LICENSE.txt ├── README.md ├── ServiceBusExtension.sln ├── SECURITY.md ├── src.ruleset └── .gitignore /sign.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure/azure-functions-servicebus-extension/HEAD/sign.snk -------------------------------------------------------------------------------- /webjobs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure/azure-functions-servicebus-extension/HEAD/webjobs.png -------------------------------------------------------------------------------- /test/Microsoft.Azure.WebJobs.Extensions.ServiceBus.Tests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System.Reflection; 5 | using Xunit; 6 | 7 | [assembly: CollectionBehavior(CollectionBehavior.CollectionPerAssembly)] -------------------------------------------------------------------------------- /NuGet.Config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/Microsoft.Azure.WebJobs.Extensions.ServiceBus/Bindings/IQueueArgumentBindingProvider.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System.Reflection; 5 | using Microsoft.Azure.WebJobs.Host.Bindings; 6 | 7 | namespace Microsoft.Azure.WebJobs.ServiceBus.Bindings 8 | { 9 | internal interface IQueueArgumentBindingProvider 10 | { 11 | IArgumentBinding TryCreate(ParameterInfo parameter); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Microsoft.Azure.WebJobs.Extensions.ServiceBus/ContentTypes.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | namespace Microsoft.Azure.WebJobs.ServiceBus 5 | { 6 | internal static class ContentTypes 7 | { 8 | public const string TextPlain = "text/plain"; 9 | 10 | public const string ApplicationJson = "application/json"; 11 | 12 | public const string ApplicationOctetStream = "application/octet-stream"; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Microsoft.Azure.WebJobs.Extensions.ServiceBus/Bindings/ServiceBusParameterDescriptor.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using Microsoft.Azure.WebJobs.Host.Protocols; 5 | 6 | namespace Microsoft.Azure.WebJobs.ServiceBus.Bindings 7 | { 8 | internal class ServiceBusParameterDescriptor : ParameterDescriptor 9 | { 10 | /// Gets or sets the name of the queue or topic. 11 | public string QueueOrTopicName { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /release_notes.md: -------------------------------------------------------------------------------- 1 | ### Release notes 2 | 5 | #### Version Version 4.3.1 6 | - Add listener details (#186) 7 | - Fix batch receive race condition (#184) 8 | - ServiceBusTrigger should allow for binding the MessageReceiver instance by interface (#69) 9 | 10 | #### Version Version 4.3.0 11 | - Added support for allowing 'autoComplete' setting at the function level for all the single, batch or session triggers. 12 | 13 | 14 | **Release sprint:** Sprint 100 15 | [ [features](https://github.com/Azure/azure-functions-servicebus-extension/issues/138) ] -------------------------------------------------------------------------------- /src/Microsoft.Azure.WebJobs.Extensions.ServiceBus/EntityType.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | namespace Microsoft.Azure.WebJobs.ServiceBus 5 | { 6 | /// 7 | /// Service Bus entity type. 8 | /// 9 | public enum EntityType 10 | { 11 | /// 12 | /// Service Bus Queue 13 | /// 14 | Queue, 15 | 16 | /// 17 | /// Service Bus Topic 18 | /// 19 | Topic 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Microsoft.Azure.WebJobs.Extensions.ServiceBus/StrictEncodings.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System.Text; 5 | 6 | namespace Microsoft.Azure.WebJobs.ServiceBus 7 | { 8 | internal static class StrictEncodings 9 | { 10 | private static UTF8Encoding _utf8 = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, 11 | throwOnInvalidBytes: true); 12 | 13 | public static UTF8Encoding Utf8 14 | { 15 | get { return _utf8; } 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /test/Microsoft.Azure.WebJobs.Extensions.ServiceBus.Tests/ServiceBusAttributeTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using Xunit; 5 | 6 | namespace Microsoft.Azure.WebJobs.ServiceBus.UnitTests 7 | { 8 | public class ServiceBusAttributeTests 9 | { 10 | [Fact] 11 | public void Constructor_Success() 12 | { 13 | ServiceBusAttribute attribute = new ServiceBusAttribute("testqueue"); 14 | Assert.Equal("testqueue", attribute.QueueOrTopicName); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Microsoft.Azure.WebJobs.Extensions.ServiceBus/ServiceBusWebJobsStartup.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using Microsoft.Azure.WebJobs.ServiceBus; 5 | using Microsoft.Azure.WebJobs.Hosting; 6 | using Microsoft.Extensions.Hosting; 7 | 8 | [assembly: WebJobsStartup(typeof(ServiceBusWebJobsStartup))] 9 | 10 | namespace Microsoft.Azure.WebJobs.ServiceBus 11 | { 12 | public class ServiceBusWebJobsStartup : IWebJobsStartup 13 | { 14 | public void Configure(IWebJobsBuilder builder) 15 | { 16 | builder.AddServiceBus(); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ### Issue describing the changes in this PR 4 | 5 | resolves #issue_for_this_pr 6 | 7 | ### Pull request checklist 8 | 9 | * [ ] My changes **do not** require documentation changes 10 | * [ ] Otherwise: Documentation issue linked to PR 11 | * [ ] My changes **should not** be added to the release notes for the next release 12 | * [ ] Otherwise: I've added my notes to `release_notes.md` 13 | * [ ] My changes **do not** need to be backported to a previous version 14 | * [ ] Otherwise: Backport tracked by issue/PR #issue_or_pr 15 | * [ ] I have added all required tests (Unit tests, E2E tests) 16 | 17 | 18 | ### Additional information 19 | 20 | Additional PR information 21 | -------------------------------------------------------------------------------- /src/Microsoft.Azure.WebJobs.Extensions.ServiceBus/Utility.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System; 5 | 6 | namespace Microsoft.Azure.WebJobs.ServiceBus 7 | { 8 | class Utility 9 | { 10 | /// 11 | /// Returns processor count for a worker, for consumption plan always returns 1 12 | /// 13 | /// 14 | public static int GetProcessorCount() 15 | { 16 | string skuValue = Environment.GetEnvironmentVariable(Constants.AzureWebsiteSku); 17 | return string.Equals(skuValue, Constants.DynamicSku, StringComparison.OrdinalIgnoreCase) ? 1 : Environment.ProcessorCount; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Microsoft.Azure.WebJobs.Extensions.ServiceBus/Bindings/MessageArgumentBindingProvider.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System.Reflection; 5 | using Microsoft.Azure.ServiceBus; 6 | using Microsoft.Azure.WebJobs.Host.Bindings; 7 | 8 | 9 | namespace Microsoft.Azure.WebJobs.ServiceBus.Bindings 10 | { 11 | internal class MessageArgumentBindingProvider : IQueueArgumentBindingProvider 12 | { 13 | public IArgumentBinding TryCreate(ParameterInfo parameter) 14 | { 15 | if (!parameter.IsOut || parameter.ParameterType != typeof(Message).MakeByRefType()) 16 | { 17 | return null; 18 | } 19 | 20 | return new MessageArgumentBinding(); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Microsoft.Azure.WebJobs.Extensions.ServiceBus/Bindings/ServiceBusEntity.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using Microsoft.Azure.ServiceBus; 8 | using Microsoft.Azure.ServiceBus.Core; 9 | 10 | namespace Microsoft.Azure.WebJobs.ServiceBus.Bindings 11 | { 12 | internal class ServiceBusEntity 13 | { 14 | public MessageSender MessageSender { get; set; } 15 | 16 | public EntityType EntityType { get; set; } = EntityType.Queue; 17 | 18 | public Task SendAndCreateEntityIfNotExistsAsync(Message message, Guid functionInstanceId, CancellationToken cancellationToken) 19 | { 20 | return MessageSender.SendAndCreateEntityIfNotExists(message, functionInstanceId, EntityType, cancellationToken); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Microsoft.Azure.WebJobs.Extensions.ServiceBus/Bindings/UserTypeToBrokeredMessageConverter.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System.Diagnostics.CodeAnalysis; 5 | using System.IO; 6 | using Microsoft.Azure.WebJobs.Host.Converters; 7 | using Microsoft.Azure.ServiceBus; 8 | using Newtonsoft.Json; 9 | 10 | namespace Microsoft.Azure.WebJobs.ServiceBus.Bindings 11 | { 12 | internal class UserTypeToBrokeredMessageConverter : IConverter 13 | { 14 | public Message Convert(TInput input) 15 | { 16 | string text = JsonConvert.SerializeObject(input, Constants.JsonSerializerSettings); 17 | byte[] bytes = StrictEncodings.Utf8.GetBytes(text); 18 | 19 | return new Message(bytes) 20 | { 21 | ContentType = ContentTypes.ApplicationJson 22 | }; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Microsoft.Azure.WebJobs.Extensions.ServiceBus/Triggers/MessageToByteArrayConverter.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.IO; 6 | using System.Runtime.Serialization; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | using Microsoft.Azure.WebJobs.Host.Converters; 10 | using Microsoft.Azure.ServiceBus; 11 | 12 | namespace Microsoft.Azure.WebJobs.ServiceBus.Triggers 13 | { 14 | internal class MessageToByteArrayConverter : IAsyncConverter 15 | { 16 | public Task ConvertAsync(Message input, CancellationToken cancellationToken) 17 | { 18 | if (input == null) 19 | { 20 | throw new ArgumentNullException(nameof(input)); 21 | } 22 | 23 | cancellationToken.ThrowIfCancellationRequested(); 24 | 25 | return Task.FromResult(input.Body); 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /src/Microsoft.Azure.WebJobs.Extensions.ServiceBus/Bindings/ByteArrayToBrokeredMessageConverter.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Diagnostics.CodeAnalysis; 6 | using Microsoft.Azure.ServiceBus; 7 | 8 | namespace Microsoft.Azure.WebJobs.ServiceBus.Bindings 9 | { 10 | internal class ByteArrayToBrokeredMessageConverter : IConverter 11 | { 12 | [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope")] 13 | public Message Convert(byte[] input) 14 | { 15 | if (input == null) 16 | { 17 | throw new InvalidOperationException("A brokered message cannot contain a null byte array instance."); 18 | } 19 | 20 | return new Message(input) 21 | { 22 | ContentType = ContentTypes.ApplicationOctetStream 23 | }; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Microsoft.Azure.WebJobs.Extensions.ServiceBus/Config/BatchOptions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | using System; 4 | 5 | namespace Microsoft.Azure.WebJobs.ServiceBus 6 | { 7 | /// 8 | /// Configuration options for ServiceBus batch receive. 9 | /// 10 | public class BatchOptions 11 | { 12 | /// 13 | /// The maximum number of messages that will be received. 14 | /// 15 | public int MaxMessageCount { get; set; } 16 | 17 | /// 18 | /// The time span the client waits for receiving a message before it times out. 19 | /// 20 | public TimeSpan OperationTimeout { get; set; } 21 | 22 | /// 23 | /// Gets or sets a value that indicates whether the messages should be completed after successful processing. 24 | /// 25 | public bool AutoComplete { get; set; } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Microsoft.Azure.WebJobs.Extensions.ServiceBus/Listeners/ServiceBusTriggerMetrics.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using Microsoft.Azure.WebJobs.Host.Scale; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Text; 8 | 9 | namespace Microsoft.Azure.WebJobs.ServiceBus.Listeners 10 | { 11 | internal class ServiceBusTriggerMetrics : ScaleMetrics 12 | { 13 | /// 14 | /// The number of messages currently in the queue/topic. 15 | /// 16 | public long MessageCount { get; set; } 17 | 18 | /// 19 | /// The number of partitions. 20 | /// 21 | public int PartitionCount { get; set; } 22 | 23 | /// 24 | /// The length of time the next message has been 25 | /// sitting there. 26 | /// 27 | public TimeSpan QueueTime { get; set; } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Microsoft.Azure.WebJobs.Extensions.ServiceBus/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System.Reflection; 5 | using System.Runtime.CompilerServices; 6 | 7 | [assembly: InternalsVisibleTo("Microsoft.Azure.WebJobs.ServiceBus.UnitTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] 8 | [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] 9 | -------------------------------------------------------------------------------- /stylecop.json: -------------------------------------------------------------------------------- 1 | { 2 | // ACTION REQUIRED: This file was automatically added to your project, but it 3 | // will not take effect until additional steps are taken to enable it. See the 4 | // following page for additional information: 5 | // 6 | // https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/EnableConfiguration.md 7 | 8 | "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json", 9 | "settings": { 10 | "documentationRules": { 11 | "companyName": ".NET Foundation", 12 | "copyrightText": "Copyright (c) .NET Foundation. All rights reserved.\r\nLicensed under the MIT License. See License.txt in the project root for license information.", 13 | "xmlHeader": false, 14 | "documentInterfaces": false, 15 | "documentInternalElements": false, 16 | "documentExposedElements": false, 17 | "documentPrivateElements": false, 18 | "documentPrivateFields": false 19 | }, 20 | "orderingRules": { 21 | "usingDirectivesPlacement": "outsideNamespace" 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) .NET Foundation. All rights reserved. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/Microsoft.Azure.WebJobs.Extensions.ServiceBus/Triggers/OutputConverter.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using Microsoft.Azure.WebJobs.Host.Converters; 5 | using Microsoft.Azure.ServiceBus; 6 | 7 | namespace Microsoft.Azure.WebJobs.ServiceBus.Triggers 8 | { 9 | internal class OutputConverter : IObjectToTypeConverter 10 | where TInput : class 11 | { 12 | private readonly IConverter _innerConverter; 13 | 14 | public OutputConverter(IConverter innerConverter) 15 | { 16 | _innerConverter = innerConverter; 17 | } 18 | 19 | public bool TryConvert(object input, out Message output) 20 | { 21 | TInput typedInput = input as TInput; 22 | 23 | if (typedInput == null) 24 | { 25 | output = null; 26 | return false; 27 | } 28 | 29 | output = _innerConverter.Convert(typedInput); 30 | return true; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Microsoft.Azure.WebJobs.Extensions.ServiceBus/Bindings/StringToBrokeredMessageConverter.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Diagnostics.CodeAnalysis; 6 | using System.IO; 7 | using Microsoft.Azure.WebJobs.Host.Converters; 8 | using Microsoft.Azure.ServiceBus; 9 | 10 | namespace Microsoft.Azure.WebJobs.ServiceBus.Bindings 11 | { 12 | internal class StringToBrokeredMessageConverter : IConverter 13 | { 14 | [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope")] 15 | public Message Convert(string input) 16 | { 17 | if (input == null) 18 | { 19 | throw new InvalidOperationException("A brokered message cannot contain a null string instance."); 20 | } 21 | 22 | byte[] bytes = StrictEncodings.Utf8.GetBytes(input); 23 | 24 | return new Message(bytes) 25 | { 26 | ContentType = ContentTypes.TextPlain 27 | }; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Microsoft.Azure.WebJobs.Extensions.ServiceBus/Bindings/MessageSenderExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Diagnostics; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | using Microsoft.Azure.WebJobs.ServiceBus.Listeners; 9 | using Microsoft.Azure.ServiceBus; 10 | using Microsoft.Azure.ServiceBus.Core; 11 | 12 | namespace Microsoft.Azure.WebJobs.ServiceBus.Bindings 13 | { 14 | internal static class MessageSenderExtensions 15 | { 16 | public static async Task SendAndCreateEntityIfNotExists(this MessageSender sender, Message message, 17 | Guid functionInstanceId, EntityType entityType, CancellationToken cancellationToken) 18 | { 19 | if (sender == null) 20 | { 21 | throw new ArgumentNullException("sender"); 22 | } 23 | 24 | ServiceBusCausalityHelper.EncodePayload(functionInstanceId, message); 25 | 26 | cancellationToken.ThrowIfCancellationRequested(); 27 | 28 | await sender.SendAsync(message); 29 | return; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Microsoft.Azure.WebJobs.Extensions.ServiceBus/Bindings/IBindableServiceBusPath.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System.Collections.Generic; 5 | 6 | namespace Microsoft.Azure.WebJobs.ServiceBus.Bindings 7 | { 8 | internal interface IBindableServiceBusPath 9 | { 10 | string QueueOrTopicNamePattern { get; } 11 | 12 | /// 13 | /// Gets a value indicating whether this path is bound. 14 | /// 15 | bool IsBound { get; } 16 | 17 | /// 18 | /// Gets the collection of parameter names for the path. 19 | /// 20 | IEnumerable ParameterNames { get; } 21 | 22 | /// 23 | /// Bind to the path. 24 | /// 25 | /// The binding data. 26 | /// The path binding. 27 | string Bind(IReadOnlyDictionary bindingData); 28 | 29 | /// 30 | /// Gets a string representation of the path. 31 | /// 32 | /// 33 | string ToString(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /test/Microsoft.Azure.WebJobs.Extensions.ServiceBus.Tests/PublicSurfaceTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using Microsoft.Azure.WebJobs.Host.TestCommon; 5 | using Xunit; 6 | 7 | namespace Microsoft.Azure.WebJobs.Host.UnitTests 8 | { 9 | public class PublicSurfaceTests 10 | { 11 | [Fact] 12 | public void WebJobs_Extensions_ServiceBus_VerifyPublicSurfaceArea() 13 | { 14 | var assembly = typeof(ServiceBusAttribute).Assembly; 15 | 16 | var expected = new[] 17 | { 18 | "Constants", 19 | "EntityType", 20 | "MessageProcessor", 21 | "MessagingProvider", 22 | "ServiceBusAccountAttribute", 23 | "ServiceBusAttribute", 24 | "ServiceBusTriggerAttribute", 25 | "ServiceBusHostBuilderExtensions", 26 | "ServiceBusOptions", 27 | "ServiceBusWebJobsStartup", 28 | "SessionMessageProcessor", 29 | "BatchOptions" 30 | }; 31 | 32 | TestHelpers.AssertPublicTypes(expected, assembly); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Microsoft.Azure.WebJobs.Extensions.ServiceBus/Bindings/CompositeArgumentBindingProvider.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System.Collections.Generic; 5 | using System.Reflection; 6 | using Microsoft.Azure.WebJobs.Host.Bindings; 7 | 8 | namespace Microsoft.Azure.WebJobs.ServiceBus.Bindings 9 | { 10 | internal class CompositeArgumentBindingProvider : IQueueArgumentBindingProvider 11 | { 12 | private readonly IEnumerable _providers; 13 | 14 | public CompositeArgumentBindingProvider(params IQueueArgumentBindingProvider[] providers) 15 | { 16 | _providers = providers; 17 | } 18 | 19 | public IArgumentBinding TryCreate(ParameterInfo parameter) 20 | { 21 | foreach (IQueueArgumentBindingProvider provider in _providers) 22 | { 23 | IArgumentBinding binding = provider.TryCreate(parameter); 24 | 25 | if (binding != null) 26 | { 27 | return binding; 28 | } 29 | } 30 | 31 | return null; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Microsoft.Azure.WebJobs.Extensions.ServiceBus/GlobalSuppressions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "AzureWebJobs", Scope = "member", Target = "Microsoft.Azure.WebJobs.ServiceBus.MessagingProvider.#GetConnectionString(System.String)")] 5 | [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Prefetch", Scope = "member", Target = "Microsoft.Azure.WebJobs.ServiceBus.ServiceBusConfiguration.#PrefetchCount")] 6 | [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable", Scope = "type", Target = "Microsoft.Azure.WebJobs.ServiceBus.EventHubListener+Listenter")] 7 | [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable", Scope = "type", Target = "Microsoft.Azure.WebJobs.ServiceBus.EventHubListener+Listener")] 8 | [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1014:MarkAssembliesWithClsCompliant")] -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Service Bus Extension for Azure Functions [Archived] 2 | 3 | This GitHub project has been archived. Ongoing development on this project can be found in https://github.com/Azure/azure-sdk-for-net/tree/main/sdk/servicebus/Microsoft.Azure.WebJobs.Extensions.ServiceBus. 4 | 5 | This extension provides functionality for receiving Service Bus messges in Azure Functions, allowing you to easily write functions that respond to any message published to Service Bus. 6 | 7 | |Branch|Status| 8 | |---|---| 9 | |dev|[![Build Status](https://azfunc.visualstudio.com/Azure%20Functions/_apis/build/status/azure-functions-servicebus-extension-ci?branchName=dev)](https://azfunc.visualstudio.com/Azure%20Functions/_build/latest?definitionId=17&branchName=dev) 10 | 11 | ## License 12 | 13 | This project is under the benevolent umbrella of the [.NET Foundation](http://www.dotnetfoundation.org/) and is licensed under [the MIT License](https://github.com/Azure/azure-webjobs-sdk/blob/master/LICENSE.txt) 14 | 15 | ## Contributing 16 | 17 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 18 | -------------------------------------------------------------------------------- /src/Microsoft.Azure.WebJobs.Extensions.ServiceBus/Listeners/ServiceBusEntityPathHelper.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Text; 7 | using System.Text.RegularExpressions; 8 | 9 | namespace Microsoft.Azure.WebJobs.ServiceBus.Listeners 10 | { 11 | internal static class ServiceBusEntityPathHelper 12 | { 13 | public static EntityType ParseEntityType(string entityPath) 14 | { 15 | return entityPath.IndexOf("/Subscriptions/", StringComparison.OrdinalIgnoreCase) >= 0 ? EntityType.Topic : EntityType.Queue; 16 | } 17 | 18 | public static void ParseTopicAndSubscription(string entityPath, out string topic, out string subscription) 19 | { 20 | string[] arr = Regex.Split(entityPath, "/Subscriptions/", RegexOptions.IgnoreCase); 21 | 22 | if (arr.Length < 2) 23 | { 24 | throw new InvalidOperationException($"{entityPath} is either formatted incorrectly, or is not a valid Service Bus subscription path"); 25 | } 26 | 27 | topic = arr[0]; 28 | subscription = arr[1]; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Microsoft.Azure.WebJobs.Extensions.ServiceBus/Listeners/ServiceBusCausalityHelper.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using Microsoft.Azure.ServiceBus; 6 | 7 | namespace Microsoft.Azure.WebJobs.ServiceBus.Listeners 8 | { 9 | internal static class ServiceBusCausalityHelper 10 | { 11 | private const string ParentGuidFieldName = "$AzureWebJobsParentId"; 12 | 13 | public static void EncodePayload(Guid functionOwner, Message msg) 14 | { 15 | msg.UserProperties[ParentGuidFieldName] = functionOwner.ToString(); 16 | } 17 | 18 | public static Guid? GetOwner(Message msg) 19 | { 20 | object parent; 21 | if (msg.UserProperties.TryGetValue(ParentGuidFieldName, out parent)) 22 | { 23 | var parentString = parent as string; 24 | if (parentString != null) 25 | { 26 | Guid parentGuid; 27 | if (Guid.TryParse(parentString, out parentGuid)) 28 | { 29 | return parentGuid; 30 | } 31 | } 32 | } 33 | return null; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Microsoft.Azure.WebJobs.Extensions.ServiceBus/Constants.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using Newtonsoft.Json; 5 | 6 | namespace Microsoft.Azure.WebJobs.ServiceBus 7 | { 8 | public static class Constants 9 | { 10 | private static JsonSerializerSettings _serializerSettings = new JsonSerializerSettings 11 | { 12 | // The default value, DateParseHandling.DateTime, drops time zone information from DateTimeOffets. 13 | // This value appears to work well with both DateTimes (without time zone information) and DateTimeOffsets. 14 | DateParseHandling = DateParseHandling.DateTimeOffset, 15 | NullValueHandling = NullValueHandling.Ignore, 16 | Formatting = Formatting.Indented 17 | }; 18 | 19 | public static JsonSerializerSettings JsonSerializerSettings 20 | { 21 | get 22 | { 23 | return _serializerSettings; 24 | } 25 | } 26 | 27 | public const string DefaultConnectionStringName = "ServiceBus"; 28 | public const string DefaultConnectionSettingStringName = "AzureWebJobsServiceBus"; 29 | public const string DynamicSku = "Dynamic"; 30 | public const string AzureWebsiteSku = "WEBSITE_SKU"; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Microsoft.Azure.WebJobs.Extensions.ServiceBus/Bindings/BoundServiceBusPath.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | 7 | namespace Microsoft.Azure.WebJobs.ServiceBus.Bindings 8 | { 9 | /// 10 | /// Bindable queue or topic path strategy implementation for "degenerate" bindable patterns, 11 | /// i.e. containing no parameters. 12 | /// 13 | internal class BoundServiceBusPath : IBindableServiceBusPath 14 | { 15 | private readonly string _queueOrTopicNamePattern; 16 | 17 | public BoundServiceBusPath(string queueOrTopicNamePattern) 18 | { 19 | _queueOrTopicNamePattern = queueOrTopicNamePattern; 20 | } 21 | 22 | public string QueueOrTopicNamePattern 23 | { 24 | get { return _queueOrTopicNamePattern; } 25 | } 26 | 27 | public bool IsBound 28 | { 29 | get { return true; } 30 | } 31 | 32 | public IEnumerable ParameterNames 33 | { 34 | get { return Enumerable.Empty(); } 35 | } 36 | 37 | public string Bind(IReadOnlyDictionary bindingData) 38 | { 39 | return QueueOrTopicNamePattern; 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Microsoft.Azure.WebJobs.Extensions.ServiceBus/Bindings/CollectorValueProvider.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Threading.Tasks; 6 | using Microsoft.Azure.WebJobs.Host.Bindings; 7 | 8 | namespace Microsoft.Azure.WebJobs.ServiceBus.Bindings 9 | { 10 | internal class CollectorValueProvider : IValueProvider 11 | { 12 | private readonly ServiceBusEntity _entity; 13 | private readonly object _value; 14 | private readonly Type _valueType; 15 | 16 | public CollectorValueProvider(ServiceBusEntity entity, object value, Type valueType) 17 | { 18 | if (value != null && !valueType.IsAssignableFrom(value.GetType())) 19 | { 20 | throw new InvalidOperationException("value is not of the correct type."); 21 | } 22 | 23 | _entity = entity; 24 | _value = value; 25 | _valueType = valueType; 26 | } 27 | 28 | public Type Type 29 | { 30 | get { return _valueType; } 31 | } 32 | 33 | public Task GetValueAsync() 34 | { 35 | return Task.FromResult(_value); 36 | } 37 | 38 | public string ToInvokeString() 39 | { 40 | return _entity.MessageSender.Path; 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /test/Microsoft.Azure.WebJobs.Extensions.ServiceBus.Tests/Bindings/BoundServiceBusTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | using Microsoft.Azure.WebJobs.ServiceBus.Bindings; 10 | using Xunit; 11 | 12 | namespace Microsoft.Azure.WebJobs.ServiceBus.UnitTests.Bindings 13 | { 14 | public class BoundServiceBusTests 15 | { 16 | [Fact] 17 | public void Bind_IfNotNullBindingData_ReturnsResolvedQueueName() 18 | { 19 | const string queueOrTopicNamePattern = "queue-name-with-no-parameters"; 20 | var bindingData = new Dictionary { { "name", "value" } }; 21 | IBindableServiceBusPath path = new BoundServiceBusPath(queueOrTopicNamePattern); 22 | 23 | string result = path.Bind(bindingData); 24 | 25 | Assert.Equal(queueOrTopicNamePattern, result); 26 | } 27 | 28 | [Fact] 29 | public void Bind_IfNullBindingData_ReturnsResolvedQueueName() 30 | { 31 | const string queueOrTopicNamePattern = "queue-name-with-no-parameters"; 32 | IBindableServiceBusPath path = new BoundServiceBusPath(queueOrTopicNamePattern); 33 | 34 | string result = path.Bind(null); 35 | 36 | Assert.Equal(queueOrTopicNamePattern, result); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /test/Microsoft.Azure.WebJobs.Extensions.ServiceBus.Tests/MessageToByteArrayConverterTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.IO; 6 | using System.Runtime.Serialization; 7 | using System.Text; 8 | using System.Threading; 9 | using System.Threading.Tasks; 10 | using Microsoft.Azure.WebJobs.ServiceBus.Triggers; 11 | using Microsoft.Azure.ServiceBus; 12 | using Xunit; 13 | 14 | namespace Microsoft.Azure.WebJobs.ServiceBus.UnitTests 15 | { 16 | public class MessageToByteArrayConverterTests 17 | { 18 | private const string TestString = "This is a test!"; 19 | 20 | [Theory] 21 | [InlineData(ContentTypes.TextPlain)] 22 | [InlineData(ContentTypes.ApplicationJson)] 23 | [InlineData(ContentTypes.ApplicationOctetStream)] 24 | [InlineData("some-other-contenttype")] 25 | [InlineData(null)] 26 | public async Task ConvertAsync_ReturnsExpectedResults(string contentType) 27 | { 28 | Message message = new Message(Encoding.UTF8.GetBytes(TestString)); 29 | message.ContentType = contentType; 30 | MessageToByteArrayConverter converter = new MessageToByteArrayConverter(); 31 | 32 | byte[] result = await converter.ConvertAsync(message, CancellationToken.None); 33 | string decoded = Encoding.UTF8.GetString(result); 34 | Assert.Equal(TestString, decoded); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Microsoft.Azure.WebJobs.Extensions.ServiceBus/Bindings/OutputConverter.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using Microsoft.Azure.WebJobs.Host.Converters; 7 | 8 | namespace Microsoft.Azure.WebJobs.ServiceBus.Bindings 9 | { 10 | internal class OutputConverter : IAsyncObjectToTypeConverter 11 | where TInput : class 12 | { 13 | private readonly IAsyncConverter _innerConverter; 14 | 15 | public OutputConverter(IAsyncConverter innerConverter) 16 | { 17 | _innerConverter = innerConverter; 18 | } 19 | 20 | public async Task> TryConvertAsync(object input, 21 | CancellationToken cancellationToken) 22 | { 23 | TInput typedInput = input as TInput; 24 | 25 | if (typedInput == null) 26 | { 27 | return new ConversionResult 28 | { 29 | Succeeded = false, 30 | Result = null 31 | }; 32 | } 33 | 34 | ServiceBusEntity entity = await _innerConverter.ConvertAsync(typedInput, cancellationToken); 35 | 36 | return new ConversionResult 37 | { 38 | Succeeded = true, 39 | Result = entity 40 | }; 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Microsoft.Azure.WebJobs.Extensions.ServiceBus/Bindings/MessageConverterFactory.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Collections; 6 | using Microsoft.Azure.WebJobs.Host.Converters; 7 | using Microsoft.Azure.ServiceBus; 8 | 9 | namespace Microsoft.Azure.WebJobs.ServiceBus.Bindings 10 | { 11 | internal static class MessageConverterFactory 12 | { 13 | internal static IConverter Create() 14 | { 15 | if (typeof(TInput) == typeof(Message)) 16 | { 17 | return (IConverter)new IdentityConverter(); 18 | } 19 | else if (typeof(TInput) == typeof(string)) 20 | { 21 | return (IConverter)new StringToBrokeredMessageConverter(); 22 | } 23 | else if (typeof(TInput) == typeof(byte[])) 24 | { 25 | return (IConverter)new ByteArrayToBrokeredMessageConverter(); 26 | } 27 | else 28 | { 29 | if (typeof(TInput).IsPrimitive) 30 | { 31 | throw new NotSupportedException("Primitive types are not supported."); 32 | } 33 | 34 | if (typeof(IEnumerable).IsAssignableFrom(typeof(TInput))) 35 | { 36 | throw new InvalidOperationException("Nested collections are not supported."); 37 | } 38 | 39 | return new UserTypeToBrokeredMessageConverter(); 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /test/Microsoft.Azure.WebJobs.Extensions.ServiceBus.Tests/ServiceBusTriggerAttributeTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using Xunit; 5 | 6 | namespace Microsoft.Azure.WebJobs.ServiceBus.UnitTests 7 | { 8 | public class ServiceBusTriggerAttributeTests 9 | { 10 | [Fact] 11 | public void Constructor_Queue_SetsExpectedValues() 12 | { 13 | ServiceBusTriggerAttribute attribute = new ServiceBusTriggerAttribute("testqueue"); 14 | Assert.Equal("testqueue", attribute.QueueName); 15 | Assert.Null(attribute.SubscriptionName); 16 | Assert.Null(attribute.TopicName); 17 | 18 | attribute = new ServiceBusTriggerAttribute("testqueue"); 19 | Assert.Equal("testqueue", attribute.QueueName); 20 | Assert.Null(attribute.SubscriptionName); 21 | Assert.Null(attribute.TopicName); 22 | } 23 | 24 | [Fact] 25 | public void Constructor_Topic_SetsExpectedValues() 26 | { 27 | ServiceBusTriggerAttribute attribute = new ServiceBusTriggerAttribute("testtopic", "testsubscription"); 28 | Assert.Null(attribute.QueueName); 29 | Assert.Equal("testtopic", attribute.TopicName); 30 | Assert.Equal("testsubscription", attribute.SubscriptionName); 31 | 32 | attribute = new ServiceBusTriggerAttribute("testtopic", "testsubscription"); 33 | Assert.Null(attribute.QueueName); 34 | Assert.Equal("testtopic", attribute.TopicName); 35 | Assert.Equal("testsubscription", attribute.SubscriptionName); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Microsoft.Azure.WebJobs.Extensions.ServiceBus/Bindings/MessageSenderCollector.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Threading; 6 | using Microsoft.Azure.WebJobs.Host.Converters; 7 | using Microsoft.Azure.ServiceBus; 8 | 9 | namespace Microsoft.Azure.WebJobs.ServiceBus.Bindings 10 | { 11 | internal class MessageSenderCollector : ICollector 12 | { 13 | private readonly ServiceBusEntity _entity; 14 | private readonly IConverter _converter; 15 | private readonly Guid _functionInstanceId; 16 | 17 | public MessageSenderCollector(ServiceBusEntity entity, IConverter converter, 18 | Guid functionInstanceId) 19 | { 20 | if (entity == null) 21 | { 22 | throw new ArgumentNullException("entity"); 23 | } 24 | 25 | if (converter == null) 26 | { 27 | throw new ArgumentNullException("converter"); 28 | } 29 | 30 | _entity = entity; 31 | _converter = converter; 32 | _functionInstanceId = functionInstanceId; 33 | } 34 | 35 | public void Add(T item) 36 | { 37 | Message message = _converter.Convert(item); 38 | 39 | if (message == null) 40 | { 41 | throw new InvalidOperationException("Cannot enqueue a null brokered message instance."); 42 | } 43 | 44 | _entity.SendAndCreateEntityIfNotExistsAsync(message, _functionInstanceId, 45 | CancellationToken.None).GetAwaiter().GetResult(); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Microsoft.Azure.WebJobs.Extensions.ServiceBus/Bindings/BindableServiceBusPath.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Diagnostics; 7 | using System.Linq; 8 | using Microsoft.Azure.WebJobs.Host.Bindings; 9 | using Microsoft.Azure.WebJobs.Host.Bindings.Path; 10 | 11 | namespace Microsoft.Azure.WebJobs.ServiceBus.Bindings 12 | { 13 | /// 14 | /// Utility class with factory method to create an instance of a strategy class implementing interface. 15 | /// 16 | internal static class BindableServiceBusPath 17 | { 18 | /// 19 | /// A factory method detecting parameters in supplied queue or topic name pattern and creating 20 | /// an instance of relevant strategy class implementing . 21 | /// 22 | /// Service Bus queue or topic name pattern containing optional binding parameters. 23 | /// An object implementing 24 | public static IBindableServiceBusPath Create(string queueOrTopicNamePattern) 25 | { 26 | if (queueOrTopicNamePattern == null) 27 | { 28 | throw new ArgumentNullException("queueOrTopicNamePattern"); 29 | } 30 | 31 | BindingTemplate template = BindingTemplate.FromString(queueOrTopicNamePattern); 32 | 33 | if (template.ParameterNames.Count() > 0) 34 | { 35 | return new ParameterizedServiceBusPath(template); 36 | } 37 | 38 | return new BoundServiceBusPath(queueOrTopicNamePattern); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Microsoft.Azure.WebJobs.Extensions.ServiceBus/Bindings/ConverterValueBinder.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using Microsoft.Azure.ServiceBus; 8 | using Microsoft.Azure.WebJobs.Host.Bindings; 9 | using System.Diagnostics; 10 | 11 | namespace Microsoft.Azure.WebJobs.ServiceBus.Bindings 12 | { 13 | internal class ConverterValueBinder : IOrderedValueBinder 14 | { 15 | private readonly ServiceBusEntity _entity; 16 | private readonly IConverter _converter; 17 | private readonly Guid _functionInstanceId; 18 | 19 | public ConverterValueBinder(ServiceBusEntity entity, IConverter converter, 20 | Guid functionInstanceId) 21 | { 22 | _entity = entity; 23 | _converter = converter; 24 | _functionInstanceId = functionInstanceId; 25 | } 26 | 27 | public BindStepOrder StepOrder 28 | { 29 | get { return BindStepOrder.Enqueue; } 30 | } 31 | 32 | public Type Type 33 | { 34 | get { return typeof(TInput); } 35 | } 36 | 37 | public Task GetValueAsync() 38 | { 39 | return Task.FromResult(default(TInput)); 40 | } 41 | 42 | public string ToInvokeString() 43 | { 44 | return _entity.MessageSender.Path; 45 | } 46 | 47 | public Task SetValueAsync(object value, CancellationToken cancellationToken) 48 | { 49 | Message message = _converter.Convert((TInput)value); 50 | Debug.Assert(message != null); 51 | return _entity.SendAndCreateEntityIfNotExistsAsync(message, _functionInstanceId, cancellationToken); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /test/Microsoft.Azure.WebJobs.Extensions.ServiceBus.Tests/Bindings/ParameterizedServiceBusPathTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System.Collections.Generic; 5 | using Microsoft.Azure.WebJobs.Host.Bindings; 6 | using Microsoft.Azure.WebJobs.Host.Bindings.Path; 7 | using Microsoft.Azure.WebJobs.Host.TestCommon; 8 | using Microsoft.Azure.WebJobs.ServiceBus.Bindings; 9 | using Xunit; 10 | 11 | namespace Microsoft.Azure.WebJobs.ServiceBus.UnitTests.Bindings 12 | { 13 | public class ParameterizedServiceBusPathTests 14 | { 15 | [Fact] 16 | public void Bind_IfNotNullBindingData_ReturnsResolvedQueueName() 17 | { 18 | const string queueOrTopicNamePattern = "queue-{name}-with-{parameter}"; 19 | var bindingData = new Dictionary { { "name", "name" }, { "parameter", "parameter" } }; 20 | IBindableServiceBusPath path = CreateProductUnderTest(queueOrTopicNamePattern); 21 | 22 | string result = path.Bind(bindingData); 23 | 24 | Assert.Equal("queue-name-with-parameter", result); 25 | } 26 | 27 | [Fact] 28 | public void Bind_IfNullBindingData_Throws() 29 | { 30 | const string queueOrTopicNamePattern = "queue-{name}-with-{parameter}"; 31 | IBindableServiceBusPath path = CreateProductUnderTest(queueOrTopicNamePattern); 32 | 33 | ExceptionAssert.ThrowsArgumentNull(() => path.Bind(null), "bindingData"); 34 | } 35 | 36 | private static IBindableServiceBusPath CreateProductUnderTest(string queueOrTopicNamePattern) 37 | { 38 | BindingTemplate template = BindingTemplate.FromString(queueOrTopicNamePattern); 39 | IBindableServiceBusPath path = new ParameterizedServiceBusPath(template); 40 | return path; 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Microsoft.Azure.WebJobs.Extensions.ServiceBus/Bindings/ParameterizedServiceBusPath.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Diagnostics; 7 | using System.Linq; 8 | using Microsoft.Azure.WebJobs.Host.Bindings; 9 | using Microsoft.Azure.WebJobs.Host.Bindings.Path; 10 | 11 | namespace Microsoft.Azure.WebJobs.ServiceBus.Bindings 12 | { 13 | /// 14 | /// Implementation of strategy for paths 15 | /// containing one or more parameters. 16 | /// 17 | internal class ParameterizedServiceBusPath : IBindableServiceBusPath 18 | { 19 | private readonly BindingTemplate _template; 20 | 21 | public ParameterizedServiceBusPath(BindingTemplate template) 22 | { 23 | Debug.Assert(template != null, "template must not be null"); 24 | Debug.Assert(template.ParameterNames.Count() > 0, "template must contain one or more parameters"); 25 | 26 | _template = template; 27 | } 28 | 29 | public string QueueOrTopicNamePattern 30 | { 31 | get { return _template.Pattern; } 32 | } 33 | 34 | public bool IsBound 35 | { 36 | get { return false; } 37 | } 38 | 39 | public IEnumerable ParameterNames 40 | { 41 | get { return _template.ParameterNames; } 42 | } 43 | 44 | public string Bind(IReadOnlyDictionary bindingData) 45 | { 46 | if (bindingData == null) 47 | { 48 | throw new ArgumentNullException("bindingData"); 49 | } 50 | 51 | string queueOrTopicName = _template.Bind(bindingData); 52 | return queueOrTopicName; 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Microsoft.Azure.WebJobs.Extensions.ServiceBus/Bindings/MessageSenderAsyncCollector.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using Microsoft.Azure.ServiceBus; 8 | 9 | namespace Microsoft.Azure.WebJobs.ServiceBus.Bindings 10 | { 11 | internal class MessageSenderAsyncCollector : IAsyncCollector 12 | { 13 | private readonly ServiceBusEntity _entity; 14 | private readonly IConverter _converter; 15 | private readonly Guid _functionInstanceId; 16 | 17 | public MessageSenderAsyncCollector(ServiceBusEntity entity, IConverter converter, 18 | Guid functionInstanceId) 19 | { 20 | if (entity == null) 21 | { 22 | throw new ArgumentNullException("entity"); 23 | } 24 | 25 | if (converter == null) 26 | { 27 | throw new ArgumentNullException("converter"); 28 | } 29 | 30 | _entity = entity; 31 | _converter = converter; 32 | _functionInstanceId = functionInstanceId; 33 | } 34 | 35 | public Task AddAsync(T item, CancellationToken cancellationToken) 36 | { 37 | Message message = _converter.Convert(item); 38 | 39 | if (message == null) 40 | { 41 | throw new InvalidOperationException("Cannot enqueue a null brokered message instance."); 42 | } 43 | 44 | return _entity.SendAndCreateEntityIfNotExistsAsync(message, _functionInstanceId, cancellationToken); 45 | } 46 | 47 | public Task FlushAsync(CancellationToken cancellationToken = default(CancellationToken)) 48 | { 49 | // Batching not supported. 50 | return Task.FromResult(0); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /ServiceBusExtension.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27130.2024 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{639967B0-0544-4C52-94AC-9A3D25E33256}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebJobs.Extensions.ServiceBus.Tests", "test\Microsoft.Azure.WebJobs.Extensions.ServiceBus.Tests\WebJobs.Extensions.ServiceBus.Tests.csproj", "{340AB554-5482-4B3D-B65F-46DFF5AF1684}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebJobs.Extensions.ServiceBus", "src\Microsoft.Azure.WebJobs.Extensions.ServiceBus\WebJobs.Extensions.ServiceBus.csproj", "{A2B3C676-3DF0-43B4-92A2-7E7DAA7BF439}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|Any CPU = Debug|Any CPU 15 | Release|Any CPU = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {340AB554-5482-4B3D-B65F-46DFF5AF1684}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {340AB554-5482-4B3D-B65F-46DFF5AF1684}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {340AB554-5482-4B3D-B65F-46DFF5AF1684}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {340AB554-5482-4B3D-B65F-46DFF5AF1684}.Release|Any CPU.Build.0 = Release|Any CPU 22 | {A2B3C676-3DF0-43B4-92A2-7E7DAA7BF439}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {A2B3C676-3DF0-43B4-92A2-7E7DAA7BF439}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {A2B3C676-3DF0-43B4-92A2-7E7DAA7BF439}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {A2B3C676-3DF0-43B4-92A2-7E7DAA7BF439}.Release|Any CPU.Build.0 = Release|Any CPU 26 | EndGlobalSection 27 | GlobalSection(SolutionProperties) = preSolution 28 | HideSolutionNode = FALSE 29 | EndGlobalSection 30 | GlobalSection(NestedProjects) = preSolution 31 | {340AB554-5482-4B3D-B65F-46DFF5AF1684} = {639967B0-0544-4C52-94AC-9A3D25E33256} 32 | EndGlobalSection 33 | GlobalSection(ExtensibilityGlobals) = postSolution 34 | SolutionGuid = {371BFA14-0980-4A43-A18A-CA1C1A9CB784} 35 | EndGlobalSection 36 | EndGlobal 37 | -------------------------------------------------------------------------------- /src/Microsoft.Azure.WebJobs.Extensions.ServiceBus/Bindings/StringToServiceBusEntityConverter.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using Microsoft.Azure.ServiceBus.Core; 8 | 9 | namespace Microsoft.Azure.WebJobs.ServiceBus.Bindings 10 | { 11 | internal class StringToServiceBusEntityConverter : IAsyncConverter 12 | { 13 | private readonly ServiceBusAccount _account; 14 | private readonly IBindableServiceBusPath _defaultPath; 15 | private readonly EntityType _entityType; 16 | private readonly MessagingProvider _messagingProvider; 17 | 18 | public StringToServiceBusEntityConverter(ServiceBusAccount account, IBindableServiceBusPath defaultPath, EntityType entityType, MessagingProvider messagingProvider) 19 | { 20 | _account = account; 21 | _defaultPath = defaultPath; 22 | _entityType = entityType; 23 | _messagingProvider = messagingProvider; 24 | } 25 | 26 | public Task ConvertAsync(string input, CancellationToken cancellationToken) 27 | { 28 | string queueOrTopicName; 29 | 30 | // For convenience, treat an an empty string as a request for the default value. 31 | if (String.IsNullOrEmpty(input) && _defaultPath.IsBound) 32 | { 33 | queueOrTopicName = _defaultPath.Bind(null); 34 | } 35 | else 36 | { 37 | queueOrTopicName = input; 38 | } 39 | 40 | cancellationToken.ThrowIfCancellationRequested(); 41 | var messageSender = _messagingProvider.CreateMessageSender(queueOrTopicName, _account.ConnectionString); 42 | 43 | var entity = new ServiceBusEntity 44 | { 45 | MessageSender = messageSender, 46 | EntityType = _entityType 47 | }; 48 | 49 | return Task.FromResult(entity); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Microsoft.Azure.WebJobs.Extensions.ServiceBus/Config/ServiceBusWebJobsBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using Microsoft.Azure.WebJobs; 6 | using Microsoft.Azure.WebJobs.ServiceBus; 7 | using Microsoft.Azure.WebJobs.ServiceBus.Config; 8 | using Microsoft.Extensions.Configuration; 9 | using Microsoft.Extensions.DependencyInjection; 10 | using Microsoft.Extensions.DependencyInjection.Extensions; 11 | 12 | namespace Microsoft.Extensions.Hosting 13 | { 14 | public static class ServiceBusHostBuilderExtensions 15 | { 16 | public static IWebJobsBuilder AddServiceBus(this IWebJobsBuilder builder) 17 | { 18 | if (builder == null) 19 | { 20 | throw new ArgumentNullException(nameof(builder)); 21 | } 22 | 23 | builder.AddServiceBus(p => { }); 24 | 25 | return builder; 26 | } 27 | 28 | public static IWebJobsBuilder AddServiceBus(this IWebJobsBuilder builder, Action configure) 29 | { 30 | if (builder == null) 31 | { 32 | throw new ArgumentNullException(nameof(builder)); 33 | } 34 | 35 | if (configure == null) 36 | { 37 | throw new ArgumentNullException(nameof(configure)); 38 | } 39 | 40 | builder.AddExtension() 41 | .ConfigureOptions((config, path, options) => 42 | { 43 | options.ConnectionString = config.GetConnectionString(Constants.DefaultConnectionStringName) ?? 44 | config[Constants.DefaultConnectionSettingStringName]; 45 | 46 | IConfigurationSection section = config.GetSection(path); 47 | section.Bind(options); 48 | 49 | configure(options); 50 | }); 51 | 52 | builder.Services.TryAddSingleton(); 53 | 54 | return builder; 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /test/Microsoft.Azure.WebJobs.Extensions.ServiceBus.Tests/Bindings/BindableServiceBusPathTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System.Collections.Generic; 5 | using Microsoft.Azure.WebJobs.Host.TestCommon; 6 | using Microsoft.Azure.WebJobs.ServiceBus.Bindings; 7 | using Xunit; 8 | 9 | namespace Microsoft.Azure.WebJobs.ServiceBus.UnitTests.Bindings 10 | { 11 | public class BindableServiceBusPathTests 12 | { 13 | [Fact] 14 | public void Create_IfNonParameterizedPattern_ReturnsBoundPath() 15 | { 16 | const string queueOrTopicNamePattern = "queue-name-with-no-parameters"; 17 | 18 | IBindableServiceBusPath path = BindableServiceBusPath.Create(queueOrTopicNamePattern); 19 | 20 | Assert.NotNull(path); 21 | Assert.Equal(queueOrTopicNamePattern, path.QueueOrTopicNamePattern); 22 | Assert.True(path.IsBound); 23 | } 24 | 25 | [Fact] 26 | public void Create_IfParameterizedPattern_ReturnsNotBoundPath() 27 | { 28 | const string queueOrTopicNamePattern = "queue-{name}-with-{parameter}"; 29 | 30 | IBindableServiceBusPath path = BindableServiceBusPath.Create(queueOrTopicNamePattern); 31 | 32 | Assert.NotNull(path); 33 | Assert.Equal(queueOrTopicNamePattern, path.QueueOrTopicNamePattern); 34 | Assert.False(path.IsBound); 35 | } 36 | 37 | [Fact] 38 | public void Create_IfNullPattern_Throws() 39 | { 40 | ExceptionAssert.ThrowsArgumentNull(() => BindableServiceBusPath.Create(null), "queueOrTopicNamePattern"); 41 | } 42 | 43 | [Fact] 44 | public void Create_IfMalformedPattern_PropagatesThrownException() 45 | { 46 | const string queueNamePattern = "malformed-queue-{name%"; 47 | 48 | ExceptionAssert.ThrowsFormat( 49 | () => BindableServiceBusPath.Create(queueNamePattern), 50 | "Invalid template 'malformed-queue-{name%'. Missing closing bracket at position 17."); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Microsoft.Azure.WebJobs.Extensions.ServiceBus/Bindings/NonNullConverterValueBinder.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Diagnostics; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | using Microsoft.Azure.ServiceBus; 9 | using Microsoft.Azure.WebJobs.Host.Bindings; 10 | 11 | namespace Microsoft.Azure.WebJobs.ServiceBus.Bindings 12 | { 13 | // Same as ConverterValueBinder, but doesn't enqueue null values. 14 | internal class NonNullConverterValueBinder : IOrderedValueBinder 15 | { 16 | private readonly ServiceBusEntity _entity; 17 | private readonly IConverter _converter; 18 | private readonly Guid _functionInstanceId; 19 | 20 | public NonNullConverterValueBinder(ServiceBusEntity entity, IConverter converter, 21 | Guid functionInstanceId) 22 | { 23 | _entity = entity; 24 | _converter = converter; 25 | _functionInstanceId = functionInstanceId; 26 | } 27 | 28 | public BindStepOrder StepOrder 29 | { 30 | get { return BindStepOrder.Enqueue; } 31 | } 32 | 33 | public Type Type 34 | { 35 | get { return typeof(TInput); } 36 | } 37 | 38 | public Task GetValueAsync() 39 | { 40 | return Task.FromResult(null); 41 | } 42 | 43 | public string ToInvokeString() 44 | { 45 | return _entity.MessageSender.Path; 46 | } 47 | 48 | public Task SetValueAsync(object value, CancellationToken cancellationToken) 49 | { 50 | if (value == null) 51 | { 52 | return Task.FromResult(0); 53 | } 54 | 55 | Debug.Assert(value is TInput); 56 | Message message = _converter.Convert((TInput)value); 57 | Debug.Assert(message != null); 58 | 59 | return _entity.SendAndCreateEntityIfNotExistsAsync(message, _functionInstanceId, cancellationToken); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/Microsoft.Azure.WebJobs.Extensions.ServiceBus/ServiceBusAccount.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Globalization; 6 | using Microsoft.Azure.WebJobs.Logging; 7 | using Microsoft.Extensions.Configuration; 8 | 9 | namespace Microsoft.Azure.WebJobs.ServiceBus 10 | { 11 | internal class ServiceBusAccount 12 | { 13 | private readonly ServiceBusOptions _options; 14 | private readonly IConnectionProvider _connectionProvider; 15 | private readonly IConfiguration _configuration; 16 | private string _connectionString; 17 | 18 | public ServiceBusAccount(ServiceBusOptions options, IConfiguration configuration, IConnectionProvider connectionProvider = null) 19 | { 20 | _options = options ?? throw new ArgumentNullException(nameof(options)); 21 | _configuration = configuration; 22 | _connectionProvider = connectionProvider; 23 | } 24 | 25 | internal ServiceBusAccount() 26 | { 27 | } 28 | 29 | public virtual string ConnectionString 30 | { 31 | get 32 | { 33 | if (string.IsNullOrEmpty(_connectionString)) 34 | { 35 | _connectionString = _options.ConnectionString; 36 | if (_connectionProvider != null && !string.IsNullOrEmpty(_connectionProvider.Connection)) 37 | { 38 | _connectionString = _configuration.GetWebJobsConnectionString(_connectionProvider.Connection); 39 | } 40 | 41 | if (string.IsNullOrEmpty(_connectionString)) 42 | { 43 | throw new InvalidOperationException( 44 | string.Format(CultureInfo.InvariantCulture, "Microsoft Azure WebJobs SDK ServiceBus connection string '{0}' is missing or empty.", 45 | Sanitizer.Sanitize(_connectionProvider.Connection) ?? Constants.DefaultConnectionSettingStringName)); 46 | } 47 | } 48 | 49 | return _connectionString; 50 | } 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /test/Microsoft.Azure.WebJobs.Extensions.ServiceBus.Tests/WebJobs.Extensions.ServiceBus.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netcoreapp2.1 4 | false 5 | Microsoft.Azure.WebJobs.ServiceBus.UnitTests 6 | Microsoft.Azure.WebJobs.ServiceBus.UnitTests 7 | ..\..\src.ruleset 8 | true 9 | true 10 | ..\..\sign.snk 11 | 12 | 13 | false 14 | true 15 | 16 | 17 | 18 | false 19 | true 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | all 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /test/Microsoft.Azure.WebJobs.Extensions.ServiceBus.Tests/README.md: -------------------------------------------------------------------------------- 1 | # Service Bus Extension for Azure Functions guide to running integration tests locally 2 | Integration tests are implemented in the `EndToEndTests` and `SessionsEndToEndTests` classes and require special configuration to execute locally in Visual Studio or via dotnet test. 3 | 4 | All configuration is done via a json file called `appsettings.tests.json` which on windows should be located in the `%USERPROFILE%\.azurefunctions` folder (e.g. `C:\Users\user123\.azurefunctions`) 5 | 6 | **Note:** *The specifics of the configuration may change when validation code is modified so check the code for the latest configuration settings if tests do not pass in case this readme file was not updated.* 7 | 8 | Create the appropriate Azure resources if needed as explained below and create or update the `appsettings.tests.json` file in the location specified above by copying the configuration below and replacing all the `PLACEHOLDER` values 9 | 10 | appsettings.tests.json contents 11 | ``` 12 | { 13 | "ConnectionStrings": { 14 | "ServiceBus": "PLACEHOLDER", 15 | "ServiceBusSecondary": "PLACEHOLDER" 16 | }, 17 | "AzureWebJobsStorage": "PLACEHOLDER" 18 | } 19 | ``` 20 | Create a storage account and configure its connection string into `AzureWebJobsStorage`. This will be used by the webjobs hosts created by the tests. 21 | 22 | Create two service bus namespaces and configure their connection strings in `ConnectionStrings:ServiceBus` and `ConnectionStrings:ServiceBusSecondary`. 23 | 1. In the namespace configured into `ConnectionStrings:ServiceBus`, create queues with the following names: 24 | 1. `core-test-queue1` 25 | 2. `core-test-queue2` 26 | 3. `core-test-queue3` 27 | 4. `core-test-queue1-sessions` (enable sessions when creating) 28 | 2. In the namespace configured into `ConnectionStrings:ServiceBus`, create topics and subscriptions with the following names: 29 | 1. `core-test-topic1` with two subscriptions: `sub1` and `sub2` 30 | 2. `core-test-topic1-sessions` with one subscription: `sub1-sessions` (enable sessions in the subscription when creating) 31 | 3. In the namespace configured into `ConnectionStrings:ServiceBusSecondary`, create a queue with the following name: 32 | 1. `core-test-queue1` 33 | 34 | Change the message lock duration setting on all queues and topic subscriptions to 5 minutes to allow for delays associated with stepping through code in debug mode. -------------------------------------------------------------------------------- /src/Microsoft.Azure.WebJobs.Extensions.ServiceBus/Listeners/ServiceBusListenerFactory.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using Microsoft.Azure.WebJobs.Host.Executors; 5 | using Microsoft.Azure.WebJobs.Host.Listeners; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Text; 9 | using System.Threading; 10 | using System.Threading.Tasks; 11 | using Microsoft.Extensions.Logging; 12 | using Microsoft.Azure.WebJobs.Host.Protocols; 13 | 14 | namespace Microsoft.Azure.WebJobs.ServiceBus.Listeners 15 | { 16 | internal class ServiceBusListenerFactory : IListenerFactory 17 | { 18 | private readonly ServiceBusAccount _account; 19 | private readonly EntityType _entityType; 20 | private readonly string _entityPath; 21 | private readonly bool _isSessionsEnabled; 22 | private readonly ITriggeredFunctionExecutor _executor; 23 | private readonly FunctionDescriptor _descriptor; 24 | private readonly ServiceBusOptions _options; 25 | private readonly MessagingProvider _messagingProvider; 26 | private readonly ILoggerFactory _loggerFactory; 27 | private readonly bool _singleDispatch; 28 | 29 | public ServiceBusListenerFactory(ServiceBusAccount account, EntityType entityType, string entityPath, bool isSessionsEnabled, ITriggeredFunctionExecutor executor, 30 | FunctionDescriptor descriptor, ServiceBusOptions options, MessagingProvider messagingProvider, ILoggerFactory loggerFactory, bool singleDispatch) 31 | { 32 | _account = account; 33 | _entityType = entityType; 34 | _entityPath = entityPath; 35 | _isSessionsEnabled = isSessionsEnabled; 36 | _executor = executor; 37 | _descriptor = descriptor; 38 | _options = options; 39 | _messagingProvider = messagingProvider; 40 | _loggerFactory = loggerFactory; 41 | _singleDispatch = singleDispatch; 42 | } 43 | 44 | public Task CreateAsync(CancellationToken cancellationToken) 45 | { 46 | var listener = new ServiceBusListener(_descriptor.Id, _entityType, _entityPath, _isSessionsEnabled, _executor, _options, _account, _messagingProvider, _loggerFactory, _singleDispatch); 47 | return Task.FromResult(listener); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Microsoft.Azure.WebJobs.Extensions.ServiceBus/ServiceBusAccountAttribute.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System; 5 | 6 | namespace Microsoft.Azure.WebJobs 7 | { 8 | /// 9 | /// Attribute used to override the default ServiceBus account used by triggers and binders. 10 | /// 11 | /// 12 | /// This attribute can be applied at the parameter/method/class level, and the precedence 13 | /// is in that order. 14 | /// 15 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method | AttributeTargets.Parameter)] 16 | public sealed class ServiceBusAccountAttribute : Attribute, IConnectionProvider 17 | { 18 | /// 19 | /// Constructs a new instance. 20 | /// 21 | /// A string value indicating the Service Bus connection string to use. This 22 | /// string should be in one of the following formats. These checks will be applied in order and the 23 | /// first match wins. 24 | /// - The name of an "AzureWebJobs" prefixed app setting or connection string name. E.g., if your setting 25 | /// name is "AzureWebJobsMyServiceBus", you can specify "MyServiceBus" here. 26 | /// - Can be a string containing %% values (e.g. %StagingServiceBus%). The value provided will be passed 27 | /// to any INameResolver registered on the JobHostConfiguration to resolve the actual setting name to use. 28 | /// - Can be an app setting or connection string name of your choosing. 29 | /// 30 | public ServiceBusAccountAttribute(string account) 31 | { 32 | Account = account; 33 | } 34 | 35 | /// 36 | /// Gets or sets the name of the app setting that contains the Service Bus connection string. 37 | /// 38 | public string Account { get; private set; } 39 | 40 | /// 41 | /// Gets or sets the app setting name that contains the Service Bus connection string. 42 | /// 43 | string IConnectionProvider.Connection 44 | { 45 | get 46 | { 47 | return Account; 48 | } 49 | set 50 | { 51 | Account = value; 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /test/Microsoft.Azure.WebJobs.Extensions.ServiceBus.Tests/MessageProcessorTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using Microsoft.Azure.ServiceBus; 8 | using Microsoft.Azure.ServiceBus.Core; 9 | using Microsoft.Azure.WebJobs.Host.Executors; 10 | using Xunit; 11 | 12 | namespace Microsoft.Azure.WebJobs.ServiceBus.UnitTests 13 | { 14 | public class MessageProcessorTests 15 | { 16 | private readonly MessageProcessor _processor; 17 | private readonly MessageHandlerOptions _options; 18 | 19 | public MessageProcessorTests() 20 | { 21 | _options = new MessageHandlerOptions(ExceptionReceivedHandler); 22 | MessageReceiver receiver = new MessageReceiver("Endpoint=sb://test.servicebus.windows.net/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=abc123=", "test-entity"); 23 | _processor = new MessageProcessor(receiver, _options); 24 | } 25 | 26 | [Fact] 27 | public async Task CompleteProcessingMessageAsync_Failure_PropagatesException() 28 | { 29 | _options.AutoComplete = false; 30 | 31 | Message message = new Message(); 32 | var functionException = new InvalidOperationException("Kaboom!"); 33 | FunctionResult result = new FunctionResult(functionException); 34 | var ex = await Assert.ThrowsAsync(async () => 35 | { 36 | await _processor.CompleteProcessingMessageAsync(message, result, CancellationToken.None); 37 | }); 38 | 39 | Assert.Same(functionException, ex); 40 | } 41 | 42 | [Fact] 43 | public async Task CompleteProcessingMessageAsync_DefaultOnMessageOptions() 44 | { 45 | Message message = new Message(); 46 | FunctionResult result = new FunctionResult(true); 47 | await _processor.CompleteProcessingMessageAsync(message, result, CancellationToken.None); 48 | } 49 | 50 | [Fact] 51 | public void MessageOptions_ReturnsOptions() 52 | { 53 | Assert.Same(_options, _processor.MessageOptions); 54 | } 55 | 56 | Task ExceptionReceivedHandler(ExceptionReceivedEventArgs eventArgs) 57 | { 58 | return Task.CompletedTask; 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /src/Microsoft.Azure.WebJobs.Extensions.ServiceBus/Bindings/StringArgumentBindingProvider.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Reflection; 6 | using System.Threading.Tasks; 7 | using Microsoft.Azure.WebJobs.Host.Bindings; 8 | 9 | namespace Microsoft.Azure.WebJobs.ServiceBus.Bindings 10 | { 11 | internal class StringArgumentBindingProvider : IQueueArgumentBindingProvider 12 | { 13 | public IArgumentBinding TryCreate(ParameterInfo parameter) 14 | { 15 | if (!parameter.IsOut || parameter.ParameterType != typeof(string).MakeByRefType()) 16 | { 17 | return null; 18 | } 19 | 20 | return new StringArgumentBinding(); 21 | } 22 | 23 | private class StringArgumentBinding : IArgumentBinding 24 | { 25 | public Type ValueType 26 | { 27 | get { return typeof(string); } 28 | } 29 | 30 | /// 31 | /// The out string parameter is processed as follows: 32 | /// 33 | /// 34 | /// 35 | /// If the value is , no message will be sent. 36 | /// 37 | /// 38 | /// 39 | /// 40 | /// If the value is an empty string, a message with empty content will be sent. 41 | /// 42 | /// 43 | /// 44 | /// 45 | /// If the value is a non-empty string, a message with that content will be sent. 46 | /// 47 | /// 48 | /// 49 | /// 50 | public Task BindAsync(ServiceBusEntity value, ValueBindingContext context) 51 | { 52 | if (context == null) 53 | { 54 | throw new ArgumentNullException("context"); 55 | } 56 | 57 | IValueProvider provider = new NonNullConverterValueBinder(value, 58 | new StringToBrokeredMessageConverter(), context.FunctionInstanceId); 59 | 60 | return Task.FromResult(provider); 61 | } 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/Microsoft.Azure.WebJobs.Extensions.ServiceBus/Bindings/ByteArrayArgumentBindingProvider.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Reflection; 6 | using System.Threading.Tasks; 7 | using Microsoft.Azure.WebJobs.Host.Bindings; 8 | 9 | namespace Microsoft.Azure.WebJobs.ServiceBus.Bindings 10 | { 11 | internal class ByteArrayArgumentBindingProvider : IQueueArgumentBindingProvider 12 | { 13 | public IArgumentBinding TryCreate(ParameterInfo parameter) 14 | { 15 | if (!parameter.IsOut || parameter.ParameterType != typeof(byte[]).MakeByRefType()) 16 | { 17 | return null; 18 | } 19 | 20 | return new ByteArrayArgumentBinding(); 21 | } 22 | 23 | private class ByteArrayArgumentBinding : IArgumentBinding 24 | { 25 | public Type ValueType 26 | { 27 | get { return typeof(byte[]); } 28 | } 29 | 30 | /// 31 | /// The out byte array parameter is processed as follows: 32 | /// 33 | /// 34 | /// 35 | /// If the value is , no message will be sent. 36 | /// 37 | /// 38 | /// 39 | /// 40 | /// If the value is an empty byte array, a message with empty content will be sent. 41 | /// 42 | /// 43 | /// 44 | /// 45 | /// If the value is a non-empty byte array, a message with that content will be sent. 46 | /// 47 | /// 48 | /// 49 | /// 50 | public Task BindAsync(ServiceBusEntity value, ValueBindingContext context) 51 | { 52 | if (context == null) 53 | { 54 | throw new ArgumentNullException("context"); 55 | } 56 | 57 | IValueProvider provider = new NonNullConverterValueBinder(value, 58 | new ByteArrayToBrokeredMessageConverter(), context.FunctionInstanceId); 59 | 60 | return Task.FromResult(provider); 61 | } 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/Microsoft.Azure.WebJobs.Extensions.ServiceBus/Triggers/MessageToStringConverter.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Globalization; 6 | using System.IO; 7 | using System.Text; 8 | using System.Threading; 9 | using System.Threading.Tasks; 10 | using Microsoft.Azure.ServiceBus; 11 | using Microsoft.Azure.ServiceBus.InteropExtensions; 12 | 13 | namespace Microsoft.Azure.WebJobs.ServiceBus.Triggers 14 | { 15 | internal class MessageToStringConverter : IAsyncConverter 16 | { 17 | public async Task ConvertAsync(Message input, CancellationToken cancellationToken) 18 | { 19 | if (input == null) 20 | { 21 | throw new ArgumentNullException("input"); 22 | } 23 | if (input.Body == null) 24 | { 25 | return null; 26 | } 27 | Stream stream = new MemoryStream(input.Body); 28 | 29 | TextReader reader = new StreamReader(stream, StrictEncodings.Utf8); 30 | try 31 | { 32 | cancellationToken.ThrowIfCancellationRequested(); 33 | try 34 | { 35 | return await reader.ReadToEndAsync(); 36 | } 37 | catch (DecoderFallbackException) 38 | { 39 | // we'll try again below 40 | } 41 | 42 | // We may get here if the message is a string yet was DataContract-serialized when created. We'll 43 | // try to deserialize it here using GetBody(). This may fail as well, in which case we'll 44 | // provide a decent error. 45 | 46 | try 47 | { 48 | return input.GetBody(); 49 | } 50 | catch 51 | { 52 | // always possible to get a valid string from the message 53 | return Encoding.UTF8.GetString(input.Body, 0, input.Body.Length); 54 | } 55 | } 56 | finally 57 | { 58 | if (stream != null) 59 | { 60 | stream.Dispose(); 61 | } 62 | if (reader != null) 63 | { 64 | reader.Dispose(); 65 | } 66 | } 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /test/Microsoft.Azure.WebJobs.Extensions.ServiceBus.Tests/ServiceBusAccountTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using Microsoft.Extensions.Configuration; 6 | using Xunit; 7 | 8 | namespace Microsoft.Azure.WebJobs.ServiceBus.UnitTests 9 | { 10 | public class ServiceBusAccountTests 11 | { 12 | private readonly IConfiguration _configuration; 13 | 14 | public ServiceBusAccountTests() 15 | { 16 | _configuration = new ConfigurationBuilder() 17 | .AddEnvironmentVariables() 18 | .Build(); 19 | } 20 | 21 | [Fact] 22 | public void GetConnectionString_ReturnsExpectedConnectionString() 23 | { 24 | string defaultConnection = "Endpoint=sb://default.servicebus.windows.net/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=abc123="; 25 | var options = new ServiceBusOptions() 26 | { 27 | ConnectionString = defaultConnection 28 | }; 29 | var attribute = new ServiceBusTriggerAttribute("entity-name"); 30 | var account = new ServiceBusAccount(options, _configuration, attribute); 31 | 32 | Assert.True(defaultConnection == account.ConnectionString); 33 | } 34 | 35 | [Fact] 36 | public void GetConnectionString_ThrowsIfConnectionStringNullOrEmpty() 37 | { 38 | var config = new ServiceBusOptions(); 39 | var attribute = new ServiceBusTriggerAttribute("testqueue"); 40 | attribute.Connection = "MissingConnection"; 41 | 42 | var ex = Assert.Throws(() => 43 | { 44 | var account = new ServiceBusAccount(config, _configuration, attribute); 45 | var cs = account.ConnectionString; 46 | }); 47 | Assert.Equal("Microsoft Azure WebJobs SDK ServiceBus connection string 'MissingConnection' is missing or empty.", ex.Message); 48 | 49 | attribute.Connection = null; 50 | config.ConnectionString = null; 51 | ex = Assert.Throws(() => 52 | { 53 | var account = new ServiceBusAccount(config, _configuration, attribute); 54 | var cs = account.ConnectionString; 55 | }); 56 | Assert.Equal("Microsoft Azure WebJobs SDK ServiceBus connection string 'AzureWebJobsServiceBus' is missing or empty.", ex.Message); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/Microsoft.Azure.WebJobs.Extensions.ServiceBus/Bindings/UserTypeArgumentBindingProvider.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Collections; 6 | using System.Reflection; 7 | using System.Threading.Tasks; 8 | using Microsoft.Azure.WebJobs.Host.Bindings; 9 | using Microsoft.Azure.WebJobs.Host.Converters; 10 | using Microsoft.Azure.ServiceBus; 11 | 12 | namespace Microsoft.Azure.WebJobs.ServiceBus.Bindings 13 | { 14 | internal class UserTypeArgumentBindingProvider : IQueueArgumentBindingProvider 15 | { 16 | public IArgumentBinding TryCreate(ParameterInfo parameter) 17 | { 18 | if (!parameter.IsOut) 19 | { 20 | return null; 21 | } 22 | 23 | Type itemType = parameter.ParameterType.GetElementType(); 24 | 25 | if (typeof(IEnumerable).IsAssignableFrom(itemType)) 26 | { 27 | throw new InvalidOperationException("Enumerable types are not supported. Use ICollector or IAsyncCollector instead."); 28 | } 29 | else if (typeof(object) == itemType) 30 | { 31 | throw new InvalidOperationException("Object element types are not supported."); 32 | } 33 | 34 | return CreateBinding(itemType); 35 | } 36 | 37 | private static IArgumentBinding CreateBinding(Type itemType) 38 | { 39 | Type genericType = typeof(UserTypeArgumentBinding<>).MakeGenericType(itemType); 40 | return (IArgumentBinding)Activator.CreateInstance(genericType); 41 | } 42 | 43 | private class UserTypeArgumentBinding : IArgumentBinding 44 | { 45 | public Type ValueType 46 | { 47 | get { return typeof(TInput); } 48 | } 49 | 50 | public Task BindAsync(ServiceBusEntity value, ValueBindingContext context) 51 | { 52 | if (context == null) 53 | { 54 | throw new ArgumentNullException("context"); 55 | } 56 | 57 | IConverter converter = new UserTypeToBrokeredMessageConverter(); 58 | IValueProvider provider = new ConverterValueBinder(value, converter, 59 | context.FunctionInstanceId); 60 | 61 | return Task.FromResult(provider); 62 | } 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/Microsoft.Azure.WebJobs.Extensions.ServiceBus/WebJobs.Extensions.ServiceBus.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | Microsoft.Azure.WebJobs.ServiceBus 6 | Microsoft.Azure.WebJobs.ServiceBus 7 | Microsoft.Azure.WebJobs.Extensions.ServiceBus 8 | Microsoft Azure WebJobs SDK ServiceBus Extension 9 | 4.3.1 10 | N/A 11 | $(Version) Commit hash: $(CommitHash) 12 | Microsoft 13 | Microsoft 14 | © Microsoft Corporation. All rights reserved. 15 | True 16 | MIT 17 | webjobs.png 18 | http://go.microsoft.com/fwlink/?LinkID=320972 19 | git 20 | https://github.com/Azure/azure-functions-servicebus-extension 21 | true 22 | ..\..\sign.snk 23 | ..\..\src.ruleset 24 | true 25 | 26 | 27 | 28 | false 29 | true 30 | 31 | 1701;1702 32 | 33 | 34 | 35 | false 36 | true 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | all 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /src/Microsoft.Azure.WebJobs.Extensions.ServiceBus/Bindings/MessageSenderArgumentBindingProvider.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Reflection; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | using Microsoft.Azure.ServiceBus.Core; 9 | using Microsoft.Azure.WebJobs.Host.Bindings; 10 | 11 | namespace Microsoft.Azure.WebJobs.ServiceBus.Bindings 12 | { 13 | internal class MessageSenderArgumentBindingProvider : IQueueArgumentBindingProvider 14 | { 15 | public IArgumentBinding TryCreate(ParameterInfo parameter) 16 | { 17 | if (parameter.ParameterType != typeof(MessageSender)) 18 | { 19 | return null; 20 | } 21 | 22 | return new MessageSenderArgumentBinding(); 23 | } 24 | 25 | internal class MessageSenderArgumentBinding : IArgumentBinding 26 | { 27 | public Type ValueType 28 | { 29 | get { return typeof(MessageSender); } 30 | } 31 | 32 | public Task BindAsync(ServiceBusEntity value, ValueBindingContext context) 33 | { 34 | if (context == null) 35 | { 36 | throw new ArgumentNullException("context"); 37 | } 38 | 39 | IValueProvider provider = new MessageSenderValueBinder(value.MessageSender); 40 | 41 | return Task.FromResult(provider); 42 | } 43 | 44 | private class MessageSenderValueBinder : IValueBinder 45 | { 46 | private readonly MessageSender _messageSender; 47 | 48 | public MessageSenderValueBinder(MessageSender messageSender) 49 | { 50 | _messageSender = messageSender; 51 | } 52 | 53 | public BindStepOrder StepOrder 54 | { 55 | get { return BindStepOrder.Enqueue; } 56 | } 57 | 58 | public Type Type 59 | { 60 | get { return typeof(MessageSender); } 61 | } 62 | 63 | public Task GetValueAsync() 64 | { 65 | return Task.FromResult(_messageSender); 66 | } 67 | 68 | public string ToInvokeString() 69 | { 70 | return _messageSender.Path; 71 | } 72 | 73 | public Task SetValueAsync(object value, CancellationToken cancellationToken) 74 | { 75 | return Task.CompletedTask; 76 | } 77 | } 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Security 4 | 5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). 6 | 7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below. 8 | 9 | ## Reporting Security Issues 10 | 11 | **Please do not report security vulnerabilities through public GitHub issues.** 12 | 13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report). 14 | 15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey). 16 | 17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc). 18 | 19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 20 | 21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 22 | * Full paths of source file(s) related to the manifestation of the issue 23 | * The location of the affected source code (tag/branch/commit or direct URL) 24 | * Any special configuration required to reproduce the issue 25 | * Step-by-step instructions to reproduce the issue 26 | * Proof-of-concept or exploit code (if possible) 27 | * Impact of the issue, including how an attacker might exploit the issue 28 | 29 | This information will help us triage your report more quickly. 30 | 31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs. 32 | 33 | ## Preferred Languages 34 | 35 | We prefer all communications to be in English. 36 | 37 | ## Policy 38 | 39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd). 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/Microsoft.Azure.WebJobs.Extensions.ServiceBus/Triggers/MessageToPocoConverter.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Runtime.Serialization; 7 | using System.Text; 8 | using Microsoft.Azure.ServiceBus; 9 | using Microsoft.Azure.ServiceBus.InteropExtensions; 10 | using Newtonsoft.Json; 11 | 12 | namespace Microsoft.Azure.WebJobs.ServiceBus.Triggers 13 | { 14 | // Convert from Message --> T 15 | internal class MessageToPocoConverter : IConverter 16 | { 17 | public MessageToPocoConverter() 18 | { 19 | } 20 | 21 | public TElement Convert(Message message) 22 | { 23 | // 1. If ContentType is "application/json" deserialize as JSON 24 | // 2. If ContentType is not "application/json" attempt to deserialize using Message.GetBody, which will handle cases like XML object serialization 25 | // 3. If this deserialization fails, do a final attempt at JSON deserialization to catch cases where the content type might be incorrect 26 | 27 | if (message.ContentType == ContentTypes.ApplicationJson) 28 | { 29 | return DeserializeJsonObject(message); 30 | } 31 | else 32 | { 33 | try 34 | { 35 | return message.GetBody(); 36 | } 37 | catch (SerializationException) 38 | { 39 | return DeserializeJsonObject(message); 40 | } 41 | } 42 | } 43 | 44 | private static TElement DeserializeJsonObject(Message message) 45 | { 46 | string contents = StrictEncodings.Utf8.GetString(message.Body); 47 | 48 | try 49 | { 50 | return JsonConvert.DeserializeObject(contents, Constants.JsonSerializerSettings); 51 | } 52 | catch (JsonException e) 53 | { 54 | // Easy to have the queue payload not deserialize properly. So give a useful error. 55 | string msg = string.Format( 56 | @"Binding parameters to complex objects (such as '{0}') uses Json.NET serialization or XML object serialization. 57 | 1. If ContentType is 'application/json' deserialize as JSON 58 | 2. If ContentType is not 'application/json' attempt to deserialize using Message.GetBody, which will handle cases like XML object serialization 59 | 3. If this deserialization fails, do a final attempt at JSON deserialization to catch cases where the content type might be incorrect 60 | The JSON parser failed: {1} 61 | ", typeof(TElement).Name, e.Message); 62 | throw new InvalidOperationException(msg); 63 | } 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /test/Microsoft.Azure.WebJobs.Extensions.ServiceBus.Tests/Bindings/ServiceBusAttributeBindingProviderTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Reflection; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | using Microsoft.Azure.ServiceBus; 10 | using Microsoft.Azure.WebJobs.Host.Bindings; 11 | using Microsoft.Azure.WebJobs.ServiceBus.Bindings; 12 | using Microsoft.Extensions.Configuration; 13 | using Microsoft.Extensions.Options; 14 | using Moq; 15 | using Xunit; 16 | 17 | namespace Microsoft.Azure.WebJobs.ServiceBus.UnitTests.Bindings 18 | { 19 | public class ServiceBusAttributeBindingProviderTests 20 | { 21 | private readonly ServiceBusAttributeBindingProvider _provider; 22 | private readonly IConfiguration _configuration; 23 | 24 | public ServiceBusAttributeBindingProviderTests() 25 | { 26 | _configuration = new ConfigurationBuilder() 27 | .AddEnvironmentVariables() 28 | .Build(); 29 | Mock mockResolver = new Mock(MockBehavior.Strict); 30 | ServiceBusOptions config = new ServiceBusOptions(); 31 | _provider = new ServiceBusAttributeBindingProvider(mockResolver.Object, config, _configuration, new MessagingProvider(new OptionsWrapper(config))); 32 | } 33 | 34 | [Fact] 35 | public async Task TryCreateAsync_AccountOverride_OverrideIsApplied() 36 | { 37 | 38 | ParameterInfo parameter = GetType().GetMethod("TestJob_AccountOverride").GetParameters()[0]; 39 | BindingProviderContext context = new BindingProviderContext(parameter, new Dictionary(), CancellationToken.None); 40 | 41 | IBinding binding = await _provider.TryCreateAsync(context); 42 | 43 | Assert.NotNull(binding); 44 | } 45 | 46 | [Fact] 47 | public async Task TryCreateAsync_DefaultAccount() 48 | { 49 | 50 | ParameterInfo parameter = GetType().GetMethod("TestJob").GetParameters()[0]; 51 | BindingProviderContext context = new BindingProviderContext(parameter, new Dictionary(), CancellationToken.None); 52 | 53 | IBinding binding = await _provider.TryCreateAsync(context); 54 | 55 | Assert.NotNull(binding); 56 | } 57 | 58 | public static void TestJob_AccountOverride( 59 | [ServiceBusAttribute("test"), 60 | ServiceBusAccount(Constants.DefaultConnectionStringName)] out Message message) 61 | { 62 | message = new Message(); 63 | } 64 | 65 | public static void TestJob( 66 | [ServiceBusAttribute("test", Connection = Constants.DefaultConnectionStringName)] out Message message) 67 | { 68 | message = new Message(); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/Microsoft.Azure.WebJobs.Extensions.ServiceBus/ServiceBusAttribute.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Diagnostics; 7 | using Microsoft.Azure.WebJobs.Description; 8 | using Microsoft.Azure.WebJobs.ServiceBus; 9 | using Microsoft.Azure.ServiceBus; 10 | 11 | namespace Microsoft.Azure.WebJobs 12 | { 13 | /// 14 | /// Attribute used to bind a parameter to Azure ServiceBus Queues and Topics. 15 | /// 16 | /// 17 | /// The method parameter type can be one of the following: 18 | /// 19 | /// BrokeredMessage (out parameter) 20 | /// (out parameter) 21 | /// (out parameter) 22 | /// A user-defined type (out parameter, serialized as JSON) 23 | /// 24 | /// of these types (to enqueue multiple messages via 25 | /// 26 | /// 27 | /// 28 | [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.ReturnValue)] 29 | [DebuggerDisplay("{QueueOrTopicName,nq}")] 30 | [ConnectionProvider(typeof(ServiceBusAccountAttribute))] 31 | [Binding] 32 | public sealed class ServiceBusAttribute : Attribute, IConnectionProvider 33 | { 34 | /// 35 | /// Initializes a new instance of the class. 36 | /// 37 | /// The name of the queue or topic to bind to. 38 | public ServiceBusAttribute(string queueOrTopicName) 39 | { 40 | QueueOrTopicName = queueOrTopicName; 41 | EntityType = EntityType.Queue; 42 | } 43 | 44 | /// 45 | /// Initializes a new instance of the class. 46 | /// 47 | /// The name of the queue or topic to bind to. 48 | /// The type of the entity to bind to. 49 | public ServiceBusAttribute(string queueOrTopicName, EntityType entityType) 50 | { 51 | QueueOrTopicName = queueOrTopicName; 52 | EntityType = entityType; 53 | } 54 | 55 | /// 56 | /// Gets the name of the queue or topic to bind to. 57 | /// 58 | public string QueueOrTopicName { get; private set; } 59 | 60 | /// 61 | /// Gets or sets the app setting name that contains the Service Bus connection string. 62 | /// 63 | public string Connection { get; set; } 64 | 65 | /// 66 | /// Value indicating the type of the entity to bind to. 67 | /// 68 | public EntityType EntityType { get; set; } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /test/Microsoft.Azure.WebJobs.Extensions.ServiceBus.Tests/MessagingProviderTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using Microsoft.Azure.ServiceBus; 5 | using Microsoft.Extensions.Options; 6 | using Xunit; 7 | 8 | namespace Microsoft.Azure.WebJobs.ServiceBus.UnitTests 9 | { 10 | public class MessagingProviderTests 11 | { 12 | [Fact] 13 | public void CreateMessageReceiver_ReturnsExpectedReceiver() 14 | { 15 | string defaultConnection = "Endpoint=sb://default.servicebus.windows.net/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=abc123="; 16 | var config = new ServiceBusOptions 17 | { 18 | ConnectionString = defaultConnection 19 | }; 20 | var provider = new MessagingProvider(new OptionsWrapper(config)); 21 | var receiver = provider.CreateMessageReceiver("entityPath", defaultConnection); 22 | Assert.Equal("entityPath", receiver.Path); 23 | 24 | var receiver2 = provider.CreateMessageReceiver("entityPath", defaultConnection); 25 | Assert.Same(receiver, receiver2); 26 | 27 | config.PrefetchCount = 100; 28 | receiver = provider.CreateMessageReceiver("entityPath1", defaultConnection); 29 | Assert.Equal(100, receiver.PrefetchCount); 30 | } 31 | 32 | [Fact] 33 | public void CreateClientEntity_ReturnsExpectedReceiver() 34 | { 35 | string defaultConnection = "Endpoint=sb://default.servicebus.windows.net/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=abc123="; 36 | var config = new ServiceBusOptions 37 | { 38 | ConnectionString = defaultConnection 39 | }; 40 | var provider = new MessagingProvider(new OptionsWrapper(config)); 41 | var clientEntity = provider.CreateClientEntity("entityPath", defaultConnection); 42 | Assert.Equal("entityPath", clientEntity.Path); 43 | 44 | var receiver2 = provider.CreateClientEntity("entityPath", defaultConnection); 45 | Assert.Same(clientEntity, receiver2); 46 | 47 | config.PrefetchCount = 100; 48 | clientEntity = provider.CreateClientEntity("entityPath1", defaultConnection); 49 | Assert.Equal(100, ((QueueClient)clientEntity).PrefetchCount); 50 | } 51 | 52 | [Fact] 53 | public void CreateMessageSender_ReturnsExpectedSender() 54 | { 55 | string defaultConnection = "Endpoint=sb://default.servicebus.windows.net/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=abc123="; 56 | var config = new ServiceBusOptions 57 | { 58 | ConnectionString = defaultConnection 59 | }; 60 | var provider = new MessagingProvider(new OptionsWrapper(config)); 61 | var sender = provider.CreateMessageSender("entityPath", defaultConnection); 62 | Assert.Equal("entityPath", sender.Path); 63 | 64 | var sender2 = provider.CreateMessageSender("entityPath", defaultConnection); 65 | Assert.Same(sender, sender2); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/Microsoft.Azure.WebJobs.Extensions.ServiceBus/Bindings/CollectorArgumentBindingProvider.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Diagnostics; 6 | using System.Reflection; 7 | using System.Threading.Tasks; 8 | using Microsoft.Azure.WebJobs.Host.Bindings; 9 | using Microsoft.Azure.WebJobs.Host.Converters; 10 | using Microsoft.Azure.ServiceBus; 11 | 12 | namespace Microsoft.Azure.WebJobs.ServiceBus.Bindings 13 | { 14 | internal class CollectorArgumentBindingProvider : IQueueArgumentBindingProvider 15 | { 16 | public IArgumentBinding TryCreate(ParameterInfo parameter) 17 | { 18 | Type parameterType = parameter.ParameterType; 19 | 20 | if (!parameterType.IsGenericType) 21 | { 22 | return null; 23 | } 24 | 25 | Type genericTypeDefinition = parameterType.GetGenericTypeDefinition(); 26 | 27 | if (genericTypeDefinition != typeof(ICollector<>)) 28 | { 29 | return null; 30 | } 31 | 32 | Type itemType = parameterType.GetGenericArguments()[0]; 33 | return CreateBinding(itemType); 34 | } 35 | 36 | private static IArgumentBinding CreateBinding(Type itemType) 37 | { 38 | MethodInfo method = typeof(CollectorArgumentBindingProvider).GetMethod("CreateBindingGeneric", 39 | BindingFlags.NonPublic | BindingFlags.Static); 40 | Debug.Assert(method != null); 41 | MethodInfo genericMethod = method.MakeGenericMethod(itemType); 42 | Debug.Assert(genericMethod != null); 43 | Func> lambda = 44 | (Func>)Delegate.CreateDelegate( 45 | typeof(Func>), genericMethod); 46 | return lambda.Invoke(); 47 | } 48 | 49 | private static IArgumentBinding CreateBindingGeneric() 50 | { 51 | return new CollectorArgumentBinding(MessageConverterFactory.Create()); 52 | } 53 | 54 | private class CollectorArgumentBinding : IArgumentBinding 55 | { 56 | private readonly IConverter _converter; 57 | 58 | public CollectorArgumentBinding(IConverter converter) 59 | { 60 | _converter = converter; 61 | } 62 | 63 | public Type ValueType 64 | { 65 | get { return typeof(ICollector); } 66 | } 67 | 68 | public Task BindAsync(ServiceBusEntity value, ValueBindingContext context) 69 | { 70 | if (context == null) 71 | { 72 | throw new ArgumentNullException("context"); 73 | } 74 | 75 | ICollector collector = new MessageSenderCollector(value, _converter, 76 | context.FunctionInstanceId); 77 | IValueProvider provider = new CollectorValueProvider(value, collector, typeof(ICollector)); 78 | 79 | return Task.FromResult(provider); 80 | } 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/Microsoft.Azure.WebJobs.Extensions.ServiceBus/Bindings/AsyncCollectorArgumentBindingProvider.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Diagnostics; 6 | using System.Reflection; 7 | using System.Threading.Tasks; 8 | using Microsoft.Azure.ServiceBus; 9 | using Microsoft.Azure.WebJobs.Host.Bindings; 10 | using Microsoft.Azure.WebJobs.Host.Converters; 11 | 12 | namespace Microsoft.Azure.WebJobs.ServiceBus.Bindings 13 | { 14 | internal class AsyncCollectorArgumentBindingProvider : IQueueArgumentBindingProvider 15 | { 16 | public IArgumentBinding TryCreate(ParameterInfo parameter) 17 | { 18 | Type parameterType = parameter.ParameterType; 19 | 20 | if (!parameterType.IsGenericType) 21 | { 22 | return null; 23 | } 24 | 25 | Type genericTypeDefinition = parameterType.GetGenericTypeDefinition(); 26 | 27 | if (genericTypeDefinition != typeof(IAsyncCollector<>)) 28 | { 29 | return null; 30 | } 31 | 32 | Type itemType = parameterType.GetGenericArguments()[0]; 33 | return CreateBinding(itemType); 34 | } 35 | 36 | private static IArgumentBinding CreateBinding(Type itemType) 37 | { 38 | MethodInfo method = typeof(AsyncCollectorArgumentBindingProvider).GetMethod("CreateBindingGeneric", 39 | BindingFlags.NonPublic | BindingFlags.Static); 40 | Debug.Assert(method != null); 41 | MethodInfo genericMethod = method.MakeGenericMethod(itemType); 42 | Debug.Assert(genericMethod != null); 43 | Func> lambda = 44 | (Func>)Delegate.CreateDelegate( 45 | typeof(Func>), genericMethod); 46 | return lambda.Invoke(); 47 | } 48 | 49 | private static IArgumentBinding CreateBindingGeneric() 50 | { 51 | return new AsyncCollectorArgumentBinding(MessageConverterFactory.Create()); 52 | } 53 | 54 | private class AsyncCollectorArgumentBinding : IArgumentBinding 55 | { 56 | private readonly IConverter _converter; 57 | 58 | public AsyncCollectorArgumentBinding(IConverter converter) 59 | { 60 | _converter = converter; 61 | } 62 | 63 | public Type ValueType 64 | { 65 | get { return typeof(IAsyncCollector); } 66 | } 67 | 68 | public Task BindAsync(ServiceBusEntity value, ValueBindingContext context) 69 | { 70 | if (context == null) 71 | { 72 | throw new ArgumentNullException("context"); 73 | } 74 | 75 | IAsyncCollector collector = new MessageSenderAsyncCollector(value, _converter, 76 | context.FunctionInstanceId); 77 | IValueProvider provider = new CollectorValueProvider(value, collector, typeof(IAsyncCollector)); 78 | 79 | return Task.FromResult(provider); 80 | } 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/Microsoft.Azure.WebJobs.Extensions.ServiceBus/Bindings/MessageArgumentBinding.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using Microsoft.Azure.ServiceBus; 8 | using Microsoft.Azure.WebJobs.Host.Bindings; 9 | 10 | namespace Microsoft.Azure.WebJobs.ServiceBus.Bindings 11 | { 12 | internal class MessageArgumentBinding : IArgumentBinding 13 | { 14 | public Type ValueType 15 | { 16 | get { return typeof(Message); } 17 | } 18 | 19 | public Task BindAsync(ServiceBusEntity value, ValueBindingContext context) 20 | { 21 | if (context == null) 22 | { 23 | throw new ArgumentNullException("context"); 24 | } 25 | 26 | IValueProvider provider = new MessageValueBinder(value, context.FunctionInstanceId); 27 | 28 | return Task.FromResult(provider); 29 | } 30 | 31 | private class MessageValueBinder : IOrderedValueBinder 32 | { 33 | private readonly ServiceBusEntity _entity; 34 | private readonly Guid _functionInstanceId; 35 | 36 | public MessageValueBinder(ServiceBusEntity entity, Guid functionInstanceId) 37 | { 38 | _entity = entity; 39 | _functionInstanceId = functionInstanceId; 40 | } 41 | 42 | public BindStepOrder StepOrder 43 | { 44 | get { return BindStepOrder.Enqueue; } 45 | } 46 | 47 | public Type Type 48 | { 49 | get { return typeof(Message); } 50 | } 51 | 52 | public Task GetValueAsync() 53 | { 54 | return Task.FromResult(null); 55 | } 56 | 57 | public string ToInvokeString() 58 | { 59 | return _entity.MessageSender.Path; 60 | } 61 | 62 | /// 63 | /// Sends a Message to the bound queue. 64 | /// 65 | /// BrokeredMessage instance as retrieved from user's WebJobs method argument. 66 | /// a cancellation token 67 | /// 68 | /// The out message parameter is processed as follows: 69 | /// 70 | /// 71 | /// 72 | /// If the value is , no message will be sent. 73 | /// 74 | /// 75 | /// 76 | /// 77 | /// If the value has empty content, a message with empty content will be sent. 78 | /// 79 | /// 80 | /// 81 | /// 82 | /// If the value has non-empty content, a message with that content will be sent. 83 | /// 84 | /// 85 | /// 86 | /// 87 | public async Task SetValueAsync(object value, CancellationToken cancellationToken) 88 | { 89 | if (value == null) 90 | { 91 | return; 92 | } 93 | 94 | var message = (Message)value; 95 | 96 | await _entity.SendAndCreateEntityIfNotExistsAsync(message, _functionInstanceId, cancellationToken); 97 | } 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /test/Microsoft.Azure.WebJobs.Extensions.ServiceBus.Tests/MessageToStringConverterTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.IO; 6 | using System.Runtime.Serialization; 7 | using System.Text; 8 | using System.Threading; 9 | using System.Threading.Tasks; 10 | using Microsoft.Azure.WebJobs.ServiceBus.Triggers; 11 | using Microsoft.Azure.ServiceBus; 12 | using Xunit; 13 | using System.Collections.Generic; 14 | using Microsoft.Azure.ServiceBus.InteropExtensions; 15 | 16 | namespace Microsoft.Azure.WebJobs.ServiceBus.UnitTests 17 | { 18 | public class MessageToStringConverterTests 19 | { 20 | private const string TestString = "This is a test!"; 21 | private const string TestJson = "{ value: 'This is a test!' }"; 22 | 23 | [Theory] 24 | [InlineData(ContentTypes.TextPlain, TestString)] 25 | [InlineData(ContentTypes.ApplicationJson, TestJson)] 26 | [InlineData(ContentTypes.ApplicationOctetStream, TestString)] 27 | [InlineData(null, TestJson)] 28 | [InlineData("application/xml", TestJson)] 29 | [InlineData(ContentTypes.TextPlain, null)] 30 | public async Task ConvertAsync_ReturnsExpectedResult_WithBinarySerializer(string contentType, string value) 31 | { 32 | byte[] bytes; 33 | using (MemoryStream ms = new MemoryStream()) 34 | { 35 | DataContractBinarySerializer.Instance.WriteObject(ms, value); 36 | bytes = ms.ToArray(); 37 | } 38 | 39 | Message message = new Message(bytes); 40 | message.ContentType = contentType; 41 | 42 | MessageToStringConverter converter = new MessageToStringConverter(); 43 | string result = await converter.ConvertAsync(message, CancellationToken.None); 44 | 45 | Assert.Equal(value, result); 46 | } 47 | 48 | [Theory] 49 | [InlineData(ContentTypes.TextPlain, TestString)] 50 | [InlineData(ContentTypes.ApplicationJson, TestJson)] 51 | [InlineData(ContentTypes.ApplicationOctetStream, TestString)] 52 | [InlineData(null, TestJson)] 53 | [InlineData("application/xml", TestJson)] 54 | [InlineData(ContentTypes.TextPlain, null)] 55 | [InlineData(ContentTypes.TextPlain, "")] 56 | public async Task ConvertAsync_ReturnsExpectedResult_WithSerializedString(string contentType, string value) 57 | { 58 | Message message = new Message(value == null ? null : Encoding.UTF8.GetBytes(value)); 59 | message.ContentType = contentType; 60 | 61 | MessageToStringConverter converter = new MessageToStringConverter(); 62 | string result = await converter.ConvertAsync(message, CancellationToken.None); 63 | Assert.Equal(value, result); 64 | } 65 | 66 | [Fact] 67 | public async Task ConvertAsync_ReturnsExpectedResult_WithSerializedObject() 68 | { 69 | 70 | byte[] bytes; 71 | using (MemoryStream ms = new MemoryStream()) 72 | { 73 | DataContractBinarySerializer.Instance.WriteObject(ms, new TestObject() { Text = "Test"}); 74 | bytes = ms.ToArray(); 75 | } 76 | 77 | Message message = new Message(bytes); 78 | 79 | MessageToStringConverter converter = new MessageToStringConverter(); 80 | string result = await converter.ConvertAsync(message, CancellationToken.None); 81 | Assert.Equal(Encoding.UTF8.GetString(message.Body), result); 82 | } 83 | 84 | [Serializable] 85 | public class TestObject 86 | { 87 | public string Text { get; set; } 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/Microsoft.Azure.WebJobs.Extensions.ServiceBus/SessionMessageProcessor.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using Microsoft.Azure.ServiceBus; 5 | using Microsoft.Azure.WebJobs.Host.Executors; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Text; 9 | using System.Threading; 10 | using System.Threading.Tasks; 11 | 12 | namespace Microsoft.Azure.WebJobs.ServiceBus 13 | { 14 | public class SessionMessageProcessor 15 | { 16 | public SessionMessageProcessor(ClientEntity clientEntity, SessionHandlerOptions sessionHandlerOptions) 17 | { 18 | ClientEntity = clientEntity ?? throw new ArgumentNullException(nameof(clientEntity)); 19 | SessionHandlerOptions = sessionHandlerOptions ?? throw new ArgumentNullException(nameof(sessionHandlerOptions)); 20 | } 21 | 22 | /// 23 | /// Gets the that will be used by the . 24 | /// 25 | public SessionHandlerOptions SessionHandlerOptions { get; } 26 | 27 | /// 28 | /// Gets or sets the that will be used by the . 29 | /// 30 | protected ClientEntity ClientEntity { get; set; } 31 | 32 | /// 33 | /// This method is called when there is a new message to process, before the job function is invoked. 34 | /// This allows any preprocessing to take place on the message before processing begins. 35 | /// 36 | /// The session associated with the message. 37 | /// The message to process. 38 | /// The to use. 39 | /// A that returns true if the message processing should continue, false otherwise. 40 | public virtual Task BeginProcessingMessageAsync(IMessageSession session, Message message, CancellationToken cancellationToken) 41 | { 42 | return Task.FromResult(true); 43 | } 44 | 45 | /// 46 | /// This method completes processing of the specified message, after the job function has been invoked. 47 | /// 48 | /// 49 | /// The message is completed by the ServiceBus SDK based on how the option 50 | /// is configured. E.g. if is false, it is up to the job function to complete 51 | /// the message. 52 | /// 53 | /// The session associated with the message. 54 | /// The message to complete processing for. 55 | /// The from the job invocation. 56 | /// The to use 57 | /// A that will complete the message processing. 58 | public virtual Task CompleteProcessingMessageAsync(IMessageSession session, Message message, FunctionResult result, CancellationToken cancellationToken) 59 | { 60 | if (result == null) 61 | { 62 | throw new ArgumentNullException(nameof(result)); 63 | } 64 | 65 | cancellationToken.ThrowIfCancellationRequested(); 66 | 67 | if (!result.Succeeded) 68 | { 69 | // if the invocation failed, we must propagate the 70 | // exception back to SB so it can handle message state 71 | // correctly 72 | throw result.Exception; 73 | } 74 | 75 | return Task.CompletedTask; 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/Microsoft.Azure.WebJobs.Extensions.ServiceBus/MessageProcessor.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using Microsoft.Azure.WebJobs.Host.Executors; 8 | using Microsoft.Azure.ServiceBus; 9 | using Microsoft.Azure.ServiceBus.Core; 10 | 11 | namespace Microsoft.Azure.WebJobs.ServiceBus 12 | { 13 | /// 14 | /// This class defines a strategy used for processing ServiceBus messages. 15 | /// 16 | /// 17 | /// Custom implementations can be specified by implementing 18 | /// a custom and setting it via . 19 | /// 20 | public class MessageProcessor 21 | { 22 | /// 23 | /// Constructs a new instance. 24 | /// 25 | /// The . 26 | /// The to use. 27 | public MessageProcessor(MessageReceiver messageReceiver, MessageHandlerOptions messageOptions) 28 | { 29 | MessageReceiver = messageReceiver ?? throw new ArgumentNullException(nameof(messageReceiver)); 30 | MessageOptions = messageOptions ?? throw new ArgumentNullException(nameof(messageOptions)); 31 | } 32 | 33 | /// 34 | /// Gets the that will be used by the . 35 | /// 36 | public MessageHandlerOptions MessageOptions { get; } 37 | 38 | /// 39 | /// Gets or sets the that will be used by the . 40 | /// 41 | protected MessageReceiver MessageReceiver { get; set; } 42 | 43 | /// 44 | /// This method is called when there is a new message to process, before the job function is invoked. 45 | /// This allows any preprocessing to take place on the message before processing begins. 46 | /// 47 | /// The message to process. 48 | /// The to use. 49 | /// A that returns true if the message processing should continue, false otherwise. 50 | public virtual Task BeginProcessingMessageAsync(Message message, CancellationToken cancellationToken) 51 | { 52 | return Task.FromResult(true); 53 | } 54 | 55 | /// 56 | /// This method completes processing of the specified message, after the job function has been invoked. 57 | /// 58 | /// 59 | /// The message is completed by the ServiceBus SDK based on how the option 60 | /// is configured. E.g. if is false, it is up to the job function to complete 61 | /// the message. 62 | /// 63 | /// The message to complete processing for. 64 | /// The from the job invocation. 65 | /// The to use 66 | /// A that will complete the message processing. 67 | public virtual Task CompleteProcessingMessageAsync(Message message, FunctionResult result, CancellationToken cancellationToken) 68 | { 69 | if (result == null) 70 | { 71 | throw new ArgumentNullException(nameof(result)); 72 | } 73 | 74 | cancellationToken.ThrowIfCancellationRequested(); 75 | 76 | if (!result.Succeeded) 77 | { 78 | // if the invocation failed, we must propagate the 79 | // exception back to SB so it can handle message state 80 | // correctly 81 | throw result.Exception; 82 | } 83 | 84 | return Task.CompletedTask; 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/Microsoft.Azure.WebJobs.Extensions.ServiceBus/Bindings/ServiceBusBinding.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Globalization; 6 | using System.Threading.Tasks; 7 | using Microsoft.Azure.ServiceBus.Core; 8 | using Microsoft.Azure.WebJobs.Host.Bindings; 9 | using Microsoft.Azure.WebJobs.Host.Converters; 10 | using Microsoft.Azure.WebJobs.Host.Protocols; 11 | 12 | namespace Microsoft.Azure.WebJobs.ServiceBus.Bindings 13 | { 14 | internal class ServiceBusBinding : IBinding 15 | { 16 | private readonly string _parameterName; 17 | private readonly IArgumentBinding _argumentBinding; 18 | private readonly ServiceBusAccount _account; 19 | private readonly IBindableServiceBusPath _path; 20 | private readonly IAsyncObjectToTypeConverter _converter; 21 | private readonly EntityType _entityType; 22 | private readonly MessagingProvider _messagingProvider; 23 | 24 | public ServiceBusBinding(string parameterName, IArgumentBinding argumentBinding, ServiceBusAccount account, ServiceBusOptions config, IBindableServiceBusPath path, ServiceBusAttribute attr, MessagingProvider messagingProvider) 25 | { 26 | _parameterName = parameterName; 27 | _argumentBinding = argumentBinding; 28 | _account = account; 29 | _path = path; 30 | _entityType = attr.EntityType; 31 | _messagingProvider = messagingProvider; 32 | _converter = new OutputConverter(new StringToServiceBusEntityConverter(account, _path, _entityType, _messagingProvider)); 33 | } 34 | 35 | public bool FromAttribute 36 | { 37 | get { return true; } 38 | } 39 | 40 | public async Task BindAsync(BindingContext context) 41 | { 42 | context.CancellationToken.ThrowIfCancellationRequested(); 43 | 44 | string boundQueueName = _path.Bind(context.BindingData); 45 | var messageSender = _messagingProvider.CreateMessageSender(boundQueueName, _account.ConnectionString); 46 | 47 | var entity = new ServiceBusEntity 48 | { 49 | MessageSender = messageSender, 50 | EntityType = _entityType 51 | }; 52 | 53 | return await BindAsync(entity, context.ValueContext); 54 | } 55 | 56 | public async Task BindAsync(object value, ValueBindingContext context) 57 | { 58 | ConversionResult conversionResult = await _converter.TryConvertAsync(value, context.CancellationToken); 59 | 60 | if (!conversionResult.Succeeded) 61 | { 62 | throw new InvalidOperationException("Unable to convert value to ServiceBusEntity."); 63 | } 64 | 65 | return await BindAsync(conversionResult.Result, context); 66 | } 67 | 68 | public ParameterDescriptor ToParameterDescriptor() 69 | { 70 | return new ServiceBusParameterDescriptor 71 | { 72 | Name = _parameterName, 73 | QueueOrTopicName = _path.QueueOrTopicNamePattern, 74 | DisplayHints = CreateParameterDisplayHints(_path.QueueOrTopicNamePattern, false) 75 | }; 76 | } 77 | 78 | private Task BindAsync(ServiceBusEntity value, ValueBindingContext context) 79 | { 80 | return _argumentBinding.BindAsync(value, context); 81 | } 82 | 83 | internal static ParameterDisplayHints CreateParameterDisplayHints(string entityPath, bool isInput) 84 | { 85 | ParameterDisplayHints descriptor = new ParameterDisplayHints 86 | { 87 | Description = isInput ? 88 | string.Format(CultureInfo.CurrentCulture, "dequeue from '{0}'", entityPath) : 89 | string.Format(CultureInfo.CurrentCulture, "enqueue to '{0}'", entityPath), 90 | 91 | Prompt = isInput ? 92 | "Enter the queue message body" : 93 | "Enter the output entity name", 94 | 95 | DefaultValue = isInput ? null : entityPath 96 | }; 97 | 98 | return descriptor; 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/Microsoft.Azure.WebJobs.Extensions.ServiceBus/ServiceBusTriggerAttribute.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Diagnostics; 6 | using Microsoft.Azure.WebJobs.Description; 7 | using Microsoft.Azure.ServiceBus; 8 | 9 | namespace Microsoft.Azure.WebJobs 10 | { 11 | /// 12 | /// Attribute used to bind a parameter to a ServiceBus Queue message, causing the function to run when a 13 | /// message is enqueued. 14 | /// 15 | /// 16 | /// The method parameter type can be one of the following: 17 | /// 18 | /// BrokeredMessage 19 | /// 20 | /// 21 | /// A user-defined type (serialized as JSON) 22 | /// 23 | /// 24 | [AttributeUsage(AttributeTargets.Parameter)] 25 | [DebuggerDisplay("{DebuggerDisplay,nq}")] 26 | [ConnectionProvider(typeof(ServiceBusAccountAttribute))] 27 | [Binding] 28 | public sealed class ServiceBusTriggerAttribute : Attribute, IConnectionProvider 29 | { 30 | private readonly string _queueName; 31 | private readonly string _topicName; 32 | private readonly string _subscriptionName; 33 | private bool? _autoComplete = null; 34 | 35 | /// 36 | /// Initializes a new instance of the class. 37 | /// 38 | /// The name of the queue to which to bind. 39 | public ServiceBusTriggerAttribute(string queueName) 40 | { 41 | _queueName = queueName; 42 | } 43 | 44 | /// 45 | /// Initializes a new instance of the class. 46 | /// 47 | /// The name of the topic to bind to. 48 | /// The name of the subscription in to bind to. 49 | public ServiceBusTriggerAttribute(string topicName, string subscriptionName) 50 | { 51 | _topicName = topicName; 52 | _subscriptionName = subscriptionName; 53 | } 54 | 55 | /// 56 | /// Gets or sets the app setting name that contains the Service Bus connection string. 57 | /// 58 | public string Connection { get; set; } 59 | 60 | /// 61 | /// Gets the name of the queue to which to bind. 62 | /// 63 | /// When binding to a subscription in a topic, returns . 64 | public string QueueName 65 | { 66 | get { return _queueName; } 67 | } 68 | 69 | /// 70 | /// Gets the name of the topic to which to bind. 71 | /// 72 | /// When binding to a queue, returns . 73 | public string TopicName 74 | { 75 | get { return _topicName; } 76 | } 77 | 78 | /// 79 | /// Gets the name of the subscription in to bind to. 80 | /// 81 | /// When binding to a queue, returns . 82 | public string SubscriptionName 83 | { 84 | get { return _subscriptionName; } 85 | } 86 | 87 | /// 88 | /// Gets or sets a value indicating whether the sessions are enabled. 89 | /// 90 | public bool IsSessionsEnabled { get; set; } 91 | 92 | /// 93 | /// Gets or sets the flag whether trigger should automatically call complete after processing, or if the function code will manually call complete. 94 | /// 95 | public bool AutoComplete 96 | { 97 | get 98 | { 99 | return _autoComplete.HasValue ? _autoComplete.Value : true; 100 | } 101 | set 102 | { 103 | _autoComplete = value; 104 | } 105 | } 106 | 107 | /// 108 | /// Gets a boolean to check if auto complete option was set on the trigger. 109 | /// 110 | internal bool IsAutoCompleteOptionSet { get { return _autoComplete.HasValue; } } 111 | 112 | private string DebuggerDisplay 113 | { 114 | get 115 | { 116 | if (_queueName != null) 117 | { 118 | return _queueName; 119 | } 120 | else 121 | { 122 | return _topicName + "/" + _subscriptionName; 123 | } 124 | } 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/Microsoft.Azure.WebJobs.Extensions.ServiceBus/Bindings/ServiceBusAttributeBindingProvider.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Globalization; 7 | using System.Reflection; 8 | using System.Threading.Tasks; 9 | using Microsoft.Azure.WebJobs.Host; 10 | using Microsoft.Azure.WebJobs.Host.Bindings; 11 | using Microsoft.Extensions.Configuration; 12 | 13 | namespace Microsoft.Azure.WebJobs.ServiceBus.Bindings 14 | { 15 | internal class ServiceBusAttributeBindingProvider : IBindingProvider 16 | { 17 | private static readonly IQueueArgumentBindingProvider InnerProvider = 18 | new CompositeArgumentBindingProvider( 19 | new MessageSenderArgumentBindingProvider(), 20 | new MessageArgumentBindingProvider(), 21 | new StringArgumentBindingProvider(), 22 | new ByteArrayArgumentBindingProvider(), 23 | new UserTypeArgumentBindingProvider(), 24 | new CollectorArgumentBindingProvider(), 25 | new AsyncCollectorArgumentBindingProvider()); 26 | 27 | private readonly INameResolver _nameResolver; 28 | private readonly ServiceBusOptions _options; 29 | private readonly IConfiguration _configuration; 30 | private readonly MessagingProvider _messagingProvider; 31 | 32 | public ServiceBusAttributeBindingProvider(INameResolver nameResolver, ServiceBusOptions options, IConfiguration configuration, MessagingProvider messagingProvider) 33 | { 34 | if (nameResolver == null) 35 | { 36 | throw new ArgumentNullException(nameof(nameResolver)); 37 | } 38 | if (configuration == null) 39 | { 40 | throw new ArgumentNullException(nameof(configuration)); 41 | } 42 | if (options == null) 43 | { 44 | throw new ArgumentNullException(nameof(options)); 45 | } 46 | if (messagingProvider == null) 47 | { 48 | throw new ArgumentNullException(nameof(messagingProvider)); 49 | } 50 | 51 | _nameResolver = nameResolver; 52 | _options = options; 53 | _configuration = configuration; 54 | _messagingProvider = messagingProvider; 55 | } 56 | 57 | public Task TryCreateAsync(BindingProviderContext context) 58 | { 59 | if (context == null) 60 | { 61 | throw new ArgumentNullException("context"); 62 | } 63 | 64 | ParameterInfo parameter = context.Parameter; 65 | var attribute = TypeUtility.GetResolvedAttribute(parameter); 66 | 67 | if (attribute == null) 68 | { 69 | return Task.FromResult(null); 70 | } 71 | 72 | string queueOrTopicName = Resolve(attribute.QueueOrTopicName); 73 | IBindableServiceBusPath path = BindableServiceBusPath.Create(queueOrTopicName); 74 | ValidateContractCompatibility(path, context.BindingDataContract); 75 | 76 | IArgumentBinding argumentBinding = InnerProvider.TryCreate(parameter); 77 | if (argumentBinding == null) 78 | { 79 | throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, "Can't bind ServiceBus to type '{0}'.", parameter.ParameterType)); 80 | } 81 | 82 | attribute.Connection = Resolve(attribute.Connection); 83 | ServiceBusAccount account = new ServiceBusAccount(_options, _configuration, attribute); 84 | 85 | IBinding binding = new ServiceBusBinding(parameter.Name, argumentBinding, account, _options, path, attribute, _messagingProvider); 86 | return Task.FromResult(binding); 87 | } 88 | 89 | private static void ValidateContractCompatibility(IBindableServiceBusPath path, IReadOnlyDictionary bindingDataContract) 90 | { 91 | if (path == null) 92 | { 93 | throw new ArgumentNullException("path"); 94 | } 95 | 96 | IEnumerable parameterNames = path.ParameterNames; 97 | if (parameterNames != null) 98 | { 99 | foreach (string parameterName in parameterNames) 100 | { 101 | if (bindingDataContract != null && !bindingDataContract.ContainsKey(parameterName)) 102 | { 103 | throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, "No binding parameter exists for '{0}'.", parameterName)); 104 | } 105 | } 106 | } 107 | } 108 | 109 | private string Resolve(string queueName) 110 | { 111 | if (_nameResolver == null) 112 | { 113 | return queueName; 114 | } 115 | 116 | return _nameResolver.ResolveWholeString(queueName); 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/Microsoft.Azure.WebJobs.Extensions.ServiceBus/Triggers/ServiceBusTriggerInput.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | 5 | using System; 6 | using System.Collections.Generic; 7 | using Microsoft.Azure.ServiceBus; 8 | using Microsoft.Azure.ServiceBus.Core; 9 | using Microsoft.Azure.WebJobs.Host.Executors; 10 | using Microsoft.Azure.WebJobs.ServiceBus.Listeners; 11 | 12 | namespace Microsoft.Azure.WebJobs.ServiceBus 13 | { 14 | // The core object we get when an ServiceBus is triggered. 15 | // This gets converted to the user type (Message, string, poco, etc) 16 | internal sealed class ServiceBusTriggerInput 17 | { 18 | private bool _isSingleDispatch; 19 | 20 | private ServiceBusTriggerInput() { } 21 | 22 | public IMessageReceiver MessageReceiver; 23 | 24 | public Message[] Messages { get; set; } 25 | 26 | public static ServiceBusTriggerInput CreateSingle(Message message) 27 | { 28 | return new ServiceBusTriggerInput 29 | { 30 | Messages = new Message[] 31 | { 32 | message 33 | }, 34 | _isSingleDispatch = true 35 | }; 36 | } 37 | 38 | public static ServiceBusTriggerInput CreateBatch(Message[] messages) 39 | { 40 | return new ServiceBusTriggerInput 41 | { 42 | Messages = messages, 43 | _isSingleDispatch = false 44 | }; 45 | } 46 | 47 | public bool IsSingleDispatch 48 | { 49 | get 50 | { 51 | return _isSingleDispatch; 52 | } 53 | } 54 | 55 | public TriggeredFunctionData GetTriggerFunctionData() 56 | { 57 | if (Messages.Length > 0) 58 | { 59 | Message message = Messages[0]; 60 | if (IsSingleDispatch) 61 | { 62 | Guid? parentId = ServiceBusCausalityHelper.GetOwner(message); 63 | return new TriggeredFunctionData() 64 | { 65 | ParentId = parentId, 66 | TriggerValue = this, 67 | TriggerDetails = new Dictionary() 68 | { 69 | { "MessageId", message.MessageId }, 70 | { "SequenceNumber", message.SystemProperties.SequenceNumber.ToString() }, 71 | { "DeliveryCount", message.SystemProperties.DeliveryCount.ToString() }, 72 | { "EnqueuedTimeUtc", message.SystemProperties.EnqueuedTimeUtc.ToUniversalTime().ToString("o") }, 73 | { "LockedUntilUtc", message.SystemProperties.LockedUntilUtc.ToUniversalTime().ToString("o") }, 74 | { "SessionId", message.SessionId } 75 | } 76 | }; 77 | } 78 | else 79 | { 80 | Guid? parentId = ServiceBusCausalityHelper.GetOwner(message); 81 | 82 | int length = Messages.Length; 83 | string[] messageIds = new string[length]; 84 | int[] deliveryCounts = new int[length]; 85 | long[] sequenceNumbers = new long[length]; 86 | string[] enqueuedTimes = new string[length]; 87 | string[] lockedUntils = new string[length]; 88 | string sessionId = Messages[0].SessionId; 89 | 90 | for (int i = 0; i < Messages.Length; i++) 91 | { 92 | messageIds[i] = Messages[i].MessageId; 93 | sequenceNumbers[i] = Messages[i].SystemProperties.SequenceNumber; 94 | deliveryCounts[i] = Messages[i].SystemProperties.DeliveryCount; 95 | enqueuedTimes[i] = Messages[i].SystemProperties.EnqueuedTimeUtc.ToUniversalTime().ToString("o"); 96 | lockedUntils[i] = Messages[i].SystemProperties.LockedUntilUtc.ToUniversalTime().ToString("o"); 97 | } 98 | 99 | return new TriggeredFunctionData() 100 | { 101 | ParentId = parentId, 102 | TriggerValue = this, 103 | TriggerDetails = new Dictionary() 104 | { 105 | { "MessageIdArray", string.Join(",", messageIds)}, 106 | { "SequenceNumberArray", string.Join(",", sequenceNumbers)}, 107 | { "DeliveryCountArray", string.Join(",", deliveryCounts) }, 108 | { "EnqueuedTimeUtcArray", string.Join(",", enqueuedTimes) }, 109 | { "LockedUntilArray", string.Join(",", lockedUntils) }, 110 | { "SessionId", sessionId } 111 | } 112 | }; 113 | } 114 | } 115 | return null; 116 | } 117 | } 118 | } -------------------------------------------------------------------------------- /src.ruleset: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | -------------------------------------------------------------------------------- /src/Microsoft.Azure.WebJobs.Extensions.ServiceBus/Config/ServiceBusExtensionConfigProvider.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using Microsoft.Azure.ServiceBus; 6 | using Microsoft.Azure.WebJobs.Description; 7 | using Microsoft.Azure.WebJobs.Host.Bindings; 8 | using Microsoft.Azure.WebJobs.Host.Config; 9 | using Microsoft.Azure.WebJobs.Logging; 10 | using Microsoft.Azure.WebJobs.ServiceBus.Bindings; 11 | using Microsoft.Azure.WebJobs.ServiceBus.Triggers; 12 | using Microsoft.Extensions.Configuration; 13 | using Microsoft.Extensions.Logging; 14 | using Microsoft.Extensions.Logging.Abstractions; 15 | using Microsoft.Extensions.Options; 16 | 17 | 18 | namespace Microsoft.Azure.WebJobs.ServiceBus.Config 19 | { 20 | /// 21 | /// Extension configuration provider used to register ServiceBus triggers and binders 22 | /// 23 | [Extension("ServiceBus")] 24 | internal class ServiceBusExtensionConfigProvider : IExtensionConfigProvider 25 | { 26 | private readonly INameResolver _nameResolver; 27 | private readonly IConfiguration _configuration; 28 | private readonly ILoggerFactory _loggerFactory; 29 | private readonly ServiceBusOptions _options; 30 | private readonly MessagingProvider _messagingProvider; 31 | private readonly IConverterManager _converterManager; 32 | 33 | /// 34 | /// Creates a new instance. 35 | /// 36 | /// The to use./> 37 | public ServiceBusExtensionConfigProvider(IOptions options, 38 | MessagingProvider messagingProvider, 39 | INameResolver nameResolver, 40 | IConfiguration configuration, 41 | ILoggerFactory loggerFactory, 42 | IConverterManager converterManager) 43 | { 44 | _options = options.Value; 45 | _messagingProvider = messagingProvider; 46 | _nameResolver = nameResolver; 47 | _configuration = configuration; 48 | _loggerFactory = loggerFactory ?? NullLoggerFactory.Instance; 49 | _converterManager = converterManager; 50 | } 51 | 52 | /// 53 | /// Gets the 54 | /// 55 | public ServiceBusOptions Options 56 | { 57 | get 58 | { 59 | return _options; 60 | } 61 | } 62 | 63 | /// 64 | public void Initialize(ExtensionConfigContext context) 65 | { 66 | if (context == null) 67 | { 68 | throw new ArgumentNullException("context"); 69 | } 70 | 71 | // Set the default exception handler for background exceptions 72 | // coming from MessageReceivers. 73 | Options.ExceptionHandler = (e) => 74 | { 75 | LogExceptionReceivedEvent(e, _loggerFactory); 76 | }; 77 | 78 | context 79 | .AddConverter(new MessageToStringConverter()) 80 | .AddConverter(new MessageToByteArrayConverter()) 81 | .AddOpenConverter(typeof(MessageToPocoConverter<>)); 82 | 83 | // register our trigger binding provider 84 | ServiceBusTriggerAttributeBindingProvider triggerBindingProvider = new ServiceBusTriggerAttributeBindingProvider(_nameResolver, _options, _messagingProvider, _configuration, _loggerFactory, _converterManager); 85 | context.AddBindingRule() 86 | .BindToTrigger(triggerBindingProvider); 87 | 88 | // register our binding provider 89 | ServiceBusAttributeBindingProvider bindingProvider = new ServiceBusAttributeBindingProvider(_nameResolver, _options, _configuration, _messagingProvider); 90 | context.AddBindingRule().Bind(bindingProvider); 91 | } 92 | 93 | internal static void LogExceptionReceivedEvent(ExceptionReceivedEventArgs e, ILoggerFactory loggerFactory) 94 | { 95 | try 96 | { 97 | var ctxt = e.ExceptionReceivedContext; 98 | var logger = loggerFactory?.CreateLogger(LogCategories.Executor); 99 | string message = $"Message processing error (Action={ctxt.Action}, ClientId={ctxt.ClientId}, EntityPath={ctxt.EntityPath}, Endpoint={ctxt.Endpoint})"; 100 | 101 | var logLevel = GetLogLevel(e.Exception); 102 | logger?.Log(logLevel, 0, message, e.Exception, (s, ex) => message); 103 | } 104 | catch 105 | { 106 | // best effort logging 107 | } 108 | } 109 | 110 | private static LogLevel GetLogLevel(Exception ex) 111 | { 112 | var sbex = ex as ServiceBusException; 113 | if (!(ex is OperationCanceledException) && (sbex == null || !sbex.IsTransient)) 114 | { 115 | // any non-transient exceptions or unknown exception types 116 | // we want to log as errors 117 | return LogLevel.Error; 118 | } 119 | else 120 | { 121 | // transient messaging errors we log as info so we have a record 122 | // of them, but we don't treat them as actual errors 123 | return LogLevel.Information; 124 | } 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/Microsoft.Azure.WebJobs.Extensions.ServiceBus/Triggers/ServiceBusTriggerAttributeBindingProvider.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Globalization; 6 | using System.Reflection; 7 | using System.Threading.Tasks; 8 | using Microsoft.Azure.ServiceBus; 9 | using Microsoft.Azure.WebJobs.Host; 10 | using Microsoft.Azure.WebJobs.Host.Bindings; 11 | using Microsoft.Azure.WebJobs.Host.Listeners; 12 | using Microsoft.Azure.WebJobs.Host.Protocols; 13 | using Microsoft.Azure.WebJobs.Host.Triggers; 14 | using Microsoft.Extensions.Configuration; 15 | using Microsoft.Extensions.Logging; 16 | using Microsoft.Azure.WebJobs.ServiceBus.Listeners; 17 | using Microsoft.Azure.WebJobs.Host.Config; 18 | 19 | namespace Microsoft.Azure.WebJobs.ServiceBus.Triggers 20 | { 21 | internal class ServiceBusTriggerAttributeBindingProvider : ITriggerBindingProvider 22 | { 23 | 24 | private readonly INameResolver _nameResolver; 25 | private readonly ServiceBusOptions _options; 26 | private readonly MessagingProvider _messagingProvider; 27 | private readonly IConfiguration _configuration; 28 | private readonly ILoggerFactory _loggerFactory; 29 | private readonly IConverterManager _converterManager; 30 | private readonly ILogger _logger; 31 | 32 | public ServiceBusTriggerAttributeBindingProvider(INameResolver nameResolver, ServiceBusOptions options, MessagingProvider messagingProvider, IConfiguration configuration, 33 | ILoggerFactory loggerFactory, IConverterManager converterManager) 34 | { 35 | _nameResolver = nameResolver ?? throw new ArgumentNullException(nameof(nameResolver)); 36 | _options = options ?? throw new ArgumentNullException(nameof(options)); 37 | _messagingProvider = messagingProvider ?? throw new ArgumentNullException(nameof(messagingProvider)); 38 | _configuration = configuration; 39 | _loggerFactory = loggerFactory; 40 | _converterManager = converterManager; 41 | _logger = _loggerFactory.CreateLogger(); 42 | } 43 | 44 | public Task TryCreateAsync(TriggerBindingProviderContext context) 45 | { 46 | if (context == null) 47 | { 48 | throw new ArgumentNullException("context"); 49 | } 50 | 51 | ParameterInfo parameter = context.Parameter; 52 | var attribute = TypeUtility.GetResolvedAttribute(parameter); 53 | 54 | if (attribute == null) 55 | { 56 | return Task.FromResult(null); 57 | } 58 | 59 | string queueName = null; 60 | string topicName = null; 61 | string subscriptionName = null; 62 | string entityPath = null; 63 | EntityType entityType; 64 | 65 | if (attribute.QueueName != null) 66 | { 67 | queueName = Resolve(attribute.QueueName); 68 | entityPath = queueName; 69 | entityType = EntityType.Queue; 70 | } 71 | else 72 | { 73 | topicName = Resolve(attribute.TopicName); 74 | subscriptionName = Resolve(attribute.SubscriptionName); 75 | entityPath = EntityNameHelper.FormatSubscriptionPath(topicName, subscriptionName); 76 | entityType = EntityType.Topic; 77 | } 78 | 79 | attribute.Connection = Resolve(attribute.Connection); 80 | ServiceBusAccount account = new ServiceBusAccount(_options, _configuration, attribute); 81 | 82 | Func> createListener = 83 | (factoryContext, singleDispatch) => 84 | { 85 | var options = GetServiceBusOptions(attribute, factoryContext.Descriptor.ShortName); 86 | 87 | IListener listener = new ServiceBusListener(factoryContext.Descriptor.Id, entityType, entityPath, attribute.IsSessionsEnabled, factoryContext.Executor, options, account, _messagingProvider, _loggerFactory, singleDispatch); 88 | return Task.FromResult(listener); 89 | }; 90 | 91 | ITriggerBinding binding = BindingFactory.GetTriggerBinding(new ServiceBusTriggerBindingStrategy(), parameter, _converterManager, createListener); 92 | 93 | return Task.FromResult(binding); 94 | } 95 | 96 | private string Resolve(string queueName) 97 | { 98 | if (_nameResolver == null) 99 | { 100 | return queueName; 101 | } 102 | 103 | return _nameResolver.ResolveWholeString(queueName); 104 | } 105 | 106 | /// 107 | /// Gets service bus options after applying function level options if needed. 108 | /// 109 | /// The trigger attribute. 110 | /// The function name. 111 | private ServiceBusOptions GetServiceBusOptions(ServiceBusTriggerAttribute attribute, string functionName) 112 | { 113 | if (attribute.IsAutoCompleteOptionSet) 114 | { 115 | var options = ServiceBusOptions.DeepClone(_options); 116 | 117 | _logger.LogInformation($"The 'AutoComplete' option has been overrriden to '{attribute.AutoComplete}' value for '{functionName}' function."); 118 | 119 | options.BatchOptions.AutoComplete = options.MessageHandlerOptions.AutoComplete = options.SessionHandlerOptions.AutoComplete = attribute.AutoComplete; 120 | 121 | return options; 122 | } 123 | 124 | return _options; 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /test/Microsoft.Azure.WebJobs.Extensions.ServiceBus.Tests/Config/ServiceBusHostBuilderExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using Microsoft.Azure.ServiceBus; 8 | using Microsoft.Azure.WebJobs.Host; 9 | using Microsoft.Azure.WebJobs.Host.Config; 10 | using Microsoft.Azure.WebJobs.Host.TestCommon; 11 | using Microsoft.Azure.WebJobs.ServiceBus.Config; 12 | using Microsoft.Extensions.Configuration; 13 | using Microsoft.Extensions.Configuration.EnvironmentVariables; 14 | using Microsoft.Extensions.DependencyInjection; 15 | using Microsoft.Extensions.Hosting; 16 | using Microsoft.Extensions.Options; 17 | using Xunit; 18 | 19 | namespace Microsoft.Azure.WebJobs.ServiceBus.UnitTests.Config 20 | { 21 | public class ServiceBusHostBuilderExtensionsTests 22 | { 23 | [Fact] 24 | public void ConfigureOptions_AppliesValuesCorrectly() 25 | { 26 | string extensionPath = "AzureWebJobs:Extensions:ServiceBus"; 27 | var values = new Dictionary 28 | { 29 | { $"{extensionPath}:PrefetchCount", "123" }, 30 | { $"ConnectionStrings:ServiceBus", "TestConnectionString" }, 31 | { $"{extensionPath}:MessageHandlerOptions:MaxConcurrentCalls", "123" }, 32 | { $"{extensionPath}:MessageHandlerOptions:AutoComplete", "false" }, 33 | { $"{extensionPath}:MessageHandlerOptions:MaxAutoRenewDuration", "00:00:15" } 34 | }; 35 | 36 | ServiceBusOptions options = TestHelpers.GetConfiguredOptions(b => 37 | { 38 | b.AddServiceBus(); 39 | }, values); 40 | 41 | Assert.Equal(123, options.PrefetchCount); 42 | Assert.Equal("TestConnectionString", options.ConnectionString); 43 | Assert.Equal(123, options.MessageHandlerOptions.MaxConcurrentCalls); 44 | Assert.Equal(false, options.MessageHandlerOptions.AutoComplete); 45 | Assert.Equal(TimeSpan.FromSeconds(15), options.MessageHandlerOptions.MaxAutoRenewDuration); 46 | } 47 | 48 | [Fact] 49 | public void AddServiceBus_ThrowsArgumentNull_WhenServiceBusOptionsIsNull() 50 | { 51 | IHost host = new HostBuilder() 52 | .ConfigureDefaultTestHost(b => 53 | { 54 | b.AddServiceBus(); 55 | }) 56 | .ConfigureServices(s => s.AddSingleton>(p => null)) 57 | .Build(); 58 | 59 | var exception = Assert.Throws(() => host.Services.GetServices()); 60 | 61 | Assert.Equal("serviceBusOptions", exception.ParamName); 62 | } 63 | 64 | [Fact] 65 | public void AddServiceBus_NoServiceBusOptions_PerformsExpectedRegistration() 66 | { 67 | IHost host = new HostBuilder() 68 | .ConfigureDefaultTestHost(b => 69 | { 70 | b.AddServiceBus(); 71 | }) 72 | .Build(); 73 | 74 | var extensions = host.Services.GetService(); 75 | IExtensionConfigProvider[] configProviders = extensions.GetExtensions().ToArray(); 76 | 77 | // verify that the service bus config provider was registered 78 | var serviceBusExtensionConfig = configProviders.OfType().Single(); 79 | } 80 | 81 | [Fact] 82 | public void AddServiceBus_ServiceBusOptionsProvided_PerformsExpectedRegistration() 83 | { 84 | string fakeConnStr = "test service bus connection"; 85 | 86 | IHost host = new HostBuilder() 87 | .ConfigureDefaultTestHost(b => 88 | { 89 | b.AddServiceBus(); 90 | }) 91 | .ConfigureServices(s => 92 | { 93 | s.Configure(o => 94 | { 95 | o.ConnectionString = fakeConnStr; 96 | }); 97 | }) 98 | .Build(); 99 | 100 | // verify that the service bus config provider was registered 101 | var extensions = host.Services.GetService(); 102 | IExtensionConfigProvider[] configProviders = extensions.GetExtensions().ToArray(); 103 | 104 | // verify that the service bus config provider was registered 105 | var serviceBusExtensionConfig = configProviders.OfType().Single(); 106 | 107 | Assert.Equal(fakeConnStr, serviceBusExtensionConfig.Options.ConnectionString); 108 | } 109 | 110 | 111 | [Theory] 112 | [InlineData("DefaultConnectionString", "DefaultConectionSettingString", "DefaultConnectionString")] 113 | [InlineData("DefaultConnectionString", null, "DefaultConnectionString")] 114 | [InlineData(null, "DefaultConectionSettingString", "DefaultConectionSettingString")] 115 | [InlineData(null, null, null)] 116 | public void ReadDeafultConnectionString(string defaultConnectionString, string sefaultConectionSettingString, string expectedValue) 117 | { 118 | ServiceBusOptions options = TestHelpers.GetConfiguredOptions(b => 119 | { 120 | var test = b.Services.Single(x => x.ServiceType == typeof(IConfiguration)); 121 | 122 | var envPrpvider = (test.ImplementationInstance as ConfigurationRoot).Providers 123 | .Single(x => x.GetType() == typeof(EnvironmentVariablesConfigurationProvider)); 124 | envPrpvider.Set("ConnectionStrings:" + Constants.DefaultConnectionStringName, defaultConnectionString); 125 | envPrpvider.Set(Constants.DefaultConnectionSettingStringName, sefaultConectionSettingString); 126 | 127 | b.AddServiceBus(); 128 | }, new Dictionary()); 129 | 130 | Assert.Equal(options.ConnectionString, expectedValue); 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /test/Microsoft.Azure.WebJobs.Extensions.ServiceBus.Tests/Bindings/ServiceBusTriggerAttributeBindingProviderTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Reflection; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | using Microsoft.Azure.ServiceBus; 9 | using Microsoft.Azure.WebJobs.Host.Triggers; 10 | using Microsoft.Azure.WebJobs.ServiceBus.Triggers; 11 | using Microsoft.Extensions.Configuration; 12 | using Microsoft.Extensions.Options; 13 | using Microsoft.Extensions.Logging.Abstractions; 14 | using Moq; 15 | using Xunit; 16 | using Microsoft.Azure.WebJobs.Host.Listeners; 17 | using Microsoft.Azure.WebJobs.Host.Protocols; 18 | using Microsoft.Azure.WebJobs.Host.Executors; 19 | using Microsoft.Azure.ServiceBus.Core; 20 | using Microsoft.Azure.WebJobs.Host.TestCommon; 21 | 22 | namespace Microsoft.Azure.WebJobs.ServiceBus.UnitTests.Bindings 23 | { 24 | public class ServiceBusTriggerAttributeBindingProviderTests 25 | { 26 | private readonly Mock _mockMessagingProvider; 27 | private readonly ServiceBusTriggerAttributeBindingProvider _provider; 28 | private readonly IConfiguration _configuration; 29 | private readonly string _connectionString; 30 | private readonly ServiceBusOptions _options; 31 | 32 | public ServiceBusTriggerAttributeBindingProviderTests() 33 | { 34 | _configuration = new ConfigurationBuilder() 35 | .AddEnvironmentVariables() 36 | .AddTestSettings() 37 | .Build(); 38 | 39 | // Add all test configuration to the environment as WebJobs requires a few of them to be in the environment 40 | foreach (var kv in _configuration.AsEnumerable()) 41 | { 42 | Environment.SetEnvironmentVariable(kv.Key, kv.Value); 43 | } 44 | 45 | Mock mockResolver = new Mock(MockBehavior.Strict); 46 | _connectionString = _configuration.GetConnectionStringOrSetting(ServiceBus.Constants.DefaultConnectionStringName); 47 | 48 | _options = new ServiceBusOptions() 49 | { 50 | ConnectionString = _connectionString 51 | }; 52 | _mockMessagingProvider = new Mock(MockBehavior.Strict, new OptionsWrapper(_options)); 53 | 54 | Mock convertManager = new Mock(MockBehavior.Default); 55 | 56 | _provider = new ServiceBusTriggerAttributeBindingProvider(mockResolver.Object, _options, _mockMessagingProvider.Object, _configuration, NullLoggerFactory.Instance, convertManager.Object); 57 | } 58 | 59 | [Fact] 60 | public async Task TryCreateAsync_AccountOverride_OverrideIsApplied() 61 | { 62 | ParameterInfo parameter = GetType().GetMethod("TestJob_AccountOverride").GetParameters()[0]; 63 | TriggerBindingProviderContext context = new TriggerBindingProviderContext(parameter, CancellationToken.None); 64 | 65 | ITriggerBinding binding = await _provider.TryCreateAsync(context); 66 | 67 | Assert.NotNull(binding); 68 | } 69 | 70 | [Fact] 71 | public async Task TryCreateAsync_DefaultAccount() 72 | { 73 | ParameterInfo parameter = GetType().GetMethod("TestJob").GetParameters()[0]; 74 | TriggerBindingProviderContext context = new TriggerBindingProviderContext(parameter, CancellationToken.None); 75 | 76 | ITriggerBinding binding = await _provider.TryCreateAsync(context); 77 | 78 | Assert.NotNull(binding); 79 | } 80 | 81 | [Fact] 82 | public async Task GetServiceBusOptions_AutoCompleteDisabledOnTrigger() 83 | { 84 | var listenerContext = new ListenerFactoryContext( 85 | new Mock().Object, 86 | new Mock().Object, 87 | CancellationToken.None); 88 | var parameters = new object[] { listenerContext }; 89 | var entityPath = "autocomplete"; 90 | 91 | var _mockMessageProcessor = new Mock(MockBehavior.Strict, new MessageReceiver(_connectionString, entityPath), _options.MessageHandlerOptions); 92 | _mockMessagingProvider 93 | .Setup(p => p.CreateMessageProcessor(entityPath, _connectionString)) 94 | .Returns(_mockMessageProcessor.Object); 95 | 96 | var parameter = GetType().GetMethod("TestAutoCompleteDisbledOnTrigger").GetParameters()[0]; 97 | var context = new TriggerBindingProviderContext(parameter, CancellationToken.None); 98 | var binding = await _provider.TryCreateAsync(context); 99 | var createListenerTask = binding.GetType().GetMethod("CreateListenerAsync"); 100 | var listener = await (Task)createListenerTask.Invoke(binding, parameters); 101 | var listenerOptions = (ServiceBusOptions)listener.GetType().GetField("_serviceBusOptions", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(listener); 102 | 103 | Assert.NotNull(listenerOptions); 104 | Assert.True(_options.MessageHandlerOptions.AutoComplete); 105 | Assert.True(_options.SessionHandlerOptions.AutoComplete); 106 | Assert.True(_options.BatchOptions.AutoComplete); 107 | Assert.False(listenerOptions.MessageHandlerOptions.AutoComplete); 108 | Assert.False(listenerOptions.SessionHandlerOptions.AutoComplete); 109 | Assert.False(listenerOptions.BatchOptions.AutoComplete); 110 | } 111 | 112 | public static void TestJob_AccountOverride( 113 | [ServiceBusTriggerAttribute("test"), 114 | ServiceBusAccount(Constants.DefaultConnectionStringName)] Message message) 115 | { 116 | message = new Message(); 117 | } 118 | 119 | public static void TestJob( 120 | [ServiceBusTriggerAttribute("test", Connection = Constants.DefaultConnectionStringName)] Message message) 121 | { 122 | message = new Message(); 123 | } 124 | 125 | public static void TestAutoCompleteDisbledOnTrigger( 126 | [ServiceBusTriggerAttribute("autocomplete", AutoComplete = false), 127 | ServiceBusAccount(Constants.DefaultConnectionStringName)] Message message) 128 | { 129 | message = new Message(); 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /test/Microsoft.Azure.WebJobs.Extensions.ServiceBus.Tests/Config/ServiceBusOptionsTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Linq; 6 | using Microsoft.Azure.ServiceBus; 7 | using Microsoft.Azure.WebJobs.Host.TestCommon; 8 | using Microsoft.Azure.WebJobs.ServiceBus.Config; 9 | using Microsoft.Extensions.Logging; 10 | using Xunit; 11 | 12 | namespace Microsoft.Azure.WebJobs.ServiceBus.UnitTests.Config 13 | { 14 | public class ServiceBusOptionsTests 15 | { 16 | private readonly ILoggerFactory _loggerFactory; 17 | private readonly TestLoggerProvider _loggerProvider; 18 | 19 | public ServiceBusOptionsTests() 20 | { 21 | _loggerFactory = new LoggerFactory(); 22 | _loggerProvider = new TestLoggerProvider(); 23 | _loggerFactory.AddProvider(_loggerProvider); 24 | } 25 | 26 | [Fact] 27 | public void Constructor_SetsExpectedDefaults() 28 | { 29 | ServiceBusOptions config = new ServiceBusOptions(); 30 | Assert.Equal(16 * Utility.GetProcessorCount(), config.MessageHandlerOptions.MaxConcurrentCalls); 31 | Assert.Equal(0, config.PrefetchCount); 32 | } 33 | 34 | [Fact] 35 | public void PrefetchCount_GetSet() 36 | { 37 | ServiceBusOptions config = new ServiceBusOptions(); 38 | Assert.Equal(0, config.PrefetchCount); 39 | config.PrefetchCount = 100; 40 | Assert.Equal(100, config.PrefetchCount); 41 | } 42 | 43 | [Fact] 44 | public void LogExceptionReceivedEvent_NonTransientEvent_LoggedAsError() 45 | { 46 | var ex = new ServiceBusException(false); 47 | Assert.False(ex.IsTransient); 48 | ExceptionReceivedEventArgs e = new ExceptionReceivedEventArgs(ex, "TestAction", "TestEndpoint", "TestEntity", "TestClient"); 49 | ServiceBusExtensionConfigProvider.LogExceptionReceivedEvent(e, _loggerFactory); 50 | 51 | var expectedMessage = $"Message processing error (Action=TestAction, ClientId=TestClient, EntityPath=TestEntity, Endpoint=TestEndpoint)"; 52 | var logMessage = _loggerProvider.GetAllLogMessages().Single(); 53 | Assert.Equal(LogLevel.Error, logMessage.Level); 54 | Assert.Same(ex, logMessage.Exception); 55 | Assert.Equal(expectedMessage, logMessage.FormattedMessage); 56 | } 57 | 58 | [Fact] 59 | public void LogExceptionReceivedEvent_TransientEvent_LoggedAsInformation() 60 | { 61 | var ex = new ServiceBusException(true); 62 | Assert.True(ex.IsTransient); 63 | ExceptionReceivedEventArgs e = new ExceptionReceivedEventArgs(ex, "TestAction", "TestEndpoint", "TestEntity", "TestClient"); 64 | ServiceBusExtensionConfigProvider.LogExceptionReceivedEvent(e, _loggerFactory); 65 | 66 | var expectedMessage = $"Message processing error (Action=TestAction, ClientId=TestClient, EntityPath=TestEntity, Endpoint=TestEndpoint)"; 67 | var logMessage = _loggerProvider.GetAllLogMessages().Single(); 68 | Assert.Equal(LogLevel.Information, logMessage.Level); 69 | Assert.Same(ex, logMessage.Exception); 70 | Assert.Equal(expectedMessage, logMessage.FormattedMessage); 71 | } 72 | 73 | [Fact] 74 | public void LogExceptionReceivedEvent_NonMessagingException_LoggedAsError() 75 | { 76 | var ex = new MissingMethodException("What method??"); 77 | ExceptionReceivedEventArgs e = new ExceptionReceivedEventArgs(ex, "TestAction", "TestEndpoint", "TestEntity", "TestClient"); 78 | ServiceBusExtensionConfigProvider.LogExceptionReceivedEvent(e, _loggerFactory); 79 | 80 | var expectedMessage = $"Message processing error (Action=TestAction, ClientId=TestClient, EntityPath=TestEntity, Endpoint=TestEndpoint)"; 81 | var logMessage = _loggerProvider.GetAllLogMessages().Single(); 82 | Assert.Equal(LogLevel.Error, logMessage.Level); 83 | Assert.Same(ex, logMessage.Exception); 84 | Assert.Equal(expectedMessage, logMessage.FormattedMessage); 85 | } 86 | 87 | [Fact] 88 | public void DeepClone() 89 | { 90 | var options = new ServiceBusOptions(); 91 | var clonedOptions = ServiceBusOptions.DeepClone(options); 92 | Assert.NotEqual(options, clonedOptions); 93 | Assert.Equal(options.PrefetchCount, clonedOptions.PrefetchCount); 94 | Assert.Equal(options.ExceptionHandler, clonedOptions.ExceptionHandler); 95 | Assert.Equal(options.MessageHandlerOptions.AutoComplete, clonedOptions.MessageHandlerOptions.AutoComplete); 96 | Assert.Equal(options.MessageHandlerOptions.MaxAutoRenewDuration.Ticks, clonedOptions.MessageHandlerOptions.MaxAutoRenewDuration.Ticks); 97 | Assert.Equal(options.MessageHandlerOptions.MaxConcurrentCalls, clonedOptions.MessageHandlerOptions.MaxConcurrentCalls); 98 | Assert.Equal(options.BatchOptions.AutoComplete, clonedOptions.BatchOptions.AutoComplete); 99 | Assert.Equal(options.BatchOptions.MaxMessageCount, clonedOptions.BatchOptions.MaxMessageCount); 100 | Assert.Equal(options.BatchOptions.OperationTimeout.Ticks, clonedOptions.BatchOptions.OperationTimeout.Ticks); 101 | Assert.Equal(options.SessionHandlerOptions.AutoComplete, clonedOptions.SessionHandlerOptions.AutoComplete); 102 | Assert.Equal(options.SessionHandlerOptions.MaxAutoRenewDuration.Ticks, clonedOptions.SessionHandlerOptions.MaxAutoRenewDuration.Ticks); 103 | Assert.Equal(options.SessionHandlerOptions.MaxConcurrentSessions, clonedOptions.SessionHandlerOptions.MaxConcurrentSessions); 104 | Assert.Equal(options.SessionHandlerOptions.MessageWaitTimeout.Ticks, clonedOptions.SessionHandlerOptions.MessageWaitTimeout.Ticks); 105 | 106 | // Perform updates on cloned options. They should not copied over to the source 107 | clonedOptions.MessageHandlerOptions.AutoComplete = options.BatchOptions.AutoComplete = options.SessionHandlerOptions.AutoComplete = false; 108 | Assert.NotEqual(options.MessageHandlerOptions.AutoComplete, clonedOptions.MessageHandlerOptions.AutoComplete); 109 | Assert.NotEqual(options.BatchOptions.AutoComplete, clonedOptions.BatchOptions.AutoComplete); 110 | Assert.NotEqual(options.SessionHandlerOptions.AutoComplete, clonedOptions.SessionHandlerOptions.AutoComplete); 111 | 112 | clonedOptions.MessageHandlerOptions.MaxAutoRenewDuration = new TimeSpan(0, 10, 0); 113 | Assert.NotEqual(options.MessageHandlerOptions.MaxAutoRenewDuration.Ticks, clonedOptions.MessageHandlerOptions.MaxAutoRenewDuration.Ticks); 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015/2017 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # Visual Studio 2017 auto generated files 33 | Generated\ Files/ 34 | 35 | # MSTest test Results 36 | [Tt]est[Rr]esult*/ 37 | [Bb]uild[Ll]og.* 38 | 39 | # NUNIT 40 | *.VisualState.xml 41 | TestResult.xml 42 | 43 | # Build Results of an ATL Project 44 | [Dd]ebugPS/ 45 | [Rr]eleasePS/ 46 | dlldata.c 47 | 48 | # Benchmark Results 49 | BenchmarkDotNet.Artifacts/ 50 | 51 | # .NET Core 52 | project.lock.json 53 | project.fragment.lock.json 54 | artifacts/ 55 | **/Properties/launchSettings.json 56 | 57 | # StyleCop 58 | StyleCopReport.xml 59 | 60 | # Files built by Visual Studio 61 | *_i.c 62 | *_p.c 63 | *_i.h 64 | *.ilk 65 | *.meta 66 | *.obj 67 | *.iobj 68 | *.pch 69 | *.pdb 70 | *.ipdb 71 | *.pgc 72 | *.pgd 73 | *.rsp 74 | *.sbr 75 | *.tlb 76 | *.tli 77 | *.tlh 78 | *.tmp 79 | *.tmp_proj 80 | *.log 81 | *.vspscc 82 | *.vssscc 83 | .builds 84 | *.pidb 85 | *.svclog 86 | *.scc 87 | 88 | # Chutzpah Test files 89 | _Chutzpah* 90 | 91 | # Visual C++ cache files 92 | ipch/ 93 | *.aps 94 | *.ncb 95 | *.opendb 96 | *.opensdf 97 | *.sdf 98 | *.cachefile 99 | *.VC.db 100 | *.VC.VC.opendb 101 | 102 | # Visual Studio profiler 103 | *.psess 104 | *.vsp 105 | *.vspx 106 | *.sap 107 | 108 | # Visual Studio Trace Files 109 | *.e2e 110 | 111 | # TFS 2012 Local Workspace 112 | $tf/ 113 | 114 | # Guidance Automation Toolkit 115 | *.gpState 116 | 117 | # ReSharper is a .NET coding add-in 118 | _ReSharper*/ 119 | *.[Rr]e[Ss]harper 120 | *.DotSettings.user 121 | 122 | # JustCode is a .NET coding add-in 123 | .JustCode 124 | 125 | # TeamCity is a build add-in 126 | _TeamCity* 127 | 128 | # DotCover is a Code Coverage Tool 129 | *.dotCover 130 | 131 | # AxoCover is a Code Coverage Tool 132 | .axoCover/* 133 | !.axoCover/settings.json 134 | 135 | # Visual Studio code coverage results 136 | *.coverage 137 | *.coveragexml 138 | 139 | # NCrunch 140 | _NCrunch_* 141 | .*crunch*.local.xml 142 | nCrunchTemp_* 143 | 144 | # MightyMoose 145 | *.mm.* 146 | AutoTest.Net/ 147 | 148 | # Web workbench (sass) 149 | .sass-cache/ 150 | 151 | # Installshield output folder 152 | [Ee]xpress/ 153 | 154 | # DocProject is a documentation generator add-in 155 | DocProject/buildhelp/ 156 | DocProject/Help/*.HxT 157 | DocProject/Help/*.HxC 158 | DocProject/Help/*.hhc 159 | DocProject/Help/*.hhk 160 | DocProject/Help/*.hhp 161 | DocProject/Help/Html2 162 | DocProject/Help/html 163 | 164 | # Click-Once directory 165 | publish/ 166 | 167 | # Publish Web Output 168 | *.[Pp]ublish.xml 169 | *.azurePubxml 170 | # Note: Comment the next line if you want to checkin your web deploy settings, 171 | # but database connection strings (with potential passwords) will be unencrypted 172 | *.pubxml 173 | *.publishproj 174 | 175 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 176 | # checkin your Azure Web App publish settings, but sensitive information contained 177 | # in these scripts will be unencrypted 178 | PublishScripts/ 179 | 180 | # NuGet Packages 181 | *.nupkg 182 | # The packages folder can be ignored because of Package Restore 183 | **/[Pp]ackages/* 184 | # except build/, which is used as an MSBuild target. 185 | !**/[Pp]ackages/build/ 186 | # Uncomment if necessary however generally it will be regenerated when needed 187 | #!**/[Pp]ackages/repositories.config 188 | # NuGet v3's project.json files produces more ignorable files 189 | *.nuget.props 190 | *.nuget.targets 191 | 192 | # Microsoft Azure Build Output 193 | csx/ 194 | *.build.csdef 195 | 196 | # Microsoft Azure Emulator 197 | ecf/ 198 | rcf/ 199 | 200 | # Windows Store app package directories and files 201 | AppPackages/ 202 | BundleArtifacts/ 203 | Package.StoreAssociation.xml 204 | _pkginfo.txt 205 | *.appx 206 | 207 | # Visual Studio cache files 208 | # files ending in .cache can be ignored 209 | *.[Cc]ache 210 | # but keep track of directories ending in .cache 211 | !*.[Cc]ache/ 212 | 213 | # Others 214 | ClientBin/ 215 | ~$* 216 | *~ 217 | *.dbmdl 218 | *.dbproj.schemaview 219 | *.jfm 220 | *.pfx 221 | *.publishsettings 222 | orleans.codegen.cs 223 | 224 | # Including strong name files can present a security risk 225 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 226 | #*.snk 227 | 228 | # Since there are multiple workflows, uncomment next line to ignore bower_components 229 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 230 | #bower_components/ 231 | 232 | # RIA/Silverlight projects 233 | Generated_Code/ 234 | 235 | # Backup & report files from converting an old project file 236 | # to a newer Visual Studio version. Backup files are not needed, 237 | # because we have git ;-) 238 | _UpgradeReport_Files/ 239 | Backup*/ 240 | UpgradeLog*.XML 241 | UpgradeLog*.htm 242 | ServiceFabricBackup/ 243 | *.rptproj.bak 244 | 245 | # SQL Server files 246 | *.mdf 247 | *.ldf 248 | *.ndf 249 | 250 | # Business Intelligence projects 251 | *.rdl.data 252 | *.bim.layout 253 | *.bim_*.settings 254 | *.rptproj.rsuser 255 | 256 | # Microsoft Fakes 257 | FakesAssemblies/ 258 | 259 | # GhostDoc plugin setting file 260 | *.GhostDoc.xml 261 | 262 | # Node.js Tools for Visual Studio 263 | .ntvs_analysis.dat 264 | node_modules/ 265 | 266 | # Visual Studio 6 build log 267 | *.plg 268 | 269 | # Visual Studio 6 workspace options file 270 | *.opt 271 | 272 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 273 | *.vbw 274 | 275 | # Visual Studio LightSwitch build output 276 | **/*.HTMLClient/GeneratedArtifacts 277 | **/*.DesktopClient/GeneratedArtifacts 278 | **/*.DesktopClient/ModelManifest.xml 279 | **/*.Server/GeneratedArtifacts 280 | **/*.Server/ModelManifest.xml 281 | _Pvt_Extensions 282 | 283 | # Paket dependency manager 284 | .paket/paket.exe 285 | paket-files/ 286 | 287 | # FAKE - F# Make 288 | .fake/ 289 | 290 | # JetBrains Rider 291 | .idea/ 292 | *.sln.iml 293 | 294 | # CodeRush 295 | .cr/ 296 | 297 | # Python Tools for Visual Studio (PTVS) 298 | __pycache__/ 299 | *.pyc 300 | 301 | # Cake - Uncomment if you are using it 302 | # tools/** 303 | # !tools/packages.config 304 | 305 | # Tabs Studio 306 | *.tss 307 | 308 | # Telerik's JustMock configuration file 309 | *.jmconfig 310 | 311 | # BizTalk build output 312 | *.btp.cs 313 | *.btm.cs 314 | *.odx.cs 315 | *.xsd.cs 316 | 317 | # OpenCover UI analysis results 318 | OpenCover/ 319 | 320 | # Azure Stream Analytics local run output 321 | ASALocalRun/ 322 | 323 | # MSBuild Binary and Structured Log 324 | *.binlog 325 | 326 | # NVidia Nsight GPU debugger configuration file 327 | *.nvuser 328 | 329 | # MFractors (Xamarin productivity tool) working folder 330 | .mfractor/ -------------------------------------------------------------------------------- /test/Microsoft.Azure.WebJobs.Extensions.ServiceBus.Tests/Listeners/ServiceBusListenerTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Linq; 6 | using System.Reflection; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | using Microsoft.Azure.ServiceBus; 10 | using Microsoft.Azure.ServiceBus.Core; 11 | using Microsoft.Azure.WebJobs.Host.Executors; 12 | using Microsoft.Azure.WebJobs.Host.Scale; 13 | using Microsoft.Azure.WebJobs.Host.TestCommon; 14 | using Microsoft.Azure.WebJobs.ServiceBus.Listeners; 15 | using Microsoft.Extensions.Logging; 16 | using Microsoft.Extensions.Options; 17 | using Moq; 18 | using Xunit; 19 | using static Microsoft.Azure.ServiceBus.Message; 20 | 21 | namespace Microsoft.Azure.WebJobs.ServiceBus.UnitTests.Listeners 22 | { 23 | public class ServiceBusListenerTests 24 | { 25 | private readonly ServiceBusListener _listener; 26 | private readonly Mock _mockExecutor; 27 | private readonly Mock _mockMessagingProvider; 28 | private readonly Mock _mockMessageProcessor; 29 | private readonly TestLoggerProvider _loggerProvider; 30 | private readonly LoggerFactory _loggerFactory; 31 | private readonly string _functionId = "test-functionid"; 32 | private readonly string _entityPath = "test-entity-path"; 33 | private readonly string _testConnection = "Endpoint=sb://test.servicebus.windows.net/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=abc123="; 34 | 35 | public ServiceBusListenerTests() 36 | { 37 | _mockExecutor = new Mock(MockBehavior.Strict); 38 | 39 | MessageHandlerOptions messageOptions = new MessageHandlerOptions(ExceptionReceivedHandler); 40 | MessageReceiver messageReceiver = new MessageReceiver(_testConnection, _entityPath); 41 | _mockMessageProcessor = new Mock(MockBehavior.Strict, messageReceiver, messageOptions); 42 | 43 | ServiceBusOptions config = new ServiceBusOptions 44 | { 45 | MessageHandlerOptions = messageOptions 46 | }; 47 | _mockMessagingProvider = new Mock(MockBehavior.Strict, new OptionsWrapper(config)); 48 | 49 | _mockMessagingProvider 50 | .Setup(p => p.CreateMessageProcessor(_entityPath, _testConnection)) 51 | .Returns(_mockMessageProcessor.Object); 52 | 53 | _mockMessagingProvider 54 | .Setup(p => p.CreateMessageReceiver(_entityPath, _testConnection)) 55 | .Returns(messageReceiver); 56 | 57 | Mock mockServiceBusAccount = new Mock(MockBehavior.Strict); 58 | mockServiceBusAccount.Setup(a => a.ConnectionString).Returns(_testConnection); 59 | 60 | _loggerFactory = new LoggerFactory(); 61 | _loggerProvider = new TestLoggerProvider(); 62 | _loggerFactory.AddProvider(_loggerProvider); 63 | 64 | _listener = new ServiceBusListener(_functionId, EntityType.Queue, _entityPath, false, _mockExecutor.Object, config, mockServiceBusAccount.Object, 65 | _mockMessagingProvider.Object, _loggerFactory, false); 66 | } 67 | 68 | [Fact] 69 | public async Task ProcessMessageAsync_Success() 70 | { 71 | var message = new CustomMessage(); 72 | var systemProperties = new Message.SystemPropertiesCollection(); 73 | typeof(Message.SystemPropertiesCollection).GetProperty("SequenceNumber").SetValue(systemProperties, 1); 74 | typeof(Message.SystemPropertiesCollection).GetProperty("DeliveryCount").SetValue(systemProperties, 55); 75 | typeof(Message.SystemPropertiesCollection).GetProperty("EnqueuedTimeUtc").SetValue(systemProperties, DateTime.Now); 76 | typeof(Message.SystemPropertiesCollection).GetProperty("LockedUntilUtc").SetValue(systemProperties, DateTime.Now); 77 | typeof(Message).GetProperty("SystemProperties").SetValue(message, systemProperties); 78 | 79 | message.MessageId = Guid.NewGuid().ToString(); 80 | _mockMessageProcessor.Setup(p => p.BeginProcessingMessageAsync(message, It.IsAny())).ReturnsAsync(true); 81 | 82 | FunctionResult result = new FunctionResult(true); 83 | _mockExecutor.Setup(p => p.TryExecuteAsync(It.Is(q => ((ServiceBusTriggerInput)q.TriggerValue).Messages[0] == message), It.IsAny())).ReturnsAsync(result); 84 | 85 | _mockMessageProcessor.Setup(p => p.CompleteProcessingMessageAsync(message, result, It.IsAny())).Returns(Task.FromResult(0)); 86 | 87 | await _listener.ProcessMessageAsync(message, CancellationToken.None); 88 | 89 | _mockMessageProcessor.VerifyAll(); 90 | _mockExecutor.VerifyAll(); 91 | _mockMessageProcessor.VerifyAll(); 92 | } 93 | 94 | [Fact] 95 | public async Task ProcessMessageAsync_BeginProcessingReturnsFalse_MessageNotProcessed() 96 | { 97 | var message = new CustomMessage(); 98 | message.MessageId = Guid.NewGuid().ToString(); 99 | //CancellationToken cancellationToken = new CancellationToken(); 100 | _mockMessageProcessor.Setup(p => p.BeginProcessingMessageAsync(message, It.IsAny())).ReturnsAsync(false); 101 | 102 | await _listener.ProcessMessageAsync(message, CancellationToken.None); 103 | 104 | _mockMessageProcessor.VerifyAll(); 105 | } 106 | 107 | [Fact] 108 | public void GetMonitor_ReturnsExpectedValue() 109 | { 110 | IScaleMonitor scaleMonitor = _listener.GetMonitor(); 111 | 112 | Assert.Equal(scaleMonitor.GetType(), typeof(ServiceBusScaleMonitor)); 113 | Assert.Equal(scaleMonitor.Descriptor.Id, $"{_functionId}-ServiceBusTrigger-{_entityPath}".ToLower()); 114 | 115 | var scaleMonitor2 = _listener.GetMonitor(); 116 | 117 | Assert.Same(scaleMonitor, scaleMonitor2); 118 | } 119 | 120 | [Fact] 121 | public void StopAsync_LogListenerDetails() 122 | { 123 | Assert.ThrowsAsync(() => _listener.StopAsync(CancellationToken.None)); 124 | Assert.NotNull(_loggerProvider.GetAllLogMessages().SingleOrDefault(x => x.FormattedMessage.StartsWith("ServiceBus listener stopped"))); 125 | } 126 | 127 | Task ExceptionReceivedHandler(ExceptionReceivedEventArgs eventArgs) 128 | { 129 | return Task.CompletedTask; 130 | } 131 | } 132 | 133 | // Mock calls ToString() for Mesage. This ckass fixes bug in azure-service-bus-dotnet. 134 | // https://github.com/Azure/azure-service-bus-dotnet/blob/dev/src/Microsoft.Azure.ServiceBus/Message.cs#L291 135 | internal class CustomMessage : Message 136 | { 137 | public override string ToString() 138 | { 139 | return MessageId; 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/Microsoft.Azure.WebJobs.Extensions.ServiceBus/Config/ServiceBusOptions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Threading.Tasks; 6 | using Microsoft.Azure.ServiceBus; 7 | using Microsoft.Azure.WebJobs.Hosting; 8 | using Newtonsoft.Json; 9 | using Newtonsoft.Json.Linq; 10 | 11 | namespace Microsoft.Azure.WebJobs.ServiceBus 12 | { 13 | /// 14 | /// Configuration options for the ServiceBus extension. 15 | /// 16 | public class ServiceBusOptions : IOptionsFormatter 17 | { 18 | /// 19 | /// Constructs a new instance. 20 | /// 21 | public ServiceBusOptions() 22 | { 23 | // Our default options will delegate to our own exception 24 | // logger. Customers can override this completely by setting their 25 | // own MessageHandlerOptions instance. 26 | MessageHandlerOptions = new MessageHandlerOptions(ExceptionReceivedHandler) 27 | { 28 | MaxConcurrentCalls = Utility.GetProcessorCount() * 16 29 | }; 30 | 31 | SessionHandlerOptions = new SessionHandlerOptions(ExceptionReceivedHandler); 32 | 33 | // Default operation timeout is 1 minute in ServiceBus SDK 34 | // https://github.com/Azure/azure-sdk-for-net/blob/master/sdk/servicebus/Microsoft.Azure.ServiceBus/src/Constants.cs#L30 35 | BatchOptions = new BatchOptions() 36 | { 37 | MaxMessageCount = 1000, 38 | OperationTimeout = TimeSpan.FromMinutes(1), 39 | AutoComplete = true 40 | }; 41 | } 42 | 43 | /// 44 | /// Gets or sets the Azure ServiceBus connection string. 45 | /// 46 | public string ConnectionString { get; set; } 47 | 48 | /// 49 | /// Gets or sets the default that will be used by 50 | /// s. 51 | /// 52 | public MessageHandlerOptions MessageHandlerOptions { get; set; } 53 | 54 | /// 55 | /// Gets or sets the default that will be used by 56 | /// s. 57 | /// 58 | public SessionHandlerOptions SessionHandlerOptions { get; set; } 59 | 60 | /// 61 | /// Gets or sets the default PrefetchCount that will be used by s. 62 | /// 63 | public int PrefetchCount { get; set; } 64 | 65 | /// 66 | /// Gets or sets the default that will be used by 67 | /// s. 68 | /// 69 | public BatchOptions BatchOptions { get; set; } 70 | 71 | internal Action ExceptionHandler { get; set; } 72 | 73 | public string Format() 74 | { 75 | JObject messageHandlerOptions = null; 76 | if (MessageHandlerOptions != null) 77 | { 78 | messageHandlerOptions = new JObject 79 | { 80 | { nameof(MessageHandlerOptions.AutoComplete), MessageHandlerOptions.AutoComplete }, 81 | { nameof(MessageHandlerOptions.MaxAutoRenewDuration), MessageHandlerOptions.MaxAutoRenewDuration }, 82 | { nameof(MessageHandlerOptions.MaxConcurrentCalls), MessageHandlerOptions.MaxConcurrentCalls } 83 | }; 84 | } 85 | 86 | JObject sessionHandlerOptions = null; 87 | if (SessionHandlerOptions != null) 88 | { 89 | sessionHandlerOptions = new JObject 90 | { 91 | { nameof(SessionHandlerOptions.AutoComplete), SessionHandlerOptions.AutoComplete }, 92 | { nameof(SessionHandlerOptions.MaxAutoRenewDuration), SessionHandlerOptions.MaxAutoRenewDuration }, 93 | { nameof(SessionHandlerOptions.MaxConcurrentSessions), SessionHandlerOptions.MaxConcurrentSessions }, 94 | { nameof(SessionHandlerOptions.MessageWaitTimeout), SessionHandlerOptions.MessageWaitTimeout } 95 | }; 96 | } 97 | 98 | JObject batchOptions = null; 99 | if (BatchOptions != null) 100 | { 101 | batchOptions = new JObject 102 | { 103 | { nameof(BatchOptions.MaxMessageCount), BatchOptions.MaxMessageCount }, 104 | { nameof(BatchOptions.OperationTimeout), BatchOptions.OperationTimeout }, 105 | { nameof(BatchOptions.AutoComplete), BatchOptions.AutoComplete }, 106 | }; 107 | } 108 | 109 | // Do not include ConnectionString in loggable options. 110 | JObject options = new JObject 111 | { 112 | { nameof(PrefetchCount), PrefetchCount }, 113 | { nameof(MessageHandlerOptions), messageHandlerOptions }, 114 | { nameof(SessionHandlerOptions), sessionHandlerOptions }, 115 | { nameof(BatchOptions), batchOptions} 116 | }; 117 | 118 | return options.ToString(Formatting.Indented); 119 | } 120 | 121 | /// 122 | /// Deep clones service bus options. 123 | /// 124 | /// The soure service bus options. 125 | internal static ServiceBusOptions DeepClone(ServiceBusOptions source) 126 | { 127 | if (source == null) 128 | { 129 | return null; 130 | } 131 | 132 | var options = new ServiceBusOptions() 133 | { 134 | ConnectionString = source.ConnectionString, 135 | PrefetchCount = source.PrefetchCount, 136 | }; 137 | 138 | options.MessageHandlerOptions.AutoComplete = source.MessageHandlerOptions.AutoComplete; 139 | options.MessageHandlerOptions.MaxAutoRenewDuration = new TimeSpan(source.MessageHandlerOptions.MaxAutoRenewDuration.Ticks); 140 | options.MessageHandlerOptions.MaxConcurrentCalls = source.MessageHandlerOptions.MaxConcurrentCalls; 141 | 142 | options.SessionHandlerOptions.AutoComplete = source.SessionHandlerOptions.AutoComplete; 143 | options.SessionHandlerOptions.MaxConcurrentSessions = source.SessionHandlerOptions.MaxConcurrentSessions; 144 | options.SessionHandlerOptions.MaxAutoRenewDuration = new TimeSpan(source.SessionHandlerOptions.MaxAutoRenewDuration.Ticks); 145 | options.SessionHandlerOptions.MessageWaitTimeout = new TimeSpan(source.SessionHandlerOptions.MessageWaitTimeout.Ticks); 146 | 147 | options.BatchOptions.AutoComplete = source.BatchOptions.AutoComplete; 148 | options.BatchOptions.MaxMessageCount = source.BatchOptions.MaxMessageCount; 149 | options.BatchOptions.OperationTimeout = new TimeSpan(source.BatchOptions.OperationTimeout.Ticks); 150 | 151 | return options; 152 | } 153 | 154 | private Task ExceptionReceivedHandler(ExceptionReceivedEventArgs args) 155 | { 156 | ExceptionHandler?.Invoke(args); 157 | 158 | return Task.CompletedTask; 159 | } 160 | } 161 | } 162 | --------------------------------------------------------------------------------