Downloading a file onto client in ASP.NET MVC application using JQuery - asp.net-mvc

I have a ASP.NET MVC controller with a method which needs to serve up a file to the client(browser). The controller action is invoked with a JQuery $.ajax call. The user needs to be prompted to download the file once the controller action has finished.
I used Response.Transmitfile/Response.WriteFile in the controller method but both have not prompted me to download the file in IE browser even though the file has been created and I am using the right file path as well.
When i invoke the same method directly by typing the controller URL in the browser I am prompted immediately to download the file created.
Could anyone let me know if there is something missing in this flow ?
I suspect it is the way I am calling the controller action in JQuery. How do i use the response of the JQuery Ajax call to ensure the client is prompted to download the file ?
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult CreatePresentation(string id)
{
//do something here , create the file and place in a location on the server itself
string filename = Path.GetFileName(filePath);
Response.ContentType = "APPLICATION/OCTET-STREAM";
System.String disHeader = "Attachment; Filename=\"" + filename +
"\"";
Response.AppendHeader("Content-Disposition", disHeader);
FileInfo fileToDownload = new FileInfo(filePath);
Response.WriteFile(fileToDownload.FullName);
}
On the Javascript side, this is how i invoke the controller action
function CreatePresentation()
{
// something here
$.ajax({
type: "POST",
url: "http://localhost:4844/ActionBar/CreatePresentation",
data:data
});
} // end of function

when you use $.ajax, or for that matter any other AJAX mechanism, you're going around the normal browser file-transfer pipeline. It's the main pipeline that trigger's the browser's Save This File dialog, not the AJAX one.
To achieve what you want, you'll want to use a synchronous location change rather than an asynchronous one: rather than using $.ajax, just set document.location:
function CreatePresentation()
{
//snip code that creates a map called "data"
var paramSnippets = [];
for (item in data)
{
paramSnippets.push(item + "="+data[item]);
}
document.location = "http://localhost:4844/ActionBar/CreatePresentation" + "?" + paramSnippets.join("&");
}
edited in response to comments: included example

Related

asp.net download controller client from server mvc

When I use #using (Ajax.BeginForm("Generate", "Report")) and press submit button, then the byte file is opened in end of my page
When I use Export
Export2
then the file is downloading and saving in default folder DOWNLOADS. So what is the difference between submit and a href???
public FileContentResult Generate()
{
byte[] resultee2 = new byte[12];
return new FileContentResult(resultee2, "text/plain") { FileDownloadName = "myfile.txt" };
}
And how can I create downloading file to folder after submitting form?
I have a form with fields, then I press submit button and values from fields are being send to server, server is generating byte array and then server is responding that byte array then controller has this byte array and then I want to convert this byte array to text and send to client as Download File.
Form file downloads over AJAX do not always function like opening a file from a hyperlink click or after a standard form post action. What I've done in the past is define an action:
public ActionResult Download(long id)
{
//get record info
return File(fileStream, "application/pdf");
}
I was working with PDFS in this example (note the browser by default, would open in a new tab and not prompt to download directly). And access this action via javascript:
window.open("Download?id=" + id, "_blank");
This will call the download option and return the contents. Note that this requires using some persistence mechanism, like storing the file on disk or in the database, or recreating the file from data in the database.
You also have more control if you use jQuery's $.ajax method because you have more control over the request and response, and could possibly return file data back through the AJAX call response...

Response Header issue on Azure Web Application

