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.
Related
Updated below...
So this is a random question that came about from a discussion over what I consider the over usage of await in projects at work...
Not sure why I never tried it until now, other than the fact that it's weird and random, but the fact it does work just makes me wonder...how?
The .Result usage was only used to get the responseMessage. I know that is blocking and no bueno and it is for demo purposes only...
So this endpoint works fine...nothing is awaited
[HttpGet("kitteh")]
public Task<string> GetCatFact()
{
var client = new HttpClient();
var res = client.GetAsync("https://catfact.ninja/fact").Result;
return res.Content.ReadAsStringAsync();
}
There's obviously something in the default ASP.NET pipleline that ultimately unwraps the task in order to return the result...but where...or how?
Is this "less efficient" than awaiting in the endpoint itself as the magic taking place behind the scenes is ultimately just blocking to get the result of the returned task?
Updated
So, I am still skeptical and don't think the answer is as easy as "yes it is blocking" or "not it is not blocking" unless there's some legit proof or something to indicate one way or the other. I tried digging through the code myself, and I still don't have a solid answer but...I do know more than I did before...
I simplified the endpoints I have been testing this with...
[HttpGet("taskstring")]
public Task<string> TaskString()
{
return Task.FromResult("Where does this block?");
}
[HttpGet("asyncstring")]
public async Task<string> AsyncString()
{
return await Task.FromResult("This definitely doesn't block");
}
I stepped through a bunch of code and ultimately landed on ObjectMethodExecutor and AwaitableObjectResultExecutor which sparked some interest.
What appears to happen for both the endpoint versions above is the same or nearly the same, at least the execution of these two endpoints and the code covered below. There's a boatload that goes on during this and even though the rider debugger is great, it's not possible to see a lot of the values while debugging due to "Evaluation is not allowed: The thread is not at a GC-safe point".
When the endpoint is called but prior to the endpoint beginning execution...
ObjectMethodExecutor.ctor is called
This does a lot of inspection of the endpoint to determine a bunch of things
One of the checks is to is determine if the method is "async" and it ultimately calls AwaitableInfo.IsTypeAwaitable to check for the required methods/properties and interfaces to ensure it is
If it is, which in the case of returning Task of string it is, makes sense given the above info
ObjectMethodExecutor.GetExecutorAsync() is called
AwaitableObjectResultExecutor.Execute is then called
this type inherits ActionMethodExecutor which overrides the Execute method
executor.ExecuteAsync(executor type of ObjectMethodExecutorAwaitable) is then called, and awaited
this returns a result of type object, boxing yeah I know
The endpoint then actually executes and returns to AwaitableObjectResultExecutor.Execute
the result is then passed to ConvertToActionResult along with the return type, the T of Task of T and the mapper implementation
the mapped ActionResult is then returned
So...I'm still not certain 100% either way but there is a lot of effort put into inspecting the endpoint that is being called and determining if it's async or not, which would make it seem logical to think this is an attempt to avoid something...maybe blocking?
So...does it block...maybe? Seeing that there is an await from the indirect caller of the endpoint, I'd lean towards, no...but it seems really difficult to say still.
What I do now know is...
It definitely does await the endpoint call, albeit indirectly
It converts the Task of string return type to an IActionResult
An async and task only version seem to follow the exact some flow shown above
A non async version does not
So, based on all of that...#Phil's answer seems to be pretty spot on with what I found...
"The framework supports asynchronous controller actions. In order to do so, it would need to inspect the return value of your methods. If the method returns a Task, it will ultimately await on the result before responding.
Even if your action itself does all the awaiting, it still has to return a Task so the caller will still wait (the alternative being some ugly blocking code)."
Thanks to all for participating in an extremely random question that doesn't hold much value to truly understand
Your assumption here is basically correct...
There's obviously something in the default ASP.NET pipleline that ultimately unwraps the task in order to return the result
The framework supports asynchronous controller actions. In order to do so, it would need to inspect the return value of your methods. If the method returns a Task, it will ultimately await on the result before responding.
Even if your action itself does all the awaiting, it still has to return a Task so the caller will still wait (the alternative being some ugly blocking code).
As has been pointed out in some other posts, there are some performance improvements to be had by not awaiting a returned Task so I would write your action as
public async Task<string> GetCatFact()
{
var client = new HttpClient();
var res = await client.GetAsync("https://catfact.ninja/fact");
return res.Content.ReadAsStringAsync(); // no await
}
Your controller handles waiting for the remote response but delegates waiting for the content stream to the caller.
There's obviously something in the default ASP.NET pipleline that ultimately unwraps the task in order to return the result...but where...or how?
ASP.NET asynchronously waits for your task to complete, and then it sends the HTTP response based on the result of the task. It's logically similar to await: an asynchronous wait.
Is this "less efficient" than awaiting in the endpoint itself as the magic taking place behind the scenes is ultimately just blocking to get the result of the returned task?
Yes. It is less efficient to block.
ASP.NET doesn't block; it asynchronously waits. Blocking ties up a thread. So when the code calls .Result, it will be using a thread just to wait for that HttpClient call to complete.
The proper solution is to keep async and await:
[HttpGet("kitteh")]
public async Task<string> GetCatFact()
{
var client = new HttpClient();
var res = await client.GetAsync("https://catfact.ninja/fact");
return await res.Content.ReadAsStringAsync();
}
This way, while the GetAsync is in progress, the thread is yielded back to the ASP.NET runtime and is available for handling other requests, instead of being blocked waiting for the GetAsync to complete.
More information: Task<string> is part of the method signature. ASP.NET has special understanding of the Task<T> type and knows to asynchronously wait for it. async is not part of the method signature. ASP.NET knows whether your method returns Task, but it has no idea whether it's async (and doesn't care). So, in some situations, it's OK to elide the keywords (as described on my blog, but only when the method implementation is trivial. If there's any logic in the method, keep the async and await.
I am trying to use HttpContext.Session in my ASP.NET Core Blazor Server application (as described in this MS Doc, I mean: all correctly set up in startup)
Here is the code part when I try to set a value:
var session = _contextAccessor.HttpContext?.Session;
if (session != null && session.IsAvailable)
{
session.Set(key, data);
await session.CommitAsync();
}
When this code called in Razor component's OnAfterRenderAsync the session.Set throws following exception:
The session cannot be established after the response has started.
I (probably) understand the message, but this renders the Session infrastructure pretty unusable: the application needs to access its state in every phase of the execution...
Question
Should I forget completely the DistributedSession infrastructure, and go for Cookies, or Browser SessionStorage? ...or is there a workaround here still utilizing HttpContext.Session? I would not want to just drop the distributed session infra for a way lower level implementation...
(just for the record: Browser's Session Storage is NOT across tabs, which is a pain)
Blazor is fundamentally incompatible with the concept of traditional server-side sessions, especially in the client-side or WebAssembly hosting model where there is no server-side to begin with. Even in the "server-side" hosting model, though, communication with the server is over websockets. There's only one initial request. Server-side sessions require a cookie which must be sent to the client when the session is established, which means the only point you could do that is on the first load. Afterwards, there's no further requests, and thus no opportunity to establish a session.
The docs give guidance on how to maintain state in a Blazor app. For the closest thing to traditional server-side sessions, you're looking at using the browser's sessionStorage.
Note: I know this answer is a little old, but I use sessions with WebSockets just fine, and I wanted to share my findings.
Answer
I think this Session.Set() error that you're describing is a bug, since Session.Get() works just fine even after the response has started, but Session.Set() doesn't. Regardless, the workaround (or "hack" if you will) includes making a throwaway call to Session.Set() to "prime" the session for future writing. Just find a line of code in your application where you KNOW the response hasn't sent, and insert a throwaway call to Session.Set() there. Then you will be able to make subsequent calls to Session.Set() with no error, including ones after the response has started, inside your OnInitializedAsync() method. You can check if the response is started by checking the property HttpContext.Response.HasStarted.
Try adding this app.Use() snippet into your Startup.cs Configure() method. Try to ensure the line is placed somewhere before app.UseRouting():
...
...
app.UseHttpsRedirection();
app.UseStaticFiles();
//begin Set() hack
app.Use(async delegate (HttpContext Context, Func<Task> Next)
{
//this throwaway session variable will "prime" the Set() method
//to allow it to be called after the response has started
var TempKey = Guid.NewGuid().ToString(); //create a random key
Context.Session.Set(TempKey, Array.Empty<byte>()); //set the throwaway session variable
Context.Session.Remove(TempKey); //remove the throwaway session variable
await Next(); //continue on with the request
});
//end Set() hack
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapBlazorHub();
endpoints.MapFallbackToPage("/_Host");
});
...
...
Background Info
The info I can share here is not Blazor specific, but will help you pinpoint what's happening in your setup, as I've come across the same error myself. The error occurs when BOTH of the following criteria are met simultaneously:
Criteria 1. A request is sent to the server with no session cookie, or the included session cookie is invalid/expired.
Criteria 2. The request in Criteria 1 makes a call to Session.Set() after the response has started. In other words, if the property HttpContext.Response.HasStarted is true, and Session.Set() is called, the exception will be thrown.
Important: If Criteria 1 is not met, then calling Session.Set() after the response has started will NOT cause the error.
That is why the error only seems to happen upon first load of a page--it's because often in first loads, there is no session cookie that the server can use (or the one that was provided is invalid or too old), and the server has to spin up a new session data store (I don't know why it has to spin up a new one for Set(), that's why I say I think this is a bug). If the server has to spin up a new session data store, it does so upon the first call to Session.Set(), and new session data stores cannot be spun up after the response has started. On the other hand, if the session cookie provided was a valid one, then no new data store needs to be spun up, and thus you can call Session.Set() anytime you want, including after the response has started.
What you need to do, is make a preliminary call to Session.Set() before the response gets started, so that the session data store gets spun up, and then your call to Session.Set() won't cause the error.
SessionStorege has more space than cookies.
Syncing (two ways!) the sessionStorage is impossible correctly
I think you are thinking that if it is on the browser, how can you access that in C#? Please see some examples. It actually read from the browser and transfers (use) on the server side.
sessionstorage and localstorage in blazor are encrypted. We do not need to do extra for encryption. The same applies for serialization.
I wrote the following MVC Controller to test cancellation functionality:
class MyController : Controller
{
[HttpGet("api/CancelTest")]
async Task<IActionResult> Get()
{
await Task.Delay(1000);
CancellationToken token = HttpContext.RequestAborted;
bool cancelled = token.IsCancellationRequested;
logger.LogDebug(cancelled.ToString());
return Ok();
}
}
Say, I want to cancel the request, so the value 'true' is logged in the controller action above. This is possible server-side if the server implements the IHttpRequestLifetimeFeature. Luckily Kestrel does, and this can be accomplished the following way:
var feature = (IHttpRequestLifetimeFeature) HttpContext.Features[typeof(IHttpRequestLifetimeFeature)];
feature.Abort();
The problem however is that I want to cancel the request on the client side. For example, in the browser. In pre-core versions of ASP.NET MVC/WebApi the cancellation token would automatically be cancelled if the browser aborted a request. Example: refresh the page a couple of times in Chrome. In the Network tab of the chrome dev tools you can now see the previous (unfinished) request be cancelled.
The thing is: in ASP.NET Core running on Kestrel, I can only see the following entry in the log:
Microsoft.AspNetCore.Server.Kestrel.Internal.Networking.UvException:
Error -4081 ECANCELED operation canceled
So the abort request from the browser DOES arrive and is handled by the Kestrel webserver. It does however not affect the RequestAborted property of the HttpContext in the controller, because the value 'false' is still logged by the method.
Question:
Is there a way to abort/cancel my controller's method, so that the HttpContext.RequestAborted property will be marked as cancelled?
Perhaps I can make something that would subscribe to Kestrel's operation cancelled trigger and call the IHttpRequestLifetimeFeature.Abort() method?
Update:
I did some further testing and it seems the HttpRequest IS in fact aborted, but there seems to be some kind of delay before the cancellation actually takes place. The delay is not time-factored, and seems to come straight from libuv (the library where the Kestrel webserver is build on top of). I posted more info on https://github.com/aspnet/KestrelHttpServer/issues/1103
More updates:
Issue has been moved to another one, because the previous one contained multiple problems. https://github.com/aspnet/KestrelHttpServer/issues/1139
Turns out that that simply using HttpContext.RequestAborted is indeed the right way, but due to a bug in Kestrel (the order in which FIN/RST packages were handled), the request was not aborted on a browser abort.
The bug should finally be fixed in Kestrel 2.0.
See the updates in my question for more information.
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);
}
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.