Rachit's Blog (Moved)
Moved to http://weblogs.asp.net/rachit

How to: Download file from ASP.Net 2.0 Page

Friday, April 20, 2007 9:45 AM

The purpose of this article is to provide some help who are seeking solutions how to make ASP.Net page download certain files.

In some projects, I had to implement downloads for reports or some MSMQ implementations that required to queue the request and when the report is done by MS Reporting Services or other third party mechanism, an email would be used to notify the end users to pick up their reports.

There are quite a few solutions available if you Google but then some would be okay for smaller download and they won't work for large downloads (like a few GB).

Two main points that are covered in this article:

(1) Accessing the file for download across the network

(2) Lengthy download (I've tried to download 2GB file with this code and I didn't have any problem, although it was tested in the Intranet domain scenario so in the Internet world, it may be a little different).

So, I thought I should write code to fulfil any kind of download. BTW, your mileage may vary.

In general, to support lengthy download, you can simply change the web.config file in ASP.Net to support longer timeouts.

e.g

 

<system.web>
//your original code
<httpRuntime executionTimeout="3600" maxRequestLength="10000"/>
</system.web>
 

 

Where executionTimeout is in Seconds (default is 90 sec) and maxRequestLength is in Kilobytes (default is 4MB = 4096 KB).

For lengthier download, consider the following code. I've used an aspx page but you can surely create HttpHandler (.ashx) to do the same. Another important point to remember is to remove all the HTML content from .aspx file. The .aspx.cs will handle the magic.  

 

 

//Disclaimer: Some parts of the code are copied from MSDN or other online resources. I, by no means, have tried to claim that I wrote them from scratch. :) 

using System;
using System.Collections.Generic;
using System.Text;
using System.Collections.Specialized;
using System.Security.Principal;
using System.Configuration;
using System.Diagnostics;

namespace TestOfRachit
{
    public partial class Default : System.Web.UI. Page
    {
        [DllImport("advapi32.dll", SetLastError = true)]
            static extern bool LogonUser
            (
                string principal,
                string authority,
                string password,
                LogonSessionType logonType,
                LogonProvider logonProvider,
                out IntPtr token
            );
        [DllImport("kernel32.dll", SetLastError = true)]
                static extern bool CloseHandle(IntPtr handle);
                
        enum LogonSessionType : uint
            {
            Interactive = 2,
            Network,
            Batch,
            Service,
            NetworkCleartext = 8,
            NewCredentials
            }
            enum LogonProvider : uint
            {
            Default = 0, // default for platform (use this!)
            WinNT35,     // sends smoke signals to authority
            WinNT40,     // uses NTLM
            WinNT50      // negotiates Kerb or NTLM
            }
            public enum AccessType
            {
            Query = 0,
            Modify = 1
            }                
        protected void DownloadButton_Click (object sender, EventArgs e)
        {
            //filepath = \\server\folder\en_vs_2005_pro_dvd.iso

            string filePath = ConfigurationManager.AppSettings["DownloadFolder"].ToString();

            //somehow, you need to know what file name is: e.g.  en_vs_2005_pro_dvd.iso
            string[] fileName = filePath.Split(new char[] { '\\' });

            FileStream fs = null;
            //Either use the current logged in user's credentials or use some different user.

            //-----------------------------
            //To use the current logged in user's credentials, you can call directly the following
            //fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
            //-----------------------------
            
            //-----------------------------
            //To use the some other Network user's credentials, use the following:

            //Add the required namespaces: 
            //using System.Security.Principal;
            IntPtr token = IntPtr.Zero;
            WindowsImpersonationContext impersonatedUser = null;
            try
            {               
                LogonUser("[YOUR NETWORK USER NAME (WITHOUT DOMAIN)]",
                        "YOUR DOMAIN NAME",
                        "YOUR DOMAIN PASSWORD",
                        LogonSessionType.Network,
                        LogonProvider.WinNT50,
                        out token);

                WindowsIdentity id = new WindowsIdentity(token);
                impersonatedUser = id.Impersonate();

                fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
            }
            catch (Exception ex)
            {
                throw ex;
                //Marshal.GetLastWin32Error()
            }
            finally
            {
                //You got your download file, undo the other user's credentials
                impersonatedUser.Undo();                    
                impersonatedUser.Dispose();
            }
            //-----------------------------
            
            Response.Clear();
            Response.ClearContent();
            Response.ClearHeaders();
            //To forcefully download, even for Excel, PDF files, regardless of your IE's settings which may allow to open the files right in the browser.
            Response.ContentType = "application/octet-stream"; 
            Response.AddHeader("Content-Disposition", "attachment;filename=" + fileName[fileName.Length - 1].ToString());

            long bytesToGo;
            int bytesRead;
            Byte[] buffer = new byte[1048576];    //1 MB buffer, you may want to use whatever fits your environment

            bytesToGo = fs.Length;

            while (bytesToGo > 0)
            {
                if (Response.IsClientConnected)
                {
                bytesRead = fs.Read(buffer, 0, 1048576);
                Response.OutputStream.Write(buffer, 0, bytesRead);
                Response.Flush();
                bytesToGo -= bytesRead;
                }
                else
                {
                bytesToGo = -1;
                }
            }
            fs.Close();
            Response.Flush();                
        }
    }
}

The above code may require some kind of fixing in order to compile but mostly it should work.

Let me know your comments. Suggestions to make it better?