How can I send the client multiple files to download - c#

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.

Related

C# ASP System.IO.Compression - Unauthorized access exception when using zipArchive.CreateEntryFromFile for multiple files()

I have the following lines of code that work for creating a zip using ZipFile.CreateFromDirectory(selectedFile, zipPath)
if (selectedFolder == string.Empty)
{
Console.WriteLine("Invalid folder, try again");
}
else
{
Console.WriteLine("\nSelect zipfile name: ");
var zipName = Console.ReadLine();
// Also available: extractToDirectory
var zipPath = #"C:\Users\User\Documents\Dev\" + zipName + ".zip";
ZipFile.CreateFromDirectory(selectedFolder, zipPath);
However, the following code which should for all intents and purposes do the same thing except for multiple files being archived into a single zip folder refuses to work:
public static void CreateZipFile(string folderToCreateZip, IEnumerable<string> files)
{
var zipPath = folderToCreateZip + "\\test6.zip";
// Create a new ZIP in this location
using (var zip = ZipFile.Open(zipPath, ZipArchiveMode.Create))
{
foreach (var file in files)
{
// Add entry for files
zip.CreateEntryFromFile(file, zipPath, CompressionLevel.Optimal);
}
}
// Dispose of zip object after files have been zipped
//zip.Dispose();
}
var zip == ZipArchive zip
I've tried disabling read-only mode on the folders where the zip should get created, but I don't think this matters since the prior function with CreateFromDirectory() works fine. I've also tried creating a ZIP on desktop, but I get the same error.
This is the exception I'm getting:
As a note, I noticed that it does initially create the zip despite this error, just that it cannot add anything to it unlike CreateFromDirectory() can due to the folder either being in use, no permissions to that area or the folder already existing. Is there a way I can get CreateEntryFromFile() working or an alternative that would work for multiple files?
I had the same problem. The solution was post the full path name at the destinationArchiveFileName parameter (and also a write alowed path). For example c:\my apps folder\my app\my temp\zipfile.zip

Create ZIP File Then Send to Client

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.

Get progress for saving a zip file in C#?

I have this method below, which when the user clicks the button, the program gets a list of files from a path, and zips them to a location (as long as the paths exist)
I have tested it, and it works well for small folders. When I get over 1gb, the gui was freezing. As a result, I started a new thread to stop that from happening. I tried various ways of getting the progress to display, but I get nothing.
If I manually close the program several minutes in, I get a various size temp file depending on how long I wait, so I know that it is writing the file, I just cant figure out how to tell the progress to show the user.
Any ideas?
Here is my method:
private void btnSyncJobs_Click(object sender, EventArgs e)
{
string startPath = #"J:\TV\Game Of Thrones";
string zipPath = #"j:\result.zip";
string sendPath = #"j:\";
if (Directory.Exists(startPath) && Directory.Exists(sendPath))
{
//MessageBox.Show("Correct","These 2 paths exist.");
if (File.Exists(zipPath))
{
File.Delete(zipPath); //delete existing file in order to save the new one
}
String[] allfiles = System.IO.Directory.GetFiles(startPath, "*.*", System.IO.SearchOption.AllDirectories);
int fileCount = allfiles.Length;
int filesAdded = 0;
double percentComplete = 0.00;
string fileCountString = Convert.ToString(fileCount);
MessageBox.Show("There are " + fileCountString + " files.","Count Notice.");
//create the new zip file
//ZipFile.CreateFromDirectory(startPath, zipPath, CompressionLevel.Fastest, true);
Task.Factory.StartNew(() =>
{
using (ZipFile zip = new ZipFile())
{
if (chkPassword.Checked)
{
zip.Password = txtPassword.Text;
}
foreach (string s in allfiles)
{
zip.AddItem(s);
//filesAdded++;//increment the count of files added
//percentComplete = filesAdded / fileCount;
//string percentLabel = filesAdded + " of "+ fileCount + " completed.";
//lblSyncJobsStatus.Text = percentLabel;
}
zip.Save(zipPath);
}
});
lblSyncJobsStatus.Text = "Completed successfully.";
}
else
{
MessageBox.Show("Error: One or more network drives are not attached.","Error");
lblSyncJobsStatus.Text = "Did not complete successfully.\n Please contact tech support.";
}
}
Just a note- I was testing in my tv folder to test on larger file sizes.
The line '//lblSyncJobsStatus.Text = percentLabel;' had to be commented out, because it can't update a value started in another thread. Even before that, I noticed that it was at 100% before the file was being written.
The ZipFile class does not appear to offer any events or callback opportunities to report progress.
If you're open to using the open source 7-Zip library instead (and the SevenZipSharp .NET wrapper), it looks like it provides a callback for reporting progress.
https://sevenzipsharp.codeplex.com/SourceControl/latest#SevenZip/ArchiveUpdateCallback.cs

How to create and save a temporary file on Microsoft Azure virtual server

I am using a free MS Azure virtual webserver for my site.
On my dev machine I can successfully create a CSV file, save it to a relative temp directory, and then download it to the browser client.
However, when I run it from the Azure site, I get the following error:
System.IO.DirectoryNotFoundException: Could not find a part of the
path 'D:\home\site\wwwroot\temp\somefile.csv'.
Does the free version of Azure Websites block us from saving files to disk? If not, where are we allowed to create/save files that we generate on the fly?
Code Example
private FilePathResult SaveVolunteersToCsvFile(List<Volunteer> volunteers)
{
string virtualPathToDirectory = "~/temp";
string physicalPathToDirectory = Server.MapPath(virtualPathToDirectory);
string fileName = "Volunteers.csv";
string pathToFile = Path.Combine(physicalPathToDirectory, fileName);
StringBuilder sb = new StringBuilder();
// Column Headers
sb.AppendLine("First Name,Last Name,Phone,Email,Approved,Has Background Check");
// CSV Rows
foreach (var volunteer in volunteers)
{
sb.AppendLine(string.Format("{0},{1},{2},{3},{4},{5},{6}",
volunteer.FirstName, volunteer.LastName, volunteer.MobilePhone.FormatPhoneNumber(), volunteer.EmailAddress, volunteer.IsApproved, volunteer.HasBackgroundCheckOnFile));
}
using (StreamWriter outfile = new StreamWriter(pathToFile))
{
outfile.Write(sb.ToString());
}
return File(Server.MapPath(virtualPathToDirectory + "/" + fileName), "text/csv", fileName);
}
Make sure that the ~/temp folder gets published to the server, as it's possible your publish process isn't including it.
Azure Websites provide environment variables that you can use to get to things like a temporary storage folder. For example, there is a "TEMP" variable you could access to get a path to the TEMP folder specific to your Website.
Change line 2 in your method to this:
//string physicalPathToDirectory = Server.MapPath(virtualPathToDirectory);
string physicalPathToDirectory = Environment.GetEnvironmentVariable("TEMP");
Then change the last line to this:
//return File(Server.MapPath(virtualPathToDirectory + "/" + fileName), "text/csv", fileName);
return File(pathToFile, "text/csv", fileName);

Creating an Epub file with a Zip library

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.

Categories

Resources