How to wait after a database value then continue - c#

I got an e-commerce website that uses the PayPal Adaptive Payment. The Adaptive Payment requests a PayKey to be generated from PayPal to create an invoice. The delay to get the PayKey is long enough so I had the idea to put the code in a separate thread, while the user answer some other questions before being redirected to PayPal, see code below:
await Task.Run<Task>(async () =>
{
var payResponse = await _payPalApplicationService.ProceedWithPayPal(currentEvent.Id, order.InvoiceId, order.TrackingId, owners.Single(), vModel.TotalPrice, vModel.DeliveryPriceTotal, orderToAdd.TotalTaxes, orderToAdd.SalesRate + orderToAdd.SalesRateTaxes, vModel.SKUViewModels, _payPalApplicationService.PayPalCore._serviceEndPointUrl);
order.PayKey = payResponse.payKey;
_orderService.Update(order);
await _unitOfWorkAsync.SaveChangesAsync();
});
The problem I got is some users can go quick enough so the PayKey has not been generated before being redirected to PayPal.
Did you know anything I can do to make sure I got the PayKey before redirect the users to PayPal? The thread task is done in a different controller action than the one with the redirection.
Thank you
David

It arguably violates an MVC principle of statelessness but a really simple solution would be to store the PayKey retrieval task against the session.
So change the above code to:
Session["PaypalTask"] = await Task.Run<Task>(async () =>
{
var payResponse = await _payPalApplicationService.ProceedWithPayPal(currentEvent.Id, order.InvoiceId, order.TrackingId, owners.Single(), vModel.TotalPrice, vModel.DeliveryPriceTotal, orderToAdd.TotalTaxes, orderToAdd.SalesRate + orderToAdd.SalesRateTaxes, vModel.SKUViewModels, _payPalApplicationService.PayPalCore._serviceEndPointUrl);
order.PayKey = payResponse.payKey;
_orderService.Update(order);
await _unitOfWorkAsync.SaveChangesAsync();
});
Then you can later retrieve the task in your other controller and await it there. If it has already completed exectution will just continue immeadiately, else it will wait for it to complete before continuing.
Something like this
public async Task<ActionResult> Index()
{
var payPalTask = Session["PaypalTask"] as Task;
await payPalTask;
return RedirectToAction("CompltedPayment");
}
Of course, you may also want to consider error handling etc.
UPDATE
I should mention that the above method stores session state in memory. There are some complications if your application uses multiple servers where you may want to research sticky sessions, or some kind of distributed cache like redis.
UPDATE2:
I have published a demo mocking out the async method with a simple Task.Delay to here. If you build this web app then navigate to /InitiatePp it will start the request. Then /PpResult/Result will give the status of the running task and /PpResult/Wait will await completion of the task.

Related

Write to database and return at the same time

I have a situation where I wish to return data or redirect the user elsewhere, and also log information to a database at the same time.
My current code is similar to the following:
public async Task<IActionResult> SendUser(string target)
{
Target dbTarget = await DbContext.Targets.SingleOrDefaultAsync(target => target.url == target);
dbTarget.LastAccess = DateTime.Now();
dbTarget.TimesAccessed = dbTarget.TimesAccessed++;
DbContext.Targets.Update(dbTarget);
DbContext.SaveChangesAsync();
Redirect(target);
}
I would like this to Redirect the user, but log to the database seperately, without the users request having for the database update to complete.
One solution is to create a seperate method in the controller and just POST the new log item to it, which would cause a seperate request, however I wonder if there is a better method that does not require adding extra authentication steps and methods?
Try instead of DbContext.SaveChangesAsync(); await DbContext.SaveChangesAsync(); or just DbContext.SaveChanges(); Redirect will wait till all changes are saved.

How to wait for another service to finish a task in the background without awaiting?

