Topic Subscriptions/ Sessions/ Request Response features (#36)

* Implement Request/Response and RenewLock/ReceiveBySeq Number

* Git attributes

* Configure await for the new AsyncCalls

* Test Comment Change

* nit cleanup

* With Sessions, GetState/SetState and Renewlock

* Removing orig files

* Remove test assembly changes

* Remove unused usings

* Removing files from Microsoft.Azure.Messaging

* Added some missing Configure Awaits

* Remove Unused Usings

* Remove unused usings

* Review Comments

* xmldoc comments

* Sessions/Topic Subscriptions/Refactoring/Tests

* Addressing review comments

* Removing Subscription Names
This commit is contained in:
vinaysurya 2016-12-14 23:40:00 +00:00 коммит произвёл John Taubensee
Родитель d61e62c07c
Коммит a34fbdb62e
58 изменённых файлов: 3521 добавлений и 935 удалений

79
.gitattributes поставляемый
Просмотреть файл

@ -1,26 +1,63 @@
# Default behavior: if Git thinks a file is text (as opposed to binary), it
# will normalize line endings to LF in the repository, but convert to your
# platform's native line endings on checkout (e.g., CRLF for Windows).
###############################################################################
# Set default behavior to automatically normalize line endings.
###############################################################################
* text=auto
# Explicitly declare text files you want to always be normalized and converted
# to native line endings on checkout. E.g.,
#*.c text
###############################################################################
# Set default behavior for command prompt diff.
#
# This is need for earlier builds of msysgit that does not have it on by
# default for csharp files.
# Note: This is only used by command line
###############################################################################
#*.cs diff=csharp
# Declare files that will always have CRLF line endings on checkout. E.g.,
#*.sln text eol=crlf
###############################################################################
# Set the merge driver for project and solution files
#
# Merging from the command prompt will add diff markers to the files if there
# are conflicts (Merging from VS is not affected by the settings below, in VS
# the diff markers are never inserted). Diff markers may cause the following
# file extensions to fail to load in VS. An alternative would be to treat
# these files as binary and thus will always conflict and require user
# intervention with every merge. To do so, just uncomment the entries below
###############################################################################
#*.sln merge=binary
#*.csproj merge=binary
#*.vbproj merge=binary
#*.vcxproj merge=binary
#*.vcproj merge=binary
#*.dbproj merge=binary
#*.fsproj merge=binary
#*.lsproj merge=binary
#*.wixproj merge=binary
#*.modelproj merge=binary
#*.sqlproj merge=binary
#*.wwaproj merge=binary
# Declare files that will always have LF line endings on checkout. E.g.,
*.sh text eol=lf
###############################################################################
# behavior for image files
#
# image files are treated as binary by default.
###############################################################################
#*.jpg binary
#*.png binary
#*.gif binary
# Denote all files that should not have line endings normalized, should not be
# merged, and should not show in a textual diff.
*.docm binary
*.docx binary
*.ico binary
*.lib binary
*.png binary
*.pptx binary
*.snk binary
*.vsdx binary
*.xps binary
###############################################################################
# diff behavior for common document formats
#
# Convert binary document formats to text before diffing them. This feature
# is only available from the command line. Turn it on by uncommenting the
# entries below.
###############################################################################
#*.doc diff=astextplain
#*.DOC diff=astextplain
#*.docx diff=astextplain
#*.DOCX diff=astextplain
#*.dot diff=astextplain
#*.DOT diff=astextplain
#*.pdf diff=astextplain
#*.PDF diff=astextplain
#*.rtf diff=astextplain
#*.RTF diff=astextplain

Просмотреть файл

@ -11,6 +11,7 @@ namespace Microsoft.Azure.ServiceBus.Amqp
{
// AMQP Management Operation
public const string ManagementAddress = "$management";
public const string EntityTypeManagement = "entity-mgmt";
public const string EntityNameKey = "name";
public const string PartitionNameKey = "partition";
public const string ManagementOperationKey = "operation";
@ -62,5 +63,6 @@ namespace Microsoft.Azure.ServiceBus.Amqp
public static readonly AmqpSymbol EntityAlreadyExistsError = AmqpConstants.Vendor + ":entity-already-exists";
public static readonly AmqpSymbol RelayNotFoundError = AmqpConstants.Vendor + ":relay-not-found";
public static readonly AmqpSymbol MessageNotFoundError = AmqpConstants.Vendor + ":message-not-found";
public static readonly AmqpSymbol LockedUntilUtc = AmqpConstants.Vendor + ":locked-until-utc";
}
}

Просмотреть файл

@ -0,0 +1,117 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace Microsoft.Azure.ServiceBus.Amqp
{
using System;
using Microsoft.Azure.Amqp;
using Microsoft.Azure.Amqp.Sasl;
using Microsoft.Azure.Amqp.Transport;
public class AmqpConnectionHelper
{
const string CbsSaslMechanismName = "MSSBCBS";
public static AmqpSettings CreateAmqpSettings(
Version amqpVersion,
bool useSslStreamSecurity,
bool hasTokenProvider,
string sslHostName = null,
bool useWebSockets = false,
bool sslStreamUpgrade = false,
System.Net.NetworkCredential networkCredential = null,
System.Net.Security.RemoteCertificateValidationCallback certificateValidationCallback = null,
bool forceTokenProvider = true)
{
AmqpSettings settings = new AmqpSettings();
if (useSslStreamSecurity && !useWebSockets && sslStreamUpgrade)
{
TlsTransportSettings tlsSettings = new TlsTransportSettings();
tlsSettings.CertificateValidationCallback = certificateValidationCallback;
tlsSettings.TargetHost = sslHostName;
TlsTransportProvider tlsProvider = new TlsTransportProvider(tlsSettings);
tlsProvider.Versions.Add(new AmqpVersion(amqpVersion));
settings.TransportProviders.Add(tlsProvider);
}
if (hasTokenProvider || networkCredential != null)
{
SaslTransportProvider saslProvider = new SaslTransportProvider();
saslProvider.Versions.Add(new AmqpVersion(amqpVersion));
settings.TransportProviders.Add(saslProvider);
if (forceTokenProvider)
{
saslProvider.AddHandler(new SaslAnonymousHandler(CbsSaslMechanismName));
}
else if (networkCredential != null)
{
SaslPlainHandler plainHandler = new SaslPlainHandler();
plainHandler.AuthenticationIdentity = networkCredential.UserName;
plainHandler.Password = networkCredential.Password;
saslProvider.AddHandler(plainHandler);
}
else
{
// old client behavior: keep it for validation only
saslProvider.AddHandler(new SaslExternalHandler());
}
}
AmqpTransportProvider amqpProvider = new AmqpTransportProvider();
amqpProvider.Versions.Add(new AmqpVersion(amqpVersion));
settings.TransportProviders.Add(amqpProvider);
return settings;
}
[System.Diagnostics.CodeAnalysis.SuppressMessage(
"StyleCop.CSharp.NamingRules",
"SA1305:FieldNamesMustNotUseHungarianNotation",
Justification = "tpSettings is a local variable.")]
public static TransportSettings CreateTcpTransportSettings(
string networkHost,
string hostName,
int port,
bool useSslStreamSecurity,
bool sslStreamUpgrade = false,
string sslHostName = null,
System.Security.Cryptography.X509Certificates.X509Certificate2 certificate = null,
System.Net.Security.RemoteCertificateValidationCallback certificateValidationCallback = null)
{
TcpTransportSettings tcpSettings = new TcpTransportSettings
{
Host = networkHost,
Port = port < 0 ? AmqpConstants.DefaultSecurePort : port,
ReceiveBufferSize = AmqpConstants.TransportBufferSize,
SendBufferSize = AmqpConstants.TransportBufferSize
};
TransportSettings tpSettings = tcpSettings;
if (useSslStreamSecurity && !sslStreamUpgrade)
{
TlsTransportSettings tlsSettings = new TlsTransportSettings(tcpSettings)
{
TargetHost = sslHostName ?? hostName,
Certificate = certificate,
CertificateValidationCallback = certificateValidationCallback
};
tpSettings = tlsSettings;
}
return tpSettings;
}
public static AmqpConnectionSettings CreateAmqpConnectionSettings(uint maxFrameSize, string containerId, string hostName)
{
AmqpConnectionSettings connectionSettings = new AmqpConnectionSettings
{
MaxFrameSize = maxFrameSize,
ContainerId = containerId,
HostName = hostName
};
return connectionSettings;
}
}
}

Просмотреть файл

