尝试接受具有大文件的模型时出现 .net API 错误

Posted

技术标签:

【中文标题】尝试接受具有大文件的模型时出现 .net API 错误【英文标题】:.net API error when attempting to accept model with large file 【发布时间】:2021-08-27 12:56:19 【问题描述】:

我有一个 API 方法,当一个大文件传递给它时,它接收一个空模型参数。 我创建了一个测试客户端来测试这个端点。测试客户端和 API 都具有相同的模型,并且都使用 .NET 4.5:

public class FilingPostModel
    
        public string Id  get; set; 
        public string TypeId  get; set; 
        public string FirstName  get; set; 
        public string MiddleName  get; set; 
        public string LastName  get; set; 
        public string Suffix  get; set; 
        public string Line1  get; set; 
        public string Line2  get; set; 
        public string City  get; set; 
        public string State  get; set; 
        public string PostalCode  get; set; 
        public string Country  get; set; 
        public string Email  get; set; 
        public string PhoneNumber  get; set; 
        public string Comment  get; set; 
        public string DateSubmitted  get; set; 
        public string Summary  get; set; 
        public List<FilePostModel> FileData  get; set; 
    

    public class FilePostModel
    
        public string FileId  get; set; 
        public string FileName  get; set; 
        public string ContentType  get; set; 
        public string FileContent  get; set; 
        public string DateSubmitted  get; set; 
        public string ClassificationId  get; set;      
    

测试客户端正在提交此模型:

City: "j"
Comment: null
Country: "United States"
Email: "test@test.tst"
FileData: Count = 1
TypeId: "f94e264a-c8b1-44aa-862f-e6f0f7565e19"
FirstName: "fname"
Id: null
LastName: "lname"
Line1: "testdrive 1"
Line2: null
MiddleName: null
PhoneNumber: "3748923798"
PostalCode: "12345"
State: "Pennsylvania"
Suffix: null
Summary: null

FileData 组件有一项:

FileContent: "xY+v6sC8RHQ19av2LpyFGu6si8isrn8YquwGRAalW/6Q..."
ClassificationId: null
ContentType: "text/plain"
FileName: "large.txt"

这是用于创建和发送 API 请求的测试客户端方法

public async Task<ActionResult> PostNewFiling(FilingPostModel model)

    Dictionary<string, string> req = new Dictionary<string, string>
        
            "grant_type", "password",
            "username", "some user name",
            "password", "some password",
        ;
    FilingApiPostModel postModel = new FilingApiPostModel(model);
    using (HttpClient client = new HttpClient())
    
        client.Timeout = TimeSpan.FromMinutes(15);
        client.BaseAddress = new Uri(baseUrl);
        var resp = await client.PostAsync("Token", new FormUrlEncodedContent(req));
        if (resp.IsSuccessStatusCode)
        
            TokenModel token = JsonConvert.DeserializeObject<TokenModel>(await resp.Content.ReadAsStringAsync());
            if (!String.IsNullOrEmpty(token.access_token))
            
                foreach (HttpPostedFileBase file in model.Files)
                
                    if (file != null)
                                                        
                        FilePostModel fmodel = new FilePostModel();
                        fmodel.FileName = file.FileName;
                        fmodel.ContentType = file.ContentType;
                        byte[] fileData = new byte[file.ContentLength];
                        await file.InputStream.ReadAsync(fileData, 0, file.ContentLength);
                        fmodel.FileContent = Convert.ToBase64String(fileData);
                        fmodel.ClassificationId = model.Classification1;
                        postModel.FileData.Add(fmodel);
                    
                
                
                client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token.access_token);
                var response = await client.PostAsJsonAsync("api/Filing/PostFiling", postModel);    
                var responseBody = await response.Content.ReadAsStringAsync();

                if (response.IsSuccessStatusCode)
                    return Json(new  responseBody );
                else
                    return Json(new  error = true, message = "Error Uploading", obj = responseBody );
            
        
        return Json(new  error = true, message = "Error Uploading" );
    

以下是接收此客户端请求的 API 方法:

public async Task<StatusModel> PostFiling(FilingPostModel model)

这是 web.config 中的 maxAllowedContentLength 设置:

<system.webServer>
    <security>
      <requestFiltering>
        <requestLimits maxAllowedContentLength="4294967295" />
      </requestFiltering>
    </security>
  </system.webServer>

