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));
};
Related
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
Am trying to connect to an API I built with the client version, running the client app with break points I can see that the data I pass in(signing up) gets passed down but when it get to the the PostAsync method that sends the data to the api the application breaks and I get 'system-net-webexception-failed-to-connect-to-localhost-127-0-0-1-44391' and an accompanying error that reads 'The selected debug engine does not support any code executing on the current thread (e.g. only native runtime code is executing)', there's no red highlight to indicate where the error is which makes this a real head scratcher for me.....
Some insight on the issue will be greatly appreciated.
Sub.Clicked += async (object sender, EventArgs e) =>
{
if (!Conection.IsConnected)
{
await DisplayAlert("No Connection", "Please turn on or reset your data connection", "Cancle");
}
var entirs = new string[] { Email.Text, Password.Text, FN.Text, LN.Text, Phone.Text};
var jcon = JsonConvert.SerializeObject(entirs);
var contain = new StringContent(jcon, Encoding.UTF8, "aplication/json");
message.Timeout = new TimeSpan(0, 0, 100);
var outcome = await message.PostAsync("https://localhost:5001/api/v1/identity/Register", contain);
if (outcome.StatusCode==HttpStatusCode.Created)
{
await DisplayAlert("Success", "Entries have been loaded", "X");
Application.Current.MainPage = new NavigationPage(new Selection());
}
else
{
await DisplayAlert("Failed", outcome.StatusCode.ToString(), "cancel");
};
};
I am actually trying to send a queue message on Artemis using C#.
An instance is being created but not as I would like and when I check the queue message, it says 0. Also I want to add an expiry date and set the Routing_Type to ANYCAST, but cannot see where to specify those details.
Find below my actual code, where I tried setting an expiry date of 5minutes;
private static void SendMsgAtemis()
{
string address = "amqp://tst01sacmamq.corporate.intra:61616";
Connection connection = new Connection(new Address(address));
Session session = new Session(connection);
SenderLink sender = new SenderLink(session, "test-sender", "Nad-Test");
Message message1 = new Message("Hello AMQP!");
TimeSpan ts = new TimeSpan(0, 5, 0);
sender.Send(message1, ts);
Console.WriteLine("Message sent into queue Nad-Test");
}
When running the above, the instance is being created as shown, but as you can see, the Queue message is indicating 0;
Can anyone advise on the above?
Nad-Test has been created but no Queue Count and it is in MultiCast mode.
With .NET Client for ActiveMQ Artemis, it should be as simple as that:
var connectionFactory = new ConnectionFactory();
var endpoint = Endpoint.Create("tst01sacmamq.corporate.intra", 61616);
var connection = await connectionFactory.CreateAsync(endpoint);
var producer = await connection.CreateProducerAsync("Nad-Test", RoutingType.Anycast);
var msg = new Message("Hello AMQP!")
{
TimeToLive = TimeSpan.FromMinutes(5)
};
await producer.SendAsync(msg);
This Client is based on AmqpNetLite but it handles all the ActiveMQ Artemis minutiae for you.
https://www.nuget.org/packages/ArtemisNetClient
https://havret.github.io/dotnet-activemq-artemis-client/
I'm trying to communicate between my host app and service via AppServiceConnection. I'm using the following code in my host app:
using (var connection = new AppServiceConnection())
{
connection.AppServiceName = extension.AppServiceName;
connection.PackageFamilyName = extension.PackageFamilyName;
var connectionStatus = await connection.OpenAsync();
if (connectionStatus == AppServiceConnectionStatus.Success)
{
var response = await connection.SendMessageAsync(requestMessage);
if (response.Status == AppServiceResponseStatus.Success)
returnValue = response.Message as ValueSet;
}
}
And my service code:
private async void OnRequestReceived(AppServiceConnection sender, AppServiceRequestReceivedEventArgs args)
{
var messageDeferral = args.GetDeferral();
var message = args.Request.Message;
var returnData = new ValueSet();
var command = message["Command"] as string;
switch (command)
{
case "ACTION":
var value = await AsyncAction();
returnData = new ValueSet { { "Value", JsonConvert.SerializeObject(value) } };
break;
default:
break;
}
await args.Request.SendResponseAsync(returnData);
messageDeferral.Complete();
}
This works some of the time, but other times the ValueSet (returnValue) is randomly empty when the host receives it. It has Value in it when returned in the service, but when I get the response in the host, nothing.
I've verified that the service is indeed setting the value, adding it to the ValueSet and returning it correctly.
Note that my service is receiving the request, the host is receiving the response and the response status is Success; a failed connection isn't the issue.
Sometimes this happens only once before requests start working again, other times it will happen ten times in a row.
The first working response after a failure always takes significantly longer than normal.
Also, I have no issues in the request from host to service. It's always service to host where the problem shows up.
Has anyone else run into this issue and figured it out?
In the process of creating a sample app I realized what the problem was. I was performing an asynchronous action (albeit a very short one) before my line var messageDeferral = args.GetDeferral();. It appears that this was allowing the background task to be closed before it had responded to the host. Simply moving that line to the beginning of the OnRequestReceived function fixed the problem for me.
So for anyone who runs into a similar issue, get your deferral before you do anything else! Spare yourself the pain I went through.
I'm streaming data into BQ with .NET API. And I noticed in Process Explorer that new TCP/IP connections are created and ended over and over again. I'm wondering if it's possible to reuse the connection and avoid big overhead of connection creation and end?
public async Task InsertAsync(BaseBigQueryTable table, IList<IDictionary<string, object>> rowList, GetBqInsertIdFunction getInsert,CancellationToken ct)
{
if (rowList.Count == 0)
{
return;
}
string tableId = table.TableId;
IList<TableDataInsertAllRequest.RowsData> requestRows = rowList.Select(row => new TableDataInsertAllRequest.RowsData {Json = row,InsertId = getInsert(row)}).ToList();
TableDataInsertAllRequest request = new TableDataInsertAllRequest { Rows = requestRows };
bool needCreateTable = false;
BigqueryService bqService = null;
try
{
bqService = GetBigQueryService();
TableDataInsertAllResponse response =
await
bqService.Tabledata.InsertAll(request, _account.ProjectId, table.DataSetId, tableId)
.ExecuteAsync(ct);
IList<TableDataInsertAllResponse.InsertErrorsData> insertErrors = response.InsertErrors;
if (insertErrors != null && insertErrors.Count > 0)
{
//handling errors, removed for easier reading..
}
}catch{
//... removed for easier reading
}
finally
{
if (bqService != null)
bqService.Dispose();
}
}
private BigqueryService GetBigQueryService()
{
return new BigqueryService(new BaseClientService.Initializer
{
HttpClientInitializer = _credential,
ApplicationName = _applicationName,
});
}
** Follow up **
The answer given below seems to be the only solution to reduce http connections. however, I found using batch request on large mount of live data streaming could have some limitation. see my another questions on this: Google API BatchRequest: An established connection was aborted by the software in your host machine
Below link documents how to batch API calls together to reduce the number of HTTP connections your client has to make
https://cloud.google.com/bigquery/batch
After batch request is issued, you can get response and parse out all involved jobids. As an alternative you can preset jobids in batch request for each and every inner request. Note: you need to make sure those jobids are unique
After that you can check what is going on with each of these jobs via jobs.get https://cloud.google.com/bigquery/docs/reference/v2/jobs/get