I've done this before. First, and obviously, the files have to be in a share on the external server that the user process of the website has access to.
As far as the HTTPHandler goes, I handled this by giving the users zip files containing the files they want to download; this way my handler could intercept any call for .zip files and stream them the zip file I create.
Here's the code (quite a chunk; I use MVP, so it is split into Handler and Presenter):
Handler:
public class ZipDownloadModule: IHttpHandler, ICompressFilesView, IErrorView
{
CompressFilesPresenter _presenter;
public ZipDownloadModule()
{
_presenter = new CompressFilesPresenter(this, this);
}
#region IHttpHandler Members
public bool IsReusable
{
get { return true; }
}
public void ProcessRequest(HttpContext context)
{
OnDownloadFiles();
}
private void OnDownloadFiles()
{
if(Compress != null)
Compress(this, EventArgs.Empty);
}
#endregion
#region IFileListDownloadView Members
public IEnumerable<string> FileNames
{
get
{
string files = HttpContext.Current.Request["files"] ?? string.Empty;
return files.Split(new Char[] { ',' });
}
}
public System.IO.Stream Stream
{
get
{
HttpContext.Current.Response.ContentType = "application/x-zip-compressed";
HttpContext.Current.Response.AppendHeader("Content-Disposition", "attachment; filename=ads.zip");
return HttpContext.Current.Response.OutputStream;
}
}
public event EventHandler Compress;
#endregion
#region IErrorView Members
public string errorMessage
{
set { }
}
#endregion
}
Presenter:
public class CompressFilesPresenter: PresenterBase<ICompressFilesView>
{
IErrorView _errorView;
public CompressFilesPresenter(ICompressFilesView view, IErrorView errorView)
: base(view)
{
_errorView = errorView;
this.View.Compress += new EventHandler(View_Compress);
}
void View_Compress(object sender, EventArgs e)
{
CreateZipFile();
}
private void CreateZipFile()
{
MemoryStream stream = new MemoryStream();
try
{
CreateZip(stream, this.View.FileNames);
WriteZip(stream);
}
catch(Exception ex)
{
HandleException(ex);
}
}
private void WriteZip(MemoryStream stream)
{
byte[] data = stream.ToArray();
this.View.Stream.Write(data, 0, data.Length);
}
private void CreateZip(MemoryStream stream, IEnumerable<string> filePaths)
{
using(ZipOutputStream s = new ZipOutputStream(stream)) // this.View.Stream))
{
s.SetLevel(9); // 0 = store only to 9 = best compression
foreach(string fullPath in filePaths)
AddFileToZip(fullPath, s);
s.Finish();
}
}
private static void AddFileToZip(string fullPath, ZipOutputStream s)
{
byte[] buffer = new byte[4096];
ZipEntry entry;
// Using GetFileName makes the result compatible with XP
entry = new ZipEntry(Path.GetFileName(fullPath));
entry.DateTime = DateTime.Now;
s.PutNextEntry(entry);
using(FileStream fs = File.OpenRead(fullPath))
{
int sourceBytes;
do
{
sourceBytes = fs.Read(buffer, 0, buffer.Length);
s.Write(buffer, 0, sourceBytes);
} while(sourceBytes > 0);
}
}
private void HandleException(Exception ex)
{
switch(ex.GetType().ToString())
{
case "DirectoryNotFoundException":
_errorView.errorMessage = "The expected directory does not exist.";
break;
case "FileNotFoundException":
_errorView.errorMessage = "The expected file does not exist.";
break;
default:
_errorView.errorMessage = "There has been an error. If this continues please contact AMG IT Support.";
break;
}
}
private void ClearError()
{
_errorView.errorMessage = "";
}
}
Hope this helps!!