I'm creating an ASP.NET Core WebApi, which uses the Autodesk Model Derivative API to convert a Revit file into another file format. After I've uploaded the file, the Autodesk API starts working in the background and can take several minutes to finish its work.
I want to monitor the status of the Autodesk API, to know if the conversion has been finished yet and notify the user. I'm looking for the best way to monitor the status of the job without 'awaiting' and letting the request hang stuck for several minutes.
I've tried just running the task asynchronously without awaiting the result. This worked, up to the point where I wanted to update a value in my Database Context, because that had been disposed due to the request having ended.
I've also researched several options on implementing background services, but haven't found a clear way to do that.
public async Task<ActionResult<Response<JobResponse>>> UploadFile(string bucketKey, IFormFile file)
{
// ....
// File has been uploaded
Response<JobResponse> response
= await NetworkManager.PostAsync<JobResponse>(URI.Job.Url, JsonConvert.SerializeObject(jobData));
// The job has been created in the Autodesk API, so I create a record in my own database
var job = new Job(urn, file.FileName);
context.Jobs.Add(job);
await context.SaveChangesAsync();
// This method is what I want to do in the background
MonitorStatus(job);
return Respond(response);
}
private async Task MonitorStatus(Job job)
{
bool isDone = false;
while (!isDone)
{
isDone = await IsDone(job.Urn);
if (!isDone)
await Task.Delay(10000);
}
string guid = await new JobRepository(job).GetGuid();
// The line underneath throws an error because the context has been disposed
(await context.Jobs.FindAsync(job.Id)).Finish(guid);
await context.SaveChangesAsync();
// ...
// Notify the user
}
Translation of files in Model Derivative API boils down to two main endpoints:
POST job for triggering the translation, and
GET manifest for getting the manifest of the translation (incl. its status while the translation is still running)
If you're making the HTTP requests yourself, you can just poll the manifest until you see that the translation has been completed.
If you're using the Forge .NET SDK, you can trigger the translation using the Translate method, and poll the results using the GetManifest method.
I ended up using a Webhook of Autodesk Forge, which calls an endpoint that notifies the user that the conversion has been completed. This webhook contains a body with information about which job has been completed, so I can update the database accordingly.
This webhook removes the need for my MonitorStatus(job) method.
https://forge.autodesk.com/en/docs/webhooks/v1/tutorials/create-a-hook-model-derivative/

Send email async: An asynchronous module or handler completed while an asynchronous operation was still pending

I'm trying to send email from a Controller asynchronous and getting the following error:
I don't want to wait email be sent to complete the action.
An asynchronous module or handler completed while an asynchronous
operation was still pending.
This is my implementation: I also tried using void instead async Task
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
var user = await _userManager.FindByNameAsync(model.Email);
if (user != null)
{
if (!await _userManager.IsEmailConfirmedAsync(user.Id))
{
//dont want to await result
//just send email
_mailService.SendAccountConfirmationEmailAsync(user);
...
}
...
}
return View();
}
.
public async Task SendAccountConfirmationEmailAsync(ApplicationUser user)
{
dynamic email = GetAccountConfirmationEmailTemplate();
email.Username = user.Email;
email.Name = user.UserName;
email.To = user.Email;
email.AccountConfirmationUrl = await GetAccountConfirmationUrl(user);
await _mailService.SendAsync(email);
}
What am I missing?
Since the runtime can recycle your appdomain when it knows there are no more pending requests, starting tasks that you don't wait for is specifically not recommended, hence that message.
In particular, without this check you would have no guarantee this email would ever be sent since the entire appdomain could be yanked down around that task after your controller action returns.
Now in your case you might say that the loss of the email is acceptable, but the runtime doesn't know that it is only an email, it could very well be your database call that you've forgotten to wait for that would be prematurely aborted. Hence it just informs you that this is bad form.
Instead you got two options:
Wait for that task to complete, which you have specifically said you don't want to do
Inform the runtime that you're firing off a task that you specifically don't want to wait for, but please oh please, don't yank out the appdomain before task has completed
This last part is what HostingEnvironment.QueueBackgroundWorkItem is for.
In your case you would call it like this:
HostingEnvironment.QueueBackgroundWorkItem(ct =>
_mailService.SendAccountConfirmationEmailAsync(user));
Additionally, the method provides a CancellationToken, you should check if there is an overload of SendAccountConfirmationEmailAsync that accepts it. If not, and if this is your API, you should consider adding support for such tokens.
Please be aware that this mechanism is not meant for long-running threads or tasks, for that there are other more appropriate trees to bark up, but in this case it should be enough.
You need to await your async call in order to avoid the error message, but then you'll need to wait for the email to finish sending before your action method returns (and a failure sending the email will cause your action method to error out).
await _mailService.SendAccountConfirmationEmailAsync(user);
If you ever see a method name that ends in -Async, check if that method returns a Task or Task. If it does, you need to await it.
If you have an async call that you don't want to wait for it to complete in this context, you'll get that error. If you want to continue processing, you'll need some method of sending this email in a background task. That might be that you have a separate application that handles emails, and you communicate with it via a messaging system like RabbitMQ. Or there are ways to run code in the background directly in a website such as Hangfire.

