QueryComposition & DataContracts

Topics: Web Api
Feb 8, 2011 at 3:03 PM

Hi guys,

I've been trying to use the [QueryComposition] feature with XmlSerializer serialization, and I realized that both the XmlProcessor, and the WebQueryProvider use the DataContractSerializer. Being that the custom serialization of the XmlProcessor is XmlSerializer, I get pretty different results when performing a regular GET and a WebQuery.

I can easily plug my media type processor to always use the XmlSerializer, however for the client side it would be a little more "hack-y" since I don't think there's a "public" way to plug your own WebQueryProvider.

So my first question is, is this actually correct, or am I missing something? and second, do you plan to support other serializers (maybe even ContentFormatters) in the future?

Thanks in advanced!

Gustavo

Feb 8, 2011 at 6:44 PM

Hey Gustavo,

I found it best to create my own ContentFormatters / Processors.  For example, if you attempt to serialize/deserialize POCO entities from EF4 you'll run into several problems due to XmlSerializer not supporting cyclic references.  Also json doesn't natively support references either so I had to write my own Json.NET Processor/ContentFormatter instead of using DataContractJsonSerializer.

This sounds like an annoyance, but once you start playing with the new WCF rest architecture, you'll find it so easy to plug-in whatever functionality you need.  Creating processors and formatters is a breeze. Below I've included:

  • Xml Processor & ContentFormatter which uses DataContractSerializer
  • Json Processor & ContentFormatter which uses Json.NET with support for cyclic references
    • The json serialize/deserialize functionality is shared by using extension methods provided below.
    using Microsoft.Net.Http;
    using Microsoft.ServiceModel.Http;
    using Newtonsoft.Json;

    /// <summary>
    /// Defines an Xml implementation of <see cref="IContentFormatter"/> using <see cref="DataContractSerializer"/>.
    /// </summary>
    public sealed class XmlDataContractContentFormatter : IContentFormatter
    {
        private readonly MediaTypeHeaderValue _MediaType;
        private readonly Type _ContentSerializableType;

        /// <summary>
        /// Initializes a new instance of the <see cref="XmlDataContractContentFormatter"/> class.
        /// </summary>
        /// <param name="contentSerializableType">Type of the content serializable.</param>
        /// <remarks></remarks>
        public XmlDataContractContentFormatter(Type contentSerializableType)
        {
            _MediaType = new MediaTypeHeaderValue("application/xml");
            _ContentSerializableType = contentSerializableType;
        }

        /// <summary>
        /// Gets the <see cref="JsonNetContentFormatter"/> supported media types.
        /// </summary>
        public IEnumerable<MediaTypeHeaderValue> SupportedMediaTypes
        {
            get { yield return _MediaType; }
        }

        /// <summary>
        /// Deserializes the Xml stream into an object.
        /// </summary>
        /// <param name="stream">The stream.</param>
        /// <returns>Deserialized object.</returns>
        public Object ReadFromStream(Stream stream)
        {
            var serializer = new DataContractSerializer(_ContentSerializableType, null, Int32.MaxValue, false, true, null);
            return serializer.ReadObject(stream);
        }

        /// <summary>
        /// Serializes the object instance to the stream.
        /// </summary>
        /// <param name="instance">The object to serialize.</param>
        /// <param name="stream">The stream.</param>
        public void WriteToStream(Object instance, Stream stream)
        {
            var serializer = new DataContractSerializer(_ContentSerializableType, null, Int32.MaxValue, false, true, null);
            serializer.WriteObject(stream, instance);
        }
    }


    /// <summary>
    /// Defines a Xml implementation of MediaTypeProcessor using DataContractSerializer.
    /// </summary>
    public class XmlDataContractProcessor : MediaTypeProcessor
    {
        #region Fields

        private readonly Boolean _UsesQueryComposition;
        private readonly Type _QueryCompositionType;

        #endregion

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="XmlDataContractProcessor"/> class.
        /// </summary>
        /// <param name="operation">The operation.</param>
        /// <param name="mode">The processor mode.</param>
        /// <remarks></remarks>
        public XmlDataContractProcessor(HttpOperationDescription operation, MediaTypeProcessorMode mode)
            : base(operation, mode)
        {
            // IQueryable support
            if (operation.Behaviors.Contains(typeof(QueryCompositionAttribute)))
            {
                _UsesQueryComposition = true;
                var queryCompositionItemType = operation.ReturnValue.ParameterType.GetGenericArguments()[0];
                _QueryCompositionType = typeof(List<>).MakeGenericType(queryCompositionItemType);
            }
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets the supported media types.
        /// </summary>
        /// <remarks></remarks>
        public override IEnumerable<String> SupportedMediaTypes
        {
            get
            {
                return new List<string> { "application/xml", "text/xml" };
            }
        }

        #endregion

        #region Methods

        /// <summary>
        /// Reads from stream.
        /// </summary>
        /// <param name="stream">The stream.</param>
        /// <param name="request">The request.</param>
        /// <returns>The deserialized object.</returns>
        public override Object ReadFromStream(Stream stream, HttpRequestMessage request)
        {
            var xmlSerializer = new DataContractSerializer(Parameter.ParameterType, null, Int32.MaxValue, false, true, null);
            return xmlSerializer.ReadObject(stream);
        }

        /// <summary>
        /// Writes to stream.
        /// </summary>
        /// <param name="instance">The instance.</param>
        /// <param name="stream">The stream.</param>
        /// <param name="request">The request.</param>
        /// <remarks></remarks>
        public override void WriteToStream(Object instance, Stream stream, HttpRequestMessage request)
        {
            if (_UsesQueryComposition)
            {
                instance = Activator.CreateInstance(_QueryCompositionType, instance);
                var serializer = new DataContractSerializer(_QueryCompositionType, null, Int32.MaxValue, false, true, null);
                serializer.WriteObject(stream, instance);
            }
            else
            {
                var xmlSerializer = new DataContractSerializer(Parameter.ParameterType, null, Int32.MaxValue, false, true, null);
                xmlSerializer.WriteObject(stream, instance);
            }
        }

        #endregion
    }


    // --------------------     JSON SUPPORT      -------------------------


    /// <summary>
    /// Defines a Json.NET implementation of <see cref="IContentFormatter"/>.
    /// </summary>
    public sealed class JsonNetContentFormatter : IContentFormatter
    {
        private readonly MediaTypeHeaderValue _MediaType;
        private readonly Type _ContentSerializableType;

        /// <summary>
        /// Initializes a new instance of the <see cref="JsonNetContentFormatter"/> class.
        /// </summary>
        /// <param name="contentSerializableType">Type of the content serializable.</param>
        /// <remarks></remarks>
        public JsonNetContentFormatter(Type contentSerializableType)
        {
            _MediaType = new MediaTypeHeaderValue("application/json");
            _ContentSerializableType = contentSerializableType;
        }

        /// <summary>
        /// Gets the <see cref="JsonNetContentFormatter"/> supported media types.
        /// </summary>
        public IEnumerable<MediaTypeHeaderValue> SupportedMediaTypes
        {
            get { yield return _MediaType; }
        }

        /// <summary>
        /// Deserializes the Json stream into an object.
        /// </summary>
        /// <param name="stream">The stream.</param>
        /// <returns>The deserialized object.</returns>
        public Object ReadFromStream(Stream stream)
        {
            return stream.ReadAsJsonSerializable(_ContentSerializableType);
        }

        /// <summary>
        /// Serializes the object instance to the stream.
        /// </summary>
        /// <param name="instance">The object to serialize.</param>
        /// <param name="stream">The stream.</param>
        public void WriteToStream(Object instance, Stream stream)
        {
            stream.WriteAsJsonSerializable(instance);
        }
    }

    /// <summary>
    /// Defines a JSON MediaTypeProcessor using Json.NET serialization/deserialization.
    /// </summary>
    public class JsonNetProcessor : MediaTypeProcessor
    {
        private readonly Type _ParameterType;

        /// <summary>
        /// Initializes a new instance of the <see cref="JsonNetProcessor"/> class.
        /// </summary>
        /// <param name="operation">The operation.</param>
        /// <param name="mode">The media type processor mode.</param>
        public JsonNetProcessor(HttpOperationDescription operation, MediaTypeProcessorMode mode)
            : base(operation, mode)
        {
            if (Parameter != null)
            {
                _ParameterType = Parameter.ParameterType;
            }
        }

        /// <summary>
        /// Gets the supported media types.
        /// </summary>
        public override IEnumerable<String> SupportedMediaTypes
        {
            get
            {
                return new List<String>
                           {
                               "application/json",
                               "text/json",
                               "application/x-javascript",
                               "text/javascript",
                               "text/x-javascript",
                               "text/x-json"
                           };
            }
        }

        /// <summary>
        /// Reads from stream.
        /// </summary>
        /// <param name="stream">The stream.</param>
        /// <param name="request">The request.</param>
        /// <returns>The deserialized object.</returns>
        public override Object ReadFromStream(Stream stream, HttpRequestMessage request)
        {
            return stream.ReadAsJsonSerializable(_ParameterType);
        }

        /// <summary>
        /// Writes to stream.
        /// </summary>
        /// <param name="instance">The instance.</param>
        /// <param name="stream">The stream.</param>
        /// <param name="request">The request.</param>
        /// <remarks></remarks>
        public override void WriteToStream(Object instance, Stream stream, HttpRequestMessage request)
        {
            stream.WriteAsJsonSerializable(instance);
        }
    }


    /// <summary>
    /// Defines extension methods to support Json.NET serialization and deserialization.
    /// </summary>
    public static class JsonNetExtensions
    {
        /// <summary>
        /// Deserializes the JSON stream into an object.
        /// </summary>
        /// <param name="stream">The stream.</param>
        /// <returns>The deserialized object.</returns>
        public static Object ReadAsJsonSerializable(this Stream stream)
        {
            return ReadAsJsonSerializable(stream, null);
        }

        /// <summary>
        /// Deserializes the JSON stream and casts it to the object type specified.
        /// </summary>
        /// <typeparam name="T">The type of object to deserialize.</typeparam>
        /// <param name="stream">The stream.</param>
        /// <returns>The deserialized object as type <typeparamref name="T"/></returns>
        public static T ReadAsJsonSerializable<T>(this Stream stream) where T : class
        {
            return ReadAsJsonSerializable(stream, typeof(T)) as T;
        }

        /// <summary>
        /// Reads the stream and deserializes it into a JSON object.
        /// </summary>
        /// <param name="stream">The stream to deserialize.</param>
        /// <param name="objectType">Type of the object to deserialize.</param>
        /// <returns>The deserialized object.</returns>
        public static Object ReadAsJsonSerializable(this Stream stream, Type objectType)
        {
            if (stream == null)
            {
                return null;
            }

            var jsonSerializer = new JsonSerializer
            {
                NullValueHandling = NullValueHandling.Ignore,
                MissingMemberHandling = MissingMemberHandling.Ignore,
                PreserveReferencesHandling = PreserveReferencesHandling.Objects,
                ReferenceLoopHandling = ReferenceLoopHandling.Ignore
            };

            try
            {
                using (var streamReader = new StreamReader(stream))
                using (var jsonTextReader = new JsonTextReader(streamReader))
                {
                    return (objectType != null)
                               ? jsonSerializer.Deserialize(jsonTextReader, objectType)
                               : jsonSerializer.Deserialize(jsonTextReader);
                }
            }
            catch (JsonReaderException)
            {
                // Logging...
                throw;
            }
            catch (JsonSerializationException)
            {
                // Logging...
                throw;
            }
        }

        /// <summary>
        /// Serializes the object into JSON and writes the data into the specified stream.
        /// </summary>
        /// <param name="stream">The stream.</param>
        /// <param name="instance">The object instance to serialize.</param>
        public static void WriteAsJsonSerializable(this Stream stream, Object instance)
        {
            if (instance == null)
            {
                return;
            }

            var jsonSerializer = new JsonSerializer
            {
                NullValueHandling = NullValueHandling.Ignore,
                MissingMemberHandling = MissingMemberHandling.Ignore,
                PreserveReferencesHandling = PreserveReferencesHandling.Objects,
                ReferenceLoopHandling = ReferenceLoopHandling.Ignore
            };

            try
            {
                var streamWriter = new StreamWriter(stream);
                var jsonTextWriter = new JsonTextWriter(streamWriter);
                jsonSerializer.Serialize(jsonTextWriter, instance);
                jsonTextWriter.Flush();
            }
            catch (JsonWriterException)
            {
                // Logging...
                stream.Dispose();
                throw;
            }
            catch (JsonSerializationException)
            {
                // Logging...
                stream.Dispose();
                throw;
            }
        }

        /// <summary>
        /// Serializes the object into JSON and returns an <see cref="HttpContent"/> of type <see cref="StreamContent"/>.
        /// </summary>
        /// <param name="instance">The object instance to serialize.</param>
        /// <returns><see cref="HttpContent"/> of type <see cref="StreamContent"/></returns>
        public static HttpContent ToHttpContentAsJsonSerializable(this Object instance)
        {
            if (instance == null)
            {
                return null;
            }

            var jsonSerializer = new JsonSerializer
            {
                NullValueHandling = NullValueHandling.Ignore,
                MissingMemberHandling = MissingMemberHandling.Ignore,
                PreserveReferencesHandling = PreserveReferencesHandling.Objects,
                ReferenceLoopHandling = ReferenceLoopHandling.Ignore
            };

            var memoryStream = new MemoryStream();
            try
            {
                var streamWriter = new StreamWriter(memoryStream);
                var jsonTextWriter = new JsonTextWriter(streamWriter);
                jsonSerializer.Serialize(jsonTextWriter, instance);
                jsonTextWriter.Flush();

                return new StreamContent(memoryStream);
            }
            catch
            {
                memoryStream.Dispose();
                throw;
            }
        }
    }

 

Feb 14, 2011 at 1:58 PM

Hi dgdev,

Thanks for the reply, I know about the processors and formatters, I already have them ready to be used, however how would you go about consuming a [QueryComposition] IEnumerable<T> using XmlSerializer instead of DataContracts? As far as I have researched, that logic is inside of the WebQueryProvider, and I have not found a way yet to use the whatever IContentFormatter in the CreateQuery<T> without modifying the client library manually myself. Basically do this using XmlSerializer:

var client = new HttpClient(BaseAddress);
var query = client.CreateQuery<T>(queryableResource);

Gustavo