2016-04-12 6 views
2

Ich habe einige große Datendateien, die ich in Chunks von sagen wir 32kb mit einer speziell dafür entwickelten API abrufen kann. Eine Verwendung der API die folgenden sein kann:Erstellen von .Net Stream aus Datenstücken

LargeFileAPI lfa = new LargeFileAPI("file1.bin"); 
bool moredata = true; 
List<byte[]> theWholeFile = new List<byte[]>(); 
while (moredata ) 
{ 
    byte[] arrayRead = new byte[32768]; 
    moredata = lfa.Read(arrayRead); 
    theWholeFile.Add(arrayRead); 
} 

Das Problem mit der oben ist, dass aus der Lektüre so viel Speicher wie die Größe der großen Datei in Anspruch nimmt (100Mb sagen wir mal). Und da ich dies als Ergebnis an einen WCF-Dienst weitergeben möchte, würde ich lieber einen Stream als Ausgabe des Dienstes verwenden.

Wie kann ich ein Stream-Objekt daraus erstellen und es als Rückgabewert an einen WCF-Dienst übergeben, ohne die gesamte Dateigröße im Speicher zu belegen?

Ich dachte an eine Klasse LargeFileStream Schaffung erben von

System.IO.Stream 

und die Read-Methode außer Kraft setzen. Aber ich kann nicht herausfinden, wie man durch die Tatsache arbeitet, dass Stream.Read einen Offset-Parameter und eine Anzahl von Bytes zum Lesen benötigt, weil die API, die ich erwähnte, eine feste Anzahl von Bytes für jeden Lesevorgang lesen muss. Außerdem, was ist mit all den anderen Methoden, die ich überschreiben muss, wie Flush(), Position und was sonst noch dort ist. Was sollten sie implementieren? Ich frage, weil ich keine Ahnung habe, welche anderen Funktionen als Stream.Read() WCF aufrufen würde, wenn ich den Stream vom Client (dem Aufrufer des WCF-Dienstes) lese.

Darüber hinaus muss es serialisierbar sein, damit es ein Ausgabeparameter für einen WCF-Dienst sein kann.

Dank Jihad

Antwort

2

Sie können Ihren Stream schreiben, um zu tun, was Sie wollen, mit einem Puffer Ihrer API-Größe (d. H. 32kb) und recyceln Sie es beim Lesen. Beispielcode ist unten (nicht, dass es nicht die Produktion bereit ist, und braucht Tests, aber etwas, das man einen Anfang zu machen):

public class LargeFileApiStream : Stream { 
    private readonly LargeFileApi _api; 
    private bool _hasMore; 
    private bool _done; 
    private byte[] _buffer; 
    const int ApiBufferSize = 32768; 
    public LargeFileApiStream(LargeFileApi api) { 
     _api = api;  
    } 

    public override void Flush() { 
     // you can ignore that, this stream is not writable 
    } 

    public override long Seek(long offset, SeekOrigin origin) { 
     throw new NotSupportedException(); // not seekable, only read from beginning to end 
    } 

    public override void SetLength(long value) { 
     throw new NotSupportedException(); // not writable 
    }   

    public override void Write(byte[] buffer, int offset, int count) { 
     throw new NotSupportedException(); // not writable 
    } 

    public override int Read(byte[] buffer, int offset, int count) { 
     // if we reached end of stream before - done 
     if (_done) 
      return 0; 

     if (_buffer == null) { 
      // we are just starting, read first block 
      _buffer = new byte[ApiBufferSize]; 
      _hasMore = _api.Read(_buffer); 
     } 

     var nextIndex = _position % ApiBufferSize; 
     int bytesRead = 0; 
     for (int i = 0; i < count; i++) { 
      if (_buffer.Length <= nextIndex) { 
       // ran out of current chunk - fetch next if possible      
       if (_hasMore) { 
        _hasMore = _api.Read(_buffer); 
       } 
       else { 
        // we are done, nothing more to read 
        _done = true; 
        break; 
       } 
       // reset next index back to 0, we are now reading next chunk 
       nextIndex = 0; 
       buffer[offset + i] = _buffer[nextIndex]; 
       nextIndex++; 
       bytesRead++; 
      } 
      else { 
       // write byte to output buffer 
       buffer[offset + i] = _buffer[nextIndex]; 
       nextIndex++; 
       bytesRead++; 
      }                 
     } 

     _position += bytesRead; 
     return bytesRead; 
    } 

    public override bool CanRead { 
     get { return true; } 
    } 
    public override bool CanSeek { 
     get { return false; } 
    } 
    public override bool CanWrite { 
     get { return false; } 
    } 
    public override long Length { 
     get { throw new NotSupportedException(); } 
    } 

    private long _position; 
    public override long Position 
    { 
     get { return _position; } 
     set { throw new NotSupportedException(); } // not seekable 
    } 
} 
+0

