File: System\Messaging\XmlMessageFormatter.cs
Project: ndp\cdf\src\NetFx20\System.Messaging\System.Messaging.csproj (System.Messaging)
//------------------------------------------------------------------------------
// <copyright file="XmlMessageFormatter.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>                                                                
//------------------------------------------------------------------------------
 
namespace System.Messaging
{
    using System;
    using System.IO;
    using System.Xml;
    using System.Collections;
    using System.Xml.Serialization;
    using System.ComponentModel;
    using System.Security.Permissions;
 
    /// <include file='doc\XmlMessageFormatter.uex' path='docs/doc[@for="XmlMessageFormatter"]/*' />
    /// <devdoc>
    ///    Formatter class that serializes and deserializes objects into
    ///    and from  MessageQueue messages using Xml.
    /// </devdoc>    
    public class XmlMessageFormatter : IMessageFormatter
    {
        private Type[] targetTypes;
        private string[] targetTypeNames;
        Hashtable targetSerializerTable = new Hashtable();
        private bool typeNamesAdded;
        private bool typesAdded;
 
        /// <include file='doc\XmlMessageFormatter.uex' path='docs/doc[@for="XmlMessageFormatter.XmlMessageFormatter"]/*' />
        /// <devdoc>
        ///    Creates a new Xml message formatter object.
        /// </devdoc>
        public XmlMessageFormatter()
        {
            this.TargetTypes = new Type[0];
            this.TargetTypeNames = new string[0];
        }
 
        /// <include file='doc\XmlMessageFormatter.uex' path='docs/doc[@for="XmlMessageFormatter.XmlMessageFormatter1"]/*' />
        /// <devdoc>
        ///    Creates a new Xml message formatter object,
        ///    using the given properties.
        /// </devdoc>
        public XmlMessageFormatter(string[] targetTypeNames)
        {
            this.TargetTypeNames = targetTypeNames;
            this.TargetTypes = new Type[0];
        }
 
        /// <include file='doc\XmlMessageFormatter.uex' path='docs/doc[@for="XmlMessageFormatter.XmlMessageFormatter2"]/*' />
        /// <devdoc>
        ///    Creates a new Xml message formatter object,
        ///    using the given properties.
        /// </devdoc>
        public XmlMessageFormatter(Type[] targetTypes)
        {
            this.TargetTypes = targetTypes;
            this.TargetTypeNames = new string[0];
        }
 
        /// <include file='doc\XmlMessageFormatter.uex' path='docs/doc[@for="XmlMessageFormatter.TargetTypeNames"]/*' />
        /// <devdoc>
        ///    Specifies the set of possible types that will
        ///    be deserialized by the formatter from the 
        ///    message provided.
        /// </devdoc>
        [MessagingDescription(Res.XmlMsgTargetTypeNames)]
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
        public string[] TargetTypeNames
        {
            get
            {
                return this.targetTypeNames;
            }
 
            set
            {
                if (value == null)
                    throw new ArgumentNullException("value");
 
                this.typeNamesAdded = false;
                this.targetTypeNames = value;
            }
        }
 
        /// <include file='doc\XmlMessageFormatter.uex' path='docs/doc[@for="XmlMessageFormatter.TargetTypes"]/*' />
        /// <devdoc>
        ///    Specifies the set of possible types that will
        ///    be deserialized by the formatter from the 
        ///    message provided.
        /// </devdoc>
        [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), MessagingDescription(Res.XmlMsgTargetTypes)]
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
        public Type[] TargetTypes
        {
            get
            {
                return this.targetTypes;
            }
 
            set
            {
                if (value == null)
                    throw new ArgumentNullException("value");
 
                this.typesAdded = false;
                this.targetTypes = value;
            }
        }
 
