1

I have to read a file content Base64 string in an element of XML that is returned from an API.

My problem is this string can be very long, depending on file size.

At first, I used XmlDocument to read XML. Now I use XmlReader to avoid System.OutOfMemoryException when XML is too large.

But I when I read the string, receive a System.OutOfMemoryException too. The string is too long, I guess.

using (XmlReader reader = Response.ResponseXmlXmlReader)
{
    bool found = false;
    //Read result
    while (reader.Read() && !found)
    {
        if(reader.NodeType == XmlNodeType.Element && reader.Name == "content")
        {
            //Read file content
            string file_content = reader.ReadElementContentAsString();
            //Write file
            File.WriteAllBytes(savepath + file.name, Convert.FromBase64String(file_content));

            //Set Found!
            found = true;
        }
    }
} 

How can I read file content string with XmlReader without System.OutOfMemoryException?

dbc
  • 104,963
  • 20
  • 228
  • 340
ONLAEG
  • 197
  • 3
  • 12
  • You could probably use [XmlReader.ReadValueChunk](https://learn.microsoft.com/en-us/dotnet/api/system.xml.xmlreader.readvaluechunk?view=netframework-4.7.2) to read and decode the large Base64 content piece-by-piece. Make sure the char buffer is of a size that allows the whole buffer to be fully Base64-decoded. Since a Base64 character always encodes 6 bits, choose a buffer size `numB64Chars` that decode into `numBytes` bytes where `numB64Chars = numBytes * 4/3` (= numBytes * 8/6) –  Jan 10 '19 at 10:54
  • (Side note: Pay attention to the documentation. XmlReader.ReadValueChunk does not guarantee that it will fill the buffer in one call. Rather check the return value of XmlReader.ReadValueChunk to see how many Base64 chars it has read, and if necessary call this method again - with approriately adjusted arguments, of course - until the buffer is completely filled or the end of the content is reached) –  Jan 10 '19 at 10:57
  • @elgonzo thank you. This is a good solution. I searched only for readelement.... Next time, I have to read document more. – ONLAEG Jan 11 '19 at 01:22

1 Answers1

2

You can use XmlReader.ReadElementContentAsBase64(Byte[] buffer, Int32 index, Int32 count) for this purpose. This method allows for Base64 element contents of an XML element to be read and decoded in chunks, thus avoiding an OutOfMemoryException for large elements.

For instance, you could introduce the following extension methods:

public static class XmlReaderExtensions
{
    public static bool ReadToAndCopyBase64ElementContentsToFile(this XmlReader reader, string localName, string namespaceURI, string path)
    {
        if (!reader.ReadToFollowing(localName, namespaceURI))
            return false;
        return reader.CopyBase64ElementContentsToFile(path);
    }

    public static bool CopyBase64ElementContentsToFile(this XmlReader reader, string path)
    {
        using (var stream = File.Create(path))
        {
            byte[] buffer = new byte[8192];
            int readBytes = 0;

            while ((readBytes = reader.ReadElementContentAsBase64(buffer, 0, buffer.Length)) > 0)
            {
                stream.Write(buffer, 0, readBytes);
            }
        }
        return true;
    }
}

And then do:

var path = Path.Combine(savepath, file.name);
var found = reader.ReadToAndCopyBase64ElementContentsToFile("content", "", path);

Demo fiddle here.

dbc
  • 104,963
  • 20
  • 228
  • 340
  • 1
    Thank you for a very very clear answer. Your XmlReaderExtensions class is really helpful for me. – ONLAEG Jan 11 '19 at 01:32