ASP.NET Core静态文件服务器如何监控文件下载进度?
ASP.NET Core服务端监控文件下载进度方案
方案1:自定义文件传输中间件(推荐)
直接替换默认静态文件中间件,手动处理文件读取与传输,全程监控字节发送量,适配你的静态文件分发场景。
- 定义线程安全的进度跟踪类,用于在中间件和GUI线程间共享进度数据
- 编写自定义中间件,处理文件请求、分块传输并更新进度
- 在GUI中监听进度对象,实时更新UI
1. 进度跟踪类实现
public class DownloadProgressTracker { public string FilePath { get; set; } public long TotalBytes { get; set; } public long SentBytes { get; private set; } public double Progress => TotalBytes == 0 ? 0 : (double)SentBytes / TotalBytes * 100; private readonly object _lock = new object(); public void AddSentBytes(long bytes) { lock (_lock) { SentBytes += bytes; } } public void Reset() { lock (_lock) { SentBytes = 0; TotalBytes = 0; } } }
2. 注册进度跟踪对象并替换中间件
private DownloadProgressTracker _progressTracker; private void RunServer(string configPath) { _progressTracker = new DownloadProgressTracker(); var builder = WebApplication.CreateBuilder(); builder.Services.AddSingleton(_progressTracker); var app = builder.Build(); // 替换默认静态文件中间件为自定义实现 app.UseMiddleware<CustomFileTransferMiddleware>(configPath); app.Run(); }
3. 自定义文件传输中间件
public class CustomFileTransferMiddleware { private readonly RequestDelegate _next; private readonly string _configPath; private readonly DownloadProgressTracker _progressTracker; public CustomFileTransferMiddleware(RequestDelegate next, string configPath, DownloadProgressTracker progressTracker) { _next = next; _configPath = configPath; _progressTracker = progressTracker; } public async Task InvokeAsync(HttpContext context) { var filePath = Path.Combine(_configPath, context.Request.Path.Value.TrimStart('/')); if (!File.Exists(filePath)) { await _next(context); return; } var fileInfo = new FileInfo(filePath); _progressTracker.Reset(); _progressTracker.FilePath = filePath; _progressTracker.TotalBytes = fileInfo.Length; context.Response.ContentType = GetContentType(filePath); context.Response.ContentLength = fileInfo.Length; using var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read); var buffer = new byte[8192]; int bytesRead; while ((bytesRead = await fileStream.ReadAsync(buffer, 0, buffer.Length)) > 0) { await context.Response.Body.WriteAsync(buffer, 0, bytesRead); _progressTracker.AddSentBytes(bytesRead); await context.Response.Body.FlushAsync(); } } private string GetContentType(string filePath) { var ext = Path.GetExtension(filePath).ToLowerInvariant(); return ext switch { ".txt" => "text/plain", ".bin" => "application/octet-stream", _ => "application/octet-stream" }; } }
4. GUI端监控进度(以WPF为例)
private async void MonitorDownloadProgress() { while (true) { var progress = _progressTracker.Progress; Dispatcher.Invoke(() => { DownloadProgressBar.Value = progress; StatusLabel.Text = $"下载进度:{progress:F1}%"; }); await Task.Delay(100); } }
方案2:包装响应流监控进度
若需保留静态文件中间件原有功能,可通过包装响应流拦截字节写入操作,统计传输进度。
1. 进度跟踪流实现
public class ProgressTrackingStream : Stream { private readonly Stream _innerStream; private readonly DownloadProgressTracker _progressTracker; public ProgressTrackingStream(Stream innerStream, DownloadProgressTracker progressTracker) { _innerStream = innerStream; _progressTracker = progressTracker; } public override bool CanRead => _innerStream.CanRead; public override bool CanSeek => _innerStream.CanSeek; public override bool CanWrite => _innerStream.CanWrite; public override long Length => _innerStream.Length; public override long Position { get => _innerStream.Position; set => _innerStream.Position = value; } public override void Flush() => _innerStream.Flush(); public override int Read(byte[] buffer, int offset, int count) => _innerStream.Read(buffer, offset, count); public override long Seek(long offset, SeekOrigin origin) => _innerStream.Seek(offset, origin); public override void SetLength(long value) => _innerStream.SetLength(value); public override void Write(byte[] buffer, int offset, int count) { _innerStream.Write(buffer, offset, count); _progressTracker.AddSentBytes(count); } public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) { await _innerStream.WriteAsync(buffer, offset, count, cancellationToken); _progressTracker.AddSentBytes(count); } protected override void Dispose(bool disposing) { if (disposing) { _innerStream.Dispose(); } base.Dispose(disposing); } }
2. 集成到现有服务启动代码
private void RunServer(string configPath) { _progressTracker = new DownloadProgressTracker(); var builder = WebApplication.CreateBuilder(); builder.Services.AddSingleton(_progressTracker); var app = builder.Build(); // 在静态文件中间件前添加流包装逻辑 app.Use(async (context, next) => { var originalStream = context.Response.Body; try { var filePath = Path.Combine(configPath, context.Request.Path.Value.TrimStart('/')); if (File.Exists(filePath)) { var fileInfo = new FileInfo(filePath); _progressTracker.Reset(); _progressTracker.TotalBytes = fileInfo.Length; context.Response.Body = new ProgressTrackingStream(originalStream, _progressTracker); } await next(); } finally { context.Response.Body = originalStream; } }); app.UseStaticFiles(new StaticFileOptions() { FileProvider = new PhysicalFileProvider(configPath), ServeUnknownFileTypes = true, DefaultContentType = "text/plain" }); app.Run(); }
注意事项
- 多设备同时下载时,需扩展进度跟踪类,支持按文件路径或请求ID区分任务
- 确保进度跟踪对象的线程安全性,避免读写冲突
- GUI更新需遵循对应框架的跨线程操作规范(如WinForms需用
Invoke)
内容的提问来源于stack exchange,提问作者roegra
相关产品推荐
相关产品推荐

