Display or Download document from Ajax call in MVC C# Application - c#

I am attempting to download an SSRS report from a MVC C# application. I have an HttpResponseBase object and am sending it back to the client to be displayed/downloaded by the user, however, when the response is sent back to the client, I am getting symbols only.
Can some one please help clarify what I am missing upon the success of the ajax call?
I have attempted to use an option which worked with some success, however, the URL is too long when selecting certain parameters.
My controller is as follows:
[HttpPost]
public ActionResult DownloadReportData(string reportName, string reportPath, string reportParams, string repID, string book, string exportFormat)
{
string extension = string.Empty;
string fileName = reportName + "_" + (book.Length > 0 ? "" + book + "_" : "") + repID;
Dictionary<string, string> dc = new Dictionary<string, string>();
if (reportParams.Length > 0)
{
string[] param = reportParams.Split(';');
int i = 0;
while (i < param.Length)
{
dc.Add(param[i].Split('=')[0], param[i].Split('=')[1]);
i += 1;
}
}
//PDF for pdf export, EXCEL for excel export
byte[] bytes = autil.Reporting.ReportsManager.GenerateReport(reportPath, dc, exportFormat);
Response.ClearContent();
if (exportFormat.ToLower() == "pdf")
{
Response.ContentType = "application/pdf";
extension = ".pdf";
//Response.AppendHeader("Content-Disposition", "inline;filename=" + fileName + "" + extension + "");
Response.AppendHeader("Content-Disposition", "attachment;filename=" + fileName + "" + extension + "");
}
else if (exportFormat.ToLower() == "excel")
{
//Response.ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
Response.ContentType = "application/vnd.ms-excel";
extension = ".xls";
Response.AppendHeader("Content-Disposition", "attachment;filename=" + fileName + "");
}
//Response.AppendHeader("Content-Disposition", "inline;filename=" + fileName + "" + extension + "");
Response.BufferOutput = true;
Response.AddHeader("Content-Length", bytes.Length.ToString());
Response.BinaryWrite(bytes);
Response.End();
//return null;
return Json(Response);
}
My view is as follows:
<div id="target"></div>
My javascript (ajax call) is as follows:
<script>
$.ajax({
type: 'POST',
url: '#Url.Action("DownloadReportData", "Reports")',
data:
{
reportName: reportName,
reportPath: reportPath,
reportParams: reportParams,
book: bookCode,
exportFormat: exportType
}
, success: function (data) {
$("#target").append($("<iframe>", {
srcdoc: data
}));
if (data.error == "none") {
alert("success" + data);
}
}
});
</script>
I am expecting either a downloaded version of the report or a prompt to Open (inline) or Save As to appear once the call is completed.
I am currently experiencing the following to be displayed:
%PDF-1.3 1 0 obj [/PDF /Text /ImageB /ImageC /ImageI] endobj 5 0 obj << /Length 1968 /Filter /FlateDecode >> stream x��[]o7}G��DZ��{�7��REi�H�Z�!�R�]�I������kdz��i#D���{αϽ���9��7h��|5�SM6�Y��Ҍt�t�J���4�DkC�$����9������.{�����y�� =����'�������Q�j ���]��]^����E���gG�^M�c�ʨ��/�A(�<�����a��"�>y�����b/��Ś�

