Stahování (nejen) velkých souborů v ASP.NET

1. 12. 2017, Vladimír Klaus, přečteno 262x

ASP.NET/C#

Pokud vytváříte web a potřebujete dát uživateli možnost stáhnout soubory, máte zpravidla 3 možnosti:

  1. normální <a> s odkazem na soubor
  2. funkci/stránku, která zajistí "sofistikované" stažení souboru
  3. funkci/stránku, která zajistí "sofistikované" stažení také velkého souboru

Na normálním stažení asi není třeba cokoliv vysvětlovat a bude v 99 % případů dostačovat. Ovšem pokud potřebujete stažení omezit nebo jinak zabránit neomezenému stahování, je třeba dát soubor do chráněné složky /App_Data/ a na ní se přes <a> odkazovat nelze. Nebo lze, ale server vrátí chybu:

Pak je třeba připravit speciální funkci, která stažení zajistí. Po chvilce pátrání najdete nějakou část kódu, která může nakonec vypadat třeba nějak takto:

Response.Buffer = true;
Response.Clear();
//když se nezakóduje HttpUtility.UrlEncode(filename),
//pak to ve FF zlobí a může stahovat bez přípony
destFileName = HttpUtility.UrlEncode(destFileName);
Response.AddHeader("content-disposition",
    "attachment; filename=" + destFileName);
Response.ContentType = "octet/stream";
Response.WriteFile("~/App_Data/" + appDataFolder + "/" + srcFilename);
Response.Flush();
//toto je nezbytně nutné, jinak má stažený soubor jinou velikost
//a může to zlobit (třeba u PDF se AR stále ptá, zda při zavírání uložit změny)
Response.End();

Vše funguje k plné spokojenosti, ale jen do chvíle, kdy chcete stáhnout "větší" soubor. Co přesně je větším souborem je dáno jistým nastavením serveru, ale dá se předpokládat, že soubory větší než 100 MB tímto určitě neprojdou.

Pak je třeba zvolit náhradní, resp. zcela nové řešení, které stahuje soubor po částech.

System.IO.Stream iStream = null;
//Buffer na čtení 10K dat
byte[] buffer = new Byte[10000];
//Velikost souboru
int length;
//Kolik bajtů budeme číst
long dataToRead;
//Soubor a cesta
string filepath = SrcFile;
string filename = srcFilename;

try {
    //Otevřeme soubor/stream
    iStream = new System.IO.FileStream(filepath, System.IO.FileMode.Open,
              System.IO.FileAccess.Read, System.IO.FileShare.Read);
    //Kolik bajtů budeme číst...
    dataToRead = iStream.Length;
    Response.ContentType = "application/octet-stream";
    Response.AddHeader("Content-Disposition",
        "attachment; filename=" + filename);
    //Čteme data/bajty
    while (dataToRead > 0) {
        //Pokud jsme stále připojeni...
        if (Response.IsClientConnected) {
            //Načteme data do bufferu
            length = iStream.Read(buffer, 0, 10000);
            //Zapíšeme data do výstupního streamu
            Response.OutputStream.Write(buffer, 0, length);
            Response.Flush();
            buffer = new Byte[10000];
            dataToRead = dataToRead - length;
        } else {
            //zajistí, aby nedošlo k nekonečné smyčce, pokud se uživatel odpojí
            dataToRead = -1;
        }
    }
} catch (Exception ex) {
    //Zachytávání chyb
    Response.Write("Error : " + ex.Message);
} finally {
    if (iStream != null) {
        //Vždy uzavřeme soubor/stream
        iStream.Close();
    }
    Response.Close();
}

Po takové úpravě není problém stahovat soubory o velikosti 1 GB nebo větší.

Viz též: