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 );
        }
    }
}

ASP.NET modules on IIS 7.5

IIS 7.0/7.5 is still new to me as I am not deploying applications but rather developing them. Anyway, I've been asked to help in moving one of applications from IIS 6.0 to 7.5 as someone had noticed that it just does not work.

Few hours spent on trying to find out what's wrong and the answer is: for some reason it is not sufficient to list your ASP.NET modules in the web.config file as you always do:

<httpModules>
 
<add name="MyModule" type="MyModule.MyModule, MyModule" />
 
</httpModules>

 

Such modules just do not work at all. Instead, you have to use a new configuration tab, "Modules" where you have to find and "enable" your modules.


I have no idea where this information is stored and why the web.config is not respected regarding modules anymore.


If there's a simple way to just say to IIS "yes, please use all my modules listed in web.config as you did in previous versions" I would like to learn it.


Edit: seems that the solution exists and is relatively simple. I've just missed the system.webServer section of the web.config file. Please read more on this here.

Thursday, December 10, 2009

Automated precompilation of xml serialized classes

To improve the performance of your code, the XmlSerializer( Type ) generates dynamic assemblies which store serialization and deserialization code of your classes.

This, however, impacts the performance as the generation takes about 300ms to complete.

.NET uses some kind of a cache to store these precompiled classes (use Reflector to inspect constructors of the class), however, a suprisingly little known SDK tool named sgen.exe can be used to actually force the precompilation. The sgen.exe invoked on Your.Assembly.dll creates Your.Assembly.XmlSerializers.dll which, if exists, is used by the XmlSerializer class.

To use the tool, create GenerateSerializers.bat in the root of your library project and put following line into it:

"c:\Program Files\Microsoft SDKs\Windows\v6.0A\bin\sgen.exe" /force /compiler:/keyfile:key_file.snk ./bin/Debug/Your.Assembly.dll

Remeber to prepare key_file.snk with sn.exe and put it beside the GenerateSerializers.bat.

Now open your project's properties and on the Build Events tab page type following lines into the "Post-build event command line" field:

cd "$(ProjectDir)"
GenerateSerializers.bat

That's all. Your.Assembly.XmlSerializers.dll will be rebuilt whenever you rebuild project with Your.Assembly.dll.

What's more - Your.Assembly.XmlSerializers.dll will be automatically copied to other projects in the same solution which reference Your.Assembly.dll. It means that you do not have to include or reference Your.Assembly.XmlSerializers.dll anywhere in your project.