I have a c# WebApi project in which the users can make orders in a website, each order after payment complete will execute a function called ConfirmOrder which will update the order status from STB to COMPLETED.
In the following function that looks like this:
public static void ConfirmOrder(string piva, string orderID, double importo = 0, string transazione = "", string paymentID = "", string tipo = "MENU")
{
string connectionString = getConnectionString(piva);
using var connection = new MySqlConnection(connectionString);
string query_menu = "QUERY";
string query_pagamenti = "QUERY";
using var cmd = new MySqlCommand(query_pagamenti, connection);
connection.Open();
cmd.Parameters.AddWithValue("#tipo", tipo.ToUpper());
cmd.Parameters.AddWithValue("#importo", importo);
cmd.Parameters.AddWithValue("#transazione", transazione);
cmd.Parameters.AddWithValue("#dataOra", DateTime.Now);
cmd.Parameters.AddWithValue("#orderID", orderID);
cmd.Parameters.AddWithValue("#paymentID", paymentID);
cmd.Prepare();
cmd.ExecuteNonQuery();
cmd.CommandText = query_menu;
cmd.ExecuteNonQuery();
if (!tipo.Equals("MENU"))
{
EmailHelper.SendRiepilogo(piva, int.Parse(orderID)); // SENDING SUMMARY MAIL
}
}
I'm calling another function SendRiepilogo which sends a summary to the user and the shop, but in this case i can't wait for that function response but it have to be executed for it's own without stucking ConfirmOrder callback.. so i can't wait for SendRiepilogo to be executed, at this point i've read about IHostingService, but i can't figure out on how i could migrate my SendRiepilogo to a IHostingService and run it from ConfirmOrder...
My SendRiepilogo looks like this:
public static async void SendRiepilogo(string piva, int idOrdine)
{
var order = GetOrdine(piva, idOrdine);
if (order == null)
{
return;
}
try
{
var negozio = getNegozio(order.idNegozio);
var from = new MailAddress("ordini#visualorder.it", "VisualOrder");
var to = new MailAddress(order.cliente.FirstOrDefault().email);
using MemoryStream ms = new MemoryStream();
QRCodeGenerator qrGenerator = new QRCodeGenerator();
QRCodeData qrCodeData = qrGenerator.CreateQrCode("vo/" + idOrdine, QRCodeGenerator.ECCLevel.Q);
Base64QRCode qrCode = new Base64QRCode(qrCodeData);
byte[] byteQr = Convert.FromBase64String(qrCode.GetGraphic(20));
MemoryStream streamQr = new MemoryStream(byteQr);
var qrImage = new LinkedResource(streamQr, MediaTypeNames.Image.Jpeg)
{
ContentId = "qrImage"
};
string nome = order.cliente.FirstOrDefault().nome;
var orderEmail = new { idOrdine, order, nome, negozio };
byte[] byteLogo = Convert.FromBase64String(System.Text.Encoding.UTF8.GetString(negozio.logo));
MemoryStream streamLogo = new MemoryStream(byteLogo);
var logoImage = new LinkedResource(streamLogo, MediaTypeNames.Image.Jpeg)
{
ContentId = "logoImage"
};
string template = File.ReadAllText("Views/Emails/EmailRiepilogo.cshtml");
var htmlBody = Engine.Razor.RunCompile(template, "riepilogo", null, orderEmail);
AlternateView alternateView = AlternateView.CreateAlternateViewFromString(htmlBody, null, MediaTypeNames.Text.Html);
alternateView.LinkedResources.Add(qrImage);
alternateView.LinkedResources.Add(logoImage);
var message = new MailMessage(from, to)
{
Subject = "Riepilogo ordine",
Body = htmlBody
};
message.IsBodyHtml = true;
message.AlternateViews.Add(alternateView);
using var smtp = new SmtpClient("smtps.aruba.it", 587)
{
EnableSsl = true,
Credentials = new NetworkCredential("XXX", "XXX")
};
await smtp.SendMailAsync(message); // sending email to user
await smtp.SendMailAsync(MessageNegozio(order, idOrdine, negozio)); // sending email to shop
}
catch (Exception e)
{
return;
}
ConfirmEmail(piva, idOrdine); // setting "EMAIL SENT" flag in DB to true
return;
}
A background (hosted) service is a completely different service, using its own thread to do its job. You can't have your controller "run" something on that service, you have to tell it what to do, and have it do it.
The Background tasks with hosted services section in the docs shows two different ways a long running background service can work :
A timed service can run each time a timer fires and do a periodic job, as long as the application is running
A queued service waits for messages in a queue and performs a job when a message arrives
Sending an email fits into the second case. You could use the documentation example almost as-is. You can create an IBackgroundTaskQueue interface that clients like your controller can use to submit jobs to run in the background:
public interface IBackgroundTaskQueue
{
void QueueBackgroundWorkItem(Func<CancellationToken, Task> workItem);
Task<Func<CancellationToken, Task>> DequeueAsync(
CancellationToken cancellationToken);
}
This interface can be added as a dependency in your container's constructor.
Assuming the injected service is called myJobQueue, the controller can enqueue a job to run in the background with :
IBackgroundTaskQueue _myJobQueue
public MyController(IBackgroundTaskQueue myJobQueue)
{
_myJobQueue=myJobQueue;
}
public void ConfirmOrder(...)
{
...
if (!tipo.Equals("MENU"))
{
var ordId=int.Parse(orderID);
_myJobQueue.QueueBackgroundWorkItem(ct=>EmailHelper.SendRiepilogoAsync(piva,ordId ));
}
async void should only be used for asynchronous event handlers. That's not what SendRiepilogo is. async void methods can't be awaited, they are essentially fire-and-forget methods that may never run, as the application doesn't know it has to await them. The correct syntax should be :
public static async Task SendRiepilogoAsync(string piva, int idOrdine)
{
...
}
The rest of the documentation example can be used as-is.
Simplifying the service
Instead of a generic queued service that runs any available job, you could create a queue that accepts specific message classes only, only an address and order ID, and have the service do the job of retrieving any data and sending the email. Essentially, SendRiepilogoAsync becomes part of the background service. This allows creating services that could eg batch emails, send several emails concurrently, apply throttling etc.
This would allow reusing expensive resources or perform expensive operations just once, eg create the SmptClient and authenticate before starting to process queue messages
Related
I'm currently working on a Discord bot written in C#. It is supposed to celebrate the birthday of the users in a server, once they have provided their day and month of birth by sending a message (user-birthday pairs are stored inside a local .txt file). A birthday is celebrated by sending an embed in the main channel of the guild (with which it shares the ID parameter) if the date of execution happens to be someone's birthday.
The content of the Ready event handler of the DiscordSocketClient element within the Program class and a partial call stack are shown below:
public DiscordSocketClient Client;
//...
private async Task Client_Ready()
{
await Commands.CelebrateUsers(Client);
}
also
public static async Task CelebrateUsers(DiscordSocketClient client)
{
DateTime now = DateTime.Now;
List<ulong> users = new List<ulong>();
using (StreamReader reader = new StreamReader(#"docs\birthdays.txt"))
{
string date;
ulong user;
while ((date = reader.ReadLine()) != null)
{
user = Convert.ToUInt64(reader.ReadLine());
int[] dm = date.Split(' ').Select(x=>Convert.ToInt32(x)).ToArray();
if(now.Day == dm[0] && now.Month == dm[1])
{
foreach(SocketGuild g in client.Guilds)
{
if(g.Users.FirstOrDefault(x=>x.Id == user) != null)
await CelebrateUser(user, g);
}
}
}
}
}
private static async Task CelebrateUser(ulong id, SocketGuild guild)
{
var embed = new EmbedBuilder()
{
Title = "Auguri ",
Color = Discord.Color.Green,
ThumbnailUrl = "https://c7.uihere.com/files/240/249/186/hot-air-balloon-birthday-balloon.jpg",
ImageUrl = guild.GetUser(id).GetAvatarUrl(size:900),
Footer =
{
IconUrl = "https://cdn.discordapp.com/avatars/406195350903193600/9629b569007720204f7fe775a9be0aeb.png",
Text = "Generato da DrKikkoCeccato++."
},
Timestamp = DateTimeOffset.Now
};
await guild.DefaultChannel.SendMessageAsync("",embed:embed.Build());
}
However, I have an issue with the last function. The last piece of code (the "SendMessageAsync" part) is not even called.
Upon the initialization of the embed, the Log event of the DiscordSocketClient is fired with the following records:
"A Ready handler is blocking the gateway task."
"A Ready handler has thrown an unhandled exception."
The state changes to "Ready" immediately afterwards. Needless to say, my embed is not being sent.
I don't get what the problem might be, since every action is marked as "asynchronous" (in order to avoid problems with the 3-second timeout of the "Ready" event). Could anyone help me?
Thanks in advance.
I found two ways to send messages into service bus topic from azure function.
one is using output -
[FunctionName("ServiceBusOutput")]
[return: ServiceBus("myqueue", Connection = "ServiceBusConnection")]
public static string ServiceBusOutput([HttpTrigger] dynamic input, ILogger log)
{
log.LogInformation($"C# function processed: {input.Text}");
return input.Text;
}
Another is using code -
const string ServiceBusConnectionString = "string";
const string TopicName = "topicName";
static ITopicClient topicClient;
topicClient = new TopicClient(ServiceBusConnectionString, TopicName);
string messageBody = "Test";
var message = new Message(Encoding.UTF8.GetBytes(messageBody));
await topicClient.SendAsync(message);
I'm not getting which one we should use and when?
if we use Output how to pass queue name myqueue as a variable
so that in code we can assign it.
if i have array how can we return one by one message to output
return which will send one by one message to queue ?
Full examples from here. For instance, how to write multiple messages, using the ICollector
public static void Run(TimerInfo myTimer, ILogger log, [ServiceBus("myqueue", Connection = "ServiceBusConnection")] ICollector<string> outputSbQueue)
{
string message = $"Service Bus queue messages created at: {DateTime.Now}";
log.LogInformation(message);
outputSbQueue.Add("1 " + message);
outputSbQueue.Add("2 " + message);
}
As far as I know the first version, using return does not work if you have any async calls inside your Function. The version using the collector can also work in async Functions but simply using an IAsyncCollector instead.
if we use Output how to pass queue name myqueue as a variable
so that in code we can assign it.
For this you can use Imperative Binding.Imperative binding is useful when binding parameters need to be computed at runtime rather than design time. More reference here
Example:
public static async Task ServiceBusBinderTest(
string message,
int numMessages,
Binder binder) {
var attribute = new ServiceBusAttribute(BinderQueueName) {
EntityType = EntityType.Queue
};
var collector = await binder.BindAsync < IAsyncCollector < string >> (attribute);
for (int i = 0; i < numMessages; i++) {
await collector.AddAsync(message + i);
}
await collector.FlushAsync();
}
if i have array how can we return one by one message to output return which will send one by one message to queue ?
You can configure the OnMessageOptions instance with decreasing your MaxConcurrentCalls
OnMessageOptions options = new OnMessageOptions();
options.AutoComplete = false;
options.MaxConcurrentCalls = 5;
I'm trying to subscribe to real-time updates with Cloud Firestore in c# using Google.Cloud.Firestore.V1Beta1. I'm using the following code, which receives updates for a short time, until the stream is closed. Has anyone got FirestoreClient.Listen to work?
// Create client
FirestoreClient firestoreClient = FirestoreClient.Create();
// Initialize streaming call, retrieving the stream object
FirestoreClient.ListenStream duplexStream = firestoreClient.Listen();
// Create task to do something with responses from server
Task responseHandlerTask = Task.Run(async () =>
{
IAsyncEnumerator<ListenResponse> responseStream = duplexStream.ResponseStream;
while (await responseStream.MoveNext())
{
ListenResponse response = responseStream.Current;
Console.WriteLine(response);
}
});
// Send requests to the server
var citiesPath = string.Format("projects/{0}/databases/{1}/documents/cities/CJThcwCipOtIEAm2tEMY", projectId, databaseId);
// Initialize a request
var dt = new DocumentsTarget { };
dt.Documents.Add(citiesPath);
ListenRequest request = new ListenRequest
{
Database = new DatabaseRootName(projectId, databaseId).ToString(),
AddTarget = new Target
{
Documents = dt
}
};
// Stream a request to the server
await duplexStream.WriteAsync(request);
// Await the response handler.
// This will complete once all server responses have been processed.
Console.WriteLine("Awaiting responseHandlerTask");
await responseHandlerTask;
Edit 1:
I've tried setting the expiration explicitly to never expire, but still no luck, I get 5 minutes in then receive a RST_STREAM.
//Setup no expiration for the listen
CallSettings listenSettings = CallSettings.FromCallTiming(CallTiming.FromExpiration(Expiration.None));
// Initialize streaming call, retrieving the stream object
FirestoreClient.ListenStream duplexStream = firestoreClient.Listen(listenSettings);
Edit 2:
It seems like a bit of a kludge, but I found it works to keep track of the last resetToken, catch the exception, then restart the request with the request token. I've updated the code that makes the original request to take an optional resumeToken.
ListenRequest request = new ListenRequest
{
Database = new DatabaseRootName(projectId, databaseId).ToString(),
AddTarget = new Target
{
Documents = dt
}
};
if (resumeToken != null)
{
Console.WriteLine(string.Format("Resuming a listen with token {0}", resumeToken.ToBase64()));
request.AddTarget.ResumeToken = resumeToken;
}
// Stream a request to the server
await duplexStream.WriteAsync(request);
It's not perfect, but I think it's the way Google implemented it in Node.js. It does result in an API call every 5 minutes, so there is some expense to it. Maybe that's the why it works this way?
Thanks
Until Jon finishes the official support, you can use something I put together if you need it right away. https://github.com/cleversolutions/FirebaseDotNetRamblings/blob/master/FirebaseDocumentListener.cs Its an extension method you can drop into your project and use like this:
//Create our database connection
FirestoreDb db = FirestoreDb.Create(projectId);
//Create a query
CollectionReference collection = db.Collection("cities");
Query qref = collection.Where("Capital", QueryOperator.Equal, true);
//Listen to realtime updates
FirebaseDocumentListener listener = qref.AddSnapshotListener();
//Listen to document changes
listener.DocumentChanged += (obj, e) =>
{
var city = e.DocumentSnapshot.Deserialize<City>();
Console.WriteLine(string.Format("City {0} Changed/Added with pop {1}", city.Name, city.Population));
};
I am developing a chatbot using the Microsoft bot framework in C#. We have a functionality where it queries the database and returns the result, but it might take up to 25-30 secs for the result to return.
By that time bot says "cannot send,please retry". Is there a way to increase this timeout? Or can we have something like "please wait" message for the user so that user will know that the request is processing?
It's hard coded in SDK, we're not able to override the message like "Couldn't send, retry". As Nicolas said, a workaround is to send a proactive message to user.
For example you can firstly create a ConversationStarter.cs class like this:
public class ConversationStarter
{
//Note: Of course you don't want these here. Eventually you will need to save these in some table
//Having them here as static variables means we can only remember one user :)
public static string fromId;
public static string fromName;
public static string toId;
public static string toName;
public static string serviceUrl;
public static string channelId;
public static string conversationId;
//This will send an adhoc message to the user
public static async Task Resume(string conversationId, string channelId)
{
var userAccount = new ChannelAccount(toId, toName);
var botAccount = new ChannelAccount(fromId, fromName);
var connector = new ConnectorClient(new Uri(serviceUrl));
IMessageActivity message = Activity.CreateMessageActivity();
if (!string.IsNullOrEmpty(conversationId) && !string.IsNullOrEmpty(channelId))
{
message.ChannelId = channelId;
}
else
{
conversationId = (await connector.Conversations.CreateDirectConversationAsync(botAccount, userAccount)).Id;
}
message.From = botAccount;
message.Recipient = userAccount;
message.Conversation = new ConversationAccount(id: conversationId);
message.Text = "Hello, work is done!";
message.Locale = "en-Us";
await connector.Conversations.SendToConversationAsync((Activity)message);
}
}
Then in your dialog, you can code like this:
public async Task MessageReceivedAsync(IDialogContext context, IAwaitable<IMessageActivity> result)
{
var message = await result;
//We need to keep this data so we know who to send the message to. Assume this would be stored somewhere, e.g. an Azure Table
ConversationStarter.toId = message.From.Id;
ConversationStarter.toName = message.From.Name;
ConversationStarter.fromId = message.Recipient.Id;
ConversationStarter.fromName = message.Recipient.Name;
ConversationStarter.serviceUrl = message.ServiceUrl;
ConversationStarter.channelId = message.ChannelId;
ConversationStarter.conversationId = message.Conversation.Id;
await context.PostAsync("Please wait, we're processing...");
Processing();
}
public async Task Processing()
{
//replace the task.delay() method with your task.
await Task.Delay(30000).ContinueWith((t) =>
{
ConversationStarter.Resume(ConversationStarter.conversationId, ConversationStarter.channelId);
});
}
Then Task.Delay(30000) method is used for a 30s task testing, you should be able to replace it with your task for retrieving data from your database.
You should do the following:
acknowledge the request of the user with a basic text reply
save the message information and process the request
make a proactive message once you got the reply of your system
I have a SignalR application in two parts. I have basic messaging going back and forth between an MVC app (SignalR Server) and the Windows Service (SignalR Client). The Windows Service is on a back-end machine that has the AdventureWork2012 database on it. Management desires that the Windows Service - SignalR client query the database and send back a DataSet via SignalR. Can SignalR pass DataSets? I am getting an exception "Invalid URI: The Uri string is too long." in the event log when I call the server method.
Here is the MVC Server Hub:
public class AlphaHub : Hub
{
public void Hello(string message)
{
// We got the string from the Windows Service
// using SignalR. Now need to send to the clients
Clients.All.addNewMessageToPage(message);
// Call Windows Service
string message1 = System.Environment.MachineName;
Clients.All.Notify(message1);
}
public void Register(string registrationID,string connectionID)
{
// We got the string from the Windows Service
// using SignalR. Now need to send to the clients
System.Web.HttpContext.Current.Application["registrationID"] = registrationID + "|" + connectionID;
}
public void RecieveDataSet(DataSet ds)
{
Clients.All.addNewMessageToPage("Data Set recieved");
}
Here is the Windows Service Code that throws an exception on the line:
await alphaProxy.Invoke("RecieveDataSet", employees);
The code is:
protected override async void OnStart(string[] args)
{
eventLog1.WriteEntry("In OnStart");
try
{
var hubConnection = new HubConnection("http://www.someurl.com/signalr", useDefaultUrl: false);
IHubProxy alphaProxy = hubConnection.CreateHubProxy("AlphaHub");
await hubConnection.Start();
string cid = hubConnection.ConnectionId.ToString();
eventLog1.WriteEntry("ConnectionID: " + cid);
// Invoke method on hub
await alphaProxy.Invoke("Hello", "Message from Service - ConnectionID: " + cid + " - " + System.Environment.MachineName.ToString() + " " + DateTime.Now.ToString());
await
alphaProxy.Invoke("Register", "81577f58-0e05-43f4-b322-fbf0d9d1e79e",
hubConnection.ConnectionId.ToString());
alphaProxy.On("addNewMessageToPage", () => eventLog1.WriteEntry("Notified!"));
DataSet employees = new DataSet();
string connString = "...";
SqlConnection conn = new SqlConnection(connString);
string queryString = "SELECT * FROM [HumanResources].[Employee]";
SqlDataAdapter adapter = new SqlDataAdapter(queryString, conn);
adapter.Fill(employees, "HumanResources.Employee");
await alphaProxy.Invoke("RecieveDataSet", employees);
}
catch (Exception ex)
{
eventLog1.WriteEntry(ex.Message);
}
}
Is it possible to pass a DataSet? If not, do I have to serialize/deserialize? If so, can you show the code to do that?
You want to keep your messages pretty lightweight with SignalR for many reasons (especially with scale-out via backplanes), so I would NOT pass something big like a DataSet via a SignalR message personally. What I usually suggest for larger payloads is that you send a specific message that tells the browser it should update that data which it then does by downloading using a traditional GET to some kind of REST based service. This way you have real-time notifications of updates, but you're using traditional HTTP GET to transfer the actual payload.
I recommend you convert your dataset into a Data Transfer Object and pass that. With SignalR, you could pass each object as it is hydrated allowing you to notify observers not only when you fetch, but also whenever any new item is added as well. I haven't tested this code, so I may well have missed something, but it should give you the idea.
adapter.Fill(employees, "HumanResources.Employee");
foreach (var dr in employees)
{
var empDto = new EmployeeDto
{
FirstName = dr.Fields("FirstName"),
LastName = dr.Fields("LastName"),
// Additional fields go here...
};
alphaProxy.Clients.ReceiveDataSet(empDto); // Serializes the dto as json by default.
}
Of course, if you want to send the full list in the response you should be able to modify this slightly:
adapter.Fill(employees, "HumanResources.Employee");
var items = new List<EmployeeDto>();
foreach (var dr in employees)
{
var empDto = new EmployeeDto
{
FirstName = dr.Fields("FirstName"),
LastName = dr.Fields("LastName"),
// Additional fields go here...
};
items.Add(empDto);
}
alphaProxy.Clients.ReceiveDataSet(items); // Serializes the dto as json array by default.
http://www.asp.net/signalr/overview/signalr-20/hubs-api/hubs-api-guide-net-client#clientmethodswithoutparms
See the guide of passing methods with and without params.