Existen algunos escenarios donde hay la necesidad de que antes de autorizar la descarga de un archivo desde nuestra aplicación, debamos realizar algún tipo de proceso. En estos casos lo que básicamente hacía es lo siguiente:

// Algun tipo de validación o proceso
// ...
string file = Request.Params["file"];
if (!string.IsNullOrEmpty(file))
{
    file = Path.Combine(Server.MapPath("downloads"), Path.GetFileName(file));

    Response.Clear();
    Response.ContentType = "application/octect-stream";
    Response.AddHeader("Content–Disposition", "attachment; filename=foo.xyz");
    Response.WriteFile(file);
    Response.End();
}

Si bien es cierto que el código mostrado funcionaba bien para archivos pequeños, el problema se presentaba cuando los archivos tenían un tamaño grande, puesto que este método carga el contenido del archivo en memoria antes de enviarlo al cliente. Para evitar este problema, tal como se puede observar en la página de soporte de Microsoft, bastaba enviar pequeñas porciones del archivo a la vez.

System.IO.Stream iStream = null;

// Búfer para leer 10 KB en el fragmento: byte[] buffer = new Byte[10000];
// Longitud del archivo: 
int length;

// Número total de bytes que leer: 
long dataToRead;

// Identificar el archivo que descargar incluyendo su ruta de acceso. 
string filepath = "DownloadFileName";

// Identificar el nombre de archivo. 
string filename = System.IO.Path.GetFileName(filepath);

try
{ // Abrir el archivo. 
    iStream = new System.IO.FileStream(filepath, System.IO.FileMode.Open, System.IO.FileAccess.Read, System.IO.FileShare.Read);

    // Número total de bytes que leer: dataToRead = iStream.Length;
    Response.ContentType = "application/octet-stream"; Response.AddHeader("Content-Disposition", "attachment; filename=" + filename);

    // Leer los bytes. 
    while (dataToRead > 0)
    { // Comprobar que el cliente está conectado. 
        if (Response.IsClientConnected)
        { // Read the data in buffer. 
            length = iStream.Read(buffer, 0, 10000);

            // Escribir los datos en la secuencia de salida actual. 
            Response.OutputStream.Write(buffer, 0, length);

            // Vaciar los datos en la salida HTML. 
            Response.Flush();

            buffer = new Byte[10000]; dataToRead = dataToRead - length;
        }
        else
        { //impedir un bucle infinito si el usuario se desconecta dataToRead = -1; 
        }
    }
}
catch (Exception ex)
{ // Capturar el error, si lo hay. 
    Response.Write("Error : " + ex.Message);
}
finally
{
    if (iStream != null)
    { //Cerrar el archivo. 
        iStream.Close();
    }
}

En ASP.NET 2, se incluye el nuevo método TransmitFile para la clase HttpResponse, que hace lo mismo que el método WriteFile pero sin las limitaciones ni problemas que tiene este último.

// Algun tipo de validación o proceso
// ...
string file = Request.Params["file"];
if (!string.IsNullOrEmpty(file))
{
    file = Path.Combine(Server.MapPath("downloads"), Path.GetFileName(file));

    Response.Clear();
    Response.ContentType = "application/octect-stream";
    Response.AddHeader("Content–Disposition", "attachment; filename=foo.xyz");
    Response.TransmitFile(file);
    Response.End();
}