I have created below sample application to test "MaxAutoLockRenewalDuration" functionality.
Here is my test scenario,
Created two console application with the same code.
Add one message in the queue
Run Console App1 which receives the message
Run Console App2 which receives the same message.
With below code I have setup MaxAutoLockRenewalDuration to 10 minutes.
To my understanding of "MaxAutoLockRenewalDuration" it should renew the lock automatically till 10 minutes and second console app should not receive the same message.
public class Program
{
static string connectionString = "***";
static string queueName = "firstqueue";
static async Task Main(string[] args)
{
try
{
//uncomment below if you would like to add message to queue
//await CreateMessage(queueName, "Message 1 to test 'MaxAutoLockRenewalDuration'");
await ReceiveMessagesAsync();
}
catch (Exception ex)
{
throw;
}
Console.ReadKey();
}
private static async Task CreateMessage(string queueName, string textMessage)
{
// create a Service Bus client
await using (ServiceBusClient client = new ServiceBusClient(connectionString))
{
// create a sender for the queue
ServiceBusSender sender = client.CreateSender(queueName);
// create a message that we can send
ServiceBusMessage message = new ServiceBusMessage(textMessage);
// send the message
await sender.SendMessageAsync(message);
Console.WriteLine($"Sent a single message to the queue: {queueName}");
}
}
// handle received messages
static async Task MessageHandler(ProcessMessageEventArgs args)
{
string body = args.Message.Body.ToString();
Console.WriteLine($"Received: {body}");
System.Threading.Thread.Sleep(TimeSpan.FromMinutes(5));
// complete the message. messages is deleted from the queue.
await args.CompleteMessageAsync(args.Message);
}
// handle any errors when receiving messages
static Task ErrorHandler(ProcessErrorEventArgs args)
{
Console.WriteLine(args.Exception.ToString());
return Task.CompletedTask;
}
static async Task ReceiveMessagesAsync()
{
var processorOptions = new ServiceBusProcessorOptions
{
AutoCompleteMessages = false,
MaxConcurrentCalls = 1,
MaxAutoLockRenewalDuration = TimeSpan.FromMinutes(10),
ReceiveMode = ServiceBusReceiveMode.PeekLock,
PrefetchCount = 1
};
await using (ServiceBusClient client = new ServiceBusClient(connectionString))
{
// create a processor that we can use to process the messages
ServiceBusProcessor processor = client.CreateProcessor(queueName, processorOptions);
// add handler to process messages
processor.ProcessMessageAsync += MessageHandler;
// add handler to process any errors
processor.ProcessErrorAsync += ErrorHandler;
// start processing
await processor.StartProcessingAsync();
Console.WriteLine("Wait for a minute and then press any key to end the processing");
Console.ReadKey();
// stop processing
Console.WriteLine("\nStopping the receiver...");
await processor.StopProcessingAsync();
Console.WriteLine("Stopped receiving messages");
}
}
static async Task ReceiveMessagesAsync(string queueName)
{
await using (ServiceBusClient client = new ServiceBusClient(connectionString))
{
// create a processor that we can use to process the messages
ServiceBusProcessor processor = client.CreateProcessor(queueName, new ServiceBusProcessorOptions());
// add handler to process messages
processor.ProcessMessageAsync += MessageHandler;
// add handler to process any errors
processor.ProcessErrorAsync += ErrorHandler;
// start processing
await processor.StartProcessingAsync();
Console.WriteLine("Wait for a minute and then press any key to end the processing");
Console.ReadKey();
// stop processing
Console.WriteLine("\nStopping the receiver...");
await processor.StopProcessingAsync();
Console.WriteLine("Stopped receiving messages");
}
}
}
I'm using your code with Azure.Messaging.ServiceBus Version "7.1.1" and "7.1.2", the "MaxAutoLockRenewalDuration" functionality works well.
Here are my steps:
1.Send a message to the queue.
2.Run Console App1 which receives the message
3.Keep Console App1 running, and then run Console App2, there is no message received there. Here is the screenshot:
But if you first run Console App1 which receives the message, then close the Console App1 -> then run Console App2, you can see the message in Console App2. This is expected.
Related
Whenever I send a message to the topic I am unable to receive it from the subscription. From my understand this should be possible. Not sure what in the code is currently making it unable to forward the message from the topic to the subscription.
static void main(string[] args)
{
MainAsync().GetAwaiter().GetResult();
}
static async Task MainAsync()
{
var factory = MessagingFactory.CreateFromConnectionString(connectionstring);
var receiver = await factory.CreateMessageReceiverAsync(topic name + "/subcriptions/" +subcriptionName);
var sender = await factory.CreateMessageSenderAsync(topicName);
// receive message from subscription
receiver.OnMessageAsync( async receivedMessage =>
{
Console.WriteLine("receive message - {0}, receivedMessage.MessageId);
await receivedMessage.CompleteAsync();
}, new OnMessageOptions() {AutoComplete = false});
// send message to topic
await sender.SendAsync(new BrokeredMessage("testing") {MessageId ="dhfs8264"});
await Task.WhenAny(
Task.Run(() => Console.Readkey()),
Task.Delay(TimeSpan.FromSeconds(10)));
}}}
I tried in my environment and got below results:
Make sure you were using latest version of NuGet package for Azure-Messaging-ServiceBus- 7.11.1 in your environment because Microsoft.Azure.ServiceBus is deprecated.
Initially, I created service bus with topic and subscription through portal using this Document and sending 5 messages to the topic.
Sending messages to the topic with below code:
using System.Diagnostics;
using System;
using System.Threading.Tasks;
using Azure.Messaging.ServiceBus;
ServiceBusClient client;
ServiceBusSender sender;
const int numOfMessages = 5;
client = new ServiceBusClient("<connect strng >");
sender = client.CreateSender("topicname");
using ServiceBusMessageBatch messageBatch = await sender.CreateMessageBatchAsync();
for (int i = 1; i <= numOfMessages; i++)
{
if (!messageBatch.TryAddMessage(new ServiceBusMessage($"Message {i}")))
{
throw new Exception($"The message {i} is too large to fit in the batch.");
}
}
try
{
await sender.SendMessagesAsync(messageBatch);
Console.WriteLine($"A batch of {numOfMessages} messages has been published to the topic.");
}
finally
{
await sender.DisposeAsync();
await client.DisposeAsync();
}
Console.WriteLine("Press any key to end the application");
Console.ReadKey();
Console:
Portal:
In portal you can check in the overview tab of incoming messages is 5.
Receiving messages from a subscription with below code:
using System;
using System.Threading.Tasks;
using Azure.Messaging.ServiceBus;
ServiceBusClient client;
ServiceBusProcessor processor;
async Task MessageHandler(ProcessMessageEventArgs args)
{
string body = args.Message.Body.ToString();
Console.WriteLine($"Received: {body} from subscription.");
await args.CompleteMessageAsync(args.Message);
}
Task ErrorHandler(ProcessErrorEventArgs args)
{
Console.WriteLine(args.Exception.ToString());
return Task.CompletedTask;
}
client = new ServiceBusClient("connect strng");
processor = client.CreateProcessor("topicname", "subscriptionname", new ServiceBusProcessorOptions());
try
{
processor.ProcessMessageAsync += MessageHandler;
processor.ProcessErrorAsync += ErrorHandler;
await processor.StartProcessingAsync();
Console.WriteLine("Wait for a minute and then press any key to end the processing");
Console.ReadKey();
Console.WriteLine("\nStopping the receiver...");
Console.WriteLine("Stopped receiving messages");
}
finally
{
await processor.DisposeAsync();
await client.DisposeAsync();
}
Console:
The above code executed successfully and received messages from subscription.
Portal:
Portal:
At last, in your Service Bus Subscription level page, you see the Active message count as zero. Because it tells receiver has read and finished messages from this subscription.
I created a .NET core 6 project. I added Azure.Messaging.ServiceBus as the dependency. I am using below code to send message to service bus topic.
// See https://aka.ms/new-console-template for more information
using Azure.Messaging.ServiceBus;
using System.Dynamic;
using System.Net;
using System.Text;
using System.Text.Json;
Console.WriteLine("Hello, World!");
Sender t = new Sender();
Sender.Send();
class Sender
{
public static async Task Send()
{
string connectionString = "Endpoint=sb://sb-test-one.servicebus.windows.net/;SharedAccessKeyName=manage;SharedAccessKey=8e+6SWp3skB3AeDlwH6ufGEainEs45353435JzDywz5DU=;";
string topicName = "topicone";
string subscriptionName = "subone";
// The Service Bus client types are safe to cache and use as a singleton for the lifetime
try
{
await using var client = new ServiceBusClient(connectionString, new ServiceBusClientOptions
{
TransportType = ServiceBusTransportType.AmqpWebSockets
});
// create the sender
ServiceBusSender sender = client.CreateSender(topicName);
dynamic data = new ExpandoObject();
data.name = "Abc";
data.age = 6;
// create a message that we can send. UTF-8 encoding is used when providing a string.
var messageBody = JsonSerializer.Serialize(data);
ServiceBusMessage message = new ServiceBusMessage(messageBody);
// send the message
await sender.SendMessageAsync(message);
var s = 10;
}
catch (Exception e)
{
var v = 10;
}
//// create a receiver for our subscription that we can use to receive the message
//ServiceBusReceiver receiver = client.CreateReceiver(topicName, subscriptionName);
//// the received message is a different type as it contains some service set properties
//ServiceBusReceivedMessage receivedMessage = await receiver.ReceiveMessageAsync();
//// get the message body as a string
//string body = receivedMessage.Body.ToString();
//Console.WriteLine(body);
Console.WriteLine("Press any key to end the application");
Console.ReadKey();
}
}
Issue: When I call await sender.SendMessageAsync(message); after this line get executed, the program is actually terminating. It not awating. The whole execution stops after this line.
System is not throwing any exception and service bus is not receiving any message.
I just noticed that all other samples I saw had a default SharedAccessPolicy called RootManageSharedAccessKey policy available by default in the azure portal. For me, I had to create this policy. To my policy I have given Manage, Send, ReceiveAccess.
Needed to change Sender.Send(); to Sender.Send().GetAwaiter().GetResult();
I'm saving a row to my db (class with teacher/students, time, date, etc), once I have the id I create a message on my Azure service bus where the unique id of the row from my db is used as the message body of the service bus message. I'm creating scheduled messages so I can notify the students before the class and after the class is over so they can rate/review their teacher.
QUESTION - I'd like to know how to roll back or an easy way to remove the db row by not allowing it to fully save if the message to the Azure service bus fails to save?
Currently I'm using a generic repository with UnitOfWork to save to my db and I'm catching the exception from my service bus service if it fails, then deleting the row that was just saved, but it's sloppy looking and I can see it will lead to problems.
Here is what I'm doing now in the controller.
[HttpPost("create")]
public async Task<IActionResult> OnCreating(OnCreatingEventDto onCreatingDto)
{
var userFromRepo = await _userManager.FindByEmailFromClaimsPrinciple(HttpContext.User);
if (userFromRepo == null)
return Unauthorized(new ApiResponse(401));
var newEvent = _mapper.Map<ClassEvent>(onCreatingDto);
_unitOfWork.Repository<ClassEvent>().Add(newEvent);
var success = await _unitOfWork.Complete();
if (success > 0) {
try {
var sequenceNUmber = await _serviceBusProducer.SendMessage(newEvent.Id.ToString(), newEvent.eventTime.addDays(1), queueName);
newEvent.ServiceBusSequenceNumber = sequenceNUmber;
_unitOfWork.Repository<ClassEvent>().Update(newEvent);
var secondSuccess = await _unitOfWork.Complete();
if (secondSuccess > 0) {
return Ok();
}
} catch(Exception ex) {
_logger.LogError("error saving to service bus");
_unitOfWork.Repository<ClassEvent>().Delete(newEvent);
var deleteSuccess = await _unitOfWork.Complete();
if (deleteSuccess > 0) {
}
return BadRequest(new ApiResponse(400, "Problem Creating Event"));
}
}
return BadRequest(new ApiResponse(400, "Problem Creating Event"));
}
Here is the method from my service that creates the message on the queue
public async Task<long> SendMessage(string messageBody, DateTimeOffset scheduledEnqueueTime, string queueName)
{
await using (ServiceBusClient client = new ServiceBusClient(_config["ServiceBus:Connection"]))
{
ServiceBusSender sender = client.CreateSender(_config["ServiceBus:" + queueName]);
ServiceBusMessage message = new ServiceBusMessage(messageBody);
var sequenceNumber =
await sender.ScheduleMessageAsync(message, scheduledEnqueueTime);
return sequenceNumber;
}
}
The below program sends the message to the queue, however most of the times the message is not sent.
I am using the Sender() method (as shown below) in my class library. This is being called from a Cloud Service(Worker Role). Please help to identify why the message is not consistently sent and correcting the code.
{
static void Main(string[] args)
{
Sender();
Console.ReadKey();
}
private static void Sender()
{
var ConnectionString = "<<Connectionstring>>";
var QueueName = "samplequeue";
var queueClient = new QueueClient(ConnectionString, QueueName);
try
{
for (var i = 0; i < 5; i++)
{
// Create a new message to send to the queue
string messageBody = $"Message {i}";
var message = new Message(Encoding.UTF8.GetBytes(messageBody));
// Write the body of the message to the console
Console.WriteLine($"Sending message: {messageBody}");
// Send the message to the queue
queueClient.SendAsync(message);
}
}
catch (Exception ex)
{
Console.WriteLine("Message queue failed\n" + ex.Message);
}
finally
{
Console.WriteLine("Message queued");
}
}
}
I believe the problem is that you're not awaiting the execution of an async method.
queueClient.SendAsync(message)
One option would be to change the above call and wait for the execution to complete
await queueClient.SendAsync(message);
In this case you will need to make Sender method async as well and then wait for it to complete in your Main method.
Other option would be to use Sync version of the method:
queueClient.Send(message);
You must wait on the call to complete. Otherwise it will be run in the background.
That means that Message 4 might complete after Message 3 (since you have a loop) or that it wont complete at all if your console application exits before it have a chance.
Since you are using a synchronous application, you can invkoe Send() like this:
queueClient.SendAsync(message).GetAwaiter().GetResult();
See .Result vs .GetAwaiter().GetResult()
I have below code which works fine but when function got trigger it only read one message at a time.
to get more than 1 message i have given count 10 into ReceiveAsync(10) , but
still getting only one message.
public static async System.Threading.Tasks.Task RunAsync([TimerTrigger("0 */2 * * * *")]TimerInfo myTimer, ILogger log)
{
log.LogInformation($"C# Timer trigger function executed at: {DateTime.Now}");
try
{
var deadQueuePath = EntityNameHelper.FormatDeadLetterPath("demo/subscriptions/demo");
MessageReceiver deadletterReceiver = new MessageReceiver(Environment.GetEnvironmentVariable("ConnectionStringSettingName"), deadQueuePath, ReceiveMode.PeekLock,RetryPolicy.Default);
MessageSender sender = new MessageSender(Environment.GetEnvironmentVariable("ConnectionStringSettingName"), "demo",RetryPolicy.Default);
var deadLetter = await deadletterReceiver.ReceiveAsync(10);
if (deadLetter != null)
{
log.LogInformation($"got new message");
Message newMessage = new Message(deadLetter.Body)
{
ContentType = deadLetter.ContentType,
CorrelationId = deadLetter.CorrelationId
};
//Send the message to the Active Queue
await sender.SendAsync(newMessage);
await deadletterReceiver.CompleteAsync(item.SystemProperties.LockToken); //Unlock the message and remove it from the DLQ
}
}
catch (Exception ex)
{
log.LogInformation($"Exception: {ex}");
}
}
ReceiveAsync(10) - This method is used for setting the sequence number of the message to receive and not the count. Look at the details on ReceiveAsync here
In order to fetch multiple messages, you need to set Prefetch property to the receiver.
Make use of the below constructor to initialize your MessageReceiver Class:
public MessageReceiver (Microsoft.Azure.ServiceBus.ServiceBusConnection serviceBusConnection, string entityPath, Microsoft.Azure.ServiceBus.ReceiveMode receiveMode = Microsoft.Azure.ServiceBus.ReceiveMode.PeekLock, Microsoft.Azure.ServiceBus.RetryPolicy retryPolicy = null, int prefetchCount = 0);