Many a times we find a need to download a file on doing a AJAX POST request.
Normally we would just use the Response.Write to write the fileStream to the MVC Output response, as follows:
1 [AcceptVerbs(HttpVerbs.Post)] 2 public FileContentResult FunctionA(string A, DateTime B) 3 { 4 try 5 { 6 string csv = "Make it downloadable "; 7 var filresult = File(new System.Text.UTF8Encoding().GetBytes(csv), "application/csv", "downloaddocuments.csv"); 8 // return filresult; 9 10 Response.Clear(); 11 Response.Buffer = true; 12 Response.ContentType = "application/vnd.ms-excel"; 13 Response.AddHeader("content-disposition", "attachment; filename=Statement_" + "Downloadfile" + ".csv"); 14 Response.Write(csv); 15 Response.Flush(); 16 return filresult; 17 } 18 } 19 20 @using (Html.BeginForm("actionName", "controllerName")) 21 { 22 @Html.Hidden("param1", "value1") 23 @Html.Hidden("param2", "value2") 24 <button type="submit">Download file</button> 25 } 26 27 OR: 28 @Html.ActionLink( 29 "download file", 30 "actionName", 31 "controllerName", 32 new { 33 param1 = "value1", 34 param2 = "value2", 35 }, 36 null 37 )
However this can be made to happen, only by using Html.BeginForm or by using Html.ActionLink. We face many situations where we need to make a AJAX POST and download a file.
or we could use the FileResult as follows (with window.location, not for HTTP Post) :
1 public FileResult Download(string ImageName) 2 { 3 return File("" + ImageName, System.Net.Mime.MediaTypeNames.Application.Octet); 4 }
However, you can also return a FileContentResult, FilePathResult, or FileStreamResult
FileContentResult : Use this when you have obtained the file as a byte array:
return new FileContentResult(byteArray, "image/jpeg");
return new FileContentResult(byteArray, "image/jpeg");
FilePathResult : Returns a file on disk using a file path:
return new FilePathResult("~/App_Data/Images/" + ImageName,System.Net.Mime.MediaTypeNames.Application.Octet);
return new FilePathResult("~/App_Data/Images/" + ImageName,System.Net.Mime.MediaTypeNames.Application.Octet);
FileStreamResult : Sends a stream out to the response.
return new FileStreamResult(new FileStream("", FileMode.Open), "image/jpeg");
return new FileStreamResult(new FileStream("
======================================================================================
However this is fine, if in the HTTP GET we are sending simple data, as in the above example (MVC Action Method “Download”), the Image name.
Thus the download can be made to happen by just setting the window.location to the url of the MVC Download action.
But there are cases where we need to send a large collection or model to the MVC action which can only be done using HTTP POST, and thus we can no longer use the window.location solution to our rescue.
In such cases, (when we need to use AJAX to make a HTTP POST and then download a file), the solution is to use a 2 pronged approach, where we would first make the HTTP Post request, and in the controller action we would store the File content in a Memory stream. Then this MemoryStream would be placed in Session and return the name of the newly generated file to the View.
Then in the success callback of the post request, we would make another ajax call by setting the window.location to the Download MVC Action method, as shown below:
The AJAX POST Method which generates the memoryStream and stores the same in Session:
1 public ActionResult GenerateExcelReport(List<ADCModel> model) 2 { 3 if (model == null || model.Count == 0) 4 return new EmptyResult(); 5 var ms = new MemoryStream(); 6 var stream = GetResourceFileStream("ADCEnquiry.xlsx"); 7 if (stream != null) 8 { 9 using (var sl = new SLDocument(stream, "Worksheet")) 10 { 11 UpdateExcelSheet1(model, sl, 16); 12 sl.SelectWorksheet("Worksheet"); 13 sl.SaveAs(ms); 14 } 15 } 16 17 // this is important. Otherwise you get an empty file 18 // (because you'd be at EOF after the stream is written to, I think...). 19 ms.Position = 0; 20 var fName = string.Format("ADCEnquiry-{0}.xlsx", DateTime.Now.ToString("s")); 21 Session[fName] = ms; 22 return Json(new { success = true, fName }, JsonRequestBehavior.AllowGet); 23 } 24
The second MVC Action method which actually downloads the Excel file (by returning a FileResult) :
1 public ActionResult DownloadExcelReport(string fName) 2 { 3 var ms = Session[fName] as MemoryStream; 4 if(ms == null) 5 return new EmptyResult(); 6 Session[fName] = null; 7 return File(ms, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", fName); 8 }
The Javascript code, the calling code (on say click of a button) which first calls the AJAX POST Method calling @URL.Action(“Home/GenerateExcelReport”) and then in the success callback of this ajax method, we set the window.location to the second URL which returns the File Result : @Url.Action(“Home/DownloadExcelReport”)
1 $('#adcExportToExcel').click(function () { 2 $('#adcExportToExcel').spin('small'); 3 var data = $("#adcResult").data('gridData'); 4 data = JSON.stringify(data); 5 var actionUrl = '@Url.Action("GenerateExcelReport", "ADC")'; 6 var getUrl = '@Url.Action("DownloadExcelReport", "ADC")'; 7 8 $.ajax(actionUrl, { 9 contentType: 'application/json; charset=utf-8', 10 dataType: 'json', 11 type: 'POST', 12 data: data, 13 success: function (d) { 14 if (d.success) { 15 window.location = getUrl + "?fName=" + d.fName; 16 } 17 }, 18 error: function () { 19 20 } 21 }).always(function() { 22 $('#adcExportToExcel').spin(false); 23 }); 24 25 });
There are also cases where we need to simply download the current View of the page as a Excel spreadsheet. Here we could use code as follows:
1 $("#btnDownLoad").click(function (e) { 2 window.open('data:application/vnd.ms-excel,' + encodeURIComponent($('#divWhichHasTheHtmlMarkupTobeExportedToExcel').html())); 3 e.preventDefault(); 4 });
Or there are scenarios where file data is stored in database and we need to download the same as a Excel File, the following code could help:
1 public ActionResult Download(FormCollection form, int id) 2 { 3 var db = new AutoReloadDB(); 4 var objItem = (from p in db.tbl_Download where p.ID == id select p).FirstOrDefault(); 5 if(objItem!=null) 6 { 7 Response.ContentType = objItem.FileType; 8 Response.AddHeader("Content-Disposition", "attachment;filename=\"" +objItem.FileName+ "\""); 9 Response.BinaryWrite((byte[])objItem.FileData); 10 Response.End(); 11 } 12 return View(); 13 }
There is also SpreadsheetLight Nuget package, that can help you create Excel files on the fly with data retrieved from database or from a web service.
The following code could be used (along with SpreadSheetLight nuget package) to generate Excel files on the fly:
Note that this solution works, when you have a existing Excel Template (blank workbook with headers) and then you use code to create an instance of thie template and then populate the data using database repository or Web Service Repository and then download the filled workbook as XLSX:
1 private static Stream GetResourceFileStream(string fileName) 2 { 3 Assembly currentAssembly = Assembly.GetExecutingAssembly(); 4 // Get all embedded resources 5 var arrResources = currentAssembly.GetManifestResourceNames(); 6 7 return (from resourceName in arrResources where resourceName.Contains(fileName) select currentAssembly.GetManifestResourceStream(resourceName)).FirstOrDefault(); 8 } 9 10 11 public ActionResult GenerateHubReport(string processId) 12 { 13 if (string.IsNullOrWhiteSpace(processId) || !processId.IsGuid()) 14 return new EmptyResult(); 15 var ms = new MemoryStream(); 16 var stream = GetResourceFileStream("HUBExcel.xlsx"); 17 if (stream != null) 18 { 19 using (var sl = new SLDocument(stream, "Sheet1")) 20 { 21 using (RepositoryReadOnlySession) 22 { 23 var repo = ObjectLocator.GetType<IReadWriteRepository<BookingProcess>>(); 24 var process = repo.GetByKey(new Guid(processId)); 25 if (process != null) 26 { 27 UpdateHubSheet1(process, sl, 13); 28 } 29 sl.SelectWorksheet("Sheet1"); 30 sl.SaveAs(ms); 31 } 32 } 33 } 34 35 ms.Position = 0; 36 var fName = string.Format("Hub-{0}.xlsx", DateTime.Now.ToString("s")); 37 return File(ms, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", fName); 38 } 39 40
No comments:
Post a Comment