我正在开发一个 UWP 应用程序,该应用程序使用自定义 API 方法将文件上传到服务器。文件上传本身可以正常工作,但我在上传过程中无法实现进度跟踪。我尝试了多种方法,包括创建自定义 ProgressStreamContent 类,但似乎都不起作用
这是没有进度跟踪的原始 API 方法:
public async Task<ApiResult<FileUploadResult>> UploadFile(string metadata, Stream content, string sessionId, string treeId, string nodeId,Action<long, long> progressCallback = null, CancellationToken? cancellationToken = null)
{
string url = $"{_httpHelper.GetFileUploadsBaseUrl()}/{sessionId}/{treeId}/{nodeId}";
MultipartFormDataContent multipartContent = new MultipartFormDataContent
{
{ new StringContent(metadata), "metadata" },
{ new StreamContent(content), "content" }
};
ApiResult<FileUploadResult> result = await _httpHelper.PostAsync<FileUploadResult>(url, multipartContent, cancellationToken);
return result;
}
这是我尝试使用自定义 ProgressStreamContent 类添加进度跟踪:
public class ProgressStreamContent : HttpContent
{
private const int defaultBufferSize = 5 * 4096;
private HttpContent content;
private int bufferSize;
//private bool contentConsumed;
private Action<long, long> progress;
public ProgressStreamContent(HttpContent content, Action<long, long> progress) : this(content, defaultBufferSize, progress) { }
public ProgressStreamContent(HttpContent content, int bufferSize, Action<long, long> progress)
{
if (content == null)
{
throw new ArgumentNullException("content");
}
if (bufferSize <= 0)
{
throw new ArgumentOutOfRangeException("bufferSize");
}
this.content = content;
this.bufferSize = bufferSize;
this.progress = progress;
foreach (var h in content.Headers)
{
this.Headers.Add(h.Key, h.Value);
}
}
protected override Task SerializeToStreamAsync(Stream stream, TransportContext context)
{
return Task.Run(async () =>
{
var buffer = new Byte[this.bufferSize];
long size;
TryComputeLength(out size);
var uploaded = 0;
using (var sinput = await content.ReadAsStreamAsync())
{
while (true)
{
var length = sinput.Read(buffer, 0, buffer.Length);
if (length <= 0) break;
uploaded += length;
progress?.Invoke(uploaded, size);
stream.Write(buffer, 0, length);
stream.Flush();
}
}
stream.Flush();
});
}
protected override bool TryComputeLength(out long length)
{
length = content.Headers.ContentLength.GetValueOrDefault();
return true;
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
content.Dispose();
}
base.Dispose(disposing);
}
}
在这里我如何使用它,我创建了 DelegatingHandler 来处理进度
public class ProgressMessageHandler : DelegatingHandler
{
private Action<long, long> _onUploadProgress;
public event Action<long, long> HttpProgress
{
add => _onUploadProgress += value;
remove => _onUploadProgress -= value;
}
public ProgressMessageHandler(HttpMessageHandler innerHandler) : base(innerHandler)
{
}
protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var progressContent = new ProgressStreamContent(
request.Content,
4096,
(sent, total) =>
{
Console.WriteLine("Uploading {0}/{1}", sent, total);
OnUploadProgress(sent, total);
});
request.Content = progressContent;
return await base.SendAsync(request, cancellationToken);
}
private void OnUploadProgress(long bytesTransferred, long totalBytes)
{
_onUploadProgress?.Invoke(bytesTransferred, totalBytes);
}
}
并将Api函数更新为这样
public async Task<ApiResult<FileUploadResult>> UploadFile(string metadata, Stream content, string sessionId, string treeId, string nodeId,Action<long, long> progressCallback = null, CancellationToken? cancellationToken = null)
{
string url = $"{_httpHelper.GetFileUploadsBaseUrl()}/{sessionId}/{treeId}/{nodeId}";
MultipartFormDataContent multipartContent = new MultipartFormDataContent
{
{ new StringContent(metadata), "metadata" },
{ new ProgressStreamContent(new StreamContent(content), progressCallback), "content" }
};
ApiResult<FileUploadResult> result = await _httpHelper.PostAsync<FileUploadResult>(url, multipartContent, cancellationToken);
return result;
}
我正在寻求有关如何在 UWP 应用程序中的文件上传过程中正确实施进度跟踪的指导。任何见解或替代方法将不胜感激。谢谢!
我认为你对此过度设计了。您可以使用此答案中的
ProgressStream
,然后只需向其中添加一个事件处理程序即可。
我对此进行了修改以添加必要的 async
版本。
public class ProgressStream : Stream
{
private Stream _input;
private long _progress = 0L;
public event Action<long, long>? UpdateProgress;
public ProgressStream(Stream input)
{
_input = input;
_length = input.Length;
}
public override void Flush() => _input.Flush();
public override Task FlushAsync(CancellationToken cancellationToken = default) => _input.FlushAsync(cancellationToken);
public override int Read(byte[] buffer, int offset, int count) =>
this.Read(buffer.AsSpan(), offset, count);
public override int Read(Span<byte> buffer, int offset, int count)
{
int n = _input.Read(buffer, offset, count);
_progress += n;
UpdateProgress?.Invoke(_progress, _input.Length);
return n;
}
public override async ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
{
int n = await _input.ReadAsync(buffer, offset, count);
_progress += n;
UpdateProgress?.Invoke(this, new ProgressEventArgs((1.0f * _progress) / _input.Length));
return n;
}
protected override void Dispose(bool disposing);
public override ValueTask DisposeAsync() => _input.DisposeAsync();
public override void Write(byte[] buffer, int offset, int count) => throw new System.NotImplementedException();
public override long Seek(long offset, SeekOrigin origin) => throw new System.NotImplementedException();
public override void SetLength(long value) => throw new System.NotImplementedException();
public override bool CanRead => true;
public override bool CanSeek => false;
public override bool CanWrite => false;
public override long Length => _input.Length;
public override long Position
{
get { return _position; }
set { throw new System.NotImplementedException();}
}
}
然后连接事件处理程序并使用普通的StreamContent
。
public async Task<ApiResult<FileUploadResult>> UploadFile(
string metadata, Stream content, string sessionId, string treeId, string nodeId,
Action<long, long> progressCallback = null, CancellationToken? cancellationToken = null)
{
var url = $"{_httpHelper.GetFileUploadsBaseUrl()}/{sessionId}/{treeId}/{nodeId}";
using var progressStream = progressCallback != null ? new ProgressStream(content) : content;
// for performance use original stream if no callback
if (progressCallback != null)
progressStream.UpdateProgress += progressCallback;
using var multipartContent = new MultipartFormDataContent
{
{ new StringContent(metadata), "metadata" },
{ new StreamContent(progressStream), "content" },
};
var result = await _httpHelper.PostAsync<FileUploadResult>(url, multipartContent, cancellationToken);
return result;
}