I'm building an mvc 4 application that makes file uploading. When I get HttpPostedFileBase range, I get streams on them and pass to my business logic layer to save them and bind with database records. This is done without any problems. But when displaying current state of uploading progress comes in, I'm a bit confused. I make a second request while an uploading runs, but it waits for first request to be executed completely.
I know that in same browser client instance (actually same session), my requests are synchronized. But there is a solution that I read about, asynchronous actions.
To try asynchronous actions, I used Stream.CopyToAsync(..) instead of Stream.CopyTo(..). I'm also using Task.Delay(10000) to simulate file uploading progress. Then while asynchronous UploadFile action runs, I invoked synchronous UploadProgress on same browser instance. Result is still waiting for first request to complete. Below is the code I use. Where am I wrong?
Here is async action to upload files;
[HttpPost]
public async Task<ActionResult> Upload(PFileUploadModel files)
{
if (!Session.GetIsLoggedIn())
return RedirectToAction("Login", "User");
var fileRequest = Session.CreateRequest<PAddFileRequest, bool>(); //Creates a business logic request
fileRequest.Files.Add(...);
var result = await Session.HandleRequestAsync(fileRequest); //Handles and executes a business logic request by checking authority
if (result)
return RedirectToAction("List");
return RedirectToError();
}
And upload progress action is simple as below for now :) :
public ActionResult UploadProgress()
{
//var saveProgressRequest = Session.CreateRequest<PSaveFileProgressInfoRequest, PSaveFileProgressInfoResponse>();
//saveProgressRequest.ProgressHandle = progressHandle;
//var response = Session.HandleRequest(saveProgressRequest);
return Content("Test!");
}
Thanks for helping.
async doesn't change the HTTP protocol. The Upload request is in progress until you return the result.
You're probably running into the ASP.NET session lock, which ensures that multiple request handlers don't interfere with each other when reading/writing session state. I believe that MVC always takes a write lock on the session state by default, preventing any other actions from executing simultaneously (in the same session).
You can override this behavior by specifying the SessionState attribute on your controller.
Related
I've a Blazor client which shows a live stream of images coming from a ASP.NET Core (.NET 6) Web Application Server which in turn requests the stream to a camera (multiple cameras actually), which returns a multipart/x-mixed-replace content.
When the user (client side) leaves the page containing the stream of images, I'd like to server to stop requesting the camera live stream. I know I need to use Cancellation Token, as explained here, but it seems not to work if client side I make the stream request like this:
// CameraLive.razor
<img src="CameraSystem/startStreaming">
Server side, request is forwarded to the actual device like this (simplified code)
// CameraSystemController.cs
[ApiController]
[Route("[controller]")]
public class CameraSystemController : ControllerBase
{
private string _contentTypeStreaming = "multipart/x-mixed-replace;boundary=myboundary";
private HttpClient _httpClient;
[HttpGet("startStreaming")]
public async Task<IActionResult>StartCameraStreaming(CancellationToken token)
{
Stream stream = await _httpClient.GetStreamAsync("http://[...]", token);
FileStreamResult result = new FileStreamResult(stream, _contentTypeStreaming) {
EnableRangeProcessing = true
};
return result;
}
}
Currently, every time a client open CameraLive.razor page, the server request a stream to the camera and returns it. If on the same client I move to another page of the web application and later I re-enter CameraLive.razor, I see the previous request is not canceled and a new one is made. Actually, if I look at task manager, I can see the bandwidth usage is doubled.
The requests are canceled only if I close the browser tab.
I'd like the request to stop as soon as the client moves to another page, as it happens if the client was to be designed this way:
// CameraLive.razor
<img src="#_frame">
// CameraLive.razor.cs
private CancellationTokenSource _tokenSource;
private System.IO.Stream _stream;
private void RetrieveLiveStream() {
_stream = await Http.GetStreamAsync("CameraSystem/startStreaming", _tokenSource.Token);
// Every time a new piece of stream comes,
// I need to extract the frame from it and update the web page
_frame = ...
stateHasChanged();
}
public void Dispose()
{
if (_tokenSource != null) {
_tokenSource.Cancel();
_tokenSource.Dispose();
}
}
If I design the client in this second way the cancellation works: as soon as the user moves to another page, the request is canceled (I can see it clearly from the bandwidth usage data)
However I'd have liked to stick with the first solution because
It's simpler, I don't have to handle all the frame-extraction related code, and I suppose the way browser does it is more efficient than the way I would do (I've never done it, I just tried making the request, doing nothing with the result and then leave the page just to make sure that request is actually canceled).
I have the feeling that having the browser auto-update the image is more efficient than having me calling StateHasChanged() continuously.
So, from what I get, in order to have the cancellationToken to work properly, I need to send the token from the client along with the http request. However in the first solution it seems like it is not possible to pass the cancellationToken along with the request URL, so I don't know what I should do.
Any advice?
I've the following controller Home:
[HttpGet]
public void Index()
{
Response.Write($"{DateTime.Now} begin <br>");
Thread.Sleep(5000);
Response.Write($"{DateTime.Now} end <br>");
}
when i open ~/Home/Index with browser in two tabs , i see that request is processed sequentially. Session disabled. How to make browser to process request concurrently ?
I don't know why, I guess the browser may thought the requests are the same and would like to checks if the response can be cached before sending the second request.
To run it concurrently, one simple way is to append fake parameter to the url to make them different every time, for example,
/Home/Index?p=1
/Home/Index?p=2
I'm getting a bit frustrated with this problem:
I have a web site that manage some files to download, cause these files are very big, and must be organized in folders and then compacted, I build an Ajax structure that do this job in background, and when these files is ready to be downloaded, this job changes the status of an object in the user session (bool isReady = true, simple like that).
To achieve this, when the user clicks "download", a jquery Post is send to an API, and this API starts the "organizer" job and finish the code (main thread, the request scoped one), leaving a background thread doing the magic (it's so beautiful haha).
This "organizer" job is a background thread that receive HttpSessionState (HttpContext.Current.Session) by parameter. It organize and ZIP the files, create a download link and, in the end, change an object in the session using the HttpSessionState that received by param.
This works great when I'm using the session "InProc" mode (I was very happy to deploy this peace of art in production after the tests).
But, my nightmares started when I have deployed the project in production environment, cause we use "StateServer" mode in this environment.
In these environment, the changes is not applied.
What I have noticed, until now, is that in the StateServer, every change I make in the background thread is not "commited" to the session when the changes occurs AFTER the user request ends (the thread that starts the thread).
If i write a thread.join() to wait the thread to finish, the changes made inside the thread is applied.
I'm thinking about use the DB to store these values, but, I will lose some performance :(
[HttpPost]
[Route("startDownloadNow")]
public void StartDownloadNow(DownloadStatusProxy input)
{
//some pieces of code...
...
//add the download request in the user session
Downloads.Add(data);
//pass the session as parameter to the thread
//cause the thread itself don't know the current httpcontext session
HttpSessionState session = HttpContext.Current.Session;
Thread thread = new Thread(() => ProccessDownload(data, session));
thread.Start();
//here, if I put a thread.join(), the changes inside the thread are applied correctly, but I can't do this, otherwise, it ceases to be ajax
}
private void ProccessDownload(DownloadStatus currentDownload, HttpSessionState session)
{
List<DownloadStatus> listDownload = ((List<DownloadStatus>)session["Downloads"]);
try
{
//just make the magic...
string downloadUrl = CartClient.CartDownloadNow(currentDownload.idRegion, currentDownload.idUser, currentDownload.idLanguage, currentDownload.listCartAsset.ToArray(), currentDownload.listCartAssetThumb.ToArray());
listDownload.Find(d => d.hashId == currentDownload.hashId).downloadUrl = downloadUrl;
listDownload.Find(d => d.hashId == currentDownload.hashId).isReady = true;
//in this point, if I inspect the current session, the values are applied but, in the next user request, these values are in the previous state... sad... .net bad dog...
}
catch (Exception e)
{
listDownload.Find(d => d.hashId == currentDownload.hashId).msgError = Utils.GetAllErrors(e);
LogService.Log(e);
}
//this was a desesperated try, I retrieve the object, manipulated and put it back again to the session, but it doesn't works too...
session["Downloads"] = listDownload;
}
I study .NET and now i'm learning web developement with ASP .NET MVC 4.
I made a Task :
Task t = new Task(new Action(() =>
{
while (convert("suitandtie.mp4") != 1)
{
if (i == 4)
{
// Here I want to access in mainthread property
// I need to change text for viewBag like :
// ViewBag.Message = "Convert failed";
// But I need a Dispatcher and invoke for accessing
// the ViewBag of the mainthread
break;
}
i++;
}
}));
t.Start();
In .Net Application, With System.Windows.Threading.Dispatcher, it's possible to use it for call invoke.
I did that in my application :
this.Dispatcher.Invoke(new Action(() =>
{
ContactBook.Add(Person("Mark", "232 521 424"));
}));
When I added Mark in contact book, it added in the ContactBook of the mainthread, not of the thread created by Task.
Help please for accessing ViewBag.Message of the mainthread?
Why do you need to create that as a separated thread?
I am asking because unless the client is making aysn calls, there is no benefit in doing that, granted that you are not going to be doing many things at the same time and deferring the execution of the controller to some other service.
The easiest way if your you to pass in the current Thread to the ViewBag thought.
There is no mapping. There is no means of interacting with the response because the response has [potentially] already been sent and the connection closed at that point in time.
If you start an asynchronous operation and don't have something in the request's context blocking on that operation then that async operation can't interact with the response at all.
The best that you'll be able to do is store the results of anything you generate in some external storage mechanism (such as a database) so that a future request from the server can get the information and send it to the client.
The Viewbag (and any page state) is only available before the response was sent to the client. It's the way the classic webserver model works: The client request an URL, the server answers something (HTML, javascript, octet Stream...) If your operation is a few minutes long, you should consider using something slightly different.
Personnaly, I would load the initial page with a classical ASP.NET MVC action, then starting the long duration task through a subsequent javascript request.
To run the task, use something like SignalR, which will enable you to perfom duplex communications between your client and your server. With SignalR, the server will be able to notify the client at the end of the task rather easily. And it's very easy to use in an ASP.NET MVC app.
Given an async controller:
public class MyController : AsyncController
{
[NoAsyncTimeout]
public void MyActionAsync() { ... }
public void MyActionCompleted() { ... }
}
Assume MyActionAsync kicks off a process that takes several minutes. If the user now goes to the MyAction action, the browser will wait with the connection open. If the user closes his browser, the connection is closed. Is it possible to detect when that happens on the server (preferably inside the controller)? If so, how? I've tried overriding OnException but that never fires in this scenario.
Note: I do appreciate the helpful answers below, but the key aspect of this question is that I'm using an AsyncController. This means that the HTTP requests are still open (they are long-lived like COMET or BOSH) which means it's a live socket connection. Why can't the server be notified when this live connection is terminated (i.e. "connection reset by peer", the TCP RST packet)?
I realise this question is old, but it turned up frequently in my search for the same answer.
The details below only apply to .Net 4.5
HttpContext.Response.ClientDisconnectedToken is what you want. That will give you a CancellationToken you can pass to your async/await calls.
public async Task<ActionResult> Index()
{
//The Connected Client 'manages' this token.
//HttpContext.Response.ClientDisconnectedToken.IsCancellationRequested will be set to true if the client disconnects
try
{
using (var client = new System.Net.Http.HttpClient())
{
var url = "http://google.com";
var html = await client.GetAsync(url, HttpContext.Response.ClientDisconnectedToken);
}
}
catch (TaskCanceledException e)
{
//The Client has gone
//you can handle this and the request will keep on being processed, but no one is there to see the resonse
}
return View();
}
You can test the snippet above by putting a breakpoint at the start of the function then closing your browser window.
And another snippet, not directly related to your question but useful all the same...
You can also put a hard limit on the amount of time an action can execute for by using the AsyncTimeout attribute. To use this use add an additional parameter of type CancellationToken. This token will allow ASP.Net to time-out the request if execution takes too long.
[AsyncTimeout(500)] //500ms
public async Task<ActionResult> Index(CancellationToken cancel)
{
//ASP.Net manages the cancel token.
//cancel.IsCancellationRequested will be set to true after 500ms
try
{
using (var client = new System.Net.Http.HttpClient())
{
var url = "http://google.com";
var html = await client.GetAsync(url, cancel);
}
}
catch (TaskCanceledException e)
{
//ASP.Net has killed the request
//Yellow Screen Of Death with System.TimeoutException
//the return View() below wont render
}
return View();
}
You can test this one by putting a breakpoint at the start of the function (thus making the request take more than 500ms when the breakpoint is hit) then letting it run out.
Does not Response.IsClientConnected work fairly well for this? I have just now tried out to in my case cancel large file uploads. By that I mean if a client abort their (in my case Ajax) requests I can see that in my Action. I am not saying it is 100% accurate but my small scale testing shows that the client browser aborts the request, and that the Action gets the correct response from IsClientConnected.
It's just as #Darin says. HTTP is a stateless protocol which means that there are no way (by using HTTP) to detect if the client is still there or not. HTTP 1.0 closes the socket after each request, while HTTP/1.1 can keep it open for a while (a keep alive timeout can be set as a header). That a HTTP/1.1 client closes the socket (or the server for that matter) doesn't mean that the client has gone away, just that the socket hasn't been used for a while.
There are something called COMET servers which are used to let client/server continue to "chat" over HTTP. Search for comet here at SO or on the net, there are several implementations available.
For obvious reasons the server cannot be notified that the client has closed his browser. Or that he went to the toilet :-) What you could do is have the client continuously poll the server with AJAX requests at regular interval (window.setInterval) and if the server detects that it is no longer polled it means the client is no longer there.