Dies ist eine Follow-up zu einer Frage, die ich zuvor gefragt hatte, dass zu weit geschlossen wurde geschlossen. Previous QuestionGroße Datei Download von SQL über WebApi nach benutzerdefinierten MultipartFormDataStreamProvider Upload
In dieser Frage habe ich erklärt, dass ich eine große Datei (1-3 GB) in die Datenbank hochladen musste, indem ich Stücke als einzelne Zeilen speicherte. Ich habe dies getan, indem ich die MultipartFormDataStreamProvider.GetStream-Methode überschrieben habe. Diese Methode hat einen benutzerdefinierten Stream zurückgegeben, der die gepufferten Chunks in die Datenbank geschrieben hat.
Das Problem besteht darin, dass die überschriebene GetStream-Methode die gesamte Anforderung in die Datenbank schreibt (einschließlich der Header). Es schreibt erfolgreich diese Daten, während die Speicherlevel flach bleiben, aber wenn ich die Datei herunterlade, gibt es zusätzlich zu den Dateiinhalten alle Headerinformationen in den heruntergeladenen Dateiinhalten zurück, so dass die Datei nicht geöffnet werden kann.
Gibt es eine Möglichkeit, in der überschriebenen GetStream-Methode nur den Inhalt der Datei in die Datenbank zu schreiben, ohne die Header zu schreiben?
API
[HttpPost]
[Route("file")]
[ValidateMimeMultipartContentFilter]
public Task<HttpResponseMessage> PostFormData()
{
var provider = new CustomMultipartFormDataStreamProvider();
// Read the form data and return an async task.
var task = Request.Content.ReadAsMultipartAsync(provider).ContinueWith<HttpResponseMessage>(t =>
{
if (t.IsFaulted || t.IsCanceled)
{
Request.CreateErrorResponse(HttpStatusCode.InternalServerError, t.Exception);
}
return Request.CreateResponse(HttpStatusCode.OK);
});
return task;
}
[HttpGet]
[Route("file/{id}")]
public async Task<HttpResponseMessage> GetFile(string id)
{
var result = new HttpResponseMessage()
{
Content = new PushStreamContent(async (outputStream, httpContent, transportContext) =>
{
await WriteDataChunksFromDBToStream(outputStream, httpContent, transportContext, id);
}),
StatusCode = HttpStatusCode.OK
};
result.Content.Headers.ContentType = new MediaTypeHeaderValue("application/zipx");
result.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment") { FileName = "test response.zipx" };
return result;
}
return new HttpResponseMessage(HttpStatusCode.BadRequest);
}
private async Task WriteDataChunksFromDBToStream(Stream responseStream, HttpContent httpContent, TransportContext transportContext, string fileIdentifier)
{
// PushStreamContent requires the responseStream to be closed
// for signaling it that you have finished writing the response.
using (responseStream)
{
using (var myConn = new SqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings["TestDB"].ConnectionString))
{
await myConn.OpenAsync();
using (var myCmd = new SqlCommand("ReadAttachmentChunks", myConn))
{
myCmd.CommandType = System.Data.CommandType.StoredProcedure;
var fileName = new SqlParameter("@Identifier", fileIdentifier);
myCmd.Parameters.Add(fileName);
// Read data back from db in async call to avoid OutOfMemoryException when sending file back to user
using (var reader = await myCmd.ExecuteReaderAsync(CommandBehavior.SequentialAccess))
{
while (await reader.ReadAsync())
{
if (!(await reader.IsDBNullAsync(3)))
{
using (var data = reader.GetStream(3))
{
// Asynchronously copy the stream from the server to the response stream
await data.CopyToAsync(responseStream);
}
}
}
}
}
}
}// close response stream
}
Benutzerdefinierte MultipartFormDataStreamProvider GetStream Methode Implementierung
public override Stream GetStream(HttpContent parent, HttpContentHeaders headers)
{
// For form data, Content-Disposition header is a requirement
ContentDispositionHeaderValue contentDisposition = headers.ContentDisposition;
if (contentDisposition != null)
{
// If we have a file name then write contents out to AWS stream. Otherwise just write to MemoryStream
if (!String.IsNullOrEmpty(contentDisposition.FileName))
{
var identifier = Guid.NewGuid().ToString();
var fileName = contentDisposition.FileName;// GetLocalFileName(headers);
if (fileName.Contains("\\"))
{
fileName = fileName.Substring(fileName.LastIndexOf("\\") + 1).Replace("\"", "");
}
// We won't post process files as form data
_isFormData.Add(false);
var stream = new CustomSqlStream();
stream.Filename = fileName;
stream.Identifier = identifier;
stream.ContentType = headers.ContentType.MediaType;
stream.Description = (_formData.AllKeys.Count() > 0 && _formData["description"] != null) ? _formData["description"] : "";
return stream;
//return new CustomSqlStream(contentDisposition.Name);
}
// We will post process this as form data
_isFormData.Add(true);
// If no filename parameter was found in the Content-Disposition header then return a memory stream.
return new MemoryStream();
}
throw new InvalidOperationException("Did not find required 'Content-Disposition' header field in MIME multipart body part..");
#endregion
}
Implementiert Write-Methode Stream genannt durch CustomSqlStream
public override void Write(byte[] buffer, int offset, int count)
{
//write buffer to database
using (var myConn = new SqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings["TestDB"].ConnectionString)) {
using (var myCmd = new SqlCommand("WriteAttachmentChunk", myConn)) {
myCmd.CommandType = System.Data.CommandType.StoredProcedure;
var pContent = new SqlParameter("@Content", buffer);
myCmd.Parameters.Add(pContent);
myConn.Open();
myCmd.ExecuteNonQuery();
if (myConn.State == System.Data.ConnectionState.Open)
{
myConn.Close();
}
}
}
((ManualResetEvent)_dataAddedEvent).Set();
}
Die gespeicherte Prozedur "ReadAttachmentChunks" ruft die entsprechenden Zeilen von der Datenbank ab, die zum Zeitpunkt des Einfügens in die Datenbank bestellt wurde. Die Art und Weise, wie der Code funktioniert, ist, dass er diese Chunks zurückzieht und dann async zurück in den PushStreamContent schreibt, um zum Benutzer zurückzukehren.
Also meine Frage ist:
Gibt es eine Möglichkeit nur den Inhalt der Datei auf die Header zusätzlich zu dem Inhalt im Gegensatz hochgeladen werden zu schreiben?
Jede Hilfe würde sehr geschätzt werden. Vielen Dank.
Ist es böse, weil es eine ist bessere Möglichkeit, dies zu tun, oder weil Sie dies im Allgemeinen nicht tun? Jedes Mal, wenn ich eine große Dateiübertragung gemacht habe, habe ich es auf die Festplatte geschafft und den Dateispeicherbereich gesperrt, so dass ich mich nie mit so etwas herumschlagen musste, also verzeih mir, wenn das, was ich mache, einfach dumm ist . Die Anforderungen sind, dass ich die große Datei in der Datenbank speichern muss (Filestream kann nicht verwendet werden), und ich muss es verschlüsseln, bevor es dort ankommt, während der Speicherbedarf gering gehalten wird. Diese chunkende Idee war die einzige Möglichkeit, die ich mir vorstellen konnte. – JakeHova
Der unangenehme Teil ist die Tatsache, dass Sie im Framework keine Sachen verwenden können, um all die schmutzige Arbeit für Sie zu erledigen. Vielleicht finden Sie auf Nuget eine http-Client-Bibliothek eines Drittanbieters, sehen sich die Quelle an und sehen, wie sie sich im Falle eines Hochladens eines Upload-Streams auswirkt. –
Es scheint, dass mein einziges Problem jetzt ist, dass die Anfrage Header in meinem Schreiben in die db enthalten sind. Ich habe verschiedene Möglichkeiten ausprobiert, die Header manuell zu entfernen, indem ich den Grenzwert nutze, um zu erkennen, wo ein Header ist/endet, aber 1) er funktioniert nicht auf Nicht-Text-Dateien 2) es fühlt sich an wie eine hackische und zerbrechliche Lösung für was sollte sei ein einfaches Problem. Irgendwelche Gedanken darüber, wie ich die Header herausziehen kann, bevor ich sie schreibe? – JakeHova