Async progress in ASP .NET Core middleware? - c#

I have a web-api with mvc that's doing a bunch of startup initialization which will take a few minutes. I want the url to respond to a request during this time with a progress-indicator. My thinking was to use a middleware to accomplish this something like this:
public async Task Invoke(HttpContext httpContext)
{
await httpContext.Response.WriteAsync("Loading...");
await Task.Delay(5000); // the initialization-stuff (which is not started here but just waited on)
httpContext.Response.Clear();
await _next(httpContext); // continue to my MVC-page
}
However this does not seem to work (ERR_INCOMPLETE_CHUNKED_ENCODING). How do I properly clear/reset the respons so that I can write a new real response once the initialization is done.
I resorted to something like this instead (good enough):
public async Task Invoke(HttpContext httpContext)
{
if (!task.IsCompleted)
await httpContext.Response.WriteAsync("Loading...");
else
await _next(httpContext); // continue...
}

Once you have sent data to the client you can't take it back. You can't replace an existing page, only append.
You can therefore do two things:
You delay the response until initialization is complete (likely not feasible).
You send something else and end the request. You could make the page that you are sending poll the server using AJAX to see if initialization has been completed. Then, the page can reload itself.
Create a new API endpoint that replies with the initialization status. Make page page poll that endpoint every few seconds.

Related

Async Await control flow in Web API

webcontroller {
async Task<ActionResult<string>> doSomething() {
var stringResult = await doSomethingAsync();
return stringResult;
}
}
what will be control flow here? will the controller return dummy response (ActionResult) to client after reaching doSomething() method call or the control remain in the web controller and return the stringResult to client? consider doSomething() is doing some network intensive tasks which might take more time to complete. Can anyone please explain the same to me if possible? Thanks in Advance!
will the controller return dummy response (ActionResult) to client
after reaching doSomething() method call or the control remain in the web controller and return the stringResult to client
It will not return anything to the client until doSomething method finished.
consider doSomething() is doing some network intensive tasks which
might take more time to complete
In this case you will have timeout on the client.
You have to start background job. Return to the client that task has been started. Then tell somehow to the client that task is finished.
Another source of information: Long running task in WebAPI
I recommend reading an article I wrote about how async works on ASP.NET.
will the controller return dummy response (ActionResult) to client after reaching doSomething() method call or the control remain in the web controller and return the stringResult to client?
When doSomethingAsync returns an incomplete task, then the await in doSomething will also return an incomplete task. Then the ASP.NET runtime (asynchronously) waits for that task to complete before sending the response.
await in ASP.NET yields to the thread pool; it does not yield to the client.

await blocks the request

