客户端调用 Http 接口实现大文件上传和下载
对于网站开发来说,下载文件一般是比较非常容易的,但是对于上传文件来说,上传大文件是比较困难的,比如上传几百M或几个G的文件。但是对于客户端来说,实现大文件的上传是比较容易的。由于本人在工作中遇到大文件上传的情景比较多,所以就决定写一个 Demo 总结一下客户端实现大文件上传和下载的技术代码,以便后续需要使用时,能够快速找到并提高工作效率。
本篇博客的 Demo 采用基于 .NET5 开发的 Asp.net Core 网站作为服务端提供 Http 接口。客户端采用 WinForm 开发,通过调用 Http 接口实现文件的上传和下载。之所以选择 Asp.net Core 网站作为接口服务端的开发,是因为它的部署比较灵活,可以采用 Windows Service 服务进行部署,开发的时候可以采用控制台进行测试。有关将控制台程序直接安装成 Windows Service 的方法,可以参考我之前编写的博客。在本博客的最后会提供源代码的下载。
一、搭建接口服务
新建一个基于 .NET5 的 Asp.net Core 网站,appsettings.json 配置文件内容如下:
{
//web访问端口
"WebPort": "http://*:9527",
//文件上传的目录名称,是网站下的一个目录,该目录必须要存在
"UploadPath": "UploadFiles"
}
建议网站采用控制台进行启动,这样开发调试比较方便。然后新建一个 CommonBLL 类,用来读取配置文件:
using Microsoft.Extensions.Configuration;
using System;
using System.IO;
namespace ServerDemo.Models
{
public static class CommonBLL
{
/// <summary>
/// 获取配置信息
/// </summary>
public static IConfiguration Config;
/// <summary>
/// 文件上传的根路径(在本 Demo 中,要求上传的文件必须在网站的目录下)
/// </summary>
public static string uploadRootPath;
//静态构造函数
static CommonBLL()
{
//Environment.CurrentDirectory 代表网站程序的根目录
Config = (new ConfigurationBuilder())
.SetBasePath(Environment.CurrentDirectory)
.AddJsonFile("appsettings.json", false, true).Build();
//为了确保文件上传的目录存在,所以在启动时就自动创建
uploadRootPath = Path.Combine(Environment.CurrentDirectory, Config["UploadPath"]);
if (Directory.Exists(uploadRootPath) == false)
{
Directory.CreateDirectory(uploadRootPath);
}
}
}
}
本博客的 Demo 要求上传的文件,都保存在网站下面的一个固定的文件夹中,并在网站启动时会自动判断该文件夹是否存在,如果不存在的话,就提前创建出来,方便后续实用该文件夹保存所有上传的文件。
然后在网站启动时,使用配置文件中所配置的端口(如果不配置的话,网站默认使用 5000 端口)。
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
using ServerDemo.Models;
namespace ServerDemo
{
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
//采用 appsettings.json 配置文件中自定义的端口启动 http 服务
webBuilder.UseUrls(CommonBLL.Config["WebPort"]).UseStartup<Startup>();
});
}
}
由于本网站只是提供 Http 接口,因此在 ConfigureServices 中只需要使用 services.AddControllers() 即可。
另外上传和下载文件,Asp.net Core 默认情况下是异步的,我们最好让它也支持同步的方式,具体细节如下:
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.Extensions.DependencyInjection;
namespace ServerDemo
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
//这里只提供接口就可以了,不需要页面
services.AddControllers();
//允许同步 IO 调用
services.Configure<KestrelServerOptions>(options =>
{
//如果部署在非 iis 上的话,使用的是 kestrel 提供 http 服务
//比如以控制台启动,或者部署成 WindowsService,或者在 linux 上部署
options.AllowSynchronousIO = true;
});
services.Configure<IISServerOptions>(options =>
{
//如果部署在 iis 上,使用的是 iis 引擎
options.AllowSynchronousIO = true;
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseDeveloperExceptionPage();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=API}/{action=Index}");
});
}
}
}
最后开发一个 APIController 提供 Http 接口即可,内容如下:
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.FileProviders;
using Newtonsoft.Json;
using ServerDemo.Models;
using System;
using System.Data;
using System.IO;
using System.Text;
namespace ServerDemo.Controllers
{
public class APIController : Controller
{
public IActionResult Index()
{
return Content(
$"Asp.Net Core Web 正在运行... {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}");
}
/// <summary>
/// 将文件大小转换成可读性好的格式
/// </summary>
private string GetSize(long size)
{
long k = 1024;
long m = k * 1024;
long g = m * 1024;
long t = g * 1024;
if (size < k)
{
return size.ToString() + "b";
}
else if (size < m)
{
return ((size * 1.00) / k).ToString("F2").Replace(".00", "") + "KB";
}
else if (size < g)
{
return ((size * 1.00) / m).ToString("F2").Replace(".00", "") + "MB";
}
else if (size < t)
{
return ((size * 1.00) / g).ToString("F2").Replace(".00", "") + "GB";
}
else
{
return ((size * 1.00) / t).ToString("F2").Replace(".00", "") + "TB";
}
}
/// <summary>
/// 获取一个路径下所有的文件
/// </summary>
public string GetAllFile()
{
DataTable dt = new DataTable("server");
dt.Columns.Add("FileName");
dt.Columns.Add("UpdateTime");
dt.Columns.Add("DataSize");
string[] fileArr = Directory.GetFiles(CommonBLL.uploadRootPath);
if (fileArr.Length > 0)
{
FileInfo fi;
foreach (string file in fileArr)
{
fi = new FileInfo(file);
dt.Rows.Add(Path.GetFileName(file),
fi.LastWriteTime.ToString("yyyy-MM-dd HH:mm:ss"), GetSize(fi.Length));
}
}
return JsonConvert.SerializeObject(dt);
}
/// <summary>
/// 获取文件的字节总数
/// </summary>
public string GetFileSize()
{
string data = string.Empty;
using (StreamReader sr = new StreamReader(Request.Body, Encoding.UTF8))
{
data = sr.ReadToEnd();
}
dynamic obj = JsonConvert.DeserializeObject(data);
string filename = obj.f;
string path = Path.Combine(CommonBLL.uploadRootPath, filename);
if (System.IO.File.Exists(path))
{
FileInfo fi = new FileInfo(path);
return fi.Length.ToString();
}
else
{
return "0";
}
}
/// <summary>
/// 分批获取文件的字节码
/// </summary>
public string GetFileByte()
{
string data = string.Empty;
using (StreamReader sr = new StreamReader(Request.Body, Encoding.UTF8))
{
data = sr.ReadToEnd();
}
dynamic obj = JsonConvert.DeserializeObject(data);
string filename = obj.f;
int sizeB = obj.sb;
long sequence = obj.sq;
int end = obj.ef;
try
{
string path = Path.Combine(CommonBLL.uploadRootPath, filename);
//以文件的全路径对应的字符串和文件打开模式来初始化FileStream文件流实例
using (FileStream SplitFileStream = new FileStream(path, FileMode.Open))
{
//每次分割读取的最大数据
SplitFileStream.Seek(sizeB * sequence, SeekOrigin.Current);
if (end == 1)
{
FileInfo fi = new FileInfo(path);
long totalsize = fi.Length;
int lastsize = (int)(totalsize - sizeB * sequence);
byte[] TempBytes = new byte[lastsize];
SplitFileStream.Read(TempBytes, 0, lastsize);
return Convert.ToBase64String(TempBytes);
}
else
{
byte[] TempBytes = new byte[sizeB];
//从大文件中读取指定大小数据
SplitFileStream.Read(TempBytes, 0, (int)sizeB);
return Convert.ToBase64String(TempBytes);
}
}
}
catch
{
throw;
}
}
/// <summary>
/// 获取文件的所有字节码
/// </summary>
public string GetFileByteAll()
{
string data = string.Empty;
using (StreamReader sr = new StreamReader(Request.Body, Encoding.UTF8))
{
data = sr.ReadToEnd();
}
dynamic obj = JsonConvert.DeserializeObject(data);
string filename = obj.f;
string path = Path.Combine(CommonBLL.uploadRootPath, filename);
using (FileStream SplitFileStream = new FileStream(path, FileMode.Open))
{
SplitFileStream.Seek(0, SeekOrigin.Current);
FileInfo fi = new FileInfo(path);
int totalsize = (int)fi.Length;
byte[] TempBytes = new byte[totalsize];
SplitFileStream.Read(TempBytes, 0, totalsize);
return Convert.ToBase64String(TempBytes);
}
}
/// <summary>
/// 分批上传文件
/// </summary>
public void UpLoadFileByte()
{
string data = string.Empty;
using (StreamReader sr = new StreamReader(Request.Body, Encoding.UTF8))
{
data = sr.ReadToEnd();
}
dynamic obj = JsonConvert.DeserializeObject(data);
string filename = obj.f;
byte[] fileBlock = obj.fb;
int start = obj.flag;
string path = Path.Combine(CommonBLL.uploadRootPath, filename);
if (start == 1)
{
using (FileStream TempStream = new FileStream(path, FileMode.Create))
{
TempStream.Write(fileBlock, 0, fileBlock.Length);
}
}
else
{
using (FileStream TempStream = new FileStream(path, FileMode.Append))
{
TempStream.Write(fileBlock, 0, fileBlock.Length);
}
}
}
/// <summary>
/// 上传文件
/// </summary>
public void UpLoadFileByteAll()
{
string data = string.Empty;
using (StreamReader sr = new StreamReader(Request.Body, Encoding.UTF8))
{
data = sr.ReadToEnd();
}
dynamic obj = JsonConvert.DeserializeObject(data);
string filename = obj.f;
byte[] fileBlock = obj.fb;
string path = Path.Combine(CommonBLL.uploadRootPath, filename);
string dir = Path.GetDirectoryName(path);
if (!Directory.Exists(dir))
{
Directory.CreateDirectory(dir);
}
using (FileStream TempStream = new FileStream(path, FileMode.Create))
{
TempStream.Write(fileBlock, 0, fileBlock.Length);
}
}
/// <summary>
/// 删除文件
/// </summary>
public void DeleteFile()
{
string data = string.Empty;
using (StreamReader sr = new StreamReader(Request.Body, Encoding.UTF8))
{
data = sr.ReadToEnd();
}
dynamic obj = JsonConvert.DeserializeObject(data);
string filename = obj.f;
string path = Path.Combine(CommonBLL.uploadRootPath, filename);
if (System.IO.File.Exists(path))
{
System.IO.File.Delete(path);
}
}
/// <summary>
/// 敲链接下载单个文件
/// </summary>
public IActionResult DownLoadFile(string fileName)
{
var provider = new PhysicalFileProvider(CommonBLL.uploadRootPath);
var fileInfo = provider.GetFileInfo(fileName);
var readStream = fileInfo.CreateReadStream();
return File(readStream, "application/octet-stream", fileName);
}
}
}
到此为止,Asp.net Core 服务端网站已经搭建完毕。需要注意的是:我们上面所提供的接口,都是读取提交过来的 body 中的 Json 数据。这就要求客户端在调用接口的时候,要使用 Post 请求并提交 Json 数据。因此无论是客户端和服务端,都需要通过 Nuget 安装 Newtonsoft.json 包。
二、搭建客户端
客户端采用 WinForm 进行开发,实现对服务器上的文件进行上传、下载、删除和获取文件列表功能。
对于客户端来说,只需要配置要请求的接口地址即可:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<!--路径不要以斜线结尾-->
<add key="ServerUrl" value="http://localhost:9527/api"/>
</appSettings>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
</configuration>
然后我们先提前创建一个 CommonBLL 类,采用 WebClient 发送 Post 请求实现每一个接口方法的调用封装,具体细节如下:
using Newtonsoft.Json;
using System;
using System.Configuration;
using System.Data;
using System.Net;
using System.Text;
using System.Windows.Forms;
namespace ClientDemo
{
public static class CommonBLL
{
//封装了一些信息弹框,方便在 WinForm 的界面中使用
public static void MessageAlert(string str)
{
MessageBox.Show(str, "提示信息", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
}
public static bool MessageConfirm(string str)
{
return MessageBox.Show(str, "提示信息",
MessageBoxButtons.OKCancel, MessageBoxIcon.Question) == DialogResult.OK;
}
public static void MessageSuccess(string str)
{
MessageBox.Show(str, "提示信息", MessageBoxButtons.OK, MessageBoxIcon.Asterisk);
}
public static void MessageError(string str)
{
MessageBox.Show(str, "提示信息", MessageBoxButtons.OK, MessageBoxIcon.Hand);
}
public static WebClient CreateWebClient()
{
WebClient wc = new WebClient();
wc.Encoding = Encoding.UTF8;
wc.Headers.Add("Content-Type", "application/json");
return wc;
}
//读取配置节中的接口地址
private static string serverurl = ConfigurationManager.AppSettings["ServerUrl"];
//下面这些方法是对网站提供的每个接口的调用封装
public static DataTable GetAllFile()
{
using (WebClient wc = CreateWebClient())
{
string result = wc.UploadString(serverurl + "/GetAllFile", string.Empty);
if (!string.IsNullOrWhiteSpace(result))
{
DataTable dt = JsonConvert.DeserializeObject<DataTable>(result);
return dt;
}
else
{
return null;
}
}
}
public static long GetFileSize(string filename)
{
using (WebClient wc = CreateWebClient())
{
var model = new { f = filename };
string json = JsonConvert.SerializeObject(model);
string result = wc.UploadString(serverurl + "/GetFileSize", json);
return long.Parse(result);
}
}
public static byte[] GetFileByte(string filename, int sizeB, int sequence, bool end)
{
int endflag = end ? 1 : 0;
using (WebClient wc = CreateWebClient())
{
var model = new { f = filename, sb = sizeB, sq = sequence, ef = endflag };
string json = JsonConvert.SerializeObject(model);
string result = wc.UploadString(serverurl + "/GetFileByte", json);
return Convert.FromBase64String(result);
}
}
public static byte[] GetFileByte(string filename)
{
using (WebClient wc = CreateWebClient())
{
var model = new { f = filename };
string json = JsonConvert.SerializeObject(model);
string result = wc.UploadString(serverurl + "/GetFileByteAll", json);
return Convert.FromBase64String(result);
}
}
public static void UpLoadFileByte(string filename, byte[] fileBlock, bool start)
{
int startflag = start ? 1 : 0;
using (WebClient wc = CreateWebClient())
{
var model = new { f = filename, fb = fileBlock, flag = startflag };
string json = JsonConvert.SerializeObject(model);
wc.UploadString(serverurl + "/UpLoadFileByte", json);
}
}
public static void UpLoadFileByte(string filename, byte[] fileBlock)
{
using (WebClient wc = CreateWebClient())
{
var model = new { f = filename, fb = fileBlock };
string json = JsonConvert.SerializeObject(model);
wc.UploadString(serverurl + "/UpLoadFileByteAll", json);
}
}
public static void DeleteFile(string filename)
{
using (WebClient wc = CreateWebClient())
{
var model = new { f = filename };
string json = JsonConvert.SerializeObject(model);
wc.UploadString(serverurl + "/DeleteFile", json);
}
}
}
}
下面只列出上传文件和下载文件的代码,具体的逻辑就是:当上传或者下载的文件小于等于 2M 的话,就直接上传或下载,如果大于 2M 的话,就分批次进行上传或下载,每批次上传或下载的数据量为 2M ,具体细节如下:
//自己开发的用于显示进度条的窗体
private ProgressForm pf;
/// <summary>
/// 上传文件
/// </summary>
private void btnUpload_Click(object sender, EventArgs e)
{
OpenFileDialog ofd = new OpenFileDialog();
ofd.Title = "上传单个文件";
if (ofd.ShowDialog() == DialogResult.OK)
{
//每次需要上传的字节数 2M
long num = 2097152L;
//获取所选择的文件全路径名
string fullname = ofd.FileName;
//获取文件名(仅仅是文件名,包含扩展名)
string fname = Path.GetFileName(fullname);
FileInfo fileInfo = new FileInfo(fullname);
//获取要上传的文件的总字节数大小
long flen = fileInfo.Length;
if (flen <= num)
{
//如果字节数小于等于 2M ,则直接上传文件
byte[] array = new byte[flen];
using (FileStream fileStream = new FileStream(fullname, FileMode.Open))
{
fileStream.Read(array, 0, (int)flen);
}
CommonBLL.UpLoadFileByte(fname, array);
CommonBLL.MessageSuccess("文件上传成功");
BindData(fname);
}
else
{
//如果字节数大于 2M ,则分批次上传,每次上传 2M
int blocknum = (int)(flen / num);
if (flen % num != 0L)
{
blocknum++;
}
pf = new ProgressForm();
pf.TopMost = true;
pf.ProgressMaxValue = blocknum;
pf.ProgressValue = 0;
pf.Show();
bwUpSingBigFile = new BackgroundWorker();
bwUpSingBigFile.WorkerReportsProgress = true;
bwUpSingBigFile.DoWork +=
new DoWorkEventHandler(this.bwUpSingBigFile_DoWork);
bwUpSingBigFile.ProgressChanged +=
new ProgressChangedEventHandler(this.bwUpSingBigFile_ProgressChanged);
bwUpSingBigFile.RunWorkerCompleted +=
new RunWorkerCompletedEventHandler(this.bwUpSingBigFile_RunWorkerCompleted);
Dictionary<string, object> dicparam = new Dictionary<string, object>();
dicparam.Add("LocalFileName", fullname); //该参数是本地文件名,这里参数是全路径名
dicparam.Add("ServerFileName", fname); //在服务器上保存的文件名称,该参数仅仅是文件名
dicparam.Add("TotalFlieSize", flen);
dicparam.Add("eachBlockSize", num);
dicparam.Add("FileBlockCount", blocknum);
this.bwUpSingBigFile.RunWorkerAsync(dicparam);
}
}
}
#region 上传文件后台线程
private BackgroundWorker bwUpSingBigFile;
private void bwUpSingBigFile_DoWork(object sender, DoWorkEventArgs e)
{
Dictionary<string, object> dicparam = e.Argument as Dictionary<string, object>;
//该参数是本地文件名,这里参数是全路径名
string clientfile = dicparam["LocalFileName"].ToString();
//在服务器上保存的文件名称,该参数仅仅是文件名
string serverfile = dicparam["ServerFileName"].ToString();
long totalsize = long.Parse(dicparam["TotalFlieSize"].ToString());
int eachsize = int.Parse(dicparam["eachBlockSize"].ToString());
int blocknum = int.Parse(dicparam["FileBlockCount"].ToString());
using (FileStream fileStream = new FileStream(clientfile, FileMode.Open))
{
for (int i = 0; i < blocknum; i++)
{
if (i == 0)
{
byte[] array = new byte[eachsize];
fileStream.Read(array, 0, eachsize);
CommonBLL.UpLoadFileByte(serverfile, array, true);
}
else
{
if (i == blocknum - 1)
{
int leftnum = (int)(totalsize - eachsize * i);
byte[] array = new byte[leftnum];
fileStream.Read(array, 0, leftnum);
CommonBLL.UpLoadFileByte(serverfile, array, false);
}
else
{
byte[] array = new byte[eachsize];
fileStream.Read(array, 0, eachsize);
CommonBLL.UpLoadFileByte(serverfile, array, false);
}
}
bwUpSingBigFile.ReportProgress(i + 1);
}
}
e.Result = serverfile; //要上传的文件名,不是全路径名
}
private void bwUpSingBigFile_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
int progressPercentage = e.ProgressPercentage;
pf.ProgressValue = progressPercentage;
}
private void bwUpSingBigFile_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
pf.Close();
if (e.Error != null)
{
CommonBLL.MessageError(e.Error.Message);
}
else
{
CommonBLL.MessageSuccess("上传成功");
BindData(e.Result.ToString());
}
}
#endregion
/// <summary>
/// 下载文件
/// </summary>
private void tsmDownload_Click(object sender, EventArgs e)
{
//获取要下载的文件名
string serverFileName = dgvFile.SelectedRows[0].Cells["FileName"].Value.ToString();
SaveFileDialog sfd = new SaveFileDialog();
sfd.FileName = serverFileName;
if (sfd.ShowDialog() == DialogResult.OK)
{
//要保存的文件名(全路径名)
string localFileName = sfd.FileName;
//每次下载的字节数 2M
long num = 2097152L;
//获取要下载的文件的总字节数大小
long fileSize = CommonBLL.GetFileSize(serverFileName);
if (fileSize <= num)
{
//所下载的文件小于等于 2M ,则直接下载
byte[] fileByte = CommonBLL.GetFileByte(serverFileName);
using (FileStream fileStream = new FileStream(localFileName, FileMode.Create))
{
fileStream.Write(fileByte, 0, fileByte.Length);
}
CommonBLL.MessageSuccess("文件下载成功");
}
else
{
//所下载的文件大于 2M ,则分批次下载,每次下载 2M
int maxnum = (int)(fileSize / num);
if (fileSize % num != 0L)
{
maxnum++;
}
pf = new ProgressForm();
pf.TopMost = true;
pf.ProgressMaxValue = maxnum;
pf.ProgressValue = 0;
pf.Show();
bwDownSingBigFile = new BackgroundWorker();
bwDownSingBigFile.WorkerReportsProgress = true;
bwDownSingBigFile.DoWork +=
new DoWorkEventHandler(bwDownSingBigFile_DoWork);
bwDownSingBigFile.ProgressChanged +=
new ProgressChangedEventHandler(bwDownSingBigFile_ProgressChanged);
bwDownSingBigFile.RunWorkerCompleted +=
new RunWorkerCompletedEventHandler(bwDownSingBigFile_RunWorkerCompleted);
Dictionary<string, object> dicparam = new Dictionary<string, object>();
dicparam.Add("ServerFileName", serverFileName); //服务器上的文件名,仅仅是文件名
dicparam.Add("SaveFileName", localFileName); //要保存到本地的文件名,全路径名
dicparam.Add("FileBlockCount", maxnum);
dicparam.Add("eachBlockSize", num);
bwDownSingBigFile.RunWorkerAsync(dicparam);
}
}
}
#region 下载单个大文件
private BackgroundWorker bwDownSingBigFile;
private void bwDownSingBigFile_DoWork(object sender, DoWorkEventArgs e)
{
Dictionary<string, object> dicparam = e.Argument as Dictionary<string, object>;
//要保存到本地的文件名,全路径名
string localFileName = dicparam["SaveFileName"].ToString();
//服务器上的文件名,仅仅是文件名
string serverFileName = dicparam["ServerFileName"].ToString();
int num = int.Parse(dicparam["FileBlockCount"].ToString());
int sizeB = int.Parse(dicparam["eachBlockSize"].ToString());
using (FileStream fileStream = new FileStream(localFileName, FileMode.Create))
{
for (int i = 0; i < num; i++)
{
byte[] fileByte;
if (i == num - 1)
{
fileByte = CommonBLL.GetFileByte(serverFileName, sizeB, i, true);
}
else
{
fileByte = CommonBLL.GetFileByte(serverFileName, sizeB, i, false);
}
fileStream.Write(fileByte, 0, fileByte.Length);
bwDownSingBigFile.ReportProgress(i + 1);
}
}
}
private void bwDownSingBigFile_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
int progressPercentage = e.ProgressPercentage;
pf.ProgressValue = progressPercentage;
}
private void bwDownSingBigFile_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
pf.Close();
if (e.Error != null)
{
CommonBLL.MessageError(e.Error.Message);
}
else
{
CommonBLL.MessageSuccess("下载成功");
}
}
#endregion
大于大文件的上传和下载,采用的是 BackgroundWorker 异步线程处理的,因为它自带了一些事件比较好用。
DoWork 事件用于异步处理业务逻辑,在本 Demo 上实现异步后台处理文件的上传和下载。
ProgressChanged 事件用于更新进度条,对于大文件的上传和下载,有进度条的话,体验会好很多。
RunWorkerCompleted 事件用于异步后台业务处理完成后的回调,使我们能够在文件上传和下载完成后给予用户一些提示信息。
到此为止,关键核心点已经介绍完毕,详细的细节请参看源代码吧。
源代码的下载地址为:https://files.cnblogs.com/files/blogs/699532/UpDownFileDemo.zip