c# - visual - WebAPI StreamContent vs PushStreamContent
web api rest c# (1)
Con respecto al uso de memoria de estos dos enfoques, para StreamContent y PushStreamContent, Web API no almacena en búfer las respuestas. La siguiente captura de código es de WebHostBufferPolicySelector. Código fuente here .
/// <summary>
/// Determines whether the host should buffer the <see cref="HttpResponseMessage"/> entity body.
/// </summary>
/// <param name="response">The <see cref="HttpResponseMessage"/>response for which to determine
/// whether host output buffering should be used for the response entity body.</param>
/// <returns><c>true</c> if buffering should be used; otherwise a streamed response should be used.</returns>
public virtual bool UseBufferedOutputStream(HttpResponseMessage response)
{
if (response == null)
{
throw Error.ArgumentNull("response");
}
// Any HttpContent that knows its length is presumably already buffered internally.
HttpContent content = response.Content;
if (content != null)
{
long? contentLength = content.Headers.ContentLength;
if (contentLength.HasValue && contentLength.Value >= 0)
{
return false;
}
// Content length is null or -1 (meaning not known).
// Buffer any HttpContent except StreamContent and PushStreamContent
return !(content is StreamContent || content is PushStreamContent);
}
return false;
}
Además, PushStreamContent es para escenarios en los que necesita ''insertar'' datos en la transmisión, mientras StreamContent ''extrae'' datos de la transmisión. Por lo tanto, para su escenario actual de descarga de archivos, usar StreamContent debería estar bien.
Ejemplos a continuación:
// Here when the response is being written out the data is pulled from the file to the destination(network) stream
response.Content = new StreamContent(File.OpenRead(filePath));
// Here we create a push stream content so that we can use XDocument.Save to push data to the destination(network) stream
XDocument xDoc = XDocument.Load("Sample.xml", LoadOptions.None);
PushStreamContent xDocContent = new PushStreamContent(
(stream, content, context) =>
{
// After save we close the stream to signal that we are done writing.
xDoc.Save(stream);
stream.Close();
},
"application/xml");
Estoy implementando una versión MVC4 + WebAPI de BluImp jQuery File Upload, todo funciona bien con mi intento inicial, pero trato de garantizar el mejor uso de la memoria mientras descargo archivos muy grandes (~ 2GB).
He leído el artículo de Filip Woj sobre PushStreamContent y lo he implementado lo mejor que puedo (eliminando las partes de sincronización - quizás este es el problema?). Cuando estoy ejecutando pruebas y viendo el Administrador de tareas, no veo demasiada diferencia en el uso de la memoria y trato de entender la diferencia entre cómo se manejan las respuestas.
Aquí está mi versión de StreamContent:
private HttpResponseMessage DownloadContentNonChunked()
{
var filename = HttpContext.Current.Request["f"];
var filePath = _storageRoot + filename;
if (File.Exists(filePath))
{
HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.OK);
response.Content = new StreamContent(new FileStream(filePath, FileMode.Open, FileAccess.Read));
response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
{
FileName = filename
};
return response;
}
return ControllerContext.Request.CreateErrorResponse(HttpStatusCode.NotFound, "");
}
Y aquí está mi versión PushStreamContent:
public class FileDownloadStream
{
private readonly string _filename;
public FileDownloadStream(string filePath)
{
_filename = filePath;
}
public void WriteToStream(Stream outputStream, HttpContent content, TransportContext context)
{
try
{
var buffer = new byte[4096];
using (var video = File.Open(_filename, FileMode.Open, FileAccess.Read))
{
var length = (int)video.Length;
var bytesRead = 1;
while (length > 0 && bytesRead > 0)
{
bytesRead = video.Read(buffer, 0, Math.Min(length, buffer.Length));
outputStream.Write(buffer, 0, bytesRead);
length -= bytesRead;
}
}
}
catch (HttpException ex)
{
return;
}
finally
{
outputStream.Close();
}
}
}
private HttpResponseMessage DownloadContentChunked()
{
var filename = HttpContext.Current.Request["f"];
var filePath = _storageRoot + filename;
if (File.Exists(filePath))
{
var fileDownload = new FileDownloadStream(filePath);
var response = Request.CreateResponse();
response.Content = new PushStreamContent(fileDownload.WriteToStream, new MediaTypeHeaderValue("application/octet-stream"));
response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
{
FileName = filename
};
return response;
}
return ControllerContext.Request.CreateErrorResponse(HttpStatusCode.NotFound, "");
}
Mi pregunta es ¿por qué no veo mucha diferencia en el uso de la memoria entre los dos enfoques? Además, he descargado el PDB para el tipo StreamContent y puedo ver referencias a tamaños de buffer y cosas por el estilo (ver más abajo), así que me gustaría saber exactamente qué PushStreamContent está haciendo más allá de StreamContent. He revisado la información de tipo en MSDN, pero el artículo fue un poco claro en la explicación.
namespace System.Net.Http
{
/// <summary>
/// Provides HTTP content based on a stream.
/// </summary>
[__DynamicallyInvokable]
public class StreamContent : HttpContent
{
private Stream content;
private int bufferSize;
private bool contentConsumed;
private long start;
private const int defaultBufferSize = 4096;
/// <summary>
/// Creates a new instance of the <see cref="T:System.Net.Http.StreamContent"/> class.
/// </summary>
/// <param name="content">The content used to initialize the <see cref="T:System.Net.Http.StreamContent"/>.</param>
[__DynamicallyInvokable]
[TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")]
public StreamContent(Stream content)
: this(content, 4096)
{
}