ASP.NET MVC How to output FileContent from within a controller action and continue - asp.net-mvc

I am fairly new to MVC and want to know what the correct approach is - when a form is submitted the Edit ActionResult is executed, once the data is saved, I want to generate a file and send to the browser for download but also continue to another action.
The file is sent to the browser but no further processing occurs in the controller, because I use return RedirectToActions. Thanks in advance for any pointers.
public ActionResult Edit(int id, InvoiceFormData formData)
{
...
return base.RedirectToAction("ReturnPdf", new { id = 99 });
// More processing...ideally...then
return base.RedirectToAction("Action", "Controller");
}
public FileResult ReturnPdf(int? id)
{
...
return output; // writes file output and prompts user to download
}

You cannot use two return statements in the same action result and expect them to both run. When you "return" it means you're returning the output and then quiting out of the action result. Therefore, you could kind of chain your actions, so rather than doing the further processing inside the Edit action result, you could move that logic into the ReturnPdf AR(since that's what's I could see in your code), then finally return the final output in whatever action result you landed on. I hope that was clear enough...

Related

Multi Post for Action with return File in ASP.NET MVC

Assume this code in One of my Actions
[HttpPost]
public ActionResult Generate (Params){
....
InsertOneRawToDB();
return RedirectToAction("Index", new { info = info });
}
So every thing is OK yet but when I change return to:
InsertOneRawToDB();
byte[] myfile = GenerateAZipFile();
return File( myfile , "application/zip" , "MyFileName");
In this case I see a weird behavior: before return, One raw inserted to DB, and after return another raw inserted, it seems the InsertOneRawToDB called again.
Does any one know about this? what happening here?
PS: I use Visual Studio 2012 RTM + ASP.NET MVC4
PS: OK I Use IDM (Internet Download Manager) to download zip file and that cause MultiPost on this Action So How can I handle this?
Based on your conclusion that the problem is related to using a download manager... That's what download mangers do. They create multiple connections to the file.
One thing you could do is store a session variable that says "Already started downloading", and then only insert the record the first time.
however if the user legitimately downloaded the file multiple times then you would only get one record.
Another option would be to examine the Http headers and look for the "Range" header, which is what is used to download a file in multiple pieces (or resume a file). You would then have to take the Range parameters and only return the portion of the file requested.
Here's an example of how to do a Ranged download: http://tpeczek.com/2011/10/range-requests-in-aspnet-mvc.html
I'm actually quite surprised that this hasn't come up before... I guess most people don't do database actions in a download action or notice it.
You can redirect to new action and in this new action return file.
[HttpPost]
public ActionResult Generate (Params){
....
InsertOneRawToDB();
return RedirectToAction("GetFile"};
}
[HttpGet]
public ActionResult GetFile
{
byte[] myfile = GenerateAZipFile();
return File(myfile, "application/zip", "filename.ext");
}

RedirectToAction is not redirecting at all

i'm using RedirectToAction to redirect after a post to another controller and it's working. so i tried to Redirect to another action in the same controller and it's NOT working too.
public ActionResult finalize(int id)
{
Meeting meeting = db.Meetings.Find(id);
meeting.meetingStatus = "finalized";
db.SaveChanges();
return RedirectToAction("Index");
}
public ActionResult Create()
{
return View();
}
[HttpPost]
public ActionResult Create(Meeting meeting)
{
if (ModelState.IsValid)
{
db.Meetings.Add(meeting);
db.SaveChanges();
// return RedirectToAction("Invitation");
return (RedirectToAction("finalize", new { id = meeting.meetingID}));
}
return View(meeting);
}
you cant make 2 redirects in the same call , a redirect just return an http redirect code to another page , redirecting 2 times just wont work, don't treat action methods like normal methods they are different
for example when you request a page example.com/controller/action
action will be executed and for example it has a return value with RedirectToAction("NotFound")
what will happen in this scenario is
action body will be executed and the return value will send to the client an http header of 302 that says your new destination is /controller/NotFound
so RedirectToAction just return an http code to the client its not calling another method
Update
i was wrong i checked with fiddler2 , you can use redirect to action many times , what will happen the server will send multiple http redirect headers for each one
i jumped to conclusion this fast because i didnt think multiple redirect is the right way to do it, so after i tried it , i can say it works , just create a new project to see where is the problem exactly or use a tool like fiddler2

