Asp.Net Core上传字节数组多种方式研究

Asp.Net Core上传字节数组的应用场景很常见,可用的技术方案也很多,小的文件也可以读取到内存再按字节数组上传,这么多方式究竟有什么区别?特意写一下代码,通过Fiddler抓包,看一下不同的技术方案上传的数据究竟有啥区别。

主要研究这3种方案:

1 表单文件

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几乎完全一样。

小结

如果是简单的上传小型字节数据,可以用HttpClientByteArrayContent参数,或者RefitByteArrayContent参数。

DEMO代码地址:woodsun/TestPostBytes - 码云 - 开源中国 (gitee.com)

原文地址:https://www.cnblogs.com/sunnytrudeau/p/15449080.html