Das ist genau das, was ich dachte. Ich wollte nur sicherstellen, dass ich den richtigen Weg dachte. Danke –

+0

Wie stelle ich sicher, dass die Verbindung zu LargeFileAPI korrekt geschlossen ist. Ich habe versucht, einige Tests Datei FileStream und haben festgestellt, dass weder Dispose noch Close aufgerufen werden, wenn der WCF-Dienst nicht mehr lesen. –

+0

WCF sollte die zurückgegebenen Streams standardmäßig zurückweisen, sofern Sie dies nicht geändert haben. Wenn Sie einen benutzerdefinierten Stream wie oben verwenden, überschreiben Sie die Dispose-Methode der Basis-Stream-Klasse und schließen Sie dort Ihre API-Verbindung. Siehe auch: http://StackOverflow.com/q/6483320/5311735 – Evk

1

speichern Sie Ihre Daten in temporäre Datei wie folgt aus:

// create temporary stream 
var stream = new FileStream(Path.GetTempFileName(), FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None, 4096, FileOptions.DeleteOnClose); 

try 
{ 
    // write all data to temporary stream 
    while (moredata) 
    { 
     byte[] arrayRead = new byte[32768]; 
     moredata = lfa.Read(arrayRead); 
     stream.Write(arrayRead, 0, arrayRead.Length); 
    } 

    stream.Flush(); 

    stream.Position = 0; // Reset position so stream will be read from beginning 
} 
catch 
{ 
    stream.Close(); // close stream to delete temporary file if error occured 
} 

temporäre Datei-Stream enthält Daten von LargeFileApi empfangen. Sie werden nicht mehr genügend Speicherplatz haben, da die Daten tatsächlich in der Datei gespeichert sind.

Die temporäre Datei wird gelöscht, nachdem der Stream wegen der an den Konstruktor übergebenen FileOptions.DeleteOnClose-Option geschlossen wurde. So können Sie den Stream einfach schließen, wenn etwas schief geht oder wenn Sie mit dem Lesen fertig sind.

+0

Danke. Dies ist definitiv ein Weg zu gehen. Ich habe jedoch die folgenden Probleme, denen Sie mir helfen können: 1- Wenn etwas schief geht (z. B. der Web-Service abstürzt), dann wird die Datei nicht gelöscht. 2- Es wird nicht so effizient sein, die ganze Datei auf die Festplatte zu schreiben und sie dann erneut zu lesen. 3. Außerdem, bin ich sicher, dass der Web Service die Erlaubnis hat, in eine temporäre Datei zu schreiben? Was ist hier Standard/Best Practice für das IIS-Setup? Die Produktionsumgebung, die wir haben, ist bekanntlich ziemlich restriktiv. –

+0

Über Effizienz: @Evk habe eine nette Idee, wie man Daten als Stream ohne temporären Puffer liest. Aber es ist viel schwieriger, den besten Weg zu implementieren, und es gibt eine Frage darüber, wie LargeFileApi funktioniert. Wenn LargeFileApi eine Verbindung hält, bis die Datei vollständig gelesen wird, kann die Verbindung ablaufen, wenn die Datei nicht vollständig gelesen wurde. Ich hatte solche Probleme (mit abgelaufener Verbindung) in der Produktion vor langer Zeit und fing an, Lösung mit temporärer Datei zu verwenden. Also, ich würde empfehlen, beide Lösungen zu testen und zu entscheiden, was am besten in Ihrem Fall funktioniert –

+0

Über Ihre Bedenken: 1. Sie können während (modedata) {...} in Versuch ... wickeln ... catch Block und schließen Stream im Ausnahmefall . 2. Es hängt davon ab, was Sie mit Stream tun möchten. 3. Ich bin zu 90% sicher, welcher Webservice Zugang zu _temporary directory_ hätte, aber es ist einfach zu überprüfen, um sicher zu sein auf 100% –

0

Sie folgendes ausführen:

  1. Erstellen Sie einen WCF-Dienst mit netTcpBinding . Der Dienst kann ein Objekt mit Message Attribut Rückkehr auf sie angewendet

    [Message] public class LargeStream {

    [MessageHeader] 
    public int Section { get; set; } 
    
    [MessageBodyMember] 
    public Stream Data { get; set; } 
    

    }

Wenn Sie weitere Metadaten hinzufügen möchten sie schmücken mit dem MessageHeader-Attribut.

Auf der Clientseite kann die Webanwendung den Dienst verwenden und zwei Anforderungen stellen.

  1. Um die Anzahl der Abschnitte für die Datei
  2. Für jeden Abschnitt Anfrage der Strom
  3. Kombinieren Sie alle Ströme zu erhalten, nachdem der Download in einer einzigen Datei durchgeführt wird.