I am not sure what is happening here.
When I run my web application locally and click a button to download a file, the file is downloaded fine and Response header as you can see in the attached screenshot where it says local.
But when I publish the application to azure web app. Somehow the download button stops working. I checked the Response Header and you can see the difference.
What would cause this problem? The code is the same? Is there any settings that I should be setting in azure web app in azure portal?
Updated to add code
I have debugged remotely to figure out what is going on as #Amor suggested.
It is so strange that When I debug on my local machine first ExportTo action gets hit which prepares the TempData then Download action gets called once the first action completed with ajax call.
However, this is not the case when I debug remotely. Somehow the ExportTo action never gets called. It directly calls the Download action. As a result the TempData null checking is always null.
But why? Why on earth and how that is possible? Is there something cached somewhere?
I have wiped the content of web application on the remote and re-publish evertyhing to ensure everything is updated. But still no success.
here is the code:
[HttpPost]
public virtual ActionResult ExportTo(SearchVm searchVm)
{
var data = _companyService.GetCompanieBySearchTerm(searchVm).Take(150).ToList();
string handle = Guid.NewGuid().ToString();
TempData[handle] = data;
var fileName = $"C-{handle}.xlsx";
var locationUrl = Url.Action("Download", new { fileGuid = handle, fileName });
var downloadUrl = Url.Action("Download");
return Json(new { success = true, locationUrl, guid = handle, downloadUrl }, JsonRequestBehavior.AllowGet);
}
[HttpGet]
public ActionResult Download(string fileGuid, string fileName)
{
if (TempData[fileGuid] != null)
{
var fileNameSafe = $"C-{fileGuid}.xlsx";
var data = TempData[fileGuid] as List<Company>;
using (MemoryStream ms = new MemoryStream())
{
GridViewExtension.WriteXlsx(GetGridSettings(fileNameSafe), data, ms);
MVCxSpreadsheet mySpreadsheet = new MVCxSpreadsheet();
ms.Position = 0;
mySpreadsheet.Open("myDoc", DocumentFormat.Xlsx, () =>
{
return ms;
});
mySpreadsheet.Document.Worksheets.Insert(0);
var image = Server.MapPath("~/images/logo.png");
var worksheet = mySpreadsheet.Document.Worksheets[0];
worksheet.Name = "Logo";
worksheet.Pictures.AddPicture(image, worksheet.Cells[0, 0]);
byte[] result = mySpreadsheet.SaveCopy(DocumentFormat.Xlsx);
DocumentManager.CloseDocument("myDoc");
Response.Clear();
//Response.AppendHeader("Set-Cookie", "fileDownload=true; path=/");
Response.ContentType = "application/force-download";
Response.AddHeader("content-disposition", $"attachment; filename={fileNameSafe}");
Response.BinaryWrite(result);
Response.End();
}
}
return new EmptyResult();
}
here is the javascript:
var exportData = function (urlExport) {
console.log('Export to link in searchController: ' + urlExport);
ExportButton.SetEnabled(false);
var objData = new Object();
var filterData = companyFilterData(objData);
console.log(filterData);
$.post(urlExport, filterData)
.done(function (data) {
console.log(data.locationUrl);
window.location.href = data.locationUrl;
});
};
When Export button is clicked exportData function is called:
var exportToLink = '#Url.Action("ExportTo")';
console.log('Export to link in index: '+exportToLink);
SearchController.exportData(exportToLink);
As I mentioned that this code works perfectly on the local machine. something weird is happening on azure webapp that ExportTo action breakpoint is never gets hit.
I am not sure what else I could change to get the ExportTo action hit?
Based on the Response Header of Azure Web App, we find that the value of Content-Length is 0. It means that no data has been sent from web app server side.
In ASP.NET MVC, we can response file using following ways.
The first way, send the file which hosted on server. For this way, please check whether the excel file has been uploaded to Azure Web App. You could use Kudu or FTP to the folder to check whether the file is exist.
string fileLocation = Server.MapPath("~/Content/myfile.xlsx");
string contentType = System.Net.Mime.MediaTypeNames.Application.Octet;
string fileName = "file.xlsx";
return File(fileLocation, contentType, fileName);
The second way, we can read the file from any location(database, server or azure storage) and send the file content to client side. For this way, please check whether the file has been read successfully. You can remote debug your azure web app to check whether the file content hasn't been read in the right way.
byte[] fileContent = GetFileContent();
string contentType = System.Net.Mime.MediaTypeNames.Application.Octet;
string fileName = "file.xlsx";
return File(fileContent, contentType, fileName);
5/27/2017 Update
Somehow the ExportTo action never gets called. It directly calls the Download action. As a result the TempData null checking is always null.
How many instances does your Web App assigned? If your Web App have multi instances, the ExportTo request is handled by one instance and the Download request is handled by another instance. Since the TempData is store in memory of dedicated instance, it can't be got from another instance. According to the remote debug document. I find out the reason why the ExportTo action never gets called.
If you do have multiple web server instances, when you attach to the debugger you'll get a random instance, and you have no way to ensure that subsequent browser requests will go to that instance.
To solve this issue, I suggest you response the data directly from the ExportTo action or save the temp data in Azure blob storage which can't be accessed from multi instances.

MVC routing changes url hash when requesting partial view via AJAX