@ -5,11 +5,12 @@ namespace Microsoft.Azure.ServiceBus.Amqp
{
using System;
using System.Collections.Generic;
using Messaging.Amqp;
using Microsoft.Azure.Amqp;
using Microsoft.Azure.Amqp.Encoding;
using Microsoft.Azure.Amqp.Framing;
class AmqpExceptionHelper
static class AmqpExceptionHelper
{
static readonly Dictionary<string, AmqpResponseStatusCode> ConditionToStatusMap = new Dictionary<string, AmqpResponseStatusCode>()
{
@ -55,6 +56,21 @@ namespace Microsoft.Azure.ServiceBus.Amqp
return AmqpErrorCode.InternalError;
}
public static AmqpResponseStatusCode GetResponseStatusCode(this AmqpMessage responseMessage)
{
AmqpResponseStatusCode responseStatusCode = AmqpResponseStatusCode.Unused;
if (responseMessage != null)
{
object statusCodeValue = responseMessage.ApplicationProperties.Map[ManagementConstants.Response.StatusCode] ?? responseMessage.ApplicationProperties.Map[AmqpClientConstants.ResponseStatusCode];
if (statusCodeValue is int && Enum.IsDefined(typeof(AmqpResponseStatusCode), statusCodeValue))
{
responseStatusCode = (AmqpResponseStatusCode)statusCodeValue;
}
}
return responseStatusCode;
}
public static Exception ToMessagingContract(Error error, bool connectionError = false)
{
if (error == null)

Просмотреть файл

@ -0,0 +1,62 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace Microsoft.Azure.ServiceBus.Amqp
{
using System;
using System.Threading.Tasks;
using Microsoft.Azure.Amqp;
using Microsoft.Azure.Amqp.Framing;
public abstract class AmqpLinkCreator
{
readonly string entityPath;
readonly ServiceBusConnection serviceBusConnection;
readonly string[] requiredClaims;
readonly ICbsTokenProvider cbsTokenProvider;
readonly AmqpLinkSettings amqpLinkSettings;
protected AmqpLinkCreator(string entityPath, ServiceBusConnection serviceBusConnection, string[] requiredClaims, ICbsTokenProvider cbsTokenProvider, AmqpLinkSettings amqpLinkSettings)
{
this.entityPath = entityPath;
this.serviceBusConnection = serviceBusConnection;
this.requiredClaims = requiredClaims;
this.cbsTokenProvider = cbsTokenProvider;
this.amqpLinkSettings = amqpLinkSettings;
}
public async Task<AmqpObject> CreateAndOpenAmqpLinkAsync()
{
TimeoutHelper timeoutHelper = new TimeoutHelper(this.serviceBusConnection.OperationTimeout);
AmqpConnection connection = await this.serviceBusConnection.ConnectionManager.GetOrCreateAsync(timeoutHelper.RemainingTime()).ConfigureAwait(false);
// Authenticate over CBS
AmqpCbsLink cbsLink = connection.Extensions.Find<AmqpCbsLink>();
Uri address = new Uri(this.serviceBusConnection.Endpoint, this.entityPath);
string audience = address.AbsoluteUri;
string resource = address.AbsoluteUri;
await cbsLink.SendTokenAsync(this.cbsTokenProvider, address, audience, resource, this.requiredClaims, timeoutHelper.RemainingTime()).ConfigureAwait(false);
AmqpSession session = null;
try
{
// Create our Session
AmqpSessionSettings sessionSettings = new AmqpSessionSettings { Properties = new Fields() };
session = connection.CreateSession(sessionSettings);
await session.OpenAsync(timeoutHelper.RemainingTime()).ConfigureAwait(false);
// Create our Link
AmqpObject link = this.OnCreateAmqpLink(connection, this.amqpLinkSettings, session);
await link.OpenAsync(timeoutHelper.RemainingTime()).ConfigureAwait(false);
return link;
}
catch (Exception)
{
session?.Abort();
throw;
}
}
protected abstract AmqpObject OnCreateAmqpLink(AmqpConnection connection, AmqpLinkSettings linkSettings, AmqpSession amqpSession);
}
}

Просмотреть файл

@ -42,7 +42,7 @@ namespace Microsoft.Azure.ServiceBus.Amqp
}
else
{
var dataList = new List<Data>();
List<Data> dataList = new List<Data>();
foreach (BrokeredMessage brokeredMessage in brokeredMessages)
{
AmqpMessage amqpMessageItem = AmqpMessageConverter.ClientGetMessage(brokeredMessage);
@ -52,7 +52,7 @@ namespace Microsoft.Azure.ServiceBus.Amqp
dataList.Add(new Data() { Value = value });
}
var firstBrokeredMessage = brokeredMessages.First();
BrokeredMessage firstBrokeredMessage = brokeredMessages.First();
amqpMessage = AmqpMessage.Create(dataList);
amqpMessage.MessageFormat = AmqpConstants.AmqpBatchedMessageFormat;
@ -336,7 +336,7 @@ namespace Microsoft.Azure.ServiceBus.Amqp
amqpObject = null;
if (netObject == null)
{
return false;
return true;
}
switch (SerializationUtilities.GetTypeId(netObject))
@ -413,7 +413,7 @@ namespace Microsoft.Azure.ServiceBus.Amqp
netObject = null;
if (amqpObject == null)
{
return false;
return true;
}
switch (SerializationUtilities.GetTypeId(amqpObject))

Просмотреть файл

@ -8,43 +8,115 @@ namespace Microsoft.Azure.ServiceBus.Amqp
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Azure.Amqp;
using Microsoft.Azure.Amqp.Encoding;
using Microsoft.Azure.Amqp.Framing;
using Microsoft.Azure.Messaging.Amqp;
using Microsoft.Azure.ServiceBus.Primitives;
sealed class AmqpMessageReceiver : MessageReceiver
{
public static readonly TimeSpan DefaultBatchFlushInterval = TimeSpan.FromMilliseconds(20);
public AmqpMessageReceiver(QueueClient queueClient)
: base(queueClient.Mode)
readonly ConcurrentExpiringSet<Guid> requestResponseLockedMessages;
readonly string entityName;
readonly bool isSessionReceiver;
string sessionId;
DateTime lockedUntilUtc;
public AmqpMessageReceiver(string entityName, MessagingEntityType entityType, ReceiveMode mode, int prefetchCount, ServiceBusConnection serviceBusConnection, ICbsTokenProvider cbsTokenProvider)
: this(entityName, entityType, mode, prefetchCount, serviceBusConnection, cbsTokenProvider, null)
{
this.QueueClient = queueClient;
this.Path = queueClient.QueueName;
}
public AmqpMessageReceiver(string entityName, MessagingEntityType entityType, ReceiveMode mode, int prefetchCount, ServiceBusConnection serviceBusConnection, ICbsTokenProvider cbsTokenProvider, string sessionId, bool isSessionReceiver = false)
: base(mode, serviceBusConnection.OperationTimeout)
{
this.entityName = entityName;
this.EntityType = entityType;
this.ServiceBusConnection = serviceBusConnection;
this.CbsTokenProvider = cbsTokenProvider;
this.sessionId = sessionId;
this.isSessionReceiver = isSessionReceiver;
this.ReceiveLinkManager = new FaultTolerantAmqpObject<ReceivingAmqpLink>(this.CreateLinkAsync, this.CloseSession);
this.PrefetchCount = queueClient.PrefetchCount;
this.RequestResponseLinkManager = new FaultTolerantAmqpObject<RequestResponseAmqpLink>(this.CreateRequestResponseLinkAsync, this.CloseRequestResponseSession);
this.requestResponseLockedMessages = new ConcurrentExpiringSet<Guid>();
this.PrefetchCount = prefetchCount;
}
/// <summary>
/// Get Prefetch Count configured on the Receiver.
/// </summary>
/// <value>The upper limit of events this receiver will actively receive regardless of whether a receive operation is pending.</value>
public int PrefetchCount { get; set; }
public override int PrefetchCount
{
get
{
return base.PrefetchCount;
}
QueueClient QueueClient { get; }
set
{
if (value != base.PrefetchCount)
{
ReceivingAmqpLink link;
if (this.ReceiveLinkManager.TryGetOpenedObject(out link))
{
link.SetTotalLinkCredit((uint)value, true, true);
}
}
}
}
string Path { get; }
public override string Path
{
get { return this.entityName; }
}
public DateTime LockedUntilUtc
{
get { return this.lockedUntilUtc; }
}
public string SessionId
{
get { return this.sessionId; }
}
ServiceBusConnection ServiceBusConnection { get; }
ICbsTokenProvider CbsTokenProvider { get; }
FaultTolerantAmqpObject<ReceivingAmqpLink> ReceiveLinkManager { get; }
public override Task CloseAsync()
FaultTolerantAmqpObject<RequestResponseAmqpLink> RequestResponseLinkManager { get; }
public override async Task CloseAsync()
{
return this.ReceiveLinkManager.CloseAsync();
await this.ReceiveLinkManager.CloseAsync().ConfigureAwait(false);
await this.RequestResponseLinkManager.CloseAsync().ConfigureAwait(false);
}
internal async Task GetSessionReceiverLinkAsync()
{
TimeoutHelper timeoutHelper = new TimeoutHelper(this.OperationTimeout, true);
ReceivingAmqpLink receivingAmqpLink = await this.ReceiveLinkManager.GetOrCreateAsync(timeoutHelper.RemainingTime()).ConfigureAwait(false);
Source source = (Source)receivingAmqpLink.Settings.Source;
if (!source.FilterSet.TryGetValue<string>(AmqpClientConstants.SessionFilterName, out this.sessionId))
{
receivingAmqpLink.Session.SafeClose();
throw new ServiceBusException(false, Resources.AmqpFieldSessionId);
}
long lockedUntilUtcTicks;
this.lockedUntilUtc = receivingAmqpLink.Settings.Properties.TryGetValue(AmqpClientConstants.LockedUntilUtc, out lockedUntilUtcTicks) ? new DateTime(lockedUntilUtcTicks, DateTimeKind.Utc) : DateTime.MinValue;
}
protected override async Task<IList<BrokeredMessage>> OnReceiveAsync(int maxMessageCount)
{
try
{
var timeoutHelper = new TimeoutHelper(this.QueueClient.ConnectionSettings.OperationTimeout, true);
TimeoutHelper timeoutHelper = new TimeoutHelper(this.OperationTimeout, true);
ReceivingAmqpLink receiveLink = await this.ReceiveLinkManager.GetOrCreateAsync(timeoutHelper.RemainingTime()).ConfigureAwait(false);
IEnumerable<AmqpMessage> amqpMessages = null;
bool hasMessages = await Task.Factory.FromAsync(
@ -67,7 +139,7 @@ namespace Microsoft.Azure.ServiceBus.Amqp
brokeredMessages = new List<BrokeredMessage>();
}
if (this.QueueClient.Mode == ReceiveMode.ReceiveAndDelete)
if (this.ReceiveMode == ReceiveMode.ReceiveAndDelete)
{
receiveLink.DisposeDelivery(amqpMessage, true, AmqpConstants.AcceptedOutcome);
}
@ -88,11 +160,57 @@ namespace Microsoft.Azure.ServiceBus.Amqp
}
}
protected override async Task<IList<BrokeredMessage>> OnReceiveBySequenceNumberAsync(IEnumerable<long> sequenceNumbers)
{
List<BrokeredMessage> messages = new List<BrokeredMessage>();
try
{
AmqpRequestMessage requestMessage = AmqpRequestMessage.CreateRequest(ManagementConstants.Operations.ReceiveBySequenceNumberOperation, this.OperationTimeout, null);
requestMessage.Map[ManagementConstants.Properties.SequenceNumbers] = sequenceNumbers.ToArray();
requestMessage.Map[ManagementConstants.Properties.ReceiverSettleMode] = (uint)(this.ReceiveMode == ReceiveMode.ReceiveAndDelete ? 0 : 1);
AmqpResponseMessage response = await this.ExecuteRequestResponseAsync(requestMessage).ConfigureAwait(false);
if (response.StatusCode == AmqpResponseStatusCode.OK)
{
IEnumerable<AmqpMap> amqpMapList = response.GetListValue<AmqpMap>(ManagementConstants.Properties.Messages);
foreach (AmqpMap entry in amqpMapList)
{
ArraySegment<byte> payload = (ArraySegment<byte>)entry[ManagementConstants.Properties.Message];
AmqpMessage amqpMessage = AmqpMessage.CreateAmqpStreamMessage(new BufferListStream(new[] { payload }), true);
BrokeredMessage brokeredMessage = AmqpMessageConverter.ClientGetMessage(amqpMessage);
brokeredMessage.Receiver = this; // Associate the Message with this Receiver.
Guid lockToken;
if (entry.TryGetValue(ManagementConstants.Properties.LockToken, out lockToken))
{
brokeredMessage.LockToken = lockToken;
this.requestResponseLockedMessages.AddOrUpdate(lockToken, brokeredMessage.LockedUntilUtc);
}
messages.Add(brokeredMessage);
}
}
}
catch (AmqpException amqpException)
{
throw AmqpExceptionHelper.ToMessagingContract(amqpException.Error);
}
return messages;
}
protected override async Task OnCompleteAsync(IEnumerable<Guid> lockTokens)
{
try
{
await this.DisposeMessagesAsync(lockTokens, AmqpConstants.AcceptedOutcome).ConfigureAwait(false);
if (lockTokens.Any((lt) => this.requestResponseLockedMessages.Contains(lt)))
{
await this.DisposeMessageRequestResponseAsync(lockTokens, DispositionStatus.Completed).ConfigureAwait(false);
}
else
{
await this.DisposeMessagesAsync(lockTokens, AmqpConstants.AcceptedOutcome).ConfigureAwait(false);
}
}
catch (AmqpException amqpException)
{
@ -104,7 +222,14 @@ namespace Microsoft.Azure.ServiceBus.Amqp
{
try
{
await this.DisposeMessagesAsync(lockTokens, new Modified()).ConfigureAwait(false);
if (lockTokens.Any((lt) => this.requestResponseLockedMessages.Contains(lt)))
{
await this.DisposeMessageRequestResponseAsync(lockTokens, DispositionStatus.Abandoned).ConfigureAwait(false);
}
else
{
await this.DisposeMessagesAsync(lockTokens, new Modified()).ConfigureAwait(false);
}
}
catch (AmqpException amqpException)
{
@ -116,7 +241,14 @@ namespace Microsoft.Azure.ServiceBus.Amqp
{
try
{
await this.DisposeMessagesAsync(lockTokens, new Modified() { UndeliverableHere = true }).ConfigureAwait(false);
if (lockTokens.Any((lt) => this.requestResponseLockedMessages.Contains(lt)))
{
await this.DisposeMessageRequestResponseAsync(lockTokens, DispositionStatus.Defered).ConfigureAwait(false);
}
else
{
await this.DisposeMessagesAsync(lockTokens, new Modified() { UndeliverableHere = true }).ConfigureAwait(false);
}
}
catch (AmqpException amqpException)
{
@ -128,7 +260,14 @@ namespace Microsoft.Azure.ServiceBus.Amqp
{
try
{
await this.DisposeMessagesAsync(lockTokens, AmqpConstants.RejectedOutcome).ConfigureAwait(false);
if (lockTokens.Any((lt) => this.requestResponseLockedMessages.Contains(lt)))
{
await this.DisposeMessageRequestResponseAsync(lockTokens, DispositionStatus.Suspended).ConfigureAwait(false);
}
else
{
await this.DisposeMessagesAsync(lockTokens, AmqpConstants.RejectedOutcome).ConfigureAwait(false);
}
}
catch (AmqpException amqpException)
{
@ -136,9 +275,49 @@ namespace Microsoft.Azure.ServiceBus.Amqp
}
}
protected override async Task<DateTime> OnRenewLockAsync(Guid lockToken)
{
DateTime lockedUntilUtc = DateTime.MinValue;
try
{
// Create an AmqpRequest Message to renew lock
AmqpRequestMessage requestMessage = AmqpRequestMessage.CreateRequest(ManagementConstants.Operations.RenewLockOperation, this.OperationTimeout, null);
requestMessage.Map[ManagementConstants.Properties.LockTokens] = new Guid[] { lockToken };
AmqpResponseMessage response = await this.ExecuteRequestResponseAsync(requestMessage).ConfigureAwait(false);
if (response.StatusCode == AmqpResponseStatusCode.OK)
{
IEnumerable<DateTime> lockedUntilUtcTimes = response.GetValue<IEnumerable<DateTime>>(ManagementConstants.Properties.Expirations);
lockedUntilUtc = lockedUntilUtcTimes.First();
}
}
catch (AmqpException amqpException)
{
throw AmqpExceptionHelper.ToMessagingContract(amqpException.Error);
}
return lockedUntilUtc;
}
protected override async Task<AmqpResponseMessage> OnExecuteRequestResponseAsync(AmqpRequestMessage amqpRequestMessage)
{
AmqpMessage amqpMessage = amqpRequestMessage.AmqpMessage;
TimeoutHelper timeoutHelper = new TimeoutHelper(this.OperationTimeout, true);
RequestResponseAmqpLink requestResponseAmqpLink = await this.RequestResponseLinkManager.GetOrCreateAsync(timeoutHelper.RemainingTime()).ConfigureAwait(false);
AmqpMessage responseAmqpMessage = await Task.Factory.FromAsync(
(c, s) => requestResponseAmqpLink.BeginRequest(amqpMessage, timeoutHelper.RemainingTime(), c, s),
(a) => requestResponseAmqpLink.EndRequest(a),
this).ConfigureAwait(false);
AmqpResponseMessage responseMessage = AmqpResponseMessage.CreateResponse(responseAmqpMessage);
return responseMessage;
}
async Task DisposeMessagesAsync(IEnumerable<Guid> lockTokens, Outcome outcome)
{
var timeoutHelper = new TimeoutHelper(this.QueueClient.ConnectionSettings.OperationTimeout, true);
TimeoutHelper timeoutHelper = new TimeoutHelper(this.OperationTimeout, true);
IList<ArraySegment<byte>> deliveryTags = this.ConvertLockTokensToDeliveryTags(lockTokens);
ReceivingAmqpLink receiveLink = await this.ReceiveLinkManager.GetOrCreateAsync(timeoutHelper.RemainingTime()).ConfigureAwait(false);
@ -155,6 +334,23 @@ namespace Microsoft.Azure.ServiceBus.Amqp
await Task.WhenAll(disposeMessageTasks).ConfigureAwait(false);
}
async Task DisposeMessageRequestResponseAsync(IEnumerable<Guid> lockTokens, DispositionStatus dispositionStatus)
{
try
{
// Create an AmqpRequest Message to update disposition
AmqpRequestMessage requestMessage = AmqpRequestMessage.CreateRequest(ManagementConstants.Operations.UpdateDispositionOperation, this.OperationTimeout, null);
requestMessage.Map[ManagementConstants.Properties.LockTokens] = lockTokens.ToArray();
requestMessage.Map[ManagementConstants.Properties.DispositionStatus] = dispositionStatus.ToString().ToLowerInvariant();
await this.ExecuteRequestResponseAsync(requestMessage);
}
catch (AmqpException amqpException)
{
throw AmqpExceptionHelper.ToMessagingContract(amqpException.Error);
}
}
IList<ArraySegment<byte>> ConvertLockTokensToDeliveryTags(IEnumerable<Guid> lockTokens)
{
return lockTokens.Select(lockToken => new ArraySegment<byte>(lockToken.ToByteArray())).ToList();
@ -162,60 +358,50 @@ namespace Microsoft.Azure.ServiceBus.Amqp
async Task<ReceivingAmqpLink> CreateLinkAsync(TimeSpan timeout)
{
var amqpQueueClient = (AmqpQueueClient)this.QueueClient;
var connectionSettings = amqpQueueClient.ConnectionSettings;
var timeoutHelper = new TimeoutHelper(connectionSettings.OperationTimeout);
AmqpConnection connection = await amqpQueueClient.ConnectionManager.GetOrCreateAsync(timeoutHelper.RemainingTime()).ConfigureAwait(false);
// Authenticate over CBS
var cbsLink = connection.Extensions.Find<AmqpCbsLink>();
ICbsTokenProvider cbsTokenProvider = amqpQueueClient.CbsTokenProvider;
Uri address = new Uri(connectionSettings.Endpoint, this.Path);
string audience = address.AbsoluteUri;
string resource = address.AbsoluteUri;
var expiresAt = await cbsLink.SendTokenAsync(cbsTokenProvider, address, audience, resource, new[] { ClaimConstants.Listen }, timeoutHelper.RemainingTime()).ConfigureAwait(false);
AmqpSession session = null;
bool succeeded = false;
try
FilterSet filterMap = null;
if (this.isSessionReceiver)
{
// Create our Session
var sessionSettings = new AmqpSessionSettings { Properties = new Fields() };
session = connection.CreateSession(sessionSettings);
await session.OpenAsync(timeoutHelper.RemainingTime()).ConfigureAwait(false);
// Create our Link
var linkSettings = new AmqpLinkSettings();
linkSettings.Role = true;
linkSettings.TotalLinkCredit = (uint)this.PrefetchCount;
linkSettings.AutoSendFlow = this.PrefetchCount > 0;
linkSettings.AddProperty(AmqpClientConstants.EntityTypeName, (int)MessagingEntityType.Queue);
linkSettings.Source = new Source { Address = address.AbsolutePath };
linkSettings.Target = new Target { Address = this.ClientId };
linkSettings.SettleType = (this.QueueClient.Mode == ReceiveMode.PeekLock) ? SettleMode.SettleOnDispose : SettleMode.SettleOnSend;
var link = new ReceivingAmqpLink(linkSettings);
linkSettings.LinkName = $"{amqpQueueClient.ContainerId};{connection.Identifier}:{session.Identifier}:{link.Identifier}";
link.AttachTo(session);
await link.OpenAsync(timeoutHelper.RemainingTime()).ConfigureAwait(false);
succeeded = true;
return link;
filterMap = new FilterSet { { AmqpClientConstants.SessionFilterName, this.sessionId } };
}
finally
AmqpLinkSettings linkSettings = new AmqpLinkSettings
{
if (!succeeded)
{
// Cleanup any session (and thus link) in case of exception.
session?.Abort();
}
}
Role = true,
TotalLinkCredit = (uint)this.PrefetchCount,
AutoSendFlow = this.PrefetchCount > 0,
Source = new Source { Address = this.Path, FilterSet = filterMap },
SettleType = (this.ReceiveMode == ReceiveMode.PeekLock) ? SettleMode.SettleOnDispose : SettleMode.SettleOnSend
};
linkSettings.AddProperty(AmqpClientConstants.EntityTypeName, (int)this.EntityType);
linkSettings.AddProperty(AmqpClientConstants.TimeoutName, (uint)timeout.TotalMilliseconds);
AmqpSendReceiveLinkCreator sendReceiveLinkCreator = new AmqpSendReceiveLinkCreator(this.Path, this.ServiceBusConnection, new[] { ClaimConstants.Listen }, this.CbsTokenProvider, linkSettings);
ReceivingAmqpLink receivingAmqpLink = (ReceivingAmqpLink)await sendReceiveLinkCreator.CreateAndOpenAmqpLinkAsync().ConfigureAwait(false);
return receivingAmqpLink;
}
// TODO: Consolidate the link creation paths
async Task<RequestResponseAmqpLink> CreateRequestResponseLinkAsync(TimeSpan timeout)
{
string entityPath = this.Path + '/' + AmqpClientConstants.ManagementAddress;
AmqpLinkSettings linkSettings = new AmqpLinkSettings();
linkSettings.AddProperty(AmqpClientConstants.EntityTypeName, AmqpClientConstants.EntityTypeManagement);
AmqpRequestResponseLinkCreator requestResponseLinkCreator = new AmqpRequestResponseLinkCreator(entityPath, this.ServiceBusConnection, new[] { ClaimConstants.Manage, ClaimConstants.Listen }, this.CbsTokenProvider, linkSettings);
RequestResponseAmqpLink requestResponseAmqpLink = (RequestResponseAmqpLink)await requestResponseLinkCreator.CreateAndOpenAmqpLinkAsync().ConfigureAwait(false);
return requestResponseAmqpLink;
}
void CloseSession(ReceivingAmqpLink link)
{
link.Session.SafeClose();
}
void CloseRequestResponseSession(RequestResponseAmqpLink requestResponseAmqpLink)
{
requestResponseAmqpLink.Session.SafeClose();
}
}
}

Просмотреть файл

@ -9,22 +9,28 @@ namespace Microsoft.Azure.ServiceBus.Amqp
using System.Threading.Tasks;
using Microsoft.Azure.Amqp;
using Microsoft.Azure.Amqp.Framing;
using Microsoft.Azure.Messaging.Amqp;
sealed class AmqpMessageSender : MessageSender
{
int deliveryCount;
internal AmqpMessageSender(AmqpQueueClient queueClient)
internal AmqpMessageSender(string entityName, MessagingEntityType entityType, ServiceBusConnection serviceBusConnection, ICbsTokenProvider cbsTokenProvider)
: base(serviceBusConnection.OperationTimeout)
{
this.QueueClient = queueClient;
this.Path = this.QueueClient.QueueName;
this.Path = entityName;
this.EntityType = entityType;
this.ServiceBusConnection = serviceBusConnection;
this.CbsTokenProvider = cbsTokenProvider;
this.SendLinkManager = new FaultTolerantAmqpObject<SendingAmqpLink>(this.CreateLinkAsync, this.CloseSession);
}
QueueClient QueueClient { get; }
string Path { get; }
ServiceBusConnection ServiceBusConnection { get; }
ICbsTokenProvider CbsTokenProvider { get; }
FaultTolerantAmqpObject<SendingAmqpLink> SendLinkManager { get; }
public override Task CloseAsync()
@ -34,10 +40,10 @@ namespace Microsoft.Azure.ServiceBus.Amqp
protected override async Task OnSendAsync(IEnumerable<BrokeredMessage> brokeredMessages)
{
var timeoutHelper = new TimeoutHelper(this.QueueClient.ConnectionSettings.OperationTimeout, true);
TimeoutHelper timeoutHelper = new TimeoutHelper(this.OperationTimeout, true);
using (AmqpMessage amqpMessage = AmqpMessageConverter.BrokeredMessagesToAmqpMessage(brokeredMessages, true))
{
var amqpLink = await this.SendLinkManager.GetOrCreateAsync(timeoutHelper.RemainingTime()).ConfigureAwait(false);
SendingAmqpLink amqpLink = await this.SendLinkManager.GetOrCreateAsync(timeoutHelper.RemainingTime()).ConfigureAwait(false);
if (amqpLink.Settings.MaxMessageSize.HasValue)
{
ulong size = (ulong)amqpMessage.SerializedMessageSize;
@ -67,51 +73,18 @@ namespace Microsoft.Azure.ServiceBus.Amqp
async Task<SendingAmqpLink> CreateLinkAsync(TimeSpan timeout)
{
var amqpQueueClient = (AmqpQueueClient)this.QueueClient;
var connectionSettings = amqpQueueClient.ConnectionSettings;
var timeoutHelper = new TimeoutHelper(connectionSettings.OperationTimeout);
AmqpConnection connection = await amqpQueueClient.ConnectionManager.GetOrCreateAsync(timeoutHelper.RemainingTime()).ConfigureAwait(false);
// Authenticate over CBS
var cbsLink = connection.Extensions.Find<AmqpCbsLink>();
ICbsTokenProvider cbsTokenProvider = amqpQueueClient.CbsTokenProvider;
Uri address = new Uri(connectionSettings.Endpoint, this.Path);
string audience = address.AbsoluteUri;
string resource = address.AbsoluteUri;
var expiresAt = await cbsLink.SendTokenAsync(cbsTokenProvider, address, audience, resource, new[] { ClaimConstants.Send }, timeoutHelper.RemainingTime()).ConfigureAwait(false);
AmqpSession session = null;
try
AmqpLinkSettings linkSettings = new AmqpLinkSettings
{
// Create our Session
var sessionSettings = new AmqpSessionSettings { Properties = new Fields() };
////sessionSettings.Properties[AmqpClientConstants.BatchFlushIntervalName] = (uint)connectionSettings.BatchFlushInterval.TotalMilliseconds;
session = connection.CreateSession(sessionSettings);
await session.OpenAsync(timeoutHelper.RemainingTime()).ConfigureAwait(false);
Role = false,
InitialDeliveryCount = 0,
Target = new Target { Address = this.Path },
Source = new Source { Address = this.ClientId },
};
linkSettings.AddProperty(AmqpClientConstants.EntityTypeName, (int)this.EntityType);
// Create our Link
var linkSettings = new AmqpLinkSettings();
linkSettings.AddProperty(AmqpClientConstants.TimeoutName, (uint)timeoutHelper.RemainingTime().TotalMilliseconds);
linkSettings.AddProperty(AmqpClientConstants.EntityTypeName, (int)MessagingEntityType.Queue);
linkSettings.Role = false;
linkSettings.InitialDeliveryCount = 0;
linkSettings.Target = new Target { Address = address.AbsolutePath };
linkSettings.Source = new Source { Address = this.ClientId };
var link = new SendingAmqpLink(linkSettings);
linkSettings.LinkName = $"{amqpQueueClient.ContainerId};{connection.Identifier}:{session.Identifier}:{link.Identifier}";
link.AttachTo(session);
await link.OpenAsync(timeoutHelper.RemainingTime()).ConfigureAwait(false);
return link;
}
catch (Exception)
{
// Cleanup any session (and thus link) in case of exception.
session?.Abort();
throw;
}
AmqpSendReceiveLinkCreator sendReceiveLinkCreator = new AmqpSendReceiveLinkCreator(this.Path, this.ServiceBusConnection, new[] { ClaimConstants.Send }, this.CbsTokenProvider, linkSettings);
SendingAmqpLink sendingAmqpLink = (SendingAmqpLink)await sendReceiveLinkCreator.CreateAndOpenAmqpLinkAsync().ConfigureAwait(false);
return sendingAmqpLink;
}
void CloseSession(SendingAmqpLink link)

Просмотреть файл

@ -0,0 +1,96 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace Microsoft.Azure.ServiceBus.Amqp
{
using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.Azure.Amqp;
using Microsoft.Azure.Messaging.Amqp;
public class AmqpMessageSession : MessageSession
{
public AmqpMessageSession(string sessionId, DateTime lockedUntilUtc, MessageReceiver innerMessageReceiver)
: base(innerMessageReceiver.ReceiveMode, sessionId, lockedUntilUtc, innerMessageReceiver)
{
}
protected override async Task<Stream> OnGetStateAsync()
{
try
{
AmqpRequestMessage amqpRequestMessage = AmqpRequestMessage.CreateRequest(ManagementConstants.Operations.GetSessionStateOperation, this.OperationTimeout, null);
amqpRequestMessage.Map[ManagementConstants.Properties.SessionId] = this.SessionId;
AmqpResponseMessage amqpResponseMessage = await this.InnerMessageReceiver.ExecuteRequestResponseAsync(amqpRequestMessage).ConfigureAwait(false);
Stream sessionState = null;
if (amqpResponseMessage.StatusCode == AmqpResponseStatusCode.OK)
{
if (amqpResponseMessage.Map[ManagementConstants.Properties.SessionState] != null)
{
sessionState = new BufferListStream(new[] { amqpResponseMessage.GetValue<ArraySegment<byte>>(ManagementConstants.Properties.SessionState) });
}
}
return sessionState;
}
catch (AmqpException amqpException)
{
throw AmqpExceptionHelper.ToMessagingContract(amqpException.Error);
}
}
protected override async Task OnSetStateAsync(Stream sessionState)
{
try
{
if (sessionState != null && sessionState.CanSeek && sessionState.Position != 0)
{
throw new InvalidOperationException("CannotSerializeSessionStateWithPartiallyConsumedStream");
}
AmqpRequestMessage amqpRequestMessage = AmqpRequestMessage.CreateRequest(ManagementConstants.Operations.SetSessionStateOperation, this.OperationTimeout, null);
amqpRequestMessage.Map[ManagementConstants.Properties.SessionId] = this.SessionId;
if (sessionState != null)
{
BufferListStream buffer = BufferListStream.Create(sessionState, AmqpConstants.SegmentSize);
ArraySegment<byte> value = buffer.ReadBytes((int)buffer.Length);
amqpRequestMessage.Map[ManagementConstants.Properties.SessionState] = value;
}
else
{
amqpRequestMessage.Map[ManagementConstants.Properties.SessionState] = null;
}
await this.InnerMessageReceiver.ExecuteRequestResponseAsync(amqpRequestMessage).ConfigureAwait(false);
}
catch (AmqpException amqpException)
{
throw AmqpExceptionHelper.ToMessagingContract(amqpException.Error);
}
}
protected override async Task OnRenewLockAsync()
{
try
{
AmqpRequestMessage amqpRequestMessage = AmqpRequestMessage.CreateRequest(ManagementConstants.Operations.RenewSessionLockOperation, this.OperationTimeout, null);
amqpRequestMessage.Map[ManagementConstants.Properties.SessionId] = this.SessionId;
AmqpResponseMessage amqpResponseMessage = await this.InnerMessageReceiver.ExecuteRequestResponseAsync(amqpRequestMessage).ConfigureAwait(false);
if (amqpResponseMessage.StatusCode == AmqpResponseStatusCode.OK)
{
this.LockedUntilUtc = amqpResponseMessage.GetValue<DateTime>(ManagementConstants.Properties.Expiration);
}
}
catch (AmqpException amqpException)
{
throw AmqpExceptionHelper.ToMessagingContract(amqpException.Error);
}
}
}
}

Просмотреть файл

@ -3,227 +3,53 @@
namespace Microsoft.Azure.ServiceBus.Amqp
{
using System;
using System.Linq;
using System.Net;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
using Microsoft.Azure.Amqp;
using Microsoft.Azure.Amqp.Sasl;
using Microsoft.Azure.Amqp.Transport;
using Microsoft.Azure.ServiceBus.Primitives;
sealed class AmqpQueueClient : QueueClient
{
const string CbsSaslMechanismName = "MSSBCBS";
public AmqpQueueClient(ServiceBusConnectionSettings connectionSettings, ReceiveMode mode)
: base(connectionSettings, mode)
public AmqpQueueClient(ServiceBusConnection servicebusConnection, string entityPath, ReceiveMode mode)
: base(servicebusConnection, entityPath, mode)
{
this.ContainerId = Guid.NewGuid().ToString("N");
this.AmqpVersion = new Version(1, 0, 0, 0);
this.MaxFrameSize = AmqpConstants.DefaultMaxFrameSize;
var tokenProvider = connectionSettings.CreateTokenProvider();
this.TokenProvider = tokenProvider;
this.CbsTokenProvider = new TokenProviderAdapter(this);
this.ConnectionManager = new FaultTolerantAmqpObject<AmqpConnection>(this.CreateConnectionAsync, this.CloseConnection);
this.TokenProvider = TokenProvider.CreateSharedAccessSignatureTokenProvider(this.ServiceBusConnection.SasKeyName, this.ServiceBusConnection.SasKey);
this.CbsTokenProvider = new TokenProviderAdapter(this.TokenProvider, this.ServiceBusConnection.OperationTimeout);
}
internal ICbsTokenProvider CbsTokenProvider { get; }
internal FaultTolerantAmqpObject<AmqpConnection> ConnectionManager { get; }
internal string ContainerId { get; }
Version AmqpVersion { get; }
uint MaxFrameSize { get; }
TokenProvider TokenProvider { get; }
internal static AmqpSettings CreateAmqpSettings(
Version amqpVersion,
bool useSslStreamSecurity,
bool hasTokenProvider,
string sslHostName = null,
bool useWebSockets = false,
bool sslStreamUpgrade = false,
NetworkCredential networkCredential = null,
RemoteCertificateValidationCallback certificateValidationCallback = null,
bool forceTokenProvider = true)
protected override MessageSender OnCreateMessageSender()
{
var settings = new AmqpSettings();
if (useSslStreamSecurity && !useWebSockets && sslStreamUpgrade)
{
var tlsSettings = new TlsTransportSettings();
tlsSettings.CertificateValidationCallback = certificateValidationCallback;
tlsSettings.TargetHost = sslHostName;
var tlsProvider = new TlsTransportProvider(tlsSettings);
tlsProvider.Versions.Add(new AmqpVersion(amqpVersion));
settings.TransportProviders.Add(tlsProvider);
}
if (hasTokenProvider || networkCredential != null)
{
var saslProvider = new SaslTransportProvider();
saslProvider.Versions.Add(new AmqpVersion(amqpVersion));
settings.TransportProviders.Add(saslProvider);
if (forceTokenProvider)
{
saslProvider.AddHandler(new SaslAnonymousHandler(CbsSaslMechanismName));
}
else if (networkCredential != null)
{
var plainHandler = new SaslPlainHandler();
plainHandler.AuthenticationIdentity = networkCredential.UserName;
plainHandler.Password = networkCredential.Password;
saslProvider.AddHandler(plainHandler);
}
else
{
// old client behavior: keep it for validation only
saslProvider.AddHandler(new SaslExternalHandler());
}
}
var amqpProvider = new AmqpTransportProvider();
amqpProvider.Versions.Add(new AmqpVersion(amqpVersion));
settings.TransportProviders.Add(amqpProvider);
return settings;
return new AmqpMessageSender(this.QueueName, MessagingEntityType.Queue, this.ServiceBusConnection, this.CbsTokenProvider);
}
internal override MessageSender OnCreateMessageSender()
protected override MessageReceiver OnCreateMessageReceiver()
{
return new AmqpMessageSender(this);
return new AmqpMessageReceiver(this.QueueName, MessagingEntityType.Queue, this.Mode, this.ServiceBusConnection.PrefetchCount, this.ServiceBusConnection, this.CbsTokenProvider);
}
internal override MessageReceiver OnCreateMessageReceiver()
protected override async Task<MessageSession> OnAcceptMessageSessionAsync(string sessionId)
{
return new AmqpMessageReceiver(this);
AmqpMessageReceiver receiver = new AmqpMessageReceiver(this.QueueName, MessagingEntityType.Queue, this.Mode, this.ServiceBusConnection.PrefetchCount, this.ServiceBusConnection, this.CbsTokenProvider, sessionId, true);
try
{
await receiver.GetSessionReceiverLinkAsync().ConfigureAwait(false);
}
catch (AmqpException exception)
{
// ToDo: Abort the Receiver here
AmqpExceptionHelper.ToMessagingContract(exception.Error, false);
}
MessageSession session = new AmqpMessageSession(receiver.SessionId, receiver.LockedUntilUtc, receiver);
return session;
}
protected override Task OnCloseAsync()
{
// Closing the Connection will also close all Links associated with it.
return this.ConnectionManager.CloseAsync();
}
[System.Diagnostics.CodeAnalysis.SuppressMessage(
"StyleCop.CSharp.NamingRules",
"SA1305:FieldNamesMustNotUseHungarianNotation",
Justification = "tpSettings is a local variable.")]
static TransportSettings CreateTcpTransportSettings(
string networkHost,
string hostName,
int port,
bool useSslStreamSecurity,
bool sslStreamUpgrade = false,
string sslHostName = null,
X509Certificate2 certificate = null,
RemoteCertificateValidationCallback certificateValidationCallback = null)
{
TcpTransportSettings tcpSettings = new TcpTransportSettings
{
Host = networkHost,
Port = port < 0 ? AmqpConstants.DefaultSecurePort : port,
ReceiveBufferSize = AmqpConstants.TransportBufferSize,
SendBufferSize = AmqpConstants.TransportBufferSize
};
TransportSettings tpSettings = tcpSettings;
if (useSslStreamSecurity && !sslStreamUpgrade)
{
TlsTransportSettings tlsSettings = new TlsTransportSettings(tcpSettings)
{
TargetHost = sslHostName ?? hostName,
Certificate = certificate,
CertificateValidationCallback = certificateValidationCallback
};
tpSettings = tlsSettings;
}
return tpSettings;
}
static AmqpConnectionSettings CreateAmqpConnectionSettings(uint maxFrameSize, string containerId, string hostName)
{
var connectionSettings = new AmqpConnectionSettings
{
MaxFrameSize = maxFrameSize,
ContainerId = containerId,
HostName = hostName
};
return connectionSettings;
}
[System.Diagnostics.CodeAnalysis.SuppressMessage(
"StyleCop.CSharp.NamingRules",
"SA1305:FieldNamesMustNotUseHungarianNotation",
Justification = "tpSettings is a local variable.")]
async Task<AmqpConnection> CreateConnectionAsync(TimeSpan timeout)
{
string hostName = this.ConnectionSettings.Endpoint.Host;
string networkHost = this.ConnectionSettings.Endpoint.Host;
int port = this.ConnectionSettings.Endpoint.Port;
var timeoutHelper = new TimeoutHelper(timeout);
var amqpSettings = CreateAmqpSettings(
amqpVersion: this.AmqpVersion,
useSslStreamSecurity: true,
hasTokenProvider: true);
TransportSettings tpSettings = CreateTcpTransportSettings(
networkHost: networkHost,
hostName: hostName,
port: port,
useSslStreamSecurity: true);
var initiator = new AmqpTransportInitiator(amqpSettings, tpSettings);
var transport = await initiator.ConnectTaskAsync(timeoutHelper.RemainingTime()).ConfigureAwait(false);
var connectionSettings = CreateAmqpConnectionSettings(this.MaxFrameSize, this.ContainerId, hostName);
var connection = new AmqpConnection(transport, amqpSettings, connectionSettings);
await connection.OpenAsync(timeoutHelper.RemainingTime()).ConfigureAwait(false);
// Always create the CBS Link + Session
var cbsLink = new AmqpCbsLink(connection);
if (connection.Extensions.Find<AmqpCbsLink>() == null)
{
connection.Extensions.Add(cbsLink);
}
return connection;
}
void CloseConnection(AmqpConnection connection)
{
connection.SafeClose();
}
/// <summary>
/// Provides an adapter from TokenProvider to ICbsTokenProvider for AMQP CBS usage.
/// </summary>
sealed class TokenProviderAdapter : ICbsTokenProvider
{
readonly AmqpQueueClient queueClient;
public TokenProviderAdapter(AmqpQueueClient queueClient)
{
Fx.Assert(queueClient != null, "tokenProvider cannot be null");
this.queueClient = queueClient;
}
public async Task<CbsToken> GetTokenAsync(Uri namespaceAddress, string appliesTo, string[] requiredClaims)
{
string claim = requiredClaims?.FirstOrDefault();
var tokenProvider = this.queueClient.TokenProvider;
var timeout = this.queueClient.ConnectionSettings.OperationTimeout;
var token = await tokenProvider.GetTokenAsync(appliesTo, claim, timeout).ConfigureAwait(false);
return new CbsToken(token.TokenValue, CbsConstants.ServiceBusSasTokenType, token.ExpiresAtUtc);
}
return this.ServiceBusConnection.CloseAsync();
}
}
}

Просмотреть файл

@ -0,0 +1,37 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace Microsoft.Azure.ServiceBus.Amqp
{
using System;
using Azure.Amqp;
using Azure.Amqp.Encoding;
using Azure.Amqp.Framing;
using Microsoft.Azure.Messaging.Amqp;
public sealed class AmqpRequestMessage
{
readonly AmqpMessage requestMessage;
AmqpRequestMessage(string operation, TimeSpan timeout, string trackingId)
{
this.Map = new AmqpMap();
this.requestMessage = AmqpMessage.Create(new AmqpValue() { Value = this.Map });
this.requestMessage.ApplicationProperties.Map[ManagementConstants.Request.Operation] = operation;
this.requestMessage.ApplicationProperties.Map[ManagementConstants.Properties.ServerTimeout] = (uint)timeout.TotalMilliseconds;
this.requestMessage.ApplicationProperties.Map[ManagementConstants.Properties.TrackingId] = trackingId ?? Guid.NewGuid().ToString();
}
public AmqpMessage AmqpMessage
{
get { return this.requestMessage; }
}
public AmqpMap Map { get; }
public static AmqpRequestMessage CreateRequest(string operation, TimeSpan timeout, string trackingId)
{
return new AmqpRequestMessage(operation, timeout, trackingId);
}
}
}

Просмотреть файл

@ -0,0 +1,25 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace Microsoft.Azure.ServiceBus.Amqp
{
using Microsoft.Azure.Amqp;
public class AmqpRequestResponseLinkCreator : AmqpLinkCreator
{
readonly string entityPath;
public AmqpRequestResponseLinkCreator(string entityPath, ServiceBusConnection serviceBusConnection, string[] requiredClaims, ICbsTokenProvider cbsTokenProvider, AmqpLinkSettings linkSettings)
: base(entityPath, serviceBusConnection, requiredClaims, cbsTokenProvider, linkSettings)
{
this.entityPath = entityPath;
}
protected override AmqpObject OnCreateAmqpLink(AmqpConnection connection, AmqpLinkSettings linkSettings, AmqpSession amqpSession)
{
AmqpObject link = new RequestResponseAmqpLink(AmqpClientConstants.EntityTypeManagement, amqpSession, this.entityPath, linkSettings.Properties);
linkSettings.LinkName = $"{connection.Settings.ContainerId};{connection.Identifier}:{amqpSession.Identifier}:{link.Identifier}";
return link;
}
}
}

Просмотреть файл

@ -0,0 +1,83 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace Microsoft.Azure.ServiceBus.Amqp
{
using System;
using System.Collections.Generic;
using System.Linq;
using Azure.Amqp;
using Azure.Amqp.Encoding;
using Azure.Amqp.Framing;
using Microsoft.Azure.Messaging.Amqp;
public sealed class AmqpResponseMessage
{
readonly AmqpMessage responseMessage;
AmqpResponseMessage(AmqpMessage responseMessage)
{
this.responseMessage = responseMessage;
this.StatusCode = this.responseMessage.GetResponseStatusCode();
string trackingId;
if (this.responseMessage.ApplicationProperties.Map.TryGetValue(ManagementConstants.Properties.TrackingId, out trackingId))
{
this.TrackingId = trackingId;
}
if (responseMessage.ValueBody != null)
{
this.Map = responseMessage.ValueBody.Value as AmqpMap;
}
}
public AmqpMessage AmqpMessage
{
get { return this.responseMessage; }
}
public AmqpResponseStatusCode StatusCode { get; }
public string TrackingId { get; private set; }
public AmqpMap Map { get; }
public static AmqpResponseMessage CreateResponse(AmqpMessage response)
{
return new AmqpResponseMessage(response);
}
public TValue GetValue<TValue>(MapKey key)
{
if (this.Map == null)
{
throw new ArgumentException(AmqpValue.Name);
}
var valueObject = this.Map[key];
if (valueObject == null)
{
throw new ArgumentException(key.ToString());
}
if (!(valueObject is TValue))
{
throw new ArgumentException(key.ToString());
}
return (TValue)this.Map[key];
}
public IEnumerable<TValue> GetListValue<TValue>(MapKey key)
{
if (this.Map == null)
{
throw new ArgumentException(AmqpValue.Name);
}
List<object> list = (List<object>)this.Map[key];
return list.Cast<TValue>();
}
}
}

Просмотреть файл

@ -0,0 +1,23 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace Microsoft.Azure.ServiceBus.Amqp
{
using Microsoft.Azure.Amqp;
public class AmqpSendReceiveLinkCreator : AmqpLinkCreator
{
public AmqpSendReceiveLinkCreator(string entityPath, ServiceBusConnection serviceBusConnection, string[] requiredClaims, ICbsTokenProvider cbsTokenProvider, AmqpLinkSettings linkSettings)
: base(entityPath, serviceBusConnection, requiredClaims, cbsTokenProvider, linkSettings)
{
}
protected override AmqpObject OnCreateAmqpLink(AmqpConnection connection, AmqpLinkSettings linkSettings, AmqpSession amqpSession)
{
AmqpObject link = (linkSettings.IsReceiver()) ? (AmqpObject)new ReceivingAmqpLink(linkSettings) : (AmqpObject)new SendingAmqpLink(linkSettings);
linkSettings.LinkName = $"{connection.Settings.ContainerId};{connection.Identifier}:{amqpSession.Identifier}:{link.Identifier}";
((AmqpLink)link).AttachTo(amqpSession);
return link;
}
}
}

Просмотреть файл

@ -0,0 +1,50 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace Microsoft.Azure.ServiceBus.Amqp
{
using System.Threading.Tasks;
using Microsoft.Azure.Amqp;
using Microsoft.Azure.ServiceBus.Primitives;
public class AmqpSubscriptionClient : SubscriptionClient
{
public AmqpSubscriptionClient(ServiceBusConnection servicebusConnection, string topicPath, string subscriptionName, ReceiveMode mode)
: base(servicebusConnection, topicPath, subscriptionName, mode)
{
this.TokenProvider = TokenProvider.CreateSharedAccessSignatureTokenProvider(this.ServiceBusConnection.SasKeyName, this.ServiceBusConnection.SasKey);
this.CbsTokenProvider = new TokenProviderAdapter(this.TokenProvider, this.ServiceBusConnection.OperationTimeout);
}
internal ICbsTokenProvider CbsTokenProvider { get; }
TokenProvider TokenProvider { get; }
protected override MessageReceiver OnCreateMessageReceiver()
{
return new AmqpMessageReceiver(this.SubscriptionPath, MessagingEntityType.Subscriber, this.Mode, this.ServiceBusConnection.PrefetchCount, this.ServiceBusConnection, this.CbsTokenProvider);
}
protected override async Task<MessageSession> OnAcceptMessageSessionAsync(string sessionId)
{
AmqpMessageReceiver receiver = new AmqpMessageReceiver(this.SubscriptionPath, MessagingEntityType.Subscriber, this.Mode, this.ServiceBusConnection.PrefetchCount, this.ServiceBusConnection, this.CbsTokenProvider, sessionId, true);
try
{
await receiver.GetSessionReceiverLinkAsync().ConfigureAwait(false);
}
catch (AmqpException exception)
{
// ToDo: Abort the Receiver here
AmqpExceptionHelper.ToMessagingContract(exception.Error, false);
}
MessageSession session = new AmqpMessageSession(receiver.SessionId, receiver.LockedUntilUtc, receiver);
return session;
}
protected override Task OnCloseAsync()
{
// Closing the Connection will also close all Links associated with it.
return this.ServiceBusConnection.CloseAsync();
}
}
}

Просмотреть файл

@ -0,0 +1,34 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace Microsoft.Azure.ServiceBus.Amqp
{
using System.Threading.Tasks;
using Microsoft.Azure.Amqp;
using Microsoft.Azure.ServiceBus.Primitives;
sealed class AmqpTopicClient : TopicClient
{
public AmqpTopicClient(ServiceBusConnection servicebusConnection, string entityPath)
: base(servicebusConnection, entityPath)
{
this.TokenProvider = TokenProvider.CreateSharedAccessSignatureTokenProvider(this.ServiceBusConnection.SasKeyName, this.ServiceBusConnection.SasKey);
this.CbsTokenProvider = new TokenProviderAdapter(this.TokenProvider, this.ServiceBusConnection.OperationTimeout);
}
internal ICbsTokenProvider CbsTokenProvider { get; }
TokenProvider TokenProvider { get; }
protected override MessageSender OnCreateMessageSender()
{
return new AmqpMessageSender(this.TopicName, MessagingEntityType.Topic, this.ServiceBusConnection, this.CbsTokenProvider);
}
protected override Task OnCloseAsync()
{
// Closing the Connection will also close all Links associated with it.
return this.ServiceBusConnection.CloseAsync();
}
}
}

Просмотреть файл

@ -0,0 +1,53 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace Microsoft.Azure.Messaging.Amqp
{
using Microsoft.Azure.Amqp.Encoding;
static class ManagementConstants
{
public const string Microsoft = "com.microsoft";
public static class Request
{
public const string Operation = "operation";
}
public static class Response
{
public const string StatusCode = "statusCode";
public const string StatusDescription = "statusDescription";
public const string ErrorCondition = "errorCondition";
}
public static class Operations
{
public const string RenewLockOperation = Microsoft + ":renew-lock";
public const string ReceiveBySequenceNumberOperation = Microsoft + ":receive-by-sequence-number";
public const string UpdateDispositionOperation = Microsoft + ":update-disposition";
public const string RenewSessionLockOperation = Microsoft + ":renew-session-lock";
public const string SetSessionStateOperation = Microsoft + ":set-session-state";
public const string GetSessionStateOperation = Microsoft + ":get-session-state";
}
public static class Properties
{
public static readonly MapKey ServerTimeout = new MapKey(Microsoft + ":server-timeout");
public static readonly MapKey TrackingId = new MapKey(Microsoft + ":tracking-id");
public static readonly MapKey SessionState = new MapKey("session-state");
public static readonly MapKey LockToken = new MapKey("lock-token");
public static readonly MapKey LockTokens = new MapKey("lock-tokens");
public static readonly MapKey SequenceNumbers = new MapKey("sequence-numbers");
public static readonly MapKey Expirations = new MapKey("expirations");
public static readonly MapKey Expiration = new MapKey("expiration");
public static readonly MapKey SessionId = new MapKey("session-id");
public static readonly MapKey ReceiverSettleMode = new MapKey("receiver-settle-mode");
public static readonly MapKey Message = new MapKey("message");
public static readonly MapKey Messages = new MapKey("messages");
public static readonly MapKey DispositionStatus = new MapKey("disposition-status");
}
}
}

Просмотреть файл

@ -59,7 +59,10 @@ namespace Microsoft.Azure.ServiceBus
{
try
{
this.messageId = messageIdGeneratorFunc();
if (messageIdGeneratorFunc != null)
{
this.messageId = messageIdGeneratorFunc();
}
}
catch (Exception ex)
{
@ -919,11 +922,7 @@ namespace Microsoft.Azure.ServiceBus
/// <exception cref="ArgumentNullException">Thrown if invoked with null.</exception>
public static void SetMessageIdGenerator(Func<string> messageIdGenerator)
{
if (messageIdGenerator == null)
{
throw new ArgumentNullException(nameof(messageIdGenerator));
}
messageIdGeneratorFunc = messageIdGenerator;
messageIdGeneratorFunc = messageIdGenerator;
}
/// <summary>Deserializes the brokered message body into an object of the specified type by using the
@ -1010,9 +1009,9 @@ namespace Microsoft.Azure.ServiceBus
return this.Receiver.AbandonAsync(new[] { this.LockToken });
}
// Summary:
// Asynchronously completes the receive operation of a message and indicates that
// the message should be marked as processed and deleted.
/// <summary>Asynchronously completes the receive operation of a message and
/// indicates that the message should be marked as processed and deleted.</summary>
/// <returns>The asynchronous result of the operation.</returns>
public Task CompleteAsync()
{
this.ThrowIfDisposed();
@ -1021,8 +1020,8 @@ namespace Microsoft.Azure.ServiceBus
return this.Receiver.CompleteAsync(new[] { this.LockToken });
}
// Summary:
// Asynchronously moves the message to the dead letter queue.
/// <summary>Asynchronously moves the message to the dead letter queue.</summary>
/// <returns>The asynchronous result of the operation.</returns>
public Task DeadLetterAsync()
{
this.ThrowIfDisposed();
@ -1031,8 +1030,8 @@ namespace Microsoft.Azure.ServiceBus
return this.Receiver.DeadLetterAsync(new[] { this.LockToken });
}
// Summary:
// Asynchronously indicates that the receiver wants to defer the processing for this message.
/// <summary>Asynchronously indicates that the receiver wants to defer the processing for this message.</summary>
/// <returns>The asynchronous result of the operation.</returns>
public Task DeferAsync()
{
this.ThrowIfDisposed();
@ -1041,6 +1040,16 @@ namespace Microsoft.Azure.ServiceBus
return this.Receiver.DeferAsync(new[] { this.LockToken });
}
/// <summary>Specifies the time period within which the host renews its lock on a message.</summary>
/// <returns>The host that is being locked.</returns>
public Task RenewLockAsync()
{
this.ThrowIfDisposed();
this.ThrowIfNotLocked();
return this.InternalRenewLockAsync(this.LockToken);
}
/// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary>
public void Dispose()
{
@ -1239,6 +1248,11 @@ namespace Microsoft.Azure.ServiceBus
return (value == null) ? typeof(object) : value.GetType();
}
async Task InternalRenewLockAsync(Guid lockToken)
{
this.LockedUntilUtc = await this.Receiver.RenewLockAsync(this.LockToken).ConfigureAwait(false);
}
/// <summary> Performs application-defined tasks associated with freeing, releasing, or resetting
/// unmanaged resources. </summary>
/// <param name="disposing"> true if resources should be disposed, false if not. </param>

Просмотреть файл

@ -7,26 +7,70 @@ namespace Microsoft.Azure.ServiceBus
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Azure.ServiceBus.Amqp;
abstract class MessageReceiver : ClientEntity
public abstract class MessageReceiver : ClientEntity
{
[System.Diagnostics.CodeAnalysis.SuppressMessage(
"StyleCop.CSharp.ReadabilityRules",
"SA1126:PrefixCallsCorrectly",
Justification = "This is not a method call, but a type.")]
protected MessageReceiver(ReceiveMode receiveMode)
readonly TimeSpan operationTimeout;
int prefetchCount;
protected MessageReceiver(ReceiveMode receiveMode, TimeSpan operationTimeout)
: base(nameof(MessageReceiver) + StringUtility.GetRandomString())
{
this.ReceiveMode = receiveMode;
this.operationTimeout = operationTimeout;
}
public abstract string Path { get; }
public ReceiveMode ReceiveMode { get; protected set; }
public virtual int PrefetchCount
{
get
{
return this.prefetchCount;
}
set
{
if (value < 0)
{
throw Fx.Exception.ArgumentOutOfRange(nameof(this.PrefetchCount), value, "Value must be greater than 0");
}
this.prefetchCount = value;
}
}
internal TimeSpan OperationTimeout
{
get { return this.operationTimeout; }
}
protected MessagingEntityType EntityType { get; set; }
public async Task<BrokeredMessage> ReceiveAsync()
{
IList<BrokeredMessage> messages = await this.ReceiveAsync(1).ConfigureAwait(false);
if (messages != null && messages.Count > 0)
{
return messages[0];
}
return null;
}
public Task<IList<BrokeredMessage>> ReceiveAsync(int maxMessageCount)
{
return this.OnReceiveAsync(maxMessageCount);
}
public Task<IList<BrokeredMessage>> ReceiveBySequenceNumberAsync(IEnumerable<long> sequenceNumbers)
{
return this.OnReceiveBySequenceNumberAsync(sequenceNumbers);
}
public Task CompleteAsync(IEnumerable<Guid> lockTokens)
{
this.ThrowIfNotPeekLockMode();
@ -59,8 +103,23 @@ namespace Microsoft.Azure.ServiceBus
return this.OnDeadLetterAsync(lockTokens);
}
public Task<DateTime> RenewLockAsync(Guid lockToken)
{
this.ThrowIfNotPeekLockMode();
MessageReceiver.ValidateLockTokens(new Guid[] { lockToken });
return this.OnRenewLockAsync(lockToken);
}
internal Task<AmqpResponseMessage> ExecuteRequestResponseAsync(AmqpRequestMessage amqpRequestMessage)
{
return this.OnExecuteRequestResponseAsync(amqpRequestMessage);
}
protected abstract Task<IList<BrokeredMessage>> OnReceiveAsync(int maxMessageCount);
protected abstract Task<IList<BrokeredMessage>> OnReceiveBySequenceNumberAsync(IEnumerable<long> sequenceNumbers);
protected abstract Task OnCompleteAsync(IEnumerable<Guid> lockTokens);
protected abstract Task OnAbandonAsync(IEnumerable<Guid> lockTokens);
@ -69,6 +128,10 @@ namespace Microsoft.Azure.ServiceBus
protected abstract Task OnDeadLetterAsync(IEnumerable<Guid> lockTokens);
protected abstract Task<DateTime> OnRenewLockAsync(Guid lockToken);
protected abstract Task<AmqpResponseMessage> OnExecuteRequestResponseAsync(AmqpRequestMessage requestAmqpMessage);
static void ValidateLockTokens(IEnumerable<Guid> lockTokens)
{
if (lockTokens == null || !lockTokens.Any())

Просмотреть файл

@ -3,19 +3,30 @@
namespace Microsoft.Azure.ServiceBus
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
abstract class MessageSender : ClientEntity
public abstract class MessageSender : ClientEntity
{
[System.Diagnostics.CodeAnalysis.SuppressMessage(
"StyleCop.CSharp.ReadabilityRules",
"SA1126:PrefixCallsCorrectly",
Justification = "This is not a method call, but a type.")]
protected MessageSender()
protected MessageSender(TimeSpan operationTimeout)
: base(nameof(MessageSender) + StringUtility.GetRandomString())
{
this.OperationTimeout = operationTimeout;
}
internal TimeSpan OperationTimeout { get; }
protected MessagingEntityType EntityType { get; set; }
public Task SendAsync(BrokeredMessage brokeredMessage)
{
return this.SendAsync(new BrokeredMessage[] { brokeredMessage });
}
public Task SendAsync(IEnumerable<BrokeredMessage> brokeredMessages)

Просмотреть файл

@ -0,0 +1,110 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace Microsoft.Azure.ServiceBus
{
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using Amqp;
using Azure.Amqp;
using Messaging.Amqp;
public abstract class MessageSession : MessageReceiver
{
/// <summary>Represents a message session that allows grouping of related messages for processing in a single transaction.</summary>
protected MessageSession(ReceiveMode receiveMode, string sessionId, DateTime lockedUntilUtc, MessageReceiver innerReceiver)
: base(receiveMode, innerReceiver.OperationTimeout)
{
if (innerReceiver == null)
{
throw Fx.Exception.ArgumentNull("innerReceiver");
}
this.SessionId = sessionId;
this.LockedUntilUtc = lockedUntilUtc;
this.InnerMessageReceiver = innerReceiver;
}
public string SessionId { get; protected set; }
public DateTime LockedUntilUtc { get; protected set; }
public override string Path
{
get { return this.InnerMessageReceiver.Path; }
}
protected MessageReceiver InnerMessageReceiver { get; set; }
public override async Task CloseAsync()
{
if (this.InnerMessageReceiver != null)
{
await this.InnerMessageReceiver.CloseAsync().ConfigureAwait(false);
}
}
public Task<Stream> GetStateAsync()
{
return this.OnGetStateAsync();
}
public Task SetStateAsync(Stream sessionState)
{
return this.OnSetStateAsync(sessionState);
}
public Task RenewLockAsync()
{
return this.OnRenewLockAsync();
}
protected abstract Task<Stream> OnGetStateAsync();
protected abstract Task OnSetStateAsync(Stream sessionState);
protected abstract Task OnRenewLockAsync();
protected override Task<IList<BrokeredMessage>> OnReceiveAsync(int maxMessageCount)
{
return this.InnerMessageReceiver.ReceiveAsync(maxMessageCount);
}
protected override Task<IList<BrokeredMessage>> OnReceiveBySequenceNumberAsync(IEnumerable<long> sequenceNumbers)
{
return this.InnerMessageReceiver.ReceiveBySequenceNumberAsync(sequenceNumbers);
}
protected override Task OnCompleteAsync(IEnumerable<Guid> lockTokens)
{
return this.InnerMessageReceiver.CompleteAsync(lockTokens);
}
protected override Task OnAbandonAsync(IEnumerable<Guid> lockTokens)
{
return this.InnerMessageReceiver.AbandonAsync(lockTokens);
}
protected override Task OnDeferAsync(IEnumerable<Guid> lockTokens)
{
return this.InnerMessageReceiver.DeferAsync(lockTokens);
}
protected override Task OnDeadLetterAsync(IEnumerable<Guid> lockTokens)
{
return this.InnerMessageReceiver.DeadLetterAsync(lockTokens);
}
protected override Task<DateTime> OnRenewLockAsync(Guid lockToken)
{
return this.InnerMessageReceiver.RenewLockAsync(lockToken);
}
protected override Task<AmqpResponseMessage> OnExecuteRequestResponseAsync(AmqpRequestMessage requestAmqpMessage)
{
throw new NotImplementedException();
}
}
}

Просмотреть файл

@ -27,7 +27,7 @@ namespace Microsoft.Azure.ServiceBus
public Task<LockRelease> LockAsync(CancellationToken cancellationToken)
{
var waitTask = this.asyncSemaphore.WaitAsync(cancellationToken);
Task waitTask = this.asyncSemaphore.WaitAsync(cancellationToken);
if (waitTask.IsCompleted)
{
// Avoid an allocation in the non-contention case.

Просмотреть файл

@ -0,0 +1,71 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace Microsoft.Azure.ServiceBus.Primitives
{
using System;
using System.Collections.Concurrent;
using System.Threading.Tasks;
sealed class ConcurrentExpiringSet<TKey>
{
readonly ConcurrentDictionary<TKey, DateTime> dictionary;
readonly object cleanupSynObject = new object();
bool cleanupScheduled;
public ConcurrentExpiringSet()
{
this.dictionary = new ConcurrentDictionary<TKey, DateTime>();
}
public void AddOrUpdate(TKey key, DateTime expiration)
{
this.dictionary[key] = expiration;
this.ScheduleCleanup();
}
public bool Contains(TKey key)
{
DateTime expiration;
if (this.dictionary.TryGetValue(key, out expiration))
{
return true;
}
return false;
}
void ScheduleCleanup()
{
lock (this.cleanupSynObject)
{
if (this.cleanupScheduled)
{
return;
}
this.cleanupScheduled = true;
Task.Run(() => this.CollectExpiredEntries());
}
}
void CollectExpiredEntries()
{
lock (this.cleanupSynObject)
{
this.cleanupScheduled = false;
}
foreach (TKey key in this.dictionary.Keys)
{
if (DateTime.UtcNow > this.dictionary[key])
{
DateTime entry;
this.dictionary.TryRemove(key, out entry);
}
}
this.ScheduleCleanup();
}
}
}

Просмотреть файл

@ -0,0 +1,14 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace Microsoft.Azure.ServiceBus.Primitives
{
enum DispositionStatus
{
Completed = 1,
Defered = 2,
Suspended = 3,
Abandoned = 4,
Renewed = 5,
}
}

Просмотреть файл

@ -6,18 +6,24 @@ namespace Microsoft.Azure.ServiceBus
public static class EntityNameHelper
{
public const string PathDelimiter = @"/";
public const string Subscriptions = "Subscriptions";
public const string SubQueuePrefix = "$";
public const string DeadLetterQueueSuffix = "DeadLetterQueue";
public const string DeadLetterQueueName = SubQueuePrefix + DeadLetterQueueSuffix;
public static string FormatDeadLetterPath(string queuePath)
public static string FormatDeadLetterPath(string entityPath)
{
return EntityNameHelper.FormatSubQueuePath(queuePath, EntityNameHelper.DeadLetterQueueName);
return EntityNameHelper.FormatSubQueuePath(entityPath, EntityNameHelper.DeadLetterQueueName);
}
public static string FormatSubQueuePath(string entityPath, string subQueueName)
{
return string.Concat(entityPath, EntityNameHelper.PathDelimiter, subQueueName);
}
public static string FormatSubscriptionPath(string topicPath, string subscriptionName)
{
return string.Concat(topicPath, PathDelimiter, Subscriptions, PathDelimiter, subscriptionName);
}
}
}

Просмотреть файл

@ -3,20 +3,13 @@
namespace Microsoft.Azure.ServiceBus
{
enum MessagingEntityType
public enum MessagingEntityType
{
Queue = 0,
Topic = 1,
Subscriber = 2,
Filter = 3,
Namespace = 4,
VolatileTopic = 5,
VolatileTopicSubscription = 6,
EventHub = 7,
ConsumerGroup = 8,
Partition = 9,
Checkpoint = 10,
RevokedPublisher = 11,
Unknown = (int)0x7FFFFFFE,
}
}

Просмотреть файл

@ -0,0 +1,139 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace Microsoft.Azure.ServiceBus
{
using System;
using System.Threading.Tasks;
using Microsoft.Azure.Amqp;
using Microsoft.Azure.Amqp.Transport;
using Microsoft.Azure.ServiceBus.Amqp;
using Microsoft.Azure.ServiceBus.Primitives;
public abstract class ServiceBusConnection
{
public static readonly TimeSpan DefaultOperationTimeout = TimeSpan.FromMinutes(1);
static readonly Version AmqpVersion = new Version(1, 0, 0, 0);
int prefetchCount;
protected ServiceBusConnection(TimeSpan operationTimeout, RetryPolicy retryPolicy)
{
this.OperationTimeout = operationTimeout;
this.RetryPolicy = retryPolicy;
}
public Uri Endpoint { get; set; }
/// <summary>
/// OperationTimeout is applied in erroneous situations to notify the caller about the relevant <see cref="ServiceBusException"/>
/// </summary>
public TimeSpan OperationTimeout { get; set; }
/// <summary>
/// Get the retry policy instance that was created as part of this builder's creation.
/// </summary>
public RetryPolicy RetryPolicy { get; set; }
/// <summary>
/// Get the shared access policy key value from the connection string
/// </summary>
/// <value>Shared Access Signature key</value>
public string SasKey { get; set; }
/// <summary>
/// Get the shared access policy owner name from the connection string
/// </summary>
public string SasKeyName { get; set; }
/// <summary>Gets or sets the number of messages that the message receiver can simultaneously request.</summary>
/// <value>The number of messages that the message receiver can simultaneously request.</value>
public int PrefetchCount
{
get
{
return this.prefetchCount;
}
set
{
if (value < 0)
{
throw Fx.Exception.ArgumentOutOfRange(nameof(this.PrefetchCount), value, "Value must be greater than 0");
}
this.prefetchCount = value;
}
}
internal FaultTolerantAmqpObject<AmqpConnection> ConnectionManager { get; set; }
public Task CloseAsync()
{
return this.ConnectionManager.CloseAsync();
}
internal QueueClient CreateQueueClient(string entityPath, ReceiveMode mode)
{
return new AmqpQueueClient(this, entityPath, mode);
}
internal TopicClient CreateTopicClient(string topicPath)
{
return new AmqpTopicClient(this, topicPath);
}
internal SubscriptionClient CreateSubscriptionClient(string topicPath, string subscriptionName, ReceiveMode mode)
{
return new AmqpSubscriptionClient(this, topicPath, subscriptionName, mode);
}
protected void InitializeConnection(ServiceBusConnectionStringBuilder builder)
{
this.Endpoint = builder.Endpoint;
this.SasKeyName = builder.SasKeyName;
this.SasKey = builder.SasKey;
this.ConnectionManager = new FaultTolerantAmqpObject<AmqpConnection>(this.CreateConnectionAsync, ServiceBusConnection.CloseConnection);
}
static void CloseConnection(AmqpConnection connection)
{
connection.SafeClose();
}
async Task<AmqpConnection> CreateConnectionAsync(TimeSpan timeout)
{
string hostName = this.Endpoint.Host;
string networkHost = this.Endpoint.Host;
int port = this.Endpoint.Port;
TimeoutHelper timeoutHelper = new TimeoutHelper(timeout);
AmqpSettings amqpSettings = AmqpConnectionHelper.CreateAmqpSettings(
amqpVersion: ServiceBusConnection.AmqpVersion,
useSslStreamSecurity: true,
hasTokenProvider: true);
TransportSettings tpSettings = AmqpConnectionHelper.CreateTcpTransportSettings(
networkHost: networkHost,
hostName: hostName,
port: port,
useSslStreamSecurity: true);
AmqpTransportInitiator initiator = new AmqpTransportInitiator(amqpSettings, tpSettings);
TransportBase transport = await initiator.ConnectTaskAsync(timeoutHelper.RemainingTime()).ConfigureAwait(false);
string containerId = Guid.NewGuid().ToString();
AmqpConnectionSettings amqpConnectionSettings = AmqpConnectionHelper.CreateAmqpConnectionSettings(AmqpConstants.DefaultMaxFrameSize, containerId, hostName);
AmqpConnection connection = new AmqpConnection(transport, amqpSettings, amqpConnectionSettings);
await connection.OpenAsync(timeoutHelper.RemainingTime()).ConfigureAwait(false);
// Always create the CBS Link + Session
AmqpCbsLink cbsLink = new AmqpCbsLink(connection);
if (connection.Extensions.Find<AmqpCbsLink>() == null)
{
connection.Extensions.Add(cbsLink);
}
return connection;
}
}
}

Просмотреть файл

@ -1,229 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace Microsoft.Azure.ServiceBus
{
using System;
using System.Text;
using Microsoft.Azure.ServiceBus.Amqp;
/// <summary>
/// ServiceBusConnectionSettings can be used to construct a connection string which can establish communication with ServiceBus entities.
/// It can also be used to perform basic validation on an existing connection string.
/// <para/>
/// A connection string is basically a string consisted of key-value pair separated by ";".
/// Basic format is "&lt;key&gt;=&lt;value&gt;[;&lt;key&gt;=&lt;value&gt;]" where supported key name are as follow:
/// <para/> Endpoint - the URL that contains the servicebus namespace
/// <para/> EntityPath - the path to the service bus entity (queue/topic/eventhub/subscription/consumergroup/partition)
/// <para/> SharedAccessKeyName - the key name to the corresponding shared access policy rule for the namespace, or entity.
/// <para/> SharedAccessKey - the key for the corresponding shared access policy rule of the namespace or entity.
/// </summary>
/// <example>
/// Sample code:
/// <code>
/// var connectionSettings = new ServiceBusConnectionSettings(
/// "ServiceBusNamespaceName",
/// "ServiceBusEntityName", // eventHub, queue, or topic name
/// "SharedAccessSignatureKeyName",
/// "SharedAccessSignatureKey");
/// string connectionString = connectionSettings.ToString();
/// </code>
/// </example>
public class ServiceBusConnectionSettings
{
const string KeyValueSeparator = "=";
const string KeyValuePairDelimiter = ";";
static readonly TimeSpan DefaultOperationTimeout = TimeSpan.FromMinutes(1);
static readonly string EndpointScheme = "amqps";
static readonly string EndpointFormat = $"{EndpointScheme}://{{0}}.servicebus.windows.net";
static readonly string EndpointConfigName = "Endpoint";
static readonly string SharedAccessKeyNameConfigName = "SharedAccessKeyName";
static readonly string SharedAccessKeyConfigName = "SharedAccessKey";
static readonly string EntityPathConfigName = "EntityPath";
/// <summary>
/// Build a connection string consumable by <see cref="QueueClient.Create(string)"/>
/// </summary>
/// <param name="namespaceName">Namespace name (the dns suffix, ex: .servicebus.windows.net, is not required)</param>
/// <param name="entityPath">Entity path. For Queue case specify Queue name.</param>
/// <param name="sharedAccessKeyName">Shared Access Key name</param>
/// <param name="sharedAccessKey">Shared Access Key</param>
public ServiceBusConnectionSettings(string namespaceName, string entityPath, string sharedAccessKeyName, string sharedAccessKey)
: this(namespaceName, entityPath, sharedAccessKeyName, sharedAccessKey, DefaultOperationTimeout, RetryPolicy.Default)
{
}
/// <summary>
/// ConnectionString format:
/// Endpoint=sb://namespace_DNS_Name;EntityPath=EVENT_HUB_NAME;SharedAccessKeyName=SHARED_ACCESS_KEY_NAME;SharedAccessKey=SHARED_ACCESS_KEY
/// </summary>
/// <param name="connectionString">ServiceBus ConnectionString</param>
public ServiceBusConnectionSettings(string connectionString)
{
if (string.IsNullOrWhiteSpace(connectionString))
{
throw Fx.Exception.ArgumentNullOrWhiteSpace(nameof(connectionString));
}
this.OperationTimeout = DefaultOperationTimeout;
this.RetryPolicy = RetryPolicy.Default;
this.ParseConnectionString(connectionString);
}
ServiceBusConnectionSettings(
string namespaceName,
string entityPath,
string sharedAccessKeyName,
string sharedAccessKey,
TimeSpan operationTimeout,
RetryPolicy retryPolicy)
{
if (string.IsNullOrWhiteSpace(namespaceName) || string.IsNullOrWhiteSpace(entityPath))
{
throw Fx.Exception.ArgumentNullOrWhiteSpace(string.IsNullOrWhiteSpace(namespaceName) ? nameof(namespaceName) : nameof(entityPath));
}
if (string.IsNullOrWhiteSpace(sharedAccessKeyName) || string.IsNullOrWhiteSpace(sharedAccessKey))
{
throw Fx.Exception.ArgumentNullOrWhiteSpace(string.IsNullOrWhiteSpace(sharedAccessKeyName) ? nameof(sharedAccessKeyName) : nameof(sharedAccessKey));
}
if (namespaceName.Contains("."))
{
// It appears to be a fully qualified host name, use it.
this.Endpoint = new Uri(EndpointScheme + "://" + namespaceName);
}
else
{
this.Endpoint = new Uri(EndpointFormat.FormatInvariant(namespaceName));
}
this.EntityPath = entityPath;
this.SasKey = sharedAccessKey;
this.SasKeyName = sharedAccessKeyName;
this.OperationTimeout = operationTimeout;
this.RetryPolicy = retryPolicy ?? RetryPolicy.Default;
}
public Uri Endpoint { get; set; }
/// <summary>
/// Get the shared access policy key value from the connection string
/// </summary>
/// <value>Shared Access Signature key</value>
public string SasKey { get; set; }
/// <summary>
/// Get the shared access policy owner name from the connection string
/// </summary>
public string SasKeyName { get; set; }
/// <summary>
/// Get the entity path value from the connection string
/// </summary>
public string EntityPath { get; set; }
/// <summary>
/// OperationTimeout is applied in erroneous situations to notify the caller about the relevant <see cref="ServiceBusException"/>
/// </summary>
public TimeSpan OperationTimeout { get; set; }
/// <summary>
/// Get the retry policy instance that was created as part of this builder's creation.
/// </summary>
public RetryPolicy RetryPolicy { get; set; }
public ServiceBusConnectionSettings Clone()
{
var clone = new ServiceBusConnectionSettings(this.ToString())
{
OperationTimeout = this.OperationTimeout,
RetryPolicy = this.RetryPolicy
};
return clone;
}
/// <summary>
/// Creates a TokenProvider given the credentials in this ServiceBusConnectionSettings.
/// </summary>
/// <returns></returns>
public TokenProvider CreateTokenProvider()
{
return TokenProvider.CreateSharedAccessSignatureTokenProvider(this.SasKeyName, this.SasKey);
}
/// <summary>
/// Returns an interoperable connection string that can be used to connect to ServiceBus Namespace
/// </summary>
/// <returns>the connection string</returns>
public override string ToString()
{
var connectionStringBuilder = new StringBuilder();
if (this.Endpoint != null)
{
connectionStringBuilder.Append($"{EndpointConfigName}{KeyValueSeparator}{this.Endpoint}{KeyValuePairDelimiter}");
}
if (!string.IsNullOrWhiteSpace(this.EntityPath))
{
connectionStringBuilder.Append($"{EntityPathConfigName}{KeyValueSeparator}{this.EntityPath}{KeyValuePairDelimiter}");
}
if (!string.IsNullOrWhiteSpace(this.SasKeyName))
{
connectionStringBuilder.Append($"{SharedAccessKeyNameConfigName}{KeyValueSeparator}{this.SasKeyName}{KeyValuePairDelimiter}");
}
if (!string.IsNullOrWhiteSpace(this.SasKey))
{
connectionStringBuilder.Append($"{SharedAccessKeyConfigName}{KeyValueSeparator}{this.SasKey}");
}
return connectionStringBuilder.ToString();
}
internal QueueClient CreateQueueClient(ReceiveMode mode)
{
// In the future to support other protocols add that logic here.
return new AmqpQueueClient(this.Clone(), mode);
}
void ParseConnectionString(string connectionString)
{
// First split based on ';'
string[] keyValuePairs = connectionString.Split(new[] { KeyValuePairDelimiter }, StringSplitOptions.RemoveEmptyEntries);
foreach (var keyValuePair in keyValuePairs)
{
// Now split based on the _first_ '='
string[] keyAndValue = keyValuePair.Split(new[] { KeyValueSeparator[0] }, 2);
string key = keyAndValue[0];
if (keyAndValue.Length != 2)
{
throw Fx.Exception.Argument(nameof(connectionString), $"Value for the connection string parameter name '{key}' was not found.");
}
string value = keyAndValue[1];
if (key.Equals(EndpointConfigName, StringComparison.OrdinalIgnoreCase))
{
this.Endpoint = new Uri(value);
}
else if (key.Equals(EntityPathConfigName, StringComparison.OrdinalIgnoreCase))
{
this.EntityPath = value;
}
else if (key.Equals(SharedAccessKeyNameConfigName, StringComparison.OrdinalIgnoreCase))
{
this.SasKeyName = value;
}
else if (key.Equals(SharedAccessKeyConfigName, StringComparison.OrdinalIgnoreCase))
{
this.SasKey = value;
}
else
{
throw Fx.Exception.Argument(nameof(connectionString), $"Illegal connection string parameter name '{key}'");
}
}
}
}
}

Просмотреть файл

@ -0,0 +1,142 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace Microsoft.Azure.ServiceBus.Primitives
{
using System;
using System.Text;
public class ServiceBusConnectionStringBuilder
{
const char KeyValueSeparator = '=';
const char KeyValuePairDelimiter = ';';
static readonly string EndpointScheme = "amqps";
static readonly string EndpointFormat = EndpointScheme + "://{0}.servicebus.windows.net";
static readonly string EndpointConfigName = "Endpoint";
static readonly string SharedAccessKeyNameConfigName = "SharedAccessKeyName";
static readonly string SharedAccessKeyConfigName = "SharedAccessKey";
static readonly string EntityPathConfigName = "EntityPath";
public ServiceBusConnectionStringBuilder(string connectionString)
{
if (!string.IsNullOrWhiteSpace(connectionString))
{
this.ParseConnectionString(connectionString);
}
}
public Uri Endpoint { get; set; }
/// <summary>
/// Get the entity path value from the connection string
/// </summary>
public string EntityPath { get; set; }
/// <summary>
/// Get the shared access policy owner name from the connection string
/// </summary>
public string SasKeyName { get; set; }
/// <summary>
/// Get the shared access policy key value from the connection string
/// </summary>
/// <value>Shared Access Signature key</value>
public string SasKey { get; set; }
public string GetConnectionString(string namespaceName, string entityPath, string sharedAccessKeyName, string sharedAccessKey)
{
if (string.IsNullOrWhiteSpace(namespaceName))
{
throw Fx.Exception.ArgumentNullOrWhiteSpace(nameof(namespaceName));
}
if (string.IsNullOrWhiteSpace(sharedAccessKeyName) || string.IsNullOrWhiteSpace(sharedAccessKey))
{
throw Fx.Exception.ArgumentNullOrWhiteSpace(string.IsNullOrWhiteSpace(sharedAccessKeyName) ? nameof(sharedAccessKeyName) : nameof(sharedAccessKey));
}
if (namespaceName.Contains("."))
{
// It appears to be a fully qualified host name, use it.
this.Endpoint = new Uri(EndpointScheme + "://" + namespaceName);
}
else
{
this.Endpoint = new Uri(EndpointFormat.FormatInvariant(namespaceName));
}
this.EntityPath = entityPath;
this.SasKeyName = sharedAccessKeyName;
this.SasKey = sharedAccessKey;
return this.ToString();
}
/// <summary>
/// Returns an interoperable connection string that can be used to connect to ServiceBus Namespace
/// </summary>
/// <returns>the connection string</returns>
public override string ToString()
{
StringBuilder connectionStringBuilder = new StringBuilder();
if (this.Endpoint != null)
{
connectionStringBuilder.Append($"{EndpointConfigName}{KeyValueSeparator}{this.Endpoint}{KeyValuePairDelimiter}");
}
if (!string.IsNullOrWhiteSpace(this.EntityPath))
{
connectionStringBuilder.Append($"{EntityPathConfigName}{KeyValueSeparator}{this.EntityPath}{KeyValuePairDelimiter}");
}
if (!string.IsNullOrWhiteSpace(this.SasKeyName))
{
connectionStringBuilder.Append($"{SharedAccessKeyNameConfigName}{KeyValueSeparator}{this.SasKeyName}{KeyValuePairDelimiter}");
}
if (!string.IsNullOrWhiteSpace(this.SasKey))
{
connectionStringBuilder.Append($"{SharedAccessKeyConfigName}{KeyValueSeparator}{this.SasKey}");
}
return connectionStringBuilder.ToString();
}
void ParseConnectionString(string connectionString)
{
// First split based on ';'
string[] keyValuePairs = connectionString.Split(new[] { KeyValuePairDelimiter }, StringSplitOptions.RemoveEmptyEntries);
foreach (var keyValuePair in keyValuePairs)
{
// Now split based on the _first_ '='
string[] keyAndValue = keyValuePair.Split(new[] { KeyValueSeparator }, 2);
string key = keyAndValue[0];
if (keyAndValue.Length != 2)
{
throw Fx.Exception.Argument(nameof(connectionString), $"Value for the connection string parameter name '{key}' was not found.");
}
string value = keyAndValue[1];
if (key.Equals(EndpointConfigName, StringComparison.OrdinalIgnoreCase))
{
this.Endpoint = new Uri(value);
}
else if (key.Equals(SharedAccessKeyNameConfigName, StringComparison.OrdinalIgnoreCase))
{
this.SasKeyName = value;
}
else if (key.Equals(EntityPathConfigName, StringComparison.OrdinalIgnoreCase))
{
this.EntityPath = value;
}
else if (key.Equals(SharedAccessKeyConfigName, StringComparison.OrdinalIgnoreCase))
{
this.SasKey = value;
}
else
{
throw Fx.Exception.Argument(nameof(connectionString), $"Illegal connection string parameter name '{key}'");
}
}
}
}
}

Просмотреть файл

@ -0,0 +1,36 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace Microsoft.Azure.ServiceBus.Primitives
{
using System;
public class ServiceBusEntityConnection : ServiceBusConnection
{
public ServiceBusEntityConnection(string entityConnectionString)
: this(entityConnectionString, ServiceBusConnection.DefaultOperationTimeout, RetryPolicy.Default)
{
}
public ServiceBusEntityConnection(string entityConnectionString, TimeSpan operationTimeout, RetryPolicy retryPolicy)
: base(operationTimeout, retryPolicy)
{
if (string.IsNullOrWhiteSpace(entityConnectionString))
{
throw Fx.Exception.ArgumentNullOrWhiteSpace(nameof(entityConnectionString));
}
ServiceBusConnectionStringBuilder builder = new ServiceBusConnectionStringBuilder(entityConnectionString);
if (string.IsNullOrWhiteSpace(builder.EntityPath))
{
throw Fx.Exception.Argument(nameof(entityConnectionString), "EntityConnectionString should contain EntityPath.");
}
this.InitializeConnection(builder);
this.EntityPath = builder.EntityPath;
}
public string EntityPath { get; }
}
}

Просмотреть файл

@ -0,0 +1,32 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace Microsoft.Azure.ServiceBus.Primitives
{
using System;
public class ServiceBusNamespaceConnection : ServiceBusConnection
{
public ServiceBusNamespaceConnection(string namespaceConnectionString)
: this(namespaceConnectionString, ServiceBusConnection.DefaultOperationTimeout, RetryPolicy.Default)
{
}
public ServiceBusNamespaceConnection(string namespaceConnectionString, TimeSpan operationTimeout, RetryPolicy retryPolicy)
: base(operationTimeout, retryPolicy)
{
if (string.IsNullOrWhiteSpace(namespaceConnectionString))
{
throw Fx.Exception.ArgumentNullOrWhiteSpace(nameof(namespaceConnectionString));
}
ServiceBusConnectionStringBuilder builder = new ServiceBusConnectionStringBuilder(namespaceConnectionString);
if (!string.IsNullOrWhiteSpace(builder.EntityPath))
{
throw Fx.Exception.Argument(nameof(namespaceConnectionString), "NamespaceConnectionString should not contain EntityPath.");
}
this.InitializeConnection(builder);
}
}
}

Просмотреть файл

@ -77,7 +77,7 @@ namespace Microsoft.Azure.ServiceBus
protected override Task<SecurityToken> OnGetTokenAsync(string appliesTo, string action, TimeSpan timeout)
{
string tokenString = this.BuildSignature(appliesTo);
var securityToken = new SharedAccessSignatureToken(tokenString);
SharedAccessSignatureToken securityToken = new SharedAccessSignatureToken(tokenString);
return Task.FromResult<SecurityToken>(securityToken);
}
@ -139,7 +139,7 @@ namespace Microsoft.Azure.ServiceBus
static string Sign(string requestString, byte[] encodedSharedAccessKey)
{
using (var hmac = new HMACSHA256(encodedSharedAccessKey))
using (HMACSHA256 hmac = new HMACSHA256(encodedSharedAccessKey))
{
return Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(requestString)));
}

Просмотреть файл

@ -0,0 +1,33 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace Microsoft.Azure.ServiceBus.Primitives
{
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Azure.Amqp;
/// <summary>
/// Provides an adapter from TokenProvider to ICbsTokenProvider for AMQP CBS usage.
/// </summary>
sealed class TokenProviderAdapter : ICbsTokenProvider
{
readonly TokenProvider tokenProvider;
readonly TimeSpan operationTimeout;
public TokenProviderAdapter(TokenProvider tokenProvider, TimeSpan operationTimeout)
{
Fx.Assert(tokenProvider != null, "tokenProvider cannot be null");
this.tokenProvider = tokenProvider;
this.operationTimeout = operationTimeout;
}
public async Task<CbsToken> GetTokenAsync(Uri namespaceAddress, string appliesTo, string[] requiredClaims)
{
string claim = requiredClaims?.FirstOrDefault();
SecurityToken token = await this.tokenProvider.GetTokenAsync(appliesTo, claim, this.operationTimeout).ConfigureAwait(false);
return new CbsToken(token.TokenValue, CbsConstants.ServiceBusSasTokenType, token.ExpiresAtUtc);
}
}
}

Просмотреть файл

@ -2,6 +2,7 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
@ -18,4 +19,12 @@ using System.Runtime.InteropServices;
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("a042adf0-ef65-4f87-b634-322a409f3d61")]
[assembly: Guid("a042adf0-ef65-4f87-b634-322a409f3d61")]
// Friend Assemblies
[assembly: InternalsVisibleTo("Microsoft.Azure.ServiceBus.UnitTests,PublicKey=" +
"0024000004800000940000000602000000240000525341310004000001000100f9bc4236ac3b7c" +
"ffc0f828bf2d60d34b447873db1fa3ce194d8ad43e134f944d40ab9a13c668f0c3c5af98a767e7" +
"cc831d9e02390dfb7252077bb8f5efd8f8c011c529040583ba9f6bc4fe31f8d5deebd3add91163" +
"7dd4e7096a409f032ca61ee270f8fca44156a6ee9bcbc6e23e650ba41d40280778985de18c4f33" +
"b3841cc6")]

Просмотреть файл

@ -6,6 +6,7 @@ namespace Microsoft.Azure.ServiceBus
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Azure.ServiceBus.Primitives;
/// <summary>
/// Anchor class - all Queue client operations start here.
@ -16,25 +17,32 @@ namespace Microsoft.Azure.ServiceBus
MessageSender innerSender;
MessageReceiver innerReceiver;
internal QueueClient(ServiceBusConnectionSettings connectionSettings, ReceiveMode receiveMode)
: base($"{nameof(QueueClient)}{ClientEntity.GetNextId()}({connectionSettings.EntityPath})")
protected QueueClient(ServiceBusConnection serviceBusConnection, string entityPath, ReceiveMode receiveMode)
: base($"{nameof(QueueClient)}{ClientEntity.GetNextId()}({entityPath})")
{
this.ConnectionSettings = connectionSettings;
this.QueueName = connectionSettings.EntityPath;
this.ServiceBusConnection = serviceBusConnection;
this.QueueName = entityPath;
this.Mode = receiveMode;
}
public string QueueName { get; }
public ServiceBusConnectionSettings ConnectionSettings { get; }
public ReceiveMode Mode { get; private set; }
public int PrefetchCount { get; set; }
public int PrefetchCount
{
get
{
return this.InnerReceiver.PrefetchCount;
}
protected object ThisLock { get; } = new object();
set
{
this.InnerReceiver.PrefetchCount = value;
}
}
MessageSender InnerSender
internal MessageSender InnerSender
{
get
{
@ -53,7 +61,7 @@ namespace Microsoft.Azure.ServiceBus
}
}
MessageReceiver InnerReceiver
internal MessageReceiver InnerReceiver
{
get
{
@ -72,67 +80,65 @@ namespace Microsoft.Azure.ServiceBus
}
}
public static QueueClient Create(string connectionString)
protected object ThisLock { get; } = new object();
protected ServiceBusConnection ServiceBusConnection { get; }
public static QueueClient CreateFromConnectionString(string entityConnectionString)
{
return Create(connectionString, ReceiveMode.PeekLock);
return CreateFromConnectionString(entityConnectionString, ReceiveMode.PeekLock);
}
public static QueueClient Create(string connectionString, ReceiveMode mode)
public static QueueClient CreateFromConnectionString(string entityConnectionString, ReceiveMode mode)
{
if (string.IsNullOrWhiteSpace(connectionString))
if (string.IsNullOrWhiteSpace(entityConnectionString))
{
throw Fx.Exception.ArgumentNullOrWhiteSpace(nameof(connectionString));
throw Fx.Exception.ArgumentNullOrWhiteSpace(nameof(entityConnectionString));
}
var connectionSettings = new ServiceBusConnectionSettings(connectionString);
return Create(connectionSettings, mode);
ServiceBusEntityConnection entityConnection = new ServiceBusEntityConnection(entityConnectionString);
return entityConnection.CreateQueueClient(entityConnection.EntityPath, mode);
}
public static QueueClient Create(string connectionString, string path)
public static QueueClient Create(ServiceBusNamespaceConnection namespaceConnection, string entityPath)
{
return Create(connectionString, path, ReceiveMode.PeekLock);
return QueueClient.Create(namespaceConnection, entityPath, ReceiveMode.PeekLock);
}
public static QueueClient Create(string connectionString, string path, ReceiveMode mode)
public static QueueClient Create(ServiceBusNamespaceConnection namespaceConnection, string entityPath, ReceiveMode mode)
{
if (string.IsNullOrWhiteSpace(connectionString))
if (namespaceConnection == null)
{
throw Fx.Exception.ArgumentNullOrWhiteSpace(nameof(connectionString));
throw Fx.Exception.Argument(nameof(namespaceConnection), "Namespace Connection is null. Create a connection using the NamespaceConnection class");
}
if (string.IsNullOrWhiteSpace(path))
if (string.IsNullOrWhiteSpace(entityPath))
{
throw Fx.Exception.ArgumentNullOrWhiteSpace(nameof(path));
throw Fx.Exception.Argument(nameof(namespaceConnection), "Entity Path is null");
}
var connectionSettings = new ServiceBusConnectionSettings(connectionString) { EntityPath = path };
return Create(connectionSettings, mode);
return namespaceConnection.CreateQueueClient(entityPath, mode);
}
public static QueueClient Create(ServiceBusConnectionSettings connectionSettings)
public static QueueClient Create(ServiceBusEntityConnection entityConnection)
{
return Create(connectionSettings, ReceiveMode.PeekLock);
return QueueClient.Create(entityConnection, ReceiveMode.PeekLock);
}
public static QueueClient Create(ServiceBusConnectionSettings connectionSettings, ReceiveMode mode)
public static QueueClient Create(ServiceBusEntityConnection entityConnection, ReceiveMode mode)
{
if (connectionSettings == null)
if (entityConnection == null)
{
throw Fx.Exception.ArgumentNull(nameof(connectionSettings));
throw Fx.Exception.Argument(nameof(entityConnection), "Namespace Connection is null. Create a connection using the NamespaceConnection class");
}
if (string.IsNullOrWhiteSpace(connectionSettings.EntityPath))
{
throw Fx.Exception.ArgumentNullOrWhiteSpace(nameof(connectionSettings.EntityPath));
}
return connectionSettings.CreateQueueClient(mode);
return entityConnection.CreateQueueClient(entityConnection.EntityPath, mode);
}
public sealed override Task CloseAsync()
public sealed override async Task CloseAsync()
{
return this.OnCloseAsync();
await this.InnerReceiver.CloseAsync().ConfigureAwait(false);
await this.OnCloseAsync().ConfigureAwait(false);
}
/// <summary>
@ -143,7 +149,7 @@ namespace Microsoft.Azure.ServiceBus
/// <returns>A Task that completes when the send operations is done.</returns>
public Task SendAsync(BrokeredMessage brokeredMessage)
{
return this.SendAsync(new[] { brokeredMessage });
return this.SendAsync(new BrokeredMessage[] { brokeredMessage });
}
public async Task SendAsync(IEnumerable<BrokeredMessage> brokeredMessages)
@ -183,9 +189,33 @@ namespace Microsoft.Azure.ServiceBus
}
}
public async Task<BrokeredMessage> ReceiveBySequenceNumberAsync(long sequenceNumber)
{
IList<BrokeredMessage> messages = await this.ReceiveBySequenceNumberAsync(new long[] { sequenceNumber });
if (messages != null && messages.Count > 0)
{
return messages[0];
}
return null;
}
public async Task<IList<BrokeredMessage>> ReceiveBySequenceNumberAsync(IEnumerable<long> sequenceNumbers)
{
try
{
return await this.InnerReceiver.ReceiveBySequenceNumberAsync(sequenceNumbers).ConfigureAwait(false);
}
catch (Exception)
{
// TODO: Log Receive Exception
throw;
}
}
public Task CompleteAsync(Guid lockToken)
{
return this.CompleteAsync(new[] { lockToken });
return this.CompleteAsync(new Guid[] { lockToken });
}
public async Task CompleteAsync(IEnumerable<Guid> lockTokens)
@ -203,7 +233,7 @@ namespace Microsoft.Azure.ServiceBus
public Task AbandonAsync(Guid lockToken)
{
return this.AbandonAsync(new[] { lockToken });
return this.AbandonAsync(new Guid[] { lockToken });
}
public async Task AbandonAsync(IEnumerable<Guid> lockTokens)
@ -219,9 +249,30 @@ namespace Microsoft.Azure.ServiceBus
}
}
public Task<MessageSession> AcceptMessageSessionAsync()
{
return this.AcceptMessageSessionAsync(null);
}
public async Task<MessageSession> AcceptMessageSessionAsync(string sessionId)
{
MessageSession session = null;
try
{
session = await this.OnAcceptMessageSessionAsync(sessionId).ConfigureAwait(false);
}
catch (Exception)
{
// TODO: Log Complete Exception
throw;
}
return session;
}
public Task DeferAsync(Guid lockToken)
{
return this.DeferAsync(new[] { lockToken });
return this.DeferAsync(new Guid[] { lockToken });
}
public async Task DeferAsync(IEnumerable<Guid> lockTokens)
@ -239,7 +290,7 @@ namespace Microsoft.Azure.ServiceBus
public Task DeadLetterAsync(Guid lockToken)
{
return this.DeadLetterAsync(new[] { lockToken });
return this.DeadLetterAsync(new Guid[] { lockToken });
}
public async Task DeadLetterAsync(IEnumerable<Guid> lockTokens)
@ -255,19 +306,34 @@ namespace Microsoft.Azure.ServiceBus
}
}
internal MessageSender CreateMessageSender()
public async Task<DateTime> RenewMessageLockAsync(Guid lockToken)
{
try
{
return await this.InnerReceiver.RenewLockAsync(lockToken).ConfigureAwait(false);
}
catch (Exception)
{
// TODO: Log Complete Exception
throw;
}
}
protected MessageSender CreateMessageSender()
{
return this.OnCreateMessageSender();
}
internal MessageReceiver CreateMessageReceiver()
protected MessageReceiver CreateMessageReceiver()
{
return this.OnCreateMessageReceiver();
}
internal abstract MessageSender OnCreateMessageSender();
protected abstract MessageSender OnCreateMessageSender();
internal abstract MessageReceiver OnCreateMessageReceiver();
protected abstract MessageReceiver OnCreateMessageReceiver();
protected abstract Task<MessageSession> OnAcceptMessageSessionAsync(string sessionId);
protected abstract Task OnCloseAsync();
}

9
src/Microsoft.Azure.ServiceBus/Resources.Designer.cs сгенерированный
Просмотреть файл

@ -59,6 +59,15 @@ namespace Microsoft.Azure.ServiceBus {
}
}
/// <summary>
/// Looks up a localized string similar to No session-id was specified for a session receiver..
/// </summary>
public static string AmqpFieldSessionId {
get {
return ResourceManager.GetString("AmqpFieldSessionId", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The received message (delivery-id:{0}, size:{1} bytes) exceeds the limit ({2} bytes) currently allowed on the link..
/// </summary>

Просмотреть файл

@ -117,6 +117,9 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="AmqpFieldSessionId" xml:space="preserve">
<value>No session-id was specified for a session receiver.</value>
</data>
<data name="AmqpMessageSizeExceeded" xml:space="preserve">
<value>The received message (delivery-id:{0}, size:{1} bytes) exceeds the limit ({2} bytes) currently allowed on the link.</value>
</data>

Просмотреть файл

@ -0,0 +1,290 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace Microsoft.Azure.ServiceBus
{
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Azure.ServiceBus.Primitives;
public abstract class SubscriptionClient : ClientEntity
{
MessageReceiver innerReceiver;
protected SubscriptionClient(ServiceBusConnection serviceBusConnection, string topicPath, string name, ReceiveMode receiveMode)
: base($"{nameof(SubscriptionClient)}{ClientEntity.GetNextId()}({name})")
{
this.ServiceBusConnection = serviceBusConnection;
this.TopicPath = topicPath;
this.Name = name;
this.SubscriptionPath = EntityNameHelper.FormatSubscriptionPath(this.TopicPath, this.Name);
this.Mode = receiveMode;
}
public string TopicPath { get; private set; }
public string Name { get; }
public ReceiveMode Mode { get; private set; }
public int PrefetchCount
{
get
{
return this.InnerReceiver.PrefetchCount;
}
set
{
this.InnerReceiver.PrefetchCount = value;
}
}
internal string SubscriptionPath { get; private set; }
internal MessageReceiver InnerReceiver
{
get
{
if (this.innerReceiver == null)
{
lock (this.ThisLock)
{
if (this.innerReceiver == null)
{
this.innerReceiver = this.CreateMessageReceiver();
}
}
}
return this.innerReceiver;
}
}
protected object ThisLock { get; } = new object();
protected ServiceBusConnection ServiceBusConnection { get; }
public static SubscriptionClient CreateFromConnectionString(string topicEntityConnectionString, string subscriptionName)
{
return CreateFromConnectionString(topicEntityConnectionString, subscriptionName, ReceiveMode.PeekLock);
}
public static SubscriptionClient CreateFromConnectionString(string topicEntityConnectionString, string subscriptionName, ReceiveMode mode)
{
if (string.IsNullOrWhiteSpace(topicEntityConnectionString))
{
throw Fx.Exception.ArgumentNullOrWhiteSpace(nameof(topicEntityConnectionString));
}
ServiceBusEntityConnection topicConnection = new ServiceBusEntityConnection(topicEntityConnectionString);
return topicConnection.CreateSubscriptionClient(topicConnection.EntityPath, subscriptionName, mode);
}
public static SubscriptionClient Create(ServiceBusNamespaceConnection namespaceConnection, string topicPath, string subscriptionName)
{
return SubscriptionClient.Create(namespaceConnection, topicPath, subscriptionName, ReceiveMode.PeekLock);
}
public static SubscriptionClient Create(ServiceBusNamespaceConnection namespaceConnection, string topicPath, string subscriptionName, ReceiveMode mode)
{
if (namespaceConnection == null)
{
throw Fx.Exception.Argument(nameof(namespaceConnection), "Namespace Connection is null. Create a connection using the NamespaceConnection class");
}
if (string.IsNullOrWhiteSpace(topicPath))
{
throw Fx.Exception.Argument(nameof(namespaceConnection), "Topic Path is null");
}
return namespaceConnection.CreateSubscriptionClient(topicPath, subscriptionName, mode);
}
public static SubscriptionClient Create(ServiceBusEntityConnection topicConnection, string subscriptionName)
{
return SubscriptionClient.Create(topicConnection, subscriptionName, ReceiveMode.PeekLock);
}
public static SubscriptionClient Create(ServiceBusEntityConnection topicConnection, string subscriptionName, ReceiveMode mode)
{
if (topicConnection == null)
{
throw Fx.Exception.Argument(nameof(topicConnection), "Namespace Connection is null. Create a connection using the NamespaceConnection class");
}
return topicConnection.CreateSubscriptionClient(topicConnection.EntityPath, subscriptionName, mode);
}
public sealed override async Task CloseAsync()
{
await this.OnCloseAsync().ConfigureAwait(false);
}
public async Task<BrokeredMessage> ReceiveAsync()
{
IList<BrokeredMessage> messages = await this.ReceiveAsync(1).ConfigureAwait(false);
if (messages != null && messages.Count > 0)
{
return messages[0];
}
return null;
}
public async Task<IList<BrokeredMessage>> ReceiveAsync(int maxMessageCount)
{
try
{
return await this.InnerReceiver.ReceiveAsync(maxMessageCount).ConfigureAwait(false);
}
catch (Exception)
{
// TODO: Log Receive Exception
throw;
}
}
public async Task<BrokeredMessage> ReceiveBySequenceNumberAsync(long sequenceNumber)
{
IList<BrokeredMessage> messages = await this.ReceiveBySequenceNumberAsync(new long[] { sequenceNumber });
if (messages != null && messages.Count > 0)
{
return messages[0];
}
return null;
}
public async Task<IList<BrokeredMessage>> ReceiveBySequenceNumberAsync(IEnumerable<long> sequenceNumbers)
{
try
{
return await this.InnerReceiver.ReceiveBySequenceNumberAsync(sequenceNumbers).ConfigureAwait(false);
}
catch (Exception)
{
// TODO: Log Receive Exception
throw;
}
}
public Task CompleteAsync(Guid lockToken)
{
return this.CompleteAsync(new Guid[] { lockToken });
}
public async Task CompleteAsync(IEnumerable<Guid> lockTokens)
{
try
{
await this.InnerReceiver.CompleteAsync(lockTokens).ConfigureAwait(false);
}
catch (Exception)
{
// TODO: Log Complete Exception
throw;
}
}
public Task AbandonAsync(Guid lockToken)
{
return this.AbandonAsync(new Guid[] { lockToken });
}
public async Task AbandonAsync(IEnumerable<Guid> lockTokens)
{
try
{
await this.InnerReceiver.AbandonAsync(lockTokens).ConfigureAwait(false);
}
catch (Exception)
{
// TODO: Log Complete Exception
throw;
}
}
public Task<MessageSession> AcceptMessageSessionAsync()
{
return this.AcceptMessageSessionAsync(null);
}
public async Task<MessageSession> AcceptMessageSessionAsync(string sessionId)
{
MessageSession session = null;
try
{
session = await this.OnAcceptMessageSessionAsync(sessionId).ConfigureAwait(false);
}
catch (Exception)
{
// TODO: Log Complete Exception
throw;
}
return session;
}
public Task DeferAsync(Guid lockToken)
{
return this.DeferAsync(new Guid[] { lockToken });
}
public async Task DeferAsync(IEnumerable<Guid> lockTokens)
{
try
{
await this.InnerReceiver.DeferAsync(lockTokens).ConfigureAwait(false);
}
catch (Exception)
{
// TODO: Log Complete Exception
throw;
}
}
public Task DeadLetterAsync(Guid lockToken)
{
return this.DeadLetterAsync(new Guid[] { lockToken });
}
public async Task DeadLetterAsync(IEnumerable<Guid> lockTokens)
{
try
{
await this.InnerReceiver.DeadLetterAsync(lockTokens).ConfigureAwait(false);
}
catch (Exception)
{
// TODO: Log Complete Exception
throw;
}
}
public async Task<DateTime> RenewMessageLockAsync(Guid lockToken)
{
try
{
return await this.InnerReceiver.RenewLockAsync(lockToken).ConfigureAwait(false);
}
catch (Exception)
{
// TODO: Log Complete Exception
throw;
}
}
protected MessageReceiver CreateMessageReceiver()
{
return this.OnCreateMessageReceiver();
}
protected abstract MessageReceiver OnCreateMessageReceiver();
protected abstract Task<MessageSession> OnAcceptMessageSessionAsync(string sessionId);
protected abstract Task OnCloseAsync();
}
}

Просмотреть файл

@ -0,0 +1,126 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace Microsoft.Azure.ServiceBus
{
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Azure.ServiceBus.Primitives;
public abstract class TopicClient : ClientEntity
{
MessageSender innerSender;
protected TopicClient(ServiceBusConnection serviceBusConnection, string entityPath)
: base($"{nameof(TopicClient)}{ClientEntity.GetNextId()}({entityPath})")
{
this.ServiceBusConnection = serviceBusConnection;
this.TopicName = entityPath;
}
public string TopicName { get; }
internal MessageSender InnerSender
{
get
{
if (this.innerSender == null)
{
lock (this.ThisLock)
{
if (this.innerSender == null)
{
this.innerSender = this.CreateMessageSender();
}
}
}
return this.innerSender;
}
}
protected ServiceBusConnection ServiceBusConnection { get; }
protected object ThisLock { get; } = new object();
public static TopicClient CreateFromConnectionString(string entityConnectionString)
{
if (string.IsNullOrWhiteSpace(entityConnectionString))
{
throw Fx.Exception.ArgumentNullOrWhiteSpace(nameof(entityConnectionString));
}
ServiceBusEntityConnection entityConnection = new ServiceBusEntityConnection(entityConnectionString);
return entityConnection.CreateTopicClient(entityConnection.EntityPath);
}
public static TopicClient Create(ServiceBusNamespaceConnection namespaceConnection, string entityPath)
{
if (namespaceConnection == null)
{
throw Fx.Exception.Argument(nameof(namespaceConnection), "Namespace Connection is null. Create a connection using the NamespaceConnection class");
}
if (string.IsNullOrWhiteSpace(entityPath))
{
throw Fx.Exception.Argument(nameof(namespaceConnection), "Entity Path is null");
}
return namespaceConnection.CreateTopicClient(entityPath);
}
public static TopicClient Create(ServiceBusEntityConnection entityConnection)
{
return TopicClient.Create(entityConnection, ReceiveMode.PeekLock);
}
public static TopicClient Create(ServiceBusEntityConnection entityConnection, ReceiveMode mode)
{
if (entityConnection == null)
{
throw Fx.Exception.Argument(nameof(entityConnection), "Namespace Connection is null. Create a connection using the NamespaceConnection class");
}
return entityConnection.CreateTopicClient(entityConnection.EntityPath);
}
public sealed override async Task CloseAsync()
{
await this.OnCloseAsync().ConfigureAwait(false);
}
/// <summary>
/// Send <see cref="BrokeredMessage"/> to Queue.
/// <see cref="SendAsync(BrokeredMessage)"/> sends the <see cref="BrokeredMessage"/> to a Service Gateway, which in-turn will forward the BrokeredMessage to the queue.
/// </summary>
/// <param name="brokeredMessage">the <see cref="BrokeredMessage"/> to be sent.</param>
/// <returns>A Task that completes when the send operations is done.</returns>
public Task SendAsync(BrokeredMessage brokeredMessage)
{
return this.SendAsync(new[] { brokeredMessage });
}
public async Task SendAsync(IEnumerable<BrokeredMessage> brokeredMessages)
{
try
{
await this.InnerSender.SendAsync(brokeredMessages).ConfigureAwait(false);
}
catch (Exception)
{
// TODO: Log Send Exception
throw;
}
}
protected MessageSender CreateMessageSender()
{
return this.OnCreateMessageSender();
}
protected abstract MessageSender OnCreateMessageSender();
protected abstract Task OnCloseAsync();
}
}

Просмотреть файл

@ -19,15 +19,6 @@ namespace Microsoft.Azure.ServiceBus.UnitTests
}
}
public class When_BrokeredMessage_message_is_given_a_null_id_generator
{
[Fact]
public void Should_throw_an_exception()
{
Assert.Throws<ArgumentNullException>(() => BrokeredMessage.SetMessageIdGenerator(null));
}
}
public class When_BrokeredMessage_id_generator_throws
{
[Fact]
@ -42,6 +33,8 @@ namespace Microsoft.Azure.ServiceBus.UnitTests
var exception = Assert.Throws<InvalidOperationException>(() => new BrokeredMessage());
Assert.Equal(exceptionToThrow, exception.InnerException);
BrokeredMessage.SetMessageIdGenerator(null);
}
}
@ -58,6 +51,8 @@ namespace Microsoft.Azure.ServiceBus.UnitTests
Assert.Equal("id-1", message1.MessageId);
Assert.Equal("id-2", message2.MessageId);
BrokeredMessage.SetMessageIdGenerator(null);
}
}
}

Просмотреть файл

@ -0,0 +1,111 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace Microsoft.Azure.ServiceBus.UnitTests
{
using System;
using System.Threading.Tasks;
using Microsoft.Azure.ServiceBus.Primitives;
using Xunit;
using Xunit.Abstractions;
public class NonPartitionedQueueClientTests : QueueClientTestBase
{
public NonPartitionedQueueClientTests(ITestOutputHelper output)
: base(output)
{
this.ConnectionString = Environment.GetEnvironmentVariable("NONPARTITIONEDQUEUECLIENTCONNECTIONSTRING");
if (string.IsNullOrWhiteSpace(this.ConnectionString))
{
throw new InvalidOperationException("QUEUECLIENTCONNECTIONSTRING environment variable was not found!");
}
}
[Fact]
async Task PeekLockTest()
{
await this.QueueClientPeekLockTestCase(messageCount: 10);
}
[Fact]
async Task ReceiveDeleteTest()
{
await this.QueueClientReceiveDeleteTestCase(messageCount: 10);
}
[Fact]
async Task PeekLockWithAbandonTest()
{
await this.QueueClientPeekLockWithAbandonTestCase(messageCount: 10);
}
[Fact]
async Task PeekLockWithDeadLetterTest()
{
await this.QueueClientPeekLockWithDeadLetterTestCase(messageCount: 10);
}
[Fact]
async Task PeekLockDeferTest()
{
await this.QueueClientPeekLockDeferTestCase(messageCount: 10);
}
// Request Response Tests
[Fact]
async Task BasicRenewLockTest()
{
await this.QueueClientRenewLockTestCase(messageCount: 1);
}
// TODO: Add this to BrokeredMessageTests after merge
[Fact]
async Task BrokeredMessageOperationsTest()
{
// Create QueueClient with ReceiveDelete,
// Send and Receive a message, Try to Complete/Abandon/Defer/DeadLetter should throw InvalidOperationException()
QueueClient queueClient = QueueClient.CreateFromConnectionString(this.ConnectionString, ReceiveMode.ReceiveAndDelete);
await TestUtility.SendMessagesAsync(queueClient.InnerSender, 1, this.Output);
BrokeredMessage message = await queueClient.ReceiveAsync();
Assert.NotNull((object)message);
await Assert.ThrowsAsync<InvalidOperationException>(async () => await message.CompleteAsync());
await Assert.ThrowsAsync<InvalidOperationException>(async () => await message.AbandonAsync());
await Assert.ThrowsAsync<InvalidOperationException>(async () => await message.DeferAsync());
await Assert.ThrowsAsync<InvalidOperationException>(async () => await message.DeadLetterAsync());
// Create a PeekLock queueClient and do rest of the operations
// Send a Message, Receive/ Abandon and Complete it using BrokeredMessage methods
queueClient = QueueClient.CreateFromConnectionString(this.ConnectionString);
await TestUtility.SendMessagesAsync(queueClient.InnerSender, 1, this.Output);
message = await queueClient.ReceiveAsync();
Assert.NotNull((object)message);
await message.AbandonAsync();
await Task.Delay(TimeSpan.FromMilliseconds(100));
message = await queueClient.ReceiveAsync();
await message.CompleteAsync();
// Send a Message, Receive / DeadLetter using BrokeredMessage methods
await TestUtility.SendMessagesAsync(queueClient.InnerSender, 1, this.Output);
message = await queueClient.ReceiveAsync();
await message.DeadLetterAsync();
ServiceBusConnectionStringBuilder builder = new ServiceBusConnectionStringBuilder(this.ConnectionString);
builder.EntityPath = EntityNameHelper.FormatDeadLetterPath(queueClient.QueueName);
QueueClient deadLetterQueueClient = QueueClient.CreateFromConnectionString(builder.ToString());
message = await deadLetterQueueClient.ReceiveAsync();
await message.CompleteAsync();
// Send a Message, Receive/Defer using BrokeredMessage methods
await TestUtility.SendMessagesAsync(queueClient.InnerSender, 1, this.Output);
message = await queueClient.ReceiveAsync();
long deferredSequenceNumber = message.SequenceNumber;
await message.DeferAsync();
var deferredMessage = await queueClient.ReceiveBySequenceNumberAsync(deferredSequenceNumber);
await deferredMessage.CompleteAsync();
queueClient.Close();
}
}
}

Просмотреть файл

@ -0,0 +1,42 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace Microsoft.Azure.ServiceBus.UnitTests
{
using System;
using System.Threading.Tasks;
using Xunit;
using Xunit.Abstractions;
public class NonPartitionedQueueSessionTests : QueueSessionTestBase
{
public NonPartitionedQueueSessionTests(ITestOutputHelper output)
: base(output)
{
this.ConnectionString = Environment.GetEnvironmentVariable("NONPARTITIONEDSESSIONQUEUECONNECTIONSTRING");
if (string.IsNullOrWhiteSpace(this.ConnectionString))
{
throw new InvalidOperationException("SESSIONQUEUECLIENTCONNECTIONSTRING environment variable was not found!");
}
}
[Fact]
async Task SessionTest()
{
await this.SessionTestCase();
}
[Fact]
async Task GetAndSetSessionStateTest()
{
await this.GetAndSetSessionStateTestCase();
}
[Fact]
async Task SessionRenewLockTest()
{
await this.SessionRenewLockTestCase();
}
}
}

Просмотреть файл

@ -0,0 +1,68 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace Microsoft.Azure.ServiceBus.UnitTests
{
using System;
using System.Threading.Tasks;
using Microsoft.Azure.ServiceBus.Primitives;
using Xunit;
using Xunit.Abstractions;
public class NonPartitionedTopicClientTests : TopicClientTestBase
{
public NonPartitionedTopicClientTests(ITestOutputHelper output)
: base(output)
{
this.ConnectionString = Environment.GetEnvironmentVariable("NONPARTITIONEDTOPICCLIENTCONNECTIONSTRING");
this.SubscriptionName = Environment.GetEnvironmentVariable("SUBSCRIPTIONNAME");
if (string.IsNullOrWhiteSpace(this.ConnectionString))
{
throw new InvalidOperationException("TopicCLIENTCONNECTIONSTRING environment variable was not found!");
}
if (string.IsNullOrWhiteSpace(this.SubscriptionName))
{
throw new InvalidOperationException("SUBSCRIPTIONNAME environment variable was not found!");
}
}
[Fact]
async Task PeekLockTest()
{
await this.TopicClientPeekLockTestCase(messageCount: 10);
}
[Fact]
async Task ReceiveDeleteTest()
{
await this.TopicClientReceiveDeleteTestCase(messageCount: 10);
}
[Fact]
async Task PeekLockWithAbandonTest()
{
await this.TopicClientPeekLockWithAbandonTestCase(messageCount: 10);
}
[Fact]
async Task PeekLockWithDeadLetterTest()
{
await this.TopicClientPeekLockWithDeadLetterTestCase(messageCount: 10);
}
[Fact]
async Task PeekLockDeferTest()
{
await this.TopicClientPeekLockDeferTestCase(messageCount: 10);
}
// Request Response Tests
[Fact]
async Task BasicRenewLockTest()
{
await this.TopicClientRenewLockTestCase(messageCount: 1);
}
}
}

Просмотреть файл

@ -0,0 +1,48 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace Microsoft.Azure.ServiceBus.UnitTests
{
using System;
using System.Threading.Tasks;
using Xunit;
using Xunit.Abstractions;
public class NonPartitionedTopicSessionTests : TopicSessionTestBase
{
public NonPartitionedTopicSessionTests(ITestOutputHelper output)
: base(output)
{
this.ConnectionString = Environment.GetEnvironmentVariable("NONPARTITIONEDTOPICCLIENTCONNECTIONSTRING");
this.SubscriptionName = Environment.GetEnvironmentVariable("SESSIONFULSUBSCRIPTIONNAME");
if (string.IsNullOrWhiteSpace(this.ConnectionString))
{
throw new InvalidOperationException("TOPICCLIENTCONNECTIONSTRING environment variable was not found!");
}
if (string.IsNullOrWhiteSpace(this.SubscriptionName))
{
throw new InvalidOperationException("SUBSCRIPTIONNAME environment variable was not found!");
}
}
[Fact]
async Task SessionTest()
{
await this.SessionTestCase();
}
[Fact]
async Task GetAndSetSessionStateTest()
{
await this.GetAndSetSessionStateTestCase();
}
[Fact]
async Task SessionRenewLockTest()
{
await this.SessionRenewLockTestCase();
}
}
}

Просмотреть файл

@ -0,0 +1,62 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace Microsoft.Azure.ServiceBus.UnitTests
{
using System;
using System.Threading.Tasks;
using Microsoft.Azure.ServiceBus.Primitives;
using Xunit;
using Xunit.Abstractions;
public class PartitionedQueueClientTests : QueueClientTestBase
{
public PartitionedQueueClientTests(ITestOutputHelper output)
: base(output)
{
this.ConnectionString = Environment.GetEnvironmentVariable("PARTITIONEDQUEUECLIENTCONNECTIONSTRING");
if (string.IsNullOrWhiteSpace(this.ConnectionString))
{
throw new InvalidOperationException("QUEUECLIENTCONNECTIONSTRING environment variable was not found!");
}
}
[Fact]
async Task PeekLockTest()
{
await this.QueueClientPeekLockTestCase(messageCount: 10);
}
[Fact]
async Task ReceiveDeleteTest()
{
await this.QueueClientReceiveDeleteTestCase(messageCount: 10);
}
[Fact]
async Task PeekLockWithAbandonTest()
{
await this.QueueClientPeekLockWithAbandonTestCase(messageCount: 10);
}
[Fact]
async Task PeekLockWithDeadLetterTest()
{
await this.QueueClientPeekLockWithDeadLetterTestCase(messageCount: 10);
}
[Fact]
async Task PeekLockDeferTest()
{
await this.QueueClientPeekLockDeferTestCase(messageCount: 10);
}
// Request Response Tests
[Fact]
async Task BasicRenewLockTest()
{
await this.QueueClientRenewLockTestCase(messageCount: 1);
}
}
}

Просмотреть файл

@ -0,0 +1,42 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace Microsoft.Azure.ServiceBus.UnitTests
{
using System;
using System.Threading.Tasks;
using Xunit;
using Xunit.Abstractions;
public class PartitionedQueueSessionTests : QueueSessionTestBase
{
public PartitionedQueueSessionTests(ITestOutputHelper output)
: base(output)
{
this.ConnectionString = Environment.GetEnvironmentVariable("PARTITIONEDSESSIONQUEUECONNECTIONSTRING");
if (string.IsNullOrWhiteSpace(this.ConnectionString))
{
throw new InvalidOperationException("SESSIONQUEUECLIENTCONNECTIONSTRING environment variable was not found!");
}
}
[Fact]
async Task SessionTest()
{
await this.SessionTestCase();
}
[Fact]
async Task GetAndSetSessionStateTest()
{
await this.GetAndSetSessionStateTestCase();
}
[Fact]
async Task SessionRenewLockTest()
{
await this.SessionRenewLockTestCase();
}
}
}

Просмотреть файл

@ -0,0 +1,68 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace Microsoft.Azure.ServiceBus.UnitTests
{
using System;
using System.Threading.Tasks;
using Microsoft.Azure.ServiceBus.Primitives;
using Xunit;
using Xunit.Abstractions;
public class PartitionedTopicClientTests : TopicClientTestBase
{
public PartitionedTopicClientTests(ITestOutputHelper output)
: base(output)
{
this.ConnectionString = Environment.GetEnvironmentVariable("PARTITIONEDTOPICCLIENTCONNECTIONSTRING");
this.SubscriptionName = Environment.GetEnvironmentVariable("SUBSCRIPTIONNAME");
if (string.IsNullOrWhiteSpace(this.ConnectionString))
{
throw new InvalidOperationException("TOPICCLIENTCONNECTIONSTRING environment variable was not found!");
}
if (string.IsNullOrWhiteSpace(this.SubscriptionName))
{
throw new InvalidOperationException("SUBSCRIPTIONNAME environment variable was not found!");
}
}
[Fact]
async Task PeekLockTest()
{
await this.TopicClientPeekLockTestCase(messageCount: 10);
}
[Fact]
async Task ReceiveDeleteTest()
{
await this.TopicClientReceiveDeleteTestCase(messageCount: 10);
}
[Fact]
async Task PeekLockWithAbandonTest()
{
await this.TopicClientPeekLockWithAbandonTestCase(messageCount: 10);
}
[Fact]
async Task PeekLockWithDeadLetterTest()
{
await this.TopicClientPeekLockWithDeadLetterTestCase(messageCount: 10);
}
[Fact]
async Task PeekLockDeferTest()
{
await this.TopicClientPeekLockDeferTestCase(messageCount: 10);
}
// Request Response Tests
[Fact]
async Task BasicRenewLockTest()
{
await this.TopicClientRenewLockTestCase(messageCount: 1);
}
}
}

Просмотреть файл

@ -0,0 +1,48 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace Microsoft.Azure.ServiceBus.UnitTests
{
using System;
using System.Threading.Tasks;
using Xunit;
using Xunit.Abstractions;
public class PartitionedTopicSessionTests : TopicSessionTestBase
{
public PartitionedTopicSessionTests(ITestOutputHelper output)
: base(output)
{
this.ConnectionString = Environment.GetEnvironmentVariable("PARTITIONEDTOPICCLIENTCONNECTIONSTRING");
this.SubscriptionName = Environment.GetEnvironmentVariable("SESSIONFULSUBSCRIPTIONNAME");
if (string.IsNullOrWhiteSpace(this.ConnectionString))
{
throw new InvalidOperationException("TOPICCLIENTCONNECTIONSTRING environment variable was not found!");
}
if (string.IsNullOrWhiteSpace(this.SubscriptionName))
{
throw new InvalidOperationException("SUBSCRIPTIONNAME environment variable was not found!");
}
}
[Fact]
async Task SessionTest()
{
await this.SessionTestCase();
}
[Fact]
async Task GetAndSetSessionStateTest()
{
await this.GetAndSetSessionStateTestCase();
}
[Fact]
async Task SessionRenewLockTest()
{
await this.SessionRenewLockTestCase();
}
}
}

Просмотреть файл

@ -3,6 +3,7 @@
using System.Reflection;
using System.Runtime.InteropServices;
using Xunit;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
@ -18,4 +19,6 @@ using System.Runtime.InteropServices;
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("bda98e20-3a26-4a5a-9371-cb02b73e3434")]
[assembly: Guid("bda98e20-3a26-4a5a-9371-cb02b73e3434")]
[assembly: CollectionBehavior(DisableTestParallelization = true)]

Просмотреть файл

@ -0,0 +1,104 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace Microsoft.Azure.ServiceBus.UnitTests
{
using System.Threading.Tasks;
using Microsoft.Azure.ServiceBus.Primitives;
using Xunit.Abstractions;
public abstract class QueueClientTestBase : SenderReceiverClientTestBase
{
protected QueueClientTestBase(ITestOutputHelper output)
: base(output)
{
}
protected string ConnectionString { get; set; }
public async Task QueueClientPeekLockTestCase(int messageCount)
{
QueueClient queueClient = QueueClient.CreateFromConnectionString(this.ConnectionString);
try
{
await this.PeekLockTestCase(queueClient.InnerSender, queueClient.InnerReceiver, messageCount);
}
finally
{
queueClient.Close();
}
}
public async Task QueueClientReceiveDeleteTestCase(int messageCount)
{
QueueClient queueClient = QueueClient.CreateFromConnectionString(this.ConnectionString, ReceiveMode.ReceiveAndDelete);
try
{
await this.ReceiveDeleteTestCase(queueClient.InnerSender, queueClient.InnerReceiver, messageCount);
}
finally
{
queueClient.Close();
}
}
public async Task QueueClientPeekLockWithAbandonTestCase(int messageCount)
{
QueueClient queueClient = QueueClient.CreateFromConnectionString(this.ConnectionString);
try
{
await this.PeekLockWithAbandonTestCase(queueClient.InnerSender, queueClient.InnerReceiver, messageCount);
}
finally
{
queueClient.Close();
}
}
public async Task QueueClientPeekLockWithDeadLetterTestCase(int messageCount)
{
QueueClient queueClient = QueueClient.CreateFromConnectionString(this.ConnectionString);
// Create DLQ Client To Receive DeadLetteredMessages
ServiceBusConnectionStringBuilder builder = new ServiceBusConnectionStringBuilder(this.ConnectionString);
builder.EntityPath = EntityNameHelper.FormatDeadLetterPath(queueClient.QueueName);
QueueClient deadLetterQueueClient = QueueClient.CreateFromConnectionString(builder.ToString());
try
{
await this.PeekLockWithDeadLetterTestCase(queueClient.InnerSender, queueClient.InnerReceiver, deadLetterQueueClient.InnerReceiver, messageCount);
}
finally
{
deadLetterQueueClient.Close();
queueClient.Close();
}
}
public async Task QueueClientPeekLockDeferTestCase(int messageCount)
{
QueueClient queueClient = QueueClient.CreateFromConnectionString(this.ConnectionString);
try
{
await this.PeekLockDeferTestCase(queueClient.InnerSender, queueClient.InnerReceiver, messageCount);
}
finally
{
queueClient.Close();
}
}
public async Task QueueClientRenewLockTestCase(int messageCount)
{
QueueClient queueClient = QueueClient.CreateFromConnectionString(this.ConnectionString);
try
{
await this.RenewLockTestCase(queueClient.InnerSender, queueClient.InnerReceiver, messageCount);
}
finally
{
queueClient.Close();
}
}
}
}

Просмотреть файл

@ -1,276 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace Microsoft.Azure.ServiceBus.UnitTests
{
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Xunit;
using Xunit.Abstractions;
public class QueueClientTests
{
const int MaxAttemptsCount = 5;
readonly string connectionString;
ITestOutputHelper output;
public QueueClientTests(ITestOutputHelper output)
{
this.output = output;
this.connectionString = Environment.GetEnvironmentVariable("QUEUECLIENTCONNECTIONSTRING");
if (string.IsNullOrWhiteSpace(this.connectionString))
{
throw new InvalidOperationException("QUEUECLIENTCONNECTIONSTRING environment variable was not found!");
}
}
[Fact]
async Task BrokeredMessageOperationsTest()
{
// Create QueueClient with ReceiveDelete,
// Send and Receive a message, Try to Complete/Abandon/Defer/DeadLetter should throw InvalidOperationException()
QueueClient queueClient = QueueClient.Create(this.connectionString, ReceiveMode.ReceiveAndDelete);
await this.SendMessagesAsync(queueClient, 1);
BrokeredMessage message = await queueClient.ReceiveAsync();
Assert.NotNull(message);
await Assert.ThrowsAsync<InvalidOperationException>(async () => await message.CompleteAsync());
await Assert.ThrowsAsync<InvalidOperationException>(async () => await message.AbandonAsync());
await Assert.ThrowsAsync<InvalidOperationException>(async () => await message.DeferAsync());
await Assert.ThrowsAsync<InvalidOperationException>(async () => await message.DeadLetterAsync());
// Create a PeekLock queueClient and do rest of the operations
// Send a Message, Receive/Abandon and Complete it using BrokeredMessage methods
queueClient = QueueClient.Create(this.connectionString);
await this.SendMessagesAsync(queueClient, 1);
message = await queueClient.ReceiveAsync();
Assert.NotNull(message);
await message.AbandonAsync();
await Task.Delay(TimeSpan.FromMilliseconds(100));
message = await queueClient.ReceiveAsync();
await message.CompleteAsync();
// Send a Message, Receive/DeadLetter using BrokeredMessage methods
await this.SendMessagesAsync(queueClient, 1);
message = await queueClient.ReceiveAsync();
await message.DeadLetterAsync();
string entityPath = EntityNameHelper.FormatDeadLetterPath(queueClient.QueueName);
QueueClient deadLetterQueueClient = QueueClient.Create(this.connectionString, entityPath);
message = await deadLetterQueueClient.ReceiveAsync();
await message.CompleteAsync();
// Send a Message, Receive/Defer using BrokeredMessage methods
await this.SendMessagesAsync(queueClient, 1);
message = await queueClient.ReceiveAsync();
await message.DeferAsync();
// TODO: Once ReceivebySequence is implemented, Receive and Complete this message
}
[Fact]
async Task BasicPeekLockTest()
{
const int MessageCount = 10;
// Create QueueClient With PeekLock
QueueClient queueClient = QueueClient.Create(this.connectionString);
// Send messages
await this.SendMessagesAsync(queueClient, MessageCount);
// Receive messages
IEnumerable<BrokeredMessage> receivedMessages = await this.ReceiveMessagesAsync(queueClient, MessageCount);
// Complete Messages
await this.CompleteMessagesAsync(queueClient, receivedMessages);
Assert.True(receivedMessages.Count() == MessageCount);
}
[Fact]
async Task BasicReceiveDeleteTest()
{
const int MessageCount = 10;
// Create QueueClient With ReceiveAndDelete
QueueClient queueClient = QueueClient.Create(this.connectionString, ReceiveMode.ReceiveAndDelete);
// Send messages
await this.SendMessagesAsync(queueClient, MessageCount);
// Receive messages
IEnumerable<BrokeredMessage> receivedMessages = await this.ReceiveMessagesAsync(queueClient, MessageCount);
Assert.True(receivedMessages.Count() == MessageCount);
}
[Fact]
async Task PeekLockWithAbandonTest()
{
const int MessageCount = 10;
// Create QueueClient With PeekLock
QueueClient queueClient = QueueClient.Create(this.connectionString);
// Send messages
await this.SendMessagesAsync(queueClient, MessageCount);
// Receive 5 messages and Abandon them
int abandonMessagesCount = 5;
IEnumerable<BrokeredMessage> receivedMessages = await this.ReceiveMessagesAsync(queueClient, abandonMessagesCount);
Assert.True(receivedMessages.Count() == abandonMessagesCount);
await this.AbandonMessagesAsync(queueClient, receivedMessages);
// Receive all 10 messages, 5 of them should have DeliveryCount = 2
receivedMessages = await this.ReceiveMessagesAsync(queueClient, MessageCount);
Assert.True(receivedMessages.Count() == MessageCount);
// 5 of these messages should have deliveryCount = 2
int messagesWithDeliveryCount2 = receivedMessages.Count(message => message.DeliveryCount == 2);
Assert.True(messagesWithDeliveryCount2 == abandonMessagesCount);
// Complete Messages
await this.CompleteMessagesAsync(queueClient, receivedMessages);
}
[Fact]
async Task PeekLockWithDeadLetterTest()
{
const int MessageCount = 10;
IEnumerable<BrokeredMessage> receivedMessages;
// Create QueueClient With PeekLock
QueueClient queueClient = QueueClient.Create(this.connectionString);
// Send messages
await this.SendMessagesAsync(queueClient, MessageCount);
// Receive 5 messages and Deadletter them
int deadLetterMessageCount = 5;
receivedMessages = await this.ReceiveMessagesAsync(queueClient, deadLetterMessageCount);
Assert.True(receivedMessages.Count() == deadLetterMessageCount);
await this.DeadLetterMessagesAsync(queueClient, receivedMessages);
// Receive and Complete 5 other regular messages
receivedMessages = await this.ReceiveMessagesAsync(queueClient, MessageCount - deadLetterMessageCount);
await this.CompleteMessagesAsync(queueClient, receivedMessages);
// TODO: After implementing Receive(WithTimeSpan), Add Try another Receive, We should not get anything.
// IEnumerable<BrokeredMessage> dummyMessages = await this.ReceiveMessagesAsync(queueClient, 10);
// Assert.True(dummyMessages == null);
// Create DLQ Client and Receive DeadLetteredMessages
var entityPath = EntityNameHelper.FormatDeadLetterPath(queueClient.QueueName);
QueueClient deadLetterQueueClient = QueueClient.Create(this.connectionString, entityPath);
// Receive 5 DLQ messages and Complete them
receivedMessages = await this.ReceiveMessagesAsync(deadLetterQueueClient, deadLetterMessageCount);
Assert.True(receivedMessages.Count() == deadLetterMessageCount);
await this.CompleteMessagesAsync(deadLetterQueueClient, receivedMessages);
}
[Fact]
async Task PeekLockDeferTest()
{
const int MessageCount = 10;
// Create QueueClient With PeekLock
QueueClient queueClient = QueueClient.Create(this.connectionString);
// Send messages
await this.SendMessagesAsync(queueClient, MessageCount);
// Receive 5 messages And Defer them
int deferMessagesCount = 5;
IEnumerable<BrokeredMessage> receivedMessages = await this.ReceiveMessagesAsync(queueClient, deferMessagesCount);
Assert.True(receivedMessages.Count() == deferMessagesCount);
await this.DeferMessagesAsync(queueClient, receivedMessages);
// Receive and Complete 5 other regular messages
receivedMessages = await this.ReceiveMessagesAsync(queueClient, MessageCount - deferMessagesCount);
await this.CompleteMessagesAsync(queueClient, receivedMessages);
Assert.True(receivedMessages.Count() == MessageCount - deferMessagesCount);
// Once Request response link is implemented, Call ReceiveBySequenceNumber() here and complete the rest of the 5 messages
}
async Task SendMessagesAsync(QueueClient queueClient, int messageCount, [CallerMemberName] string invokingMethod = "")
{
if (messageCount == 0)
{
await Task.FromResult(false);
}
List<BrokeredMessage> messagesToSend = new List<BrokeredMessage>();
for (int i = 0; i < messageCount; i++)
{
BrokeredMessage message = new BrokeredMessage("test" + i);
message.Label = $"test{i}-{invokingMethod}";
messagesToSend.Add(message);
}
await queueClient.SendAsync(messagesToSend);
this.Log(string.Format("Sent {0} messages", messageCount));
}
async Task<IEnumerable<BrokeredMessage>> ReceiveMessagesAsync(QueueClient queueClient, int messageCount)
{
int receiveAttempts = 0;
List<BrokeredMessage> messagesToReturn = new List<BrokeredMessage>();
while (receiveAttempts++ < QueueClientTests.MaxAttemptsCount && messagesToReturn.Count < messageCount)
{
var messages = await queueClient.ReceiveAsync(messageCount);
if (messages != null)
{
messagesToReturn.AddRange(messages);
}
}
this.Log(string.Format("Received {0} messages", messagesToReturn.Count));
return messagesToReturn;
}
async Task CompleteMessagesAsync(QueueClient queueClient, IEnumerable<BrokeredMessage> messages)
{
await queueClient.CompleteAsync(messages.Select(message => message.LockToken));
this.Log(string.Format("Completed {0} messages", messages.Count()));
}
async Task AbandonMessagesAsync(QueueClient queueClient, IEnumerable<BrokeredMessage> messages)
{
await queueClient.AbandonAsync(messages.Select(message => message.LockToken));
this.Log(string.Format("Abandoned {0} messages", messages.Count()));
}
async Task DeadLetterMessagesAsync(QueueClient queueClient, IEnumerable<BrokeredMessage> messages)
{
await queueClient.DeadLetterAsync(messages.Select(message => message.LockToken));
this.Log(string.Format("Deadlettered {0} messages", messages.Count()));
}
async Task DeferMessagesAsync(QueueClient queueClient, IEnumerable<BrokeredMessage> messages)
{
await queueClient.DeferAsync(messages.Select(message => message.LockToken));
this.Log(string.Format("Deferred {0} messages", messages.Count()));
}
void Log(string message)
{
var formattedMessage = string.Format("{0} {1}", DateTime.Now.TimeOfDay, message);
this.output.WriteLine(formattedMessage);
Debug.WriteLine(formattedMessage);
Console.WriteLine(formattedMessage);
}
}
}

Просмотреть файл

@ -0,0 +1,173 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace Microsoft.Azure.ServiceBus.UnitTests
{
using System;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Xunit;
using Xunit.Abstractions;
public abstract class QueueSessionTestBase
{
protected QueueSessionTestBase(ITestOutputHelper output)
{
this.Output = output;
}
protected string ConnectionString { get; set; }
protected ITestOutputHelper Output { get; set; }
public async Task SessionTestCase()
{
QueueClient queueClient = QueueClient.CreateFromConnectionString(this.ConnectionString);
try
{
string messageId1 = "test-message1";
string sessionId1 = "sessionId1";
await queueClient.SendAsync(new BrokeredMessage() { MessageId = messageId1, SessionId = sessionId1 });
TestUtility.Log(this.Output, $"Sent Message: {messageId1} to Session: {sessionId1}");
string messageId2 = "test-message2";
string sessionId2 = "sessionId2";
await queueClient.SendAsync(new BrokeredMessage() { MessageId = messageId2, SessionId = sessionId2 });
TestUtility.Log(this.Output, $"Sent Message: {messageId2} to Session: {sessionId2}");
// Receive Message, Complete and Close with SessionId - sessionId 1
await this.AcceptAndCompleteSessionsAsync(queueClient, sessionId1, messageId1);
// Receive Message, Complete and Close with SessionId - sessionId 2
await this.AcceptAndCompleteSessionsAsync(queueClient, sessionId2, messageId2);
// Receive Message, Complete and Close - With Null SessionId specified
string messageId3 = "test-message3";
string sessionId3 = "sessionId3";
await queueClient.SendAsync(new BrokeredMessage() { MessageId = messageId3, SessionId = sessionId3 });
await this.AcceptAndCompleteSessionsAsync(queueClient, null, messageId3);
}
finally
{
queueClient.Close();
}
}
public async Task GetAndSetSessionStateTestCase()
{
QueueClient queueClient = QueueClient.CreateFromConnectionString(this.ConnectionString);
try
{
string messageId = "test-message1";
string sessionId = Guid.NewGuid().ToString();
await queueClient.SendAsync(new BrokeredMessage() { MessageId = messageId, SessionId = sessionId });
TestUtility.Log(this.Output, $"Sent Message: {messageId} to Session: {sessionId}");
MessageSession sessionReceiver = await queueClient.AcceptMessageSessionAsync(sessionId);
Assert.NotNull((object)sessionReceiver);
BrokeredMessage message = await sessionReceiver.ReceiveAsync();
TestUtility.Log(this.Output, $"Received Message: {message.MessageId} from Session: {sessionReceiver.SessionId}");
Assert.True(message.MessageId == messageId);
string sessionStateString = "Received Message From Session!";
Stream sessionState = new MemoryStream(Encoding.UTF8.GetBytes(sessionStateString));
await sessionReceiver.SetStateAsync(sessionState);
TestUtility.Log(this.Output, $"Set Session State: {sessionStateString} for Session: {sessionReceiver.SessionId}");
Stream returnedSessionState = await sessionReceiver.GetStateAsync();
using (StreamReader reader = new StreamReader(returnedSessionState, Encoding.UTF8))
{
string returnedSessionStateString = reader.ReadToEnd();
TestUtility.Log(this.Output, $"Get Session State Returned: {returnedSessionStateString} for Session: {sessionReceiver.SessionId}");
Assert.Equal(sessionStateString, returnedSessionStateString);
}
// Complete message using Session Receiver
await sessionReceiver.CompleteAsync(new Guid[] { message.LockToken });
TestUtility.Log(this.Output, $"Completed Message: {message.MessageId} for Session: {sessionReceiver.SessionId}");
sessionStateString = "Completed Message On Session!";
sessionState = new MemoryStream(Encoding.UTF8.GetBytes(sessionStateString));
await sessionReceiver.SetStateAsync(sessionState);
TestUtility.Log(this.Output, $"Set Session State: {sessionStateString} for Session: {sessionReceiver.SessionId}");
returnedSessionState = await sessionReceiver.GetStateAsync();
using (StreamReader reader = new StreamReader(returnedSessionState, Encoding.UTF8))
{
string returnedSessionStateString = reader.ReadToEnd();
TestUtility.Log(this.Output, $"Get Session State Returned: {returnedSessionStateString} for Session: {sessionReceiver.SessionId}");
Assert.Equal(sessionStateString, returnedSessionStateString);
}
await sessionReceiver.CloseAsync();
}
finally
{
queueClient.Close();
}
}
public async Task SessionRenewLockTestCase()
{
QueueClient queueClient = QueueClient.CreateFromConnectionString(this.ConnectionString);
try
{
string messageId = "test-message1";
string sessionId = Guid.NewGuid().ToString();
await queueClient.SendAsync(new BrokeredMessage() { MessageId = messageId, SessionId = sessionId });
TestUtility.Log(this.Output, $"Sent Message: {messageId} to Session: {sessionId}");
MessageSession sessionReceiver = await queueClient.AcceptMessageSessionAsync(sessionId);
Assert.NotNull((object)sessionReceiver);
DateTime initialSessionLockedUntilTime = sessionReceiver.LockedUntilUtc;
TestUtility.Log(this.Output, $"Session LockedUntilUTC: {initialSessionLockedUntilTime} for Session: {sessionReceiver.SessionId}");
BrokeredMessage message = await sessionReceiver.ReceiveAsync();
TestUtility.Log(this.Output, $"Received Message: {message.MessageId} from Session: {sessionReceiver.SessionId}");
Assert.True(message.MessageId == messageId);
TestUtility.Log(this.Output, "Sleeping 10 seconds...");
Thread.Sleep(TimeSpan.FromSeconds(10));
await sessionReceiver.RenewLockAsync();
DateTime firstLockedUntilUtcTime = sessionReceiver.LockedUntilUtc;
TestUtility.Log(this.Output, $"After Renew Session LockedUntilUTC: {firstLockedUntilUtcTime} for Session: {sessionReceiver.SessionId}");
Assert.True(firstLockedUntilUtcTime >= initialSessionLockedUntilTime + TimeSpan.FromSeconds(10));
TestUtility.Log(this.Output, "Sleeping 5 seconds...");
Thread.Sleep(TimeSpan.FromSeconds(5));
await sessionReceiver.RenewLockAsync();
TestUtility.Log(this.Output, $"After Second Renew Session LockedUntilUTC: {sessionReceiver.LockedUntilUtc} for Session: {sessionReceiver.SessionId}");
Assert.True(sessionReceiver.LockedUntilUtc >= firstLockedUntilUtcTime + TimeSpan.FromSeconds(5));
await message.CompleteAsync();
TestUtility.Log(this.Output, $"Completed Message: {message.MessageId} for Session: {sessionReceiver.SessionId}");
await sessionReceiver.CloseAsync();
}
finally
{
queueClient.Close();
}
}
// Session Helpers
async Task AcceptAndCompleteSessionsAsync(QueueClient queueClient, string sessionId, string messageId)
{
MessageSession sessionReceiver = await queueClient.AcceptMessageSessionAsync(sessionId);
{
if (sessionId != null)
{
Assert.True(sessionReceiver.SessionId == sessionId);
}
BrokeredMessage message = await sessionReceiver.ReceiveAsync();
Assert.True(message.MessageId == messageId);
TestUtility.Log(this.Output, $"Received Message: {message.MessageId} from Session: {sessionReceiver.SessionId}");
await message.CompleteAsync();
TestUtility.Log(this.Output, $"Completed Message: {message.MessageId} for Session: {sessionReceiver.SessionId}");
await sessionReceiver.CloseAsync();
}
}
}
}

Просмотреть файл

@ -0,0 +1,154 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace Microsoft.Azure.ServiceBus.UnitTests
{
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Azure.ServiceBus.Primitives;
using Xunit;
using Xunit.Abstractions;
public abstract class SenderReceiverClientTestBase
{
protected SenderReceiverClientTestBase(ITestOutputHelper output)
{
this.Output = output;
}
protected ITestOutputHelper Output { get; set; }
public async Task PeekLockTestCase(MessageSender messageSender, MessageReceiver messageReceiver, int messageCount)
{
await TestUtility.SendMessagesAsync(messageSender, messageCount, this.Output);
IEnumerable<BrokeredMessage> receivedMessages = await TestUtility.ReceiveMessagesAsync(messageReceiver, messageCount, this.Output);
await TestUtility.CompleteMessagesAsync(messageReceiver, receivedMessages, this.Output);
}
public async Task ReceiveDeleteTestCase(MessageSender messageSender, MessageReceiver messageReceiver, int messageCount)
{
await TestUtility.SendMessagesAsync(messageSender, messageCount, this.Output);
IEnumerable<BrokeredMessage> receivedMessages = await TestUtility.ReceiveMessagesAsync(messageReceiver, messageCount, this.Output);
Assert.True(messageCount == receivedMessages.Count());
}
public async Task PeekLockWithAbandonTestCase(MessageSender messageSender, MessageReceiver messageReceiver, int messageCount)
{
// Send messages
await TestUtility.SendMessagesAsync(messageSender, messageCount, this.Output);
// Receive 5 messages and Abandon them
int abandonMessagesCount = 5;
IEnumerable<BrokeredMessage> receivedMessages = await TestUtility.ReceiveMessagesAsync(messageReceiver, abandonMessagesCount, this.Output);
Assert.True(receivedMessages.Count() == abandonMessagesCount);
await TestUtility.AbandonMessagesAsync(messageReceiver, receivedMessages, this.Output);
// Receive all 10 messages
receivedMessages = await TestUtility.ReceiveMessagesAsync(messageReceiver, messageCount, this.Output);
Assert.True(receivedMessages.Count() == messageCount);
// TODO: Some reason for partitioned entities the delivery count is incorrect. Investigate and enable
// 5 of these messages should have deliveryCount = 2
int messagesWithDeliveryCount2 = receivedMessages.Where(message => message.DeliveryCount == 2).Count();
TestUtility.Log(this.Output, $"Messages with Delivery Count 2: {messagesWithDeliveryCount2}");
Assert.True(messagesWithDeliveryCount2 == abandonMessagesCount);
// Complete Messages
await TestUtility.CompleteMessagesAsync(messageReceiver, receivedMessages, this.Output);
}
public async Task PeekLockWithDeadLetterTestCase(MessageSender messageSender, MessageReceiver messageReceiver, MessageReceiver deadLetterReceiver, int messageCount)
{
// Send messages
await TestUtility.SendMessagesAsync(messageSender, messageCount, this.Output);
// Receive 5 messages and Deadletter them
int deadLetterMessageCount = 5;
IEnumerable<BrokeredMessage> receivedMessages = await TestUtility.ReceiveMessagesAsync(messageReceiver, deadLetterMessageCount, this.Output);
Assert.True(receivedMessages.Count() == deadLetterMessageCount);
await TestUtility.DeadLetterMessagesAsync(messageReceiver, receivedMessages, this.Output);
// Receive and Complete 5 other regular messages
receivedMessages = await TestUtility.ReceiveMessagesAsync(messageReceiver, messageCount - deadLetterMessageCount, this.Output);
await TestUtility.CompleteMessagesAsync(messageReceiver, receivedMessages, this.Output);
// TODO: After implementing Receive(WithTimeSpan), Add Try another Receive, We should not get anything.
// IEnumerable<BrokeredMessage> dummyMessages = await this.ReceiveMessagesAsync(queueClient, 10);
// Assert.True(dummyMessages == null);
// Receive 5 DLQ messages and Complete them
receivedMessages = await TestUtility.ReceiveMessagesAsync(deadLetterReceiver, deadLetterMessageCount, this.Output);
Assert.True(receivedMessages.Count() == deadLetterMessageCount);
await TestUtility.CompleteMessagesAsync(deadLetterReceiver, receivedMessages, this.Output);
}
public async Task PeekLockDeferTestCase(MessageSender messageSender, MessageReceiver messageReceiver, int messageCount)
{
// Send messages
await TestUtility.SendMessagesAsync(messageSender, messageCount, this.Output);
// Receive 5 messages And Defer them
int deferMessagesCount = 5;
IEnumerable<BrokeredMessage> receivedMessages = await TestUtility.ReceiveMessagesAsync(messageReceiver, deferMessagesCount, this.Output);
Assert.True(receivedMessages.Count() == deferMessagesCount);
var sequenceNumbers = receivedMessages.Select(receivedMessage => receivedMessage.SequenceNumber);
await TestUtility.DeferMessagesAsync(messageReceiver, receivedMessages, this.Output);
// Receive and Complete 5 other regular messages
receivedMessages = await TestUtility.ReceiveMessagesAsync(messageReceiver, messageCount - deferMessagesCount, this.Output);
await TestUtility.CompleteMessagesAsync(messageReceiver, receivedMessages, this.Output);
Assert.True(receivedMessages.Count() == messageCount - deferMessagesCount);
// Receive / Abandon deferred messages
receivedMessages = await messageReceiver.ReceiveBySequenceNumberAsync(sequenceNumbers);
Assert.True(receivedMessages.Count() == 5);
await TestUtility.DeferMessagesAsync(messageReceiver, receivedMessages, this.Output);
// Receive Again and Check delivery count
receivedMessages = await messageReceiver.ReceiveBySequenceNumberAsync(sequenceNumbers);
int count = receivedMessages.Where((message) => message.DeliveryCount == 3).Count();
Assert.True(count == receivedMessages.Count());
// Complete messages
await TestUtility.CompleteMessagesAsync(messageReceiver, receivedMessages, this.Output);
}
public async Task RenewLockTestCase(MessageSender messageSender, MessageReceiver messageReceiver, int messageCount)
{
// Send messages
await TestUtility.SendMessagesAsync(messageSender, messageCount, this.Output);
// Receive messages
IEnumerable<BrokeredMessage> receivedMessages = await TestUtility.ReceiveMessagesAsync(messageReceiver, messageCount, this.Output);
BrokeredMessage message = receivedMessages.First();
DateTime firstLockedUntilUtcTime = message.LockedUntilUtc;
TestUtility.Log(this.Output, $"MessageLockedUntil: {firstLockedUntilUtcTime}");
TestUtility.Log(this.Output, "Sleeping 10 seconds...");
Thread.Sleep(TimeSpan.FromSeconds(10));
DateTime lockedUntilUtcTime = await messageReceiver.RenewLockAsync(receivedMessages.First().LockToken);
TestUtility.Log(this.Output, $"After First Renewal: {lockedUntilUtcTime}");
Assert.True(lockedUntilUtcTime >= firstLockedUntilUtcTime + TimeSpan.FromSeconds(10));
TestUtility.Log(this.Output, "Sleeping 5 seconds...");
Thread.Sleep(TimeSpan.FromSeconds(5));
lockedUntilUtcTime = await messageReceiver.RenewLockAsync(receivedMessages.First().LockToken);
TestUtility.Log(this.Output, $"After Second Renewal: {lockedUntilUtcTime}");
Assert.True(lockedUntilUtcTime >= firstLockedUntilUtcTime + TimeSpan.FromSeconds(5));
// Complete Messages
await TestUtility.CompleteMessagesAsync(messageReceiver, receivedMessages, this.Output);
Assert.True(receivedMessages.Count() == messageCount);
}
}
}

Просмотреть файл

@ -0,0 +1,87 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace Microsoft.Azure.ServiceBus.UnitTests
{
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Xunit;
using Xunit.Abstractions;
public class TestUtility
{
const int MaxAttemptsCount = 5;
public static void Log(ITestOutputHelper output, string message)
{
var formattedMessage = string.Format("{0} {1}", DateTime.Now.TimeOfDay, message);
output.WriteLine(formattedMessage);
Debug.WriteLine(formattedMessage);
Console.WriteLine(formattedMessage);
}
public static async Task SendMessagesAsync(MessageSender messageSender, int messageCount, ITestOutputHelper output)
{
if (messageCount == 0)
{
await Task.FromResult(false);
}
List<BrokeredMessage> messagesToSend = new List<BrokeredMessage>();
for (int i = 0; i < messageCount; i++)
{
BrokeredMessage message = new BrokeredMessage("test" + i);
message.Label = "test" + i;
messagesToSend.Add(message);
}
await messageSender.SendAsync(messagesToSend);
Log(output, string.Format("Sent {0} messages", messageCount));
}
public static async Task<IEnumerable<BrokeredMessage>> ReceiveMessagesAsync(MessageReceiver messageReceiver, int messageCount, ITestOutputHelper output)
{
int receiveAttempts = 0;
List<BrokeredMessage> messagesToReturn = new List<BrokeredMessage>();
while (receiveAttempts++ < TestUtility.MaxAttemptsCount && messagesToReturn.Count < messageCount)
{
var messages = await messageReceiver.ReceiveAsync(messageCount);
if (messages != null)
{
messagesToReturn.AddRange(messages);
}
}
Log(output, string.Format("Received {0} messages", messagesToReturn.Count));
return messagesToReturn;
}
public static async Task CompleteMessagesAsync(MessageReceiver messageReceiver, IEnumerable<BrokeredMessage> messages, ITestOutputHelper output)
{
await messageReceiver.CompleteAsync(messages.Select(message => message.LockToken));
Log(output, string.Format("Completed {0} messages", messages.Count()));
}
public static async Task AbandonMessagesAsync(MessageReceiver messageReceiver, IEnumerable<BrokeredMessage> messages, ITestOutputHelper output)
{
await messageReceiver.AbandonAsync(messages.Select(message => message.LockToken));
Log(output, string.Format("Abandoned {0} messages", messages.Count()));
}
public static async Task DeadLetterMessagesAsync(MessageReceiver messageReceiver, IEnumerable<BrokeredMessage> messages, ITestOutputHelper output)
{
await messageReceiver.DeadLetterAsync(messages.Select(message => message.LockToken));
Log(output, string.Format("Deadlettered {0} messages", messages.Count()));
}
public static async Task DeferMessagesAsync(MessageReceiver messageReceiver, IEnumerable<BrokeredMessage> messages, ITestOutputHelper output)
{
await messageReceiver.DeferAsync(messages.Select(message => message.LockToken));
Log(output, string.Format("Deferred {0} messages", messages.Count()));
}
}
}

Просмотреть файл

@ -0,0 +1,117 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace Microsoft.Azure.ServiceBus.UnitTests
{
using System.Threading.Tasks;
using Microsoft.Azure.ServiceBus.Primitives;
using Xunit.Abstractions;
public abstract class TopicClientTestBase : SenderReceiverClientTestBase
{
protected TopicClientTestBase(ITestOutputHelper output)
: base(output)
{
}
protected string ConnectionString { get; set; }
protected string SubscriptionName { get; set; }
public async Task TopicClientPeekLockTestCase(int messageCount)
{
TopicClient topicClient = TopicClient.CreateFromConnectionString(this.ConnectionString);
SubscriptionClient subscriptionClient = SubscriptionClient.CreateFromConnectionString(this.ConnectionString, this.SubscriptionName);
try
{
await this.PeekLockTestCase(topicClient.InnerSender, subscriptionClient.InnerReceiver, messageCount);
}
finally
{
subscriptionClient.Close();
topicClient.Close();
}
}
public async Task TopicClientReceiveDeleteTestCase(int messageCount)
{
TopicClient topicClient = TopicClient.CreateFromConnectionString(this.ConnectionString);
SubscriptionClient subscriptionClient = SubscriptionClient.CreateFromConnectionString(this.ConnectionString, this.SubscriptionName, ReceiveMode.ReceiveAndDelete);
try
{
await this.ReceiveDeleteTestCase(topicClient.InnerSender, subscriptionClient.InnerReceiver, messageCount);
}
finally
{
subscriptionClient.Close();
topicClient.Close();
}
}
public async Task TopicClientPeekLockWithAbandonTestCase(int messageCount)
{
TopicClient topicClient = TopicClient.CreateFromConnectionString(this.ConnectionString);
SubscriptionClient subscriptionClient = SubscriptionClient.CreateFromConnectionString(this.ConnectionString, this.SubscriptionName);
try
{
await this.PeekLockWithAbandonTestCase(topicClient.InnerSender, subscriptionClient.InnerReceiver, messageCount);
}
finally
{
subscriptionClient.Close();
topicClient.Close();
}
}
public async Task TopicClientPeekLockWithDeadLetterTestCase(int messageCount)
{
TopicClient topicClient = TopicClient.CreateFromConnectionString(this.ConnectionString);
SubscriptionClient subscriptionClient = SubscriptionClient.CreateFromConnectionString(this.ConnectionString, this.SubscriptionName);
// Create DLQ Client To Receive DeadLetteredMessages
ServiceBusConnectionStringBuilder builder = new ServiceBusConnectionStringBuilder(this.ConnectionString);
string subscriptionDeadletterPath = EntityNameHelper.FormatDeadLetterPath(this.SubscriptionName);
SubscriptionClient deadLetterSubscriptionClient = SubscriptionClient.CreateFromConnectionString(this.ConnectionString, subscriptionDeadletterPath);
try
{
await this.PeekLockWithDeadLetterTestCase(topicClient.InnerSender, subscriptionClient.InnerReceiver, deadLetterSubscriptionClient.InnerReceiver, messageCount);
}
finally
{
deadLetterSubscriptionClient.Close();
topicClient.Close();
}
}
public async Task TopicClientPeekLockDeferTestCase(int messageCount)
{
TopicClient topicClient = TopicClient.CreateFromConnectionString(this.ConnectionString);
SubscriptionClient subscriptionClient = SubscriptionClient.CreateFromConnectionString(this.ConnectionString, this.SubscriptionName);
try
{
await this.PeekLockDeferTestCase(topicClient.InnerSender, subscriptionClient.InnerReceiver, messageCount);
}
finally
{
subscriptionClient.Close();
topicClient.Close();
}
}
public async Task TopicClientRenewLockTestCase(int messageCount)
{
TopicClient topicClient = TopicClient.CreateFromConnectionString(this.ConnectionString);
SubscriptionClient subscriptionClient = SubscriptionClient.CreateFromConnectionString(this.ConnectionString, this.SubscriptionName);
try
{
await this.RenewLockTestCase(topicClient.InnerSender, subscriptionClient.InnerReceiver, messageCount);
}
finally
{
subscriptionClient.Close();
topicClient.Close();
}
}
}
}

Просмотреть файл

@ -0,0 +1,181 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace Microsoft.Azure.ServiceBus.UnitTests
{
using System;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Xunit;
using Xunit.Abstractions;
public class TopicSessionTestBase
{
protected TopicSessionTestBase(ITestOutputHelper output)
{
this.Output = output;
}
protected string ConnectionString { get; set; }
protected string SubscriptionName { get; set; }
protected ITestOutputHelper Output { get; set; }
public async Task SessionTestCase()
{
TopicClient topicClient = TopicClient.CreateFromConnectionString(this.ConnectionString);
SubscriptionClient subscriptionClient = SubscriptionClient.CreateFromConnectionString(this.ConnectionString, this.SubscriptionName);
try
{
string messageId1 = "test-message1";
string sessionId1 = "sessionId1";
await topicClient.SendAsync(new BrokeredMessage() { MessageId = messageId1, SessionId = sessionId1 });
TestUtility.Log(this.Output, $"Sent Message: {messageId1} to Session: {sessionId1}");
string messageId2 = "test-message2";
string sessionId2 = "sessionId2";
await topicClient.SendAsync(new BrokeredMessage() { MessageId = messageId2, SessionId = sessionId2 });
TestUtility.Log(this.Output, $"Sent Message: {messageId2} to Session: {sessionId2}");
// Receive Message, Complete and Close with SessionId - sessionId 1
await this.AcceptAndCompleteSessionsAsync(subscriptionClient, sessionId1, messageId1);
// Receive Message, Complete and Close with SessionId - sessionId 2
await this.AcceptAndCompleteSessionsAsync(subscriptionClient, sessionId2, messageId2);
// Receive Message, Complete and Close - With Null SessionId specified
string messageId3 = "test-message3";
string sessionId3 = "sessionId3";
await topicClient.SendAsync(new BrokeredMessage() { MessageId = messageId3, SessionId = sessionId3 });
await this.AcceptAndCompleteSessionsAsync(subscriptionClient, null, messageId3);
}
finally
{
subscriptionClient.Close();
topicClient.Close();
}
}
public async Task GetAndSetSessionStateTestCase()
{
TopicClient topicClient = TopicClient.CreateFromConnectionString(this.ConnectionString);
SubscriptionClient subscriptionClient = SubscriptionClient.CreateFromConnectionString(this.ConnectionString, this.SubscriptionName);
try
{
string messageId = "test-message1";
string sessionId = Guid.NewGuid().ToString();
await topicClient.SendAsync(new BrokeredMessage() { MessageId = messageId, SessionId = sessionId });
TestUtility.Log(this.Output, $"Sent Message: {messageId} to Session: {sessionId}");
MessageSession sessionReceiver = await subscriptionClient.AcceptMessageSessionAsync(sessionId);
Assert.NotNull((object)sessionReceiver);
BrokeredMessage message = await sessionReceiver.ReceiveAsync();
TestUtility.Log(this.Output, $"Received Message: {message.MessageId} from Session: {sessionReceiver.SessionId}");
Assert.True(message.MessageId == messageId);
string sessionStateString = "Received Message From Session!";
Stream sessionState = new MemoryStream(Encoding.UTF8.GetBytes(sessionStateString));
await sessionReceiver.SetStateAsync(sessionState);
TestUtility.Log(this.Output, $"Set Session State: {sessionStateString} for Session: {sessionReceiver.SessionId}");
Stream returnedSessionState = await sessionReceiver.GetStateAsync();
using (StreamReader reader = new StreamReader(returnedSessionState, Encoding.UTF8))
{
string returnedSessionStateString = reader.ReadToEnd();
TestUtility.Log(this.Output, $"Get Session State Returned: {returnedSessionStateString} for Session: {sessionReceiver.SessionId}");
Assert.Equal(sessionStateString, returnedSessionStateString);
}
// Complete message using Session Receiver
await sessionReceiver.CompleteAsync(new Guid[] { message.LockToken });
TestUtility.Log(this.Output, $"Completed Message: {message.MessageId} for Session: {sessionReceiver.SessionId}");
sessionStateString = "Completed Message On Session!";
sessionState = new MemoryStream(Encoding.UTF8.GetBytes(sessionStateString));
await sessionReceiver.SetStateAsync(sessionState);
TestUtility.Log(this.Output, $"Set Session State: {sessionStateString} for Session: {sessionReceiver.SessionId}");
returnedSessionState = await sessionReceiver.GetStateAsync();
using (StreamReader reader = new StreamReader(returnedSessionState, Encoding.UTF8))
{
string returnedSessionStateString = reader.ReadToEnd();
TestUtility.Log(this.Output, $"Get Session State Returned: {returnedSessionStateString} for Session: {sessionReceiver.SessionId}");
Assert.Equal(sessionStateString, returnedSessionStateString);
}
await sessionReceiver.CloseAsync();
}
finally
{
subscriptionClient.Close();
topicClient.Close();
}
}
public async Task SessionRenewLockTestCase()
{
TopicClient topicClient = TopicClient.CreateFromConnectionString(this.ConnectionString);
SubscriptionClient subscriptionClient = SubscriptionClient.CreateFromConnectionString(this.ConnectionString, this.SubscriptionName);
try
{
string messageId = "test-message1";
string sessionId = Guid.NewGuid().ToString();
await topicClient.SendAsync(new BrokeredMessage() { MessageId = messageId, SessionId = sessionId });
TestUtility.Log(this.Output, $"Sent Message: {messageId} to Session: {sessionId}");
MessageSession sessionReceiver = await subscriptionClient.AcceptMessageSessionAsync(sessionId);
Assert.NotNull((object)sessionReceiver);
DateTime initialSessionLockedUntilTime = sessionReceiver.LockedUntilUtc;
TestUtility.Log(this.Output, $"Session LockedUntilUTC: {initialSessionLockedUntilTime} for Session: {sessionReceiver.SessionId}");
BrokeredMessage message = await sessionReceiver.ReceiveAsync();
TestUtility.Log(this.Output, $"Received Message: {message.MessageId} from Session: {sessionReceiver.SessionId}");
Assert.True(message.MessageId == messageId);
TestUtility.Log(this.Output, "Sleeping 10 seconds...");
Thread.Sleep(TimeSpan.FromSeconds(10));
await sessionReceiver.RenewLockAsync();
DateTime firstLockedUntilUtcTime = sessionReceiver.LockedUntilUtc;
TestUtility.Log(this.Output, $"After Renew Session LockedUntilUTC: {firstLockedUntilUtcTime} for Session: {sessionReceiver.SessionId}");
Assert.True(firstLockedUntilUtcTime >= initialSessionLockedUntilTime + TimeSpan.FromSeconds(10));
TestUtility.Log(this.Output, "Sleeping 5 seconds...");
Thread.Sleep(TimeSpan.FromSeconds(5));
await sessionReceiver.RenewLockAsync();
TestUtility.Log(this.Output, $"After Second Renew Session LockedUntilUTC: {sessionReceiver.LockedUntilUtc} for Session: {sessionReceiver.SessionId}");
Assert.True(sessionReceiver.LockedUntilUtc >= firstLockedUntilUtcTime + TimeSpan.FromSeconds(5));
await message.CompleteAsync();
TestUtility.Log(this.Output, $"Completed Message: {message.MessageId} for Session: {sessionReceiver.SessionId}");
await sessionReceiver.CloseAsync();
}
finally
{
subscriptionClient.Close();
topicClient.Close();
}
}
// Session Helpers
async Task AcceptAndCompleteSessionsAsync(SubscriptionClient subscriptionClient, string sessionId, string messageId)
{
MessageSession sessionReceiver = await subscriptionClient.AcceptMessageSessionAsync(sessionId);
{
if (sessionId != null)
{
Assert.True(sessionReceiver.SessionId == sessionId);
}
BrokeredMessage message = await sessionReceiver.ReceiveAsync();
Assert.True(message.MessageId == messageId);
TestUtility.Log(this.Output, $"Received Message: {message.MessageId} from Session: {sessionReceiver.SessionId}");
await message.CompleteAsync();
TestUtility.Log(this.Output, $"Completed Message: {message.MessageId} for Session: {sessionReceiver.SessionId}");
await sessionReceiver.CloseAsync();
}
}
}
}

Двоичные данные
test/Microsoft.Azure.ServiceBus.UnitTests/keyfile.snk Normal file

Двоичный файл не отображается.

Просмотреть файл

@ -4,6 +4,7 @@
"buildOptions": {
"warningsAsErrors": true,
"keyFile": "keyfile.snk",
"additionalArguments": [ "/ruleset:./../../build/StyleCop.Analyzers.ruleset", "/additionalfile:./../../build/stylecop.json" ]
},