6

I have an API method that receives a null model parameter when a large file is passed to it.
I created a test client to test this endpoint. Both the test client and the API have these same identical models and are using .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; }     
    }

The test client is submitting this model:

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

The FileData component has one item:

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

This is the test clients method used to create and send the API request

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

Here is the API method to receive this client request:

public async Task<StatusModel> PostFiling(FilingPostModel model)

Here is the maxAllowedContentLength setting in web.config:

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

The API model is always null in this test scenario. I'm receiving two types of errors:

  1. Newtonsoft.Json - Array dimensions exceeded supported range
  2. Newtonsoft.Json - Unexpected character encountered while parsing value: x. Path 'FileData[0].Bytes', line 1, position 517

enter image description here

The file size of this test file is 560 MB. I used Dummy File Creator to create it. Here's a sample of what the content looks like:

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

I have also tried using "fsutil file createnew" to create a test file but receive similar error.

Everything works properly when testing with a 256 MB file.

Thank you for your help.

user2370664
  • 381
  • 5
  • 8
  • 30
  • What character is on line 1 position 517? It doesn't appear to be a size issue. Code is failing on first bad character. – jdweng Jun 10 '21 at 14:01
  • i updated the question with more detail regarding the file being uploaded. – user2370664 Jun 10 '21 at 14:20
  • 4
    I would suggest that change your model to separate the binary data and the form data. So that you can parse your model as json and process the data separately and also it will be more performant. A [sample](https://stackoverflow.com/a/53284839/12354911) about how to send multipart/form-data content with `HttpClient` – Eldar Jun 10 '21 at 18:20
  • 1
    Best would be to separate your form data and binary data, as mentioned by @Eldar. `byte []` binary data is represented as a Base64 string in JSON in JSON, and Json.NET does not have the ability to read huge strings in "chunks". So if the binary data gets too large you can exceed the [MAX .Net array size](https://stackoverflow.com/q/30895549/3744182). To ameliorate the problem see [Json.Net deserialize out of memory issue](https://stackoverflow.com/a/33489745/3744182). – dbc Jun 16 '21 at 12:41
  • Make Content type : "application/octet-stream" instead of "text/plain" – jdweng Jun 16 '21 at 16:57
  • Changing the Content Type to application/octet-stream did not work. Same error – user2370664 Jun 17 '21 at 10:23
  • `PostNewFiling(FilingPostModel model)` You are sending a file in the url? maybe try `PostNewFiling([FromBody] FilingPostModel model)` or `PostNewFiling([FromForm] FilingPostModel model)` instead. – CorrieJanse Jun 18 '21 at 00:13
  • *`Unexpected character encountered while parsing value: x. Path 'FileData[0].Bytes', line 1, position 517`* -- Json.NET automatically encodes and decodes `byte []` data as a [Base64 string](https://www.newtonsoft.com/json/help/html/SerializationGuide.htm#PrimitiveTypes). Are you sure your `FileData[0].Bytes` string is properly encoded? Can you share a [mcve] for that specific exception? – dbc Jun 19 '21 at 14:10
  • 1
    What is the maximum request size the server is set to allow? – Caius Jard Jun 22 '21 at 06:08
  • Here's my request limit: – user2370664 Jun 22 '21 at 16:46

4 Answers4

1

you can add two attributes to your action:

[RequestFormLimits(MultipartBodyLengthLimit = 209715200)]
[FromBody]
mojgan kiyani
  • 91
  • 1
  • 4
0

If you need to post JSON I would change your FileData object to use a Base64 encoded string in place of the byte array.

Change the Bytes property in FileData to a string and then when creating the request change fmodel.Bytes = fileData to fmodel.Bytes = Convert.ToBase64String(fileData).

After deserialising your JSON you can convert the base64 back to a byte[] using Convert.FromBase64String(String).

Not only will this prevent you exceeding the array limit in JSON but will also reduce the payload size considerably.

herostwist
  • 3,778
  • 1
  • 26
  • 34
  • Note: Base64'ing might reduce the size compared to the Json representation of a byte array, but not compared to a byte array. The Base64 data will be approximately 4/3 the size of the original – Caius Jard Jun 22 '21 at 06:06
  • This did not work. Same issue with null model at API. I updated the question with the base64 string changes – user2370664 Jun 22 '21 at 18:03
0

Every implementation has its own limit for example PHP has 2MB by default you can increase it using Post_Max_Size

ASP.NET Core enforces 30MB any file bigger than default limitation can cause errors.

Here's Upload Large Files In ASP.NET Core!

For IIS Express, you need to add web.config to your project and add the following markup into it:

<system.webServer>
  <security>
    <requestFiltering>
      <requestLimits maxAllowedContentLength="209715200" />
    </requestFiltering>
  </security>
</system.webServer>
Manik
  • 97
  • 1
  • 7
0

If the issue is being caused by the total size of the array, rather than the number of elements in the array, the OutOfMemoryException might be avoided by targeting 64-bit compilation and by updating your app.config:

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

gcAllowVeryLargeObjects element

user700390
  • 2,287
  • 1
  • 19
  • 26