How to get rid of BOM when downloading text from azure blob

When you download a text content using CloudBlockBlob.DownloadText() if the blob contains the BOM or byte order mark, then the returned text will contain some additional characters.

For example in case of having UTF8 with BOM, you will receive  sequence at beginning of the file content. Then if you use the text content for example to deserialize a json object, the deserialization will fail with this message:

Message: Newtonsoft.Json.JsonReaderException : Unexpected character encountered while parsing value

How can you get rid of this BOM?

When downloading file using System.Net.WebClient.DownloadString() the BOM will be eliminated automatically by the DownloadString method and you only receive the content which you expect. But to use this method to download a text blob you need to create a sas token and download the file using url containing sas token.

If you take a look at source code for WebClient.DownloadString() and source code of CloudBlockBlob.DownloadText() you will see they are acting differently.

I personally prefer the way that is used in WebClient in most cases. So as a solution, I mixed some code from those classes to create a DownloadString extension method for CloudBlockBlob, then to download text without BOM, you can simply use it this way:

var connectionString = "connection string"
var storageAccount = CloudStorageAccount.Parse(connectionString);
var blobClient = storageAccount.CreateCloudBlobClient();
var container = blobClient.GetContainerReference("container name");
var blob = container.GetBlockBlobReference("blob name");
var text = blob.DownloadString();

DownloadString extension method

Here is the code for the extension method:

using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Blob;
using Microsoft.WindowsAzure.Storage.Core;
using System.Text;

namespace AzureExtensions
{
    public static class CloudBlockBlobExtensions
    {
        /// <summary>
        /// Downloads string content of the given blob. If encoding is not specified, it removes the BOM mark from beginning of the string if exists.
        /// </summary>
        /// <param name="blob">CloudBlockBlob to download contents.</param>
        /// <param name="encoding">Encoding which should be used to convert the content of blob to string. If encoding is not specified, it removes the BOM mark from beginning of the string if exists.</param>
        /// <returns>string content of the given blob.</returns>
        public static string DownloadString(this CloudBlockBlob blob, System.Text.Encoding encoding = null, AccessCondition accessCondition = null, BlobRequestOptions options = null, OperationContext operationContext = null)
        {
            using (SyncMemoryStream stream = new SyncMemoryStream())
            {
                blob.DownloadToStream(stream, accessCondition, options, operationContext);
                byte[] streamAsBytes = stream.GetBuffer();
                return GetStringUsingEncoding(streamAsBytes, encoding);
            }
        }
        private static string GetStringUsingEncoding(byte[] data, Encoding encoding = null)
        {
            int bomLengthInData = -1;

            // If no content encoding listed in the ContentType HTTP header, or no Content-Type header present, then
            // check for a byte-order-mark (BOM) in the data to figure out encoding.
            if (encoding == null)
            {
                byte[] preamble;
                // UTF32 must be tested before Unicode because it's BOM is the same but longer.
                Encoding[] encodings = { Encoding.UTF8, Encoding.UTF32, Encoding.Unicode, Encoding.BigEndianUnicode };
                for (int i = 0; i < encodings.Length; i++)
                {
                    preamble = encodings[i].GetPreamble();
                    if (ByteArrayHasPrefix(preamble, data))
                    {
                        encoding = encodings[i];
                        bomLengthInData = preamble.Length;
                        break;
                    }
                }
            }

            // Do we have an encoding guess?  If not, use default.
            if (encoding == null)
                encoding = Encoding.UTF8;

            // Calculate BOM length based on encoding guess.  Then check for it in the data.
            if (bomLengthInData == -1)
            {
                byte[] preamble = encoding.GetPreamble();
                if (ByteArrayHasPrefix(preamble, data))
                    bomLengthInData = preamble.Length;
                else
                    bomLengthInData = 0;
            }

            // Convert byte array to string stripping off any BOM before calling GetString().
            // This is required since GetString() doesn't handle stripping off BOM.
            return encoding.GetString(data, bomLengthInData, data.Length - bomLengthInData);
        }
        private static bool ByteArrayHasPrefix(byte[] prefix, byte[] byteArray)
        {
            if (prefix == null || byteArray == null || prefix.Length > byteArray.Length)
                return false;
            for (int i = 0; i < prefix.Length; i++)
            {
                if (prefix[i] != byteArray[i])
                    return false;
            }
            return true;
        }
    }
}

You May Also Like

About the Author: Reza Aghaei

I’ve been a .NET developer since 2004. During these years, as a developer, technical lead and architect, I’ve helped organizations and development teams in design and development of different kind of applications including LOB applications, Web and Windows application frameworks and RAD tools. As a teacher and mentor, I’ve trained tens of developers in C#, ASP.NET MVC and Windows Forms. As an interviewer I’ve helped organizations to assess and hire tens of qualified developers. I really enjoy learning new things, problem solving, knowledge sharing and helping other developers. I'm usually active in .NET related tags in stackoverflow to answer community questions. I also share technical blog posts in my blog as well as sharing sample codes in GitHub.

Leave a Reply

Your email address will not be published. Required fields are marked *