2009-08-19 5 views
6

In una delle mie applicazioni Web asp.net ho bisogno di nascondere la posizione di un file pdf che viene offerto agli utenti.Cercare di eseguire lo streaming di un file PDF con asp.net sta producendo un "file danneggiato"

Così, sto scrivendo un metodo che recupera il suo contenuto binario dalla sua posizione su un sistema CMS e quindi scarica un array di byte per l'utente web.

Sto ricevendo, sfortunatamente, un errore durante il download del flusso: "Impossibile aprire il file perché è danneggiato" (o qualcosa di simile a questo, quando si apre il file in Adobe Reader).

Domanda 1: cosa sto facendo di sbagliato? Domanda 2: posso scaricare file di grandi dimensioni utilizzando questo approccio?

private void StreamFile(IItem documentItem) 
    { 
     //CMS vendor specific API 
     BinaryContent itemBinaryContent = documentItem.getBinaryContent(); 
     //Plain old .NET 
     Stream fileStream = itemBinaryContent.getContentStream(); 
     var len = itemBinaryContent.getContentLength(); 
     SendStream(fileStream, len, itemBinaryContent.getContentType()); 
    } 

    private void SendStream(Stream stream, int contentLen, string contentType) 
    { 
     Response.ClearContent(); 
     Response.ContentType = contentType; 
     Response.AppendHeader("content-Disposition", string.Format("inline;filename=file.pdf")); 
     Response.AppendHeader("content-length", contentLen.ToString()); 
     var bytes = new byte[contentLen]; 
     stream.Read(bytes, 0, contentLen); 
     stream.Close(); 
     Response.BinaryWrite(bytes); 
     Response.Flush(); 
    } 
+1

Perché non utilizzare il metodo Response.WriteFile? –

+0

Il file non esiste realmente nel file system. – Pablo

+0

Per essere più chiari: il file esiste nel database CMS. È url indirizzabile, ma non posso davvero recuperare il file dal file system. – Pablo

risposta

7

Qui è un metodo che uso. Questo passa indietro un allegato, quindi IE produce una finestra di dialogo Apri/Salva.Vedo anche che i file non saranno più grandi di 1M, quindi sono sicuro che c'è un modo più semplice per farlo

Ho avuto un problema simile con i PDF, e Mi sono reso conto che dovevo assolutamente usare stream binari e ReadBytes. Qualsiasi cosa con stringhe lo ha incasinato.

Stream stream = GetStream(); // Assuming you have a method that does this. 
BinaryReader reader = new BinaryReader(stream); 

HttpResponse response = HttpContext.Current.Response; 
response.ContentType = "application/pdf"; 
response.AddHeader("Content-Disposition", "attachment; filename=file.pdf"); 
response.ClearContent(); 
response.OutputStream.Write(reader.ReadBytes(1000000), 0, 1000000); 

// End the response to prevent further work by the page processor. 
response.End(); 
+0

Non so quale bit di codice ha funzionato, ma finalmente funziona :) Grazie! – Pablo

+0

Lo usi con ReportViewer? – JoshYates1980

+0

Lo svantaggio di specificare una certa quantità di byte è che tutti quei byte verranno scritti nello stream anche se il file non è così grande. Si può semplicemente ottenere la lunghezza dello stream e utilizzare quel numero invece quando si chiama reader.ReadBytes –

0

Ho qualcosa di simile su un sito Web corrente 2.0. E ricordo di aver lottato per farlo funzionare anche se è passato un po 'di tempo, quindi non ricordo le difficoltà.

Ci sono, tuttavia, alcune differenze tra quello che ho quello che hai. Spero che questi ti aiuteranno a risolvere il problema.

  • Dopo la chiamata ClearContent, chiamo ClearHeaders();
  • Non sto specificando una lunghezza.
  • Non sto specificando in linea a disposizione
  • So che tutti dicono di non farlo, ma ho un Response.Flush(); seguito da Response.Close();

E, un'altra cosa, controllare il valore di contentType per assicurarsi che sia corretto per i PDF (("application/pdf").

1

questo frammento ha fatto per me:

   Response.Clear(); 
       Response.ClearContent(); 
       Response.ClearHeaders(); 
       Response.ContentType = mimeType; 
       Response.AddHeader("Content-Disposition", "attachment"); 
       Response.WriteFile(filePath); 
       Response.Flush(); 
6

