views:

94

answers:

2

My question is similar to http://stackoverflow.com/questions/194579/how-to-detect-when-a-user-has-successfully-finished-downloading-a-file-in-php but I have to do the same using IIS, ASP.NET and C#.

None of the methods in the HttpResponse class provide feedback if the data was sent or not, TransmitFile just does its job (or not) and does not provide any means of knowing the result.

I was thinking of using the .Filter property but then again, the filter is based on the HttpResponseStream which does also not provide any feedback.

Any good ideas?

A: 

Check Response.IsClientConnected after calling TransmitFile.

SLaks
Nope, none of the "writer" functions do throw exceptions. The only way to know if the client is still listening is using Response.IsClientConnected as mentioned below.
Uniwares_AS
A: 

After some testing I came up with the following solution to the problem. TransmitFile() has one serious limitation: it reads the whole file into memory before sending, this is really bad for larger files. So basically I resorted to manual chunking and checking if the client is connected after each chunk.

context.Response.Clear();
context.Response.BufferOutput = false;
context.Response.ContentType = "application/octet-stream";
context.Response.AddHeader("Content-Disposition", "attachment; filename=" + originalFilename);
context.Response.AddHeader("Content-Length", fileLength.ToString());
context.Response.Cache.SetNoStore();

context.Response.Flush();

downloadFailed = !context.Response.IsClientConnected;

int thisChunk;
long offset = 0;
int chunkSize = 1024 * 8;
byte[] bytes = new byte[chunkSize];

FileStream r = File.OpenRead(localFilename);

while((offset < fileLength) && !downloadFailed)
{
    if((fileLength - offset) < chunkSize)
    {
        thisChunk = (int)(fileLength - offset);
    }
    else
    {
        thisChunk = chunkSize;
    }

    r.Read(bytes, 0, chunkSize);

    try
    {
        context.Response.BinaryWrite(bytes);
        context.Response.Flush();

        if(!context.Response.IsClientConnected)
        {
            downloadFailed = true;
        }
    }
    catch(ObjectDisposedException ex1)
    {
        // Stream is closed, nothing written
        break;
    }
    catch(System.IO.IOException ex3)
    {
        // I/O error, unknown state, abort
        Trace.Write(ex3);
        break;
    }

    offset += thisChunk;
}

if(!downloadFailed)
{
    // now update the file, statistics, etc
}

context.Response.Flush();

HttpContext.Current.ApplicationInstance.CompleteRequest();

Will need to play a bit with the chunk size to find the optimal size. But basically it works reliably like this.

Uniwares_AS