MVC async await for a long-running db insert/update - asp.net-mvc-4

I have some code which imports a csv file and creates database records from the data. Using a standard synchronous approach this works fine, but large csv files can take a while to process. Using async await, I'd like to have the csv processing run asynchronously so the user isn't stuck on the upload form until the processing is complete.
So the user would upload a file and immediately be transferred to a page which would display a message to indicate that the file was importing.
This is what I've tried. This is my controller action that gets the form POST:
public async Task<ActionResult> UploadBulkImportFile(BulkUploadCsvModel model, HttpPostedFileBase file)
{
...
within this I call the functionality that I want to run asynchronously:
await Task.Run(() => testAsyncMethod(csvmodel));
The testAsyncMethod() does all the db crud operations involved in processing the csv.
public void testAsyncMethod(IEnumerable<List<csvmodel>> model)
{
...
This code works but is no different to my previous synchronous code, i.e. the user still has to wait until the file is fully processed, so it's clearly not working properly. Note that the code in testAsyncMethod() is NOT async code, just standard db calls - I'm wondering if that's where I'm going wrong.
Is this even possible in MVC? am I using async await for the wrong reasons?

So the user would upload a file and immediately be transferred to a page which would display a message to indicate that the file was importing.
That's not possible using asynchronous controller actions. This is because (as I describe on my blog) async does not change the HTTP protocol.
In your case, I'd recommend some JavaScript on the client to fire off the POST, change the current page to a "importing..." message, and then update the same page when the POST request completes.
And then remove the async/await/Task.Run from your controller action; it won't provide any benefit.

Related

How to trigger other cancellation tokens?

I have two controllers, one provide a stream to download a file, the other allows the file to be deleted.
When a request to delete the file comes in, I'd like to interrupt the stream download (from disk) so that the file can be deleted.
I have tried combining cancellation tokens and storing them in an IMemoryCache. Whilst I can retrieve the value and call the cancel on the CancellationTokenSource, it does not trigger the cancel on the other controller.
Is there some mechanism by which I can stop the streaming of the file in order to delete it?
The use-case is similar to YouTube in the sense that if the content creator wanted to delete their video, YouTube would let you watch what is already buffered, but not allow you to stream any more of the video once the delete request came in.
Edit: Adding code:
Delete:
var cacheEntryOptions = new MemoryCacheEntryOptions().SetPriority(CacheItemPriority.NeverRemove).SetAbsoluteExpiration(TimeSpan.FromMinutes(30));
this.MemoryCache.Set(fileId, true, cacheEntryOptions);
My thought here is that the delete action will add something to the cache which the stream action can check for.
Stream action:
while (!response.HttpContext.RequestAborted.IsCancellationRequested && bytesRemaining > 0)
{
if (this.MemoryCache.TryGetValue(this.FileId, out bool dummyValue))
{
throw new OperationCanceledException("Operation cancelled remotely");
}
}
With RequestAborted I expect if the client makes their own cancellation request the stream will stop (which it does).
Surrounding the while I have a try/catch:
catch (OperationCanceledException cancelled)
Which is there to dispose of the file accessor.
I tried something similar with CancellationTokens and combining them using a TokenSource, but calling cancel seemed to do nothing at all.
The problem with the current approach is that there appears to be a situation in which the access to the file is not disposed when the delete action is called (even if within that call I sleep for 10 minutes). If I make a subsequent call instead after a minute or so (without the 10 minute delay) then the file can be deleted. It's confusing as to what is holding on to a lock to the file and I feel as though cancellation tokens would solve the problem here...

MVC4 Razor Update View in Real time

I'm a relative noob programmer so apologies in advance!
I am writing using MVC4 and Razor and have a user selection view that can potentially lead to many web calls being made from the controller. I want to update the result view
in real-time as these web service calls return, at the minute though it seems that the controller waits until all the web calls are finished before updating the model
and rendering the result view. We have attempted to use async and await to improve things with only marginal success.
Other complications I have are that we are using an in house framework and performing other operations once the webcall returns e.g. logging \ occasional db access, all of which
lead to the controller action taking a substantial period.
To put it in context this is a monitoring application so depending on the user selections we may have 0 -> several hundred internal web service calls made on the click of a button.
So essentially I am wondering how this is best handled e.g. realtime updating of an MVC4 view where you have multiple individual web service calls, some of them potentially
lengthy e.g. up to 60seconds max.
My thoughts on how to improve things were
use SignalR and at the point where the individual webservices respond, broadcast an update to the result screen ~ however I still have the issue that the code is waiting for
my controller call to the webservices to finish before rendering the view.
to avoid wait on controller method e.g. 'output = await pc.CallApproriateSCs(selectedServiceInfoDetails);' perhaps pass all the calls off to some internal stack and have a timer
pop them and process them on a different thread in another class, this could then free my controller to display a defaulted result view immediately, letting SignalR update it in
real-time
it all seems a bit like using a sledgehammer to crack a walnut though, surely MVC4 has some nicer way of handling this scenario?
Thanks in advance,
N
public virtual async Task<ActionResult> MultipleCommand(ManualSelectionVM model)
{
var viewObject = new ManualSelectionResultVM();
if (model != null)
{
var input = new ServiceInfo();
//Retrieving serviceInfo object details from cache based on user selection, they are unique
List<ServiceInfo> selectedServiceInfoDetails = GetServiceInfoDetailsFromCache(GetSelectedServiceInfoIDs(model));
List<ServiceOutputCdt> output = new List<ServiceOutputCdt>();
IGenericPC pc = null;
try
{
pc = PCFactory.Create<IGenericPC>();
output = await pc.CallApproriateSCs(selectedServiceInfoDetails);
viewObject = ConvertServiceOutputIntoVM(output);
}
finally
{
AICS.ARCHITECTURE.SERVICES.CLEANUP.CleanupSVC.Cleanup(pc);
}
}
return this.View(MVC.ManualSelection.Views.Result, viewObject);
}

Page.RegisterAsyncTask and dependent code?

We are starting to use Page.RegisterAsyncTask() in our ASP.NET webforms project to add some async goodness to our site. We utilize Page_Load event in most of our pages and in our master page (which is in another dll). The problem we are having is the master page uses a session variable that the inherited form populates:
If Not IsPostBack Then
If Session("Id") Is Nothing Then
RegisterAsyncTask(New PageAsyncTask(New Func(Of Task)(Async Function()
Await InitializeSessionVariablesAsync(EntryPointCode.AccountAccess)
End Function)))
End If
In sync mode this code works fine of course because Page_Load for the master page gets called after Page_Load for the inheriting page. When using the code above you end up with errors in the master page when you try to use the session variable because the async task hasn't run yet. I wish there was a way to await the async tasks you registered.
I tried adding ExecuteRegisteredAsyncTasks right after registering the call to RegisterAsyncTask but that didn't help.
WebForms' support for async is a bit awkward. In particular, tasks registered with RegisterAsyncTask will be executed after PreRender, which is too late for Load.
One option may be to change the master code Load handler to a PreRenderComplete handler, which (if I understand correctly) should be raised after the async tasks complete.
Otherwise, you might be able to save the task itself in the session, something like:
If Session("Id") Is Nothing Then
Session("LoadTask") = InitializeSessionVariablesAsync(EntryPointCode.AccountAccess)
End If
then in your master page:
Await Session("LoadTask")
But this would only work if you're using the in-proc session provider; any other provider would require serialization and would break.
So, you may need to just keep this code synchronous for now. ASP.NET MVC has similar issues today (not supporting async filters).

.NET Core alternative to ThreadPool.QueueUserWorkItem

I'm working on implementing the ForgotPassword functionality ie in the AccountController using ASP.NET Identity as in the standard VS 2015 project template.
The problem I'm trying to solve is that when the password reset email is sent, there is a noticeable delay in the page response. If the password recovery attempt does not find an existing account then no email is sent so there is a faster response. So I think this noticeable delay can be used for account enumeration, that is, a hacker could determine that an account exists based on the response time of the forgot password page.
So I want to eliminate this difference in page response time so that there is no way to detect if an account was found.
In the past I've queued potentially slow tasks like sending an email onto a background thread using code like this:
ThreadPool.QueueUserWorkItem(new WaitCallback(AccountNotification.SendPasswordResetLink),
notificationInfo);
But ThreadPool.QueueUserWorkItem does not exist in .NET Core, so I'm in need of some alternative.
I suppose one idea is to introduce an artificial delay in the case where no account is found with Thread.Sleep, but I'd rather find a way to send the email without blocking the UI.
UPDATE: To clarify the problem I'm posting the actual code:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> ForgotPassword(ForgotPasswordViewModel model)
{
if (ModelState.IsValid)
{
var user = await userManager.FindByNameAsync(model.Email);
if (user == null || !(await userManager.IsEmailConfirmedAsync(user)))
{
// Don't reveal that the user does not exist or is not confirmed
return View("ForgotPasswordConfirmation");
}
var code = await userManager.GeneratePasswordResetTokenAsync(user);
var resetUrl = Url.Action("ResetPassword", "Account",
new { userId = user.Id, code = code },
protocol: HttpContext.Request.Scheme);
//there is a noticeable delay in the UI here because we are awaiting
await emailSender.SendPasswordResetEmailAsync(
userManager.Site,
model.Email,
"Reset Password",
resetUrl);
return View("ForgotPasswordConfirmation");
}
// If we got this far, something failed, redisplay form
return View(model);
}
Is there a good way to handle this using other built in framework functionality?
Just don't await the task. That's then mostly-equivalent to running all of that code on the thread-pool to start with, assuming it doesn't internally await anything without calling ConfigureAwait(false). (You'll want to check that, if it's your code.)
You might want to add the task to some set of tasks which should be awaited before the server shuts down, assuming there's some appropriate notion of "requested shutdown" in ASP.NET. That's worth looking into, and would stop the notification from being lost due to unfortunate timing of the server being shut down immediately after sending the response but before sending the notification. It wouldn't help in the case where there are problems in sending the notification though, e.g. your mail server is down. At that point, the user has been told that the email is on its way, before you can really guarantee that... just something to think about.

Async ActionResult implementation is blocking

Okay,
Here I have an MVC 4 application and I am trying to create an Asynchronous ActionResult with in that.
Objective : User has a download PDF Icon on the WebPage, and downloading takes much of time. So while server is busy generating the PDF, the user shall be able to perform some actions in webpage.
(clicking "download PDF" link is sending and ajax request to the server, server is fetching some data and is pushing back the PDF)
What is happening is while I call the ajax to download the PDF it starts the process, but blocks every request until and unless it returns back to the browser. That is simple blocking request.
What I have tried so far.
1) Used AsyncController as a base class of controller.
2) Made the ActionResult to an async Task DownloadPDF(), and here I wrapped the whole code/logic to generate PDF into a wrapper. This wrapper is eventually an awaitable thing inside DownloadPDF()
something like this.
public async Task<ActionResult> DownloadPDF()
{
string filepath = await CreatePDF();
//create a file stream and return it as ActionResult
}
private async Task<string> CreatePDF()
{
// creates the PDF and returns the path as a string
return filePath;
}
YES, the Operations are session based.
Am I missing some thing some where?
Objective : User has a download PDF Icon on the WebPage, and downloading takes much of time. So while server is busy generating the PDF, the user shall be able to perform some actions in webpage.
async will not do this. As I describe in my MSDN article, async yields to the ASP.NET runtime, not the client browser. This only makes sense; async can't change the HTTP protocol (as I mention on my blog).
However, though async cannot do this, AJAX can.
What is happening is while I call the ajax to download the PDF it starts the process, but blocks every request until and unless it returns back to the browser. That is simple blocking request.
AFAIK, the request code you posted is completely asynchronous. It is returning the thread to the ASP.NET thread pool while the PDF is being created. However, there are several other aspects to concurrent requests. In particular, one common hangup is that by default the ASP.NET session state cannot be shared between multiple requests.
1) Used AsyncController as a base class of controller.
This is unnecessary. Modern controllers inspect the return type of their actions to determine whether they are asynchronous.
YES, the Operations are session based.
It sounds to me like the ASP.NET session is what is limiting your requests. See Concurrent Requests and Session State. You'll have to either turn it off or make it read-only in order to have concurrent requests within the same session.