ASP.NET: Downloading files from a UNC share

Implementing a cross-browser solution for downloading files from a UNC share in ASP.NET applications

Table of contents

The scenario

I was working recently in an intranet application that had a download page. The output HTML was similar to the following:

<li><a class="download" href="\\MYSERVER\reports 2011\report1.zip" title="Report 1">Report 1</a></li>
<li><a class="download" href="\\MYSERVER\reports 2011\report2.zip" title="Report 2">Report 2</a></li>
<li><a class="download" href="\\MYSERVER\reports 2011\report 3.zip" title="Report 3">Report 3</a></li>
<li><a class="download" href="\\MYSERVER\reports 2011\report 4.zip" title="Report 4">Report 4</a></li>
<li><a class="download" href="\\MYSERVER\reports 2011\report 5&6.zip" title="Report 5&6">Report 5&6</a></li>

This was working fine in IE9, but not in other browsers. There was no action using Google Chrome, and using Firefox there was an error (HTTP Error 400 – Bad Request).

I tried to convert the file path to a file URI but it didn’t fix it. It continued to work on IE only.

<li><a class="download" href="file://MYSERVER/reports 2011/report1.zip" title="Report 1">Report 1</a></li>
<li><a class="download" href="file://MYSERVER/reports 2011/report2.zip" title="Report 2">Report 2</a></li>
<li><a class="download" href="file://MYSERVER/reports 2011/report 3.zip" title="Report 3">Report 3</a></li>
<li><a class="download" href="file://MYSERVER/reports 2011/report 4.zip" title="Report 4">Report 4</a></li>
<li><a class="download" href="file://MYSERVER/reports 2011/report 5&6.zip" title="Report 5&6">Report 5&6</a></li>

The solution was to create a custom ASP.NET download page. I used also jquery on the client side.

Step 1: Using jquery on the client side

The first step was to add an event handler to the download links. The request URI is encoded and is sent as a parameter to the download page. Creating an hidden iframe and setting the src attribute with the download link allows the file to be downloaded asynchronously.

$("a.download").bind("click", function (e) {
    e.preventDefault();
    var requestedFile = encodeURIComponent($(this).attr('href'));

    var iframe = document.createElement("iframe");
    iframe.src = 'Download.aspx?file=' + requestedFile;
    iframe.style.display = "none";
    document.body.appendChild(iframe); // triggers download page
});

Make sure you use encodeURIComponent function to encode special characters in the filename.

Step 2: Create an ASP.NET download page

This is the source code of the download page:

protected void Page_Load(object sender, EventArgs e)
{
    try
    {
        string requestFile = Request.QueryString["file"];

        if(string.IsNullOrEmpty(requestFile))
        {
            throw new FileNotFoundException("File to download cannot be null or empty");
        }

        // Get file name from URI string in C#
        // http://stackoverflow.com/a/1105614
        var uri = new Uri(requestFile);
        string filename = Path.GetFullPath(uri.LocalPath);
        var fileInfo = new FileInfo(filename);

        if(!fileInfo.Exists)
        {
            throw new FileNotFoundException("File to download was not found", filename);
        }


        // get content type based on file extension. Example:
		// http://stackoverflow.com/a/691599
        Response.ContentType = GetContentType(fileInfo.Extension);

        Response.AddHeader("Content-Disposition", 
                           "attachment; filename=\"" + fileInfo.Name + "\"");
        Response.WriteFile(fileInfo.FullName);
        Response.End();
    }
    catch(ThreadAbortException)
    {
        // ignore exception
    }
    catch(FileNotFoundException ex)
    {
        Response.StatusCode = (int) System.Net.HttpStatusCode.NotFound;
        Response.StatusDescription = ex.Message;
    }
    catch(Exception ex)
    {
        Response.StatusCode = (int) System.Net.HttpStatusCode.InternalServerError;
        Response.StatusDescription = string.Format("Error downloading file: {0}", ex.Message);
    }
}

Some notes:

This is necessary in order to make the download work with a UNC share (\\MYSERVER\….) or a file URI (file://….)

    var uri = new Uri(requestFile);
    string filename = Path.GetFullPath(uri.LocalPath);

To avoid filename truncating, it’s necessary to wrap the filename with quotes

    Response.AddHeader("Content-Disposition", 
                       "attachment; filename=\"" + fileInfo.Name + "\"");

References

Technorati Tags: , , ,