I'm using AJAX calls to request partial views and load their html into a content area on my main Index view. I'm leveraging a hash in to the url to enable browser history support (the same way GMail url browser history works).
Everything is working fine, except after my partial view is returned and loaded, MVC seems to be clearing everything after my url hash symbol which affects the javascript browser history stack.
I have a link on my main view which initiates the request:
<div class="linkButton" data-bind="click:function(){Nav.makeRequest('#/MyController/Profile/2')}">Profile</div>
Here's the javascript that I'm using to request and load the partial views:
var Nav:function(){
var self = this;
$(window).bind("hashchange", self.onHashChange);
makeRequest: function(hash){
window.location.hash = hash;
};
onHashChange: function (e) {
var hash = window.location.hash.substring(1);
var url = 'http://localhost:3333/' + hash.substring(1);
$.get(url, function (data) {
$('#content').html(data);
});
}
}
So, one of my example requests would be for: http://localhost:3333/#/MyController/Profile/2
The request is completed successfully and my Profile view is loaded with the correct model for the id (2) passed to it in the routing and the url in the browser's navigation is what is shown above.
However after the view finishes loading, the browser's url then automatically changes to this: http://localhost:3333/#
This doesn't affect what's currently loaded on the page, but it adds this new url to the browser's history so when I hit the 'back' button it sends the request for the partial profile view again.
The only route I have in my Global.axax is the following:
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}",
new { controller = "MyController", action = "Index", id = UrlParameter.Optional }
);
I suspect that the MVC routing engine sees my request for the partial view come in (http://localhost:3333/MyController/Profile/2) and then matches it to the Default route which returns the url for my Index view, which of course is: http://localhost:3333/
I have debugged extensively on the client and the onHashChange event does indeed fire both times, once for the partial view request and then again when the url changes back to localhost:3333/# The call stack doesn't reveal any calls being made client side to cause the url to change back.
Is there a way that I can request and load my partial view using AJAX, and hashes for history support, and have the browser's url not automatically route back to the default route path?
This must what you searching for:
To manipulation with browser history you need to use new method with Html5 support
//use it in ur ajax function to save history
history.pushState({page: 1}, "title 1", "?page=1");
//and to get ur history
window.onpopstate = function(event) {
something like
$.post('url',{page:event}function(event) {
do something
})
}

Help with Ajax post to action method

I am a new to MVC an need a little help.
In my view I make an ajax post as below.
function PostCheckedPdf(e) {
var values = new Array();
$('input:checked').each(function () { values.push(this.value); });
$.post("/UnregisteredUserPreview/DownloadPdfInvoice",
{ checkedList: values });
}
This post the values of any checkboxes that are checked inside a third party Grid component (Telerik). The Action method receives the array fine and loops through each value rendering a pdf report and putting the report into a ZipStream which is attached to the Response. After the loop the zipstream is closed and I return View();
When the Action is invoked through the $.post it runs through the action method but nothing happens in the browser.
If I call the Action through an action link (with a couple of hard coded value instead of passing the checked boxes values) the zip file with all the pdfs is downloaded.
What am I doing wrong or how can I post the checked values with an ActionLink?
Thanks in Advance!
Toby.
The difference is that your ActionLink is emitting an <a> tag, which is performing a GET operation. The browser interprets the contents of the response and opens the PDF.
Your jQuery method is performing a POST, but does nothing with the response, and thus silently throws it away in the background.
You need to actually do something with the return contents, like write it out to another window.
var w = window.open('', '', 'width=800,height=600,resizeable,scrollbars');
$.post("/UnregisteredUserPreview/DownloadPdfInvoice",
{ checkedList: values },
function(content){
w.document.write(content);
w.document.close(); // needed for chrome and safari
});
You are making an Ajax call to the server there and client side code should receive the returned result which seems that you are not doing there. It should be something like below :
$.ajax({
type: 'POST'
url: '/UnregisteredUserPreview/DownloadPdfInvoice',
data: { checkedList: values },
success: function (r) {
alert(r.result);
}
});
And assume that your controller is like below :
public ActionResult DownloadPdfInvoice() {
//do you stuff here
return Json(new { result = "url_of_your_created_pdf_might_be_the_return_result_here"});
}
NOTE
If you are posting your data with anchor tag, it is better to
prevent the default action of this tag so that it won't do anything
else but the thing you're telling it to do. You can do that by adding the
following code at the end of your click event function :
$("#myLink").click(function(e) {
//do the logic here
//ajax call, etc.
e.preventDefault();
});
Have a look at the below blog post as well. It might widen your thoughts :
http://www.tugberkugurlu.com/archive/working-with-jquery-ajax-api-on-asp-net-mvc-3-0-power-of-json-jquery-and-asp-net-mvc-partial-views

Redirect after EndInvoke in ASP.NET MVC

Hi have the following code on my view (JQuery):
$.post('<%=Url.Content("~/Customer/StartLongRunningProcess")%>');
Wich invokes an asynchronous call (C#):
public void StartLongRunningProcess()
{
ProcessTask processTask = new ProcessTask();
processTask.BeginInvoke(new AsyncCallback(EndLongRunningProcess), processTask);
}
Finally, the result of the call:
public void EndLongRunningProcess(IAsyncResult result)
{
ProcessTask processTask = (ProcessTask)result.AsyncState;
string id = processTask.EndInvoke(result);
RedirectToAction("~/SubscriptionList/SubscribedIndex/" + id);
}
The redirect is ignored. Response.Redirect also fails, since the HTTP headers has been already sent. I've tried change the window.location from javascript, this works, but I'm unable to pass the parameter id by ViewData. Any idea to resolve this?
Are you sure the headers have already been sent? I'm not really up on asynchronous controllers, but I would doubt that it would start sending any headers right away. My first thought would be that a redirect response to an ajax call isn't handled by the browser. You will probably need to implement some logic that sends back a result with the URL and have your success delegate in jQuery look for that piece of data and then do the redirect through javascript (i.e. window.location).
HTH

Resources