Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using Handler to keep track of files

Tags:

c#

asp.net

I'm currently working on a website which is being developed using Asp.Net and C#. I'm making use of Asp.Net Handler to allow users to download files. I can download the files no problem. However I need to log which files were downloaded successfully. This part doesn't seem to work correctly for me. E.g. if I click on the file to download and then click cancel on the browser prompt my code still writes to the log. I can't seem to figure out how can I write to log only when the file has successfully downloaded.

My Code is below.

public void ProcessRequest(HttpContext context)
{
    string logFilePath = "PathToMyLogFile";
    string filePath = Uri.UnescapeDataString(context.Request.QueryString["file"]);
    string fileName = Path.GetFileName(filePath);

    if (context.Response.IsClientConnected) //Shouldn't this tell me if the client is connected or not?
    {
        using (var writer = new StreamWriter(logFilePath, true))
        {
            if (!File.Exists(logFilePath))
            {
                //Create log file if one does not exist
                File.Create(logFilePath);
            }
            else
            {
                writer.WriteLine("The following file was downloaded \"{0}\" on {1}", fileName, DateTime.Now.ToString("dd/MM/yyyy") + " at " + DateTime.Now.ToString("HH:mm:ss"));
                writer.WriteLine(Environment.NewLine + "-----------------------------------------------------------------------------" + Environment.NewLine);
            }
        }
    }

    context.Response.ContentType = "application/octet-stream";
    context.Response.AppendHeader("Content-Disposition", "attachment;filename=\"" + Path.GetFileName(filePath));
    context.Response.WriteFile(filePath);
    context.Response.End();
}

I appreciate all your help and support.

like image 892
Izzy Avatar asked Sep 30 '15 14:09

Izzy


2 Answers

I tried create simple handler which can detect the download was canceled/broken from server-side perspective.

The important part is "context.Response.IsClientConnected" during/after the sending data.

This example will send neverending file with random data. You can test it in all browser, how exactly they will behave. I tested it only in Chrome.

/// <summary>
/// Writes random data.
/// </summary>
public class NeverendingFile : IHttpHandler {
    public bool IsReusable {
        get { return false; }
    }

    public void ProcessRequest(HttpContext context) {
        context.Response.Buffer = false;
        context.Response.BufferOutput = false;
        context.Response.ContentType = "application/octet-stream";
        context.Response.AppendHeader("Content-Disposition", "attachment;filename=\"Neverendingfile.dat\"");
        context.Response.Flush();

        // flag used for debuging, in production it will be always false => writing into output stream will nevere ends
        var shouldStop = false;
        for(var i = 0; !shouldStop; i++) {
            // chunk contains random data
            var chunk = Guid.NewGuid().ToByteArray();

            for (var a = 0; a < 1000; a++) {
                context.Response.OutputStream.Write(chunk, 0, chunk.Length);
            }
            context.Response.OutputStream.Flush();
            // sleep is just for slowing the download
            System.Threading.Thread.Sleep(10);

            if (!context.Response.IsClientConnected) {
                // the download was canceled or broken
                return;
            }
        }
    }
}

EDIT: Edited source code:

public void ProcessRequest(HttpContext context) {
    string logFilePath = "PathToLogFile";
    //Determine the file path
    string filePath = Uri.UnescapeDataString(context.Request.QueryString["file"]);
    //Determine the file name
    string fileName = Path.GetFileName(filePath);

    context.Response.Buffer = false;
    context.Response.BufferOutput = false;
    context.Response.ContentType = "application/octet-stream";
    context.Response.AppendHeader("Content-Disposition", "attachment;filename=\"" + Path.GetFileName(filePath));
    context.Response.WriteFile(filePath);
    context.Response.Flush();
    context.Response.OutputStream.Flush();

    if (!context.Response.IsClientConnected) {
        // the download was canceled or broken
        using (var writer = new StreamWriter(logFilePath, true)) {
            if (!File.Exists(logFilePath)) {
                //Create log file if one does not exist
                File.Create(logFilePath);
            }
            else {
                writer.WriteLine("The Download was canceled");
            }
        }
        return;
    }

    context.Response.End();
}
like image 111
TcKs Avatar answered Oct 28 '22 01:10

