I have a button in my web page that will export CSV files. There are 5 files in total. When the client clicks the button, the server will create the files, compress them into one ZIP file, then send the ZIP file to the client for download.
I have heard around the forums about SharpZipLab and DotNetZip, but I haven't explored any yet. I have also heard using System.IO.Compression. Which of these methods would you recommend?
I have this code to create the 5 CSV files:
StringBuilder sb = new StringBuilder();
DataTable[] dtCSV =
{
file1BLO.SelectFile1ForCSV(),
file2BLO.SelectFile2ForCSV(),
file3BLO.SelectFile3ForCSV(),
file4BLO.SelectFile4ForCSV(),
file5BLO.SelectFile5ForCSV()
};
for (int i = 0; i <= 4; i++)
{
DataTable dt = dtCSV[i];
foreach (DataRow dr in dt.Rows)
{
string[] fields = dr.ItemArray.Select(field => field.ToString()).ToArray();
sb.AppendLine(string.Join("|", fields));
}
Response.ContentType = "application/text";
Response.AddHeader("content-disposition", "attachment;filename=CAPRES-FILE" +
(i + 1) + "-" + DateTime.Now.ToString("yyyyMMdd-HHmmss") + ".txt");
Response.Output.Write(sb);
Response.Flush();
sb.Clear();
}
Response.End();
EDIT I'm using ASP.NET v4.0.
EDIT 2 Apparently I have System.IO.Compression, which is weird because I though it is only supported in v4.5. Coincidentally, I don't have System.IO.Packaging.
With the help of Sachu, we were able to accomplish this requirement. We used DotNetZip over SharpZipLib due to its licensing issues.
In facilitate our development of this functionality, I ought to create a program flow based on my requirements:
Create text files
Add the text files to a folder
Compress this folder in Zip format
Send to client using Response
Delete files
Step 0 - Setup Project
Before we start the process, we must prepare the project. This include adding necessary folders and instantiate variables.
First we add a folder to which we will 'temporarily' add the text files. This folder will also be the one that will get compressed. I decided to create the folder in the root directory of the project with the name CSV.
Now we'll be using the DotNetZip library. You can download it here. Add the library to your project references. Then add the using, which is using Ionic.Zip;.
Then we instantiate the variables such as the zipFileName, textFileName, etc. The names speak for themselves.
The data that I'll be using for the text files will be from the DataTable[] array, which each DataTable corresponding to a specific SQL query.
DataTable[] dtCSV =
{
file1BLO.SelectFile1ForCSV(),
file2BLO.SelectFile2ForCSV(),
file3BLO.SelectFile3ForCSV(),
file4BLO.SelectFile4ForCSV(),
file5BLO.SelectFile5ForCSV()
};
StringBuilder sb = new StringBuilder();
string textFileNameTemplate = Server.MapPath(#"~\CSV") + #"\file";
Response.Clear();
Response.BufferOutput = false;
Response.ContentType = "application/zip";
Response.AppendHeader("content-disposition", "attachment;filename=CAPRES-" +
DateTime.Now.ToString("yyyyMMdd-HHmmss") + ".zip");
Step 1 - Create Text Files
This is fairly easy. I used a StringBuilder to convert the results from the DataTables. Using this, I then used a StreamWriter to build the text files themselves.
for (int i = 0; i <= 4; i++)
{
DataTable dt = dtCSV[i];
foreach (DataRow dr in dt.Rows)
{
string[] fields = dr.ItemArray.Select(field => field.ToString()).ToArray();
sb.AppendLine(string.Join("|", fields));
}
string textFileName = textFileNameTemplate + (i + 1) + ".txt";
var textFile = new StreamWriter(textFileName);
textFile.WriteLine(sb.ToString());
textFile.Flush();
textFile.Close();
}
Notice how I used the textFileNameTemplate variable. I append the iterator and a .txt file extension. Therefore, we will have files named file1.txt, file2.txt, file3.txt, etc.
Step 3 & 4 - Compress The Folder & Send To Client
Now we can proceed with the zipping. We modified the code in Step 2 to accommodate the library.
using (ZipFile zip = new ZipFile()) //encapsulate Step 2 code in this code block
{
for (int i = 0; i <= 4; i++)
{
DataTable dt = dtCSV[i];
foreach (DataRow dr in dt.Rows)
{
string[] fields = dr.ItemArray.Select(field => field.ToString()).ToArray();
sb.AppendLine(string.Join("|", fields));
}
string textFileName = textFileNameTemplate + (i + 1) + ".txt";
var textFile = new StreamWriter(textFileName);
textFile.WriteLine(sb.ToString());
textFile.Flush();
textFile.Close();
sb.Clear();
zip.AddFile(textFileName, #"\"); //this is new
}
zip.Save(Response.OutputStream); //this is also new
}
Response.Flush();
Response.End();
zip.AddFile(textFileName, #"\"); adds the text file to an archive. The #"\" means that DotNetZip will not create subfolders that lead to the file, e.g. if my file is in this path: C:\User\Documents\...\file1.txt, the archive would have a similar structure of folders. With #"\", the archive will only contain the text file.
Also take note of sb.Clear(); and its position in the code. It's important that it is inside the for loop but after the textFile.WriteLine(sb.ToString()); line. This makes sure that strings written before are cleared before looping again. This avoid carrying over strings from File1 to File2, and File2 to File3, and so on.
zip.Save(Response.OutputStream); will directly output the Zip file to the Response and does not save the file in the server.
Step 5 - Delete Files
This step depends on your requirements. For me, we will delete the generated files. Using System.IO.File, we will delete the text files. After the using ZipFile zip = new ZipFile()) code block, we'll add the following lines:
for (int i = 1; i <= 5; i++)
{
File.Delete(textFileNameTemplate + i + ".txt");
}
My code probably isn't the most optimized code. But it works. If anyone can suggest a better code that would be great. But for now, I'll be using this code. Many thanks! Especially to Sachu, a really helpful person.
Related
I want to split one large Excel file to few smaller and accessible files.
I already tried to use this code but the files are not accessible:
using (System.IO.StreamReader sr = new System.IO.StreamReader("path"))
{
int fileNumber = 0;
while (!sr.EndOfStream)
{
int count = 0;
using (System.IO.StreamWriter sw = new System.IO.StreamWriter("other path" + ++fileNumber + ".xlsx"))
{
sw.AutoFlush = true;
while (!sr.EndOfStream && ++count < 20000)
{
sw.WriteLine(sr.ReadLine());
}
}
}
}
Any ideas?
thanks.
Files, other than text files, don't work this way. You can't simply cut at a certain point and obtain a working copy.
As for Excel files, you may look into the following tutorial, which illustrates how to automate Excel from C#:
https://support.microsoft.com/en-us/help/302084/how-to-automate-microsoft-excel-from-microsoft-visual-c--net
Basically, what you want to do is open your large Excel file, decide where you want to split it (every n rows, every n sheets and so on), read each portion and write into a newly created xlsx.
I am a little stuck. I have to use the getItems method and output it to PrintItems.txt, but I am not sure how to appoach this problem.
This is my GetItems method:
public string getItems()
{
string strout = "stock items " + "\n";
foreach (Stock s in stock)
{
strout = strout + s.ToString() + "\n";
}
return strout;
}
This is my PrintItems method:
string filename = "printitems.txt";
int count = 0;
using (StreamWriter sw = new StreamWriter(filename))
{
while (count < 5 && count < stock.Count)
{
Stock t = stock[count];
sw.WriteLine(t);
count++;
}
}
It doesn't work because it doesn't write to a file at all.
You code generally should work.
But since you haven't specified full path to the text file - it will be created in the same folder where your executable file is.
If you running it from Visual Studio - it should be in your_project\bin\Debug or your_project\bin\Release folder.
You could use:
File.WriteAllText(#"C:\dummy.txt", strout);
(As long as you don't expect the string to be massive - i.e. 10MB)
'File' uses System.IO
If you made no copy & paste error then there are 4 possibilities for your pheonomenon:
string filename = "printitems.txt";
int count = 0;
using (StreamWriter sw = new StreamWriter(filename))
{
while (count < 5 && count < stock.Count)
{
Stock t = stock[count];
sw.WriteLine(t.ToString());
count++;
}
}
You had no { after the streamwriter using line was something I saw first. The second thing is that Stock t normally can't be written directly but instead you need to do the same thing as when printing it out to the console.
Third: your code does not say anything about if stock is filled or not.
Fourth: The file: You should specify a directory (not only the filename) as else it can be that it is tried to create the file in a location where you have no permissions to create a file in (normally if you put no additional path info in the same path as where the application runs in is used [if you start from the visual studio itself then the appropraite bin path] as location where the file would be created).
Additionally: You should put a try catch block around the whole thing as there can be unexpected phenomenons which result in an exception.
So I have this page on my application where the user can download a "hard copy" of a rendered view, which represents Skills and their Requirements on Roles within a Project, showing the fulfilment of said Skills by any people on the Role.
I have the functionality working with a csv file, as I can just use a StringBuilder for creating a comma-delimited file.
However, before approaching the Excel approach, where I want some light formatting, I realise I cannot acheive this the same way.
I have used Interop for generating Excel files before, but how would I be able to create one that can be downloaded after it's generation?
Here is the working code to generate and return a CSV file:
public ActionResult DownloadSQEPToCSV(int projectID)
{
//source my data
StringBuilder sBuilder = new StringBuilder();
sBuilder.Append("SQEPMatrix, For Project," + data.First().Project.ContractNumber);
foreach (var role in data)
{
sBuilder.Append("\r\nRole:," + role.First().Title.Name);
sBuilder.Append("\r\nSkill,Requirement");
foreach (var person in role.Distinct(uCom))
{
sBuilder.Append("," + person.User.UserDetail.Name);
}
foreach (var skill in role.Distinct(uCom))
{
//More stuff to generate what I want
}
sBuilder.Append("\r\n");
}
//Attach file to the header
Response.Clear();
Response.AddHeader("Content-Disposition", "attachment;filename=SQEPMatrix for " + data.First().Project.ContractNumber + ".csv");
Response.ContentType = "text/csv";
Response.Write(sBuilder);
Response.End();
return SetTitleAndID("SQEP","dl_sqep_csv");
}
This code is invoked by the following script:
function download(id) {
window.location.href = '../../Project/DownloadSQEPExcel?projectID=' + id;
}
So my question, is how can I generate an Excel spreadsheet, and return the generated file in a manner similar to how I return my .csv file?
If you use something like DocumentFormat.OpenXml you can create the Excel file in a memory stream and then return the stream using a FileStreamResult:
var theStreamContainingSpreadsheet = CreateSpreadsheet();
theStreamContainingSpreadsheet.Position = 0;
return new FileStreamResult(theStreamContainingSpreadsheet, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
{FileDownloadName = "Export.xlsx"};
I'm using a handler(.ashx) to serve some files. I have a folder where I store ebooks. I name them by the books PK, and each book may have a few different formats:
211.html
211.pdf
211.prc
The following test code successfully downloads one book.
context.Response.ContentType = "application/octet-stream";
context.Response.AppendHeader("Content-Disposition", "attachment;filename=myfile.pdf");
context.Response.TransmitFile(context.Server.MapPath("~/Media/eBooks/212.pdf"));
How can I serve the client the three different formats? (The clients existing organization isn't in a folder)
I was trying to do something like this:
DirectoryInfo bookDir = new DirectoryInfo(context.Server.MapPath("~/Media/eBooks"));
FileInfo[] f = bookDir.GetFiles();
foreach (var n in f)
{
context.Response.AppendHeader("Content-Disposition", "attachment;filename=myfile.pdf");
context.Response.TransmitFile(context.Server.MapPath("~/Media/eBooks/212.pdf"));
}
But it downloads one file with no file extension.
The only way you can send multiple files in one response is to put them inside an archive package, e.g. a .zip file. That is at least something that can be done with code, using various tools (IIRC there's a zip packager inside the main .NET framework now; otherwise, SharpZipLib will do the job nicely).
To send multiple file to be downloaded, you should zip them using sharpziplib or other file zipping utility,files should be zipped and then download link can be send to the client to download them at once. the code below use ICSharpCode.SharpZipLib.dll Library.
You can call this class and pass your files which you want to zip.
public string Makezipfile(string[] files)
{
string[] filenames = new string[files.Length];
for (int i = 0; i < files.Length; i++)
filenames[i] = HttpContext.Current.Request.PhysicalApplicationPath + files[i].Replace(HttpContext.Current.Request.UrlReferrer.ToString(), "");
string DirectoryName = filenames[0].Remove(filenames[0].LastIndexOf('/'));
DirectoryName = DirectoryName.Substring(DirectoryName.LastIndexOf('/') + 1).Replace("\\", "");
try
{
string newFile = HttpContext.Current.Request.PhysicalApplicationPath + "your image directory\\" + DirectoryName + ".zip";
if (File.Exists(newFile))
File.Delete(newFile);
using (ZipFile zip = new ZipFile())
{
foreach (string file in filenames)
{
string newfileName = file.Replace("\\'", "'");
zip.CompressionLevel = 0;
zip.AddFile(newfileName, "");
}
zip.Save(newFile);
}
}
catch (Exception ex)
{
//Console.WriteLine("Exception during processing {0}", ex);
Response.Write(ex);
// No need to rethrow the exception as for our purposes its handled.
}
string a;
a = "your images/" + DirectoryName + ".zip";
return a;
}
I acknowledge the good Zip solutions mentioned here, but alternatively could you make 3 calls to the handler using javascript/XHR, requesting a different file format each time?
Admittedly, you are restricted by the number of concurrent requests supported by the browser, though I believe the browser will queue requests over the limit.
The benefit is that the User won't need to deal with a zip file, which may confuse them. Instead they should get 3 separate downloads.
HI All,
I am trying to zip up an Epub file i have made using c#
Things I have tried
Dot Net Zip http://dotnetzip.codeplex.com/
- DotNetZip works but epubcheck fails the resulting file (**see edit below)
ZipStorer zipstorer.codeplex.com
- creates an epub file that passes validation but the file won't open in Adobe Digital Editions
7 zip
- I have not tried this using c# but when i zip the file using there interface it tells me that the mimetype file name has a length of 9 and it should be 8
In all cases the mimetype file is the first file added to the archive and is not compressed
The Epub validator that I'am using is epubcheck http://code.google.com/p/epubcheck/
if anyone has succesfully zipped an epub file with one of these libraries please let me know how or if anyone has zipped an epub file successfully with any other open source zipping api that would also work.
EDIT
DotNetZip works, see accepted answer below.
If you need to control the order of the entries in the ZIP file, you can use DotNetZip and the ZipOutputStream.
You said you tried DotNetZip and it (the epub validator) gave you an error complaining about the mime type thing. This is probably because you used the ZipFile type within DotNetZip. If you use ZipOutputStream, you can control the ordering of the zip entries, which is apparently important for epub (I don't know the format, just surmising).
EDIT
I just checked, and the epub page on Wikipedia describes how you need to format the .epub file. It says that the mimetype file must contain specific text, must be uncompressed and unencrypted, and must appear as the first file in the ZIP archive.
Using ZipOutputStream, you would do this by setting CompressionLevel = None on that particular ZipEntry - that value is not the default.
Here's some sample code:
private void Zipup()
{
string _outputFileName = "Fargle.epub";
using (FileStream fs = File.Open(_outputFileName, FileMode.Create, FileAccess.ReadWrite ))
{
using (var output= new ZipOutputStream(fs))
{
var e = output.PutNextEntry("mimetype");
e.CompressionLevel = CompressionLevel.None;
byte[] buffer= System.Text.Encoding.ASCII.GetBytes("application/epub+zip");
output.Write(buffer,0,buffer.Length);
output.PutNextEntry("META-INF/container.xml");
WriteExistingFile(output, "META-INF/container.xml");
output.PutNextEntry("OPS/"); // another directory
output.PutNextEntry("OPS/whatever.xhtml");
WriteExistingFile(output, "OPS/whatever.xhtml");
// ...
}
}
}
private void WriteExistingFile(Stream output, string filename)
{
using (FileStream fs = File.Open(fileName, FileMode.Read))
{
int n = -1;
byte[] buffer = new byte[2048];
while ((n = fs.Read(buffer,0,buffer.Length)) > 0)
{
output.Write(buffer,0,n);
}
}
}
See the documentation for ZipOutputStream here.
Why not make life easier?
private void IonicZip()
{
string sourcePath = "C:\\pulications\\";
string fileName = "filename.epub";
// Creating ZIP file and writing mimetype
using (ZipOutputStream zs = new ZipOutputStream(sourcePath + fileName))
{
var o = zs.PutNextEntry("mimetype");
o.CompressionLevel = CompressionLevel.None;
byte[] mimetype = System.Text.Encoding.ASCII.GetBytes("application/epub+zip");
zs.Write(mimetype, 0, mimetype.Length);
}
// Adding META-INF and OEPBS folders including files
using (ZipFile zip = new ZipFile(sourcePath + fileName))
{
zip.AddDirectory(sourcePath + "META-INF", "META-INF");
zip.AddDirectory(sourcePath + "OEBPS", "OEBPS");
zip.Save();
}
}
For anyone like me who's searching for other ways to do this, I would like to add that the ZipStorer class from Jaime Olivares is a great alternative. You can copy the code right into your project, and it's very easy to choose between 'deflate' and 'store'.
https://github.com/jaime-olivares/zipstorer
Here's my code for creating an EPUB:
Dictionary<string, string> FilesToZip = new Dictionary<string, string>()
{
{ ConfigPath + #"mimetype", #"mimetype"},
{ ConfigPath + #"container.xml", #"META-INF/container.xml" },
{ OutputFolder + Name.Output_OPF_Name, #"OEBPS/" + Name.Output_OPF_Name},
{ OutputFolder + Name.Output_XHTML_Name, #"OEBPS/" + Name.Output_XHTML_Name},
{ ConfigPath + #"style.css", #"OEBPS/style.css"},
{ OutputFolder + Name.Output_NCX_Name, #"OEBPS/" + Name.Output_NCX_Name}
};
using (ZipStorer EPUB = ZipStorer.Create(OutputFolder + "book.epub", ""))
{
bool First = true;
foreach (KeyValuePair<string, string> File in FilesToZip)
{
if (First) { EPUB.AddFile(ZipStorer.Compression.Store, File.Key, File.Value, ""); First = false; }
else EPUB.AddFile(ZipStorer.Compression.Deflate, File.Key, File.Value, "");
}
}
This code creates a perfectly valid EPUB file. However, if you don't need to worry about validation, it seems most eReaders will accept an EPUB with a 'deflate' mimetype. So my previous code using .NET's ZipArchive produced EPUBs that worked in Adobe Digital Editions and a PocketBook.