Friday, December 18, 2009

Rough implementation of Base64StreamReader and Base64StreamWriter

Decorator-like streams in .NET work beautifully - you can decorate any stream with any other stream so for example you can put compression and encryption over network stream seamlessly and this makes no difference for the client code as it always expects just a stream.

Few days ago I had to wrap a compressed stream so that the data which is actually passed to the client code is not an arbitrary stream of bytes but something that can be written in a text file. An obvious answer is Base64.

There's however one caveat - we do not have any implementation of base64 decorating streams in the base class library.

What I expected is something like:

using ( FileStream fs = new FileStream() )
using ( Base64StreamWriter b64 = new Base64StreamWriter( fs ) )
using ( StreamWriter sw = new StreamWriter( b64 ) )
    sw.Write( "some text" );

and corresponding:



using ( FileStream fs = new FileStream(...) )
using ( Base64StreamReader bw = new Base64StreamReader( fs ) )
using ( StreamReader sw = new StreamReader( bw ) )
    MessageBox.Show( sw.ReadToEnd() );

As I've not been able to find any useful implementation, I wrote some rough code which is not fully tested but seems to work correctly in few important scenarios. Please use and modify the code at your own risk.


Note also that you can alternatively switch between Base64 and BinHex encodings (BinHex uses only digits to encode data).



using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Xml;
 
namespace Base64Streams
{
    public class Base64StreamReader : Stream
    {
        private static XmlReaderSettings InitXmlReaderSettings()
        {
            XmlReaderSettings settings = new XmlReaderSettings();
            settings.ConformanceLevel = ConformanceLevel.Auto;
            settings.CloseInput = false;
            return settings;
        }
 
        private Stream TheStream;
        private XmlReader xw;
 
        public Base64StreamReader( Stream Stream )
        {
            this.TheStream = Stream;
        }
 
        public override bool CanRead
        {
            get { return TheStream.CanRead; }
        }
 
        public override bool CanSeek
        {
            get { return TheStream.CanSeek; }
        }
 
        public override bool CanWrite
        {
            get { return TheStream.CanWrite; }
        }
 
        public override void Flush()
        {
        }
 
        public override void Close()
        {
            xw.Close();
        }
 
        public override long Length
        {
            get
            {
                return TheStream.Length;
            }
        }
 
        public override long Position
        {
            get
            {
                return TheStream.Position;
            }
            set
            {
                throw new NotImplementedException();
            }
        }
 
        bool movedToContent = false;
        public override int Read( byte[] buffer, int offset, int count )
        {
            if ( !movedToContent )
            {
                xw = XmlReader.Create( TheStream, InitXmlReaderSettings() );
                xw.MoveToContent();
 
                movedToContent = true;
            }
 
            /* use 
             * int readed = xw.ReadElementContentAsBase64( buffer, offset, count );
             * for Base64 encoding
             */
            int readed = xw.ReadElementContentAsBinHex( buffer, offset, count );
 
            return readed;
        }
 
        public override long Seek( long offset, SeekOrigin origin )
        {
            throw new NotImplementedException();
        }
 
        public override void SetLength( long value )
        {
            throw new NotImplementedException();
        }
 
        public override void Write( byte[] buffer, int offset, int count )
        {
        }
    }
 
    public class Base64StreamWriter : Stream
    {
        private static XmlWriterSettings InitXmlWriterSettings()
        {
            XmlWriterSettings settings = new XmlWriterSettings();
            settings.ConformanceLevel = ConformanceLevel.Auto;
            settings.Encoding = Encoding.ASCII;
            settings.OmitXmlDeclaration = true;
            settings.CloseOutput = true;
            return settings;
        }
 
        private Stream TheStream;
        private XmlWriter xw;
 
        public Base64StreamWriter( Stream Stream )
        {
            this.TheStream = Stream;
 
            xw = XmlWriter.Create( Stream, InitXmlWriterSettings() );
            xw.WriteStartElement( "data" );
        }
 
        public override bool CanRead
        {
            get { return TheStream.CanRead; }
        }
 
        public override bool CanSeek
        {
            get { return TheStream.CanSeek; }
        }
 
        public override bool CanWrite
        {
            get { return TheStream.CanWrite; }
        }
 
        public override void Close()
        {
            if ( xw.WriteState != WriteState.Closed )
            {
                xw.WriteEndElement();
                xw.Close();
            }
            base.Close();
        }
 
        public override void Flush()
        {
            xw.Flush();
        }
 
        public override long Length
        {
            get
            {
                return TheStream.Length;
            }
        }
 
        public override long Position
        {
            get
            {
                return TheStream.Position;
            }
            set
            {
                throw new NotImplementedException();
            }
        }
 
        public override int Read( byte[] buffer, int offset, int count )
        {
            throw new NotImplementedException();
        }
 
        public override long Seek( long offset, SeekOrigin origin )
        {
            throw new NotImplementedException();
        }
 
        public override void SetLength( long value )
        {
            throw new NotImplementedException();
        }
 
        public override void Write( byte[] buffer, int offset, int count )
        {
            /* use 
             * xw.WriteBase64( buffer, offset, count ); 
             * for Base64 encoding
             */
            xw.WriteBinHex( buffer, offset, count );
        }
    }
}

2 comments:

Said said...

Hi
I have question about this article (http://netpl.blogspot.com/2007/11/persistentstatepage-with-event.html)

May i have your Yahoo or Gmail ID ?
How can i contact you?

Thanks

Wiktor Zychla said...

Please drop a note under the article, I will surely read it.

Regards,
Wiktor