OK so having tried the first answer it did not work as the call to base.WriteFile(response); works asynchronously.
I have since written an extension of the FilePathResult class which works by streaming the response. I have also added simple support for file resuming using the range header instruction.
public class LoggedFileDownload : FilePathResult
{
private readonly IRepository repository;
private readonly AssetDownload assetDownload;
public LoggedFileDownload(string fileName, string contentType, string downloadName, IRepository repository, AssetDownload assetDownload) : base(fileName, contentType)
{
FileDownloadName = downloadName;
this.repository = repository;
this.assetDownload = assetDownload;
}
protected override void WriteFile(HttpResponseBase response)
{
long totalSent = 0;
long bytesRead = 0;
var fileInfo = new FileInfo(FileName);
var readStream = fileInfo.OpenRead();
var buffer = new Byte[4096];
long responseLength = readStream.Length;
var rangeHeader = HttpContext.Current.Request.Headers["Range"];
if (!rangeHeader.IsNullOrEmpty())
{
string[] range = rangeHeader.Substring(rangeHeader.IndexOf("=") + 1).Split('-');
long start = Convert.ToInt64(range[0]);
long end = 0;
if (range[1].Length > 0) end = int.Parse(range[1]);
if (end < 1) end = fileInfo.Length;
if (start > 0)
{
responseLength -= start;
readStream.Seek(start, 0);
totalSent += start;
var rangeStr = string.Format("bytes {0}-{1}/{2}", start, end, fileInfo.Length);
response.StatusCode = 206;
response.AddHeader("Content-Range",rangeStr);
}
}
response.AddHeader("Content-Disposition", string.Format("attachment; filename=\"{0}\"", FileDownloadName));
response.AddHeader("Content-MD5", GetMD5Hash(fileInfo));
response.AddHeader("Accept-Ranges", "bytes");
response.AddHeader("Content-Length", (responseLength).ToString());
response.AddHeader("Connection", "Keep-Alive");
response.ContentType = FileTypeHelper.GetContentType(fileInfo.Name);
response.ContentEncoding = Encoding.UTF8;
response.Clear();
while(response.IsClientConnected && (bytesRead = readStream.Read(buffer, 0, buffer.Length)) != 0 )
{
totalSent += bytesRead;
response.BinaryWrite(buffer);
response.Flush();
}
if (totalSent == fileInfo.Length)
{
// This means the file has completely downloaded so we update the DB with the completed field set to true
assetDownload.Completed = true;
repository.Save(assetDownload);
repository.Flush();
}
}
private static string GetMD5Hash(FileInfo file)
{
var stream = file.OpenRead();
MD5 md5 = new MD5CryptoServiceProvider();
byte[] retVal = md5.ComputeHash(stream);
stream.Close();
var sb = new StringBuilder();
for (int i = 0; i < retVal.Length; i++)
{
sb.Append(retVal[i].ToString("x2"));
}
return sb.ToString();
}
}