Checking Client Download Success with ASP.NET MVC

Wednesday, June 23, 2010

Scenario: You want to know if a client completes a large download successfully.

Possible Solution: Create a custom ActionResult (perhaps derived from FileStreamResult) that streams the data and checks to see if the client remains connected.

public class CheckedFileStreamResult : FileStreamResult
{
    public CheckedFileStreamResult(FileStream stream, string contentType)
        :base(stream, contentType)
    {
        DownloadCompleted = false;
    }

    public bool DownloadCompleted { get; set; }

    protected override void WriteFile(HttpResponseBase response)
    {
        var outputStream = response.OutputStream;
        using (FileStream)
        {
            var buffer = new byte[_bufferSize];
            var count = FileStream.Read(buffer, 0, _bufferSize);
            while(count != 0 && response.IsClientConnected)
            {                 
                outputStream.Write(buffer, 0, count);
                count = FileStream.Read(buffer, 0, _bufferSize);
            }
            DownloadCompleted = response.IsClientConnected;
        }
    }
    
    private const int _bufferSize = 0x1000;
}

You can now log or audit the DownloadCompleted property of the result from an action filter. I'm sure this isn't 100% foolproof, but it seems to work reasonably well. Note: The IsClientConnected property of the response only tells the truth when running under IIS. With the WebDev server the property always returns true.


Comments
gravatar Nick Berardi Thursday, June 24, 2010
Hi Scott,

Don't forget to turn off the buffer. So that your file is sent in chuncks to the client

response.BufferOutput = false;

I don't know if you set this in your web.config or somewhere else, but it is important for your readers to understand the ASP.NET by default buffers all output before it is sent back to the client.
gravatar scott Thursday, June 24, 2010
Good point, Nick. Thanks.
gravatar Erik Thursday, June 24, 2010
I wonder if passing an Action<bool> to trigger on complete would work..

Nice sample anyhow :D

Erik
Imran Balcoh Sunday, July 4, 2010
First point is that you need to inherit your class from FileResult instead of FileStreamResult. Because WriteFile is already overriden in FileStreamResult class.

Second point is that due to Lazy execuation of ActionResult in ASP.NET MVC, there is no way to query DownloadCompleted property. The reason is that ASP.NET MVC internally calls the ExecuteResult method which results in call of WriteFile method.
gravatar Scott Sunday, July 4, 2010
@Imran - Sorry - I don't understand either of your points.

When we derive from FileSteamResult we can still override WriteFile - this is a streaming operation after all.

It's easy to check DownloadCompleted during OnActionExecuted (which is after the result executes).
Imran Balcoh Sunday, July 4, 2010
@Scott, I am not saying that it is incorrect. I am just saying that it is illogical that a member which is override in parent class again override in child class.

For my second point, the only way to inspect DownloadCompleted property is to use ResultExecuted filter not ActionExecuted filter because ActionExecuted is executed before WriteFile method. But at this point you have no choice to change Result.
gravatar scott Monday, July 5, 2010
@Imran - Ah, yes. We could debate this one ;)

On your second point - yes, my mistake. I meant to say OnResultExecuted not OnActionExecuted.

protected override void OnResultExecuted(ResultExecutedContext filterContext)
{
var download = filterContext.Result as CheckedFileStreamResult;
if(download != null)
{
// ...
}

}

I'm not worried about changing the result - just logging if the download appears to have been successful.
gravatar Zen Monday, July 5, 2010
@Imran

just curious, why is it not logical to override a method in a child class when the same method is overriden in the parent class. Can you point me to any resource that touch upon this topic?

Thanks!

Zen
gravatar Thanigainathan Monday, July 5, 2010
Hi,
You need to be more precise. You have to mention this is article related to MVC in very first stage. Please take this as my request.Also please mention where this will be used in applciation. An example will be enough.

Thanks,
Thani
gravatar scott Monday, July 5, 2010
@Thani - the title says "with ASP.NET MVC".
Imran Balcoh Monday, July 5, 2010
@Zen,
I have seen lot of methods which are overridden in child and are declared as virtual in parent but never seen a method which is defined as override in parent and again overridden in child.

But the truth fact is that override method is by default virtual for it's child classes(From a Microsoft Book). So I am agreeing that your method is not illogical.
Imran Balcoh Monday, July 5, 2010
@Zen,
I have seen lot of methods which are overridden in child and are declared as virtual in parent but never seen a method which is defined as override in parent and again overridden in child.

But the truth fact is that override method is by default virtual for it's child classes(From a Microsoft Book). So I am agreeing that your method is not illogical.
gravatar Frank Tuesday, July 6, 2010
Hi Scott

Could you make a complete example?

/Frank
gravatar Doug Rathbone Monday, July 12, 2010
This is a cool idea - i'd never thought of checking for client connection at the end of the stream.

Apologies if you think this is blatant spam, but you could then use my open source Google Analytics wrapper - GaDotNet to log the download success to your GA account :)
www.diaryofaninja.com/projects/details/ga-dot-net

keep up the great work - you have a new subscriber!
Comments are now closed.
by K. Scott Allen K.Scott Allen
My Pluralsight Courses
The Podcast!