客户端UI:
服务端WebApi:
客户端代码:
App.config:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="WebApi" value="https://localhost:7285"/>
<add key="SavePath" value="E:\Down"/>
<add key="DownFileName" value="win11x64.esd"/>
<add key="UploadFileName" value="win11x64.esd"/>
</appSettings>
</configuration>
自定义进度条:
CustomProgressBar.cs(长方形)
using System;
using System.Windows.Forms;
using System.Drawing;
namespace FileUploadAndDown
{
public class CustomProgressBar : ProgressBar
{
// 添加一个属性来存储速率文本
public string RateText { get; set; } = "0 KB/s";
public CustomProgressBar()
{
// 设置样式以允许重绘。
this.SetStyle(ControlStyles.UserPaint, true);
}
protected override void OnPaint(PaintEventArgs e)
{
Rectangle rect = this.ClientRectangle;
Graphics g = e.Graphics;
ProgressBarRenderer.DrawHorizontalBar(g, rect);
rect.Inflate(-3, -3); // 减小大小以适应边界
if (this.Value > 0)
{
Rectangle clip = new Rectangle(rect.X, rect.Y, (int)Math.Round(((float)this.Value / this.Maximum) * rect.Width), rect.Height);
g.FillRectangle(Brushes.GreenYellow, clip);
}
string text = this.Value.ToString() + "%";
using (Font f = new Font(FontFamily.GenericSansSerif, 10))
{
SizeF len = g.MeasureString(text, f);
Point location = new Point((int)((rect.Width / 2) - (len.Width / 2)), (int)((rect.Height / 2) - (len.Height / 2)));
g.DrawString(text, f, Brushes.Black, location);
// 绘制速率文本
SizeF rateLen = g.MeasureString(RateText, f);
Point rateLocation = new Point(rect.Right - (int)rateLen.Width - 5, (int)((rect.Height / 2) - (rateLen.Height / 2)));
g.DrawString(RateText, f, Brushes.Black, rateLocation);
}
}
}
}
自定义圆型进度条:
CircularProgressBar.cs
using System;
using System.Drawing;
using System.Windows.Forms;
namespace FileUploadAndDown
{
public class CircularProgressBar : UserControl
{
private int progress = 0;
private int max = 100;
public int Progress
{
get { return progress; }
set
{
progress = value;
this.Invalidate(); // 通知控件需要重绘
}
}
public int Maximum
{
get { return max; }
set
{
max = value;
this.Invalidate(); // 通知控件需要重绘
}
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
Graphics g = e.Graphics;
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
// 绘制外圈
g.DrawEllipse(Pens.Black, 0, 0, this.Width - 1, this.Height - 1);
// 计算进度
float sweepAngle = 360f * progress / max;
// 绘制进度
using (Brush brush = new SolidBrush(Color.Blue))
{
g.FillPie(brush, 0, 0, this.Width, this.Height, -90, sweepAngle);
}
// 绘制中心覆盖圆,形成环形进度条
int coverSize = 20; // 中心圆的大小,可以根据需要调整
using (Brush brush = new SolidBrush(this.BackColor))
{
g.FillEllipse(brush, (this.Width - coverSize) / 2, (this.Height - coverSize) / 2, coverSize, coverSize);
}
}
}
}
HttpContent.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net;
using System.Text;
using System.Threading.Tasks;
namespace FileUploadAndDown
{
public class ProgressableStreamContent : HttpContent
{
private readonly HttpContent content;
private readonly int bufferSize = 4096;
private readonly Action<long, long> progress;
public ProgressableStreamContent(HttpContent content, Action<long, long> progress)
{
this.content = content ?? throw new ArgumentNullException(nameof(content));
this.progress = progress ?? throw new ArgumentNullException(nameof(progress));
foreach (var header in content.Headers)
{
this.Headers.TryAddWithoutValidation(header.Key, header.Value);
}
}
protected override async Task SerializeToStreamAsync(Stream stream, TransportContext context)
{
var buffer = new byte[bufferSize];
TryComputeLength(out long size);
var uploaded = 0L;
using (var contentStream = await content.ReadAsStreamAsync())
{
var read = 0;
while ((read = await contentStream.ReadAsync(buffer, 0, buffer.Length)) > 0)
{
await stream.WriteAsync(buffer, 0, read);
uploaded += read;
progress(uploaded, size);
}
}
}
protected override bool TryComputeLength(out long length)
{
length = content.Headers.ContentLength ?? -1;
return length != -1;
}
}
}
ServerResponse.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace FileUploadAndDown
{
public class ServerResponse
{
public int Code { get; set; }
public string Message { get; set; }
public FileListData Data { get; set; }
}
public class FileListData
{
public List<string> Files { get; set; }
}
}
MainWinFrm.cs
using System;
using System.IO;
using System.Net.Http;
using System.Windows.Forms;
using System.Threading.Tasks;
using static System.Windows.Forms.VisualStyles.VisualStyleElement;
using System.Configuration;
using static System.Net.WebRequestMethods;
namespace FileUploadAndDown
{
public partial class MainWinFrm : Form
{
private HttpClient _httpClient = new HttpClient();
public List<string> fileInfos = new List<string>();
/// <summary>
/// 保存文件路径
/// </summary>
public string? SavePath { get; set; }
/// <summary>
/// 下载文件名称
/// </summary>
public string? DownFileName { get; set; }
/// <summary>
/// 上传文件名称
/// </summary>
public string? UploadFileName { get; set; }
/// <summary>
/// WebApi接口
/// </summary>
public string? WebApi { get; set; }
public MainWinFrm()
{
InitializeComponent();
//读取配置信息
this.WebApi = ConfigurationManager.AppSettings["WebApi"]!;
this.SavePath = ConfigurationManager.AppSettings["SavePath"]!;
this.DownFileName = ConfigurationManager.AppSettings["DownFileName"]!;
this.UploadFileName = ConfigurationManager.AppSettings["UploadFileName"]!;
_httpClient = new HttpClient
{
Timeout = TimeSpan.FromMinutes(10) // 设置超时时间为10分钟
};
// 初始化进度条
InitializeUI();
}
private void InitializeUI()
{
// 设置进度条属性
progressBar.Minimum = 0;
progressBar.Maximum = 100;
progressBar.Value = 0;
FetchDownloadableFiles();//获取所有文件
}
#region 上传文件
private async void btn_Upload_Click(object sender, EventArgs e)
{
OpenFileDialog openFileDialog = new OpenFileDialog();
if (openFileDialog.ShowDialog() == DialogResult.OK)
{
string filePath = openFileDialog.FileName;
await UploadFile(filePath);
}
}
#endregion
#region 下载
private async void btn_Dwon_Click(object sender, EventArgs e)
{
// 这里可以使用fileInfos列表让用户选择要下载的文件,例如通过一个下拉菜单
if (fileInfos == null || fileInfos.Count == 0)
{
MessageBox.Show("No files available for download.");
return;
}
string fileName = fileInfos[0]; // 假设用户选择了列表中的第一个文件
await DownloadFile(fileName);
}
#endregion
#region 上传
/// <summary>
/// 上传文件
/// </summary>
/// <param name="filePath">上传文件路径及名称</param>
/// <returns></returns>
private async Task UploadFile(string filePath)
{
var fileInfo = new FileInfo(filePath);
var fileContent = new StreamContent(System.IO.File.OpenRead(filePath));
var content = new MultipartFormDataContent
{
{ fileContent, "file", fileInfo.Name }
};
var lastUpdateTime = DateTime.Now; // 用于跟踪上次更新时间
var lastReportedProgress = -1; // 用于跟踪上次报告的进度
long lastUploadedBytes = 0; // 上次上传的字节数
var progressContent = new ProgressableStreamContent(content, (uploaded, total) =>
{
var now = DateTime.Now;
var timeSpan = now - lastUpdateTime;
if (timeSpan.TotalSeconds >= 1) // 每秒更新一次速率
{
var rate = (uploaded - lastUploadedBytes) / timeSpan.TotalSeconds / 1024; // 速率 KB/s
lastUpdateTime = now;
lastUploadedBytes = uploaded;
var progressPercentage = (int)((uploaded * 100) / total);
if (Math.Abs(progressPercentage - lastReportedProgress) >= 1)
{
lastReportedProgress = progressPercentage;
Invoke((MethodInvoker)delegate
{
progressBar.Value = progressPercentage;
progressBar.RateText = $"{rate:0.00} KB/s"; // 更新速率信息
lblProgress.Text = $"{progressPercentage}%"; // 更新进度标签
});
}
}
});
var response = await _httpClient.PostAsync($@"{this.WebApi}/Files/upload", progressContent);
if (response.IsSuccessStatusCode)
{
Invoke((MethodInvoker)delegate
{
MessageBox.Show("File uploaded successfully.");
});
}
else
{
Invoke((MethodInvoker)delegate
{
MessageBox.Show($"Upload failed: {response.ReasonPhrase}");
});
}
}
#endregion
#region 下载
/// <summary>
/// 下载文件
/// </summary>
/// <param name="fileName">下载文件名称</param>
/// <param name="savePath">下载文件保存路径</param>
/// <returns></returns>
private async Task DownloadFile(string fileName)
{
var response = await _httpClient.GetAsync($"{this.WebApi}/Files/download/{fileName}", HttpCompletionOption.ResponseHeadersRead);
if (response.IsSuccessStatusCode)
{
var saveFilePath = Path.Combine(SavePath, fileName);
using (var stream = await response.Content.ReadAsStreamAsync())
using (var fileStream = new FileStream(saveFilePath, FileMode.Create, FileAccess.Write, FileShare.None))
{
var totalRead = 0L;
var totalReadBytes = response.Content.Headers.ContentLength ?? 0;
var buffer = new byte[4096];
var isMoreToRead = true;
var lastReportedProgress = -1; // 用于跟踪上次报告的进度
var lastUpdateTime = DateTime.Now; // 用于跟踪上次更新时间
long lastDownloadedBytes = 0; // 上次下载的字节数
do
{
var read = await stream.ReadAsync(buffer, 0, buffer.Length);
if (read == 0)
{
isMoreToRead = false;
}
else
{
await fileStream.WriteAsync(buffer, 0, read);
totalRead += read;
var progress = (int)((totalRead * 100) / totalReadBytes);
var now = DateTime.Now;
var timeSpan = now - lastUpdateTime;
if (timeSpan.TotalSeconds >= 1) // 每秒更新一次速率
{
var rate = (totalRead - lastDownloadedBytes) / timeSpan.TotalSeconds / 1024; // 速率 KB/s
lastUpdateTime = now;
lastDownloadedBytes = totalRead;
// 仅当进度有显著变化时才更新 UI
if (Math.Abs(progress - lastReportedProgress) >= 1) // 这里的 1 表示至少有 1% 的变化
{
lastReportedProgress = progress;
Invoke((MethodInvoker)delegate
{
progressBar.Value = progress;
progressBar.RateText = $"{rate:0.00} KB/s"; // 更新速率信息
lblProgress.Text = $"{progress}%"; // 更新进度标签
});
}
}
}
} while (isMoreToRead);
}
Invoke((MethodInvoker)delegate
{
MessageBox.Show($"File downloaded successfully and saved as {saveFilePath}.");
});
}
else
{
Invoke((MethodInvoker)delegate
{
MessageBox.Show($"Download failed: {response.ReasonPhrase}");
});
}
}
#endregion
// 新增方法:获取服务器上可下载的文件列表
#region 获取所有文件信息
private async void FetchDownloadableFiles()
{
try
{
var response = await _httpClient.GetAsync($"{this.WebApi}/Files/list");
if (response.IsSuccessStatusCode)
{
var jsonString = await response.Content.ReadAsStringAsync();
var serverResponse = Newtonsoft.Json.JsonConvert.DeserializeObject<ServerResponse>(jsonString);
if (serverResponse != null && serverResponse.Data != null && serverResponse.Data.Files != null)
{
this.fileInfos = serverResponse.Data.Files;
// 更新UI,例如使用ComboBox或ListBox展示文件列表
// 注意:此处更新UI的代码应该在UI线程上执行,可能需要使用Invoke或BeginInvoke方法
}
}
else
{
MessageBox.Show("Failed to fetch files list.");
}
}
catch (Exception ex)
{
MessageBox.Show($"Error fetching files list: {ex.Message}");
}
}
#endregion
}
}
MainWinFrm.Designer.cs
namespace FileUploadAndDown
{
partial class MainWinFrm
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
splitContainer1 = new SplitContainer();
progressBar = new CustomProgressBar();
lblProgress = new Label();
btn_Dwon = new Button();
btn_Upload = new Button();
label3 = new Label();
label2 = new Label();
label1 = new Label();
circularProgressBar1 = new CircularProgressBar();
((System.ComponentModel.ISupportInitialize)splitContainer1).BeginInit();
splitContainer1.Panel1.SuspendLayout();
splitContainer1.Panel2.SuspendLayout();
splitContainer1.SuspendLayout();
SuspendLayout();
//
// splitContainer1
//
splitContainer1.Dock = DockStyle.Fill;
splitContainer1.Location = new Point(0, 0);
splitContainer1.Margin = new Padding(4);
splitContainer1.Name = "splitContainer1";
splitContainer1.Orientation = Orientation.Horizontal;
//
// splitContainer1.Panel1
//
splitContainer1.Panel1.Controls.Add(progressBar);
splitContainer1.Panel1.Controls.Add(lblProgress);
//
// splitContainer1.Panel2
//
splitContainer1.Panel2.Controls.Add(circularProgressBar1);
splitContainer1.Panel2.Controls.Add(btn_Dwon);
splitContainer1.Panel2.Controls.Add(btn_Upload);
splitContainer1.Panel2.Controls.Add(label3);
splitContainer1.Panel2.Controls.Add(label2);
splitContainer1.Panel2.Controls.Add(label1);
splitContainer1.Size = new Size(975, 135);
splitContainer1.SplitterDistance = 47;
splitContainer1.SplitterWidth = 6;
splitContainer1.TabIndex = 0;
//
// progressBar
//
progressBar.Dock = DockStyle.Fill;
progressBar.Location = new Point(0, 0);
progressBar.Name = "progressBar";
progressBar.RateText = "0 KB/s";
progressBar.Size = new Size(975, 47);
progressBar.TabIndex = 2;
//
// lblProgress
//
lblProgress.AutoSize = true;
lblProgress.BackColor = Color.Transparent;
lblProgress.Location = new Point(494, 16);
lblProgress.Name = "lblProgress";
lblProgress.Size = new Size(0, 21);
lblProgress.TabIndex = 1;
lblProgress.TextAlign = ContentAlignment.MiddleCenter;
//
// btn_Dwon
//
btn_Dwon.BackColor = Color.PeachPuff;
btn_Dwon.Dock = DockStyle.Bottom;
btn_Dwon.Location = new Point(385, 47);
btn_Dwon.Name = "btn_Dwon";
btn_Dwon.Size = new Size(205, 35);
btn_Dwon.TabIndex = 4;
btn_Dwon.Text = "下载(&D)";
btn_Dwon.UseVisualStyleBackColor = false;
btn_Dwon.Click += btn_Dwon_Click;
//
// btn_Upload
//
btn_Upload.BackColor = Color.PeachPuff;
btn_Upload.Dock = DockStyle.Top;
btn_Upload.FlatAppearance.BorderSize = 0;
btn_Upload.FlatStyle = FlatStyle.Flat;
btn_Upload.Location = new Point(385, 0);
btn_Upload.Name = "btn_Upload";
btn_Upload.Size = new Size(205, 32);
btn_Upload.TabIndex = 3;
btn_Upload.Text = "上传(&U)";
btn_Upload.UseVisualStyleBackColor = false;
btn_Upload.Click += btn_Upload_Click;
//
// label3
//
label3.Dock = DockStyle.Fill;
label3.Location = new Point(385, 0);
label3.Name = "label3";
label3.Size = new Size(205, 82);
label3.TabIndex = 2;
label3.Text = "label3";
//
// label2
//
label2.Dock = DockStyle.Right;
label2.Location = new Point(590, 0);
label2.Name = "label2";
label2.Size = new Size(385, 82);
label2.TabIndex = 1;
//
// label1
//
label1.Dock = DockStyle.Left;
label1.Location = new Point(0, 0);
label1.Name = "label1";
label1.Size = new Size(385, 82);
label1.TabIndex = 0;
//
// circularProgressBar1
//
circularProgressBar1.Location = new Point(114, 15);
circularProgressBar1.Maximum = 100;
circularProgressBar1.Name = "circularProgressBar1";
circularProgressBar1.Progress = 0;
circularProgressBar1.Size = new Size(57, 55);
circularProgressBar1.TabIndex = 5;
//
// MainWinFrm
//
AutoScaleDimensions = new SizeF(10F, 21F);
AutoScaleMode = AutoScaleMode.Font;
ClientSize = new Size(975, 135);
Controls.Add(splitContainer1);
Font = new Font("Microsoft YaHei UI", 12F, FontStyle.Regular, GraphicsUnit.Point);
Margin = new Padding(4);
Name = "MainWinFrm";
StartPosition = FormStartPosition.CenterScreen;
Text = "Form1";
splitContainer1.Panel1.ResumeLayout(false);
splitContainer1.Panel1.PerformLayout();
splitContainer1.Panel2.ResumeLayout(false);
((System.ComponentModel.ISupportInitialize)splitContainer1).EndInit();
splitContainer1.ResumeLayout(false);
ResumeLayout(false);
}
#endregion
private SplitContainer splitContainer1;
private Label label1;
private Button btn_Dwon;
private Button btn_Upload;
private Label label3;
private Label label2;
private Label lblProgress;
private CustomProgressBar progressBar;
private CircularProgressBar circularProgressBar1;
}
}
服务端:
FilesController.cs
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System.IO;
using System.Threading.Tasks;
namespace LargeFileHandling.Controllers
{
[ApiController]
[Route("[controller]")]
public class FilesController : ControllerBase
{
private readonly string _uploadFolderPath;
public FilesController(FileStorageSettings fileStorageSettings)
{
_uploadFolderPath = fileStorageSettings.UploadFolderPath;
// Ensure the upload folder exists
if (!Directory.Exists(_uploadFolderPath))
{
Directory.CreateDirectory(_uploadFolderPath);
}
}
// Upload large file
[HttpPost("upload")]
public async Task<IActionResult> UploadLargeFile(IFormFile file)
{
if (file == null || file.Length == 0)
{
return BadRequest(new { code = 400, message = "Upload failed. No file provided.", data = new { } });
}
var filename = Path.GetFileName(file.FileName);
var filePath = Path.Combine(_uploadFolderPath, filename);
await using (var stream = System.IO.File.Create(filePath))
{
await file.CopyToAsync(stream);
}
return Ok(new { code = 200, message = "File uploaded successfully.", data = new { filePath } });
}
// Check if file exists and return JSON response
[HttpGet("exists/{fileName}")]
public IActionResult CheckFileExists(string fileName)
{
var filePath = Path.Combine(_uploadFolderPath, fileName);
if (System.IO.File.Exists(filePath))
{
return Ok(new { code = 200, message = "File exists.", data = new { filePath } });
}
else
{
return NotFound(new { code = 404, message = "File not found.", data = new { } });
}
}
// Actual file download operation
[HttpGet("download/{fileName}")]
public IActionResult DownloadLargeFile(string fileName)
{
var filePath = Path.Combine(_uploadFolderPath, fileName);
if (!System.IO.File.Exists(filePath))
{
return NotFound(new { code = 404, message = "File not found.", data = new { } });
}
var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read);
return new FileStreamResult(stream, "application/octet-stream")
{
FileDownloadName = fileName
};
}
// Modified method to list downloadable files including files in subdirectories
[HttpGet("list")]
public IActionResult ListDownloadableFiles()
{
if (!Directory.Exists(_uploadFolderPath))
{
return NotFound(new { code = 404, message = "Upload folder not found.", data = new { } });
}
var files = Directory.GetFiles(_uploadFolderPath, "*.*", SearchOption.AllDirectories)
.Select(file => file.Replace(_uploadFolderPath, "").TrimStart(Path.DirectorySeparatorChar))
.ToList();
return Ok(new { code = 200, message = "File list retrieved successfully.", data = new { files } });
}
}
}
appsettings.json
{
"FileStorageSettings": {
"UploadFolderPath": "F:\\Down"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
FileStorageSettings.cs
namespace LargeFileHandling
{
public class FileStorageSettings
{
public string UploadFolderPath { get; set; }
}
}
Program.cs
using LargeFileHandling;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.Extensions.Options;
var builder = WebApplication.CreateBuilder(args);
// 配置 Kestrel 服务器以允许大文件上传
builder.WebHost.ConfigureKestrel(serverOptions =>
{
serverOptions.Limits.MaxRequestBodySize = 10L * 1024 * 1024 * 1024; // 10GB
});
// 配置 FormOptions
builder.Services.Configure<FormOptions>(options =>
{
options.MultipartBodyLengthLimit = 10L * 1024 * 1024 * 1024; // 10GB
});
// 绑定文件存储配置并注册为单例服务
builder.Services.Configure<FileStorageSettings>(builder.Configuration.GetSection("FileStorageSettings"));
builder.Services.AddSingleton(resolver =>
resolver.GetRequiredService<IOptions<FileStorageSettings>>().Value);
// 其他服务和配置...
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
// 应用的其余配置...
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
WeatherForecast.cs
namespace LargeFileHandling
{
public class WeatherForecast
{
public DateTime Date { get; set; }
public int TemperatureC { get; set; }
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
public string? Summary { get; set; }
}
}