Tribridge Connections

A Technology, Cloud Solutions & Industry Expertise Blog


Solving OutOfMemoryException errors when attempting to attach large Base64 encoded content into CRM annotations

Published: April 29, 2011

Microsoft Dynamics CRM (versions 3-2011) has shipped with functionality that allows developers and end users to attach files to annotations (notes) on any entity.  When doing so, files are stored in CRM’s native store as a Base64 encoded string.  If your usage scenario involves .Net code, the easiest way to encode and decode into Base64 format is to use System.Convert.ToBase64String and System.Convert.FromBase64String.    On larger byte arrays, both of these methods leak memory (no doubt due to the immutable nature of strings and some poor design inside the framework methods).  There is a not-so-known set of Transformation classes inside the System.Security.Cryptography namespace that can accomplish the same task using a buffer.  To simplify the syntax and execution, we have wrapped these functions in a static class:

namespace Tribridge.IO
{
using System.IO;
using System.Security.Cryptography;
using System.Text;

/// <summary>
/// Transforms text to and from base64 encoding using streams.
/// </summary>
/// <remarks>
/// <para>
/// The built in System.Convert.ToBase64String and FromBase64String methods are prone
/// to error with OutOfMemoryException when used with larger strings or byte arrays.
/// </para>
/// <para>
/// This class remedies the problem by using classes from the System.Security.Cryptography
/// namespace to do the byte conversion with streams and buffered output.
/// </para>
/// </remarks>
public static class Base64
{
/// <summary>
/// Converts a byte array to a base64 string one block at a time.
/// </summary>
/// <param name="data">The data.</param>
/// <returns></returns>
public static string ToBase64(byte[] data)
{
var builder = new StringBuilder();

using (var writer = new StringWriter(builder))
{
using (var transformation = new ToBase64Transform())
{
// Transform the data in chunks the size of InputBlockSize.
var bufferedOutputBytes = new byte[transformation.OutputBlockSize];
var i = 0;
var inputBlockSize = transformation.InputBlockSize;

while (data.Length - i > inputBlockSize)
{
transformation.TransformBlock(data, i, data.Length - i, bufferedOutputBytes, 0);
i += inputBlockSize;
writer.Write(Encoding.UTF8.GetString(bufferedOutputBytes));
}

// Transform the final block of data.
bufferedOutputBytes = transformation.TransformFinalBlock(data, i, data.Length - i);
writer.Write(Encoding.UTF8.GetString(bufferedOutputBytes));

// Free up any used resources.
transformation.Clear();
}

writer.Close();
}

return builder.ToString();
}

/// <summary>
/// Converts a base64 string to a byte array.
/// </summary>
/// <param name="s">The s.</param>
/// <returns></returns>
public static byte[] FromBase64(string s)
{
byte[] bytes;

using (var writer = new MemoryStream())
{
byte[] bufferedOutputBytes;
var inputBytes = s.ToBytes();

using (var transformation = new FromBase64Transform(FromBase64TransformMode.IgnoreWhiteSpaces))
{
bufferedOutputBytes = new byte[transformation.OutputBlockSize];

// Transform the data in chunks the size of InputBlockSize.
var i = 0;

while (inputBytes.Length - i > 4)
{
transformation.TransformBlock(inputBytes, i, 4, bufferedOutputBytes, 0);
i += 4;
writer.Write(bufferedOutputBytes, 0, transformation.OutputBlockSize);
}

// Transform the final block of data.
bufferedOutputBytes = transformation.TransformFinalBlock(inputBytes, i, inputBytes.Length - i);
writer.Write(bufferedOutputBytes, 0, bufferedOutputBytes.Length);

// Free up any used resources.
transformation.Clear();
}

writer.Position = 0;
bytes = writer.ReadAllBytes();

writer.Close();
}

return bytes;
}
}
}

Calling the static methods is easy, and operates as follows:

Tribridge.IO.Base64.ToBase64(bytes)

and

Tribridge.IO.Base64.FromBase64(string)

Both of these methods are adaptations from the following MSDN articles:

FromBase64Transformation class
ToBase64Transformation class

Enjoy!

Share Your Thoughts With Us

Load more comments
Thank you for the comment! Your comment must be approved first