Long running RabbitMQ connection in ASP.NET - c#

I know that establishing a RabbitMQ connection is expensive, so it is not recommended to {connect / publish / disconnect} whenever I want to put a message to queue. However, I couldn' t find a way to have a long running RabbitMQ connection across many requests.
What I want to achieve is to have only one(or a limited # of) RabbitMQ connection(s) in my ASP.NET application that runs on IIS and use it for different requests, instead of creating different connections for each request.
The following code is what I have up to now. I need somehow to remove these using statements and keep my connection open.
Thanks a lot
public ReturnCode AtomicPublish(string userName, string password, string virtualHost, string hostName, string exchangeName, string queueName, string message)
{
using (IConnection conn = new ConnectionFactory()
{
UserName = userName,
Password = password,
VirtualHost = virtualHost,
HostName = hostName
}.CreateConnection())
{
using (IModel model = conn.CreateModel())
{
model.ExchangeDeclare(exchangeName, ExchangeType.Fanout, true);
model.QueueDeclare(queueName, true, false, false, null);
model.QueueBind(queueName, exchangeName, "", null);
byte[] messageBodyBytes = System.Text.Encoding.UTF8.GetBytes(message);
model.BasicPublish(exchangeName, string.Empty, null, messageBodyBytes);
}
}
return ReturnCode.OK;
}

you should use server side storage for your variable connection,
if connection is same for all requests of same user save it in the Session objerct of ASP.NET,
if it is same for all users save it in the Cache application object.
then when you need to load it again and re-use it, get it back from Session or Cache, see here an example: Application vs Session vs Cache

off the top of my head.
private static IConnection _connection {get;set;}
private static object LockObject = new object();
private static IConnection GetConnection (string username, string password, string virtualHost, string hostName)
get{
// do work here in case the connection is closed as well.
if (_connection == null){
lock(LockObject){
if (_connection == null){
_connection = new ConnectionFactory
{
UserName = userName,
Password = password,
VirtualHost = virtualHost,
HostName = hostName
}.CreateConnection();
}
}
}
return _connection;
}
}
public ReturnCode AtomicPublish(string userName, string password, string virtualHost, string hostName, string exchangeName, string queueName, string message)
{
using (IModel model = GetConnection(userName, password, virtualHost, hostName).CreateModel()) //lazy loads the get connection
{
model.ExchangeDeclare(exchangeName, ExchangeType.Fanout, true);
model.QueueDeclare(queueName, true, false, false, null);
model.QueueBind(queueName, exchangeName, "", null);
byte[] messageBodyBytes = System.Text.Encoding.UTF8.GetBytes(message);
model.BasicPublish(exchangeName, string.Empty, null, messageBodyBytes);
}
return ReturnCode.OK;
}

Like you said, establishing a RabbitMQ connection is expensive, so when you open a connection you shouldn't send a message and disconnect.
In fact, RabbitMQ is based on the protocole AMQP which gives the possibility to send many messages to many receivers in different topics(instead of using queues), which means that every receiver is listening in a different topic like this exemple :
using System;
using System.Linq;
using RabbitMQ.Client;
using System.Text;
class EmitLogTopic
{
public static void Main(string[] args)
{
var factory = new ConnectionFactory() { HostName = "localhost" };
using(var connection = factory.CreateConnection())
using(var channel = connection.CreateModel())
{
channel.ExchangeDeclare(exchange: "topic_logs",
type: "topic");
var routingKey = (args.Length > 0) ? args[0] : "anonymous.info";
var message = (args.Length > 1)
? string.Join(" ", args.Skip( 1 ).ToArray())
: "Hello World!";
var body = Encoding.UTF8.GetBytes(message);
channel.BasicPublish(exchange: "topic_logs",
routingKey: routingKey,
basicProperties: null,
body: body);
Console.WriteLine(" [x] Sent '{0}':'{1}'", routingKey, message);
}
}
}
this link provides a tutorial for more informations.

Related

Queue is locking even though it is declared as non-exclusive (RabbitMQ)

