How to programmatically record the start and end of MVC FileResult to register incomplete downloads?

I am exploring how an ASP.NET MVC application can register failed / incomplete downloads. I have a controller that handles file requests that are currently returning a FileResult. I need to write IPAddress, the file name and at boot, then the same data and when it is complete. I looked at intercepting the start and end of a request using the HttpModule, as well as tracking failed IIS7 requests, but I'm not sure which route would be the best, and I have the feeling that maybe I don't see an obvious answer to this problem.

Does anyone have any suggestions or any alternatives since this is similar to what many people would like to know from their web server?

thanks for the help

+3
source share
3 answers

OK, therefore, having tried the first answer, it did not work as a call to base.WriteFile (answer); works asynchronously.

Since then I have written an extension to the FilePathResult class that works by streaming a response. I also added simple support for resuming a file using the range header command.

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();
        }


    }
+1
source

You can try writing FilePathResult and overriding WriteFileMethod :

public class CustomFileResult : FilePathResult
{
    public CustomFileResult(string fileName, string contentType)
        : base(fileName, contentType)
    { }

    protected override void WriteFile(HttpResponseBase response)
    {
        // TODO: Record file download start
        base.WriteFile(response);
        // TODO: Record file download end
    }
}

and in your controller:

public class HomeController : Controller
{
    public ActionResult Index()
    {
        return new CustomFileResult(@"d:\test.jpg", "image/jpg");
    }
}
+2
source

This can be done more simply: use Response.TransmitFile () and (IMPORTANT!) Set Response.BufferOutput = false. The default value of BufferOutput is true - this means that the output is buffered and will be sent after the complete processing of the page.

Controller:

// record download start event here
...

// send a file to Response, not buffered.
// In my case the downloaded file has name "setup.exe"
Response.ContentType = "application/octet-stream";
Response.AddHeader("Content-Disposition", @"filename=""setup.exe""");
Response.BufferOutput = false;
Response.TransmitFile(fileURL); 

// record download finish event here
...
0
source

Source: https://habr.com/ru/post/1742797/


All Articles