        /// <include file='doc\XmlMessageFormatter.uex' path='docs/doc[@for="XmlMessageFormatter.CanRead"]/*' />
        /// <devdoc>
        ///    When this method is called, the formatter will attempt to determine 
        ///    if the contents of the message are something the formatter can deal with.
        /// </devdoc>
        public bool CanRead(Message message)
        {
            if (message == null)
                throw new ArgumentNullException("message");
 
            this.CreateTargetSerializerTable();
 
            Stream stream = message.BodyStream;
            XmlTextReader reader = new XmlTextReader(stream);
            reader.WhitespaceHandling = WhitespaceHandling.Significant;
            reader.DtdProcessing = DtdProcessing.Prohibit;
            bool result = false;
            foreach (XmlSerializer serializer in targetSerializerTable.Values)
            {
                if (serializer.CanDeserialize(reader))
                {
                    result = true;
                    break;
                }
            }
 
            message.BodyStream.Position = 0; // reset stream in case CanRead is followed by Deserialize
            return result;
        }
 
        /// <include file='doc\XmlMessageFormatter.uex' path='docs/doc[@for="XmlMessageFormatter.Clone"]/*' />
        /// <devdoc>
        ///    This method is needed to improve scalability on Receive and ReceiveAsync scenarios.  Not requiring 
        ///     thread safety on read and write.
        /// </devdoc>
        public object Clone()
        {
            XmlMessageFormatter formatter = new XmlMessageFormatter();
            formatter.targetTypes = targetTypes;
            formatter.targetTypeNames = targetTypeNames;
            formatter.typesAdded = typesAdded;
            formatter.typeNamesAdded = typeNamesAdded;
            foreach (Type targetType in targetSerializerTable.Keys)
                formatter.targetSerializerTable[targetType] = new XmlSerializer(targetType);
 
            return formatter;
        }
 
 
        /// <internalonly/>        
        private void CreateTargetSerializerTable()
        {
            if (!this.typeNamesAdded)
            {
                for (int index = 0; index < this.targetTypeNames.Length; ++index)
                {
                    Type targetType = Type.GetType(this.targetTypeNames[index], true);
                    if (targetType != null)
                        this.targetSerializerTable[targetType] = new XmlSerializer(targetType);
                }
 
                this.typeNamesAdded = true;
            }
 
            if (!this.typesAdded)
            {
                for (int index = 0; index < this.targetTypes.Length; ++index)
                    this.targetSerializerTable[this.targetTypes[index]] = new XmlSerializer(this.targetTypes[index]);
 
                this.typesAdded = true;
            }
 
            if (this.targetSerializerTable.Count == 0)
                throw new InvalidOperationException(Res.GetString(Res.TypeListMissing));
        }
 
        /// <include file='doc\XmlMessageFormatter.uex' path='docs/doc[@for="XmlMessageFormatter.Read"]/*' />
        /// <devdoc>
        ///    This method is used to read the contents from the given message 
        ///     and create an object.
        /// </devdoc>
        public object Read(Message message)
        {
            if (message == null)
                throw new ArgumentNullException("message");
 
            this.CreateTargetSerializerTable();
 
            Stream stream = message.BodyStream;
            XmlTextReader reader = new XmlTextReader(stream);
            reader.WhitespaceHandling = WhitespaceHandling.Significant;
            reader.DtdProcessing = DtdProcessing.Prohibit;
            foreach (XmlSerializer serializer in targetSerializerTable.Values)
            {
                if (serializer.CanDeserialize(reader))
                    return serializer.Deserialize(reader);
            }
 
            throw new InvalidOperationException(Res.GetString(Res.InvalidTypeDeserialization));
        }
 
        /// <include file='doc\XmlMessageFormatter.uex' path='docs/doc[@for="XmlMessageFormatter.Write"]/*' />
        /// <devdoc>
        ///    This method is used to write the given object into the given message.  
        ///     If the formatter cannot understand the given object, an exception is thrown.
        /// </devdoc>
        public void Write(Message message, object obj)
        {
            if (message == null)
                throw new ArgumentNullException("message");
 
            if (obj == null)
                throw new ArgumentNullException("obj");
 
            Stream stream = new MemoryStream();
            Type serializedType = obj.GetType();
            XmlSerializer serializer = null;
            if (this.targetSerializerTable.ContainsKey(serializedType))
                serializer = (XmlSerializer)this.targetSerializerTable[serializedType];
            else
            {
                serializer = new XmlSerializer(serializedType);
                this.targetSerializerTable[serializedType] = serializer;
            }
 
            serializer.Serialize(stream, obj);
            message.BodyStream = stream;
            //Need to reset the body type, in case the same message
            //is reused by some other formatter.
            message.BodyType = 0;
        }
    }
}