Le altre risposte copiare il contenuto del file in memoria prima di inviare la risposta. Se i dati sono già in memoria, avrai due copie, il che non è molto buono per la scalabilità. Questo può lavorare meglio, invece:

public void SendFile(Stream inputStream, long contentLength, string mimeType, string fileName) 
{ 
    string clength = contentLength.ToString(CultureInfo.InvariantCulture); 
    HttpResponse response = HttpContext.Current.Response; 
    response.ContentType = mimeType; 
    response.AddHeader("Content-Disposition", "attachment; filename=" + fileName); 
    if (contentLength != -1) response.AddHeader("Content-Length", clength); 
    response.ClearContent(); 
    inputStream.CopyTo(response.OutputStream); 
    response.OutputStream.Flush(); 
    response.End(); 
} 

Dal momento che un flusso è un insieme di byte, non v'è alcuna necessità di utilizzare un BinaryReader. E finché il flusso di input termina alla fine del file, puoi semplicemente utilizzare il metodo CopyTo() nello stream che vuoi inviare al browser web. Tutti i contenuti saranno scritti nello stream di destinazione, senza effettuare alcuna copia intermedia dei dati.

Se è necessario copiare solo un certo numero di byte dal flusso, è possibile creare un metodo di estensione che aggiunge un altro paio CopyTo() sovraccarichi:

public static class Extensions 
{ 
    public static void CopyTo(this Stream inStream, Stream outStream, long length) 
    { 
     CopyTo(inStream, outStream, length, 4096); 
    } 

    public static void CopyTo(this Stream inStream, Stream outStream, long length, int blockSize) 
    { 
     byte[] buffer = new byte[blockSize]; 
     long currentPosition = 0; 

     while (true) 
     { 
      int read = inStream.Read(buffer, 0, blockSize); 
      if (read == 0) break; 
      long cPosition = currentPosition + read; 
      if (cPosition > length) read = read - Convert.ToInt32(cPosition - length); 
      outStream.Write(buffer, 0, read); 
      currentPosition += read; 
      if (currentPosition >= length) break; 
     } 
    } 
} 

È quindi possibile utilizzare in questo modo:

inputStream.CopyTo(response.OutputStream, contentLength); 

Questo potrebbe funzionare con qualsiasi flusso di input, ma un esempio veloce sarebbe la lettura di un file dal file system:

string filename = @"C:\dirs.txt"; 
using (FileStream fs = File.Open(filename, FileMode.Open)) 
{ 
    SendFile(fs, fs.Length, "application/octet-stream", filename); 
} 

Come accennato in precedenza, assicurarsi che il tipo MIME sia corretto per il contenuto.

0

quando si utilizza il codice si copiano i byte dal pdf direttamente nella risposta. tutti i codici speciali ASCII devono essere codificati per passare in http. Quando si utilizza la funzione di streaming, lo stream è codificato in modo da non doversi preoccupare di ciò.

var bytes = new byte[contentLen]; 
    stream.Read(bytes, 0, contentLen); 
    stream.Close(); 
    Response.BinaryWrite(bytes); 
0

Questo frammento include il codice per la lettura nel file da un percorso di file, ed estrarre il nome del file:

private void DownloadFile(string path) 
{ 
    using (var file = System.IO.File.Open(path, FileMode.Open)) 
    { 
     var buffer = new byte[file.Length]; 
     file.Read(buffer, 0, (int)file.Length); 

     var fileName = GetFileName(path); 
     WriteFileToOutputStream(fileName, buffer); 
    } 
} 

private string GetFileName(string path) 
{ 
    var fileNameSplit = path.Split('\\'); 
    var fileNameSplitLength = fileNameSplit.Length; 
    var fileName = fileNameSplit[fileNameSplitLength - 1].Split('.')[0]; 
    return fileName; 
} 

private void WriteFileToOutputStream(string fileName, byte[] buffer) 
{ 
    Response.Clear(); 
    Response.ContentType = "application/pdf"; 
    Response.AddHeader("Content-Disposition", 
     $"attachment; filename\"{fileName}.pdf"); 
    Response.OutputStream.Write(buffer, 0, buffer.Length); 
    Response.OutputStream.Close(); 
    Response.Flush(); 
    Response.End(); 
} 
+0

E legge l'intero file in una matrice di byte e lascia aperto il file. – CodeCaster

+0

Che cambiamento consiglieresti? Cheers –

+0

Devo anche chiamare Response.OutputStream.Close()? –