TcKs


I am assuming that both download and cancel buttons are on same page. I created the following example to check if the file is already started download while canceling using a singleton class that have a flag "isFileDownload" so that the flag value wont be initialized for every request for the same user.

  1. Everytime a request comes from user, assign "isFileDownload" json variable to the "isFileDownload" of singleton class.
  2. When a file download request comes to handler, the "isFileDownload" of singleton will be set to true. while sending the response back to the user, check if "isFileDownload" true den log else dont.
  3. if user clicks download button then "isFileDownload" = true and user clicks cancel button before the file is downloaded then "isFileDownload" = false.
  4. Since "isFileDownload" property is part of the singleton class, same instance will be updated.

FYI, i am new to handlers.Sorry in advance if I missed something to take into account while posting this

Handler Class:

public class DownloadFile : IHttpHandler
{

    public void ProcessRequest(HttpContext context)
    {
        string fileName = @"test.txt";
        string filePath = context.Server.MapPath("/test.txt");
        string logFilePath = context.Server.MapPath("/Log.txt");
        //string filePath = Uri.UnescapeDataString(context.Request.QueryString["file"]);
        //string fileName = Path.GetFileName(filePath);
        Singleton s = Singleton.Instance;
        s.isFileDownload = Convert.ToBoolean(context.Request.Form["isFileDownload"]);
        if (context.Response.IsClientConnected) //Shouldn't this tell me if the client is connected or not?
        {

            using (var writer = new StreamWriter(logFilePath,true))
            {
                if (!File.Exists(logFilePath))
                {
                    //Create log file if one does not exist
                    File.Create(logFilePath);
                }
                else
                {
                    writer.WriteLine("The following file was downloaded \"{0}\" on {1}", fileName, DateTime.Now.ToString("dd/MM/yyyy") + " at " + DateTime.Now.ToString("HH:mm:ss"));
                    writer.WriteLine(Environment.NewLine + "-----------------------------------------------------------------------------" + Environment.NewLine);
                }
            }
        }

        //To mock the large file download
        if (s.isFileDownload)
        System.Threading.Thread.Sleep(10000);

        if (context.Response.IsClientConnected )
        {
            if (s.isFileDownload){
            System.Threading.Thread.Sleep(100);
            context.Response.ContentType = "application/octet-stream";
            context.Response.AppendHeader("Content-Disposition", "attachment;filename=\"" + Path.GetFileName(filePath));
            context.Response.WriteFile(filePath);
            context.Response.OutputStream.Flush();
            context.Response.End();
        }
            else
            {
                return;
            }
        }
        else
        {
            return;
        }


    }

    public bool IsReusable
    {
        get
        {
            return false;
        }
    }
}

Singleton class: Reference

public sealed class Singleton
{
    private static volatile Singleton instance;
    private static object syncRoot = new Object();

    private Singleton() { }

    public static Singleton Instance
    {
        get
        {
            if (instance == null)
            {
                lock (syncRoot)
                {
                    if (instance == null)
                        instance = new Singleton();
                }
            }

            return instance;
        }
    }

    public bool isFileDownload { get; set; }
}

Html Page :

$(document).ready(function(){ 
var isFileDownload = false;
        $("#download").click(function () {
            isFileDownload = true;
            console.log(isFileDownload);
            $.ajax({
                method: "POST",
                url: "DownloadFile.ashx",
                data: { isFileDownload:true }
            }).done(function (data) {
                alert(data);
               
            });
        });

        $("#cancel").click(function () {
            if (isFileDownload) {
                $.ajax({
                    method: "POST",
                    url: "DownloadFile.ashx",
                    data: { isFileDownload: false }
                }).done(function (data) {
                    alert("canceld");
                    isFileDownload = false;
                });
            }
        });
        $(document).ajaxComplete(function () {
            isFileDownload = false;
        });
)};
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<body>
<button type="button" value="DownLoad" id="download" title="Download">Download</button>
<button type="button" value="Cancel" id="cancel" title="Cancel">Cancel</button>
  </body>
like image 25
kranthiv Avatar answered Oct 28 '22 00:10

kranthiv