Calling method async from WCF service and return immediately

A third party is calling our WCF service. The caller wants confirmation, that the sent records have been received and stored, within a small timeframe.
The records that are stored need some lenghty processing. Can the processing be executed async, right after storing the records, so the confirmation can be send immediately?
Ofcourse there can be a separate process that does the processing, but the question is whether I can combine storage and processing without timing out.
Update:
It looks like this works:
var aTask = new Task(myService.TheMethod);
aTask.Start();
return aVariableAsync;
Or is this a very bad idea to do from within my WCF host, because.. ?
You can set "AsyncPattern" to true on the OperationContract attribute as described on MSDN.
You can then control the concurrency using the following attribute on the service method:
[ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Multiple)]
Yes it can be done. I dont have a ton of experience with it, but this is a snippet of some code showing that. The service calls this method on the controller that saves an xml message to the hard drive and then kicks off a separate task to process it into MongoDB and returns a message back to the service that it was successfully saved.
public string SaveTransaction(XElement pTransactionXml, string pSavePath)
{
//save the transaction to the drive locally
pTransactionXml.Save(pSavePath);
...
var mongoTask = Task.Run(async () =>
{
await SendXMLFilesToMongo(pSavePath);
});
return response.WithResult("Successfully saved to disk.");
}
public virtual async Task<int> SendXMLFilesToMongo(string pSavePath)
{
//call the code to save to mongo and do additional processing
}

How to make controller action truly async

I have the following controller:
public class PingController : ApiController
{
[Route("api/ping")]
[HttpGet]
public IHttpActionResult Ping()
{
var log = HostLogger.Get(typeof(PingController));
log.Info("Ping called.");
return Ok("Ping succeeded # " + DateTime.UtcNow);
}
[Route("api/long-ping")]
[HttpGet]
public async Task<IHttpActionResult> LongPing(CancellationToken cancelToken)
{
await Task.Delay(30 * 1000);
return Ok("Ping succeeded # " + DateTime.UtcNow);
}
}
If I execute LongPing, followed by Ping in different browser tabs, the Ping will execute and return before LongPing does -- which is exactly what I'm looking for. The problem is when I execute two LongPing calls the second one takes around 60s to complete (not 30 seconds). Chrome reports the second call has a latency of 58s (60s minus the time it took me to start the second request). It seems to me that both LongPing calls should execute in around 30s if I had this working correctly.
I also should mention that I'm hosting this in an OWIN hosting environment, not IIS. But I didn't think that made any difference but maybe someone will prove me wrong.
How do I make LongPing behave truly like an async request?
It's quite likely that your session state is causing your problems. There's a long-winded explanation for this behaviour, but the short version is that a particular user session can only do one request at a time because the session state locks to ensure consistent state. If you want to speed this up, disable your cookies to test the session state hypothesis (you'll get 1 session state per request that way), or disable session state in the application. Your code is otherwise a-ok async wise.
It turns out this is Chrome's behavior when calling the same URL. I always forget this when testing with Chrome. Normally I test with Fiddler, but this VM doesn't have Fiddler.
See this SO Q&A:
Chrome treating smart url and causing concurrent requests pend for each other

Categories