I have this method here:
[Authorize]
[HttpPost]
[ValidateInput(false)]
public void ExportCostMatrixExcel(string GridHtmlExcel, string GridCommunityExcel)
{
Response.ClearContent();
Response.ClearHeaders();
Response.BufferOutput = true;
Response.ContentType = "application/excel";
Response.AddHeader("Content-Disposition", "attachment; filename=Reliquat.xlsx");
Response.Write(GridHtmlExcel);
Response.Flush();
Response.Close();
Response.End();
}
This takes me html table and converts it over to an Excel spreadsheet, when I try to open the file, I get this error message:
Excel cannot open the file 'Reliquat.xlsx' because the file format
or file extension is not valid. Verify that the file has not been
corrupted and that the file extension matches the format of the file.
Why is this happening, you can see GridHtmlExcel here on the link below, its an HTML table with colspans, is the colspans messing it up?
https://jsfiddle.net/2nyjhpaz/3/
In essence it looks like you're merely dumping the contents into a file and just renaming it to an XLSX. However, that extension follows a specific XML-based schema, and that's why it doesn't play well.
You have a few options:
Find a library that can do this for you - initial searches list a few but they're often fickle little beings.
Use something like HTML Agility Pack to parse the HTML into a usable format and write it into an excel file. You might have to create an excel file manually, possibly using the Office Interop stuff.
If the excel format itself isn't that much of an issue, you could choose to write a CSV file instead (and can be opened by excel), using CSV Helper - but you'd still have to parse the HTML.
Related
I have an ASP.NET MVC application with an ActionResult called GenerateReport. I'm trying to return a byte array to save an Excel file. Here are snippets:
var contentType = "application/vnd.ms-excel";
var fileName = "Statistics.xlsx";
...
var fileBytes = package.GetAsByteArray();
return File(fileBytes, contentType, fileName);
When I'm prompted to save the file, it sometimes (but not always) asks what I want to do with "GenerateReport". It's naming the file the same as the ActionResult and it's not giving it a file type. I will request to save it and it will say that it failed to save. I will select Retry and it will save fine. Then, if I rename it to an .xlsx, all of the data is there and correct. I'm using IE9 and Chrome and I haven't noticed it happen in Chrome. Unfortunately, it needs to work in IE9.
Does anyone know why it's not getting my content type and file name sometimes?
Check the response for Content-Encoding. It might be the same issue like here PHP File Serving Script: Unreliable Downloads?
I usually use Response.Write to export my reports to an excel file.
E.g
Response.Clear();
Response.Buffer = true;
Response.AddHeader("content-disposition", "attachment; filename=MyFile.xls");
Response.ContentType = "application/vnd.ms-excel";
Response.Write("<meta http-equiv=Content-Type content=text/html;charset=UTF-8>");
Response.Write("<table>");
Response.Write("<tr>");
Response.Write("<td>");
Response.Write("Hello");
Response.Write("</td>");
Response.Write("</tr>");
Response.Write("</table>");
Response.End();
Now i want to export an excel file with multiple sheets.
How can i accomplish this?
Tehnically speaking this is not export to excel, but you send a html with a wrong headers to trick browser to open this content with excel.
Better use NPOI (xls) or / and EPPlus (xlsx) and fully control your excel export and then you can create new sheets and more.
Update: In this SO answer you can see example of ashx handler that returns excel file that is created from DataTable :
C# create/modify/read .xlsx files
I've written an ASHX generic handler to output XML. However, for some reason, ASP.net is appending numerous whitespace characters to the end of the output which breaks the XML.
My code looks like this:
context.Response.ContentType = "text/xml";
XmlSerializer oSerializer = new XmlSerializer(typeof(ModelXml[]),new XmlRootAttribute("rows"));
System.IO.MemoryStream ms2 = new System.IO.MemoryStream();
System.Xml.XmlTextWriter tw = new System.Xml.XmlTextWriter(ms2,new System.Text.UTF8Encoding());
oSerializer.Serialize(tw,models);
string s = System.Text.Encoding.UTF8.GetString(ms2.GetBuffer());
tw.Close();
ms2.Close();
context.Response.Write(s.Trim());
context.Response.End();
When I run this code thru the debugger, I see that the string s does indeed contain the XML data with NO WHITESPACE. However, when I point Internet Explorer at this file, I get the following error:
The XML page cannot be displayed
Cannot view XML input using XSL style sheet. Please correct the error and then click the Refresh button, or try again later.
--------------------------------------------------------------------------------
Invalid at the top level of the document. Error processing resource 'http://localhost:5791/XXXXX.ashx'.
When I view the page source in Notepad, I see that the file begins with the correct XML, but there are numerous spaces appended to the end. If I remove these spaces, the XML file works fine with my browser and applications.
Why is ASP.net appending these spaces to my output and what can I do about it?
Switch from MS2.GetBuffer() to MS2.ToArray(). You are reading the buffer from the MemoryStream, which is preallocated for efficiency. You want just the used data, not the whole buffer.
Instead of serializing to a MemoryStream, you should serialize directly to Response.Output.
This should solve the issue.
I am working on a website at the moment which is displaying a strange bug with generated word documents. The site has a feature on it which allows the user to download a word document containing information related to their visit. This file is generated via some vb.net code and takes an xml template of the final document and inserts the relevant content required.
The strange behaviour is that on some machines the .doc file generated displays fine and on others it displays as XML when opened in Word. Both behaviours have been seen in the same version of Office (2003) but on seperate machines. My question is really whether the error lies with the set up of word on the individual machines, or whether there is an error in the code.
The code to create the file and download it is as follows:
Response.Clear()
Response.ClearHeaders()
Response.AddHeader("content-disposition", "inline; filename=MyNewFile")
Response.ContentType = "application/msword"
'Create the word file as a byte array based off an xml template document'
Dim objWordGenerator As New WordFileGenerator
Response.BinaryWrite(objWordGenerator.GetWordBytes)
Response.Flush()
Response.Clear()
Response.End()
The actual xml template is quite large so probably not suitable to post here but I can provide any more information if necessary.
Update:
Having managed to fix the original bug (it turns out that the original filename being used didn't have the .doc extension) I have found another bit of strange behaviour.
When the file is opened it opens in Word correctly, however when you go to save it the default file type is XML. When saved as an XML file it will open in Word correctly, but I feel this is slightly confusing behaviour for the end user. I would like the file to default to saving as a DOC file instead. Is there a way to force this to happen?
Update 2:
Below is a section of the XML that relates to the Document properties. The rest of the document deals with content and styles etc, so my assumption is that this is the most relevant section. To reiterate, my problem is that when the downloaded .doc file is opened in word, the default "save as" option is as an XML file.
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<?mso-application progid="Word.Document"?>
<w:wordDocument xmlns:w="http://schemas.microsoft.com/office/word/2003/wordml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w10="urn:schemas-microsoft-com:office:word" xmlns:sl="http://schemas.microsoft.com/schemaLibrary/2003/core" xmlns:aml="http://schemas.microsoft.com/aml/2001/core" xmlns:wx="http://schemas.microsoft.com/office/word/2003/auxHint" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:dt="uuid:C2F41010-65B3-11d1-A29F-00AA00C14882" w:macrosPresent="no" w:embeddedObjPresent="no" w:ocxPresent="no" xml:space="preserve">
<o:DocumentProperties>
<o:Title>Fancy Word Doc</o:Title>
<o:Author>Bob Bobertson</o:Author>
<o:Characters>999</o:Characters>
<o:Company>A Fancy Company</o:Company>
<o:Version>1.1.1</o:Version>
</o:DocumentProperties>
Cheers
The File -> SaveAs filetype is XML because that is what the file open in Word is. If you want it to say 'Word Document (*.doc) then you will need to create a real Word document on the server and not an XML. Just by putting a .doc extension on the filename doesn't change it's real contents. Word knows the file type that is loaded into it and suggests that as the file type when saving. I don't know of any way to override this behavior.
I've been using Office XML with Excel for awhile now and this is very similar to the code that I'm using to send it down to the client. You might want to try and see if it works for you.
Dim xml As XmlDocument = New XmlDocument()
xml.Load("report.doc")
Response.ContentType = "application/vnd.ms-word"
Response.AppendHeader("CONTENT-DISPOSITION", "attachment; filename=report.doc")
Response.Write(xml.OuterXml)
Try it with firefox and you will probably find that it will be saved with the correct extension.
IIRC, since version 3 IE prefers to ignore the mime type and sniff the file content to see what the "correct" file format is. Maybe is uses the magic cookie?
Is this Word 2007 or later? Try
Response.AddHeader("content-disposition", "attachment; filename='MyNewFile.doc'")
attachment encourages the browser to save the file instead of displaying it.
I ran some tests and could not reproduce your problem on my system in Word 2003. Without a specific example (and actual file that is misbehaving), it would be pure speculation to make any suggestions.
For some reason when I download a zip folder from my server's folder it is always corrupted. Here is the code:
protected void gvFiles_RowCommand(object sender, System.Web.UI.WebControls.GridViewCommandEventArgs e)
{
string fileUrl = String.Empty;
if(e.CommandName.Equals("DownloadFile"))
{
fileUrl = e.CommandArgument as String;
string fileName = Path.GetFileName(fileUrl);
Response.AppendHeader("content-disposition",
"attachment; filename=" + fileName);
Response.ContentType = "application/zip";
Response.WriteFile(fileUrl);
Response.End();
}
}
And here is how the GridView is populated:
private void BindData()
{
List<SampleFile> files = new List<SampleFile>();
for(int i=1;i<=3;i++)
{
SampleFile sampleFile = new SampleFile();
sampleFile.Name = "File " + i;
sampleFile.Url = Server.MapPath("~/Files/File"+i+".txt");
files.Add(sampleFile);
}
SampleFile file = new SampleFile();
file.Name = "Zip File";
file.Url = Server.MapPath("~/Files/WebSiteNestedMasters.zip");
files.Add(file);
gvFiles.DataSource = files;
gvFiles.DataBind();
}
I don't know asp.net at all, but this is pretty commonly a result of doing the download in a text mode instead of binary mode. Line ending characters get converted from \r\n to \n or vice versa, and everything goes nuts.
As #lassevk suggested you should download the corrupted zip file and compare it with the original file on the server. Are both the same length? Use a hex editor to inspect the contents of the file. In this related thread you are saying that if you point the browser directly to the zip file it is also corrupted, meaning that the problem is probably not related to the headers but something wrong with IIS. Are you using some third party ISAPI extensions that could modify the file?
Assuming that you need to do this: (e.g. you are unable to provide a direct link)
<a href='<% Eval("Url")) %>'>download</a>
I'll first make an observation that having a RowCommand handler start to return a file isn't the way to do this. Create some sort of download link to a different page. Separate file downloading from the rest of the page containing the grid.
now ...
You've got the basics about right, but like a comment in this CodeProject tutorial you're soon going to run into issues.
Where the above code sample falls
down:
not responsive to user
the code will keep going and you'll have no
idea if the user actually downloaded
the file (it may not matter to you)
won't work with all browsers
cannot resume a download
doesn't show progress
larger files will use more server memory and take longer to stream
and a lot of downloads will mean the server is going to take a resource hit
whereas you might want ..
works just like a clicked download (ie using get, not post)
works in all browsers on all platforms in the way the user expects
(ie filename hint works on things like
IE for Mac or Netscape 4.1).
show download progress
resumable downloads
tracking all downloads and knowing when downloads complete
looks like a file, even if it isn't
expiration on url.
allows for high concurrent # of downloads for any size of file
Although written in VB .net1.1 the article Tracking and Resuming Large File Downloads in ASP.NET [devx] is far better at explaining how to do it correctly.
Easy code is easy but doesn't always work, and efficiently streaming files to users 100% of the time takes a little more effort than setting a content header and shoveling some bits down the wire.
First, verify that the contents of the file actually looks like a zip file. You can do that simply by dropping the file into notepad, and looking at the contents. If the file starts with PK and some funny characters, it might be a zip file. If it contains HTML, that might be a clue as to why it is going wrong.
Looking at your code, it struck me that you're passing the url to the file to Response.WriteFile. Does Response.WriteFile handle url's or does it use local filenames? Perhaps everything up to Response.WriteFile works, and thus the right headers are sent, etc. but then the code crashes with an exception, which might make your "zip file" contain a HTML error message.
Edit: Ok, first problem-solving step completed. File looks like a zip file in notepad, not a HTML error message.
Next step, since the file resides on the server, try writing an url directly to the file, instead of going through your application. Does that work?
If not, try a different zip program (which one are you using by the way?).
As an example of why you should try the file directly and different unzipping programs. FinalBuilder produces zip files that PowerArchiver complains about, but 7zip does not, so there might be something wrong with either the file or your program, or possibly even both.
But verify that the files are correct, can be opened, both if you don't download them at all, and check what happens if you download them directly outside of your application.
Edit: Ok, you've verified that the file won't open even if you download it directly from your server.
How about opening the file in just explorer, bypassing the web server altogether? For instance, since it looks like you have the file locally, try just navigating to the right folder and opening the file directly. Does that work?
Here is the updated code:
fileUrl = e.CommandArgument as String;
string fileName = Path.GetFileName(fileUrl);
FileStream fs = new FileStream(fileUrl, FileMode.Open);
byte[] buffer = new byte[fs.Length];
fs.Read(buffer, 0, (int) fs.Length);
fs.Close();
Response.Clear();
Response.AppendHeader("content-disposition",
"attachment; filename=" + fileName);
Response.ContentType = "application/octet-stream";
Response.AppendHeader("content-length", buffer.Length.ToString());
Response.BinaryWrite(buffer);
Response.Flush();
Response.End();
Here is the fiddler stats:
HTTP/1.1 200 OK
Server: ASP.NET Development Server/8.0.0.0
Date: Wed, 17 Dec 2008 22:22:05 GMT
X-AspNet-Version: 2.0.50727
Content-Disposition: attachment; filename=MyZipFolder.zip
Content-Length: 148
Cache-Control: private
Content-Type: application/x-zip-compressed
Connection: Close
I just tried the same code with the .docx file and same result (The file is corrupted): Here is the code:
if(e.CommandName.Equals("DownloadFile"))
{
fileUrl = e.CommandArgument as String;
string fileName = Path.GetFileName(fileUrl);
FileInfo info = new FileInfo(fileUrl);
Response.Clear();
Response.ClearContent();
Response.ClearHeaders();
Response.Buffer = true;
Response.AppendHeader("Content-Length",info.Length.ToString());
Response.ContentType = GetContentType(fileUrl);
Response.AppendHeader("Content-Disposition:", "attachment; filename=" + fileName);
Response.TransmitFile(fileUrl);
Response.Flush();
Response.End();
}