I am streaming a file to client for download. But it may happen that file size could be really big (upto few GBs) and thus I don't want to block the user to click other buttons on the webpage which goes to the same controller as Download. From reading on internet, I found that I can make it asynchronous using "Async" and "Completed" suffixes and this is my code:
public void DownloadAsync(string filename, string Id, string docId)
{
AsyncManager.OutstandingOperations.Increment();
// code to get the file from server and send it to client.
AsyncManager.OutstandingOperations.Decrement();
}
public ActionResult DownloadCompleted()
{
return RedirectToAction("Index");
}
public string OtherAction()
{
// code for this action.
}
When I click the Download on webpage and also clicks the "OtherAction" button. It still process the requests synchronously. The "OtherAction" just returns a string to user and is not time intensive and that's why I didn't make it asynchronous.
Do I need to include some code between the .Increment() and .Decrement() operations to wrap the code to download file inside "something" to start a new thread or something like that? I am not able to figure out what other piece I am missing here. I am inheriting the controller from AsyncController.
I think that you are missing some concepts here. You have two parts.
The server
Every request is asynchronous even without using any Async, so the user can send other requests to the server without being blocked.
The client
As long as the user starts the download and don't exit the browser or stop the download, the user can keep doing operations in the same tab or in another. The request that it's being completed by the download don't stop.
So, you don't need to make anything async in the server. I would just recommend my users to use some download manager if the download is several GB heavy
Related
I need to call a process from my Web API controller action asynchronously so the action doesn't wait for the process to finish to return. How is this done? I'm trying to avoid writing a message queue.
This is an example of what I am looking to do
public JsonResult Index(string key)
{
//call some process here but don't wait for it to finish,
//this would be something like logging or sending an email
// this returns immediately
return new JsonResult
{
...
};
}
Task.Run() or ThreadPool.QueueUserWorkItem()
For logging you might want to write an asynchronous log appender, if using log4net. For email, I would consider using a drop directory and having the SMTP server pick up the mail asynchronously. Both of these would remove the complexity from the controller action and localize it in the component you're using instead.
If you can afford to lose your background work, you can use Task.Run(). If it is important, use QueueBackgroundWorkItem. For more info, take a look at this.
http://blogs.msdn.com/b/webdev/archive/2014/06/04/queuebackgroundworkitem-to-reliably-schedule-and-run-long-background-process-in-asp-net.aspx
You could also look at background job schedulers like hangfire or Quartz.net
Below is a nice blog entry on these solutions.
How to run Background Tasks in ASP.Net
How can i log requests that are going on some link?
I need to store requests Headers, Verb (Get or Post etc.), Request Data and Request Body.
It's must be some separate application like Fiddler.
DESC: I have web application. It makes some search. I want to log data of search request using another application which can log any requests for some site (in my case for my web app). How to make it? I make research for solution but find many examples where user can create some Module or Filter which must be included in web application. This case for me is not allowed.
If you have control of both sides, you can basically do whatever you want..
Maybe link to an action first that acts as a tracker:
public ActionResult Track()
{
//get whatever data you want here
//Request.Headers, Request.RequestType ect
//track the data in a database or whatever
SaveSomeData();
//get the original url from a post variable, or querystring, where you put it
var redirectUrl = Request["redirect"];
return Redirect(redirectUrl);
}
Then you would change your links for example a link to http://google.com, would change to
http://mywebsite.com/mycontroller/track?url=http://google.com
Another possible way would be to create a proxy, and monitor the data that goes through it.
Need a better idea of what you need though to help out more.
In both contexts, same code, same application , same routes, only controller action differs.
context 1:
synchronous controller - gets called once
public void Index(string parameters)
{
// called only once
}
context 2:
asynchronous controller actions - gets called lots of times
public void IndexAsync(string parameters)
{
// called many times (per image etc on the page)
}
public ActionResult IndexCompleted()
{
return null;
}
my question,
does anyone understand/know why the same routes/actions pick up all the extra requests when I make the controller action asynchronous..?
any help would be very much appreciated,
thanks,
J.
Fire up fiddler and see how the image requests are coming across in both cases. If they come up once for the synchronous method and the browser then realizes it can cache this image or not. Are the URL/Parameters combination the same in the async, sync method?
Please post the responses here on what you see with Fiddler if you can. If you download a demo project at http://msdn.microsoft.com/en-us/library/ee728598.aspx does the same thing happen for you there? Im gathering not which makes me think its a caching issue (off the top of my head).
I know you mention 'same routes' above, but same route and parameters in both cases?
This is the client's request: in some cases there are several forms that don't require authentication or it takes too long for a logged user to finish completing a form and the session expires. In theses cases he wants to retain the data when the user submits the form by serializing and storing it in a SQL table and then, after the user (re)logs in, he is redirected to the respective form and that is repopulated with the data retrieved from the database and deserialized.
The problem is that he wants all the logic of the storing, retrieving and resending the data in the authorization code block. I know how to do the storing, serialization, retrieving, deserialization of data and the user redirection to the respective page, but I don't know hoe to make it generic so that it works for every model on every form.
The client does not want any code for this task done in the form action method. For example:
[HttpPost]
[Authorize]
public ActionResult Create(Post post)
{
if (ModelState.IsValid)
{
post.CreatedBy = (Guid)Membership.GetUser().ProviderUserKey;
post.CreateTime = DateTime.Now;
repo.Add(post);
repo.Save();
return RedirectToAction("Index");
}
else
{
return View(post);
}
}
As you can see he wants to keep it as clean as possible. He had this suggestion:
The server receives a HTTP request in RAW text format. Using this text, it builds the objects (RequestContext, FormCollection collection, etc. etc.). So, you should be able to build in a hook and e.g. save the raw request. After succesfull login, this previous raw text of the HTTP request could be injected in the handling.
I don't really know how to do that or even if it is possible in MVC.
If someone can help I'll be extremely grateful.
Thanks,
ABTeam
The proper way to do this is to capture the user's progress in the database, and provide a mechanism for returning them to the next step in the process. This can be done with a ?step=n parameter in the URL. If the user gets logged off, they can log back in and be returned to the correct step in the process.
Your client's request for doing this in the authorization code block is not an appropriate use of functionality. That's not the purpose of the authorization block, and attempting to do business logic there, in the manner that the client describes, will almost certainly compromise security and result in unmaintainable code.
The authorization block is not the right place for it, but you may be able to do something fairly generic with action filters.
Load the saved data in OnActionExecuting. I'm not sure if you'll be able to get it passed to the action method as a parameter, but at the least you should be able to add it into ViewData so it can be used as a starting point for generating the model for the form page.
Not sure if the model will be available for saving before ActionExecuting, but if not the model as it exists after the action method runs should be an appropriate alternative.
What I am trying to do is render a View in an MVC site informing the user to not refresh their browser while server side processing is taking place. This could be a long running task, and if they hit refresh it will send the request again, thus sending them to the error screen when the process was actually successful. I was going to do this in JS, either with JQuery $.ajax(...) or with a simple $(document).ready(function() { window.location = ... }); but I was hoping there was a way to do it in MVC, thus giving me more control over the HttpResponseCode which is returned to the client calling. Is there a way to do this?
I was thinking along the lines of
public ActionResult LoadingAsync(string UserKey, string userInf)
{
AsyncManager.OutstandingOperations.Increment();
CallProcess(...)
return View();
}
then have a Completed Action perform the redirect
public ActionResult LoadingCompleted()
{
LongRunningProcess();
return Redirect("http://yourdone.com");
}
or just have something that Renders inside the view that will perform the Redirect from inside the View
<% Html.RenderAction("Process"); %><!--This won't actually redirect-->
Any ideas?
There is no way to achieve this without invoking some kind of Javascript on the client.
When you return a response to the user, you either return a page to display with an HTTP code of 200 (OK), or an instruction to redirect with an HTTP code of 301 (Moved Permanently) or 307 (Moved Temporarily) and a URL to redirect to.
You have to choose either of these return values and cannot return both.
The simplest solution is to use Javascript to redirect the user from your "please wait" page to the destination once it determines the background process has completed.
One simple solution would be to fire off your background process, then display a page which (1) asks the user not to refresh, (2) polls the server every couple of seconds via Javascript to determine if the background process is complete, and (3) redirects upon determining that it is complete.