2010-02-03 12 views
9

Prima di tutto, I amore ASP.NET MVC. Questa domanda non è una critica di ciò. Piuttosto, voglio confermare ciò che penso di vedere e assicurarmi di non aver perso qualcosa. Resta con me ... Non riesco ad arrivare alla domanda senza fornire un po 'di contesto.FileStreamResult di ASP.NET MVC è meno efficiente della scrittura diretta sul flusso di risposta in uscita o mi manca qualcosa?

La domanda ha a che fare con la restituzione dei dati in un flusso in risposta a un post HTTP. Nei giorni precedenti a ASP.NET MVC, è possibile eseguire questa operazione collegando i dati direttamente al flusso di risposta. Ad esempio, si potrebbe fare qualcosa di simile:

someObjectThatDumpsOutputToWhateverStreamYouHandIt.WriteTo(Response.OutputStream); 

Avviso un aspetto fondamentale di questo codice: non ho implementato QUALSIASI archivio di backup. Non dovevo trasmettere le informazioni a un array di byte, né scaricarle in un file temporaneo. Piuttosto, ASP.NET aveva già impostato un flusso allo scopo di comunicare la risposta al browser e ho scaricato l'output che voglio direttamente in quel flusso. Questo è più efficiente in termini di CPU, memoria e tempo di esecuzione che copiare tutti i dati in una posizione temporanea e quindi spostarli nel flusso di risposta.

Tuttavia, non è molto amichevole ai test unitari. In effetti, puoi ancora fare quel tipo di codice in ASP.NET MVC, ma l'abitudine sarebbe quella di evitarlo. Piuttosto, ASP.NET MVC ti incoraggerebbe a restituire un ActionResult. ActionResult è un concetto ASP.NET MVC che esiste principalmente per essere un test unitario. Ti permette di "dichiarare" ciò che vuoi fare. Un test unitario può esercitare un'azione Controller e confermare che ottiene l'ActionResult previsto. Quel test unitario può essere eseguito al di fuori di un browser.

ASP.NET MVC offre un tipo di ActionResult per la restituzione di flussi di dati. Si chiama FileStreamResult. Non lasciare che la parola "File" nel nome ti inganni. Si tratta di restituire un flusso di dati, esattamente come stiamo parlando sopra.

Tuttavia, ecco il problema e la base della mia domanda: Se si rende il metodo Controller restituito un FileStreamResult e si passa a un Stream che si desidera restituire, allora sembra che non vi sia più alcun modo per a scaricare i dati direttamente nel flusso di risposta. Ora, sembra che tu sia costretto a creare il tuo stream con un backing store (come la memoria o un file), eseguire il dump dei tuoi dati in esso, e poi passarlo a FileStreamResult che tu restituisci.

Così sembra che devo fare qualcosa di simile (volutamente omettendo smaltire/utilizzando/ecc):

MemoryStream myIntermediateStream = new MemoryStream(); 
someObjectThatDumpsOutputToWhateverStreamYouHandIt.WriteTo(myIntermediateStream); 

return new FileStreamResult(myIntermediateStream, "application/pdf"); 

noti che myIntermediateStream fa sì che il contenuto del grande flusso di dati da memorizzare nella memoria temporanea , in modo che FileStreamResult possa in seguito copiarlo nuovamente nel flusso di output di risposta.

Quindi, ecco la mia domanda: ho trascurato qualcosa, o è esatto dire che l'uso di FileStreamResult ti costringe ad avere un percorso di archiviazione intermedio che non saresti costretto ad avere se dovessi scrivere direttamente sull'output della Response lo streaming?

Grazie.

+0

Jonathan, grazie per la correzione di errore. –

risposta

5

Credo che sarebbe più esatto affermarlo, assumendo someObjectThatDumpsOutputToWhateverStreamYouHandIt non eredita da System.IO.Stream, che l'utilizzo di forze FileStreamResult di sia

a) Implementare un wrapper per someObjectThatDumpsOutputToWhateverStreamYouHandIt che non eredita da System.IO.Stream, o

b) utilizzare un'istanza MemoryStream temporanea.

Quindi, non è necessariamente meno efficiente, ma sembra richiedere un po 'più di lavoro allo scopo di restituire un valore.

Modifica su domanda Richiedente: Sto scegliendo questa domanda come risposta accettata. Entrambe le risposte sono state molto utili. Devo sceglierne una, e questa volta mi ha indirizzato a DelefementResult di Phil Haack, che è esattamente ciò di cui avevo bisogno.

+0

