Tuesday, February 28, 2012

How to Read Base64 encoded WCF Data Service Response Stream

WCF Data Services by default use base64 encoding to transmit the content over the wire that that can be easily handled by most of the application out of the box, but in some scenarios it may required explicit manipulation.
I my case it was related with a message interceptor that i wrote to translated the outgoing message from Base64 encoded XML content to plain text file
Alternative Titles 
How to decode Base64 encoded streams.
How to reading data service response in massage interceptors.
Solution
Read the response content in smaller chunks and decode them as base 64 string then look for the start and end tokens looking for that is in most case are start and end tag for entity

Implementation 
I have created an out of the box solution for this problem and code is provided below. It is actually wrapper over XmlDictionaryReader class that WCF data service uses to wrap it response

Class Base64StreamReader   has one public function "ReadNextFragment" that reads a string fragment from given stream in most of the cases it is start and end tag of xml element.

It also has a public property "CanRead" on which consumer can loop through.

If you face any problem or found any bug then please please reply to this post I will try to fix ASAP.

Example:

This example explains how to use this reader

//In order to intercept WCF Response we need to implement “IDispatchMessageInspector. BeforeSendReply then //need to bind with Service Behavior (Not covered in this post)

public void BeforeSendReply(ref Message reply, object correlationState)
{
HttpResponseMessageProperty response = reply.Properties[HttpResponseMessageProperty.Name] as HttpResponseMessageProperty;
       Encoding encoding = Encoding.UTF8;

//Other codes …….

XmlDictionaryReader base64ReplyStream;
base64ReplyStream = reply.GetReaderAtBodyContents();

//Tags to search for
string startElement = "<m:properties>";
string endElement = "</m:properties>";

//Reader Get Created
Base64StreamReader reader64 = new Base64StreamReader(base64ReplyStream, encoding);

do
{
                   
temp = reader64.ReadNextFragment(startElement, endElement);

//TODO use temp here

} while (reader64.CanRead);

}

Actual Reader Class Implementation 
[Copy paste entire class code in your application]

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;

class Base64StreamReader
    {
        private int maxBufferSize = 1024 * 1024;
        byte[] buffer;
        XmlDictionaryReader base64ReplyStream;
        int bufferIndexTokenStart;
        int bufferIndexTokenEnd;
        int lastReadCountFromStream = 0;
        string currentContentString = string.Empty;
        private bool canRead = true;
        public bool CanRead { get { return lastReadCountFromStream > 0; } }
        Encoding encodingUsed;

        public Base64StreamReader(XmlDictionaryReader base64RplyStream,
Encoding encodingUsed)
        {
            buffer = new byte[maxBufferSize];
            this.encodingUsed = encodingUsed;
            this.base64ReplyStream = base64RplyStream;
            base64ReplyStream.ReadStartElement();
            FillBufferString();
        }

        private void FillBufferString()
        {
            string tempString;
            lastReadCountFromStream = base64ReplyStream.ReadContentAsBase64(buffer, 0,
              maxBufferSize);
            tempString = encodingUsed.GetString(buffer, 0, lastReadCountFromStream);
            currentContentString = currentContentString + tempString;

        }

        private void FeatchStreamIfEmpty(string startElement, string endElement)
        {
            bufferIndexTokenStart = currentContentString.IndexOf(startElement);
            bufferIndexTokenEnd = currentContentString.IndexOf(endElement);

            if (!(bufferIndexTokenEnd >= 0 && bufferIndexTokenStart >= 0))
            {
                if (this.canRead == true)
                {
                    this.FillBufferString();
                }
                else
                {
                    throw new ApplicationException("End of stream");
                }
            }
        }

        private string ReadFragmentFromBuffer(string startElement, string endElement)
        {
            string entityXML = string.Empty;

            bufferIndexTokenStart = currentContentString.IndexOf(startElement);
            bufferIndexTokenEnd = currentContentString.IndexOf(endElement);

            if (bufferIndexTokenEnd >= 0 && bufferIndexTokenStart >= 0)
            {
                // get string content in between start and end token.
                entityXML = currentContentString.Substring(bufferIndexTokenStart,
                    bufferIndexTokenEnd - bufferIndexTokenStart + endElement.Length);

              
                bufferIndexTokenStart = bufferIndexTokenEnd + endElement.Length;
currentContentString = currentContentString.Substring(bufferIndexTokenStart,
                    currentContentString.Length - bufferIndexTokenStart);

            }
            return entityXML;
        }

       public string ReadNextFragment(string startElement, string endElement)
        {
            string entityXML;
            FeatchStreamIfEmpty(startElement, endElement);
            entityXML = ReadFragmentFromBuffer(startElement, endElement);
            FeatchStreamIfEmpty(startElement, endElement);
            return entityXML;
        }
    }




No comments:

Post a Comment