Asp.Net Core上传字节数组多种方式研究
Asp.Net Core上传字节数组的应用场景很常见,可用的技术方案也很多,小的文件也可以读取到内存再按字节数组上传,这么多方式究竟有什么区别?特意写一下代码,通过Fiddler抓包,看一下不同的技术方案上传的数据究竟有啥区别。
主要研究这3种方案:
1 表单文件
2 Json字节数组
3 字节流
创建服务端Asp.Net Core项目
服务端写了3个方法,分别对应3种传输方案,另外增加一个传输Json整型数组的方法,用于对比。
/// <summary> /// 上传表单文件 /// </summary> /// <param name="form"></param> /// <returns></returns> [ProducesResponseType(StatusCodes.Status200OK)] [HttpPost("PostFormFile")] public async Task<int> PostFormFile(IFormCollection form) { byte[] ary = new byte[0]; var formFile = form.Files?[0]; if (formFile?.Length > 0) { using var ms = new MemoryStream(); await formFile.CopyToAsync(ms); ary = ms.ToArray(); } string msg = $"上传表单文件, 长度 ={ary.Length}{Environment.NewLine}"; //msg += string.Join(", ", ary.Take(10)); msg += Encoding.UTF8.GetString(ary); _logger.LogDebug(msg); return ary.Length; } /// <summary> /// 上传字节Json数组 /// </summary> /// <param name="ary"></param> /// <returns></returns> [ProducesResponseType(StatusCodes.Status200OK)] [HttpPost("PostByteJson")] public async Task<int> PostByteJson(byte[] ary) { string msg = $"上传字节Json数组, 长度 ={ary.Length}{Environment.NewLine}"; //msg += string.Join(", ", ary.Take(10)); msg += Encoding.UTF8.GetString(ary); _logger.LogDebug(msg); await Task.CompletedTask; return ary.Length; } /// <summary> /// 上传整型Json数组 /// </summary> /// <param name="ary"></param> /// <returns></returns> [ProducesResponseType(StatusCodes.Status200OK)] [HttpPost("PostIntJson")] public async Task<int> PostIntJson(int[] ary) { string msg = $"上传整型Json数组, 长度 ={ary.Length}{Environment.NewLine}"; msg += string.Join(", ", ary.Take(10)); _logger.LogDebug(msg); await Task.CompletedTask; return ary.Length; } /// <summary> /// 上传字节数组 /// </summary> /// <returns></returns> [ProducesResponseType(StatusCodes.Status200OK)] [HttpPost("PostByteAry")] public async Task<int> PostByteAry() { using var ms = new MemoryStream(); await Request.Body.CopyToAsync(ms); var ary = ms.ToArray(); string msg = $"上传字节数组, 长度 ={ary.Length}{Environment.NewLine}"; //msg += string.Join(", ", ary.Take(10)); msg += Encoding.UTF8.GetString(ary); _logger.LogDebug(msg); await Task.CompletedTask; return ary.Length; }
创建客户端项目
客户端也对应写了4种通过HttpClient上传数据的方法。
/// <summary> /// HttpClient上传数据 /// </summary> public class UploadClient { private readonly HttpClient _client; private readonly ILogger<UploadClient> _logger; public UploadClient(HttpClient client, ILogger<UploadClient> logger) { _client = client; _logger = logger; } /// <summary> /// HttpClient上传表单文件 /// </summary> /// <param name="ary"></param> /// <returns></returns> public async Task<bool> PostFormFileAsync(byte[] ary) { _logger.LogInformation("HttpClient上传表单文件..."); var multipartFormDataContent = new MultipartFormDataContent(); multipartFormDataContent.Add(new ByteArrayContent(ary), "file", "test.txt"); var response = await _client.PostAsync("/api/Test/PostFormFile", multipartFormDataContent); return response.IsSuccessStatusCode; } /// <summary> /// HttpClient上传字节Json数组 /// </summary> /// <param name="ary"></param> /// <returns></returns> public async Task<bool> PostByteJsonAsync(byte[] ary) { _logger.LogInformation("HttpClient上传字节Json数组..."); string json = JsonConvert.SerializeObject(ary); var stringContent = new StringContent(json, Encoding.UTF8, "application/json"); var response = await _client.PostAsync("/api/Test/PostByteJson", stringContent); return response.IsSuccessStatusCode; } /// <summary> /// HttpClient上传整型Json数组 /// </summary> /// <param name="intAry"></param> /// <returns></returns> public async Task<bool> PostIntJsonAsync(int[] intAry) { _logger.LogInformation("HttpClient上传整型Json数组..."); string json = JsonConvert.SerializeObject(intAry); var stringContent = new StringContent(json, Encoding.UTF8, "application/json"); var response = await _client.PostAsync("/api/Test/PostIntJson", stringContent); return response.IsSuccessStatusCode; } /// <summary> /// HttpClient上传字节数组 /// </summary> /// <param name="ary"></param> /// <returns></returns> public async Task<bool> PostByteAryAsync(byte[] ary) { _logger.LogInformation("HttpClient上传字节数组..."); //两种参数类型发送抓包一样 //var ms = new MemoryStream(ary); //var byteArrayContent = new StreamContent(ms); var byteArrayContent = new ByteArrayContent(ary); var response = await _client.PostAsync("/api/Test/PostByteAry", byteArrayContent); return response.IsSuccessStatusCode; } }
运行测试
通过Fiddler抓包对比。
表单文件方式上传数据比较复杂,适合上传大文件,或者表单内容有键值对又有文件。
POST http://localhost:5000/api/Test/PostFormFile HTTP/1.1
Host: localhost:5000
Content-Type: multipart/form-data; boundary="2bc9b8ca-4ddf-4d5a-b4d3-4337cd6b07d3"
Content-Length: 223
--2bc9b8ca-4ddf-4d5a-b4d3-4337cd6b07d3
Content-Disposition: form-data; name=file; filename=test.txt; filename*=utf-8''test.txt
客户端现在时间=2021/10/23 16:48:19 +08:00
--2bc9b8ca-4ddf-4d5a-b4d3-4337cd6b07d3--
Json字节数组方式上传数据比较简单,字节数组json被自动转换为base64编码,减少传输数据量。
POST http://localhost:5000/api/Test/PostByteJson HTTP/1.1
Host: localhost:5000
Content-Type: application/json; charset=utf-8
Content-Length: 66
"5a6i5oi356uv546w5Zyo5pe26Ze0PTIwMjEvMTAvMjMgMTY6NDg6MTkgKzA4OjAw"
json整型数组方式上传数据,可以看到参数仍然保留了json数组的格式,它是无法被转换为base64编码字符串的。
POST http://localhost:5000/api/Test/PostIntJson HTTP/1.1
Host: localhost:5000
Content-Type: application/json; charset=utf-8
Content-Length: 41
[900,901,902,903,904,905,906,907,908,909]
字节流方式上传数据最精简。
POST http://localhost:5000/api/Test/PostByteAry HTTP/1.1
Host: localhost:5000
Content-Length: 48
客户端现在时间=2021/10/23 16:48:19 +08:00
增加Refit客户端上传对比测试
Refit采用声明式定义Web Api的接口,它的参数类型跟HttpClient不太一样。
首先声明4个函数接口。
public interface ITestPostApi { /// <summary> /// 上传表单文件 /// </summary> /// <param name="ary"></param> /// <returns></returns> [Multipart] [Post("/api/Test/PostFormFile")] Task<int> PostFormFile(ByteArrayPart ary); /// <summary> /// 上传字节Json数组 /// </summary> /// <param name="ary"></param> /// <returns></returns> [Post("/api/Test/PostByteJson")] Task<int> PostByteJson(byte[] ary); /// <summary> /// 上传整型Json数组 /// </summary> /// <param name="ary"></param> /// <returns></returns> [Post("/api/Test/PostIntJson")] Task<int> PostIntJson(int[] ary); /// <summary> /// 上传字节数组 /// </summary> /// <param name="ary"></param> /// <returns></returns> [Post("/api/Test/PostByteAry")] //两种参数类型发送抓包一样 //Task<int> PostByteAry(StreamContent ary);//传参StreamContent类型 Task<int> PostByteAry(ByteArrayContent ary);//传参ByteArrayContent类型 }
然后在startup注入Refit接口
//注册ITestPostApi services.AddRefitClient<ITestPostApi>() .ConfigureHttpClient(c => c.BaseAddress = new Uri("http://localhost:5000"));
实现4种方式上传数据。
//测试Refit上传数据 private async Task TestRefitUploadAsync(CancellationToken stoppingToken) { //上传表单文件 _logger.LogInformation("Refit上传表单文件..."); var byteArrayPart = new ByteArrayPart(value: ary, fileName: "testfile", name: "file"); int result = await _testPostApi.PostFormFile(byteArrayPart); await Task.Delay(100, stoppingToken); //上传字节Json数组 _logger.LogInformation("Refit上传字节Json数组..."); result = await _testPostApi.PostByteJson(ary); await Task.Delay(100, stoppingToken); //上传整型Json数组 _logger.LogInformation("Refit上传整型Json数组..."); result = await _testPostApi.PostIntJson(intAry); await Task.Delay(100, stoppingToken); //上传字节数组 _logger.LogInformation("Refit上传字节数组..."); //result = await _testPostApi.PostByteAry(byteArrayPart); //Task<int> PostByteAry(StreamContent ary) //var ms = new MemoryStream(ary); //var streamContent = new StreamContent(ms); //result = await _testPostApi.PostByteAry(streamContent); //Task<int> PostByteAry(ByteArrayContent ary) var byteArrayContent = new ByteArrayContent(ary); result = await _testPostApi.PostByteAry(byteArrayContent); //两种参数类型发送抓包一样 /* POST http://localhost:5000/api/Test/PostByteAry HTTP/1.1 Host: localhost:5000 Content-Length: 48 客户端现在时间=2021/10/23 15:56:55 +08:00 */ //} }
运行测试,用Fiddler抓包,发现Refit上传数据跟HttpClient几乎完全一样。
小结
如果是简单的上传小型字节数据,可以用HttpClient的ByteArrayContent参数,或者Refit的ByteArrayContent参数。
DEMO代码地址:woodsun/TestPostBytes - 码云 - 开源中国 (gitee.com)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现