C# WinFrom+AspNetCore WebApi实现大文件下载与上传

漂泊_人生 2024-08-01 15:33:02 阅读 78

客户端UI:

服务端WebApi:

客户端代码:

App.config:

<code><?xml version="1.0" encoding="utf-8" ?>code>

<configuration>

<appSettings>

<add key="WebApi" value="https://localhost:7285"/>code>

<add key="SavePath" value="E:\Down"/>code>

<add key="DownFileName" value="win11x64.esd"/>code>

<add key="UploadFileName" value="win11x64.esd"/>code>

</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>code>

/// <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>code>

/// <param name="savePath">下载文件保存路径</param>code>

/// <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>code>

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; }

}

}



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。