I'm using the .NET client for RabbitMQ to implement the RPC pattern for a client and server. The client and server both declare the queues using the same parameters so that messages sent before the server is up won't be lost and vice versa. However, declaring the queue on the client side throws an OperationInterruptedException:
operation queue.declare caused a channel exception resource_locked: cannot obtain exclusive access to locked queue '151' in vhost '/'. It could be originally declared on another connection or the exclusive property value does not match that of the original declaration.
This question is similar to RabbitMQ: ACCESS_REFUSED even if the queue is non-exclusive, but the answer there did not work for me. The BasicConsumer defaults to being non-exclusive in the .NET client. If I declare the queue only in the server, the code works without issues. I don't want to have to make sure the server starts before the client, though.
What is causing this exception? Neither queue is declared as exclusive.
(Both code samples have been compressed for brevity.)
Client:
public MyClient(){
try {
ConnectionFactory factory = new() {
HostName = "localhost"
};
IConnection connection = factory.CreateConnection();
channel = connection.CreateModel();
sendUpdatesMessenger = new("sendUpdates", channel);
getUpdatesMessenger = new("getUpdates", channel);
getUpdatesReceiver = new("getUpdates", "localhost", channel);
sendUpdatesReceiver = new("sendUpdates", "localhost", channel);
string bodyStr = "test example string";
string messageId = new Guid().ToString();
bool success = getUpdatesMessenger.SendMessage(messageBody: bodyStr, routingKey: "151", correlationId: messageId, replyQueue: "myReplyQueue");
//Do stuff based on success or failure
} catch (Exception ex){
Debug.WriteLine("Exception " + ex);
}
}
public bool SendMessage(string messageBody, string routingKey, string correlationId, string replyQueue = ""){
try{
IBasicProperties props = channel.CreateBasicProperties();
props.CorrelationId = correlationId;
props.ReplyTo = replyQueue;
channel.QueueDeclare(queue: routingKey, durable: true, exclusive: false, autoDelete: false);
channel.BasicPublish(exchange: exchange, routingKey: routingKey, basicProperties: props, body: Encoding.ASCII.GetBytes(messageBody));
return true;
}
catch(Exception ex){
Debug.WriteLine("Error: " + ex);
return false;
}
}
Server:
public bool BindQueue(string routingKey){
try {
channel.QueueDeclare(queue: routingKey, durable: true, autoDelete: false);
channel.QueueBind(queue: routingKey, routingKey: routingKey, exchange: exchangeName);
queueNames.Add(routingKey);
return true;
}
catch (Exception ex){
Console.WriteLine(ex);
return false;
}
}

How to send acknowledgment (Consumer) in RabbitMQ externally?

I have an application where it sending message to RMQ broker as below:
var connectionFactory = new ConnectionFactory()
{
HostName = "localhost"
};
using (var connection = connectionFactory.CreateConnection())
{
using (var channel = connection.CreateModel())
{
channel.QueueDeclare("demo",
durable:true,
exclusive:false,
autoDelete:false,
arguments:null);
Console.WriteLine("Click enters to send random case Id");
do
{
Console.ReadLine();
var message = new {CaseId = new Random().Next()};
var body = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(message));
channel.BasicPublish("", "demo", null, body);
Console.WriteLine("Successfully send message.");
} while (true);
}
}
It successfully sent the message.
There is another app called the consumer app.
The code is below:
private void InitiateRabbitMq()
{
var connectionFactory = new ConnectionFactory()
{
HostName = "localhost"
};
var connection = connectionFactory.CreateConnection();
var channel = connection.CreateModel();
MessageHandler messageReceiver = new MessageHandler(channel);
channel.BasicConsume("demo", false, messageReceiver);
}
The message handler is:
public class MessageHandler : DefaultBasicConsumer
{
private readonly IModel _channel;
public MessageHandler(IModel channel)
{
_channel = channel;
}
public override async void HandleBasicDeliver(string consumerTag, ulong deliveryTag, bool redelivered, string exchange, string routingKey,
IBasicProperties properties, ReadOnlyMemory<byte> body)
{
var message = JsonConvert.DeserializeObject(Encoding.UTF8.GetString(body.ToArray()));
_processor.process(message);
}
}
Here is the process method parth which is another class:
client.BeginTransfer(transfer,
HandleUploadProgressChanged,
HandleUploadComplete,
HandleUploadInterrupted,
HandleUploadCancelled,
3600,
UploadFilesFinishedCallback);
Once begin transfer completed. It invoked UploadFilesFinishedCallback . I want to acknowledge in this method. How can I do it?
This is how you Acknowledge message:
channel.BasicAck(deliveryTag, false);
So it means your Function "UploadFilesFinishedCallback" must have the deliveryTag
==> and this means that also your "process" function must have the deliveryTag (which currently gets only the message content)
solution:
add new parameter "deliveryTag" to function "process", and to function "UploadFilesFinishedCallback"
you can use it in the callback like this:
client.BeginTransfer(transfer,
HandleUploadProgressChanged,
HandleUploadComplete,
HandleUploadInterrupted,
HandleUploadCancelled,
3600,
() => { UploadFilesFinishedCallback(deliveryTag) });
(depends on the signature of the callback function)