I faced the same issue when I was trying to download excel from my ajax call. After many days I got the answer that you can't (I read it somewhere on SO). So I converted my ajax call to this
<div class="col-md-3">
#Html.ActionLink("Export Report", "ExportExcel", "Training",
new { }, new { #class = "btn btn-danger" })
</div>
and after that my code worked. You might have to make a few changes in order for this to work. I hope this help.

Related

Error code 0x800704CD when generating PDF to zip

I know this question was asked & answered before, but the solutions don't work for me. I have to dynamically create a list of PDFs, and each row has a checkbox. You check the PDFs you want to download, whose ID get passed to a function to create the PDF. I store those PDF's in a list, which gets passed to the Zip function (DotNetZip). Problem is, when first generating a PDF, I get that error somewhere in the middle of creating it, about halfway through, when adding a new page in the PDF. Usually it's the same spot but occasionally it changes where it crashes. Could anyone look at my code and point out where I'm messing up?
protected void Download_PDF_Click(object sender, EventArgs e)
{
Response.Clear();
Response.BufferOutput = false;
HttpContext c = HttpContext.Current;
String archiveName = "Arhiva Inspectii.zip";
Response.ContentType = "application/zip";
Response.AddHeader("content-disposition", "filename=\"" + archiveName + "\"");
int nr_rows = tbl_inspectii.Rows.Count - 1;
foreach (String id_chbx in ID_Checkbox)
{
CheckBox chbx = tbl_inspectii.FindControl(id_chbx) as CheckBox;
String PDF_id = "";
if(chbx != null)
{
if(chbx.Checked)
{
PDF_id = chbx.ID.Replace("ChBx", string.Empty);
Create_PDF(PDF_id);
}
}
}
string destdir = Server.MapPath("~/Zip/") + archiveName;
using (ZipFile zip = new ZipFile())
{
zip.AddFiles(PDF_list, destdir);
zip.Save(destdir);
}
}
protected byte[] Create_PDF(String id_insp_max)
{
using (MemoryStream ms = new MemoryStream())
{
Document brosura = new Document(PageSize.A4);
brosura.SetMargins(40, 40, 40, 40);
PdfWriter wri = PdfWriter.GetInstance(brosura, ms);//new FileStream(Server.MapPath("Pdf/") + titlu_pdf + ".pdf", FileMode.Create)
HttpContext.Current.Response.AppendHeader("Content-Disposition", "inline; filename=\"" + titlu_pdf + ".pdf\"");
PdfWriter.GetInstance(brosura, HttpContext.Current.Response.OutputStream);
brosura.Open();
//lots of SQL, and brosura.Add();
//at some point, a brosura.Add() has the error halfway through the pdf
brosura.Close();
PDF_list.Add(titlu_pdf);
wri.Close();
return ms.ToArray();
}
}
Try to move the Response block after you closed the Document.
And try to add Response.End();
And why are you return the Array in Create_PDF but does not using it?

How to force a download of a created file to a users computer c#

I am looking to allow a person to to export journal entries into a text file. I can create a file with all the data but rather strictly saving the file somewhere specific I want to allow a user to download and save the file where they want on their computer. How to I force a download of a file after I create it with StreamWriter. I currently have the following code:
string fileName = "Journal.txt";
using (StreamWriter journalExport = new StreamWriter(fileName))
{
foreach (JournalEntryView entry in journalEnteries)
{
//write each journal entery to file/document
journalExport.WriteLine(entry.timestamp + " - " + entry.author + " (" + entry.authorRole + ")");
journalExport.WriteLine(entry.text);
journalExport.WriteLine("");
journalExport.WriteLine("");
}
}
I am also trying to put this into an ActionResult and return the file.
EDIT:
The following code is my new current code and the direction I am looking to go in, but when I use an ActionLink to call this method, i just get redirected to a new page rather than downloading the file.
string fileName = "Journal.txt";
string filepath = ConfigurationManager.AppSettings["DocumentRoot"] + "\\" + id + "\\" + fileName;
using (StreamWriter journalExport = new StreamWriter(filepath))
{
foreach (JournalEntryView entry in journalEnteries)
{
//write each journal entery to file/document
journalExport.WriteLine(entry.timestamp + " - " + entry.author + " (" + entry.authorRole + ")");
journalExport.WriteLine(entry.text);
journalExport.WriteLine("");
journalExport.WriteLine("");
}
}
byte[] fileData = System.IO.File.ReadAllBytes(filepath);
string contentType = MimeMapping.GetMimeMapping(filepath);
var cd = new System.Net.Mime.ContentDisposition
{
FileName = fileName,
Inline = true,
};
Response.AppendHeader("Content-Disposition", cd.ToString());
return File(fileData, contentType);
This might be what you are looking for:
public ActionResult GetFile()
{
...processing stuff...
return File("/files/file.pdf", "application/pdf");
//or
return File("/files/file.pdf", "application/force-download", "donwloadname.pdf");
}

HTTP Header Issue

I'm working on MVC5 project on .NET 4.5.3. I have View with #Html.BeginForm and FormMethod.Post this view calls [HttpPost] ActionResult. In the Controller i get necessary ID's from the submitted form and then pass them for exporting.
[HttpPost]
public ActionResult PrepareForExport()
{
ExportService export = new ExportService();
if (Request.Form["button"] != null)
{
string selected = Request.Form["button"].ToString();
export.GeneratePdf(ptoRequestsService.GetByID(Convert.ToInt32(selected)));
}
else if (Request.Form["toExport"] != null)
{
List<PtoRequest> ptoListForExport = new List<PtoRequest>();
string selectedItems = Request.Form["toExport"].ToString();
string[] selectedList = selectedItems.Split(',');
foreach (var pto in selectedList)
{
ptoListForExport.Add(ptoRequestsService.GetByID(Convert.ToInt32(pto)));
}
export.GenerateZip(ptoListForExport);
}
return RedirectToAction("Requests" + LoggedUser.ID);
}
And in the ExportService class i have this export method.
public void GenerateZip(List<PtoRequest> approvedPtos)
{
byte[] pdfContent = null;
string dateFormat = "yyyy-MM-dd";
string filePath = null;
if (!Directory.Exists(HttpContext.Current.Server.MapPath(#"~/Files/PdfFiles/")))
{
Directory.CreateDirectory(HttpContext.Current.Server.MapPath("~/Files/PdfFiles/"));
filePath = HttpContext.Current.Server.MapPath("~/Files/PdfFiles/");
}
else
{
filePath = HttpContext.Current.Server.MapPath("~/Files/PdfFiles/");
}
foreach (var Pto in approvedPtos)
{
pdfContent = FillPdfTemplate(Pto);
string fileName = Pto.User.FirstName + " " + Pto.User.LastName + "_" + Pto.StartDate.ToString(dateFormat) + ".pdf";
string fileDirectory = filePath + fileName;
using (FileStream fs = new FileStream(fileDirectory, FileMode.OpenOrCreate))
{
fs.Write(pdfContent, 0, pdfContent.Length);
}
}
string zipName = String.Format("Report_{0}.zip", DateTime.Now.ToString("yyyy-mm-dd-HHmmss"));
string zipFile = filePath + zipName;
using (ZipFile zip = new ZipFile())
{
zip.AddDirectory(filePath);
HttpContext.Current.Response.Clear();
HttpContext.Current.Response.BufferOutput = false;
HttpContext.Current.Response.ContentType = "application/zip";
HttpContext.Current.Response.AddHeader("content-disposition", "attachment; filename=" + zipName);
zip.Save(HttpContext.Current.Response.OutputStream);
HttpContext.Current.Response.Write(zip.ToString());
HttpContext.Current.Response.End();
Directory.Delete(filePath, true);
}
}
Everything works fine, but when my method complete the job i got exception with code 500. I do my googling to understand my problem and it is something like this 1. I understand that the problem is in HttpHeader but did not understand how to solve it in my case. Then i tried the solution with if (!Response.IsRequestBeingRedirected) but i still got this exception. After this I tried to return ZipFile from GenerateZip Method and instead calling Response in ExportService class to call it at Controller, but i still got this Error. I have an idea to remove [HttpPost] attribute and to take my ID's in other way but my point is to do it that way. Can anyone point me in any direction to solving this? What is the right decision, where should i call Response for this case, is it option to write jQuery script to prevent submitting form in .cshtml?
You are calling HttpContext.Current.Response.End(); in your GenerateZip method and then attempting to Redirect the user in the ControllerAction. This is causing your "Headers cannot be written after response is sent" error. Instead of that, return a byte array or Stream object (FileStream?) from the GenerateZip method and return a FileResult in the Action. Redirecting the user after the file download can (only?) be achieved using JavaScript. Here is an example. How to return a FileResult AND RedirectToAction in ASP.NET MVC

Method to return a file or a view

I am using the jquery below to call a GetFile method.
I want to generate a text file based on some conditions. This will not always
generate a text file all the time.
If condition = True generate the file
If condition = False return back to the main page
Right now if i do return, I just get a blank page.
How can I write this such that my two conditions are considered?
<script language="javascript" type="text/javascript">
$(document).ready(function () {
$("#Button1").click(function () {
var SelCat = $("#mylist").val();
if (SelCat != 0) {
var data = {
categoryId: SelCat
}
var url = '#Url.Action("GetFile", "Home")' + '?' + $.param(data);
window.location.assign(url);
} else {
alert("You need to select an city");
}
});
});
</script>
public void GetFile(int categoryId)
{
var fileName = "ErrorMessages.txt";
StringBuilder strObj = new StringBuilder();
for (int i = 0; i <= 20000; i++)
{
strObj.Append("Number:" + i + " " + "Here is the first line" );
strObj.Append(Environment.NewLine);
}
strObj.ToString();
// ...
// Based on some conditions determine whether to return a file or not
// return;
Response.Clear();
Response.ContentType = "text/plain";
Response.AddHeader("content-disposition", "attachment; filename=" + fileName );
Response.Write(strObj);
//Response.BinaryWrite(strObj);
Response.End();
}
IMHO:
Change your GetFile to be an ActionResult.
If the file can be written
a. Writing to a memory stream (instead of response)
b. return File(memoryStream, "text/plain");
If it can't be written
a. return Redirect("~/Main/Page");
Pseudo code:
public ActionResult GetFile(Int32 CategoryId)
{
if (/*can be written*/)
{
using (MemoryStream stream = new MemoryStream())
{
using (StreamWriter writer new StreamWriter(stream))
{
/* writer.Write(...); */
}
return File(stream, "text/plain");
}
}
return Redirect("~/main/Page"); // or redirectToRoute/RedirectToAction
}
And for reference: File() response methods
FWIW: You can continue to use the StringBuilder and pass that to one of the File() overloads, I guess I just prefer streams. :shrug:

Response.WriteFile writing the content twice

Here is my code..
string attachment = "attachment; filename=Call-Details-Report-" + startDate.SelectedDate.Value.ToString("MM-dd-yyyy") + ".csv";
Response.Clear();
Response.ClearHeaders();
Response.ClearContent();
Response.AddHeader("Content-Disposition", attachment);
Response.ContentType = "text/csv";
Response.AddHeader("Pragma", "public");
Response.WriteFile(downloadLocation+"\\"+fileName);
Response.End();
I'm using the above code to download a csv file from a location.But surprisingly the contents gets written twice or some times thrice into the file that i download though actually it isn't so with the file on server.I'm writing my code in c#.
The above piece of code works perfectly in local machine,but the issue is in Production server.
Here is my complete Method
private void DownloadReport(string query)
{
string downloadFolderPath = "";
string filePath = "";
string dbAndApplicationServerStatus = ConfigurationManager.AppSettings["SameDBAndApplicationServer"] != null ? ConfigurationManager.AppSettings["SameDBAndApplicationServer"] : "1";
if (dbAndApplicationServerStatus == "0")
{
Log.Write("So the DB And Application are on differrent servers,hence trying to read Download folder path on DB Server....");
downloadFolderPath = ConfigurationManager.AppSettings["ReportDownloadLocation"] != null ? ConfigurationManager.AppSettings["ReportDownloadLocation"] : "-1";
Log.Write("Download Path is " + downloadFolderPath);
}
else
{
Log.Write("So the DB and Application and Db are on same server......");
downloadFolderPath = Server.MapPath("Download");
downloadFolderPath = downloadFolderPath.Replace("\\", "//");
if (!Directory.Exists(downloadFolderPath))
{
Directory.CreateDirectory(downloadFolderPath);
}
Log.Write("Download Path is " + downloadFolderPath);
}
string status="";
StringBuilder headerQuery = new StringBuilder();
StringBuilder rowQuery = new StringBuilder();
StringBuilder sqlQuery = new StringBuilder();
filePath = downloadFolderPath;
string folderName = DateTime.Now.ToString("MM-dd-yyyy");
string timeStamp = DateTime.Now.ToString("MM-dd-yy-HH-mm-ss");
string fileName = "Call-Details-Report-" + startDate.SelectedDate.Value.ToString("MM-dd-yyyy") + "_" + timeStamp + ".csv";
filePath = filePath + "/" + fileName;
bool commaRequired = false;
sqlQuery.Append("SELECT * INTO OUTFILE '");
sqlQuery.Append(filePath);
sqlQuery.Append("' FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '\"' LINES TERMINATED BY '\n' FROM (");
headerQuery.Append("Select ");
rowQuery.Append("(Select ");
#region Creating Query
/*Sql Query is Created in this region*/
#endregion
if (!CdrSearch.WriteReportToFile(sqlQuery.ToString(),out status))
{
Log.Write("Failed to generate the file to download......");
WebPagesHelper.ShowMessage(ref lblMessage, WebPagesHelper.MessageType.Message, status);
}
else
{
Log.Write("Succesfully generated file to Download");
string downloadLocation = Server.MapPath("Download");
if (dbAndApplicationServerStatus == "0")
{
WebClient webClient = new WebClient();
string path = ConfigurationManager.AppSettings["DownloadURL"] != null ? ConfigurationManager.AppSettings["DownloadURL"].ToString() : "";
if (!Directory.Exists(downloadLocation))
{
Directory.CreateDirectory(downloadLocation);
}
if (File.Exists(downloadLocation + "\\" + fileName))
{
File.Delete(downloadLocation + "\\" + fileName);
}
webClient.DownloadFile(path + fileName, downloadLocation + "\\" + fileName);
}
Log.Write("Configured Download Location on Application" + downloadLocation);
string attachment = "attachment; filename=Call-Details-Report-" + startDate.SelectedDate.Value.ToString("MM-dd-yyyy") + ".csv";
Response.Clear();
Response.ClearHeaders();
Response.ClearContent();
Response.AddHeader("Content-Disposition", attachment);
Response.ContentType = "text/csv";
Response.AddHeader("Pragma", "public");
Log.Write(downloadLocation + "\\" + fileName);
Response.WriteFile(downloadLocation+"\\"+fileName);
Response.SetCookie(new HttpCookie("DStatus", "Completed"));
Response.End();
}
}
And the above method is called only once straight away on click of a button,so no question of any looping here.
You may check what the following lines are meant for :
webClient.DownloadFile(path + fileName, downloadLocation + "\\" + fileName);
Response.WriteFile(downloadLocation+"\\"+fileName);
Give a try by commenting out one of them, if they are really doing the same.
As a safe measure, please disable the button until your download is finished.
When i tried the following code (even published on IIS), it is just downloading once, as expected.
protected void Button1_Click(object sender, EventArgs e)
{
string attachment = "attachment; filename=Call-Details-Report-" + DateTime.Now.ToString("MM-dd-yyyy") + ".txt";
Response.ContentType = "text/html";
Response.AddHeader("Content-Disposition", attachment);
Response.AddHeader("Pragma", "public");
Response.WriteFile(#"C:\test.txt");
Response.SetCookie(new HttpCookie("DStatus", "Completed"));
Response.End();
}
There's obviously something hinky going on. You've said that it works in dev but not Prod - Are you using the same server config in both environemts (ie are you using 1 server in dev but 2 in prod?)
You've potentially got 3 steps, assuming I've understood your code...
Generate Report from SQL and write it to a file
If the file is stored on a different server, download it to the web server
Serve it
So, in the more complex scenario (which I assume Production is), At what step in the process do you start to see double entries? On the server the report is generated on, on the web servers' copy or only in the client after getting it from the Webserver?
I can't see any reason why your code to serve the file to the client would be duplicating data so can only assume it's happening before there at some point.
It would be helpful if you could use Firebug/Fiddler/??? to post the exact contents of the transmission from the webserver to the client
(Incidentally, you may want to look at the System.IO.Path class for manipulating paths, it makes your code more readable and robust - No more worrying about trailing slashes!)
ok so the real culprit was Response.WriteFile in this case.In my case i guess since the size of data was quite huge Response.WriteFile was not working as expected.I found some where that in case of large file downloads, its best to use Response.TransmitFile.Left with no other option i changed my code and used Response.TransmitFile and eureka! the problem was solved.No more duplicate records in downloaded file.Though the reason is still unknown, Response.TransmitFile solved the issue.....

Categories

Resources