TempData & Redirect for Errors

I have a controller action in my project that has a situation where it needs to display an error message to the user under certain scenarios. This action occurs in a POST:
[HttpPost]
public ActionResult DoSomeAction() {
if( someCondition )
return RedirectToAction("SomeActionError");
return RedirectToAction("Index");
}
public ActionResult SomeActionError() {
return View();
}
Currently I have it set up so that it will redirect to an error controller action. I'm not really fond of this approach because in the URL they see /SomeActionError and it also means that the user can directly navigate to this URL.
Is it a bad design/approach to put some flag in TempData and redirect to another controller that checks for the TempData error flag?
Example:
[HttpPost]
public ActionResult DoSomeAction() {
if( someCondition ) {
TempData["DoSomeActionError"] = true;
}
return RedirectToAction("Index");
}
public ActionResult Index() {
// check for error
if( TempData["DoSomeActionError"] ) {
return View("SomeActionError");
}
}
Is this a bad idea? Is there another approach that does something similar (doesn't allow the user to directly navigate to the error action)? I don't want to return the View on the POST action because I don't want them to refresh and cause another POST.
TempData is not per se a bad concept. TempData is for transporting an information to some consumer that reads that information and the information should vanish after it's been read.
The way your're using TempData is odd. A more elegant implementation for your requirements (you should show an error message) is to implement an equivalent to the rails flash concept and don't redirect to an error page but display an error message in your index view. Something like a red banner that says "The record could not be saved".
This question shows a nice flash implementation including the view stuff (not the accepted answer but the answer by #jim)
using tempdata in mvc is not a good approach.
If i were you i'll do as following:
[HttpPost]
public ActionResult DoSomeAction() {
if( someCondition ) {
return RedirectToAction("Index", new{error=true}
}
return RedirectToAction("Index");
}
public ActionResult Index(bool? error) {
// check for error
if(error?? false ) {
return View("SomeActionError");
}
}
While I don't agree TempData is always bad (I find it great for status messages I absolutely don't want passed on the url such as "record saved", I think in your case there may be a better option.
First you don't want an error page to be accessible - may I ask why?
To do a redirect when an error happens only to redirect again is a bit odd. I would throw the exception and handle that exception by your error view. MVC automatically adds the [HandleError] attribute as a global filter, so throw your exception (a custom type if necessary) and handle it in your error page as you see fit since you can access exception details there and it doesn't require a redirect.

What is the MVC way of simultaneously sending a file and redirecting to a new page?

I have a form which users must fill out and submit. The controller action does some work and decides the user can have a file and so redirects to another action which is a FilePathResult.
[CaptchaValidator]
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Index(FormCollection collection)
{
// do some stuff ...
return RedirectToAction("Download");
}
[AcceptVerbs(HttpVerbs.Get)]
public FilePathResult Download()
{
var fileName = "c:\foo.exe";
return File(fileName, "application/octet-stream", "installer.exe");
}
What I would like to do is redirect the user to another page which thanks the user for downloading the file but I'm not sure how to accomplish that in a "MVC-like" way.
The only way I can think of off the top of my head is to skip the Download action and instead redirect to the ThankYou action, and have the ThankYou view use javascript to send the file. But this just doesn't seem very MVC to me. Is there a better approach?
Results:
The accepted answer is correct enough but I wanted to show I implemented it.
The Index action changes where it redirects to:
return RedirectToAction("Thankyou");
I added this controller (and view) to show the user any "post download information" and to say thanks for downloading the file. The AutoRefresh attribute I grabbed from link text which shows some other excellent uses.
[AutoRefresh(ControllerName="Download", ActionName="GetFile", DurationInSeconds=3)]
[AcceptVerbs(HttpVerbs.Get)]
public ActionResult Thankyou()
{
return View();
}
The action which get redirected to is this same as it was before:
[AcceptVerbs(HttpVerbs.Get)]
public FilePathResult GetFile()
{
var fileName = "c:\foo.exe";
return File(fileName, "application/octet-stream", "installer.exe");
}
Just add a header to your response, in the action for your redirected page.
Googling came up with this header:
Refresh: 5; URL=http://host/path
In your case the URL would be replaced with the URL to your download action
As the page I was reading says, the number 5 is the number of seconds to wait before "refreshing" to the url.
With the file being a download, it shouldn't move you off your nice redirect page :)

HOW? Controller return nothing/current view

SHORT:
How do I make a controller return the current view or just simply do nothing?
LONG:
I have a partial view where i've created an imageslider.
It contains a link which sends a request to a controller to get the next image (using ajax).
The controller fetches the next image, stores it in ViewData and sends back a partial view (the one above).
Now, what I do today is that when the controller reaches the last image it re-return the very same image (by refetching it), but still creates a new view, that is, the client/browser re-parses the "same" data.
This seems somewhat non-optimal.
What I'd like to do is that when controller reaches the last image it should simply do nothing.
If I return null then the view is updated with empty contents.
I want the view/client/browser to retain whatever it has and the controller to simply do nothing.
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult GetNextImage(...)
{
if(Request.IsAjaxRequest())
{
if(CURRENT_IMAGE != LAST_IMAGE)
{
Image image = GetNextImage(...);
var partialViewResult = new PartialViewResult();
partialViewResult.ViewName = "ImageSlide";
partialViewResult.ViewData.Model = image;
return partialViewResult;
}
else
{
// DO NOTHING, HOW?
}
}
return RedirectToAction("Error", "Home");
}
You can return an EmptyResult if you want it to do nothing...
return new EmptyResult();
If you're using the AjaxHelper you can avoid the update by supplying an unsuccessful status code (e.g. 404 or whatever is most appropriate), that'll stop it replacing your div as the javascript in MicrosoftMvcAjax.js explicitly checks for a successful response before updating any elements:-
this.ControllerContext.HttpContext.Response.StatusCode = 404;
return new EmptyResult();
Ultimately the best way to avoid it is to design the partial view so it avoids the problem in the first place (like you mention yourself).
I ran into this problem today. I wanted to find a solution for how to deal with double-clicks on the client side trying to reenter the controller action on the server side while it was still processing. If a user entered that action, I wanted it to just ignore the request and do nothing on the browser side.
Solution looks like this:
public async Task<ActionResult> MyAction()
{
if(!CanEnterAction(nameof(MyAction))) return new HttpStatusCodeResult(204);
try
{
// Do long running stuff
return ValidActionResult();
}
finally
{
ExitedAction(nameof(MyAction));
}
}
Returning a status code of 204 basically does nothing to the page displayed in the browser. The actual result eventually makes it back to the browser when the action is complete.
This question is old, but I wasn't able to find an answer anywhere on StackOverflow. I figured it had to be possible since a FileResult doesn't really affect the current page, either, other than saving a file.
I would use
return new HttpStatusCodeResult(204);
this way you would stay on the same page and there is no post back.
Here is the defination
The HTTP 204 No Content success status response code indicates that the request has succeeded, but that the client doesn't need to go away from its current page
Assuming that you are using MicrosoftMvcAjax, you could send back a JavascriptResult that alerts the user that they have reached the end of the slider. If the response is javascript rather than content, the MicrosoftMvcAjax handler executes it instead of replacing the DOM contents.
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult GetNextImage(...)
{
if(Request.IsAjaxRequest())
{
if(CURRENT_IMAGE != LAST_IMAGE)
{
Image image = GetNextImage(...);
var partialViewResult = new PartialViewResult();
partialViewResult.ViewName = "ImageSlide";
partialViewResult.ViewData.Model = image;
return partialViewResult;
}
else
{
return JavaScript( "alert('No more images');" );
}
}
return RedirectToAction("Error", "Home");
}
Of course, you'd probably want to be more creative and use the jQuery dialog plugin or something rather than an alert.
Ok, I've got it.
Not a solution to my question but still solves the problem.
I'll simply not show the "Next" link when the view shows the last image.
Can't believe I didn't think of it earlier...
Thanks for your efforts guys

Resources