RabbitMQ and SharedQueue closed

Im using RabbitMQ to send simple short int information, first I'm sending id to one project like that:
private void SendPgcIdToRabbitMQ(string id)
{
var factory = new ConnectionFactory() { HostName = "localhost" };
using (var connection = factory.CreateConnection())
{
using (var channel = connection.CreateModel())
{
bool durable = true;
channel.QueueDeclare("XQueue", durable, false, false, null);
var body = Encoding.UTF8.GetBytes(id);
channel.BasicPublish("", "XQueue", null, body);
Console.WriteLine(" [x] Sent {0}", id);
}
}
}
and listener of it:
public void Listener()
{
var factory = new ConnectionFactory() { HostName = "localhost" };
using (var connection = factory.CreateConnection())
{
using (var channel = connection.CreateModel())
{
channel.QueueDeclare("XQueue", true, false, false, null);
var consumer = new QueueingBasicConsumer(channel);
channel.BasicConsume("XQueue", false, consumer);
Console.WriteLine(" [*] Waiting for messages. " +
"To exit press CTRL+C");
while (true) {
var ea =
(BasicDeliverEventArgs)consumer.Queue.Dequeue();
var body = ea.Body;
var message = Encoding.UTF8.GetString(body);
Console.WriteLine(" [x] Received {0}", message);
AddPGCFileID(message);
channel.BasicAck(ea.DeliveryTag, false);
Thread.Sleep(500);
}
}
}
}
It works fine, so after receiving message I'm processing some operation wit it, then I get second ID and create other queue to do this same:
private void SendSurveyIdToRabbitMQ(int yID)
{
var factory = new ConnectionFactory() { HostName = "localhost" };
using (var connection = factory.CreateConnection()) {
using (var channel = connection.CreateModel()) {
bool durable = true;
channel.QueueDeclare("YQueue", durable, false, false, null);
var body = Encoding.UTF8.GetBytes(yID.ToString());
channel.BasicPublish("", "YQueue", null, body);
Console.WriteLine(" [x] Sent {0}", yID);
}
}
}
and receive:
public void InquiryListener()
{
var factory = new ConnectionFactory() { HostName = "localhost" };
using (var connection = factory.CreateConnection()) {
using (var channel = connection.CreateModel()) {
channel.QueueDeclare("YQueue", true, false, false, null);
var consumer = new QueueingBasicConsumer(channel);
channel.BasicConsume("YQueue", false, consumer);
Console.WriteLine(" [*] Waiting for messages. " +
"To exit press CTRL+C");
while (true) {
var ea =
(BasicDeliverEventArgs)consumer.Queue.Dequeue();
var body = ea.Body;
var message = Encoding.UTF8.GetString(body);
Console.WriteLine(" [x] Received {0}", message);
StartProcessing(Convert.ToInt32(message));
channel.BasicAck(ea.DeliveryTag, false);
Thread.Sleep(500);
}
}
}
}
First queue sending and receiving works fine but at second I get:
It is strange because it was working that way, from some time I'm geting this problem. I whas reseting rabbitmq, removin all queues etc. can't find where is a problem. Any ideas?
edit:
I whas debuging to know if second process is ending properly (eariel crash on second proces don't cause problem with rabbitmq) and it passed, I whas supriced because no error ocurs on YQueue, but after about minute of working my process (only waiting, non incomming message, non processing) I gey this same exception on XQueue
Check first if the queue is empty before executing while(true){ ... }.

vsphere api to change ip address of VirtualMachine

I'm new to the vsphere api and I'm trying to change the network settings of a virtual machine from dynamic ip to static ip but I can't find the setting.
Here's the code I have so far, it connects to vsphere, finds the virtual machine, and changes the name of the VM.
I assume there is a setting in the VirtualMachineConfigSpec that will also change the network settings, but I can't find it.
VimClient vimClient = new VimClient();
ServiceContent serviceContent = vimClient.Connect("https://[MY ADDRESS]/sdk");
UserSession us = vimClient.Login("[USERNAME]","[PASSWORD]");
ManagedObjectReference _svcRef = new ManagedObjectReference();
_svcRef.Type = "ServiceInstance";
_svcRef.Value = "ServiceInstance";
NameValueCollection filterForVM = new NameValueCollection();
filterForVM.Add("Name","[VIRTUAL MACHINE NAME]");
VirtualMachine vm = (VirtualMachine)vimClient.FindEntityView(typeof(VirtualMachine),null,filterForVM,null);
VirtualMachineConfigSpec vmConfigSpec = new VirtualMachineConfigSpec();
vmConfigSpec.Name = "[NEW NAME]"; // change the VM name
vmConfigSpec.???? // how to set the ip address
vm.ReconfigVM_Task(vmConfigSpec);
vimClient.Disconnect();
VMware API don’t have a setting to set IP address on virtual machine guest OS, because the IP address setting depends on version guest OS. You could use two ways to do it:
1) You could use GuestOperationsManager from VMware vSphere API to launch the script of IP address setting on guest OS.
Prerequisites:
You should write scripts of IP address setting for each supported OS (Linux, Windows, etc.)
VMware Tools must be installed on each supported VM (for use GuestOperationsManager).
Update2. The following simplified example of running a script on the guest OS. This example does not include error handling, getting logs of the script, VM power on, etc.
using System;
using System.IO;
using System.Net;
using Vim25Api;
namespace RunScriptOnGuestOsTest
{
class Program
{
static void Main(string[] args)
{
var program = new Program();
program.RunScriptInGuestOs(
"https://10.1.1.10/sdk",
"root",
"vmware",
"c:\\temp\\test.bat",
"vm-73",
"Administrator",
"P#ssword",
"c:\\test.bat",
String.Empty);
}
public void RunScriptInGuestOs(string vCenterUrl, string vCenterUserName, string vCenterPassword, string scriptFilePatch, string vmKey, string username, string password, string destinationFilePath, string arguments)
{
var service = CreateVimService(vCenterUrl, 600000, true);
var serviceContent = RetrieveServiceContent(service);
service.Login(serviceContent.sessionManager, vCenterUserName, vCenterPassword, null);
byte[] dataFile;
using (var fileStream = new FileStream(scriptFilePatch, FileMode.Open, FileAccess.Read))
{
dataFile = new byte[fileStream.Length];
fileStream.Read(dataFile, 0, dataFile.Length);
}
FileTransferToGuest(service, vmKey, username, password, destinationFilePath, dataFile);
RunProgramInGuest(service, vmKey, username, password, destinationFilePath, arguments);
}
private static VimService CreateVimService(string url, int serviceTimeout, bool trustAllCertificates)
{
ServicePointManager.ServerCertificateValidationCallback = (sender, certificate, chain, errors) => true;
return new VimService
{
Url = url,
Timeout = serviceTimeout,
CookieContainer = new CookieContainer()
};
}
private ServiceContent RetrieveServiceContent(VimService service)
{
var serviceInstance = new ManagedObjectReference
{
type = "ServiceInstance",
Value = "ServiceInstance"
};
var content = service.RetrieveServiceContent(serviceInstance);
if (content.sessionManager == null)
{
throw new ApplicationException("Session manager is null.");
}
return content;
}
private void FileTransferToGuest(VimService service, string vmKey, string username, string password, string fileName, byte[] fileData)
{
var auth = new NamePasswordAuthentication { username = username, password = password, interactiveSession = false };
var vmRef = new ManagedObjectReference { type = "VirtualMachine", Value = vmKey };
var fileMgr = new ManagedObjectReference { type = "GuestFileManager", Value = "guestOperationsFileManager" };
var posixFileAttributes = new GuestPosixFileAttributes();
posixFileAttributes.ownerId = 1;
posixFileAttributes.groupId = 1;
posixFileAttributes.permissions = (long)0777; //execution file
var requestUrl = service.InitiateFileTransferToGuest(fileMgr, vmRef, auth, fileName, posixFileAttributes, fileData.Length, true);
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(requestUrl);
request.ContentType = "application/octet-stream";
request.Method = "PUT";
request.ContentLength = fileData.Length;
Stream requestStream = request.GetRequestStream();
requestStream.Write(fileData, 0, fileData.Length);
requestStream.Close();
request.GetResponse();
}
private void RunProgramInGuest(VimService service, string vmKey, string username, string password, string programPath, string arguments)
{
var auth = new NamePasswordAuthentication { username = username, password = password, interactiveSession = false };
var vmRef = new ManagedObjectReference { type = "VirtualMachine", Value = vmKey };
var progSpec = new GuestProgramSpec { programPath = programPath, arguments = arguments };
var processMgr = new ManagedObjectReference { type = "GuestProcessManager", Value = "guestOperationsProcessManager" };
var result = service.StartProgramInGuest(processMgr, vmRef, auth, progSpec);
}
}
}
2) You could use DHCP server to manage distributing IP addresses. Using VMware API, you can get MAC address of virtual machine. Next you should set up DHCP server for distributing desired IP addresses on obtained MAC addresses.
Prerequisites:
Each VM must be set up to obtain IP address from DHCP server.
DHCP server which can be configured to fit your needs.

