I'm creating a REST endpoint which should return a sfx (Self Extracting Archive). I'm using Ionic.Zip to do the heavy lifting as far as creating the actual archive, but I'm having some trouble understanding how I should write the finished sfx archive back to the client.
From what I can tell, ZipFile.Save(Response.OutputStream)
works fine in order to write a zip file back, and I was rather surprised that I could not do the same using something like ZipFile.SaveSelfExtractor(Response.OutputStream, options)
. According to the docs, there is no overload for SaveSelfExtractor which takes in a stream.
The examples I'm able to dig up on the web explains either
But I do not need nor want to temporarily store the sfx executable on the server, and I do not want to create my own sfx stub. I'm quite happy to use the stub already provided in the Ionic package.
Is there any way for me to just let the Ionic.Zip.ZipFile
create the sfx and write it back to Response.OutputStream
in one go?
This is what I have now:
using System.IO;
using Ionic.Zip;
using System.Web.Http;
using Context = System.Web.HttpContext;
namespace MyCompany.web.Controllers
{
[HttpGet]
public void Export()
{
var response = Context.Current.Response;
var stream = response.OutputStream;
// Create the zip archive in memory
using (var archive = new ZipFile())
{
archive.Comment = "Self extracting export";
archive.CompressionLevel = Ionic.Zlib.CompressionLevel.BestCompression;
using (var memoryStream = new MemoryStream())
using (var streamWriter = new StreamWriter(memoryStream))
{
streamWriter.WriteLine("Hello World");
archive.AddEntry("testfile.txt", memoryStream.ToArray());
}
// What I want is to write this to outputstream
archive.SaveSelfExtractor(/*stream*/ "export.exe", new SelfExtractorSaveOptions
{
Flavor = SelfExtractorFlavor.ConsoleApplication,
Quiet = true,
ExtractExistingFile = ExtractExistingFileAction.OverwriteSilently,
RemoveUnpackedFilesAfterExecute = false
});
/*archive.Save(stream); // This will write to outputstream */
}
response.AddHeader("Content-Disposition", "attachment; filename=export.exe");
response.AddHeader("Content-Description", "File Transfer");
response.AddHeader("Content-Transfer-Encoding", "binary");
response.ContentType = "application/exe";
response.Flush();
response.Close();
response.End();
}
}
I'm posting this for posterity. It was the best solution I could come up with. Others are welcome to provide a better solution.
[HttpGet]
public void Export()
{
var response = Context.Current.Response;
var writeStream = response.OutputStream;
var name = "export.exe";
// Create the zip archive in memory
using (var archive = new Ionic.Zip.ZipFile())
{
archive.Comment = "Self extracting export";
archive.CompressionLevel = Ionic.Zlib.CompressionLevel.BestCompression;
using (var memoryStream = new MemoryStream())
using (var streamWriter = new StreamWriter(memoryStream))
{
streamWriter.WriteLine("Hello World");
streamWriter.Flush();
archive.AddEntry("testfile.txt", memoryStream.ToArray());
}
// Write sfx file to temp folder on server
archive.SaveSelfExtractor(name, new Ionic.Zip.SelfExtractorSaveOptions
{
Flavor = Ionic.Zip.SelfExtractorFlavor.ConsoleApplication,
Quiet = true,
DefaultExtractDirectory = "\\temp",
SfxExeWindowTitle = "Export",
ExtractExistingFile = Ionic.Zip.ExtractExistingFileAction.OverwriteSilently,
RemoveUnpackedFilesAfterExecute = false
});
// Read file back and output to response
using (var fileStream = new FileStream(name, FileMode.Open))
{
byte[] buffer = new byte[4000];
int n = 1;
while (n != 0)
{
n = fileStream.Read(buffer, 0, buffer.Length);
if (n != 0)
writeStream.Write(buffer, 0, n);
}
}
// Delete the temporary file
if (File.Exists(name))
{
try { File.Delete(name); }
catch (System.IO.IOException exc1)
{
Debug.WriteLine("Warning: Could not delete file: {0} {1}", name, exc1);
}
}
}
response.AddHeader("Content-Disposition", "attachment; filename=" + name);
response.AddHeader("Content-Description", "File Transfer");
response.AddHeader("Content-Transfer-Encoding", "binary");
response.ContentType = "application/exe";
response.Flush();
response.Close();
response.End();
}