Removing ServiceBus extension
This commit is contained in:
Родитель
cf6a413859
Коммит
adabc4f006
|
@ -17,12 +17,13 @@ if (-not $?) { exit 1 }
|
|||
$projects =
|
||||
"src\Microsoft.Azure.WebJobs\WebJobs.csproj",
|
||||
"src\Microsoft.Azure.WebJobs.Host\WebJobs.Host.csproj",
|
||||
"src\Microsoft.Azure.WebJobs.Host\WebJobs.Host.Sources.csproj",
|
||||
"src\Microsoft.Azure.WebJobs.Logging\WebJobs.Logging.csproj",
|
||||
"src\Microsoft.Azure.WebJobs.Logging.ApplicationInsights\WebJobs.Logging.ApplicationInsights.csproj",
|
||||
"src\Microsoft.Azure.WebJobs.Extensions.EventHubs\WebJobs.Extensions.EventHubs.csproj",
|
||||
"src\Microsoft.Azure.WebJobs.Extensions.ServiceBus\WebJobs.Extensions.ServiceBus.csproj",
|
||||
"src\Microsoft.Azure.WebJobs.Extensions.Storage\WebJobs.Extensions.Storage.csproj",
|
||||
"src\Microsoft.Azure.WebJobs.Host.Storage\WebJobs.Host.Storage.csproj"
|
||||
"src\Microsoft.Azure.WebJobs.Host.Storage\WebJobs.Host.Storage.csproj",
|
||||
"src\Microsoft.Azure.WebJobs.Host.TestCommon\WebJobs.Host.TestCommon.csproj"
|
||||
|
||||
foreach ($project in $projects)
|
||||
{
|
||||
|
|
13
WebJobs.sln
13
WebJobs.sln
|
@ -35,12 +35,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{3B089351
|
|||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebJobs.Extensions.EventHubs.UnitTests", "test\Microsoft.Azure.WebJobs.Extensions.EventHubs.UnitTests\WebJobs.Extensions.EventHubs.UnitTests.csproj", "{27F0E6AC-505E-4BEC-81CA-8DF777DEA9C7}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebJobs.Extensions.ServiceBus", "src\Microsoft.Azure.WebJobs.Extensions.ServiceBus\WebJobs.Extensions.ServiceBus.csproj", "{A2B3C676-3DF0-43B4-92A2-7E7DAA7BF439}"
|
||||
EndProject
|
||||
Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "WebJobs.Shared", "src\Microsoft.Azure.WebJobs.Shared\WebJobs.Shared.shproj", "{ADD036F5-2170-4B05-9E0A-C2ED0A08A929}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebJobs.Extensions.ServiceBus.UnitTests", "test\Microsoft.Azure.WebJobs.Extensions.ServiceBus.UnitTests\WebJobs.Extensions.ServiceBus.UnitTests.csproj", "{D37637D5-7EF9-43CB-86BE-537473CD613B}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebJobs.Extensions.Storage", "src\Microsoft.Azure.WebJobs.Extensions.Storage\WebJobs.Extensions.Storage.csproj", "{A9733406-267C-4A53-AB07-D3A834E22153}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebJobs.Host.Storage", "src\Microsoft.Azure.WebJobs.Host.Storage\WebJobs.Host.Storage.csproj", "{DED33098-FE99-436C-96CC-B59A30BEF027}"
|
||||
|
@ -114,14 +110,6 @@ Global
|
|||
{27F0E6AC-505E-4BEC-81CA-8DF777DEA9C7}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{27F0E6AC-505E-4BEC-81CA-8DF777DEA9C7}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{27F0E6AC-505E-4BEC-81CA-8DF777DEA9C7}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{A2B3C676-3DF0-43B4-92A2-7E7DAA7BF439}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{A2B3C676-3DF0-43B4-92A2-7E7DAA7BF439}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A2B3C676-3DF0-43B4-92A2-7E7DAA7BF439}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{A2B3C676-3DF0-43B4-92A2-7E7DAA7BF439}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{D37637D5-7EF9-43CB-86BE-537473CD613B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{D37637D5-7EF9-43CB-86BE-537473CD613B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{D37637D5-7EF9-43CB-86BE-537473CD613B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{D37637D5-7EF9-43CB-86BE-537473CD613B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{A9733406-267C-4A53-AB07-D3A834E22153}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{A9733406-267C-4A53-AB07-D3A834E22153}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A9733406-267C-4A53-AB07-D3A834E22153}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
|
@ -158,7 +146,6 @@ Global
|
|||
{C8EAAE01-E8CF-4131-9D4B-F0FDF00DA4BE} = {639967B0-0544-4C52-94AC-9A3D25E33256}
|
||||
{340AB554-5482-4B3D-B65F-46DFF5AF1684} = {639967B0-0544-4C52-94AC-9A3D25E33256}
|
||||
{27F0E6AC-505E-4BEC-81CA-8DF777DEA9C7} = {639967B0-0544-4C52-94AC-9A3D25E33256}
|
||||
{D37637D5-7EF9-43CB-86BE-537473CD613B} = {639967B0-0544-4C52-94AC-9A3D25E33256}
|
||||
{0CC5741F-ACDA-4DB8-9C17-074E8896F244} = {639967B0-0544-4C52-94AC-9A3D25E33256}
|
||||
{337B79EB-A3CB-4CE0-A7F2-DD5E638AC882} = {639967B0-0544-4C52-94AC-9A3D25E33256}
|
||||
{C5E1A8E8-711F-4377-A8BD-7DB58E6C580D} = {639967B0-0544-4C52-94AC-9A3D25E33256}
|
||||
|
|
|
@ -56,10 +56,6 @@ test_script:
|
|||
|
||||
dotnet test .\test\Microsoft.Azure.Webjobs.Extensions.Storage.UnitTests\ -v q --no-build
|
||||
|
||||
$success = $success -and $?
|
||||
|
||||
dotnet test .\test\Microsoft.Azure.WebJobs.Extensions.ServiceBus.UnitTests\ -v q --no-build
|
||||
|
||||
$success = $success -and $?
|
||||
|
||||
if (-not $success) { exit 1 }
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Azure.WebJobs.Extensions.ServiceBus" Version="3.0.6" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.CommandLine" Version="2.1.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.1.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.1.0" />
|
||||
|
@ -28,7 +29,6 @@
|
|||
<ProjectReference Include="..\..\src\Microsoft.Azure.WebJobs.Host.Storage\WebJobs.Host.Storage.csproj" />
|
||||
<ProjectReference Include="..\..\src\Microsoft.Azure.WebJobs.Host\WebJobs.Host.csproj" />
|
||||
<ProjectReference Include="..\..\src\Microsoft.Azure.WebJobs.Logging.ApplicationInsights\WebJobs.Logging.ApplicationInsights.csproj" />
|
||||
<ProjectReference Include="..\..\src\Microsoft.Azure.WebJobs.Extensions.ServiceBus\WebJobs.Extensions.ServiceBus.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
|
|
|
@ -1,83 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.ServiceBus;
|
||||
using Microsoft.Azure.WebJobs.Host.Bindings;
|
||||
using Microsoft.Azure.WebJobs.Host.Converters;
|
||||
|
||||
namespace Microsoft.Azure.WebJobs.ServiceBus.Bindings
|
||||
{
|
||||
internal class AsyncCollectorArgumentBindingProvider : IQueueArgumentBindingProvider
|
||||
{
|
||||
public IArgumentBinding<ServiceBusEntity> TryCreate(ParameterInfo parameter)
|
||||
{
|
||||
Type parameterType = parameter.ParameterType;
|
||||
|
||||
if (!parameterType.IsGenericType)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
Type genericTypeDefinition = parameterType.GetGenericTypeDefinition();
|
||||
|
||||
if (genericTypeDefinition != typeof(IAsyncCollector<>))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
Type itemType = parameterType.GetGenericArguments()[0];
|
||||
return CreateBinding(itemType);
|
||||
}
|
||||
|
||||
private static IArgumentBinding<ServiceBusEntity> CreateBinding(Type itemType)
|
||||
{
|
||||
MethodInfo method = typeof(AsyncCollectorArgumentBindingProvider).GetMethod("CreateBindingGeneric",
|
||||
BindingFlags.NonPublic | BindingFlags.Static);
|
||||
Debug.Assert(method != null);
|
||||
MethodInfo genericMethod = method.MakeGenericMethod(itemType);
|
||||
Debug.Assert(genericMethod != null);
|
||||
Func<IArgumentBinding<ServiceBusEntity>> lambda =
|
||||
(Func<IArgumentBinding<ServiceBusEntity>>)Delegate.CreateDelegate(
|
||||
typeof(Func<IArgumentBinding<ServiceBusEntity>>), genericMethod);
|
||||
return lambda.Invoke();
|
||||
}
|
||||
|
||||
private static IArgumentBinding<ServiceBusEntity> CreateBindingGeneric<TItem>()
|
||||
{
|
||||
return new AsyncCollectorArgumentBinding<TItem>(MessageConverterFactory.Create<TItem>());
|
||||
}
|
||||
|
||||
private class AsyncCollectorArgumentBinding<TItem> : IArgumentBinding<ServiceBusEntity>
|
||||
{
|
||||
private readonly IConverter<TItem, Message> _converter;
|
||||
|
||||
public AsyncCollectorArgumentBinding(IConverter<TItem, Message> converter)
|
||||
{
|
||||
_converter = converter;
|
||||
}
|
||||
|
||||
public Type ValueType
|
||||
{
|
||||
get { return typeof(IAsyncCollector<TItem>); }
|
||||
}
|
||||
|
||||
public Task<IValueProvider> BindAsync(ServiceBusEntity value, ValueBindingContext context)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException("context");
|
||||
}
|
||||
|
||||
IAsyncCollector<TItem> collector = new MessageSenderAsyncCollector<TItem>(value, _converter,
|
||||
context.FunctionInstanceId);
|
||||
IValueProvider provider = new CollectorValueProvider(value, collector, typeof(IAsyncCollector<TItem>));
|
||||
|
||||
return Task.FromResult(provider);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using Microsoft.Azure.WebJobs.Host.Bindings;
|
||||
using Microsoft.Azure.WebJobs.Host.Bindings.Path;
|
||||
|
||||
namespace Microsoft.Azure.WebJobs.ServiceBus.Bindings
|
||||
{
|
||||
/// <summary>
|
||||
/// Utility class with factory method to create an instance of a strategy class implementing <see cref="IBindableServiceBusPath"/> interface.
|
||||
/// </summary>
|
||||
internal static class BindableServiceBusPath
|
||||
{
|
||||
/// <summary>
|
||||
/// A factory method detecting parameters in supplied queue or topic name pattern and creating
|
||||
/// an instance of relevant strategy class implementing <see cref="IBindableServiceBusPath"/>.
|
||||
/// </summary>
|
||||
/// <param name="queueOrTopicNamePattern">Service Bus queue or topic name pattern containing optional binding parameters.</param>
|
||||
/// <returns>An object implementing <see cref="IBindableServiceBusPath"/></returns>
|
||||
public static IBindableServiceBusPath Create(string queueOrTopicNamePattern)
|
||||
{
|
||||
if (queueOrTopicNamePattern == null)
|
||||
{
|
||||
throw new ArgumentNullException("queueOrTopicNamePattern");
|
||||
}
|
||||
|
||||
BindingTemplate template = BindingTemplate.FromString(queueOrTopicNamePattern);
|
||||
|
||||
if (template.ParameterNames.Count() > 0)
|
||||
{
|
||||
return new ParameterizedServiceBusPath(template);
|
||||
}
|
||||
|
||||
return new BoundServiceBusPath(queueOrTopicNamePattern);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Microsoft.Azure.WebJobs.ServiceBus.Bindings
|
||||
{
|
||||
/// <summary>
|
||||
/// Bindable queue or topic path strategy implementation for "degenerate" bindable patterns,
|
||||
/// i.e. containing no parameters.
|
||||
/// </summary>
|
||||
internal class BoundServiceBusPath : IBindableServiceBusPath
|
||||
{
|
||||
private readonly string _queueOrTopicNamePattern;
|
||||
|
||||
public BoundServiceBusPath(string queueOrTopicNamePattern)
|
||||
{
|
||||
_queueOrTopicNamePattern = queueOrTopicNamePattern;
|
||||
}
|
||||
|
||||
public string QueueOrTopicNamePattern
|
||||
{
|
||||
get { return _queueOrTopicNamePattern; }
|
||||
}
|
||||
|
||||
public bool IsBound
|
||||
{
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
public IEnumerable<string> ParameterNames
|
||||
{
|
||||
get { return Enumerable.Empty<string>(); }
|
||||
}
|
||||
|
||||
public string Bind(IReadOnlyDictionary<string, object> bindingData)
|
||||
{
|
||||
return QueueOrTopicNamePattern;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,64 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.WebJobs.Host.Bindings;
|
||||
|
||||
namespace Microsoft.Azure.WebJobs.ServiceBus.Bindings
|
||||
{
|
||||
internal class ByteArrayArgumentBindingProvider : IQueueArgumentBindingProvider
|
||||
{
|
||||
public IArgumentBinding<ServiceBusEntity> TryCreate(ParameterInfo parameter)
|
||||
{
|
||||
if (!parameter.IsOut || parameter.ParameterType != typeof(byte[]).MakeByRefType())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new ByteArrayArgumentBinding();
|
||||
}
|
||||
|
||||
private class ByteArrayArgumentBinding : IArgumentBinding<ServiceBusEntity>
|
||||
{
|
||||
public Type ValueType
|
||||
{
|
||||
get { return typeof(byte[]); }
|
||||
}
|
||||
|
||||
/// <remarks>
|
||||
/// The out byte array parameter is processed as follows:
|
||||
/// <list type="bullet">
|
||||
/// <item>
|
||||
/// <description>
|
||||
/// If the value is <see langword="null"/>, no message will be sent.
|
||||
/// </description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <description>
|
||||
/// If the value is an empty byte array, a message with empty content will be sent.
|
||||
/// </description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <description>
|
||||
/// If the value is a non-empty byte array, a message with that content will be sent.
|
||||
/// </description>
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// </remarks>
|
||||
public Task<IValueProvider> BindAsync(ServiceBusEntity value, ValueBindingContext context)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException("context");
|
||||
}
|
||||
|
||||
IValueProvider provider = new NonNullConverterValueBinder<byte[]>(value,
|
||||
new ByteArrayToBrokeredMessageConverter(), context.FunctionInstanceId);
|
||||
|
||||
return Task.FromResult(provider);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Microsoft.Azure.ServiceBus;
|
||||
|
||||
namespace Microsoft.Azure.WebJobs.ServiceBus.Bindings
|
||||
{
|
||||
internal class ByteArrayToBrokeredMessageConverter : IConverter<byte[], Message>
|
||||
{
|
||||
[SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope")]
|
||||
public Message Convert(byte[] input)
|
||||
{
|
||||
if (input == null)
|
||||
{
|
||||
throw new InvalidOperationException("A brokered message cannot contain a null byte array instance.");
|
||||
}
|
||||
|
||||
return new Message(input)
|
||||
{
|
||||
ContentType = ContentTypes.ApplicationOctetStream
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,83 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.WebJobs.Host.Bindings;
|
||||
using Microsoft.Azure.WebJobs.Host.Converters;
|
||||
using Microsoft.Azure.ServiceBus;
|
||||
|
||||
namespace Microsoft.Azure.WebJobs.ServiceBus.Bindings
|
||||
{
|
||||
internal class CollectorArgumentBindingProvider : IQueueArgumentBindingProvider
|
||||
{
|
||||
public IArgumentBinding<ServiceBusEntity> TryCreate(ParameterInfo parameter)
|
||||
{
|
||||
Type parameterType = parameter.ParameterType;
|
||||
|
||||
if (!parameterType.IsGenericType)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
Type genericTypeDefinition = parameterType.GetGenericTypeDefinition();
|
||||
|
||||
if (genericTypeDefinition != typeof(ICollector<>))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
Type itemType = parameterType.GetGenericArguments()[0];
|
||||
return CreateBinding(itemType);
|
||||
}
|
||||
|
||||
private static IArgumentBinding<ServiceBusEntity> CreateBinding(Type itemType)
|
||||
{
|
||||
MethodInfo method = typeof(CollectorArgumentBindingProvider).GetMethod("CreateBindingGeneric",
|
||||
BindingFlags.NonPublic | BindingFlags.Static);
|
||||
Debug.Assert(method != null);
|
||||
MethodInfo genericMethod = method.MakeGenericMethod(itemType);
|
||||
Debug.Assert(genericMethod != null);
|
||||
Func<IArgumentBinding<ServiceBusEntity>> lambda =
|
||||
(Func<IArgumentBinding<ServiceBusEntity>>)Delegate.CreateDelegate(
|
||||
typeof(Func<IArgumentBinding<ServiceBusEntity>>), genericMethod);
|
||||
return lambda.Invoke();
|
||||
}
|
||||
|
||||
private static IArgumentBinding<ServiceBusEntity> CreateBindingGeneric<TItem>()
|
||||
{
|
||||
return new CollectorArgumentBinding<TItem>(MessageConverterFactory.Create<TItem>());
|
||||
}
|
||||
|
||||
private class CollectorArgumentBinding<TItem> : IArgumentBinding<ServiceBusEntity>
|
||||
{
|
||||
private readonly IConverter<TItem, Message> _converter;
|
||||
|
||||
public CollectorArgumentBinding(IConverter<TItem, Message> converter)
|
||||
{
|
||||
_converter = converter;
|
||||
}
|
||||
|
||||
public Type ValueType
|
||||
{
|
||||
get { return typeof(ICollector<TItem>); }
|
||||
}
|
||||
|
||||
public Task<IValueProvider> BindAsync(ServiceBusEntity value, ValueBindingContext context)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException("context");
|
||||
}
|
||||
|
||||
ICollector<TItem> collector = new MessageSenderCollector<TItem>(value, _converter,
|
||||
context.FunctionInstanceId);
|
||||
IValueProvider provider = new CollectorValueProvider(value, collector, typeof(ICollector<TItem>));
|
||||
|
||||
return Task.FromResult(provider);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.WebJobs.Host.Bindings;
|
||||
|
||||
namespace Microsoft.Azure.WebJobs.ServiceBus.Bindings
|
||||
{
|
||||
internal class CollectorValueProvider : IValueProvider
|
||||
{
|
||||
private readonly ServiceBusEntity _entity;
|
||||
private readonly object _value;
|
||||
private readonly Type _valueType;
|
||||
|
||||
public CollectorValueProvider(ServiceBusEntity entity, object value, Type valueType)
|
||||
{
|
||||
if (value != null && !valueType.IsAssignableFrom(value.GetType()))
|
||||
{
|
||||
throw new InvalidOperationException("value is not of the correct type.");
|
||||
}
|
||||
|
||||
_entity = entity;
|
||||
_value = value;
|
||||
_valueType = valueType;
|
||||
}
|
||||
|
||||
public Type Type
|
||||
{
|
||||
get { return _valueType; }
|
||||
}
|
||||
|
||||
public Task<object> GetValueAsync()
|
||||
{
|
||||
return Task.FromResult(_value);
|
||||
}
|
||||
|
||||
public string ToInvokeString()
|
||||
{
|
||||
return _entity.MessageSender.Path;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using Microsoft.Azure.WebJobs.Host.Bindings;
|
||||
|
||||
namespace Microsoft.Azure.WebJobs.ServiceBus.Bindings
|
||||
{
|
||||
internal class CompositeArgumentBindingProvider : IQueueArgumentBindingProvider
|
||||
{
|
||||
private readonly IEnumerable<IQueueArgumentBindingProvider> _providers;
|
||||
|
||||
public CompositeArgumentBindingProvider(params IQueueArgumentBindingProvider[] providers)
|
||||
{
|
||||
_providers = providers;
|
||||
}
|
||||
|
||||
public IArgumentBinding<ServiceBusEntity> TryCreate(ParameterInfo parameter)
|
||||
{
|
||||
foreach (IQueueArgumentBindingProvider provider in _providers)
|
||||
{
|
||||
IArgumentBinding<ServiceBusEntity> binding = provider.TryCreate(parameter);
|
||||
|
||||
if (binding != null)
|
||||
{
|
||||
return binding;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,54 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.ServiceBus;
|
||||
using Microsoft.Azure.WebJobs.Host.Bindings;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Microsoft.Azure.WebJobs.ServiceBus.Bindings
|
||||
{
|
||||
internal class ConverterValueBinder<TInput> : IOrderedValueBinder
|
||||
{
|
||||
private readonly ServiceBusEntity _entity;
|
||||
private readonly IConverter<TInput, Message> _converter;
|
||||
private readonly Guid _functionInstanceId;
|
||||
|
||||
public ConverterValueBinder(ServiceBusEntity entity, IConverter<TInput, Message> converter,
|
||||
Guid functionInstanceId)
|
||||
{
|
||||
_entity = entity;
|
||||
_converter = converter;
|
||||
_functionInstanceId = functionInstanceId;
|
||||
}
|
||||
|
||||
public BindStepOrder StepOrder
|
||||
{
|
||||
get { return BindStepOrder.Enqueue; }
|
||||
}
|
||||
|
||||
public Type Type
|
||||
{
|
||||
get { return typeof(TInput); }
|
||||
}
|
||||
|
||||
public Task<object> GetValueAsync()
|
||||
{
|
||||
return Task.FromResult<object>(default(TInput));
|
||||
}
|
||||
|
||||
public string ToInvokeString()
|
||||
{
|
||||
return _entity.MessageSender.Path;
|
||||
}
|
||||
|
||||
public Task SetValueAsync(object value, CancellationToken cancellationToken)
|
||||
{
|
||||
Message message = _converter.Convert((TInput)value);
|
||||
Debug.Assert(message != null);
|
||||
return _entity.SendAndCreateEntityIfNotExistsAsync(message, _functionInstanceId, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.Azure.WebJobs.ServiceBus.Bindings
|
||||
{
|
||||
internal interface IBindableServiceBusPath
|
||||
{
|
||||
string QueueOrTopicNamePattern { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this path is bound.
|
||||
/// </summary>
|
||||
bool IsBound { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the collection of parameter names for the path.
|
||||
/// </summary>
|
||||
IEnumerable<string> ParameterNames { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Bind to the path.
|
||||
/// </summary>
|
||||
/// <param name="bindingData">The binding data.</param>
|
||||
/// <returns>The path binding.</returns>
|
||||
string Bind(IReadOnlyDictionary<string, object> bindingData);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a string representation of the path.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
string ToString();
|
||||
}
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
using System.Reflection;
|
||||
using Microsoft.Azure.WebJobs.Host.Bindings;
|
||||
|
||||
namespace Microsoft.Azure.WebJobs.ServiceBus.Bindings
|
||||
{
|
||||
internal interface IQueueArgumentBindingProvider
|
||||
{
|
||||
IArgumentBinding<ServiceBusEntity> TryCreate(ParameterInfo parameter);
|
||||
}
|
||||
}
|
|
@ -1,100 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.ServiceBus;
|
||||
using Microsoft.Azure.WebJobs.Host.Bindings;
|
||||
|
||||
namespace Microsoft.Azure.WebJobs.ServiceBus.Bindings
|
||||
{
|
||||
internal class MessageArgumentBinding : IArgumentBinding<ServiceBusEntity>
|
||||
{
|
||||
public Type ValueType
|
||||
{
|
||||
get { return typeof(Message); }
|
||||
}
|
||||
|
||||
public Task<IValueProvider> BindAsync(ServiceBusEntity value, ValueBindingContext context)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException("context");
|
||||
}
|
||||
|
||||
IValueProvider provider = new MessageValueBinder(value, context.FunctionInstanceId);
|
||||
|
||||
return Task.FromResult(provider);
|
||||
}
|
||||
|
||||
private class MessageValueBinder : IOrderedValueBinder
|
||||
{
|
||||
private readonly ServiceBusEntity _entity;
|
||||
private readonly Guid _functionInstanceId;
|
||||
|
||||
public MessageValueBinder(ServiceBusEntity entity, Guid functionInstanceId)
|
||||
{
|
||||
_entity = entity;
|
||||
_functionInstanceId = functionInstanceId;
|
||||
}
|
||||
|
||||
public BindStepOrder StepOrder
|
||||
{
|
||||
get { return BindStepOrder.Enqueue; }
|
||||
}
|
||||
|
||||
public Type Type
|
||||
{
|
||||
get { return typeof(Message); }
|
||||
}
|
||||
|
||||
public Task<object> GetValueAsync()
|
||||
{
|
||||
return Task.FromResult<object>(null);
|
||||
}
|
||||
|
||||
public string ToInvokeString()
|
||||
{
|
||||
return _entity.MessageSender.Path;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends a Message to the bound queue.
|
||||
/// </summary>
|
||||
/// <param name="value">BrokeredMessage instance as retrieved from user's WebJobs method argument.</param>
|
||||
/// <param name="cancellationToken">a cancellation token</param>
|
||||
/// <remarks>
|
||||
/// The out message parameter is processed as follows:
|
||||
/// <list type="bullet">
|
||||
/// <item>
|
||||
/// <description>
|
||||
/// If the value is <see langword="null"/>, no message will be sent.
|
||||
/// </description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <description>
|
||||
/// If the value has empty content, a message with empty content will be sent.
|
||||
/// </description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <description>
|
||||
/// If the value has non-empty content, a message with that content will be sent.
|
||||
/// </description>
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// </remarks>
|
||||
public async Task SetValueAsync(object value, CancellationToken cancellationToken)
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var message = (Message)value;
|
||||
|
||||
await _entity.SendAndCreateEntityIfNotExistsAsync(message, _functionInstanceId, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
using System.Reflection;
|
||||
using Microsoft.Azure.ServiceBus;
|
||||
using Microsoft.Azure.WebJobs.Host.Bindings;
|
||||
|
||||
|
||||
namespace Microsoft.Azure.WebJobs.ServiceBus.Bindings
|
||||
{
|
||||
internal class MessageArgumentBindingProvider : IQueueArgumentBindingProvider
|
||||
{
|
||||
public IArgumentBinding<ServiceBusEntity> TryCreate(ParameterInfo parameter)
|
||||
{
|
||||
if (!parameter.IsOut || parameter.ParameterType != typeof(Message).MakeByRefType())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new MessageArgumentBinding();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using Microsoft.Azure.WebJobs.Host.Converters;
|
||||
using Microsoft.Azure.ServiceBus;
|
||||
|
||||
namespace Microsoft.Azure.WebJobs.ServiceBus.Bindings
|
||||
{
|
||||
internal static class MessageConverterFactory
|
||||
{
|
||||
internal static IConverter<TInput, Message> Create<TInput>()
|
||||
{
|
||||
if (typeof(TInput) == typeof(Message))
|
||||
{
|
||||
return (IConverter<TInput, Message>)new IdentityConverter<TInput>();
|
||||
}
|
||||
else if (typeof(TInput) == typeof(string))
|
||||
{
|
||||
return (IConverter<TInput, Message>)new StringToBrokeredMessageConverter();
|
||||
}
|
||||
else if (typeof(TInput) == typeof(byte[]))
|
||||
{
|
||||
return (IConverter<TInput, Message>)new ByteArrayToBrokeredMessageConverter();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (typeof(TInput).IsPrimitive)
|
||||
{
|
||||
throw new NotSupportedException("Primitive types are not supported.");
|
||||
}
|
||||
|
||||
if (typeof(IEnumerable).IsAssignableFrom(typeof(TInput)))
|
||||
{
|
||||
throw new InvalidOperationException("Nested collections are not supported.");
|
||||
}
|
||||
|
||||
return new UserTypeToBrokeredMessageConverter<TInput>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,80 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.ServiceBus.Core;
|
||||
using Microsoft.Azure.WebJobs.Host.Bindings;
|
||||
|
||||
namespace Microsoft.Azure.WebJobs.ServiceBus.Bindings
|
||||
{
|
||||
internal class MessageSenderArgumentBindingProvider : IQueueArgumentBindingProvider
|
||||
{
|
||||
public IArgumentBinding<ServiceBusEntity> TryCreate(ParameterInfo parameter)
|
||||
{
|
||||
if (parameter.ParameterType != typeof(MessageSender))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new MessageSenderArgumentBinding();
|
||||
}
|
||||
|
||||
internal class MessageSenderArgumentBinding : IArgumentBinding<ServiceBusEntity>
|
||||
{
|
||||
public Type ValueType
|
||||
{
|
||||
get { return typeof(MessageSender); }
|
||||
}
|
||||
|
||||
public Task<IValueProvider> BindAsync(ServiceBusEntity value, ValueBindingContext context)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException("context");
|
||||
}
|
||||
|
||||
IValueProvider provider = new MessageSenderValueBinder(value.MessageSender);
|
||||
|
||||
return Task.FromResult(provider);
|
||||
}
|
||||
|
||||
private class MessageSenderValueBinder : IValueBinder
|
||||
{
|
||||
private readonly MessageSender _messageSender;
|
||||
|
||||
public MessageSenderValueBinder(MessageSender messageSender)
|
||||
{
|
||||
_messageSender = messageSender;
|
||||
}
|
||||
|
||||
public BindStepOrder StepOrder
|
||||
{
|
||||
get { return BindStepOrder.Enqueue; }
|
||||
}
|
||||
|
||||
public Type Type
|
||||
{
|
||||
get { return typeof(MessageSender); }
|
||||
}
|
||||
|
||||
public Task<object> GetValueAsync()
|
||||
{
|
||||
return Task.FromResult<object>(_messageSender);
|
||||
}
|
||||
|
||||
public string ToInvokeString()
|
||||
{
|
||||
return _messageSender.Path;
|
||||
}
|
||||
|
||||
public Task SetValueAsync(object value, CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,53 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.ServiceBus;
|
||||
|
||||
namespace Microsoft.Azure.WebJobs.ServiceBus.Bindings
|
||||
{
|
||||
internal class MessageSenderAsyncCollector<T> : IAsyncCollector<T>
|
||||
{
|
||||
private readonly ServiceBusEntity _entity;
|
||||
private readonly IConverter<T, Message> _converter;
|
||||
private readonly Guid _functionInstanceId;
|
||||
|
||||
public MessageSenderAsyncCollector(ServiceBusEntity entity, IConverter<T, Message> converter,
|
||||
Guid functionInstanceId)
|
||||
{
|
||||
if (entity == null)
|
||||
{
|
||||
throw new ArgumentNullException("entity");
|
||||
}
|
||||
|
||||
if (converter == null)
|
||||
{
|
||||
throw new ArgumentNullException("converter");
|
||||
}
|
||||
|
||||
_entity = entity;
|
||||
_converter = converter;
|
||||
_functionInstanceId = functionInstanceId;
|
||||
}
|
||||
|
||||
public Task AddAsync(T item, CancellationToken cancellationToken)
|
||||
{
|
||||
Message message = _converter.Convert(item);
|
||||
|
||||
if (message == null)
|
||||
{
|
||||
throw new InvalidOperationException("Cannot enqueue a null brokered message instance.");
|
||||
}
|
||||
|
||||
return _entity.SendAndCreateEntityIfNotExistsAsync(message, _functionInstanceId, cancellationToken);
|
||||
}
|
||||
|
||||
public Task FlushAsync(CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
// Batching not supported.
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,48 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using Microsoft.Azure.WebJobs.Host.Converters;
|
||||
using Microsoft.Azure.ServiceBus;
|
||||
|
||||
namespace Microsoft.Azure.WebJobs.ServiceBus.Bindings
|
||||
{
|
||||
internal class MessageSenderCollector<T> : ICollector<T>
|
||||
{
|
||||
private readonly ServiceBusEntity _entity;
|
||||
private readonly IConverter<T, Message> _converter;
|
||||
private readonly Guid _functionInstanceId;
|
||||
|
||||
public MessageSenderCollector(ServiceBusEntity entity, IConverter<T, Message> converter,
|
||||
Guid functionInstanceId)
|
||||
{
|
||||
if (entity == null)
|
||||
{
|
||||
throw new ArgumentNullException("entity");
|
||||
}
|
||||
|
||||
if (converter == null)
|
||||
{
|
||||
throw new ArgumentNullException("converter");
|
||||
}
|
||||
|
||||
_entity = entity;
|
||||
_converter = converter;
|
||||
_functionInstanceId = functionInstanceId;
|
||||
}
|
||||
|
||||
public void Add(T item)
|
||||
{
|
||||
Message message = _converter.Convert(item);
|
||||
|
||||
if (message == null)
|
||||
{
|
||||
throw new InvalidOperationException("Cannot enqueue a null brokered message instance.");
|
||||
}
|
||||
|
||||
_entity.SendAndCreateEntityIfNotExistsAsync(message, _functionInstanceId,
|
||||
CancellationToken.None).GetAwaiter().GetResult();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.WebJobs.ServiceBus.Listeners;
|
||||
using Microsoft.Azure.ServiceBus;
|
||||
using Microsoft.Azure.ServiceBus.Core;
|
||||
|
||||
namespace Microsoft.Azure.WebJobs.ServiceBus.Bindings
|
||||
{
|
||||
internal static class MessageSenderExtensions
|
||||
{
|
||||
public static async Task SendAndCreateEntityIfNotExists(this MessageSender sender, Message message,
|
||||
Guid functionInstanceId, EntityType entityType, CancellationToken cancellationToken)
|
||||
{
|
||||
if (sender == null)
|
||||
{
|
||||
throw new ArgumentNullException("sender");
|
||||
}
|
||||
|
||||
ServiceBusCausalityHelper.EncodePayload(functionInstanceId, message);
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
await sender.SendAsync(message);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,62 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.ServiceBus;
|
||||
using Microsoft.Azure.WebJobs.Host.Bindings;
|
||||
|
||||
namespace Microsoft.Azure.WebJobs.ServiceBus.Bindings
|
||||
{
|
||||
// Same as ConverterValueBinder, but doesn't enqueue null values.
|
||||
internal class NonNullConverterValueBinder<TInput> : IOrderedValueBinder
|
||||
{
|
||||
private readonly ServiceBusEntity _entity;
|
||||
private readonly IConverter<TInput, Message> _converter;
|
||||
private readonly Guid _functionInstanceId;
|
||||
|
||||
public NonNullConverterValueBinder(ServiceBusEntity entity, IConverter<TInput, Message> converter,
|
||||
Guid functionInstanceId)
|
||||
{
|
||||
_entity = entity;
|
||||
_converter = converter;
|
||||
_functionInstanceId = functionInstanceId;
|
||||
}
|
||||
|
||||
public BindStepOrder StepOrder
|
||||
{
|
||||
get { return BindStepOrder.Enqueue; }
|
||||
}
|
||||
|
||||
public Type Type
|
||||
{
|
||||
get { return typeof(TInput); }
|
||||
}
|
||||
|
||||
public Task<object> GetValueAsync()
|
||||
{
|
||||
return Task.FromResult<object>(null);
|
||||
}
|
||||
|
||||
public string ToInvokeString()
|
||||
{
|
||||
return _entity.MessageSender.Path;
|
||||
}
|
||||
|
||||
public Task SetValueAsync(object value, CancellationToken cancellationToken)
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
Debug.Assert(value is TInput);
|
||||
Message message = _converter.Convert((TInput)value);
|
||||
Debug.Assert(message != null);
|
||||
|
||||
return _entity.SendAndCreateEntityIfNotExistsAsync(message, _functionInstanceId, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.WebJobs.Host.Converters;
|
||||
|
||||
namespace Microsoft.Azure.WebJobs.ServiceBus.Bindings
|
||||
{
|
||||
internal class OutputConverter<TInput> : IAsyncObjectToTypeConverter<ServiceBusEntity>
|
||||
where TInput : class
|
||||
{
|
||||
private readonly IAsyncConverter<TInput, ServiceBusEntity> _innerConverter;
|
||||
|
||||
public OutputConverter(IAsyncConverter<TInput, ServiceBusEntity> innerConverter)
|
||||
{
|
||||
_innerConverter = innerConverter;
|
||||
}
|
||||
|
||||
public async Task<ConversionResult<ServiceBusEntity>> TryConvertAsync(object input,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
TInput typedInput = input as TInput;
|
||||
|
||||
if (typedInput == null)
|
||||
{
|
||||
return new ConversionResult<ServiceBusEntity>
|
||||
{
|
||||
Succeeded = false,
|
||||
Result = null
|
||||
};
|
||||
}
|
||||
|
||||
ServiceBusEntity entity = await _innerConverter.ConvertAsync(typedInput, cancellationToken);
|
||||
|
||||
return new ConversionResult<ServiceBusEntity>
|
||||
{
|
||||
Succeeded = true,
|
||||
Result = entity
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using Microsoft.Azure.WebJobs.Host.Bindings;
|
||||
using Microsoft.Azure.WebJobs.Host.Bindings.Path;
|
||||
|
||||
namespace Microsoft.Azure.WebJobs.ServiceBus.Bindings
|
||||
{
|
||||
/// <summary>
|
||||
/// Implementation of <see cref="IBindableServiceBusPath"/> strategy for paths
|
||||
/// containing one or more parameters.
|
||||
/// </summary>
|
||||
internal class ParameterizedServiceBusPath : IBindableServiceBusPath
|
||||
{
|
||||
private readonly BindingTemplate _template;
|
||||
|
||||
public ParameterizedServiceBusPath(BindingTemplate template)
|
||||
{
|
||||
Debug.Assert(template != null, "template must not be null");
|
||||
Debug.Assert(template.ParameterNames.Count() > 0, "template must contain one or more parameters");
|
||||
|
||||
_template = template;
|
||||
}
|
||||
|
||||
public string QueueOrTopicNamePattern
|
||||
{
|
||||
get { return _template.Pattern; }
|
||||
}
|
||||
|
||||
public bool IsBound
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
public IEnumerable<string> ParameterNames
|
||||
{
|
||||
get { return _template.ParameterNames; }
|
||||
}
|
||||
|
||||
public string Bind(IReadOnlyDictionary<string, object> bindingData)
|
||||
{
|
||||
if (bindingData == null)
|
||||
{
|
||||
throw new ArgumentNullException("bindingData");
|
||||
}
|
||||
|
||||
string queueOrTopicName = _template.Bind(bindingData);
|
||||
return queueOrTopicName;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,119 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.WebJobs.Host;
|
||||
using Microsoft.Azure.WebJobs.Host.Bindings;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace Microsoft.Azure.WebJobs.ServiceBus.Bindings
|
||||
{
|
||||
internal class ServiceBusAttributeBindingProvider : IBindingProvider
|
||||
{
|
||||
private static readonly IQueueArgumentBindingProvider InnerProvider =
|
||||
new CompositeArgumentBindingProvider(
|
||||
new MessageSenderArgumentBindingProvider(),
|
||||
new MessageArgumentBindingProvider(),
|
||||
new StringArgumentBindingProvider(),
|
||||
new ByteArrayArgumentBindingProvider(),
|
||||
new UserTypeArgumentBindingProvider(),
|
||||
new CollectorArgumentBindingProvider(),
|
||||
new AsyncCollectorArgumentBindingProvider());
|
||||
|
||||
private readonly INameResolver _nameResolver;
|
||||
private readonly ServiceBusOptions _options;
|
||||
private readonly IConfiguration _configuration;
|
||||
private readonly MessagingProvider _messagingProvider;
|
||||
|
||||
public ServiceBusAttributeBindingProvider(INameResolver nameResolver, ServiceBusOptions options, IConfiguration configuration, MessagingProvider messagingProvider)
|
||||
{
|
||||
if (nameResolver == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(nameResolver));
|
||||
}
|
||||
if (configuration == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(configuration));
|
||||
}
|
||||
if (options == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
if (messagingProvider == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(messagingProvider));
|
||||
}
|
||||
|
||||
_nameResolver = nameResolver;
|
||||
_options = options;
|
||||
_configuration = configuration;
|
||||
_messagingProvider = messagingProvider;
|
||||
}
|
||||
|
||||
public Task<IBinding> TryCreateAsync(BindingProviderContext context)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException("context");
|
||||
}
|
||||
|
||||
ParameterInfo parameter = context.Parameter;
|
||||
var attribute = TypeUtility.GetResolvedAttribute<ServiceBusAttribute>(parameter);
|
||||
|
||||
if (attribute == null)
|
||||
{
|
||||
return Task.FromResult<IBinding>(null);
|
||||
}
|
||||
|
||||
string queueOrTopicName = Resolve(attribute.QueueOrTopicName);
|
||||
IBindableServiceBusPath path = BindableServiceBusPath.Create(queueOrTopicName);
|
||||
ValidateContractCompatibility(path, context.BindingDataContract);
|
||||
|
||||
IArgumentBinding<ServiceBusEntity> argumentBinding = InnerProvider.TryCreate(parameter);
|
||||
if (argumentBinding == null)
|
||||
{
|
||||
throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, "Can't bind ServiceBus to type '{0}'.", parameter.ParameterType));
|
||||
}
|
||||
|
||||
attribute.Connection = Resolve(attribute.Connection);
|
||||
ServiceBusAccount account = new ServiceBusAccount(_options, _configuration, attribute);
|
||||
|
||||
IBinding binding = new ServiceBusBinding(parameter.Name, argumentBinding, account, _options, path, attribute, _messagingProvider);
|
||||
return Task.FromResult<IBinding>(binding);
|
||||
}
|
||||
|
||||
private static void ValidateContractCompatibility(IBindableServiceBusPath path, IReadOnlyDictionary<string, Type> bindingDataContract)
|
||||
{
|
||||
if (path == null)
|
||||
{
|
||||
throw new ArgumentNullException("path");
|
||||
}
|
||||
|
||||
IEnumerable<string> parameterNames = path.ParameterNames;
|
||||
if (parameterNames != null)
|
||||
{
|
||||
foreach (string parameterName in parameterNames)
|
||||
{
|
||||
if (bindingDataContract != null && !bindingDataContract.ContainsKey(parameterName))
|
||||
{
|
||||
throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, "No binding parameter exists for '{0}'.", parameterName));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string Resolve(string queueName)
|
||||
{
|
||||
if (_nameResolver == null)
|
||||
{
|
||||
return queueName;
|
||||
}
|
||||
|
||||
return _nameResolver.ResolveWholeString(queueName);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,101 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.ServiceBus.Core;
|
||||
using Microsoft.Azure.WebJobs.Host.Bindings;
|
||||
using Microsoft.Azure.WebJobs.Host.Converters;
|
||||
using Microsoft.Azure.WebJobs.Host.Protocols;
|
||||
|
||||
namespace Microsoft.Azure.WebJobs.ServiceBus.Bindings
|
||||
{
|
||||
internal class ServiceBusBinding : IBinding
|
||||
{
|
||||
private readonly string _parameterName;
|
||||
private readonly IArgumentBinding<ServiceBusEntity> _argumentBinding;
|
||||
private readonly ServiceBusAccount _account;
|
||||
private readonly IBindableServiceBusPath _path;
|
||||
private readonly IAsyncObjectToTypeConverter<ServiceBusEntity> _converter;
|
||||
private readonly EntityType _entityType;
|
||||
private readonly MessagingProvider _messagingProvider;
|
||||
|
||||
public ServiceBusBinding(string parameterName, IArgumentBinding<ServiceBusEntity> argumentBinding, ServiceBusAccount account, ServiceBusOptions config, IBindableServiceBusPath path, ServiceBusAttribute attr, MessagingProvider messagingProvider)
|
||||
{
|
||||
_parameterName = parameterName;
|
||||
_argumentBinding = argumentBinding;
|
||||
_account = account;
|
||||
_path = path;
|
||||
_entityType = attr.EntityType;
|
||||
_messagingProvider = messagingProvider;
|
||||
_converter = new OutputConverter<string>(new StringToServiceBusEntityConverter(account, _path, _entityType, _messagingProvider));
|
||||
}
|
||||
|
||||
public bool FromAttribute
|
||||
{
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
public async Task<IValueProvider> BindAsync(BindingContext context)
|
||||
{
|
||||
context.CancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
string boundQueueName = _path.Bind(context.BindingData);
|
||||
var messageSender = _messagingProvider.CreateMessageSender(boundQueueName, _account.ConnectionString);
|
||||
|
||||
var entity = new ServiceBusEntity
|
||||
{
|
||||
MessageSender = messageSender,
|
||||
EntityType = _entityType
|
||||
};
|
||||
|
||||
return await BindAsync(entity, context.ValueContext);
|
||||
}
|
||||
|
||||
public async Task<IValueProvider> BindAsync(object value, ValueBindingContext context)
|
||||
{
|
||||
ConversionResult<ServiceBusEntity> conversionResult = await _converter.TryConvertAsync(value, context.CancellationToken);
|
||||
|
||||
if (!conversionResult.Succeeded)
|
||||
{
|
||||
throw new InvalidOperationException("Unable to convert value to ServiceBusEntity.");
|
||||
}
|
||||
|
||||
return await BindAsync(conversionResult.Result, context);
|
||||
}
|
||||
|
||||
public ParameterDescriptor ToParameterDescriptor()
|
||||
{
|
||||
return new ServiceBusParameterDescriptor
|
||||
{
|
||||
Name = _parameterName,
|
||||
QueueOrTopicName = _path.QueueOrTopicNamePattern,
|
||||
DisplayHints = CreateParameterDisplayHints(_path.QueueOrTopicNamePattern, false)
|
||||
};
|
||||
}
|
||||
|
||||
private Task<IValueProvider> BindAsync(ServiceBusEntity value, ValueBindingContext context)
|
||||
{
|
||||
return _argumentBinding.BindAsync(value, context);
|
||||
}
|
||||
|
||||
internal static ParameterDisplayHints CreateParameterDisplayHints(string entityPath, bool isInput)
|
||||
{
|
||||
ParameterDisplayHints descriptor = new ParameterDisplayHints
|
||||
{
|
||||
Description = isInput ?
|
||||
string.Format(CultureInfo.CurrentCulture, "dequeue from '{0}'", entityPath) :
|
||||
string.Format(CultureInfo.CurrentCulture, "enqueue to '{0}'", entityPath),
|
||||
|
||||
Prompt = isInput ?
|
||||
"Enter the queue message body" :
|
||||
"Enter the output entity name",
|
||||
|
||||
DefaultValue = isInput ? null : entityPath
|
||||
};
|
||||
|
||||
return descriptor;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.ServiceBus;
|
||||
using Microsoft.Azure.ServiceBus.Core;
|
||||
|
||||
namespace Microsoft.Azure.WebJobs.ServiceBus.Bindings
|
||||
{
|
||||
internal class ServiceBusEntity
|
||||
{
|
||||
public MessageSender MessageSender { get; set; }
|
||||
|
||||
public EntityType EntityType { get; set; } = EntityType.Queue;
|
||||
|
||||
public Task SendAndCreateEntityIfNotExistsAsync(Message message, Guid functionInstanceId, CancellationToken cancellationToken)
|
||||
{
|
||||
return MessageSender.SendAndCreateEntityIfNotExists(message, functionInstanceId, EntityType, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.Azure.WebJobs.Host.Protocols;
|
||||
|
||||
namespace Microsoft.Azure.WebJobs.ServiceBus.Bindings
|
||||
{
|
||||
internal class ServiceBusParameterDescriptor : ParameterDescriptor
|
||||
{
|
||||
/// <summary>Gets or sets the name of the queue or topic.</summary>
|
||||
public string QueueOrTopicName { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,64 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.WebJobs.Host.Bindings;
|
||||
|
||||
namespace Microsoft.Azure.WebJobs.ServiceBus.Bindings
|
||||
{
|
||||
internal class StringArgumentBindingProvider : IQueueArgumentBindingProvider
|
||||
{
|
||||
public IArgumentBinding<ServiceBusEntity> TryCreate(ParameterInfo parameter)
|
||||
{
|
||||
if (!parameter.IsOut || parameter.ParameterType != typeof(string).MakeByRefType())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new StringArgumentBinding();
|
||||
}
|
||||
|
||||
private class StringArgumentBinding : IArgumentBinding<ServiceBusEntity>
|
||||
{
|
||||
public Type ValueType
|
||||
{
|
||||
get { return typeof(string); }
|
||||
}
|
||||
|
||||
/// <remarks>
|
||||
/// The out string parameter is processed as follows:
|
||||
/// <list type="bullet">
|
||||
/// <item>
|
||||
/// <description>
|
||||
/// If the value is <see langword="null"/>, no message will be sent.
|
||||
/// </description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <description>
|
||||
/// If the value is an empty string, a message with empty content will be sent.
|
||||
/// </description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <description>
|
||||
/// If the value is a non-empty string, a message with that content will be sent.
|
||||
/// </description>
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// </remarks>
|
||||
public Task<IValueProvider> BindAsync(ServiceBusEntity value, ValueBindingContext context)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException("context");
|
||||
}
|
||||
|
||||
IValueProvider provider = new NonNullConverterValueBinder<string>(value,
|
||||
new StringToBrokeredMessageConverter(), context.FunctionInstanceId);
|
||||
|
||||
return Task.FromResult(provider);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using Microsoft.Azure.WebJobs.Host.Converters;
|
||||
using Microsoft.Azure.ServiceBus;
|
||||
|
||||
namespace Microsoft.Azure.WebJobs.ServiceBus.Bindings
|
||||
{
|
||||
internal class StringToBrokeredMessageConverter : IConverter<string, Message>
|
||||
{
|
||||
[SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope")]
|
||||
public Message Convert(string input)
|
||||
{
|
||||
if (input == null)
|
||||
{
|
||||
throw new InvalidOperationException("A brokered message cannot contain a null string instance.");
|
||||
}
|
||||
|
||||
byte[] bytes = StrictEncodings.Utf8.GetBytes(input);
|
||||
|
||||
return new Message(bytes)
|
||||
{
|
||||
ContentType = ContentTypes.TextPlain
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,52 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.ServiceBus.Core;
|
||||
|
||||
namespace Microsoft.Azure.WebJobs.ServiceBus.Bindings
|
||||
{
|
||||
internal class StringToServiceBusEntityConverter : IAsyncConverter<string, ServiceBusEntity>
|
||||
{
|
||||
private readonly ServiceBusAccount _account;
|
||||
private readonly IBindableServiceBusPath _defaultPath;
|
||||
private readonly EntityType _entityType;
|
||||
private readonly MessagingProvider _messagingProvider;
|
||||
|
||||
public StringToServiceBusEntityConverter(ServiceBusAccount account, IBindableServiceBusPath defaultPath, EntityType entityType, MessagingProvider messagingProvider)
|
||||
{
|
||||
_account = account;
|
||||
_defaultPath = defaultPath;
|
||||
_entityType = entityType;
|
||||
_messagingProvider = messagingProvider;
|
||||
}
|
||||
|
||||
public Task<ServiceBusEntity> ConvertAsync(string input, CancellationToken cancellationToken)
|
||||
{
|
||||
string queueOrTopicName;
|
||||
|
||||
// For convenience, treat an an empty string as a request for the default value.
|
||||
if (String.IsNullOrEmpty(input) && _defaultPath.IsBound)
|
||||
{
|
||||
queueOrTopicName = _defaultPath.Bind(null);
|
||||
}
|
||||
else
|
||||
{
|
||||
queueOrTopicName = input;
|
||||
}
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
var messageSender = _messagingProvider.CreateMessageSender(queueOrTopicName, _account.ConnectionString);
|
||||
|
||||
var entity = new ServiceBusEntity
|
||||
{
|
||||
MessageSender = messageSender,
|
||||
EntityType = _entityType
|
||||
};
|
||||
|
||||
return Task.FromResult(entity);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,65 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.WebJobs.Host.Bindings;
|
||||
using Microsoft.Azure.WebJobs.Host.Converters;
|
||||
using Microsoft.Azure.ServiceBus;
|
||||
|
||||
namespace Microsoft.Azure.WebJobs.ServiceBus.Bindings
|
||||
{
|
||||
internal class UserTypeArgumentBindingProvider : IQueueArgumentBindingProvider
|
||||
{
|
||||
public IArgumentBinding<ServiceBusEntity> TryCreate(ParameterInfo parameter)
|
||||
{
|
||||
if (!parameter.IsOut)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
Type itemType = parameter.ParameterType.GetElementType();
|
||||
|
||||
if (typeof(IEnumerable).IsAssignableFrom(itemType))
|
||||
{
|
||||
throw new InvalidOperationException("Enumerable types are not supported. Use ICollector<T> or IAsyncCollector<T> instead.");
|
||||
}
|
||||
else if (typeof(object) == itemType)
|
||||
{
|
||||
throw new InvalidOperationException("Object element types are not supported.");
|
||||
}
|
||||
|
||||
return CreateBinding(itemType);
|
||||
}
|
||||
|
||||
private static IArgumentBinding<ServiceBusEntity> CreateBinding(Type itemType)
|
||||
{
|
||||
Type genericType = typeof(UserTypeArgumentBinding<>).MakeGenericType(itemType);
|
||||
return (IArgumentBinding<ServiceBusEntity>)Activator.CreateInstance(genericType);
|
||||
}
|
||||
|
||||
private class UserTypeArgumentBinding<TInput> : IArgumentBinding<ServiceBusEntity>
|
||||
{
|
||||
public Type ValueType
|
||||
{
|
||||
get { return typeof(TInput); }
|
||||
}
|
||||
|
||||
public Task<IValueProvider> BindAsync(ServiceBusEntity value, ValueBindingContext context)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException("context");
|
||||
}
|
||||
|
||||
IConverter<TInput, Message> converter = new UserTypeToBrokeredMessageConverter<TInput>();
|
||||
IValueProvider provider = new ConverterValueBinder<TInput>(value, converter,
|
||||
context.FunctionInstanceId);
|
||||
|
||||
return Task.FromResult(provider);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using Microsoft.Azure.WebJobs.Host.Converters;
|
||||
using Microsoft.Azure.ServiceBus;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Microsoft.Azure.WebJobs.ServiceBus.Bindings
|
||||
{
|
||||
internal class UserTypeToBrokeredMessageConverter<TInput> : IConverter<TInput, Message>
|
||||
{
|
||||
public Message Convert(TInput input)
|
||||
{
|
||||
string text = JsonConvert.SerializeObject(input, Constants.JsonSerializerSettings);
|
||||
byte[] bytes = StrictEncodings.Utf8.GetBytes(text);
|
||||
|
||||
return new Message(bytes)
|
||||
{
|
||||
ContentType = ContentTypes.ApplicationJson
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,116 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.Azure.ServiceBus;
|
||||
using Microsoft.Azure.WebJobs.Description;
|
||||
using Microsoft.Azure.WebJobs.Host.Config;
|
||||
using Microsoft.Azure.WebJobs.Logging;
|
||||
using Microsoft.Azure.WebJobs.ServiceBus.Bindings;
|
||||
using Microsoft.Azure.WebJobs.ServiceBus.Triggers;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Microsoft.Azure.WebJobs.ServiceBus.Config
|
||||
{
|
||||
/// <summary>
|
||||
/// Extension configuration provider used to register ServiceBus triggers and binders
|
||||
/// </summary>
|
||||
[Extension("ServiceBus")]
|
||||
internal class ServiceBusExtensionConfigProvider : IExtensionConfigProvider
|
||||
{
|
||||
private readonly INameResolver _nameResolver;
|
||||
private readonly IConfiguration _configuration;
|
||||
private readonly ILoggerFactory _loggerFactory;
|
||||
private readonly ServiceBusOptions _options;
|
||||
private readonly MessagingProvider _messagingProvider;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="ServiceBusExtensionConfigProvider"/> instance.
|
||||
/// </summary>
|
||||
/// <param name="options">The <see cref="ServiceBusOptions"></see> to use./></param>
|
||||
public ServiceBusExtensionConfigProvider(IOptions<ServiceBusOptions> options,
|
||||
MessagingProvider messagingProvider,
|
||||
INameResolver nameResolver,
|
||||
IConfiguration configuration,
|
||||
ILoggerFactory loggerFactory)
|
||||
{
|
||||
_options = options.Value;
|
||||
_messagingProvider = messagingProvider;
|
||||
_nameResolver = nameResolver;
|
||||
_configuration = configuration;
|
||||
_loggerFactory = loggerFactory ?? NullLoggerFactory.Instance;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="ServiceBusOptions"/>
|
||||
/// </summary>
|
||||
public ServiceBusOptions Options
|
||||
{
|
||||
get
|
||||
{
|
||||
return _options;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Initialize(ExtensionConfigContext context)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException("context");
|
||||
}
|
||||
|
||||
// Set the default exception handler for background exceptions
|
||||
// coming from MessageReceivers.
|
||||
Options.ExceptionHandler = (e) =>
|
||||
{
|
||||
LogExceptionReceivedEvent(e, _loggerFactory);
|
||||
};
|
||||
|
||||
// register our trigger binding provider
|
||||
ServiceBusTriggerAttributeBindingProvider triggerBindingProvider = new ServiceBusTriggerAttributeBindingProvider(_nameResolver, _options, _messagingProvider, _configuration);
|
||||
context.AddBindingRule<ServiceBusTriggerAttribute>().BindToTrigger(triggerBindingProvider);
|
||||
|
||||
// register our binding provider
|
||||
ServiceBusAttributeBindingProvider bindingProvider = new ServiceBusAttributeBindingProvider(_nameResolver, _options, _configuration, _messagingProvider);
|
||||
context.AddBindingRule<ServiceBusAttribute>().Bind(bindingProvider);
|
||||
}
|
||||
|
||||
internal static void LogExceptionReceivedEvent(ExceptionReceivedEventArgs e, ILoggerFactory loggerFactory)
|
||||
{
|
||||
try
|
||||
{
|
||||
var ctxt = e.ExceptionReceivedContext;
|
||||
var logger = loggerFactory?.CreateLogger(LogCategories.Executor);
|
||||
string message = $"MessageReceiver error (Action={ctxt.Action}, ClientId={ctxt.ClientId}, EntityPath={ctxt.EntityPath}, Endpoint={ctxt.Endpoint})";
|
||||
|
||||
var logLevel = GetLogLevel(e.Exception);
|
||||
logger?.Log(logLevel, 0, message, e.Exception, (s, ex) => message);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// best effort logging
|
||||
}
|
||||
}
|
||||
|
||||
private static LogLevel GetLogLevel(Exception ex)
|
||||
{
|
||||
var sbex = ex as ServiceBusException;
|
||||
if (!(ex is OperationCanceledException) && (sbex == null || !sbex.IsTransient))
|
||||
{
|
||||
// any non-transient exceptions or unknown exception types
|
||||
// we want to log as errors
|
||||
return LogLevel.Error;
|
||||
}
|
||||
else
|
||||
{
|
||||
// transient messaging errors we log as info so we have a record
|
||||
// of them, but we don't treat them as actual errors
|
||||
return LogLevel.Information;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,80 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.ServiceBus;
|
||||
using Microsoft.Azure.WebJobs.Hosting;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Microsoft.Azure.WebJobs.ServiceBus
|
||||
{
|
||||
/// <summary>
|
||||
/// Configuration options for the ServiceBus extension.
|
||||
/// </summary>
|
||||
public class ServiceBusOptions : IOptionsFormatter
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructs a new instance.
|
||||
/// </summary>
|
||||
public ServiceBusOptions()
|
||||
{
|
||||
// Our default options will delegate to our own exception
|
||||
// logger. Customers can override this completely by setting their
|
||||
// own MessageHandlerOptions instance.
|
||||
MessageHandlerOptions = new MessageHandlerOptions(ExceptionReceivedHandler)
|
||||
{
|
||||
MaxConcurrentCalls = 16
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Azure ServiceBus connection string.
|
||||
/// </summary>
|
||||
public string ConnectionString { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the default <see cref="Azure.ServiceBus.MessageHandlerOptions"/> that will be used by
|
||||
/// <see cref="MessageReceiver"/>s.
|
||||
/// </summary>
|
||||
public MessageHandlerOptions MessageHandlerOptions { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the default PrefetchCount that will be used by <see cref="MessageReceiver"/>s.
|
||||
/// </summary>
|
||||
public int PrefetchCount { get; set; }
|
||||
|
||||
internal Action<ExceptionReceivedEventArgs> ExceptionHandler { get; set; }
|
||||
|
||||
public string Format()
|
||||
{
|
||||
JObject messageHandlerOptions = null;
|
||||
if (MessageHandlerOptions != null)
|
||||
{
|
||||
messageHandlerOptions = new JObject
|
||||
{
|
||||
{ nameof(MessageHandlerOptions.AutoComplete), MessageHandlerOptions.AutoComplete },
|
||||
{ nameof(MessageHandlerOptions.MaxAutoRenewDuration), MessageHandlerOptions.MaxAutoRenewDuration },
|
||||
{ nameof(MessageHandlerOptions.MaxConcurrentCalls), MessageHandlerOptions.MaxConcurrentCalls }
|
||||
};
|
||||
}
|
||||
|
||||
// Do not include ConnectionString in loggable options.
|
||||
JObject options = new JObject
|
||||
{
|
||||
{ nameof(PrefetchCount), PrefetchCount },
|
||||
{ nameof(MessageHandlerOptions), messageHandlerOptions }
|
||||
};
|
||||
|
||||
return options.ToString(Formatting.Indented);
|
||||
}
|
||||
|
||||
private Task ExceptionReceivedHandler(ExceptionReceivedEventArgs args)
|
||||
{
|
||||
ExceptionHandler?.Invoke(args);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,56 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.Azure.WebJobs;
|
||||
using Microsoft.Azure.WebJobs.ServiceBus;
|
||||
using Microsoft.Azure.WebJobs.ServiceBus.Config;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
|
||||
namespace Microsoft.Extensions.Hosting
|
||||
{
|
||||
public static class ServiceBusHostBuilderExtensions
|
||||
{
|
||||
public static IWebJobsBuilder AddServiceBus(this IWebJobsBuilder builder)
|
||||
{
|
||||
if (builder == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(builder));
|
||||
}
|
||||
|
||||
builder.AddServiceBus(p => { });
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
public static IWebJobsBuilder AddServiceBus(this IWebJobsBuilder builder, Action<ServiceBusOptions> configure)
|
||||
{
|
||||
if (builder == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(builder));
|
||||
}
|
||||
|
||||
if (configure == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(configure));
|
||||
}
|
||||
|
||||
builder.AddExtension<ServiceBusExtensionConfigProvider>()
|
||||
.ConfigureOptions<ServiceBusOptions>((config, path, options) =>
|
||||
{
|
||||
options.ConnectionString = config.GetConnectionString(Constants.DefaultConnectionStringName);
|
||||
|
||||
IConfigurationSection section = config.GetSection(path);
|
||||
section.Bind(options);
|
||||
|
||||
configure(options);
|
||||
});
|
||||
|
||||
builder.Services.TryAddSingleton<MessagingProvider>();
|
||||
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Microsoft.Azure.WebJobs.ServiceBus
|
||||
{
|
||||
public static class Constants
|
||||
{
|
||||
private static JsonSerializerSettings _serializerSettings = new JsonSerializerSettings
|
||||
{
|
||||
// The default value, DateParseHandling.DateTime, drops time zone information from DateTimeOffets.
|
||||
// This value appears to work well with both DateTimes (without time zone information) and DateTimeOffsets.
|
||||
DateParseHandling = DateParseHandling.DateTimeOffset,
|
||||
NullValueHandling = NullValueHandling.Ignore,
|
||||
Formatting = Formatting.Indented
|
||||
};
|
||||
|
||||
public static JsonSerializerSettings JsonSerializerSettings
|
||||
{
|
||||
get
|
||||
{
|
||||
return _serializerSettings;
|
||||
}
|
||||
}
|
||||
|
||||
public const string DefaultConnectionStringName = "ServiceBus";
|
||||
}
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.Azure.WebJobs.ServiceBus
|
||||
{
|
||||
internal static class ContentTypes
|
||||
{
|
||||
public const string TextPlain = "text/plain";
|
||||
|
||||
public const string ApplicationJson = "application/json";
|
||||
|
||||
public const string ApplicationOctetStream = "application/octet-stream";
|
||||
}
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.Azure.WebJobs.ServiceBus
|
||||
{
|
||||
/// <summary>
|
||||
/// Service Bus entity type.
|
||||
/// </summary>
|
||||
public enum EntityType
|
||||
{
|
||||
/// <summary>
|
||||
/// Service Bus Queue
|
||||
/// </summary>
|
||||
Queue,
|
||||
|
||||
/// <summary>
|
||||
/// Service Bus Topic
|
||||
/// </summary>
|
||||
Topic
|
||||
}
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
[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)")]
|
||||
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Prefetch", Scope = "member", Target = "Microsoft.Azure.WebJobs.ServiceBus.ServiceBusConfiguration.#PrefetchCount")]
|
||||
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable", Scope = "type", Target = "Microsoft.Azure.WebJobs.ServiceBus.EventHubListener+Listenter")]
|
||||
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable", Scope = "type", Target = "Microsoft.Azure.WebJobs.ServiceBus.EventHubListener+Listener")]
|
||||
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1014:MarkAssembliesWithClsCompliant")]
|
|
@ -1,36 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.Azure.ServiceBus;
|
||||
|
||||
namespace Microsoft.Azure.WebJobs.ServiceBus.Listeners
|
||||
{
|
||||
internal static class ServiceBusCausalityHelper
|
||||
{
|
||||
private const string ParentGuidFieldName = "$AzureWebJobsParentId";
|
||||
|
||||
public static void EncodePayload(Guid functionOwner, Message msg)
|
||||
{
|
||||
msg.UserProperties[ParentGuidFieldName] = functionOwner.ToString();
|
||||
}
|
||||
|
||||
public static Guid? GetOwner(Message msg)
|
||||
{
|
||||
object parent;
|
||||
if (msg.UserProperties.TryGetValue(ParentGuidFieldName, out parent))
|
||||
{
|
||||
var parentString = parent as string;
|
||||
if (parentString != null)
|
||||
{
|
||||
Guid parentGuid;
|
||||
if (Guid.TryParse(parentString, out parentGuid))
|
||||
{
|
||||
return parentGuid;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,126 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.ServiceBus;
|
||||
using Microsoft.Azure.ServiceBus.Core;
|
||||
using Microsoft.Azure.WebJobs.Host.Executors;
|
||||
using Microsoft.Azure.WebJobs.Host.Listeners;
|
||||
|
||||
namespace Microsoft.Azure.WebJobs.ServiceBus.Listeners
|
||||
{
|
||||
internal sealed class ServiceBusListener : IListener
|
||||
{
|
||||
private readonly MessagingProvider _messagingProvider;
|
||||
private readonly string _entityPath;
|
||||
private readonly ServiceBusTriggerExecutor _triggerExecutor;
|
||||
private readonly CancellationTokenSource _cancellationTokenSource;
|
||||
private readonly MessageProcessor _messageProcessor;
|
||||
private readonly ServiceBusAccount _serviceBusAccount;
|
||||
|
||||
private MessageReceiver _receiver;
|
||||
private bool _disposed;
|
||||
private bool _started;
|
||||
|
||||
public ServiceBusListener(string entityPath, ServiceBusTriggerExecutor triggerExecutor, ServiceBusOptions config, ServiceBusAccount serviceBusAccount, MessagingProvider messagingProvider)
|
||||
{
|
||||
_entityPath = entityPath;
|
||||
_triggerExecutor = triggerExecutor;
|
||||
_cancellationTokenSource = new CancellationTokenSource();
|
||||
_messagingProvider = messagingProvider;
|
||||
_serviceBusAccount = serviceBusAccount;
|
||||
_messageProcessor = messagingProvider.CreateMessageProcessor(entityPath, _serviceBusAccount.ConnectionString);
|
||||
}
|
||||
|
||||
internal MessageReceiver Receiver => _receiver;
|
||||
|
||||
public Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
if (_started)
|
||||
{
|
||||
throw new InvalidOperationException("The listener has already been started.");
|
||||
}
|
||||
|
||||
_receiver = _messagingProvider.CreateMessageReceiver(_entityPath, _serviceBusAccount.ConnectionString);
|
||||
_receiver.RegisterMessageHandler(ProcessMessageAsync, _messageProcessor.MessageOptions);
|
||||
_started = true;
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
if (!_started)
|
||||
{
|
||||
throw new InvalidOperationException("The listener has not yet been started or has already been stopped.");
|
||||
}
|
||||
|
||||
// cancel our token source to signal any in progress
|
||||
// ProcessMessageAsync invocations to cancel
|
||||
_cancellationTokenSource.Cancel();
|
||||
|
||||
await _receiver.CloseAsync();
|
||||
_receiver = null;
|
||||
_started = false;
|
||||
}
|
||||
|
||||
public void Cancel()
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
_cancellationTokenSource.Cancel();
|
||||
}
|
||||
|
||||
[SuppressMessage("Microsoft.Usage", "CA2213:DisposableFieldsShouldBeDisposed", MessageId = "_cancellationTokenSource")]
|
||||
public void Dispose()
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
// Running callers might still be using the cancellation token.
|
||||
// Mark it canceled but don't dispose of the source while the callers are running.
|
||||
// Otherwise, callers would receive ObjectDisposedException when calling token.Register.
|
||||
// For now, rely on finalization to clean up _cancellationTokenSource's wait handle (if allocated).
|
||||
_cancellationTokenSource.Cancel();
|
||||
|
||||
if (_receiver != null)
|
||||
{
|
||||
_receiver.CloseAsync().Wait();
|
||||
_receiver = null;
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
private Task ProcessMessageAsync(Message message)
|
||||
{
|
||||
return ProcessMessageAsync(message, _cancellationTokenSource.Token);
|
||||
}
|
||||
|
||||
internal async Task ProcessMessageAsync(Message message, CancellationToken cancellationToken)
|
||||
{
|
||||
if (!await _messageProcessor.BeginProcessingMessageAsync(message, cancellationToken))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
FunctionResult result = await _triggerExecutor.ExecuteAsync(message, cancellationToken);
|
||||
|
||||
await _messageProcessor.CompleteProcessingMessageAsync(message, result, cancellationToken);
|
||||
}
|
||||
|
||||
private void ThrowIfDisposed()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
throw new ObjectDisposedException(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.WebJobs.Host.Executors;
|
||||
using Microsoft.Azure.WebJobs.Host.Listeners;
|
||||
|
||||
namespace Microsoft.Azure.WebJobs.ServiceBus.Listeners
|
||||
{
|
||||
internal class ServiceBusQueueListenerFactory : IListenerFactory
|
||||
{
|
||||
private readonly ServiceBusAccount _account;
|
||||
private readonly string _queueName;
|
||||
private readonly ITriggeredFunctionExecutor _executor;
|
||||
private readonly ServiceBusOptions _options;
|
||||
private readonly MessagingProvider _messagingProvider;
|
||||
|
||||
public ServiceBusQueueListenerFactory(ServiceBusAccount account, string queueName, ITriggeredFunctionExecutor executor, ServiceBusOptions options, MessagingProvider messagingProvider)
|
||||
{
|
||||
_account = account;
|
||||
_queueName = queueName;
|
||||
_executor = executor;
|
||||
_options = options;
|
||||
_messagingProvider = messagingProvider;
|
||||
}
|
||||
|
||||
public Task<IListener> CreateAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var triggerExecutor = new ServiceBusTriggerExecutor(_executor);
|
||||
var listener = new ServiceBusListener(_queueName, triggerExecutor, _options, _account, _messagingProvider);
|
||||
|
||||
return Task.FromResult<IListener>(listener);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.ServiceBus;
|
||||
using Microsoft.Azure.WebJobs.Host.Executors;
|
||||
using Microsoft.Azure.WebJobs.Host.Listeners;
|
||||
|
||||
namespace Microsoft.Azure.WebJobs.ServiceBus.Listeners
|
||||
{
|
||||
internal class ServiceBusSubscriptionListenerFactory : IListenerFactory
|
||||
{
|
||||
private readonly ServiceBusAccount _account;
|
||||
private readonly string _topicName;
|
||||
private readonly string _subscriptionName;
|
||||
private readonly ITriggeredFunctionExecutor _executor;
|
||||
private readonly ServiceBusOptions _options;
|
||||
private readonly MessagingProvider _messagingProvider;
|
||||
|
||||
public ServiceBusSubscriptionListenerFactory(ServiceBusAccount account, string topicName, string subscriptionName, ITriggeredFunctionExecutor executor, ServiceBusOptions options, MessagingProvider messagingProvider)
|
||||
{
|
||||
_account = account;
|
||||
_topicName = topicName;
|
||||
_subscriptionName = subscriptionName;
|
||||
_executor = executor;
|
||||
_options = options;
|
||||
_messagingProvider = messagingProvider;
|
||||
}
|
||||
|
||||
public Task<IListener> CreateAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
string entityPath = EntityNameHelper.FormatSubscriptionPath(_topicName, _subscriptionName);
|
||||
|
||||
ServiceBusTriggerExecutor triggerExecutor = new ServiceBusTriggerExecutor(_executor);
|
||||
var listener = new ServiceBusListener(entityPath, triggerExecutor, _options, _account, _messagingProvider);
|
||||
|
||||
return Task.FromResult<IListener>(listener);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.WebJobs.Host.Executors;
|
||||
using Microsoft.Azure.ServiceBus;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.Azure.WebJobs.ServiceBus.Listeners
|
||||
{
|
||||
internal class ServiceBusTriggerExecutor
|
||||
{
|
||||
private readonly ITriggeredFunctionExecutor _innerExecutor;
|
||||
|
||||
public ServiceBusTriggerExecutor(ITriggeredFunctionExecutor innerExecutor)
|
||||
{
|
||||
_innerExecutor = innerExecutor;
|
||||
}
|
||||
|
||||
public async Task<FunctionResult> ExecuteAsync(Message value, CancellationToken cancellationToken)
|
||||
{
|
||||
Guid? parentId = ServiceBusCausalityHelper.GetOwner(value);
|
||||
TriggeredFunctionData input = new TriggeredFunctionData
|
||||
{
|
||||
ParentId = parentId,
|
||||
TriggerValue = value,
|
||||
TriggerDetails = PopulateTriggerDetails(value)
|
||||
};
|
||||
return await _innerExecutor.TryExecuteAsync(input, cancellationToken);
|
||||
}
|
||||
|
||||
private Dictionary<string, string> PopulateTriggerDetails(Message value)
|
||||
{
|
||||
return new Dictionary<string, string>()
|
||||
{
|
||||
{ "MessageId", value.MessageId },
|
||||
{ "DeliveryCount", value.SystemProperties.DeliveryCount.ToString() },
|
||||
{ "EnqueuedTime", value.SystemProperties.EnqueuedTimeUtc.ToString() },
|
||||
{ "LockedUntil", value.SystemProperties.LockedUntilUtc.ToString() }
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,87 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.WebJobs.Host.Executors;
|
||||
using Microsoft.Azure.ServiceBus;
|
||||
using Microsoft.Azure.ServiceBus.Core;
|
||||
|
||||
namespace Microsoft.Azure.WebJobs.ServiceBus
|
||||
{
|
||||
/// <summary>
|
||||
/// This class defines a strategy used for processing ServiceBus messages.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Custom <see cref="MessageProcessor"/> implementations can be specified by implementing
|
||||
/// a custom <see cref="MessagingProvider"/> and setting it via <see cref="ServiceBusOptions.MessagingProvider"/>.
|
||||
/// </remarks>
|
||||
public class MessageProcessor
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructs a new instance.
|
||||
/// </summary>
|
||||
/// <param name="messageReceiver">The <see cref="MessageReceiver"/>.</param>
|
||||
/// <param name="messageOptions">The <see cref="MessageHandlerOptions"/> to use.</param>
|
||||
public MessageProcessor(MessageReceiver messageReceiver, MessageHandlerOptions messageOptions)
|
||||
{
|
||||
MessageReceiver = messageReceiver ?? throw new ArgumentNullException(nameof(messageReceiver));
|
||||
MessageOptions = messageOptions ?? throw new ArgumentNullException(nameof(messageOptions));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="MessageHandlerOptions"/> that will be used by the <see cref="MessageReceiver"/>.
|
||||
/// </summary>
|
||||
public MessageHandlerOptions MessageOptions { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="MessageReceiver"/> that will be used by the <see cref="MessageReceiver"/>.
|
||||
/// </summary>
|
||||
protected MessageReceiver MessageReceiver { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// This method is called when there is a new message to process, before the job function is invoked.
|
||||
/// This allows any preprocessing to take place on the message before processing begins.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to process.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to use.</param>
|
||||
/// <returns>A <see cref="Task"/> that returns true if the message processing should continue, false otherwise.</returns>
|
||||
public virtual async Task<bool> BeginProcessingMessageAsync(Message message, CancellationToken cancellationToken)
|
||||
{
|
||||
return await Task.FromResult<bool>(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method completes processing of the specified message, after the job function has been invoked.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The message is completed by the ServiceBus SDK based on how the <see cref="MessageHandlerOptions.AutoComplete"/> option
|
||||
/// is configured. E.g. if <see cref="MessageHandlerOptions.AutoComplete"/> is false, it is up to the job function to complete
|
||||
/// the message.
|
||||
/// </remarks>
|
||||
/// <param name="message">The message to complete processing for.</param>
|
||||
/// <param name="result">The <see cref="FunctionResult"/> from the job invocation.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to use</param>
|
||||
/// <returns>A <see cref="Task"/> that will complete the message processing.</returns>
|
||||
public virtual Task CompleteProcessingMessageAsync(Message message, FunctionResult result, CancellationToken cancellationToken)
|
||||
{
|
||||
if (result == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(result));
|
||||
}
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
if (!result.Succeeded)
|
||||
{
|
||||
// if the invocation failed, we must propagate the
|
||||
// exception back to SB so it can handle message state
|
||||
// correctly
|
||||
throw result.Exception;
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,112 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using Microsoft.Azure.ServiceBus.Core;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Microsoft.Azure.WebJobs.ServiceBus
|
||||
{
|
||||
/// <summary>
|
||||
/// This class provides factory methods for the creation of instances
|
||||
/// used for ServiceBus message processing.
|
||||
/// </summary>
|
||||
public class MessagingProvider
|
||||
{
|
||||
private readonly ServiceBusOptions _options;
|
||||
private readonly ConcurrentDictionary<string, MessageReceiver> _messageReceiverCache = new ConcurrentDictionary<string, MessageReceiver>();
|
||||
private readonly ConcurrentDictionary<string, MessageSender> _messageSenderCache = new ConcurrentDictionary<string, MessageSender>();
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new instance.
|
||||
/// </summary>
|
||||
/// <param name="serviceBusOptions">The <see cref="ServiceBusOptions"/>.</param>
|
||||
public MessagingProvider(IOptions<ServiceBusOptions> serviceBusOptions)
|
||||
{
|
||||
_options = serviceBusOptions?.Value ?? throw new ArgumentNullException(nameof(serviceBusOptions));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="MessageProcessor"/> for the specified ServiceBus entity.
|
||||
/// </summary>
|
||||
/// <param name="entityPath">The ServiceBus entity to create a <see cref="MessageProcessor"/> for.</param>
|
||||
/// <param name="connectionString">The ServiceBus connection string.</param>
|
||||
/// <returns>The <see cref="MessageProcessor"/>.</returns>
|
||||
public virtual MessageProcessor CreateMessageProcessor(string entityPath, string connectionString)
|
||||
{
|
||||
if (string.IsNullOrEmpty(entityPath))
|
||||
{
|
||||
throw new ArgumentNullException("entityPath");
|
||||
}
|
||||
if (string.IsNullOrEmpty(connectionString))
|
||||
{
|
||||
throw new ArgumentNullException("connectionString");
|
||||
}
|
||||
|
||||
return new MessageProcessor(GetOrAddMessageReceiver(entityPath, connectionString), _options.MessageHandlerOptions);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="MessageReceiver"/> for the specified ServiceBus entity.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// You can override this method to customize the <see cref="MessageReceiver"/>.
|
||||
/// </remarks>
|
||||
/// <param name="entityPath">The ServiceBus entity to create a <see cref="MessageReceiver"/> for.</param>
|
||||
/// <param name="connectionString">The ServiceBus connection string.</param>
|
||||
/// <returns></returns>
|
||||
public virtual MessageReceiver CreateMessageReceiver(string entityPath, string connectionString)
|
||||
{
|
||||
if (string.IsNullOrEmpty(entityPath))
|
||||
{
|
||||
throw new ArgumentNullException("entityPath");
|
||||
}
|
||||
if (string.IsNullOrEmpty(connectionString))
|
||||
{
|
||||
throw new ArgumentNullException("connectionString");
|
||||
}
|
||||
|
||||
return GetOrAddMessageReceiver(entityPath, connectionString);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="MessageSender"/> for the specified ServiceBus entity.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// You can override this method to customize the <see cref="MessageSender"/>.
|
||||
/// </remarks>
|
||||
/// <param name="entityPath">The ServiceBus entity to create a <see cref="MessageSender"/> for.</param>
|
||||
/// <param name="connectionString">The ServiceBus connection string.</param>
|
||||
/// <returns></returns>
|
||||
public virtual MessageSender CreateMessageSender(string entityPath, string connectionString)
|
||||
{
|
||||
if (string.IsNullOrEmpty(entityPath))
|
||||
{
|
||||
throw new ArgumentNullException("entityPath");
|
||||
}
|
||||
if (string.IsNullOrEmpty(connectionString))
|
||||
{
|
||||
throw new ArgumentNullException("connectionString");
|
||||
}
|
||||
|
||||
return GetOrAddMessageSender(entityPath, connectionString);
|
||||
}
|
||||
|
||||
private MessageReceiver GetOrAddMessageReceiver(string entityPath, string connectionString)
|
||||
{
|
||||
string cacheKey = $"{entityPath}-{connectionString}";
|
||||
return _messageReceiverCache.GetOrAdd(cacheKey,
|
||||
new MessageReceiver(connectionString, entityPath)
|
||||
{
|
||||
PrefetchCount = _options.PrefetchCount
|
||||
});
|
||||
}
|
||||
|
||||
private MessageSender GetOrAddMessageSender(string entityPath, string connectionString)
|
||||
{
|
||||
string cacheKey = $"{entityPath}-{connectionString}";
|
||||
return _messageSenderCache.GetOrAdd(cacheKey, new MessageSender(connectionString, entityPath));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("Microsoft.Azure.WebJobs.ServiceBus.UnitTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")]
|
||||
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")]
|
|
@ -1,54 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using Microsoft.Azure.WebJobs.Logging;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace Microsoft.Azure.WebJobs.ServiceBus
|
||||
{
|
||||
internal class ServiceBusAccount
|
||||
{
|
||||
private readonly ServiceBusOptions _options;
|
||||
private readonly IConnectionProvider _connectionProvider;
|
||||
private readonly IConfiguration _configuration;
|
||||
private string _connectionString;
|
||||
|
||||
public ServiceBusAccount(ServiceBusOptions options, IConfiguration configuration, IConnectionProvider connectionProvider = null)
|
||||
{
|
||||
_options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
_configuration = configuration;
|
||||
_connectionProvider = connectionProvider;
|
||||
}
|
||||
|
||||
internal ServiceBusAccount()
|
||||
{
|
||||
}
|
||||
|
||||
public virtual string ConnectionString
|
||||
{
|
||||
get
|
||||
{
|
||||
if (string.IsNullOrEmpty(_connectionString))
|
||||
{
|
||||
_connectionString = _options.ConnectionString;
|
||||
if (_connectionProvider != null && !string.IsNullOrEmpty(_connectionProvider.Connection))
|
||||
{
|
||||
_connectionString = _configuration.GetWebJobsConnectionString(_connectionProvider.Connection);
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(_connectionString))
|
||||
{
|
||||
var defaultConnectionName = "AzureWebJobsServiceBus";
|
||||
throw new InvalidOperationException(
|
||||
string.Format(CultureInfo.InvariantCulture, "Microsoft Azure WebJobs SDK ServiceBus connection string '{0}' is missing or empty.",
|
||||
Sanitizer.Sanitize(_connectionProvider.Connection) ?? defaultConnectionName));
|
||||
}
|
||||
}
|
||||
|
||||
return _connectionString;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.Azure.WebJobs
|
||||
{
|
||||
/// <summary>
|
||||
/// Attribute used to override the default ServiceBus account used by triggers and binders.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This attribute can be applied at the parameter/method/class level, and the precedence
|
||||
/// is in that order.
|
||||
/// </remarks>
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method | AttributeTargets.Parameter)]
|
||||
public sealed class ServiceBusAccountAttribute : Attribute, IConnectionProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructs a new instance.
|
||||
/// </summary>
|
||||
/// <param name="account">A string value indicating the Service Bus connection string to use. This
|
||||
/// string should be in one of the following formats. These checks will be applied in order and the
|
||||
/// first match wins.
|
||||
/// - The name of an "AzureWebJobs" prefixed app setting or connection string name. E.g., if your setting
|
||||
/// name is "AzureWebJobsMyServiceBus", you can specify "MyServiceBus" here.
|
||||
/// - Can be a string containing %% values (e.g. %StagingServiceBus%). The value provided will be passed
|
||||
/// to any INameResolver registered on the JobHostConfiguration to resolve the actual setting name to use.
|
||||
/// - Can be an app setting or connection string name of your choosing.
|
||||
/// </param>
|
||||
public ServiceBusAccountAttribute(string account)
|
||||
{
|
||||
Account = account;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the app setting that contains the Service Bus connection string.
|
||||
/// </summary>
|
||||
public string Account { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the app setting name that contains the Service Bus connection string.
|
||||
/// </summary>
|
||||
string IConnectionProvider.Connection
|
||||
{
|
||||
get
|
||||
{
|
||||
return Account;
|
||||
}
|
||||
set
|
||||
{
|
||||
Account = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,70 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using Microsoft.Azure.WebJobs.Description;
|
||||
using Microsoft.Azure.WebJobs.ServiceBus;
|
||||
using Microsoft.Azure.ServiceBus;
|
||||
|
||||
namespace Microsoft.Azure.WebJobs
|
||||
{
|
||||
/// <summary>
|
||||
/// Attribute used to bind a parameter to Azure ServiceBus Queues and Topics.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The method parameter type can be one of the following:
|
||||
/// <list type="bullet">
|
||||
/// <item><description>BrokeredMessage (out parameter)</description></item>
|
||||
/// <item><description><see cref="string"/> (out parameter)</description></item>
|
||||
/// <item><description><see cref="T:byte[]"/> (out parameter)</description></item>
|
||||
/// <item><description>A user-defined type (out parameter, serialized as JSON)</description></item>
|
||||
/// <item><description>
|
||||
/// <see cref="ICollection{T}"/> of these types (to enqueue multiple messages via <see cref="ICollection{T}.Add"/>
|
||||
/// </description></item>
|
||||
/// </list>
|
||||
/// </remarks>
|
||||
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.ReturnValue)]
|
||||
[DebuggerDisplay("{QueueOrTopicName,nq}")]
|
||||
[ConnectionProvider(typeof(ServiceBusAccountAttribute))]
|
||||
[Binding]
|
||||
public sealed class ServiceBusAttribute : Attribute, IConnectionProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ServiceBusAttribute"/> class.
|
||||
/// </summary>
|
||||
/// <param name="queueOrTopicName">The name of the queue or topic to bind to.</param>
|
||||
public ServiceBusAttribute(string queueOrTopicName)
|
||||
{
|
||||
QueueOrTopicName = queueOrTopicName;
|
||||
EntityType = EntityType.Queue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ServiceBusAttribute"/> class.
|
||||
/// </summary>
|
||||
/// <param name="queueOrTopicName">The name of the queue or topic to bind to.</param>
|
||||
/// <param name="queueOrTopicName">The type of the entity to bind to.</param>
|
||||
public ServiceBusAttribute(string queueOrTopicName, EntityType entityType)
|
||||
{
|
||||
QueueOrTopicName = queueOrTopicName;
|
||||
EntityType = entityType;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the queue or topic to bind to.
|
||||
/// </summary>
|
||||
public string QueueOrTopicName { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the app setting name that contains the Service Bus connection string.
|
||||
/// </summary>
|
||||
public string Connection { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Value indicating the type of the entity to bind to.
|
||||
/// </summary>
|
||||
public EntityType EntityType { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,101 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using Microsoft.Azure.WebJobs.Description;
|
||||
using Microsoft.Azure.ServiceBus;
|
||||
|
||||
namespace Microsoft.Azure.WebJobs
|
||||
{
|
||||
/// <summary>
|
||||
/// Attribute used to bind a parameter to a ServiceBus Queue message, causing the function to run when a
|
||||
/// message is enqueued.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The method parameter type can be one of the following:
|
||||
/// <list type="bullet">
|
||||
/// <item><description>BrokeredMessage</description></item>
|
||||
/// <item><description><see cref="string"/></description></item>
|
||||
/// <item><description><see cref="T:byte[]"/></description></item>
|
||||
/// <item><description>A user-defined type (serialized as JSON)</description></item>
|
||||
/// </list>
|
||||
/// </remarks>
|
||||
[AttributeUsage(AttributeTargets.Parameter)]
|
||||
[DebuggerDisplay("{DebuggerDisplay,nq}")]
|
||||
[ConnectionProvider(typeof(ServiceBusAccountAttribute))]
|
||||
[Binding]
|
||||
public sealed class ServiceBusTriggerAttribute : Attribute, IConnectionProvider
|
||||
{
|
||||
private readonly string _queueName;
|
||||
private readonly string _topicName;
|
||||
private readonly string _subscriptionName;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ServiceBusTriggerAttribute"/> class.
|
||||
/// </summary>
|
||||
/// <param name="queueName">The name of the queue to which to bind.</param>
|
||||
public ServiceBusTriggerAttribute(string queueName)
|
||||
{
|
||||
_queueName = queueName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ServiceBusTriggerAttribute"/> class.
|
||||
/// </summary>
|
||||
/// <param name="topicName">The name of the topic to bind to.</param>
|
||||
/// <param name="subscriptionName">The name of the subscription in <paramref name="topicName"/> to bind to.</param>
|
||||
public ServiceBusTriggerAttribute(string topicName, string subscriptionName)
|
||||
{
|
||||
_topicName = topicName;
|
||||
_subscriptionName = subscriptionName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the app setting name that contains the Service Bus connection string.
|
||||
/// </summary>
|
||||
public string Connection { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the queue to which to bind.
|
||||
/// </summary>
|
||||
/// <remarks>When binding to a subscription in a topic, returns <see langword="null"/>.</remarks>
|
||||
public string QueueName
|
||||
{
|
||||
get { return _queueName; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the topic to which to bind.
|
||||
/// </summary>
|
||||
/// <remarks>When binding to a queue, returns <see langword="null"/>.</remarks>
|
||||
public string TopicName
|
||||
{
|
||||
get { return _topicName; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the subscription in <see cref="TopicName"/> to bind to.
|
||||
/// </summary>
|
||||
/// <remarks>When binding to a queue, returns <see langword="null"/>.</remarks>
|
||||
public string SubscriptionName
|
||||
{
|
||||
get { return _subscriptionName; }
|
||||
}
|
||||
|
||||
private string DebuggerDisplay
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_queueName != null)
|
||||
{
|
||||
return _queueName;
|
||||
}
|
||||
else
|
||||
{
|
||||
return _topicName + "/" + _subscriptionName;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.Azure.WebJobs.ServiceBus;
|
||||
using Microsoft.Azure.WebJobs.Hosting;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
[assembly: WebJobsStartup(typeof(ServiceBusWebJobsStartup))]
|
||||
|
||||
namespace Microsoft.Azure.WebJobs.ServiceBus
|
||||
{
|
||||
public class ServiceBusWebJobsStartup : IWebJobsStartup
|
||||
{
|
||||
public void Configure(IWebJobsBuilder builder)
|
||||
{
|
||||
builder.AddServiceBus();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.Azure.WebJobs.ServiceBus
|
||||
{
|
||||
internal static class StrictEncodings
|
||||
{
|
||||
private static UTF8Encoding _utf8 = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false,
|
||||
throwOnInvalidBytes: true);
|
||||
|
||||
public static UTF8Encoding Utf8
|
||||
{
|
||||
get { return _utf8; }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using Microsoft.Azure.WebJobs.Host.Bindings;
|
||||
using Microsoft.Azure.WebJobs.Host.Triggers;
|
||||
using Microsoft.Azure.ServiceBus;
|
||||
|
||||
namespace Microsoft.Azure.WebJobs.ServiceBus.Triggers
|
||||
{
|
||||
internal class CompositeArgumentBindingProvider : IQueueTriggerArgumentBindingProvider
|
||||
{
|
||||
private readonly IEnumerable<IQueueTriggerArgumentBindingProvider> _providers;
|
||||
|
||||
public CompositeArgumentBindingProvider(params IQueueTriggerArgumentBindingProvider[] providers)
|
||||
{
|
||||
_providers = providers;
|
||||
}
|
||||
|
||||
public ITriggerDataArgumentBinding<Message> TryCreate(ParameterInfo parameter)
|
||||
{
|
||||
foreach (IQueueTriggerArgumentBindingProvider provider in _providers)
|
||||
{
|
||||
ITriggerDataArgumentBinding<Message> binding = provider.TryCreate(parameter);
|
||||
|
||||
if (binding != null)
|
||||
{
|
||||
return binding;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,63 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.WebJobs.Host.Bindings;
|
||||
using Microsoft.Azure.WebJobs.Host.Converters;
|
||||
using Microsoft.Azure.WebJobs.Host.Triggers;
|
||||
using Microsoft.Azure.ServiceBus;
|
||||
|
||||
namespace Microsoft.Azure.WebJobs.ServiceBus.Triggers
|
||||
{
|
||||
internal class ConverterArgumentBindingProvider<T> : IQueueTriggerArgumentBindingProvider
|
||||
{
|
||||
private readonly IAsyncConverter<Message, T> _converter;
|
||||
|
||||
public ConverterArgumentBindingProvider(IAsyncConverter<Message, T> converter)
|
||||
{
|
||||
_converter = converter;
|
||||
}
|
||||
|
||||
public ITriggerDataArgumentBinding<Message> TryCreate(ParameterInfo parameter)
|
||||
{
|
||||
if (parameter.ParameterType != typeof(T))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new ConverterArgumentBinding(_converter);
|
||||
}
|
||||
|
||||
internal class ConverterArgumentBinding : ITriggerDataArgumentBinding<Message>
|
||||
{
|
||||
private readonly IAsyncConverter<Message, T> _converter;
|
||||
|
||||
public ConverterArgumentBinding(IAsyncConverter<Message, T> converter)
|
||||
{
|
||||
_converter = converter;
|
||||
}
|
||||
|
||||
public Type ValueType
|
||||
{
|
||||
get { return typeof(T); }
|
||||
}
|
||||
|
||||
public IReadOnlyDictionary<string, Type> BindingDataContract
|
||||
{
|
||||
get { return null; }
|
||||
}
|
||||
|
||||
public async Task<ITriggerData> BindAsync(Message value, ValueBindingContext context)
|
||||
{
|
||||
Message clone = value.Clone();
|
||||
object converted = await _converter.ConvertAsync(value, context.CancellationToken);
|
||||
IValueProvider provider = await MessageValueProvider.CreateAsync(clone, converted, typeof(T),
|
||||
context.CancellationToken);
|
||||
return new TriggerData(provider, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
using System.Reflection;
|
||||
using Microsoft.Azure.WebJobs.Host.Triggers;
|
||||
using Microsoft.Azure.ServiceBus;
|
||||
|
||||
namespace Microsoft.Azure.WebJobs.ServiceBus.Triggers
|
||||
{
|
||||
internal interface IQueueTriggerArgumentBindingProvider
|
||||
{
|
||||
ITriggerDataArgumentBinding<Message> TryCreate(ParameterInfo parameter);
|
||||
}
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.WebJobs.Host.Converters;
|
||||
using Microsoft.Azure.ServiceBus;
|
||||
|
||||
namespace Microsoft.Azure.WebJobs.ServiceBus.Triggers
|
||||
{
|
||||
internal class MessageToByteArrayConverter : IAsyncConverter<Message, byte[]>
|
||||
{
|
||||
public Task<byte[]> ConvertAsync(Message input, CancellationToken cancellationToken)
|
||||
{
|
||||
if (input == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(input));
|
||||
}
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
return Task.FromResult(input.Body);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,72 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.ServiceBus;
|
||||
using Microsoft.Azure.ServiceBus.InteropExtensions;
|
||||
|
||||
namespace Microsoft.Azure.WebJobs.ServiceBus.Triggers
|
||||
{
|
||||
internal class MessageToStringConverter : IAsyncConverter<Message, string>
|
||||
{
|
||||
public async Task<string> ConvertAsync(Message input, CancellationToken cancellationToken)
|
||||
{
|
||||
if (input == null)
|
||||
{
|
||||
throw new ArgumentNullException("input");
|
||||
}
|
||||
if (input.Body == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
Stream stream = new MemoryStream(input.Body);
|
||||
|
||||
TextReader reader = new StreamReader(stream, StrictEncodings.Utf8);
|
||||
try
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
try
|
||||
{
|
||||
return await reader.ReadToEndAsync();
|
||||
}
|
||||
catch (DecoderFallbackException)
|
||||
{
|
||||
// we'll try again below
|
||||
}
|
||||
|
||||
// We may get here if the message is a string yet was DataContract-serialized when created. We'll
|
||||
// try to deserialize it here using GetBody<string>(). This may fail as well, in which case we'll
|
||||
// provide a decent error.
|
||||
|
||||
try
|
||||
{
|
||||
return input.GetBody<string>();
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
string contentType = input.ContentType ?? "null";
|
||||
string msg = string.Format(CultureInfo.InvariantCulture, "The Message with ContentType '{0}' failed to deserialize to a string with the message: '{1}'",
|
||||
contentType, exception.Message);
|
||||
|
||||
throw new InvalidOperationException(msg, exception);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (stream != null)
|
||||
{
|
||||
stream.Dispose();
|
||||
}
|
||||
if (reader != null)
|
||||
{
|
||||
reader.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,98 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.ServiceBus;
|
||||
using Microsoft.Azure.WebJobs.Host.Bindings;
|
||||
|
||||
namespace Microsoft.Azure.WebJobs.ServiceBus.Triggers
|
||||
{
|
||||
internal class MessageValueProvider : IValueProvider
|
||||
{
|
||||
private readonly object _value;
|
||||
private readonly Type _valueType;
|
||||
private readonly string _invokeString;
|
||||
|
||||
private MessageValueProvider(object value, Type valueType, string invokeString)
|
||||
{
|
||||
if (value != null && !valueType.IsAssignableFrom(value.GetType()))
|
||||
{
|
||||
throw new InvalidOperationException("value is not of the correct type.");
|
||||
}
|
||||
|
||||
_value = value;
|
||||
_valueType = valueType;
|
||||
_invokeString = invokeString;
|
||||
}
|
||||
|
||||
public Type Type
|
||||
{
|
||||
get { return _valueType; }
|
||||
}
|
||||
|
||||
public Task<object> GetValueAsync()
|
||||
{
|
||||
return Task.FromResult(_value);
|
||||
}
|
||||
|
||||
public string ToInvokeString()
|
||||
{
|
||||
return _invokeString;
|
||||
}
|
||||
|
||||
public static async Task<MessageValueProvider> CreateAsync(Message clone, object value,
|
||||
Type valueType, CancellationToken cancellationToken)
|
||||
{
|
||||
string invokeString = await CreateInvokeStringAsync(clone, cancellationToken);
|
||||
return new MessageValueProvider(value, valueType, invokeString);
|
||||
}
|
||||
|
||||
private static Task<string> CreateInvokeStringAsync(Message clonedMessage,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
switch (clonedMessage.ContentType)
|
||||
{
|
||||
case ContentTypes.ApplicationJson:
|
||||
case ContentTypes.TextPlain:
|
||||
return GetTextAsync(clonedMessage, cancellationToken);
|
||||
case ContentTypes.ApplicationOctetStream:
|
||||
return GetBase64StringAsync(clonedMessage, cancellationToken);
|
||||
default:
|
||||
return GetDescriptionAsync(clonedMessage);
|
||||
}
|
||||
}
|
||||
|
||||
private static Task<string> GetBase64StringAsync(Message clonedMessage,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (clonedMessage.Body == null)
|
||||
{
|
||||
return Task.FromResult((string)null);
|
||||
}
|
||||
string converted = Convert.ToBase64String(clonedMessage.Body);
|
||||
return Task.FromResult(converted);
|
||||
}
|
||||
|
||||
private static Task<string> GetDescriptionAsync(Message clonedMessage)
|
||||
{
|
||||
string description = clonedMessage.Body != null
|
||||
? string.Format(CultureInfo.InvariantCulture, "byte[{0}]", clonedMessage.Body.Length) : "null";
|
||||
|
||||
return Task.FromResult(description);
|
||||
}
|
||||
|
||||
private static Task<string> GetTextAsync(Message clonedMessage,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (clonedMessage.Body == null)
|
||||
{
|
||||
return Task.FromResult((string)null);
|
||||
}
|
||||
string s = StrictEncodings.Utf8.GetString(clonedMessage.Body);
|
||||
return Task.FromResult(s);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.Azure.WebJobs.Host.Converters;
|
||||
using Microsoft.Azure.ServiceBus;
|
||||
|
||||
namespace Microsoft.Azure.WebJobs.ServiceBus.Triggers
|
||||
{
|
||||
internal class OutputConverter<TInput> : IObjectToTypeConverter<Message>
|
||||
where TInput : class
|
||||
{
|
||||
private readonly IConverter<TInput, Message> _innerConverter;
|
||||
|
||||
public OutputConverter(IConverter<TInput, Message> innerConverter)
|
||||
{
|
||||
_innerConverter = innerConverter;
|
||||
}
|
||||
|
||||
public bool TryConvert(object input, out Message output)
|
||||
{
|
||||
TInput typedInput = input as TInput;
|
||||
|
||||
if (typedInput == null)
|
||||
{
|
||||
output = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
output = _innerConverter.Convert(typedInput);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,103 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.ServiceBus;
|
||||
using Microsoft.Azure.WebJobs.Host;
|
||||
using Microsoft.Azure.WebJobs.Host.Converters;
|
||||
using Microsoft.Azure.WebJobs.Host.Triggers;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace Microsoft.Azure.WebJobs.ServiceBus.Triggers
|
||||
{
|
||||
internal class ServiceBusTriggerAttributeBindingProvider : ITriggerBindingProvider
|
||||
{
|
||||
private static readonly IQueueTriggerArgumentBindingProvider InnerProvider =
|
||||
new CompositeArgumentBindingProvider(
|
||||
new ConverterArgumentBindingProvider<Message>(
|
||||
new AsyncConverter<Message, Message>(new IdentityConverter<Message>())),
|
||||
new ConverterArgumentBindingProvider<string>(new MessageToStringConverter()),
|
||||
new ConverterArgumentBindingProvider<byte[]>(new MessageToByteArrayConverter()),
|
||||
new UserTypeArgumentBindingProvider()); // Must come last, because it will attempt to bind all types.
|
||||
|
||||
private readonly INameResolver _nameResolver;
|
||||
private readonly ServiceBusOptions _options;
|
||||
private readonly MessagingProvider _messagingProvider;
|
||||
private readonly IConfiguration _configuration;
|
||||
|
||||
public ServiceBusTriggerAttributeBindingProvider(INameResolver nameResolver, ServiceBusOptions options, MessagingProvider messagingProvider, IConfiguration configuration)
|
||||
{
|
||||
_nameResolver = nameResolver ?? throw new ArgumentNullException(nameof(nameResolver));
|
||||
_options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
_messagingProvider = messagingProvider ?? throw new ArgumentNullException(nameof(messagingProvider));
|
||||
_configuration = configuration;
|
||||
}
|
||||
|
||||
public Task<ITriggerBinding> TryCreateAsync(TriggerBindingProviderContext context)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException("context");
|
||||
}
|
||||
|
||||
ParameterInfo parameter = context.Parameter;
|
||||
var attribute = TypeUtility.GetResolvedAttribute<ServiceBusTriggerAttribute>(parameter);
|
||||
|
||||
if (attribute == null)
|
||||
{
|
||||
return Task.FromResult<ITriggerBinding>(null);
|
||||
}
|
||||
|
||||
string queueName = null;
|
||||
string topicName = null;
|
||||
string subscriptionName = null;
|
||||
string entityPath = null;
|
||||
|
||||
if (attribute.QueueName != null)
|
||||
{
|
||||
queueName = Resolve(attribute.QueueName);
|
||||
entityPath = queueName;
|
||||
}
|
||||
else
|
||||
{
|
||||
topicName = Resolve(attribute.TopicName);
|
||||
subscriptionName = Resolve(attribute.SubscriptionName);
|
||||
entityPath = EntityNameHelper.FormatSubscriptionPath(topicName, subscriptionName);
|
||||
}
|
||||
|
||||
ITriggerDataArgumentBinding<Message> argumentBinding = InnerProvider.TryCreate(parameter);
|
||||
if (argumentBinding == null)
|
||||
{
|
||||
throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, "Can't bind ServiceBusTrigger to type '{0}'.", parameter.ParameterType));
|
||||
}
|
||||
|
||||
attribute.Connection = Resolve(attribute.Connection);
|
||||
ServiceBusAccount account = new ServiceBusAccount(_options, _configuration, attribute);
|
||||
|
||||
ITriggerBinding binding;
|
||||
if (queueName != null)
|
||||
{
|
||||
binding = new ServiceBusTriggerBinding(parameter.Name, parameter.ParameterType, argumentBinding, account, _options, _messagingProvider, queueName);
|
||||
}
|
||||
else
|
||||
{
|
||||
binding = new ServiceBusTriggerBinding(parameter.Name, parameter.ParameterType, argumentBinding, account, _options, _messagingProvider, topicName, subscriptionName);
|
||||
}
|
||||
|
||||
return Task.FromResult<ITriggerBinding>(binding);
|
||||
}
|
||||
|
||||
private string Resolve(string queueName)
|
||||
{
|
||||
if (_nameResolver == null)
|
||||
{
|
||||
return queueName;
|
||||
}
|
||||
|
||||
return _nameResolver.ResolveWholeString(queueName);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,208 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.ServiceBus;
|
||||
using Microsoft.Azure.ServiceBus.Core;
|
||||
using Microsoft.Azure.WebJobs.Host.Bindings;
|
||||
using Microsoft.Azure.WebJobs.Host.Converters;
|
||||
using Microsoft.Azure.WebJobs.Host.Listeners;
|
||||
using Microsoft.Azure.WebJobs.Host.Protocols;
|
||||
using Microsoft.Azure.WebJobs.Host.Triggers;
|
||||
using Microsoft.Azure.WebJobs.ServiceBus.Bindings;
|
||||
using Microsoft.Azure.WebJobs.ServiceBus.Listeners;
|
||||
|
||||
namespace Microsoft.Azure.WebJobs.ServiceBus.Triggers
|
||||
{
|
||||
internal class ServiceBusTriggerBinding : ITriggerBinding
|
||||
{
|
||||
private readonly string _parameterName;
|
||||
private readonly IObjectToTypeConverter<Message> _converter;
|
||||
private readonly ITriggerDataArgumentBinding<Message> _argumentBinding;
|
||||
private readonly IReadOnlyDictionary<string, Type> _bindingDataContract;
|
||||
private readonly ServiceBusAccount _account;
|
||||
private readonly string _queueName;
|
||||
private readonly string _topicName;
|
||||
private readonly string _subscriptionName;
|
||||
private readonly string _entityPath;
|
||||
private readonly ServiceBusOptions _options;
|
||||
private ServiceBusListener _listener;
|
||||
private readonly MessagingProvider _messagingProvider;
|
||||
|
||||
public ServiceBusTriggerBinding(string parameterName, Type parameterType, ITriggerDataArgumentBinding<Message> argumentBinding, ServiceBusAccount account,
|
||||
ServiceBusOptions options, MessagingProvider messagingProvider, string queueName)
|
||||
: this(parameterName, parameterType, argumentBinding, account, options, messagingProvider)
|
||||
{
|
||||
_queueName = queueName;
|
||||
_entityPath = queueName;
|
||||
}
|
||||
|
||||
public ServiceBusTriggerBinding(string parameterName, Type parameterType, ITriggerDataArgumentBinding<Message> argumentBinding, ServiceBusAccount account,
|
||||
ServiceBusOptions options, MessagingProvider messagingProvider, string topicName, string subscriptionName)
|
||||
: this(parameterName, parameterType, argumentBinding, account, options, messagingProvider)
|
||||
{
|
||||
_topicName = topicName;
|
||||
_subscriptionName = subscriptionName;
|
||||
_entityPath = EntityNameHelper.FormatSubscriptionPath(topicName, subscriptionName);
|
||||
}
|
||||
|
||||
private ServiceBusTriggerBinding(string parameterName, Type parameterType, ITriggerDataArgumentBinding<Message> argumentBinding,
|
||||
ServiceBusAccount account, ServiceBusOptions options, MessagingProvider messagingProvider)
|
||||
{
|
||||
_parameterName = parameterName;
|
||||
_converter = CreateConverter(parameterType);
|
||||
_argumentBinding = argumentBinding;
|
||||
_bindingDataContract = CreateBindingDataContract(argumentBinding.BindingDataContract);
|
||||
_account = account;
|
||||
_options = options;
|
||||
_messagingProvider = messagingProvider;
|
||||
}
|
||||
|
||||
public Type TriggerValueType
|
||||
{
|
||||
get
|
||||
{
|
||||
return typeof(Message);
|
||||
}
|
||||
}
|
||||
|
||||
public IReadOnlyDictionary<string, Type> BindingDataContract
|
||||
{
|
||||
get { return _bindingDataContract; }
|
||||
}
|
||||
|
||||
public async Task<ITriggerData> BindAsync(object value, ValueBindingContext context)
|
||||
{
|
||||
Message message = value as Message;
|
||||
if (message == null && !_converter.TryConvert(value, out message))
|
||||
{
|
||||
throw new InvalidOperationException("Unable to convert trigger to BrokeredMessage.");
|
||||
}
|
||||
|
||||
ITriggerData triggerData = await _argumentBinding.BindAsync(message, context);
|
||||
IReadOnlyDictionary<string, object> bindingData = CreateBindingData(message, _listener?.Receiver, triggerData.BindingData);
|
||||
|
||||
return new TriggerData(triggerData.ValueProvider, bindingData);
|
||||
}
|
||||
|
||||
public async Task<IListener> CreateListenerAsync(ListenerFactoryContext context)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException("context");
|
||||
}
|
||||
|
||||
IListenerFactory factory = null;
|
||||
if (_queueName != null)
|
||||
{
|
||||
factory = new ServiceBusQueueListenerFactory(_account, _queueName, context.Executor, _options, _messagingProvider);
|
||||
}
|
||||
else
|
||||
{
|
||||
factory = new ServiceBusSubscriptionListenerFactory(_account, _topicName, _subscriptionName, context.Executor, _options, _messagingProvider);
|
||||
}
|
||||
_listener = (ServiceBusListener)(await factory.CreateAsync(context.CancellationToken));
|
||||
|
||||
return _listener;
|
||||
}
|
||||
|
||||
internal static IReadOnlyDictionary<string, Type> CreateBindingDataContract(IReadOnlyDictionary<string, Type> argumentBindingContract)
|
||||
{
|
||||
var contract = new Dictionary<string, Type>(StringComparer.OrdinalIgnoreCase);
|
||||
contract.Add("DeliveryCount", typeof(int));
|
||||
contract.Add("DeadLetterSource", typeof(string));
|
||||
contract.Add("LockToken", typeof(string));
|
||||
contract.Add("ExpiresAtUtc", typeof(DateTime));
|
||||
contract.Add("EnqueuedTimeUtc", typeof(DateTime));
|
||||
contract.Add("MessageId", typeof(string));
|
||||
contract.Add("ContentType", typeof(string));
|
||||
contract.Add("ReplyTo", typeof(string));
|
||||
contract.Add("SequenceNumber", typeof(long));
|
||||
contract.Add("To", typeof(string));
|
||||
contract.Add("Label", typeof(string));
|
||||
contract.Add("CorrelationId", typeof(string));
|
||||
contract.Add("UserProperties", typeof(IDictionary<string, object>));
|
||||
contract.Add("MessageReceiver", typeof(MessageReceiver));
|
||||
|
||||
if (argumentBindingContract != null)
|
||||
{
|
||||
foreach (KeyValuePair<string, Type> item in argumentBindingContract)
|
||||
{
|
||||
// In case of conflict, binding data from the value type overrides the built-in binding data above.
|
||||
contract[item.Key] = item.Value;
|
||||
}
|
||||
}
|
||||
|
||||
return contract;
|
||||
}
|
||||
|
||||
internal static IReadOnlyDictionary<string, object> CreateBindingData(Message value, MessageReceiver receiver, IReadOnlyDictionary<string, object> bindingDataFromValueType)
|
||||
{
|
||||
var bindingData = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
SafeAddValue(() => bindingData.Add(nameof(value.SystemProperties.DeliveryCount), value.SystemProperties.DeliveryCount));
|
||||
SafeAddValue(() => bindingData.Add(nameof(value.SystemProperties.DeadLetterSource), value.SystemProperties.DeadLetterSource));
|
||||
SafeAddValue(() => bindingData.Add(nameof(value.SystemProperties.LockToken), value.SystemProperties.IsLockTokenSet ? value.SystemProperties.LockToken : string.Empty));
|
||||
SafeAddValue(() => bindingData.Add(nameof(value.ExpiresAtUtc), value.ExpiresAtUtc));
|
||||
SafeAddValue(() => bindingData.Add(nameof(value.SystemProperties.EnqueuedTimeUtc), value.SystemProperties.EnqueuedTimeUtc));
|
||||
SafeAddValue(() => bindingData.Add(nameof(value.MessageId), value.MessageId));
|
||||
SafeAddValue(() => bindingData.Add(nameof(value.ContentType), value.ContentType));
|
||||
SafeAddValue(() => bindingData.Add(nameof(value.ReplyTo), value.ReplyTo));
|
||||
SafeAddValue(() => bindingData.Add(nameof(value.SystemProperties.SequenceNumber), value.SystemProperties.SequenceNumber));
|
||||
SafeAddValue(() => bindingData.Add(nameof(value.To), value.To));
|
||||
SafeAddValue(() => bindingData.Add(nameof(value.Label), value.Label));
|
||||
SafeAddValue(() => bindingData.Add(nameof(value.CorrelationId), value.CorrelationId));
|
||||
SafeAddValue(() => bindingData.Add(nameof(value.UserProperties), value.UserProperties));
|
||||
SafeAddValue(() => bindingData.Add("MessageReceiver", receiver));
|
||||
|
||||
if (bindingDataFromValueType != null)
|
||||
{
|
||||
foreach (KeyValuePair<string, object> item in bindingDataFromValueType)
|
||||
{
|
||||
// In case of conflict, binding data from the value type overrides the built-in binding data above.
|
||||
bindingData[item.Key] = item.Value;
|
||||
}
|
||||
}
|
||||
|
||||
return bindingData;
|
||||
}
|
||||
|
||||
private static void SafeAddValue(Action addValue)
|
||||
{
|
||||
try
|
||||
{
|
||||
addValue();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// some message property getters can throw, based on the
|
||||
// state of the message
|
||||
}
|
||||
}
|
||||
|
||||
public ParameterDescriptor ToParameterDescriptor()
|
||||
{
|
||||
string entityPath = _queueName != null ?
|
||||
_queueName : string.Format(CultureInfo.InvariantCulture, "{0}/Subscriptions/{1}", _topicName, _subscriptionName);
|
||||
|
||||
return new ServiceBusTriggerParameterDescriptor
|
||||
{
|
||||
Name = _parameterName,
|
||||
QueueName = _queueName,
|
||||
TopicName = _topicName,
|
||||
SubscriptionName = _subscriptionName,
|
||||
DisplayHints = ServiceBusBinding.CreateParameterDisplayHints(entityPath, true)
|
||||
};
|
||||
}
|
||||
|
||||
private static IObjectToTypeConverter<Message> CreateConverter(Type parameterType)
|
||||
{
|
||||
return new CompositeObjectToTypeConverter<Message>(
|
||||
new OutputConverter<Message>(new IdentityConverter<Message>()),
|
||||
new OutputConverter<string>(StringTodMessageConverterFactory.Create(parameterType)));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using Microsoft.Azure.WebJobs.Host.Protocols;
|
||||
|
||||
namespace Microsoft.Azure.WebJobs.ServiceBus.Triggers
|
||||
{
|
||||
internal class ServiceBusTriggerParameterDescriptor : TriggerParameterDescriptor
|
||||
{
|
||||
/// <summary>Gets or sets the name of the queue.</summary>
|
||||
/// <remarks>When binding to a subscription in a topic, returns <see langword="null"/>.</remarks>
|
||||
public string QueueName { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the name of the queue.</summary>
|
||||
/// <remarks>When binding to a queue, returns <see langword="null"/>.</remarks>
|
||||
public string TopicName { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the name of the subscription in <see cref="TopicName"/>.</summary>
|
||||
/// <remarks>When binding to a queue, returns <see langword="null"/>.</remarks>
|
||||
public string SubscriptionName { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string GetTriggerReason(IDictionary<string, string> arguments)
|
||||
{
|
||||
string path;
|
||||
if (QueueName != null)
|
||||
{
|
||||
path = QueueName;
|
||||
}
|
||||
else
|
||||
{
|
||||
path = string.Format(CultureInfo.InvariantCulture, "{0}/Subscriptions/{1}", TopicName, SubscriptionName);
|
||||
}
|
||||
|
||||
return string.Format(CultureInfo.CurrentCulture, "New ServiceBus message detected on '{0}'.", path);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text;
|
||||
using Microsoft.Azure.WebJobs.Host.Converters;
|
||||
using Microsoft.Azure.ServiceBus;
|
||||
|
||||
|
||||
namespace Microsoft.Azure.WebJobs.ServiceBus.Triggers
|
||||
{
|
||||
internal class StringToBinarydMessageConverter : IConverter<string, Message>
|
||||
{
|
||||
[SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope")]
|
||||
public Message Convert(string input)
|
||||
{
|
||||
byte[] contents = StrictEncodings.Utf8.GetBytes(input);
|
||||
Message message = new Message(contents);
|
||||
message.ContentType = ContentTypes.ApplicationOctetStream;
|
||||
return message;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using Microsoft.Azure.WebJobs.Host.Converters;
|
||||
using Microsoft.Azure.ServiceBus;
|
||||
|
||||
namespace Microsoft.Azure.WebJobs.ServiceBus.Triggers
|
||||
{
|
||||
internal class StringToJsonMessageConverter : IConverter<string, Message>
|
||||
{
|
||||
[SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope")]
|
||||
public Message Convert(string input)
|
||||
{
|
||||
Message message = new Message(StrictEncodings.Utf8.GetBytes(input));
|
||||
message.ContentType = ContentTypes.ApplicationJson;
|
||||
return message;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.Azure.WebJobs.Host.Converters;
|
||||
using Microsoft.Azure.ServiceBus;
|
||||
|
||||
namespace Microsoft.Azure.WebJobs.ServiceBus.Triggers
|
||||
{
|
||||
internal class StringTodMessageConverterFactory
|
||||
{
|
||||
public static IConverter<string, Message> Create(Type parameterType)
|
||||
{
|
||||
if (parameterType == typeof(Message) || parameterType == typeof(string))
|
||||
{
|
||||
return new StringToTextMessageConverter();
|
||||
}
|
||||
else if (parameterType == typeof(byte[]))
|
||||
{
|
||||
return new StringToBinarydMessageConverter();
|
||||
}
|
||||
else
|
||||
{
|
||||
return new StringToJsonMessageConverter();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Microsoft.Azure.ServiceBus;
|
||||
|
||||
namespace Microsoft.Azure.WebJobs.ServiceBus.Triggers
|
||||
{
|
||||
internal class StringToTextMessageConverter : IConverter<string, Message>
|
||||
{
|
||||
[SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope")]
|
||||
public Message Convert(string input)
|
||||
{
|
||||
Message message = new Message(StrictEncodings.Utf8.GetBytes(input))
|
||||
{
|
||||
ContentType = ContentTypes.TextPlain
|
||||
};
|
||||
return message;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,121 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.WebJobs.Host.Bindings;
|
||||
using Microsoft.Azure.WebJobs.Host.Triggers;
|
||||
using Microsoft.Azure.ServiceBus;
|
||||
using Newtonsoft.Json;
|
||||
using Microsoft.Azure.ServiceBus.InteropExtensions;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Microsoft.Azure.WebJobs.ServiceBus.Triggers
|
||||
{
|
||||
internal class UserTypeArgumentBindingProvider : IQueueTriggerArgumentBindingProvider
|
||||
{
|
||||
public ITriggerDataArgumentBinding<Message> TryCreate(ParameterInfo parameter)
|
||||
{
|
||||
// At indexing time, attempt to bind all types.
|
||||
// (Whether or not actual binding is possible depends on the message shape at runtime.)
|
||||
return CreateBinding(parameter.ParameterType);
|
||||
}
|
||||
|
||||
private static ITriggerDataArgumentBinding<Message> CreateBinding(Type itemType)
|
||||
{
|
||||
Type genericType = typeof(UserTypeArgumentBinding<>).MakeGenericType(itemType);
|
||||
return (ITriggerDataArgumentBinding<Message>)Activator.CreateInstance(genericType);
|
||||
}
|
||||
|
||||
private class UserTypeArgumentBinding<TInput> : ITriggerDataArgumentBinding<Message>
|
||||
{
|
||||
private readonly IBindingDataProvider _bindingDataProvider;
|
||||
|
||||
public UserTypeArgumentBinding()
|
||||
{
|
||||
_bindingDataProvider = BindingDataProvider.FromType(typeof(TInput));
|
||||
}
|
||||
|
||||
public Type ValueType
|
||||
{
|
||||
get { return typeof(TInput); }
|
||||
}
|
||||
|
||||
public IReadOnlyDictionary<string, Type> BindingDataContract
|
||||
{
|
||||
get { return _bindingDataProvider != null ? _bindingDataProvider.Contract : null; }
|
||||
}
|
||||
|
||||
public async Task<ITriggerData> BindAsync(Message value, ValueBindingContext context)
|
||||
{
|
||||
IValueProvider provider;
|
||||
Message clone = value.Clone();
|
||||
|
||||
TInput contents = GetBody(value);
|
||||
|
||||
if (contents == null)
|
||||
{
|
||||
provider = await MessageValueProvider.CreateAsync(clone, null, ValueType,
|
||||
context.CancellationToken);
|
||||
return new TriggerData(provider, null);
|
||||
}
|
||||
|
||||
provider = await MessageValueProvider.CreateAsync(clone, contents, ValueType,
|
||||
context.CancellationToken);
|
||||
|
||||
IReadOnlyDictionary<string, object> bindingData = (_bindingDataProvider != null)
|
||||
? _bindingDataProvider.GetBindingData(contents) : null;
|
||||
|
||||
return new TriggerData(provider, bindingData);
|
||||
}
|
||||
|
||||
private static TInput GetBody(Message message)
|
||||
{
|
||||
// 1. If ContentType is "application/json" deserialize as JSON
|
||||
// 2. If ContentType is not "application/json" attempt to deserialize using Message.GetBody, which will handle cases like XML object serialization
|
||||
// 3. If this deserialization fails, do a final attempt at JSON deserialization to catch cases where the content type might be incorrect
|
||||
|
||||
if (message.ContentType == ContentTypes.ApplicationJson)
|
||||
{
|
||||
return DeserializeJsonObject(message);
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
return message.GetBody<TInput>();
|
||||
}
|
||||
catch (SerializationException)
|
||||
{
|
||||
return DeserializeJsonObject(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static TInput DeserializeJsonObject(Message message)
|
||||
{
|
||||
string contents = StrictEncodings.Utf8.GetString(message.Body);
|
||||
|
||||
try
|
||||
{
|
||||
return JsonConvert.DeserializeObject<TInput>(contents, Constants.JsonSerializerSettings);
|
||||
}
|
||||
catch (JsonException e)
|
||||
{
|
||||
// Easy to have the queue payload not deserialize properly. So give a useful error.
|
||||
string msg = string.Format(
|
||||
@"Binding parameters to complex objects (such as '{0}') uses Json.NET serialization or XML object serialization.
|
||||
1. If ContentType is 'application/json' deserialize as JSON
|
||||
2. If ContentType is not 'application/json' attempt to deserialize using Message.GetBody, which will handle cases like XML object serialization
|
||||
3. If this deserialization fails, do a final attempt at JSON deserialization to catch cases where the content type might be incorrect
|
||||
The JSON parser failed: {1}
|
||||
", typeof(TInput).Name, e.Message);
|
||||
throw new InvalidOperationException(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,53 +0,0 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<Import Project="..\Microsoft.Azure.WebJobs.Shared\WebJobs.Shared.projitems" Label="Shared" />
|
||||
<Import Project="..\..\build\common.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<Version>$(ExtensionsServiceBusVersion)</Version>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<AssemblyName>Microsoft.Azure.WebJobs.ServiceBus</AssemblyName>
|
||||
<PackageId>Microsoft.Azure.WebJobs.Extensions.ServiceBus</PackageId>
|
||||
<RootNamespace>Microsoft.Azure.WebJobs.ServiceBus</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<StyleCopTreatErrorsAsWarnings>false</StyleCopTreatErrorsAsWarnings>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<WarningsAsErrors />
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
<StyleCopTreatErrorsAsWarnings>false</StyleCopTreatErrorsAsWarnings>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<WarningsAsErrors />
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Azure.ServiceBus" Version="3.0.2" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.1.0-beta004">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="WindowsAzure.Storage" Version="9.3.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="..\Microsoft.Azure.WebJobs.Host\Converters\AsyncConverter.cs" Link="Converters\AsyncConverter.cs" />
|
||||
<Compile Include="..\Microsoft.Azure.WebJobs.Host\Converters\CompositeObjectToTypeConverter.cs" Link="Converters\CompositeObjectToTypeConverter.cs" />
|
||||
<Compile Include="..\Microsoft.Azure.WebJobs.Host\Converters\ConversionResult.cs" Link="Converters\ConversionResult.cs" />
|
||||
<Compile Include="..\Microsoft.Azure.WebJobs.Host\Converters\IAsyncObjectToTypeConverter.cs" Link="Converters\IAsyncObjectToTypeConverter.cs" />
|
||||
<Compile Include="..\Microsoft.Azure.WebJobs.Host\Converters\IdentityConverter.cs" Link="Converters\IdentityConverter.cs" />
|
||||
<Compile Include="..\Microsoft.Azure.WebJobs.Host\Converters\IObjectToTypeConverter.cs" Link="Converters\IObjectToTypeConverter.cs" />
|
||||
<Compile Include="..\Microsoft.Azure.WebJobs.Host\Triggers\ITriggerDataArgumentBinding.cs" Link="Triggers\ITriggerDataArgumentBinding.cs" />
|
||||
<Compile Include="..\Microsoft.Azure.WebJobs.Host\TypeUtility.cs" Link="TypeUtility.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<AdditionalFiles Include="..\..\stylecop.json" Link="stylecop.json" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Microsoft.Azure.WebJobs.Host\WebJobs.Host.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -0,0 +1,71 @@
|
|||
<Project>
|
||||
|
||||
<Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
|
||||
<Import Project="..\..\build\common.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<PackageId>Microsoft.Azure.WebJobs.Sources</PackageId>
|
||||
<IsPackable>true</IsPackable>
|
||||
<IncludeBuildOutput>false</IncludeBuildOutput>
|
||||
<ContentTargetFolders>contentFiles</ContentTargetFolders>
|
||||
<DisableImplicitFrameworkReferences>true</DisableImplicitFrameworkReferences>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<GenerateTargetFrameworkAttribute>false</GenerateTargetFrameworkAttribute>
|
||||
<NoWarn>CS8021</NoWarn>
|
||||
<NoBuild>true</NoBuild>
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Update="Converters\AsyncConverter.cs">
|
||||
<Pack>true</Pack>
|
||||
<PackagePath>$(ContentTargetFolders)\cs\netstandard2.0\$(PackageId)</PackagePath>
|
||||
</Compile>
|
||||
<Compile Update="Converters\CompositeObjectToTypeConverter.cs">
|
||||
<Pack>true</Pack>
|
||||
<PackagePath>$(ContentTargetFolders)\cs\netstandard2.0\$(PackageId)\</PackagePath>
|
||||
</Compile>
|
||||
<Compile Update="Converters\IAsyncObjectToTypeConverter.cs">
|
||||
<Pack>true</Pack>
|
||||
<PackagePath>$(ContentTargetFolders)\cs\netstandard2.0\$(PackageId)</PackagePath>
|
||||
</Compile>
|
||||
<Compile Update="Converters\ConversionResult.cs">
|
||||
<Pack>true</Pack>
|
||||
<PackagePath>$(ContentTargetFolders)\cs\netstandard2.0\$(PackageId)</PackagePath>
|
||||
</Compile>
|
||||
<Compile Update="Converters\IdentityConverter.cs">
|
||||
<Pack>true</Pack>
|
||||
<PackagePath>$(ContentTargetFolders)\cs\netstandard2.0\$(PackageId)</PackagePath>
|
||||
</Compile>
|
||||
<Compile Update="Converters\IObjectToTypeConverter.cs">
|
||||
<Pack>true</Pack>
|
||||
<PackagePath>$(ContentTargetFolders)\cs\netstandard2.0\$(PackageId)</PackagePath>
|
||||
</Compile>
|
||||
<Compile Update="Triggers\ITriggerDataArgumentBinding.cs">
|
||||
<Pack>true</Pack>
|
||||
<PackagePath>$(ContentTargetFolders)\cs\netstandard2.0\$(PackageId)</PackagePath>
|
||||
</Compile>
|
||||
<Compile Update="TypeUtility.cs">
|
||||
<Pack>true</Pack>
|
||||
<PackagePath>$(ContentTargetFolders)\cs\netstandard2.0\$(PackageId)</PackagePath>
|
||||
</Compile>
|
||||
<Compile Include="..\Microsoft.Azure.WebJobs.Shared\DictionaryExtensions.cs" Link="DictionaryExtensions.cs">
|
||||
<Pack>true</Pack>
|
||||
<PackagePath>$(ContentTargetFolders)\cs\netstandard2.0\$(PackageId)</PackagePath>
|
||||
</Compile>
|
||||
<Compile Include="..\Microsoft.Azure.WebJobs.Shared\Sanitizer.cs" Link="Sanitizer.cs">
|
||||
<Pack>true</Pack>
|
||||
<PackagePath>$(ContentTargetFolders)\cs\netstandard2.0\$(PackageId)</PackagePath>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Remove="@(PackageReference)" />
|
||||
</ItemGroup>
|
||||
|
||||
<Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" />
|
||||
|
||||
<Target Name="Compile" />
|
||||
<Target Name="CopyFilesToOutputDirectory" />
|
||||
|
||||
</Project>
|
|
@ -1,53 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Azure.WebJobs.Host.TestCommon;
|
||||
using Microsoft.Azure.WebJobs.ServiceBus.Bindings;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.Azure.WebJobs.ServiceBus.UnitTests.Bindings
|
||||
{
|
||||
public class BindableServiceBusPathTests
|
||||
{
|
||||
[Fact]
|
||||
public void Create_IfNonParameterizedPattern_ReturnsBoundPath()
|
||||
{
|
||||
const string queueOrTopicNamePattern = "queue-name-with-no-parameters";
|
||||
|
||||
IBindableServiceBusPath path = BindableServiceBusPath.Create(queueOrTopicNamePattern);
|
||||
|
||||
Assert.NotNull(path);
|
||||
Assert.Equal(queueOrTopicNamePattern, path.QueueOrTopicNamePattern);
|
||||
Assert.True(path.IsBound);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Create_IfParameterizedPattern_ReturnsNotBoundPath()
|
||||
{
|
||||
const string queueOrTopicNamePattern = "queue-{name}-with-{parameter}";
|
||||
|
||||
IBindableServiceBusPath path = BindableServiceBusPath.Create(queueOrTopicNamePattern);
|
||||
|
||||
Assert.NotNull(path);
|
||||
Assert.Equal(queueOrTopicNamePattern, path.QueueOrTopicNamePattern);
|
||||
Assert.False(path.IsBound);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Create_IfNullPattern_Throws()
|
||||
{
|
||||
ExceptionAssert.ThrowsArgumentNull(() => BindableServiceBusPath.Create(null), "queueOrTopicNamePattern");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Create_IfMalformedPattern_PropagatesThrownException()
|
||||
{
|
||||
const string queueNamePattern = "malformed-queue-{name%";
|
||||
|
||||
ExceptionAssert.ThrowsFormat(
|
||||
() => BindableServiceBusPath.Create(queueNamePattern),
|
||||
"Invalid template 'malformed-queue-{name%'. Missing closing bracket at position 17.");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.WebJobs.ServiceBus.Bindings;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.Azure.WebJobs.ServiceBus.UnitTests.Bindings
|
||||
{
|
||||
public class BoundServiceBusTests
|
||||
{
|
||||
[Fact]
|
||||
public void Bind_IfNotNullBindingData_ReturnsResolvedQueueName()
|
||||
{
|
||||
const string queueOrTopicNamePattern = "queue-name-with-no-parameters";
|
||||
var bindingData = new Dictionary<string, object> { { "name", "value" } };
|
||||
IBindableServiceBusPath path = new BoundServiceBusPath(queueOrTopicNamePattern);
|
||||
|
||||
string result = path.Bind(bindingData);
|
||||
|
||||
Assert.Equal(queueOrTopicNamePattern, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Bind_IfNullBindingData_ReturnsResolvedQueueName()
|
||||
{
|
||||
const string queueOrTopicNamePattern = "queue-name-with-no-parameters";
|
||||
IBindableServiceBusPath path = new BoundServiceBusPath(queueOrTopicNamePattern);
|
||||
|
||||
string result = path.Bind(null);
|
||||
|
||||
Assert.Equal(queueOrTopicNamePattern, result);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Azure.WebJobs.Host.Bindings;
|
||||
using Microsoft.Azure.WebJobs.Host.Bindings.Path;
|
||||
using Microsoft.Azure.WebJobs.Host.TestCommon;
|
||||
using Microsoft.Azure.WebJobs.ServiceBus.Bindings;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.Azure.WebJobs.ServiceBus.UnitTests.Bindings
|
||||
{
|
||||
public class ParameterizedServiceBusPathTests
|
||||
{
|
||||
[Fact]
|
||||
public void Bind_IfNotNullBindingData_ReturnsResolvedQueueName()
|
||||
{
|
||||
const string queueOrTopicNamePattern = "queue-{name}-with-{parameter}";
|
||||
var bindingData = new Dictionary<string, object> { { "name", "name" }, { "parameter", "parameter" } };
|
||||
IBindableServiceBusPath path = CreateProductUnderTest(queueOrTopicNamePattern);
|
||||
|
||||
string result = path.Bind(bindingData);
|
||||
|
||||
Assert.Equal("queue-name-with-parameter", result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Bind_IfNullBindingData_Throws()
|
||||
{
|
||||
const string queueOrTopicNamePattern = "queue-{name}-with-{parameter}";
|
||||
IBindableServiceBusPath path = CreateProductUnderTest(queueOrTopicNamePattern);
|
||||
|
||||
ExceptionAssert.ThrowsArgumentNull(() => path.Bind(null), "bindingData");
|
||||
}
|
||||
|
||||
private static IBindableServiceBusPath CreateProductUnderTest(string queueOrTopicNamePattern)
|
||||
{
|
||||
BindingTemplate template = BindingTemplate.FromString(queueOrTopicNamePattern);
|
||||
IBindableServiceBusPath path = new ParameterizedServiceBusPath(template);
|
||||
return path;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,71 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.ServiceBus;
|
||||
using Microsoft.Azure.WebJobs.Host.Bindings;
|
||||
using Microsoft.Azure.WebJobs.ServiceBus.Bindings;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.Azure.WebJobs.ServiceBus.UnitTests.Bindings
|
||||
{
|
||||
public class ServiceBusAttributeBindingProviderTests
|
||||
{
|
||||
private readonly ServiceBusAttributeBindingProvider _provider;
|
||||
private readonly IConfiguration _configuration;
|
||||
|
||||
public ServiceBusAttributeBindingProviderTests()
|
||||
{
|
||||
_configuration = new ConfigurationBuilder()
|
||||
.AddEnvironmentVariables()
|
||||
.Build();
|
||||
Mock<INameResolver> mockResolver = new Mock<INameResolver>(MockBehavior.Strict);
|
||||
ServiceBusOptions config = new ServiceBusOptions();
|
||||
_provider = new ServiceBusAttributeBindingProvider(mockResolver.Object, config, _configuration, new MessagingProvider(new OptionsWrapper<ServiceBusOptions>(config)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task TryCreateAsync_AccountOverride_OverrideIsApplied()
|
||||
{
|
||||
|
||||
ParameterInfo parameter = GetType().GetMethod("TestJob_AccountOverride").GetParameters()[0];
|
||||
BindingProviderContext context = new BindingProviderContext(parameter, new Dictionary<string, Type>(), CancellationToken.None);
|
||||
|
||||
IBinding binding = await _provider.TryCreateAsync(context);
|
||||
|
||||
Assert.NotNull(binding);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task TryCreateAsync_DefaultAccount()
|
||||
{
|
||||
|
||||
ParameterInfo parameter = GetType().GetMethod("TestJob").GetParameters()[0];
|
||||
BindingProviderContext context = new BindingProviderContext(parameter, new Dictionary<string, Type>(), CancellationToken.None);
|
||||
|
||||
IBinding binding = await _provider.TryCreateAsync(context);
|
||||
|
||||
Assert.NotNull(binding);
|
||||
}
|
||||
|
||||
public static void TestJob_AccountOverride(
|
||||
[ServiceBusAttribute("test"),
|
||||
ServiceBusAccount("testaccount")] out Message message)
|
||||
{
|
||||
message = new Message();
|
||||
}
|
||||
|
||||
public static void TestJob(
|
||||
[ServiceBusAttribute("test")] out Message message)
|
||||
{
|
||||
message = new Message();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,71 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.ServiceBus;
|
||||
using Microsoft.Azure.WebJobs.Host.Triggers;
|
||||
using Microsoft.Azure.WebJobs.ServiceBus.Triggers;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.Azure.WebJobs.ServiceBus.UnitTests.Bindings
|
||||
{
|
||||
public class ServiceBusTriggerAttributeBindingProviderTests
|
||||
{
|
||||
private readonly Mock<MessagingProvider> _mockMessagingProvider;
|
||||
private readonly ServiceBusTriggerAttributeBindingProvider _provider;
|
||||
private readonly IConfiguration _configuration;
|
||||
|
||||
public ServiceBusTriggerAttributeBindingProviderTests()
|
||||
{
|
||||
_configuration = new ConfigurationBuilder()
|
||||
.AddEnvironmentVariables()
|
||||
.Build();
|
||||
Mock<INameResolver> mockResolver = new Mock<INameResolver>(MockBehavior.Strict);
|
||||
|
||||
ServiceBusOptions config = new ServiceBusOptions();
|
||||
_mockMessagingProvider = new Mock<MessagingProvider>(MockBehavior.Strict, new OptionsWrapper<ServiceBusOptions>(config));
|
||||
|
||||
_provider = new ServiceBusTriggerAttributeBindingProvider(mockResolver.Object, config, _mockMessagingProvider.Object, _configuration);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task TryCreateAsync_AccountOverride_OverrideIsApplied()
|
||||
{
|
||||
ParameterInfo parameter = GetType().GetMethod("TestJob_AccountOverride").GetParameters()[0];
|
||||
TriggerBindingProviderContext context = new TriggerBindingProviderContext(parameter, CancellationToken.None);
|
||||
|
||||
ITriggerBinding binding = await _provider.TryCreateAsync(context);
|
||||
|
||||
Assert.NotNull(binding);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task TryCreateAsync_DefaultAccount()
|
||||
{
|
||||
ParameterInfo parameter = GetType().GetMethod("TestJob").GetParameters()[0];
|
||||
TriggerBindingProviderContext context = new TriggerBindingProviderContext(parameter, CancellationToken.None);
|
||||
|
||||
ITriggerBinding binding = await _provider.TryCreateAsync(context);
|
||||
|
||||
Assert.NotNull(binding);
|
||||
}
|
||||
|
||||
public static void TestJob_AccountOverride(
|
||||
[ServiceBusTriggerAttribute("test"),
|
||||
ServiceBusAccount("testaccount")] Message message)
|
||||
{
|
||||
message = new Message();
|
||||
}
|
||||
|
||||
public static void TestJob(
|
||||
[ServiceBusTriggerAttribute("test")] Message message)
|
||||
{
|
||||
message = new Message();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,96 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using Microsoft.Azure.WebJobs.ServiceBus.Triggers;
|
||||
using Microsoft.Azure.ServiceBus;
|
||||
using Xunit;
|
||||
using Microsoft.Azure.ServiceBus.Core;
|
||||
|
||||
namespace Microsoft.Azure.WebJobs.ServiceBus.UnitTests.Bindings
|
||||
{
|
||||
public class ServiceBusTriggerBindingTests
|
||||
{
|
||||
[Fact]
|
||||
public void CreateBindingDataContract_ReturnsExpectedValue()
|
||||
{
|
||||
IReadOnlyDictionary<string, Type> argumentContract = null;
|
||||
var bindingDataContract = ServiceBusTriggerBinding.CreateBindingDataContract(argumentContract);
|
||||
Assert.Equal(14, bindingDataContract.Count);
|
||||
Assert.Equal(bindingDataContract["DeliveryCount"], typeof(int));
|
||||
Assert.Equal(bindingDataContract["DeadLetterSource"], typeof(string));
|
||||
Assert.Equal(bindingDataContract["LockToken"], typeof(string));
|
||||
Assert.Equal(bindingDataContract["ExpiresAtUtc"], typeof(DateTime));
|
||||
Assert.Equal(bindingDataContract["EnqueuedTimeUtc"], typeof(DateTime));
|
||||
Assert.Equal(bindingDataContract["MessageId"], typeof(string));
|
||||
Assert.Equal(bindingDataContract["ContentType"], typeof(string));
|
||||
Assert.Equal(bindingDataContract["ReplyTo"], typeof(string));
|
||||
Assert.Equal(bindingDataContract["SequenceNumber"], typeof(long));
|
||||
Assert.Equal(bindingDataContract["To"], typeof(string));
|
||||
Assert.Equal(bindingDataContract["Label"], typeof(string));
|
||||
Assert.Equal(bindingDataContract["CorrelationId"], typeof(string));
|
||||
Assert.Equal(bindingDataContract["UserProperties"], typeof(IDictionary<string, object>));
|
||||
Assert.Equal(bindingDataContract["MessageReceiver"], typeof(MessageReceiver));
|
||||
|
||||
// verify that argument binding values override built ins
|
||||
argumentContract = new Dictionary<string, Type>
|
||||
{
|
||||
{ "DeliveryCount", typeof(string) },
|
||||
{ "NewProperty", typeof(decimal) }
|
||||
};
|
||||
bindingDataContract = ServiceBusTriggerBinding.CreateBindingDataContract(argumentContract);
|
||||
Assert.Equal(15, bindingDataContract.Count);
|
||||
Assert.Equal(bindingDataContract["DeliveryCount"], typeof(string));
|
||||
Assert.Equal(bindingDataContract["NewProperty"], typeof(decimal));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("application/json")]
|
||||
[InlineData("text/plain")]
|
||||
public void CreateBindingData_ReturnsExpectedValue(string contentType)
|
||||
{
|
||||
Message message = new Message(Encoding.UTF8.GetBytes("Test Message"))
|
||||
{
|
||||
ReplyTo = "test-queue",
|
||||
To = "test",
|
||||
ContentType = contentType,
|
||||
Label = "test label",
|
||||
CorrelationId = Guid.NewGuid().ToString(),
|
||||
};
|
||||
|
||||
IReadOnlyDictionary<string, object> valueBindingData = null;
|
||||
|
||||
var config = new ServiceBusOptions
|
||||
{
|
||||
ConnectionString = "Endpoint=sb://test.servicebus.windows.net/;SharedAccessKeyName=TestKey;SharedAccessKey=00000000000000000"
|
||||
};
|
||||
|
||||
var messageReceiver = new MessageReceiver(config.ConnectionString, "test");
|
||||
var bindingData = ServiceBusTriggerBinding.CreateBindingData(message, messageReceiver, valueBindingData);
|
||||
Assert.Equal(9, bindingData.Count);
|
||||
Assert.Equal(message.ReplyTo, bindingData["ReplyTo"]);
|
||||
Assert.Equal(string.Empty, bindingData["lockToken"]);
|
||||
Assert.Equal(message.To, bindingData["To"]);
|
||||
Assert.Equal(message.MessageId, bindingData["MessageId"]);
|
||||
Assert.Equal(message.ContentType, bindingData["ContentType"]);
|
||||
Assert.Equal(message.Label, bindingData["Label"]);
|
||||
Assert.Equal(message.CorrelationId, bindingData["CorrelationId"]);
|
||||
Assert.Same(message.UserProperties, bindingData["UserProperties"]);
|
||||
Assert.Same(messageReceiver, bindingData["MessageReceiver"]);
|
||||
|
||||
// verify that value binding data overrides built ins
|
||||
valueBindingData = new Dictionary<string, object>
|
||||
{
|
||||
{ "ReplyTo", "override" },
|
||||
{ "NewProperty", 123 }
|
||||
};
|
||||
bindingData = ServiceBusTriggerBinding.CreateBindingData(message, messageReceiver, valueBindingData);
|
||||
Assert.Equal(10, bindingData.Count);
|
||||
Assert.Equal("override", bindingData["ReplyTo"]);
|
||||
Assert.Equal(123, bindingData["NewProperty"]);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,107 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Azure.WebJobs.Host;
|
||||
using Microsoft.Azure.WebJobs.Host.Config;
|
||||
using Microsoft.Azure.WebJobs.Host.TestCommon;
|
||||
using Microsoft.Azure.WebJobs.ServiceBus.Config;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.Azure.WebJobs.ServiceBus.UnitTests.Config
|
||||
{
|
||||
public class ServiceBusHostBuilderExtensionsTests
|
||||
{
|
||||
[Fact]
|
||||
public void ConfigureOptions_AppliesValuesCorrectly()
|
||||
{
|
||||
string extensionPath = "AzureWebJobs:Extensions:ServiceBus";
|
||||
var values = new Dictionary<string, string>
|
||||
{
|
||||
{ $"{extensionPath}:PrefetchCount", "123" },
|
||||
{ $"ConnectionStrings:ServiceBus", "TestConnectionString" },
|
||||
{ $"{extensionPath}:MessageHandlerOptions:MaxConcurrentCalls", "123" },
|
||||
{ $"{extensionPath}:MessageHandlerOptions:AutoComplete", "false" },
|
||||
{ $"{extensionPath}:MessageHandlerOptions:MaxAutoRenewDuration", "00:00:15" }
|
||||
};
|
||||
|
||||
ServiceBusOptions options = TestHelpers.GetConfiguredOptions<ServiceBusOptions>(b =>
|
||||
{
|
||||
b.AddServiceBus();
|
||||
}, values);
|
||||
|
||||
Assert.Equal(123, options.PrefetchCount);
|
||||
Assert.Equal("TestConnectionString", options.ConnectionString);
|
||||
Assert.Equal(123, options.MessageHandlerOptions.MaxConcurrentCalls);
|
||||
Assert.Equal(false, options.MessageHandlerOptions.AutoComplete);
|
||||
Assert.Equal(TimeSpan.FromSeconds(15), options.MessageHandlerOptions.MaxAutoRenewDuration);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddServiceBus_ThrowsArgumentNull_WhenServiceBusOptionsIsNull()
|
||||
{
|
||||
IHost host = new HostBuilder()
|
||||
.ConfigureDefaultTestHost(b =>
|
||||
{
|
||||
b.AddServiceBus();
|
||||
})
|
||||
.ConfigureServices(s => s.AddSingleton<IOptions<ServiceBusOptions>>(p => null))
|
||||
.Build();
|
||||
|
||||
var exception = Assert.Throws<ArgumentNullException>(() => host.Services.GetServices<IExtensionConfigProvider>());
|
||||
|
||||
Assert.Equal("serviceBusOptions", exception.ParamName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddServiceBus_NoServiceBusOptions_PerformsExpectedRegistration()
|
||||
{
|
||||
IHost host = new HostBuilder()
|
||||
.ConfigureDefaultTestHost(b =>
|
||||
{
|
||||
b.AddServiceBus();
|
||||
})
|
||||
.Build();
|
||||
|
||||
var extensions = host.Services.GetService<IExtensionRegistry>();
|
||||
IExtensionConfigProvider[] configProviders = extensions.GetExtensions<IExtensionConfigProvider>().ToArray();
|
||||
|
||||
// verify that the service bus config provider was registered
|
||||
var serviceBusExtensionConfig = configProviders.OfType<ServiceBusExtensionConfigProvider>().Single();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddServiceBus_ServiceBusOptionsProvided_PerformsExpectedRegistration()
|
||||
{
|
||||
string fakeConnStr = "test service bus connection";
|
||||
|
||||
IHost host = new HostBuilder()
|
||||
.ConfigureDefaultTestHost(b =>
|
||||
{
|
||||
b.AddServiceBus();
|
||||
})
|
||||
.ConfigureServices(s =>
|
||||
{
|
||||
s.Configure<ServiceBusOptions>(o =>
|
||||
{
|
||||
o.ConnectionString = fakeConnStr;
|
||||
});
|
||||
})
|
||||
.Build();
|
||||
|
||||
// verify that the service bus config provider was registered
|
||||
var extensions = host.Services.GetService<IExtensionRegistry>();
|
||||
IExtensionConfigProvider[] configProviders = extensions.GetExtensions<IExtensionConfigProvider>().ToArray();
|
||||
|
||||
// verify that the service bus config provider was registered
|
||||
var serviceBusExtensionConfig = configProviders.OfType<ServiceBusExtensionConfigProvider>().Single();
|
||||
|
||||
Assert.Equal(fakeConnStr, serviceBusExtensionConfig.Options.ConnectionString);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,88 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Microsoft.Azure.ServiceBus;
|
||||
using Microsoft.Azure.WebJobs.Host.TestCommon;
|
||||
using Microsoft.Azure.WebJobs.Logging;
|
||||
using Microsoft.Azure.WebJobs.ServiceBus.Config;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.Azure.WebJobs.ServiceBus.UnitTests.Config
|
||||
{
|
||||
public class ServiceBusOptionsTests
|
||||
{
|
||||
private readonly ILoggerFactory _loggerFactory;
|
||||
private readonly TestLoggerProvider _loggerProvider;
|
||||
|
||||
public ServiceBusOptionsTests()
|
||||
{
|
||||
_loggerFactory = new LoggerFactory();
|
||||
_loggerProvider = new TestLoggerProvider();
|
||||
_loggerFactory.AddProvider(_loggerProvider);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_SetsExpectedDefaults()
|
||||
{
|
||||
ServiceBusOptions config = new ServiceBusOptions();
|
||||
Assert.Equal(16, config.MessageHandlerOptions.MaxConcurrentCalls);
|
||||
Assert.Equal(0, config.PrefetchCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PrefetchCount_GetSet()
|
||||
{
|
||||
ServiceBusOptions config = new ServiceBusOptions();
|
||||
Assert.Equal(0, config.PrefetchCount);
|
||||
config.PrefetchCount = 100;
|
||||
Assert.Equal(100, config.PrefetchCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LogExceptionReceivedEvent_NonTransientEvent_LoggedAsError()
|
||||
{
|
||||
var ex = new ServiceBusException(false);
|
||||
Assert.False(ex.IsTransient);
|
||||
ExceptionReceivedEventArgs e = new ExceptionReceivedEventArgs(ex, "TestAction", "TestEndpoint", "TestEntity", "TestClient");
|
||||
ServiceBusExtensionConfigProvider.LogExceptionReceivedEvent(e, _loggerFactory);
|
||||
|
||||
var expectedMessage = $"MessageReceiver error (Action=TestAction, ClientId=TestClient, EntityPath=TestEntity, Endpoint=TestEndpoint)";
|
||||
var logMessage = _loggerProvider.GetAllLogMessages().Single();
|
||||
Assert.Equal(LogLevel.Error, logMessage.Level);
|
||||
Assert.Same(ex, logMessage.Exception);
|
||||
Assert.Equal(expectedMessage, logMessage.FormattedMessage);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LogExceptionReceivedEvent_TransientEvent_LoggedAsInformation()
|
||||
{
|
||||
var ex = new ServiceBusException(true);
|
||||
Assert.True(ex.IsTransient);
|
||||
ExceptionReceivedEventArgs e = new ExceptionReceivedEventArgs(ex, "TestAction", "TestEndpoint", "TestEntity", "TestClient");
|
||||
ServiceBusExtensionConfigProvider.LogExceptionReceivedEvent(e, _loggerFactory);
|
||||
|
||||
var expectedMessage = $"MessageReceiver error (Action=TestAction, ClientId=TestClient, EntityPath=TestEntity, Endpoint=TestEndpoint)";
|
||||
var logMessage = _loggerProvider.GetAllLogMessages().Single();
|
||||
Assert.Equal(LogLevel.Information, logMessage.Level);
|
||||
Assert.Same(ex, logMessage.Exception);
|
||||
Assert.Equal(expectedMessage, logMessage.FormattedMessage);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LogExceptionReceivedEvent_NonMessagingException_LoggedAsError()
|
||||
{
|
||||
var ex = new MissingMethodException("What method??");
|
||||
ExceptionReceivedEventArgs e = new ExceptionReceivedEventArgs(ex, "TestAction", "TestEndpoint", "TestEntity", "TestClient");
|
||||
ServiceBusExtensionConfigProvider.LogExceptionReceivedEvent(e, _loggerFactory);
|
||||
|
||||
var expectedMessage = $"MessageReceiver error (Action=TestAction, ClientId=TestClient, EntityPath=TestEntity, Endpoint=TestEndpoint)";
|
||||
var logMessage = _loggerProvider.GetAllLogMessages().Single();
|
||||
Assert.Equal(LogLevel.Error, logMessage.Level);
|
||||
Assert.Same(ex, logMessage.Exception);
|
||||
Assert.Equal(expectedMessage, logMessage.FormattedMessage);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,107 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.ServiceBus;
|
||||
using Microsoft.Azure.ServiceBus.Core;
|
||||
using Microsoft.Azure.WebJobs.Host.Executors;
|
||||
using Microsoft.Azure.WebJobs.ServiceBus.Listeners;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.Azure.WebJobs.ServiceBus.UnitTests.Listeners
|
||||
{
|
||||
public class ServiceBusListenerTests
|
||||
{
|
||||
//private readonly MessagingFactory _messagingFactory;
|
||||
private readonly ServiceBusListener _listener;
|
||||
private readonly Mock<ITriggeredFunctionExecutor> _mockExecutor;
|
||||
private readonly Mock<MessagingProvider> _mockMessagingProvider;
|
||||
private readonly Mock<MessageProcessor> _mockMessageProcessor;
|
||||
private readonly string _entityPath = "test-entity-path";
|
||||
private readonly string _testConnection = "Endpoint=sb://test.servicebus.windows.net/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=abc123=";
|
||||
|
||||
public ServiceBusListenerTests()
|
||||
{
|
||||
_mockExecutor = new Mock<ITriggeredFunctionExecutor>(MockBehavior.Strict);
|
||||
|
||||
MessageHandlerOptions messageOptions = new MessageHandlerOptions(ExceptionReceivedHandler);
|
||||
MessageReceiver messageReceiver = new MessageReceiver(_testConnection, _entityPath);
|
||||
_mockMessageProcessor = new Mock<MessageProcessor>(MockBehavior.Strict, messageReceiver, messageOptions);
|
||||
|
||||
ServiceBusOptions config = new ServiceBusOptions
|
||||
{
|
||||
MessageHandlerOptions = messageOptions
|
||||
};
|
||||
_mockMessagingProvider = new Mock<MessagingProvider>(MockBehavior.Strict, new OptionsWrapper<ServiceBusOptions>(config));
|
||||
|
||||
_mockMessagingProvider.Setup(p => p.CreateMessageProcessor(_entityPath, _testConnection))
|
||||
.Returns(_mockMessageProcessor.Object);
|
||||
|
||||
ServiceBusTriggerExecutor triggerExecutor = new ServiceBusTriggerExecutor(_mockExecutor.Object);
|
||||
var mockServiceBusAccount = new Mock<ServiceBusAccount>(MockBehavior.Strict);
|
||||
mockServiceBusAccount.Setup(a => a.ConnectionString).Returns(_testConnection);
|
||||
|
||||
_listener = new ServiceBusListener(_entityPath, triggerExecutor, config, mockServiceBusAccount.Object, _mockMessagingProvider.Object);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ProcessMessageAsync_Success()
|
||||
{
|
||||
var message = new CustomMessage();
|
||||
var systemProperties = new Message.SystemPropertiesCollection();
|
||||
typeof(Message.SystemPropertiesCollection).GetProperty("SequenceNumber").SetValue(systemProperties, 1);
|
||||
typeof(Message.SystemPropertiesCollection).GetProperty("DeliveryCount").SetValue(systemProperties, 55);
|
||||
typeof(Message.SystemPropertiesCollection).GetProperty("EnqueuedTimeUtc").SetValue(systemProperties, DateTime.Now);
|
||||
typeof(Message.SystemPropertiesCollection).GetProperty("LockedUntilUtc").SetValue(systemProperties, DateTime.Now);
|
||||
typeof(Message).GetProperty("SystemProperties").SetValue(message, systemProperties);
|
||||
|
||||
message.MessageId = Guid.NewGuid().ToString();
|
||||
CancellationToken cancellationToken = new CancellationToken();
|
||||
_mockMessageProcessor.Setup(p => p.BeginProcessingMessageAsync(message, cancellationToken)).ReturnsAsync(true);
|
||||
|
||||
FunctionResult result = new FunctionResult(true);
|
||||
_mockExecutor.Setup(p => p.TryExecuteAsync(It.Is<TriggeredFunctionData>(q => q.TriggerValue == message), cancellationToken)).ReturnsAsync(result);
|
||||
|
||||
_mockMessageProcessor.Setup(p => p.CompleteProcessingMessageAsync(message, result, cancellationToken)).Returns(Task.FromResult(0));
|
||||
|
||||
await _listener.ProcessMessageAsync(message, CancellationToken.None);
|
||||
|
||||
_mockMessageProcessor.VerifyAll();
|
||||
_mockExecutor.VerifyAll();
|
||||
_mockMessageProcessor.VerifyAll();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ProcessMessageAsync_BeginProcessingReturnsFalse_MessageNotProcessed()
|
||||
{
|
||||
var message = new CustomMessage();
|
||||
message.MessageId = Guid.NewGuid().ToString();
|
||||
CancellationToken cancellationToken = new CancellationToken();
|
||||
_mockMessageProcessor.Setup(p => p.BeginProcessingMessageAsync(message, cancellationToken)).ReturnsAsync(false);
|
||||
|
||||
await _listener.ProcessMessageAsync(message, CancellationToken.None);
|
||||
|
||||
_mockMessageProcessor.VerifyAll();
|
||||
}
|
||||
|
||||
Task ExceptionReceivedHandler(ExceptionReceivedEventArgs eventArgs)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
// Mock calls ToString() for Mesage. This ckass fixes bug in azure-service-bus-dotnet.
|
||||
// https://github.com/Azure/azure-service-bus-dotnet/blob/dev/src/Microsoft.Azure.ServiceBus/Message.cs#L291
|
||||
internal class CustomMessage : Message
|
||||
{
|
||||
public override string ToString()
|
||||
{
|
||||
return MessageId;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.WebJobs.Host.Executors;
|
||||
using Microsoft.Azure.WebJobs.Host.Listeners;
|
||||
using Microsoft.Azure.WebJobs.Host.TestCommon;
|
||||
using Microsoft.Azure.WebJobs.ServiceBus.Listeners;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.Azure.WebJobs.ServiceBus.UnitTests.Listeners
|
||||
{
|
||||
public class ServiceBusQueueListenerFactoryTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task CreateAsync_Success()
|
||||
{
|
||||
var configuration = new ConfigurationBuilder()
|
||||
.AddEnvironmentVariables()
|
||||
.AddTestSettings()
|
||||
.Build();
|
||||
|
||||
var config = new ServiceBusOptions
|
||||
{
|
||||
ConnectionString = configuration.GetWebJobsConnectionString("ServiceBus")
|
||||
};
|
||||
|
||||
var messagingProvider = new MessagingProvider(new OptionsWrapper<ServiceBusOptions>(config));
|
||||
|
||||
var account = new ServiceBusAccount(config, configuration);
|
||||
Mock<ITriggeredFunctionExecutor> mockExecutor = new Mock<ITriggeredFunctionExecutor>(MockBehavior.Strict);
|
||||
ServiceBusQueueListenerFactory factory = new ServiceBusQueueListenerFactory(account, "testqueue", mockExecutor.Object, config, messagingProvider);
|
||||
|
||||
IListener listener = await factory.CreateAsync(CancellationToken.None);
|
||||
Assert.NotNull(listener);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,61 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.ServiceBus;
|
||||
using Microsoft.Azure.ServiceBus.Core;
|
||||
using Microsoft.Azure.WebJobs.Host.Executors;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.Azure.WebJobs.ServiceBus.UnitTests
|
||||
{
|
||||
public class MessageProcessorTests
|
||||
{
|
||||
private readonly MessageProcessor _processor;
|
||||
private readonly MessageHandlerOptions _options;
|
||||
|
||||
public MessageProcessorTests()
|
||||
{
|
||||
_options = new MessageHandlerOptions(ExceptionReceivedHandler);
|
||||
MessageReceiver receiver = new MessageReceiver("Endpoint=sb://test.servicebus.windows.net/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=abc123=", "test-entity");
|
||||
_processor = new MessageProcessor(receiver, _options);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CompleteProcessingMessageAsync_Failure_PropagatesException()
|
||||
{
|
||||
_options.AutoComplete = false;
|
||||
|
||||
Message message = new Message();
|
||||
var functionException = new InvalidOperationException("Kaboom!");
|
||||
FunctionResult result = new FunctionResult(functionException);
|
||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(async () =>
|
||||
{
|
||||
await _processor.CompleteProcessingMessageAsync(message, result, CancellationToken.None);
|
||||
});
|
||||
|
||||
Assert.Same(functionException, ex);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CompleteProcessingMessageAsync_DefaultOnMessageOptions()
|
||||
{
|
||||
Message message = new Message();
|
||||
FunctionResult result = new FunctionResult(true);
|
||||
await _processor.CompleteProcessingMessageAsync(message, result, CancellationToken.None);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MessageOptions_ReturnsOptions()
|
||||
{
|
||||
Assert.Same(_options, _processor.MessageOptions);
|
||||
}
|
||||
|
||||
Task ExceptionReceivedHandler(ExceptionReceivedEventArgs eventArgs)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.WebJobs.ServiceBus.Triggers;
|
||||
using Microsoft.Azure.ServiceBus;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.Azure.WebJobs.ServiceBus.UnitTests
|
||||
{
|
||||
public class MessageToByteArrayConverterTests
|
||||
{
|
||||
private const string TestString = "This is a test!";
|
||||
|
||||
[Theory]
|
||||
[InlineData(ContentTypes.TextPlain)]
|
||||
[InlineData(ContentTypes.ApplicationJson)]
|
||||
[InlineData(ContentTypes.ApplicationOctetStream)]
|
||||
[InlineData("some-other-contenttype")]
|
||||
[InlineData(null)]
|
||||
public async Task ConvertAsync_ReturnsExpectedResults(string contentType)
|
||||
{
|
||||
Message message = new Message(Encoding.UTF8.GetBytes(TestString));
|
||||
message.ContentType = contentType;
|
||||
MessageToByteArrayConverter converter = new MessageToByteArrayConverter();
|
||||
|
||||
byte[] result = await converter.ConvertAsync(message, CancellationToken.None);
|
||||
string decoded = Encoding.UTF8.GetString(result);
|
||||
Assert.Equal(TestString, decoded);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,92 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.WebJobs.ServiceBus.Triggers;
|
||||
using Microsoft.Azure.ServiceBus;
|
||||
using Xunit;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Azure.ServiceBus.InteropExtensions;
|
||||
|
||||
namespace Microsoft.Azure.WebJobs.ServiceBus.UnitTests
|
||||
{
|
||||
public class MessageToStringConverterTests
|
||||
{
|
||||
private const string TestString = "This is a test!";
|
||||
private const string TestJson = "{ value: 'This is a test!' }";
|
||||
|
||||
[Theory]
|
||||
[InlineData(ContentTypes.TextPlain, TestString)]
|
||||
[InlineData(ContentTypes.ApplicationJson, TestJson)]
|
||||
[InlineData(ContentTypes.ApplicationOctetStream, TestString)]
|
||||
[InlineData(null, TestJson)]
|
||||
[InlineData("application/xml", TestJson)]
|
||||
[InlineData(ContentTypes.TextPlain, null)]
|
||||
public async Task ConvertAsync_ReturnsExpectedResult_WithBinarySerializer(string contentType, string value)
|
||||
{
|
||||
byte[] bytes;
|
||||
using (MemoryStream ms = new MemoryStream())
|
||||
{
|
||||
DataContractBinarySerializer<string>.Instance.WriteObject(ms, value);
|
||||
bytes = ms.ToArray();
|
||||
}
|
||||
|
||||
Message message = new Message(bytes);
|
||||
message.ContentType = contentType;
|
||||
|
||||
MessageToStringConverter converter = new MessageToStringConverter();
|
||||
string result = await converter.ConvertAsync(message, CancellationToken.None);
|
||||
|
||||
Assert.Equal(value, result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(ContentTypes.TextPlain, TestString)]
|
||||
[InlineData(ContentTypes.ApplicationJson, TestJson)]
|
||||
[InlineData(ContentTypes.ApplicationOctetStream, TestString)]
|
||||
[InlineData(null, TestJson)]
|
||||
[InlineData("application/xml", TestJson)]
|
||||
[InlineData(ContentTypes.TextPlain, null)]
|
||||
[InlineData(ContentTypes.TextPlain, "")]
|
||||
public async Task ConvertAsync_ReturnsExpectedResult_WithSerializedString(string contentType, string value)
|
||||
{
|
||||
Message message = new Message(value == null ? null : Encoding.UTF8.GetBytes(value));
|
||||
message.ContentType = contentType;
|
||||
|
||||
MessageToStringConverter converter = new MessageToStringConverter();
|
||||
string result = await converter.ConvertAsync(message, CancellationToken.None);
|
||||
Assert.Equal(value, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ConvertAsync_Throws_WithSerializedObject()
|
||||
{
|
||||
|
||||
byte[] bytes;
|
||||
using (MemoryStream ms = new MemoryStream())
|
||||
{
|
||||
DataContractBinarySerializer<TestObject>.Instance.WriteObject(ms, new TestObject() { Text = "Test" });
|
||||
bytes = ms.ToArray();
|
||||
}
|
||||
|
||||
Message message = new Message(bytes);
|
||||
|
||||
MessageToStringConverter converter = new MessageToStringConverter();
|
||||
var exception = await Assert.ThrowsAsync<InvalidOperationException>(() => converter.ConvertAsync(message, CancellationToken.None));
|
||||
|
||||
Assert.IsType<SerializationException>(exception.InnerException);
|
||||
Assert.StartsWith("The Message with ContentType 'null' failed to deserialize to a string with the message:", exception.Message);
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class TestObject
|
||||
{
|
||||
public string Text { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.Extensions.Options;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.Azure.WebJobs.ServiceBus.UnitTests
|
||||
{
|
||||
public class MessagingProviderTests
|
||||
{
|
||||
[Fact]
|
||||
public void CreateMessageReceiver_ReturnsExpectedReceiver()
|
||||
{
|
||||
string defaultConnection = "Endpoint=sb://default.servicebus.windows.net/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=abc123=";
|
||||
var config = new ServiceBusOptions
|
||||
{
|
||||
ConnectionString = defaultConnection
|
||||
};
|
||||
var provider = new MessagingProvider(new OptionsWrapper<ServiceBusOptions>(config));
|
||||
var receiver = provider.CreateMessageReceiver("entityPath", defaultConnection);
|
||||
Assert.Equal("entityPath", receiver.Path);
|
||||
|
||||
var receiver2 = provider.CreateMessageReceiver("entityPath", defaultConnection);
|
||||
Assert.Same(receiver, receiver2);
|
||||
|
||||
config.PrefetchCount = 100;
|
||||
receiver = provider.CreateMessageReceiver("entityPath1", defaultConnection);
|
||||
Assert.Equal(100, receiver.PrefetchCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateMessageSender_ReturnsExpectedSender()
|
||||
{
|
||||
string defaultConnection = "Endpoint=sb://default.servicebus.windows.net/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=abc123=";
|
||||
var config = new ServiceBusOptions
|
||||
{
|
||||
ConnectionString = defaultConnection
|
||||
};
|
||||
var provider = new MessagingProvider(new OptionsWrapper<ServiceBusOptions>(config));
|
||||
var sender = provider.CreateMessageSender("entityPath", defaultConnection);
|
||||
Assert.Equal("entityPath", sender.Path);
|
||||
|
||||
var sender2 = provider.CreateMessageSender("entityPath", defaultConnection);
|
||||
Assert.Same(sender, sender2);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
using System.Reflection;
|
||||
using Xunit;
|
||||
|
||||
[assembly: CollectionBehavior(CollectionBehavior.CollectionPerAssembly)]
|
|
@ -1,33 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.Azure.WebJobs.Host.TestCommon;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.Azure.WebJobs.Host.UnitTests
|
||||
{
|
||||
public class PublicSurfaceTests
|
||||
{
|
||||
[Fact]
|
||||
public void WebJobs_Extensions_ServiceBus_VerifyPublicSurfaceArea()
|
||||
{
|
||||
var assembly = typeof(ServiceBusAttribute).Assembly;
|
||||
|
||||
var expected = new[]
|
||||
{
|
||||
"Constants",
|
||||
"EntityType",
|
||||
"MessageProcessor",
|
||||
"MessagingProvider",
|
||||
"ServiceBusAccountAttribute",
|
||||
"ServiceBusAttribute",
|
||||
"ServiceBusTriggerAttribute",
|
||||
"ServiceBusHostBuilderExtensions",
|
||||
"ServiceBusOptions",
|
||||
"ServiceBusWebJobsStartup"
|
||||
};
|
||||
|
||||
TestHelpers.AssertPublicTypes(expected, assembly);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,59 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.Azure.WebJobs.ServiceBus.UnitTests
|
||||
{
|
||||
public class ServiceBusAccountTests
|
||||
{
|
||||
private readonly IConfiguration _configuration;
|
||||
|
||||
public ServiceBusAccountTests()
|
||||
{
|
||||
_configuration = new ConfigurationBuilder()
|
||||
.AddEnvironmentVariables()
|
||||
.Build();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetConnectionString_ReturnsExpectedConnectionString()
|
||||
{
|
||||
string defaultConnection = "Endpoint=sb://default.servicebus.windows.net/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=abc123=";
|
||||
var options = new ServiceBusOptions()
|
||||
{
|
||||
ConnectionString = defaultConnection
|
||||
};
|
||||
var attribute = new ServiceBusTriggerAttribute("entity-name");
|
||||
var account = new ServiceBusAccount(options, _configuration, attribute);
|
||||
|
||||
Assert.True(defaultConnection == account.ConnectionString);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetConnectionString_ThrowsIfConnectionStringNullOrEmpty()
|
||||
{
|
||||
var config = new ServiceBusOptions();
|
||||
var attribute = new ServiceBusTriggerAttribute("testqueue");
|
||||
attribute.Connection = "MissingConnection";
|
||||
|
||||
var ex = Assert.Throws<InvalidOperationException>(() =>
|
||||
{
|
||||
var account = new ServiceBusAccount(config, _configuration, attribute);
|
||||
var cs = account.ConnectionString;
|
||||
});
|
||||
Assert.Equal("Microsoft Azure WebJobs SDK ServiceBus connection string 'MissingConnection' is missing or empty.", ex.Message);
|
||||
|
||||
attribute.Connection = null;
|
||||
config.ConnectionString = null;
|
||||
ex = Assert.Throws<InvalidOperationException>(() =>
|
||||
{
|
||||
var account = new ServiceBusAccount(config, _configuration, attribute);
|
||||
var cs = account.ConnectionString;
|
||||
});
|
||||
Assert.Equal("Microsoft Azure WebJobs SDK ServiceBus connection string 'AzureWebJobsServiceBus' is missing or empty.", ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.Azure.WebJobs.ServiceBus.UnitTests
|
||||
{
|
||||
public class ServiceBusAttributeTests
|
||||
{
|
||||
[Fact]
|
||||
public void Constructor_Success()
|
||||
{
|
||||
ServiceBusAttribute attribute = new ServiceBusAttribute("testqueue");
|
||||
Assert.Equal("testqueue", attribute.QueueOrTopicName);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.Azure.WebJobs.ServiceBus.UnitTests
|
||||
{
|
||||
public class ServiceBusTriggerAttributeTests
|
||||
{
|
||||
[Fact]
|
||||
public void Constructor_Queue_SetsExpectedValues()
|
||||
{
|
||||
ServiceBusTriggerAttribute attribute = new ServiceBusTriggerAttribute("testqueue");
|
||||
Assert.Equal("testqueue", attribute.QueueName);
|
||||
Assert.Null(attribute.SubscriptionName);
|
||||
Assert.Null(attribute.TopicName);
|
||||
|
||||
attribute = new ServiceBusTriggerAttribute("testqueue");
|
||||
Assert.Equal("testqueue", attribute.QueueName);
|
||||
Assert.Null(attribute.SubscriptionName);
|
||||
Assert.Null(attribute.TopicName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_Topic_SetsExpectedValues()
|
||||
{
|
||||
ServiceBusTriggerAttribute attribute = new ServiceBusTriggerAttribute("testtopic", "testsubscription");
|
||||
Assert.Null(attribute.QueueName);
|
||||
Assert.Equal("testtopic", attribute.TopicName);
|
||||
Assert.Equal("testsubscription", attribute.SubscriptionName);
|
||||
|
||||
attribute = new ServiceBusTriggerAttribute("testtopic", "testsubscription");
|
||||
Assert.Null(attribute.QueueName);
|
||||
Assert.Equal("testtopic", attribute.TopicName);
|
||||
Assert.Equal("testsubscription", attribute.SubscriptionName);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.WebJobs.Host.Executors;
|
||||
using Microsoft.Azure.WebJobs.Host.Listeners;
|
||||
using Microsoft.Azure.WebJobs.Host.TestCommon;
|
||||
using Microsoft.Azure.WebJobs.ServiceBus.Listeners;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.Azure.WebJobs.ServiceBus.UnitTests.Listeners
|
||||
{
|
||||
public class ServiceBusSubscriptionListenerFactoryTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task CreateAsync_Success()
|
||||
{
|
||||
var configuration = new ConfigurationBuilder()
|
||||
.AddEnvironmentVariables()
|
||||
.AddTestSettings()
|
||||
.Build();
|
||||
|
||||
var config = new ServiceBusOptions
|
||||
{
|
||||
ConnectionString = configuration.GetWebJobsConnectionString("ServiceBus")
|
||||
};
|
||||
|
||||
var messagingProvider = new MessagingProvider(new OptionsWrapper<ServiceBusOptions>(config));
|
||||
|
||||
var account = new ServiceBusAccount(config, configuration);
|
||||
Mock<ITriggeredFunctionExecutor> mockExecutor = new Mock<ITriggeredFunctionExecutor>(MockBehavior.Strict);
|
||||
ServiceBusSubscriptionListenerFactory factory = new ServiceBusSubscriptionListenerFactory(account, "testtopic", "testsubscription", mockExecutor.Object, config, messagingProvider);
|
||||
|
||||
IListener listener = await factory.CreateAsync(CancellationToken.None);
|
||||
Assert.NotNull(listener);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,89 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using Microsoft.Azure.ServiceBus;
|
||||
using Microsoft.Azure.WebJobs.Host.Bindings;
|
||||
using Microsoft.Azure.WebJobs.Host.TestCommon;
|
||||
using Microsoft.Azure.WebJobs.Host.Triggers;
|
||||
using Microsoft.Azure.WebJobs.ServiceBus.Triggers;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Newtonsoft.Json;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.Azure.WebJobs.ServiceBus.UnitTests.Triggers
|
||||
{
|
||||
public class ServiceBusTriggerBindingIntegrationTests : IClassFixture<InvariantCultureFixture>
|
||||
{
|
||||
private ITriggerBinding _queueBinding;
|
||||
private ITriggerBinding _topicBinding;
|
||||
|
||||
public ServiceBusTriggerBindingIntegrationTests()
|
||||
{
|
||||
IQueueTriggerArgumentBindingProvider provider = new UserTypeArgumentBindingProvider();
|
||||
ParameterInfo pi = new StubParameterInfo("parameterName", typeof(UserDataType));
|
||||
var argumentBinding = provider.TryCreate(pi);
|
||||
var options = new ServiceBusOptions();
|
||||
var messagingProvider = new MessagingProvider(new OptionsWrapper<ServiceBusOptions>(options));
|
||||
_queueBinding = new ServiceBusTriggerBinding("parameterName", typeof(UserDataType), argumentBinding, null, options, messagingProvider, "queueName");
|
||||
_topicBinding = new ServiceBusTriggerBinding("parameterName", typeof(UserDataType), argumentBinding, null, options, messagingProvider, "subscriptionName", "topicName");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("RequestId", "4b957741-c22e-471d-9f0f-e1e8534b9cb6")]
|
||||
[InlineData("RequestReceivedTime", "8/16/2014 12:09:36 AM")]
|
||||
[InlineData("DeliveryCount", "8")]
|
||||
[InlineData("IsSuccess", "False")]
|
||||
public void BindAsync_IfUserDataType_ReturnsValidBindingData(string userPropertyName, string userPropertyValue)
|
||||
{
|
||||
// Arrange
|
||||
UserDataType expectedObject = new UserDataType();
|
||||
PropertyInfo userProperty = typeof(UserDataType).GetProperty(userPropertyName);
|
||||
var parseMethod = userProperty.PropertyType.GetMethod(
|
||||
"Parse", new Type[] { typeof(string) });
|
||||
object convertedPropertyValue = parseMethod.Invoke(null, new object[] { userPropertyValue });
|
||||
userProperty.SetValue(expectedObject, convertedPropertyValue);
|
||||
string messageContent = JsonConvert.SerializeObject(expectedObject);
|
||||
ValueBindingContext context = new ValueBindingContext(null, CancellationToken.None);
|
||||
|
||||
Action<ITriggerBinding> testBinding = (b) =>
|
||||
{
|
||||
// Act
|
||||
Message message = new Message(Encoding.UTF8.GetBytes(messageContent));
|
||||
message.ContentType = ContentTypes.ApplicationJson;
|
||||
ITriggerData data = _queueBinding.BindAsync(message, context).GetAwaiter().GetResult();
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(data);
|
||||
Assert.NotNull(data.ValueProvider);
|
||||
Assert.NotNull(data.BindingData);
|
||||
Assert.True(data.BindingData.ContainsKey(userPropertyName));
|
||||
Assert.Equal(userProperty.GetValue(expectedObject, null), data.BindingData[userPropertyName]);
|
||||
};
|
||||
|
||||
testBinding(_queueBinding);
|
||||
testBinding(_topicBinding);
|
||||
}
|
||||
|
||||
private class StubParameterInfo : ParameterInfo
|
||||
{
|
||||
public StubParameterInfo(string name, Type type)
|
||||
{
|
||||
NameImpl = name;
|
||||
ClassImpl = type;
|
||||
}
|
||||
}
|
||||
|
||||
public class UserDataType
|
||||
{
|
||||
public Guid RequestId { get; set; }
|
||||
public string BlobFile { get; set; }
|
||||
public DateTime RequestReceivedTime { get; set; }
|
||||
public Int32 DeliveryCount { get; set; }
|
||||
public Boolean IsSuccess { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,61 +0,0 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="..\..\build\common.props" />
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp2.1</TargetFramework>
|
||||
<IsPackable>false</IsPackable>
|
||||
<AssemblyName>Microsoft.Azure.WebJobs.ServiceBus.UnitTests</AssemblyName>
|
||||
<RootNamespace>Microsoft.Azure.WebJobs.ServiceBus.UnitTests</RootNamespace>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
<StyleCopTreatErrorsAsWarnings>false</StyleCopTreatErrorsAsWarnings>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<WarningsAsErrors />
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<StyleCopTreatErrorsAsWarnings>false</StyleCopTreatErrorsAsWarnings>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<WarningsAsErrors />
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Remove="EventHubTests.cs" />
|
||||
<Compile Remove="Listeners\EventHubListenerTests.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<AdditionalFiles Include="..\..\stylecop.json" Link="stylecop.json" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Azure.ServiceBus" Version="3.0.2" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.1.0-beta004">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http" Version="2.1.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.1.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.5.0" />
|
||||
<PackageReference Include="Moq" Version="4.7.145" />
|
||||
<PackageReference Include="WindowsAzure.Storage" Version="9.3.1" />
|
||||
<PackageReference Include="xunit" Version="2.3.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.1" />
|
||||
<DotNetCliToolReference Include="dotnet-xunit" Version="2.3.0-beta3-build3705" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Microsoft.Azure.WebJobs.Extensions.ServiceBus\WebJobs.Extensions.ServiceBus.csproj" />
|
||||
<ProjectReference Include="..\..\src\Microsoft.Azure.WebJobs.Host\WebJobs.Host.csproj" />
|
||||
<ProjectReference Include="..\..\src\Microsoft.Azure.WebJobs.Logging.ApplicationInsights\WebJobs.Logging.ApplicationInsights.csproj" />
|
||||
<ProjectReference Include="..\..\src\Microsoft.Azure.WebJobs.Logging\WebJobs.Logging.csproj" />
|
||||
<ProjectReference Include="..\Microsoft.Azure.WebJobs.Host.TestCommon\WebJobs.Host.TestCommon.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="appsettings.json">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -1,509 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.ServiceBus;
|
||||
using Microsoft.Azure.ServiceBus.Core;
|
||||
using Microsoft.Azure.WebJobs.Host.TestCommon;
|
||||
using Microsoft.Azure.WebJobs.ServiceBus;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.Azure.WebJobs.Host.EndToEndTests
|
||||
{
|
||||
public class ServiceBusEndToEndTests : IDisposable
|
||||
{
|
||||
private const string SecondaryConnectionStringKey = "ServiceBusSecondary";
|
||||
private const string Prefix = "core-test-";
|
||||
private const string FirstQueueName = Prefix + "queue1";
|
||||
private const string SecondQueueName = Prefix + "queue2";
|
||||
private const string BinderQueueName = Prefix + "queue3";
|
||||
|
||||
private const string TopicName = Prefix + "topic1";
|
||||
private const string TopicSubscriptionName1 = "sub1";
|
||||
private const string TopicSubscriptionName2 = "sub2";
|
||||
|
||||
private const string TriggerDetailsMessageStart = "Trigger Details:";
|
||||
|
||||
private const int SBTimeout = 60 * 1000;
|
||||
|
||||
private static EventWaitHandle _topicSubscriptionCalled1;
|
||||
private static EventWaitHandle _topicSubscriptionCalled2;
|
||||
|
||||
// These two variables will be checked at the end of the test
|
||||
private static string _resultMessage1;
|
||||
private static string _resultMessage2;
|
||||
|
||||
private readonly RandomNameResolver _nameResolver;
|
||||
private readonly string _primaryConnectionString;
|
||||
private readonly string _secondaryConnectionString;
|
||||
|
||||
public ServiceBusEndToEndTests()
|
||||
{
|
||||
var config = new ConfigurationBuilder()
|
||||
.AddEnvironmentVariables()
|
||||
.AddTestSettings()
|
||||
.Build();
|
||||
|
||||
_primaryConnectionString = config.GetConnectionString(ServiceBus.Constants.DefaultConnectionStringName);
|
||||
_secondaryConnectionString = config.GetConnectionString(SecondaryConnectionStringKey);
|
||||
|
||||
_nameResolver = new RandomNameResolver();
|
||||
|
||||
Cleanup().GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ServiceBusEndToEnd()
|
||||
{
|
||||
await ServiceBusEndToEndInternal();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ServiceBusBinderTest()
|
||||
{
|
||||
var hostType = typeof(ServiceBusTestJobs);
|
||||
var host = CreateHost();
|
||||
var method = typeof(ServiceBusTestJobs).GetMethod("ServiceBusBinderTest");
|
||||
|
||||
int numMessages = 10;
|
||||
var args = new { message = "Test Message", numMessages = numMessages };
|
||||
var jobHost = host.GetJobHost<ServiceBusTestJobs>();
|
||||
await jobHost.CallAsync(method, args);
|
||||
await jobHost.CallAsync(method, args);
|
||||
await jobHost.CallAsync(method, args);
|
||||
|
||||
var count = await CleanUpEntity(BinderQueueName);
|
||||
|
||||
Assert.Equal(numMessages * 3, count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CustomMessageProcessorTest()
|
||||
{
|
||||
IHost host = new HostBuilder()
|
||||
.ConfigureDefaultTestHost<ServiceBusTestJobs>(b =>
|
||||
{
|
||||
b.AddAzureStorage()
|
||||
.AddServiceBus();
|
||||
})
|
||||
.ConfigureServices(services =>
|
||||
{
|
||||
services.AddSingleton<MessagingProvider, CustomMessagingProvider>();
|
||||
})
|
||||
.Build();
|
||||
|
||||
var loggerProvider = host.GetTestLoggerProvider();
|
||||
|
||||
await ServiceBusEndToEndInternal(host: host);
|
||||
|
||||
// in addition to verifying that our custom processor was called, we're also
|
||||
// verifying here that extensions can log
|
||||
IEnumerable<LogMessage> messages = loggerProvider.GetAllLogMessages().Where(m => m.Category == CustomMessagingProvider.CustomMessagingCategory);
|
||||
Assert.Equal(4, messages.Count(p => p.FormattedMessage.Contains("Custom processor Begin called!")));
|
||||
Assert.Equal(4, messages.Count(p => p.FormattedMessage.Contains("Custom processor End called!")));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task MultipleAccountTest()
|
||||
{
|
||||
IHost host = new HostBuilder()
|
||||
.ConfigureDefaultTestHost<ServiceBusTestJobs>(b =>
|
||||
{
|
||||
b.AddAzureStorage()
|
||||
.AddServiceBus();
|
||||
}, nameResolver: _nameResolver)
|
||||
.ConfigureServices(services =>
|
||||
{
|
||||
services.AddSingleton<MessagingProvider, CustomMessagingProvider>();
|
||||
})
|
||||
.Build();
|
||||
|
||||
await WriteQueueMessage(_secondaryConnectionString, FirstQueueName, "Test");
|
||||
|
||||
_topicSubscriptionCalled1 = new ManualResetEvent(initialState: false);
|
||||
|
||||
await host.StartAsync();
|
||||
|
||||
_topicSubscriptionCalled1.WaitOne(SBTimeout);
|
||||
|
||||
// ensure all logs have had a chance to flush
|
||||
await Task.Delay(3000);
|
||||
|
||||
// Wait for the host to terminate
|
||||
await host.StopAsync();
|
||||
host.Dispose();
|
||||
|
||||
Assert.Equal("Test-topic-1", _resultMessage1);
|
||||
}
|
||||
|
||||
private async Task<int> CleanUpEntity(string queueName, string connectionString = null)
|
||||
{
|
||||
var messageReceiver = new MessageReceiver(!string.IsNullOrEmpty(connectionString) ? connectionString : _primaryConnectionString, queueName, ReceiveMode.ReceiveAndDelete);
|
||||
Message message;
|
||||
int count = 0;
|
||||
do
|
||||
{
|
||||
message = await messageReceiver.ReceiveAsync(TimeSpan.FromSeconds(3)).ConfigureAwait(false);
|
||||
if (message != null)
|
||||
{
|
||||
count++;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
} while (true);
|
||||
await messageReceiver.CloseAsync();
|
||||
return count;
|
||||
}
|
||||
|
||||
private async Task Cleanup()
|
||||
{
|
||||
await CleanUpEntity(FirstQueueName);
|
||||
await CleanUpEntity(SecondQueueName);
|
||||
await CleanUpEntity(BinderQueueName);
|
||||
await CleanUpEntity(FirstQueueName, _secondaryConnectionString);
|
||||
|
||||
await CleanUpEntity(EntityNameHelper.FormatSubscriptionPath(TopicName, TopicSubscriptionName1));
|
||||
await CleanUpEntity(EntityNameHelper.FormatSubscriptionPath(TopicName, TopicSubscriptionName2));
|
||||
}
|
||||
|
||||
private IHost CreateHost()
|
||||
{
|
||||
return new HostBuilder()
|
||||
.ConfigureDefaultTestHost<ServiceBusTestJobs>(b =>
|
||||
{
|
||||
b.AddAzureStorage()
|
||||
.AddServiceBus();
|
||||
})
|
||||
.ConfigureServices(services =>
|
||||
{
|
||||
services.AddSingleton<INameResolver>(_nameResolver);
|
||||
})
|
||||
.Build();
|
||||
}
|
||||
|
||||
private async Task ServiceBusEndToEndInternal(IHost host = null)
|
||||
{
|
||||
if (host == null)
|
||||
{
|
||||
host = CreateHost();
|
||||
}
|
||||
|
||||
var jobContainerType = typeof(ServiceBusTestJobs);
|
||||
|
||||
await WriteQueueMessage(_primaryConnectionString, FirstQueueName, "E2E");
|
||||
|
||||
_topicSubscriptionCalled1 = new ManualResetEvent(initialState: false);
|
||||
_topicSubscriptionCalled2 = new ManualResetEvent(initialState: false);
|
||||
|
||||
using (host)
|
||||
{
|
||||
await host.StartAsync();
|
||||
|
||||
_topicSubscriptionCalled1.WaitOne(SBTimeout);
|
||||
_topicSubscriptionCalled2.WaitOne(SBTimeout);
|
||||
|
||||
// ensure all logs have had a chance to flush
|
||||
await Task.Delay(4000);
|
||||
|
||||
// Wait for the host to terminate
|
||||
await host.StopAsync();
|
||||
|
||||
Assert.Equal("E2E-SBQueue2SBQueue-SBQueue2SBTopic-topic-1", _resultMessage1);
|
||||
Assert.Equal("E2E-SBQueue2SBQueue-SBQueue2SBTopic-topic-2", _resultMessage2);
|
||||
|
||||
IEnumerable<LogMessage> logMessages = host.GetTestLoggerProvider()
|
||||
.GetAllLogMessages();
|
||||
|
||||
// filter out anything from the custom processor for easier validation.
|
||||
IEnumerable<LogMessage> consoleOutput = logMessages
|
||||
.Where(m => m.Category != CustomMessagingProvider.CustomMessagingCategory);
|
||||
|
||||
Assert.DoesNotContain(consoleOutput, p => p.Level == LogLevel.Error);
|
||||
|
||||
string[] consoleOutputLines = consoleOutput
|
||||
.Where(p => p.FormattedMessage != null)
|
||||
.SelectMany(p => p.FormattedMessage.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries))
|
||||
.OrderBy(p => p)
|
||||
.ToArray();
|
||||
|
||||
string[] expectedOutputLines = new string[]
|
||||
{
|
||||
"Found the following functions:",
|
||||
$"{jobContainerType.FullName}.SBQueue2SBQueue",
|
||||
$"{jobContainerType.FullName}.MultipleAccounts",
|
||||
$"{jobContainerType.FullName}.SBQueue2SBTopic",
|
||||
$"{jobContainerType.FullName}.SBTopicListener1",
|
||||
$"{jobContainerType.FullName}.SBTopicListener2",
|
||||
$"{jobContainerType.FullName}.ServiceBusBinderTest",
|
||||
"Job host started",
|
||||
$"Executing '{jobContainerType.Name}.SBQueue2SBQueue' (Reason='New ServiceBus message detected on '{FirstQueueName}'.', Id=",
|
||||
$"Executed '{jobContainerType.Name}.SBQueue2SBQueue' (Succeeded, Id=",
|
||||
$"Trigger Details:",
|
||||
$"Executing '{jobContainerType.Name}.SBQueue2SBTopic' (Reason='New ServiceBus message detected on '{SecondQueueName}'.', Id=",
|
||||
$"Executed '{jobContainerType.Name}.SBQueue2SBTopic' (Succeeded, Id=",
|
||||
$"Trigger Details:",
|
||||
$"Executing '{jobContainerType.Name}.SBTopicListener1' (Reason='New ServiceBus message detected on '{EntityNameHelper.FormatSubscriptionPath(TopicName, TopicSubscriptionName1)}'.', Id=",
|
||||
$"Executed '{jobContainerType.Name}.SBTopicListener1' (Succeeded, Id=",
|
||||
$"Trigger Details:",
|
||||
$"Executing '{jobContainerType.Name}.SBTopicListener2' (Reason='New ServiceBus message detected on '{EntityNameHelper.FormatSubscriptionPath(TopicName, TopicSubscriptionName2)}'.', Id=",
|
||||
$"Executed '{jobContainerType.Name}.SBTopicListener2' (Succeeded, Id=",
|
||||
$"Trigger Details:",
|
||||
"Job host stopped",
|
||||
"Starting JobHost",
|
||||
"Stopping JobHost",
|
||||
"BlobsOptions",
|
||||
"{",
|
||||
" \"CentralizedPoisonQueue\": false",
|
||||
"}",
|
||||
"FunctionResultAggregatorOptions",
|
||||
"{",
|
||||
" \"BatchSize\": 1000",
|
||||
" \"FlushTimeout\": \"00:00:30\",",
|
||||
" \"IsEnabled\": true",
|
||||
"}",
|
||||
"LoggerFilterOptions",
|
||||
"{",
|
||||
" \"MinLevel\": \"Information\"",
|
||||
" \"Rules\": []",
|
||||
"}",
|
||||
"QueuesOptions",
|
||||
"{",
|
||||
" \"BatchSize\": 16",
|
||||
" \"MaxDequeueCount\": 5,",
|
||||
" \"MaxPollingInterval\": \"00:00:02\",",
|
||||
" \"NewBatchThreshold\": 8,",
|
||||
" \"VisibilityTimeout\": \"00:00:00\"",
|
||||
"}",
|
||||
"ServiceBusOptions",
|
||||
"{",
|
||||
" \"PrefetchCount\": 0,",
|
||||
" \"MessageHandlerOptions\": {",
|
||||
" \"AutoComplete\": true,",
|
||||
" \"MaxAutoRenewDuration\": \"00:05:00\",",
|
||||
" \"MaxConcurrentCalls\": 16",
|
||||
" }",
|
||||
"}",
|
||||
"SingletonOptions",
|
||||
"{",
|
||||
" \"ListenerLockPeriod\": \"00:01:00\"",
|
||||
" \"ListenerLockRecoveryPollingInterval\": \"00:01:00\"",
|
||||
" \"LockAcquisitionPollingInterval\": \"00:00:05\"",
|
||||
" \"LockAcquisitionTimeout\": \"",
|
||||
" \"LockPeriod\": \"00:00:15\"",
|
||||
"}",
|
||||
}.OrderBy(p => p).ToArray();
|
||||
|
||||
Action<string>[] inspectors = expectedOutputLines.Select<string, Action<string>>(p => (string m) => m.StartsWith(p)).ToArray();
|
||||
Assert.Collection(consoleOutputLines, inspectors);
|
||||
|
||||
// Verify that trigger details are properly formatted
|
||||
string[] triggerDetailsConsoleOutput = consoleOutputLines
|
||||
.Where(m => m.StartsWith(TriggerDetailsMessageStart)).ToArray();
|
||||
|
||||
string expectedPattern = "Trigger Details: MessageId: (.*), DeliveryCount: [0-9]+, EnqueuedTime: (.*), LockedUntil: (.*)";
|
||||
|
||||
foreach (string msg in triggerDetailsConsoleOutput)
|
||||
{
|
||||
Assert.True(Regex.IsMatch(msg, expectedPattern), $"Expected trace event {expectedPattern} not found.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task WriteQueueMessage(string connectionString, string queueName, string message)
|
||||
{
|
||||
QueueClient queueClient = new QueueClient(connectionString, queueName);
|
||||
await queueClient.SendAsync(new Message(Encoding.UTF8.GetBytes(message)));
|
||||
await queueClient.CloseAsync();
|
||||
}
|
||||
|
||||
public abstract class ServiceBusTestJobsBase
|
||||
{
|
||||
protected static Message SBQueue2SBQueue_GetOutputMessage(string input)
|
||||
{
|
||||
input = input + "-SBQueue2SBQueue";
|
||||
return new Message
|
||||
{
|
||||
ContentType = "text/plain",
|
||||
Body = Encoding.UTF8.GetBytes(input)
|
||||
};
|
||||
}
|
||||
|
||||
protected static Message SBQueue2SBTopic_GetOutputMessage(string input)
|
||||
{
|
||||
input = input + "-SBQueue2SBTopic";
|
||||
|
||||
return new Message(Encoding.UTF8.GetBytes(input))
|
||||
{
|
||||
ContentType = "text/plain"
|
||||
};
|
||||
}
|
||||
|
||||
protected static void SBTopicListener1Impl(string input)
|
||||
{
|
||||
_resultMessage1 = input + "-topic-1";
|
||||
_topicSubscriptionCalled1.Set();
|
||||
}
|
||||
|
||||
protected static void SBTopicListener2Impl(Message message)
|
||||
{
|
||||
using (Stream stream = new MemoryStream(message.Body))
|
||||
using (TextReader reader = new StreamReader(stream))
|
||||
{
|
||||
_resultMessage2 = reader.ReadToEnd() + "-topic-2";
|
||||
}
|
||||
|
||||
_topicSubscriptionCalled2.Set();
|
||||
}
|
||||
}
|
||||
|
||||
public class ServiceBusTestJobs : ServiceBusTestJobsBase
|
||||
{
|
||||
// Passes service bus message from a queue to another queue
|
||||
public static async Task SBQueue2SBQueue(
|
||||
[ServiceBusTrigger(FirstQueueName)] string start, int deliveryCount,
|
||||
MessageReceiver messageReceiver,
|
||||
string lockToken,
|
||||
[ServiceBus(SecondQueueName)] MessageSender messageSender)
|
||||
{
|
||||
Assert.Equal(FirstQueueName, messageReceiver.Path);
|
||||
Assert.Equal(1, deliveryCount);
|
||||
|
||||
// verify the message receiver and token are valid
|
||||
await messageReceiver.RenewLockAsync(lockToken);
|
||||
|
||||
var message = SBQueue2SBQueue_GetOutputMessage(start);
|
||||
await messageSender.SendAsync(message);
|
||||
}
|
||||
|
||||
// Passes a service bus message from a queue to topic using a brokered message
|
||||
public static void SBQueue2SBTopic(
|
||||
[ServiceBusTrigger(SecondQueueName)] string message,
|
||||
[ServiceBus(TopicName)] out Message output)
|
||||
{
|
||||
output = SBQueue2SBTopic_GetOutputMessage(message);
|
||||
}
|
||||
|
||||
// First listener for the topic
|
||||
public static void SBTopicListener1(
|
||||
[ServiceBusTrigger(TopicName, TopicSubscriptionName1)] string message,
|
||||
MessageReceiver messageReceiver,
|
||||
string lockToken)
|
||||
{
|
||||
SBTopicListener1Impl(message);
|
||||
}
|
||||
|
||||
// Second listener for the topic
|
||||
// Just sprinkling Singleton here because previously we had a bug where this didn't work
|
||||
// for ServiceBus.
|
||||
[Singleton]
|
||||
public static void SBTopicListener2(
|
||||
[ServiceBusTrigger(TopicName, TopicSubscriptionName2)] Message message)
|
||||
{
|
||||
SBTopicListener2Impl(message);
|
||||
}
|
||||
|
||||
// Demonstrate triggering on a queue in one account, and writing to a topic
|
||||
// in the primary subscription
|
||||
public static void MultipleAccounts(
|
||||
[ServiceBusTrigger(FirstQueueName, Connection = SecondaryConnectionStringKey)] string input,
|
||||
[ServiceBus(TopicName)] out string output)
|
||||
{
|
||||
output = input;
|
||||
}
|
||||
|
||||
[NoAutomaticTrigger]
|
||||
public static async Task ServiceBusBinderTest(
|
||||
string message,
|
||||
int numMessages,
|
||||
Binder binder)
|
||||
{
|
||||
var attribute = new ServiceBusAttribute(BinderQueueName)
|
||||
{
|
||||
EntityType = EntityType.Queue
|
||||
};
|
||||
var collector = await binder.BindAsync<IAsyncCollector<string>>(attribute);
|
||||
|
||||
for (int i = 0; i < numMessages; i++)
|
||||
{
|
||||
await collector.AddAsync(message + i);
|
||||
}
|
||||
|
||||
await collector.FlushAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private class CustomMessagingProvider : MessagingProvider
|
||||
{
|
||||
public const string CustomMessagingCategory = "CustomMessagingProvider";
|
||||
private readonly ILogger _logger;
|
||||
private readonly ServiceBusOptions _options;
|
||||
|
||||
public CustomMessagingProvider(IOptions<ServiceBusOptions> serviceBusOptions, ILoggerFactory loggerFactory)
|
||||
: base(serviceBusOptions)
|
||||
{
|
||||
_options = serviceBusOptions.Value;
|
||||
_logger = loggerFactory?.CreateLogger(CustomMessagingCategory);
|
||||
}
|
||||
|
||||
public override MessageProcessor CreateMessageProcessor(string entityPath, string connectionName = null)
|
||||
{
|
||||
var options = new MessageHandlerOptions(ExceptionReceivedHandler)
|
||||
{
|
||||
MaxConcurrentCalls = 3,
|
||||
MaxAutoRenewDuration = TimeSpan.FromMinutes(1)
|
||||
};
|
||||
|
||||
var messageReceiver = new MessageReceiver(_options.ConnectionString, entityPath);
|
||||
|
||||
return new CustomMessageProcessor(messageReceiver, options, _logger);
|
||||
}
|
||||
|
||||
private class CustomMessageProcessor : MessageProcessor
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public CustomMessageProcessor(MessageReceiver messageReceiver, MessageHandlerOptions messageOptions, ILogger logger)
|
||||
: base(messageReceiver, messageOptions)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public override async Task<bool> BeginProcessingMessageAsync(Message message, CancellationToken cancellationToken)
|
||||
{
|
||||
_logger?.LogInformation("Custom processor Begin called!");
|
||||
return await base.BeginProcessingMessageAsync(message, cancellationToken);
|
||||
}
|
||||
|
||||
public override async Task CompleteProcessingMessageAsync(Message message, Executors.FunctionResult result, CancellationToken cancellationToken)
|
||||
{
|
||||
_logger?.LogInformation("Custom processor End called!");
|
||||
await base.CompleteProcessingMessageAsync(message, result, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
private Task ExceptionReceivedHandler(ExceptionReceivedEventArgs eventArgs)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Cleanup().GetAwaiter().GetResult();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -25,7 +25,7 @@
|
|||
<PackageReference Include="appinsights.testlogger" Version="1.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore" Version="2.1.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="2.1.0" />
|
||||
<PackageReference Include="Microsoft.Azure.ServiceBus" Version="3.0.2" />
|
||||
<PackageReference Include="Microsoft.Azure.WebJobs.Extensions.ServiceBus" Version="3.0.6" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.1.0-beta004">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
|
@ -39,7 +39,6 @@
|
|||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Microsoft.Azure.WebJobs.Extensions.EventHubs\WebJobs.Extensions.EventHubs.csproj" />
|
||||
<ProjectReference Include="..\..\src\Microsoft.Azure.WebJobs.Extensions.ServiceBus\WebJobs.Extensions.ServiceBus.csproj" />
|
||||
<ProjectReference Include="..\..\src\Microsoft.Azure.WebJobs.Extensions.Storage\WebJobs.Extensions.Storage.csproj" />
|
||||
<ProjectReference Include="..\..\src\Microsoft.Azure.WebJobs.Host.Storage\WebJobs.Host.Storage.csproj" />
|
||||
<ProjectReference Include="..\..\src\Microsoft.Azure.WebJobs.Host\WebJobs.Host.csproj" />
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Azure.WebJobs.Extensions.ServiceBus" Version="3.0.6" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.1.0-beta004">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
|
@ -34,7 +35,6 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Microsoft.Azure.WebJobs.Extensions.ServiceBus\WebJobs.Extensions.ServiceBus.csproj" />
|
||||
<ProjectReference Include="..\..\src\Microsoft.Azure.WebJobs.Extensions.Storage\WebJobs.Extensions.Storage.csproj" />
|
||||
<ProjectReference Include="..\..\src\Microsoft.Azure.WebJobs.Host.Storage\WebJobs.Host.Storage.csproj" />
|
||||
<ProjectReference Include="..\..\src\Microsoft.Azure.WebJobs.Host\WebJobs.Host.csproj" />
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<Import Project="..\..\build\common.props" />
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp2.1</TargetFramework>
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsPackable>true</IsPackable>
|
||||
<AssemblyName>Microsoft.Azure.WebJobs.Host.TestCommon</AssemblyName>
|
||||
<RootNamespace>Microsoft.Azure.WebJobs.Host.TestCommon</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
|
Загрузка…
Ссылка в новой задаче