I have an action in my controller in which there is an async function call
public async Task<ActionResult> Subscribe(AspNetUser user, string newUploadPath)
{
//do some work
await DocServiceImpl.CopyUserAllModels(user, newUploadPath);
//do some work
return RedirectToAction("List", "ClientDashboard");
}
and this function uploads a lot of files to Azure CDN so it takes too long.
The problem is that the client who makes this request has to wait until the CopyUserAllModels function finishes, because all this waiting time the client sees his page is reloading in the browser.
I've tried not to wait at all and used without await
DocServiceImpl.CopyUserAllModels(user, newUploadPath);
but I've searched that this is a bad experience, and except it,
in this case I find that some part of the files haven't been uploaded at all, so
without await it does not work properly in my case(I couldn't understand why).
My problem is: How to finish the request earlier, and then after it do all the work in CopyUserAllModels function?

WebApi Controller - Send Mail Async

I have a WebApi Controller that one of the parts is sending emails to a set of users.
[HttpPost]
[Authorize]
[Route("{id}/Do")]
public async Task<HttpResponseMessage> Post(int id, Model model)
...
await _emailService.SendAsync(message);
...
Now the method that sends the emails (SendGrid)
public override async Task SendAsync(MailMessage message)
{
var client =
new SmtpClient(SendGridServerName, SendGridServerPort)
{
Port = SendGridServerPort,
DeliveryMethod = SmtpDeliveryMethod.Network,
UseDefaultCredentials = false
};
var credentials = new NetworkCredential(SendGridUserName, SendGridPassword);
client.EnableSsl = true;
client.Credentials = credentials;
message.From = new MailAddress(FromAddress);
await client.SendMailAsync(message);
}
Everything works, but it's very slow. I was expecting it to be fast, but the await _emailService.SendAsync(message); does not appear to be async. It stops there for a while.
Any ideas?
Thanks
What async does is allowing your server to run other threads while your slow method is executed asynchronously. I.e., when you await for an async method, the server executes a different thread until the async method finishes execution, and then keep running the thread that called the async method. Async actions in a controller are treated exactly in the same way behind the scenes. So, the response from your async action to the browser will not happen until the async email sending has finished.
NOTE: for example, if there are connectivity problems to the email server, or the DNS resolution, you'll usually get a time out after 30 seconds, so your thread will be slept during 30 seconds, and only then will send the answer to the browser
If you want to return the response to your browser quickly, you need to implement a different idea, which is to start a new thread that sends the email, but don't wait for it to finish, so that your thread keeps running and inmediately returns the asnwer to the browser. That's known as fire and forget. To understand what I'm speaking about, please see this: Fire and forget approach. And then read this other carefully: Fire and Forget (Asynch) ASP.NET Method Call. Take into account that MVC itself is threaded and you need to have it into account when using the fire and forget approach.
Obvioulsy in fire and forget, the controller will not be able to detect errors during the email sending, beacause the new thread runs on its own while the main thread has already finished. So you have to implement something to at least log the possible error, and ideally let the user now what happened (for example which reports that he can see later on). Please, see this: ASP.NET Exception Handling in background threads

WebClient vs. HttpClient - Async request

I have this very simple WebApi method:
[HttpGet]
public IHttpActionResult Foo()
{
Thread.Sleep(3000);
return Ok("Bar");
}
And I have these two methods in a console application that call it:
async Task UsingWebClient()
{
Task<string> task = new WebClient().DownloadStringTaskAsync (new Uri ("http://localhost.fiddler:63710/api/producttype/Foo"));
Console.WriteLine("WebClient - Before calling wait");
string result = await task;
Console.WriteLine("WebClient - After calling wait");
}
async Task UsingHttpClient()
{
Task<string> task = new HttpClient().GetStringAsync (new Uri ("http://localhost.fiddler:63710/api/producttype/Foo"));
Console.WriteLine("HttpClient - Before calling wait");
string result = await task;
Console.WriteLine("HttpClient - After calling wait");
}
And I am calling these methods from LinqPad like this:
async Task Main()
{
await UsingWebClient();
await UsingHttpClient();
}
I was monitoring the traffic using Fiddler and I noticed that:
when using WebClient the request to the web api is made immediately
and then execution continues to Console.WriteLine("WebClient - Before
calling wait");
when using HttpClient the request to the web api is not made until
the call to await task;
I'm trying to understand why the request is not made immediately when using HttpClient. Can anyone point me in the right direction?
This is not a duplicate question. I'm not looking for reasons to choose one option over the other - I'll use HttpClient. I would like to know specifically why the request is created at a later stage when using HttpClient.
Thanks,
David
Since both of the requests are async, none of them should delay execution of your current thread (significantly).
It is possible, though, that one of them can send the request before the current thread reaches the next line, while the other cannot.
These kinds of timing issues can happen in asynchronous/parallel environments and they're nothing to worry about as long as you don't separate logically successive operations.

How to make an async call not prevent action from returning response to user

I have the following async code
public async static void SendAsync(string url)
{
try
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.KeepAlive = false;
request.Timeout = 5000;
var response = await request.GetResponseAsync().ConfigureAwait(false);
response.Close();
}
catch (Exception ex)
{
SendException(ex, url);
}
}
Which is called by the controller action.
public ActionResult Details(string id)
{
// DO something
SendAsync(some-valid-url);
// Do something
return View();
}
When i debug it - it runs async and hits
return View();
and exits before it is comes to
response.Close();
But in fiddler you can see that the http response is not sent by the browser and it waits for the SendAsync function to finish processing, which is not what i want. I want the http response to return back to the user right away.
What do I have to change here to achieve that?
i actually edited it and am now doing async static void since i do not
need to track the end of the task,
But you do need to keep track, as MVC will close the connection when Details returns, you need to tell MVC you are waiting for a background action.
public async Task<ActionResult> DetailsAsync(string id)
{
// DO something
var sendTask = SendAsync(some-valid-url);
// Do something else that does not depend on sendTask
await sendTask;
return View();
}
To answer your updated comment
doing an await will make it wait for the async http call to finish.
Which i do not want.
To get the behavior you are looking for you need to use AJAX, the async/await system will not give you what you want by itself. (I have never really done MVC with AJAX so I don't have a example for you how to do it)
doing an await will make it wait for the async http call to finish.
Which i do not want.
It sounds to me like you want fire and forget since you don't want to wait for the response.
There are a few negative points to bare in mind (such as no error handling, no guarantees, etc), so I would recommend reading into this a bit before dropping it into your project.
This is, in my opinion, is the easiest way to call a method with fire and forget.
ThreadPool.QueueUserWorkItem(o => SendAsync(some-valid-url));
This will request an available thread from the app pool. It will then use that thread to execute the method without blocking your current running one.
I hope this helps.

Categories