Reading emails from Gmail in C#

I am trying to read emails from Gmail. I have tried every API / open source project I can find, and can not get any of them working.
Does anyone have a sample of working code that will allow me to authenticate and download emails from a Gmail account?
Final working version posted below: https://stackoverflow.com/a/19570553/550198
Using the library from: https://github.com/pmengal/MailSystem.NET
Here is my complete code sample:
Email Repository
using System.Collections.Generic;
using System.Linq;
using ActiveUp.Net.Mail;
namespace GmailReadImapEmail
{
public class MailRepository
{
private Imap4Client client;
public MailRepository(string mailServer, int port, bool ssl, string login, string password)
{
if (ssl)
Client.ConnectSsl(mailServer, port);
else
Client.Connect(mailServer, port);
Client.Login(login, password);
}
public IEnumerable<Message> GetAllMails(string mailBox)
{
return GetMails(mailBox, "ALL").Cast<Message>();
}
public IEnumerable<Message> GetUnreadMails(string mailBox)
{
return GetMails(mailBox, "UNSEEN").Cast<Message>();
}
protected Imap4Client Client
{
get { return client ?? (client = new Imap4Client()); }
}
private MessageCollection GetMails(string mailBox, string searchPhrase)
{
Mailbox mails = Client.SelectMailbox(mailBox);
MessageCollection messages = mails.SearchParse(searchPhrase);
return messages;
}
}
}
Usage
[TestMethod]
public void ReadImap()
{
var mailRepository = new MailRepository(
"imap.gmail.com",
993,
true,
"yourEmailAddress#gmail.com",
"yourPassword"
);
var emailList = mailRepository.GetAllMails("inbox");
foreach (Message email in emailList)
{
Console.WriteLine("<p>{0}: {1}</p><p>{2}</p>", email.From, email.Subject, email.BodyHtml.Text);
if (email.Attachments.Count > 0)
{
foreach (MimePart attachment in email.Attachments)
{
Console.WriteLine("<p>Attachment: {0} {1}</p>", attachment.ContentName, attachment.ContentType.MimeType);
}
}
}
}
Another example, this time using MailKit
public class MailRepository : IMailRepository
{
private readonly string mailServer, login, password;
private readonly int port;
private readonly bool ssl;
public MailRepository(string mailServer, int port, bool ssl, string login, string password)
{
this.mailServer = mailServer;
this.port = port;
this.ssl = ssl;
this.login = login;
this.password = password;
}
public IEnumerable<string> GetUnreadMails()
{
var messages = new List<string>();
using (var client = new ImapClient())
{
client.Connect(mailServer, port, ssl);
// Note: since we don't have an OAuth2 token, disable
// the XOAUTH2 authentication mechanism.
client.AuthenticationMechanisms.Remove("XOAUTH2");
client.Authenticate(login, password);
// The Inbox folder is always available on all IMAP servers...
var inbox = client.Inbox;
inbox.Open(FolderAccess.ReadOnly);
var results = inbox.Search(SearchOptions.All, SearchQuery.Not(SearchQuery.Seen));
foreach (var uniqueId in results.UniqueIds)
{
var message = inbox.GetMessage(uniqueId);
messages.Add(message.HtmlBody);
//Mark message as read
//inbox.AddFlags(uniqueId, MessageFlags.Seen, true);
}
client.Disconnect(true);
}
return messages;
}
public IEnumerable<string> GetAllMails()
{
var messages = new List<string>();
using (var client = new ImapClient())
{
client.Connect(mailServer, port, ssl);
// Note: since we don't have an OAuth2 token, disable
// the XOAUTH2 authentication mechanism.
client.AuthenticationMechanisms.Remove("XOAUTH2");
client.Authenticate(login, password);
// The Inbox folder is always available on all IMAP servers...
var inbox = client.Inbox;
inbox.Open(FolderAccess.ReadOnly);
var results = inbox.Search(SearchOptions.All, SearchQuery.NotSeen);
foreach (var uniqueId in results.UniqueIds)
{
var message = inbox.GetMessage(uniqueId);
messages.Add(message.HtmlBody);
//Mark message as read
//inbox.AddFlags(uniqueId, MessageFlags.Seen, true);
}
client.Disconnect(true);
}
return messages;
}
}
Usage
[Test]
public void GetAllEmails()
{
var mailRepository = new MailRepository("imap.gmail.com", 993, true, "YOUREMAILHERE#gmail.com", "YOURPASSWORDHERE");
var allEmails = mailRepository.GetAllMails();
foreach(var email in allEmails)
{
Console.WriteLine(email);
}
Assert.IsTrue(allEmails.ToList().Any());
}
You don't need any extra 3rd Party Libraries if a summary of the 20 most recent emails is sufficient for you. You can read the data from API that Gmail has provided here: https://mail.google.com/mail/feed/atom
The response in XML format can be handled by the code below:
try {
const string emailAddress = "YourEmail";
// App Password, not password
// See: https://support.google.com/accounts/answer/185833?hl=en
const string appPassword = "YourAppPassword";
string response;
string title;
string summary;
XmlDocument xmlDocument = new XmlDocument();
HttpClient httpClient = new HttpClient();
// Logging in Gmail server to get data
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.UTF8.GetBytes($"{emailAddress}:{appPassword}")));
// Reading data and converting to string
response = await httpClient.GetStringAsync(#"https://mail.google.com/mail/feed/atom");
// Remove XML namespace to simplify parsing/selecting nodes
response = response.Replace(#"<feed version=""0.3"" xmlns=""http://purl.org/atom/ns#"">", #"<feed>");
// Loading into an XML so we can get information easily
xmlDocument.LoadXml(response);
// Amount of emails
string nr = xmlDocument.SelectSingleNode(#"/feed/fullcount").InnerText;
// Reading the title and the summary for every email
foreach (XmlNode node in xmlDocument.SelectNodes(#"/feed/entry")) {
title = node.SelectSingleNode("title").InnerText;
summary = node.SelectSingleNode("summary").InnerText;
Console.WriteLine($"> {title}");
Console.WriteLine($"{summary}");
Console.WriteLine();
}
} catch (Exception ex) {
MessageBox.Show($"Error retrieving mails: {ex.Message}");
}
Have you tried POP3 Email Client with full MIME Support ?
If you don't it's a very good example for you. As an alternativ;
OpenPop.NET
.NET class library in C# for communicating with POP3 servers. Easy to
use but yet powerful. Includes a robust MIME parser backed by several
hundred test cases. For more information, visit our project homepage.
Lumisoft
You can also try Mail.dll IMAP client.
It supports all Gmail IMAP protocol extensions:
Thread ID,
Message ID,
Labels,
Localized folder names,
Google search syntax
OAuth authentication.
Please note that Mail.dll is a commercial product, I've developed.

Categories

Resources