在此测试场景中,API 模型始终为 null。我收到两种类型的错误:

    Newtonsoft.Json - 数组尺寸超出支持范围 Newtonsoft.Json - 解析值时遇到意外字符:x。路径 'FileData[0].Bytes',第 1 行,位置 517

此测试文件的文件大小为 560 MB。我使用Dummy File Creator 创建它。以下是内容的示例:

ůêÀ¼Dt5õ«ö.œ…Ȭ®ªìD¥[þ6\hW åz·cʾYP¸‡>•;,–@Ó¶ÿm™­fø@ÃNÇIäÀ¿Y4~ëÆÃc¥EWÀ_÷õ9%«éÀG!WBÍ*G2P×æŸ7ú‚ÓêRúÅîµMZSªšpt6ä”Òø˜H

我也尝试使用“fsutil file createnew”创建测试文件,但收到类似错误。

使用 256 MB 文件进行测试时一切正常。

感谢您的帮助。

【问题讨论】:

第 1 行位置 517 上的字符是什么?看起来不是大小问题。代码在第一个坏字符上失败。 我更新了问题,提供了有关正在上传的文件的更多详细信息。 我建议更改您的模型以分离二进制数据和表单数据。这样您就可以将模型解析为 json 并单独处理数据,而且它的性能也会更高。 A sample 关于如何使用 HttpClient 发送 multipart/form-data 内容 如@Eldar 所述,最好将表单数据和二进制数据分开。 byte [] 二进制数据在 JSON 中表示为 JSON 中的 Base64 字符串,而 Json.NET 不具备读取“块”中的巨大字符串的能力。因此,如果二进制数据太大,您可能会超过MAX .Net array size。要改善问题,请参阅Json.Net deserialize out of memory issue。 服务器设置允许的最大请求大小是多少? 【参考方案1】:

如果问题是由数组的总大小而不是数组中的元素数量引起的,则可以通过定位 64 位编译和更新 app.config 来避免 OutOfMemoryException

<configuration>
  <runtime>
    <gcAllowVeryLargeObjects enabled="true" />
  </runtime>
</configuration>

gcAllowVeryLargeObjects element

【讨论】:

【参考方案2】:

每个实现都有自己的限制,例如 php 默认有 2MB,您可以使用 Post_Max_Size 增加它

ASP.NET Core 强制 30MB 大于默认限制的任何文件都可能导致错误。

这里是Upload Large Files In ASP.NET Core!

对于 IIS Express,您需要将 web.config 添加到您的项目中,并在其中添加以下标记:

<system.webServer>
  <security>
    <requestFiltering>
      <requestLimits maxAllowedContentLength="209715200" />
    </requestFiltering>
  </security>
</system.webServer>

【讨论】:

【参考方案3】:

您可以为您的操作添加两个属性:

[RequestFormLimits(MultipartBodyLengthLimit = 209715200)]
[FromBody]

【讨论】:

【参考方案4】:

如果您需要发布 JSON,我会将您的 FileData 对象更改为使用 Base64 编码的字符串来代替字节数组。

将 FileData 中的 Bytes 属性更改为字符串,然后在创建请求时将 fmodel.Bytes = fileData 更改为 fmodel.Bytes = Convert.ToBase64String(fileData)

反序列化 JSON 后,您可以使用 Convert.FromBase64String(String) 将 base64 转换回字节[]。

这不仅可以防止您超出 JSON 中的数组限制,还可以大大减少负载大小。

【讨论】:

注意:与字节数组的 Json 表示相比,Base64'ing 可能会减小大小,但与字节数组相比则不会。 Base64 数据的大小约为原始数据的 4/3 这不起作用。 API 上的空模型存在同样的问题。我用 base64 字符串更改更新了问题

以上是关于尝试接受具有大文件的模型时出现 .net API 错误的主要内容,如果未能解决你的问题,请参考以下文章

使用 Trainer API 预训练 BERT 模型时出现 ValueError

从 Ember 发布到 DRF API 时出现 406(不可接受)

尝试从 SAP HANA DB 创建实体模型时出现连接错误

创建具有大 (>1GB) BytesWritable 值大小的 SequenceFile 时出现 NegativeArraySizeException

在谷歌浏览器上下载大文件(最大 15 mb)时出现问题

尝试构建 RESTless API 时出现“方法不允许”错误