2

I have a big JSON object which I need to pass to the view displayed in web browser. Since I can not have a Controller with an Action to return this JSON object, I thought to add the JSON object in the Razor view.

  1. @Html.Hidden("fileContent", fileContent);

  2. <textarea style="display:none"> @fileContent </textarea>

None of the above works as the ways I expected and gives me,

Exception of type 'System.OutOfMemoryException' was thrown.

Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.

Exception Details: System.OutOfMemoryException: Exception of type 'System.OutOfMemoryException' was thrown.

Yes, I agreed that's better to restructure the flow in another way (may be as Mediator suggest or have an action to return the JSON object.)

  1. Is that a limitation of MVC, the maximum size we can have for a MVC view?
  2. Is this because of the IIS Express configuration?
  3. Any other way to overcome this issue? Or the best way to pass a large object to the client browser.

Thank you for your time. Any help would be highly appreciated.

EDIT

Controller

var file = System.IO.File.ReadAllText(HttpContext.Server.MapPath("~/content/data.csv"));
ViewData.Add("file", file);
return View();

Stack trace

[OutOfMemoryException: Exception of type 'System.OutOfMemoryException' was thrown.] System.Text.StringBuilder.ExpandByABlock(Int32 minBlockCharCount) +163 System.Text.StringBuilder.Append(Char* value, Int32 valueCount) +82
System.Text.StringBuilder.AppendHelper(String value) +31
System.Text.StringBuilder.Append(String value) +186
System.IO.StringWriter.Write(String value) +30
System.Web.WebPages.WebPageBase.Write(Object value) +87

Community
  • 1
  • 1
Geeganage
  • 182
  • 1
  • 11
  • What is `fileContent`? And what is the point of sending it to the view and then sending it back again unchanged? –  Feb 08 '17 at 05:37
  • There are many factors causing `OutOfMemoryException`, e.g. `StringBuilder` overflow or lack of memory allocation for certain thread. Check controller code to make sure resource allocations are handled properly. – Tetsuya Yamamoto Feb 08 '17 at 06:00
  • @StephenMuecke the fileContent is a JSON string. I convert that string into JSON object in the client side java script. This JSON object will use to render the page. – Geeganage Feb 08 '17 at 06:15
  • @TetsuyaYamamoto You're correct. I have added the stack trace and the controller code. I didn't use any resource allocation. Did you mean something like run the garbage collector or so? Could you please share a link regarding that or any tips to how to do that? – Geeganage Feb 08 '17 at 06:47
  • 1
    How big is `data.csv`? – Leonid Vasilev Feb 08 '17 at 07:04
  • Better to use `ReadAllLines(HttpContext.Server.MapPath("~/content/data.csv"))` or `ReadLines` since it reads a line at a time as `IEnumerable`, hence less memory cost than loading all text into an array string passed for `ViewData`. Read this post for details: http://stackoverflow.com/questions/2965497/what-is-the-difference-between-file-readalllines-and-file-readalltext. – Tetsuya Yamamoto Feb 08 '17 at 07:24
  • @LeonidVasilyev It is getting bigger. At the moment it is nearly 1.5 MB – Geeganage Feb 08 '17 at 07:40
  • @TetsuyaYamamoto Thank you. Is there any other things I can do with resource allocation? And after I retrieve the list from _viewdata_ shouldn't I have to combine them again, to pass that as a single value or the content – Geeganage Feb 08 '17 at 07:49
  • Considering the file size is more than 1 MB, and array of chars should be contiguous, it may be achieved by using separate iterations with combining afterwards other than storing all at once in memory (and be wise calling `GC.Collect()` to clean up memory after dealing with large size objects). – Tetsuya Yamamoto Feb 08 '17 at 07:59
  • 1
    The problem appears to be that Razor can't allocate enough contiguous memory to create a 1.5 MB array (probably 3 MB due to UTF-16, and probably 4 MB due to the expanding of the StringBuilder's internal array). This is an awful little amount of memory to throw this exception. Try using a string array of lines instead and see if that resolves the problem. – CodeCaster Feb 08 '17 at 08:55
  • @CodeCaster I quite agree with you, but this _StringBuilder_ and the _StringWriter_ use inside MVC (_System.Web.WebPages_), so how we suppose to manage this. By the way, could you please explain me, what you mean by using string array of lines? – Geeganage Feb 08 '17 at 10:00
  • Instead of reading the file into one string, read it into a string array and write that in a loop. Though this may not make a difference, because it's the view's StringBuilder throwing the exception. – CodeCaster Feb 08 '17 at 10:03
  • @CodeCaster Yeah it is working. But seems there is no other way to send that object at once inside a DOM element. Isn't it? – Geeganage Feb 08 '17 at 11:06

1 Answers1

-1

Depending on your CSV file size, better to use ReadLines than ReadAllText like below:

var sb = new StringBuilder();
sb.Capacity = 16; // default, adjust it to less number as you wish

foreach (String line in System.IO.File.ReadLines(HttpContext.Server.MapPath("~/content/data.csv")))
{
    sb.Append(line);    
}

ViewData.Add("file", sb.ToString());

ReadLines returns IEnumerable<String> that reads a file line by line (one line at a time), hence it is suitable for certain files which potentially consuming huge memory space when stored as single large string.

However, if file contents are extremely large, consider using StreamReader instead of StringBuilder which requires contiguous array size and depends on heap fragmentation:

using (var sr = new System.IO.StreamReader(HttpContext.Server.MapPath("~/content/data.csv")))
{
     String line;
     while ((line = sr.ReadLine()) != null)
     {
         // combine source strings here
     }
}

Note that each character requires 2 bytes, and strings are array of characters, thus there should be contiguous arrays which affects performance and size during generating objects in memory. Hence, optimizing garbage collector functionality with GC.Collect() method may helpful after dealing with huge amount of objects requiring immediate cleanup.

Related references:

interesting OutOfMemoryException with StringBuilder

MSDN: System.Text.StringBuilder

MSDN: System.IO.StreamReader

Eric Lippert: Out Of Memory” Does Not Refer to Physical Memory

Community
  • 1
  • 1
Tetsuya Yamamoto
  • 24,297
  • 8
  • 39
  • 61
  • The reading is not the problem, the writing onto the view is. – CodeCaster Feb 08 '17 at 08:45
  • Thank you for passing valuable tips. File reads successfully to the variable. (into _ViewData_), error occurs once it try to render the DOM element with that value. – Geeganage Feb 08 '17 at 10:04
  • Not sure if it works, but my idea was to split JSON strings by each line into several hidden fields after executing `ReadLines` which passed array of strings e.g. `@for (int i = 0; i < file.Length; i++) { @Html.Hidden("fileContent" + i, file[i]); }` due to possible limitations in hidden field value rendered by HTML. – Tetsuya Yamamoto Feb 08 '17 at 10:07
  • @TetsuyaYamamoto It is working. It should because Razor doesn't use the lengthy value. But again I have to combine those strings into one in the java script which also can cause the lag. Seems that's is a limitation of the Razor engine, isn't it? – Geeganage Feb 08 '17 at 11:04
  • I think it isn't limitation from Razor engine, it may comes from HTML specification which limits value in hidden field at certain amount of characters. I will edit the answer above after all things were confirmed. – Tetsuya Yamamoto Feb 08 '17 at 13:28
  • @TetsuyaYamamoto That is not only for the hidden fields, it happens even you try to have this content inside the DIV. I think that happen when Razor tries to build the view result. – Geeganage Feb 09 '17 at 05:40