我已经能够使用以下方法从 HTTP API 读取原始二进制内容:
var response = httpClient.GetStreamAsync("http://...");
但是如何做同样的写作?
到目前为止,我还没有找到打开
Stream
来异步写入服务器的方法。
方法
HttpClient.PostAsync(...)
对我来说没有用,因为它将控制服务器的读写过程。
我的目标是拥有一个可以写入的
Stream
,就像任何其他流一样。
这可能吗?
查看
HttpContent
类,这实际上应该很容易实现。您可以在使用回调方法的情况下进行自己的实现。然后,将使用您需要写入的 Stream
来调用此回调。
实施:
public class HttpContentCallback : HttpContent
{
private readonly Func<Stream, Task> _callback;
public HttpContentCallback(Func<Stream, Task> callback)
{
_callback = callback;
}
protected override Task SerializeToStreamAsync(Stream stream, TransportContext? context)
{
return _callback(stream);
}
protected override bool TryComputeLength(out long length)
{
length = 0;
return false;
}
}
按照 @Charlieface 的建议,具有取消令牌支持(.NET 5 或更高版本):
public class HttpContentCallback : HttpContent
{
private readonly Func<Stream, CancellationToken, Task> _callback;
public HttpContentCallback(Func<Stream, CancellationToken, Task> callback)
{
_callback = callback;
}
protected override Task SerializeToStreamAsync(Stream stream, TransportContext? context)
{
return SerializeToStreamAsync(stream, context, CancellationToken.None);
}
protected override Task SerializeToStreamAsync(Stream stream, TransportContext? context, CancellationToken token)
{
return _callback(stream, token);
}
protected override bool TryComputeLength(out long length)
{
length = 0;
return false;
}
}
使用当前的 API 做你想做的事情有点困难。
根据您的要求,您可能只想“传递”现有流。在这种情况下使用 StreamContent
。请注意,在进行调用时需要填充流(或以其他方式完全可读)。例如,您可以通过这种方式传递
FileStream
。using var fs = new FileStream("someFile");
using var message = new HttpRequestMessage(HttpMethod.Post, "http://someurl.com");
message.Content = new StreamContent(fs);
using var resp = await _client.SendAsync(message);
// etc
这将是发送大多数请求的最简单方法,因为您通常要发送另一个流。
如果您希望能够拦截调用以检索流并填充它,那么情况会更复杂一些。您需要创建自己的
HttpContent
类,至少实现必要的最小覆盖。
HttpContent
的文档中给出了一个示例。
HttpClient
类不支持以您想要的方式写入流,您必须使用旧的
HttpWebRequest
类。using System;
using System.IO;
using System.Net;
internal class Program
{
public static void Main(string[] args)
{
string filePath = "C:/example.bin";
string uploadUrl = "http://example.com/upload";
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uploadUrl);
request.Method = "POST";
request.ContentType = "application/octet-stream";
byte[] fileData = File.ReadAllBytes(filePath);
request.ContentLength = fileData.Length;
using (Stream requestStream = request.GetRequestStream())
{
int bufferSize = 4096;
int bytesWritten = 0;
while (bytesWritten < fileData.Length)
{
int bytesToWrite = Math.Min(bufferSize, fileData.Length - bytesWritten);
requestStream.Write(fileData, bytesWritten, bytesToWrite);
bytesWritten += bytesToWrite;
}
}
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
using (Stream responseStream = response.GetResponseStream())
using (StreamReader reader = new StreamReader(responseStream))
{
Console.WriteLine(reader.ReadToEnd());
}
response.Close();
}
}
请注意,Microsoft 建议XY问题
。 如果您的根本问题是“如何实现进度条”,并且您正在寻求帮助来实现“在循环中使用流”的解决方案,那么有一个替代解决方案可以实现效果很好的进度条与
HTTPClient
班一起。
private async Task UploadFileWithProgress(string url, string filePath)
{
using (var httpClient = new HttpClient())
{
using (var fileStream = File.OpenRead(filePath))
{
var content = new StreamContent(fileStream);
content.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data")
{
FileName = Path.GetFileName(filePath)
};
content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
var progressContent = new ProgressableStreamContent(content, 4096, UploadProgressCallback);
var response = await httpClient.PostAsync(url, progressContent);
var responseContent = await response.Content.ReadAsStringAsync();
Console.WriteLine(responseContent);
}
}
}
private void UploadProgressCallback(long bytesUploaded, long totalBytes)
{
// Update progress bar here
}