Bene, in questo caso, sto chiamando un metodo su una classe di libreria di terze parti che accetta uno stream come parametro del metodo e lo scrive. Quindi non avrebbe senso ereditare da Stream (a meno che non ti fraintenda). Non importa quale libreria di terze parti sto usando ... è un modello abbastanza comune per le librerie che producono grandi risultati simili a stream per prendere uno stream come parametro e scrivere direttamente su di esso. Ma esiste un modo per ereditare da Stream per risolvere il problema? –

+0

Il punto di ereditare da Stream sarebbe che si potesse semplicemente passare lo stream a FileStreamResult. Se la libreria di terze parti supporta la scrittura in blocchi, è possibile implementare un wrapper di flusso attorno all'oggetto che è possibile quindi restituire racchiuso in un FileStreamResult. Se la libreria non supporta la scrittura in blocchi, è possibile provare a creare la propria sottoclasse di ActionResult. – Joseph

+0

Sì, rendere la mia sottoclasse di ActionResult potrebbe avere senso. Questa particolare libreria chiude lo stream prima di tornare. Devo solo fare in modo che ActionResult lo faccia, e potrei renderlo dichiarativo e quindi unitivo al test, credo. In realtà, abbiamo bisogno di un'interfaccia "pull" o di una valutazione lenta come Haskell o qualcosa del genere. Hmmm. Più 1, grazie. –

8

Penso che l'idea principale dietro FileStreamResult è che si utilizza quando si dispone già di un ruscello. Se devi creare un flusso temporaneo per usarlo, allora non ha senso; ecco perché c'è anche un FilePathResult e FileContentResult.

Ci sono diversi casi in cui si potrebbe già avere un flusso:

  • I dati memorizzati in uno SQL 2008 FILESTREAM colonna;
  • Dati inoltrati direttamente da un altro endpoint (NetworkStream);
  • A GzipStream o DeflateStream per l'invio di qualcosa di compresso;
  • Invio da una named pipe (PipeStream);
  • ... e così via.

Il caso d'uso principale per FileStreamResult (se ho capito bene) è quando si dispone di un pre-esistente ruscello che si avrebbe bisogno di leggere da e poi scrivere di nuovo alla risposta; invece di dover leggere e scrivere e capire da solo il buffering, lo FileStreamResult lo gestisce per te.

Quindi la risposta breve è no, FileStreamResult non obbliga necessariamente ad avere un percorso di memorizzazione "intermedio", se lo stream è i tuoi dati source. Non mi preoccuperei di FileStreamResult se i dati sono già in memoria o su disco da qualche parte, pronti e in attesa di essere scritti direttamente nel flusso di risposta.


vorrei solo aggiungere un altro chiarimento: Il problema con questa libreria è che inverte la funzionalità si ha realmente bisogno per FileStreamResult. Invece di darti un flusso da cui puoi leggere, si aspetta che tu fornisca il flusso in modo che possa scrivere su di esso. Questo è un modello comune, attenzione, ma non è molto adatto al compito.

Se lo stream contiene molti dati e non si desidera riascoltare memoria con esso, si dovrebbe essere in grado di invertire il flusso utilizzando le derivate PipeStream. Creare una pipe anonima o anonima, creare un flusso server (che è possibile inizializzare con una dimensione del buffer di dimensioni ridotte) e inviarlo alla libreria, quindi creare un flusso client sulla stessa pipe e inviarlo allo FileStreamResult.

Questo fornisce la testabilità dell'unità desiderata e consente di controllare esattamente la quantità di memoria che il processo può utilizzare. Ha anche il vantaggio di essere in grado di produrre i dati in modo asincrono, cosa che si potrebbe desiderare se si sta cercando di estrarre gigabyte di dati.

+0

Mi stai dando speranza. Ho una libreria con un metodo che mi vuole consegnare un flusso vuoto. Quindi riempirà quel flusso. Io * sicuramente non voglio * un luogo di stoccaggio intermedio. Ma se uso FileStreamResult, sembra che I * am * sia forzato ad avere una memoria intermedia. Stai dicendo che vedi un modo per aggirare questo? –

+0

@Charlie: Se ti fidi della libreria per scrivere direttamente nel flusso di risposta, allora la darei semplicemente. Questo * non * è il caso d'uso principale per 'FileStreamResult'; quel tipo di risultato è migliore per quando stai semplicemente * leggendo * da un flusso che ti è stato fornito, non * scrivendo * su un flusso vuoto che hai creato. È tutto basato sul contesto; 'FileStreamResult' ha i suoi usi ma non sembra che questo sia uno di questi. – Aaronaught

+0

Fresco, questo ha senso. Sono d'accordo sul fatto che io possa vedere buoni usi per questo. È molto utile avere una conferma che non mi manchi qualcosa, quindi grazie. –

Problemi correlati