C#:HttpClient,实现上传文件时的进度(UWP)

问题描述 投票:0回答:1

我正在开发一个 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 应用程序中的文件上传过程中正确实施进度跟踪的指导。任何见解或替代方法将不胜感激。谢谢!

c# file file-upload uwp dotnet-httpclient
1个回答
0
投票

我认为你对此过度设计了。您可以使用此答案中的

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; }
    
© www.soinside.com 2019 - 2024. All rights reserved.