瀏覽代碼

merged issue-24

csharptest 14 年之前
父節點
當前提交
247c75344e
共有 27 個文件被更改,包括 1795 次插入113 次删除
  1. 10 6
      src/ProtocolBuffers.Serialization/AbstractReader.cs
  2. 0 4
      src/ProtocolBuffers.Serialization/AbstractTextReader.cs
  3. 19 16
      src/ProtocolBuffers.Serialization/AbstractWriter.cs
  4. 13 1
      src/ProtocolBuffers.Serialization/DictionaryReader.cs
  5. 14 1
      src/ProtocolBuffers.Serialization/DictionaryWriter.cs
  6. 162 0
      src/ProtocolBuffers.Serialization/Http/FormUrlEncodedReader.cs
  7. 153 0
      src/ProtocolBuffers.Serialization/Http/MessageFormatFactory.cs
  8. 167 0
      src/ProtocolBuffers.Serialization/Http/MessageFormatOptions.cs
  9. 34 0
      src/ProtocolBuffers.Serialization/Http/ServiceExtensions.cs
  10. 22 5
      src/ProtocolBuffers.Serialization/JsonFormatReader.cs
  11. 24 4
      src/ProtocolBuffers.Serialization/JsonFormatWriter.cs
  12. 4 0
      src/ProtocolBuffers.Serialization/ProtocolBuffers.Serialization.csproj
  13. 92 53
      src/ProtocolBuffers.Serialization/XmlFormatReader.cs
  14. 57 20
      src/ProtocolBuffers.Serialization/XmlFormatWriter.cs
  15. 18 0
      src/ProtocolBuffers.Test/Compatibility/JsonCompatibilityTests.cs
  16. 21 0
      src/ProtocolBuffers.Test/Compatibility/XmlCompatibilityTests.cs
  17. 1 1
      src/ProtocolBuffers.Test/Properties/AssemblyInfo.cs
  18. 3 0
      src/ProtocolBuffers.Test/ProtocolBuffers.Test.csproj
  19. 264 0
      src/ProtocolBuffers.Test/TestMimeMessageFormats.cs
  20. 84 0
      src/ProtocolBuffers.Test/TestReaderForUrlEncoded.cs
  21. 386 0
      src/ProtocolBuffers.Test/TestRpcForMimeTypes.cs
  22. 86 0
      src/ProtocolBuffers.Test/TestWriterFormatJson.cs
  23. 119 2
      src/ProtocolBuffers.Test/TestWriterFormatXml.cs
  24. 3 0
      src/ProtocolBuffers/CodedInputStream.cs
  25. 3 0
      src/ProtocolBuffers/CodedOutputStream.cs
  26. 18 0
      src/ProtocolBuffers/ICodedInputStream.cs
  27. 18 0
      src/ProtocolBuffers/ICodedOutputStream.cs

+ 10 - 6
src/ProtocolBuffers.Serialization/AbstractReader.cs

@@ -17,12 +17,6 @@ namespace Google.ProtocolBuffers.Serialization
         
         /// <summary> Constructs a new reader </summary>
         protected AbstractReader() { MaxDepth = DefaultMaxDepth; }
-        /// <summary> Constructs a new child reader </summary>
-        protected AbstractReader(AbstractReader copyFrom)
-        {
-            _depth = copyFrom._depth + 1;
-            MaxDepth = copyFrom.MaxDepth;
-        }
 
         /// <summary> Gets or sets the maximum recursion depth allowed </summary>
         public int MaxDepth { get; set; }
@@ -112,6 +106,16 @@ namespace Google.ProtocolBuffers.Serialization
         /// Merges the input stream into the provided IBuilderLite 
         /// </summary>
         protected abstract bool ReadMessage(IBuilderLite builder, ExtensionRegistry registry);
+        
+        /// <summary>
+        /// Reads the root-message preamble specific to this formatter
+        /// </summary>
+        public abstract void ReadMessageStart();
+
+        /// <summary>
+        /// Reads the root-message close specific to this formatter
+        /// </summary>
+        public abstract void ReadMessageEnd();
 
         /// <summary>
         /// Merges the input stream into the provided IBuilderLite 

+ 0 - 4
src/ProtocolBuffers.Serialization/AbstractTextReader.cs

@@ -11,10 +11,6 @@ namespace Google.ProtocolBuffers.Serialization
     {
         /// <summary> Constructs a new reader </summary>
         protected AbstractTextReader() { }
-        /// <summary> Constructs a new child reader </summary>
-        protected AbstractTextReader(AbstractTextReader copyFrom)
-            : base(copyFrom)
-        { }
 
         /// <summary>
         /// Reads a typed field as a string

+ 19 - 16
src/ProtocolBuffers.Serialization/AbstractWriter.cs

@@ -12,36 +12,39 @@ namespace Google.ProtocolBuffers.Serialization
     /// <summary>
     /// Provides a base class for writers that performs some basic type dispatching
     /// </summary>
-    public abstract class AbstractWriter : ICodedOutputStream, IDisposable
+    public abstract class AbstractWriter : ICodedOutputStream
     {
         /// <summary>
-        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
+        /// Completes any pending write operations
         /// </summary>
-        public void Dispose()
+        public virtual void Flush()
         {
-            GC.SuppressFinalize(this);
-            Flush();
-            Dispose(true);
         }
 
         /// <summary>
-        /// Completes any pending write operations
+        /// Writes the message to the the formatted stream.
         /// </summary>
-        public virtual void Flush()
-        {
-        }
+        public abstract void WriteMessage(IMessageLite message);
 
         /// <summary>
-        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
+        /// Used to write any nessary root-message preamble. After this call you can call 
+        /// IMessageLite.MergeTo(...) and complete the message with a call to WriteMessageEnd().
+        /// These three calls are identical to just calling WriteMessage(message);
         /// </summary>
-        protected virtual void Dispose(bool disposing)
-        {
-        }
+        /// <example>
+        /// AbstractWriter writer;
+        /// writer.WriteMessageStart();
+        /// message.WriteTo(writer);
+        /// writer.WriteMessageEnd();
+        /// // ... or, but not both ...
+        /// writer.WriteMessage(message);
+        /// </example>
+        public abstract void WriteMessageStart();
 
         /// <summary>
-        /// Writes the message to the the formatted stream.
+        /// Used to complete a root-message previously started with a call to WriteMessageStart()
         /// </summary>
-        public abstract void WriteMessage(IMessageLite message);
+        public abstract void WriteMessageEnd();
 
         /// <summary>
         /// Writes a Boolean value

+ 13 - 1
src/ProtocolBuffers.Serialization/DictionaryReader.cs

@@ -1,4 +1,4 @@
-using System;
+using System;
 using System.Collections.Generic;
 using System.Globalization;
 using Google.ProtocolBuffers.Descriptors;
@@ -22,6 +22,18 @@ namespace Google.ProtocolBuffers.Serialization
             _ready = _input.MoveNext();
         }
 
+        /// <summary>
+        /// No-op
+        /// </summary>
+        public override void ReadMessageStart()
+        { }
+
+        /// <summary>
+        /// No-op
+        /// </summary>
+        public override void ReadMessageEnd()
+        { }
+
         /// <summary>
         /// Merges the contents of stream into the provided message builder
         /// </summary>

+ 14 - 1
src/ProtocolBuffers.Serialization/DictionaryWriter.cs

@@ -1,4 +1,4 @@
-using System;
+using System;
 using System.Collections;
 using System.Collections.Generic;
 using Google.ProtocolBuffers.Descriptors;
@@ -53,6 +53,19 @@ namespace Google.ProtocolBuffers.Serialization
             message.WriteTo(this);
         }
 
+
+        /// <summary>
+        /// No-op
+        /// </summary>
+        public override void WriteMessageStart()
+        { }
+
+        /// <summary>
+        /// No-op
+        /// </summary>
+        public override void WriteMessageEnd()
+        { }
+
         /// <summary>
         /// Writes a Boolean value
         /// </summary>

+ 162 - 0
src/ProtocolBuffers.Serialization/Http/FormUrlEncodedReader.cs

@@ -0,0 +1,162 @@
+using System;
+using System.IO;
+using System.Text;
+
+namespace Google.ProtocolBuffers.Serialization.Http
+{
+    /// <summary>
+    /// Allows reading messages from a name/value dictionary
+    /// </summary>
+    public class FormUrlEncodedReader : AbstractTextReader
+    {
+        private readonly TextReader _input;
+        private string _fieldName, _fieldValue;
+        private bool _ready;
+
+        /// <summary>
+        /// Creates a dictionary reader from an enumeration of KeyValuePair data, like an IDictionary
+        /// </summary>
+        FormUrlEncodedReader(TextReader input)
+        {
+            _input = input;
+            int ch = input.Peek();
+            if (ch == '?')
+            {
+                input.Read();
+            }
+            _ready = ReadNext();
+        }
+
+        #region CreateInstance overloads
+        /// <summary>
+        /// Constructs a FormUrlEncodedReader to parse form data, or url query text into a message.
+        /// </summary>
+        public static FormUrlEncodedReader CreateInstance(Stream stream)
+        {
+            return new FormUrlEncodedReader(new StreamReader(stream, Encoding.UTF8, false));
+        }
+
+        /// <summary>
+        /// Constructs a FormUrlEncodedReader to parse form data, or url query text into a message.
+        /// </summary>
+        public static FormUrlEncodedReader CreateInstance(byte[] bytes)
+        {
+            return new FormUrlEncodedReader(new StreamReader(new MemoryStream(bytes, false), Encoding.UTF8, false));
+        }
+
+        /// <summary>
+        /// Constructs a FormUrlEncodedReader to parse form data, or url query text into a message.
+        /// </summary>
+        public static FormUrlEncodedReader CreateInstance(string text)
+        {
+            return new FormUrlEncodedReader(new StringReader(text));
+        }
+
+        /// <summary>
+        /// Constructs a FormUrlEncodedReader to parse form data, or url query text into a message.
+        /// </summary>
+        public static FormUrlEncodedReader CreateInstance(TextReader input)
+        {
+            return new FormUrlEncodedReader(input);
+        }
+        #endregion
+
+        private bool ReadNext()
+        {
+            StringBuilder field = new StringBuilder(32);
+            StringBuilder value = new StringBuilder(64);
+            int ch;
+            while (-1 != (ch = _input.Read()) && ch != '=' && ch != '&')
+            {
+                field.Append((char)ch);
+            }
+
+            if (ch != -1 && ch != '&')
+            {
+                while (-1 != (ch = _input.Read()) && ch != '&')
+                {
+                    value.Append((char)ch);
+                }
+            }
+
+            _fieldName = field.ToString();
+            _fieldValue = Uri.UnescapeDataString(value.Replace('+', ' ').ToString());
+            
+            return !String.IsNullOrEmpty(_fieldName);
+        }
+
+        /// <summary>
+        /// No-op
+        /// </summary>
+        public override void ReadMessageStart()
+        { }
+
+        /// <summary>
+        /// No-op
+        /// </summary>
+        public override void ReadMessageEnd()
+        { }
+
+        /// <summary>
+        /// Merges the contents of stream into the provided message builder
+        /// </summary>
+        public override TBuilder Merge<TBuilder>(TBuilder builder, ExtensionRegistry registry)
+        {
+            builder.WeakMergeFrom(this, registry);
+            return builder;
+        }
+
+        /// <summary>
+        /// Causes the reader to skip past this field
+        /// </summary>
+        protected override void Skip()
+        {
+            _ready = ReadNext();
+        }
+
+        /// <summary>
+        /// Peeks at the next field in the input stream and returns what information is available.
+        /// </summary>
+        /// <remarks>
+        /// This may be called multiple times without actually reading the field.  Only after the field
+        /// is either read, or skipped, should PeekNext return a different value.
+        /// </remarks>
+        protected override bool PeekNext(out string field)
+        {
+            field = _ready ? _fieldName : null;
+            return field != null;
+        }
+
+        /// <summary>
+        /// Returns true if it was able to read a String from the input
+        /// </summary>
+        protected override bool ReadAsText(ref string value, Type typeInfo)
+        {
+            if (_ready)
+            {
+                value = _fieldValue;
+                _ready = ReadNext();
+                return true;
+            }
+            return false;
+        }
+
+        /// <summary>
+        /// It's unlikely this will work for anything but text data as bytes UTF8 are transformed to text and back to bytes
+        /// </summary>
+        protected override ByteString DecodeBytes(string bytes)
+        { return ByteString.CopyFromUtf8(bytes); }
+
+        /// <summary>
+        /// Not Supported
+        /// </summary>
+        public override bool ReadGroup(IBuilderLite value, ExtensionRegistry registry)
+        { throw new NotSupportedException(); }
+
+        /// <summary>
+        /// Not Supported
+        /// </summary>
+        protected override bool ReadMessage(IBuilderLite builder, ExtensionRegistry registry)
+        { throw new NotSupportedException(); }
+    }
+}

+ 153 - 0
src/ProtocolBuffers.Serialization/Http/MessageFormatFactory.cs

@@ -0,0 +1,153 @@
+using System;
+using System.IO;
+using System.Xml;
+using System.Text;
+
+namespace Google.ProtocolBuffers.Serialization.Http
+{
+    /// <summary>
+    /// Extensions and helpers to abstract the reading/writing of messages by a client-specified content type.
+    /// </summary>
+    public static class MessageFormatFactory
+    {
+        /// <summary>
+        /// Constructs an ICodedInputStream from the input stream based on the contentType provided
+        /// </summary>
+        /// <param name="options">Options specific to reading this message and/or content type</param>
+        /// <param name="contentType">The mime type of the input stream content</param>
+        /// <param name="input">The stream to read the message from</param>
+        /// <returns>The ICodedInputStream that can be given to the IBuilder.MergeFrom(...) method</returns>
+        public static ICodedInputStream CreateInputStream(MessageFormatOptions options, string contentType, Stream input)
+        {
+            ICodedInputStream codedInput = ContentTypeToInputStream(contentType, options, input);
+
+            if (codedInput is XmlFormatReader)
+            {
+                XmlFormatReader reader = (XmlFormatReader)codedInput;
+                reader.RootElementName = options.XmlReaderRootElementName;
+                reader.Options = options.XmlReaderOptions;
+            }
+
+            return codedInput;
+        }
+
+        /// <summary>
+        /// Merges the message from the input stream based on the contentType provided
+        /// </summary>
+        /// <typeparam name="TBuilder">A type derived from IBuilderLite</typeparam>
+        /// <param name="builder">An instance of a message builder</param>
+        /// <param name="options">Options specific to reading this message and/or content type</param>
+        /// <param name="contentType">The mime type of the input stream content</param>
+        /// <param name="input">The stream to read the message from</param>
+        /// <returns>The same builder instance that was supplied in the builder parameter</returns>
+        public static TBuilder MergeFrom<TBuilder>(this TBuilder builder, MessageFormatOptions options, string contentType, Stream input) where TBuilder : IBuilderLite
+        {
+            ICodedInputStream codedInput = CreateInputStream(options, contentType, input);
+            codedInput.ReadMessageStart();
+            builder.WeakMergeFrom(codedInput, options.ExtensionRegistry);
+            codedInput.ReadMessageEnd();
+            return builder;
+        }
+        
+        /// <summary>
+        /// Writes the message instance to the stream using the content type provided
+        /// </summary>
+        /// <param name="options">Options specific to writing this message and/or content type</param>
+        /// <param name="contentType">The mime type of the content to be written</param>
+        /// <param name="output">The stream to write the message to</param>
+        /// <remarks> If you do not dispose of ICodedOutputStream some formats may yield incomplete output </remarks>
+        public static ICodedOutputStream CreateOutputStream(MessageFormatOptions options, string contentType, Stream output)
+        {
+            ICodedOutputStream codedOutput = ContentTypeToOutputStream(contentType, options, output);
+
+            if (codedOutput is JsonFormatWriter)
+            {
+                JsonFormatWriter writer = (JsonFormatWriter)codedOutput;
+                if (options.FormattedOutput)
+                {
+                    writer.Formatted();
+                }
+            }
+            else if (codedOutput is XmlFormatWriter)
+            {
+                XmlFormatWriter writer = (XmlFormatWriter)codedOutput;
+                if (options.FormattedOutput)
+                {
+                    XmlWriterSettings settings = new XmlWriterSettings()
+                                                     {
+                                                         CheckCharacters = false,
+                                                         NewLineHandling = NewLineHandling.Entitize,
+                                                         OmitXmlDeclaration = true,
+                                                         Encoding = new UTF8Encoding(false),
+                                                         Indent = true,
+                                                         IndentChars = "    ",
+                                                         NewLineChars = Environment.NewLine,
+                                                     };
+                    // Don't know how else to change xml writer options?
+                    codedOutput = writer = XmlFormatWriter.CreateInstance(XmlWriter.Create(output, settings));
+                }
+                writer.RootElementName = options.XmlWriterRootElementName;
+                writer.Options = options.XmlWriterOptions;
+            }
+
+            return codedOutput;
+        }
+
+        /// <summary>
+        /// Writes the message instance to the stream using the content type provided
+        /// </summary>
+        /// <param name="message">An instance of a message</param>
+        /// <param name="options">Options specific to writing this message and/or content type</param>
+        /// <param name="contentType">The mime type of the content to be written</param>
+        /// <param name="output">The stream to write the message to</param>
+        public static void WriteTo(this IMessageLite message, MessageFormatOptions options, string contentType, Stream output)
+        {
+            ICodedOutputStream codedOutput = CreateOutputStream(options, contentType, output);
+
+            // Output the appropriate message preamble
+            codedOutput.WriteMessageStart();
+
+            // Write the message content to the output
+            message.WriteTo(codedOutput);
+
+            // Write the closing message fragment
+            codedOutput.WriteMessageEnd();
+            codedOutput.Flush();
+        }
+
+        private static ICodedInputStream ContentTypeToInputStream(string contentType, MessageFormatOptions options, Stream input)
+        {
+            contentType = (contentType ?? String.Empty).Split(';')[0].Trim();
+
+            Converter<Stream, ICodedInputStream> factory;
+            if(!options.MimeInputTypesReadOnly.TryGetValue(contentType, out factory) || factory == null)
+            {
+                if(String.IsNullOrEmpty(options.DefaultContentType) ||
+                    !options.MimeInputTypesReadOnly.TryGetValue(options.DefaultContentType, out factory) || factory == null)
+                {
+                    throw new ArgumentOutOfRangeException("contentType");
+                }
+            }
+
+            return factory(input);
+        }
+
+        private static ICodedOutputStream ContentTypeToOutputStream(string contentType, MessageFormatOptions options, Stream output)
+        {
+            contentType = (contentType ?? String.Empty).Split(';')[0].Trim();
+
+            Converter<Stream, ICodedOutputStream> factory;
+            if (!options.MimeOutputTypesReadOnly.TryGetValue(contentType, out factory) || factory == null)
+            {
+                if (String.IsNullOrEmpty(options.DefaultContentType) ||
+                    !options.MimeOutputTypesReadOnly.TryGetValue(options.DefaultContentType, out factory) || factory == null)
+                {
+                    throw new ArgumentOutOfRangeException("contentType");
+                }
+            }
+
+            return factory(output);
+        }
+
+    }
+}

+ 167 - 0
src/ProtocolBuffers.Serialization/Http/MessageFormatOptions.cs

@@ -0,0 +1,167 @@
+using System;
+using System.IO;
+using System.Collections.Generic;
+using Google.ProtocolBuffers.Collections;
+
+namespace Google.ProtocolBuffers.Serialization.Http
+{
+    /// <summary>
+    /// Defines control information for the various formatting used with HTTP services
+    /// </summary>
+    public class MessageFormatOptions
+    {
+        /// <summary>The mime type for xml content</summary>
+        /// <remarks>Other valid xml mime types include: application/binary, application/x-protobuf</remarks>
+        public const string ContentTypeProtoBuffer = "application/vnd.google.protobuf";
+
+        /// <summary>The mime type for xml content</summary>
+        /// <remarks>Other valid xml mime types include: text/xml</remarks>
+        public const string ContentTypeXml = "application/xml";
+        
+        /// <summary>The mime type for json content</summary>
+        /// <remarks>
+        /// Other valid json mime types include: application/json, application/x-json, 
+        /// application/x-javascript, text/javascript, text/x-javascript, text/x-json, text/json
+        /// </remarks>
+        public const string ContentTypeJson = "application/json";
+
+        /// <summary>The mime type for query strings and x-www-form-urlencoded content</summary>
+        /// <remarks>This mime type is input-only</remarks>
+        public const string ContentFormUrlEncoded = "application/x-www-form-urlencoded";
+
+        /// <summary>
+        /// Default mime-type handling for input
+        /// </summary>
+        private static readonly IDictionary<string, Converter<Stream, ICodedInputStream>> MimeInputDefaults =
+            new ReadOnlyDictionary<string, Converter<Stream, ICodedInputStream>>(
+            new Dictionary<string, Converter<Stream, ICodedInputStream>>(StringComparer.OrdinalIgnoreCase)
+                {
+                    {"application/json", JsonFormatReader.CreateInstance},
+                    {"application/x-json", JsonFormatReader.CreateInstance},
+                    {"application/x-javascript", JsonFormatReader.CreateInstance},
+                    {"text/javascript", JsonFormatReader.CreateInstance},
+                    {"text/x-javascript", JsonFormatReader.CreateInstance},
+                    {"text/x-json", JsonFormatReader.CreateInstance},
+                    {"text/json", JsonFormatReader.CreateInstance},
+                    {"text/xml", XmlFormatReader.CreateInstance},
+                    {"application/xml", XmlFormatReader.CreateInstance},
+                    {"application/binary", CodedInputStream.CreateInstance},
+                    {"application/x-protobuf", CodedInputStream.CreateInstance},
+                    {"application/vnd.google.protobuf", CodedInputStream.CreateInstance},
+                    {"application/x-www-form-urlencoded", FormUrlEncodedReader.CreateInstance},
+                }
+            );
+
+        /// <summary>
+        /// Default mime-type handling for output
+        /// </summary>
+        private static readonly IDictionary<string, Converter<Stream, ICodedOutputStream>> MimeOutputDefaults =
+            new ReadOnlyDictionary<string, Converter<Stream, ICodedOutputStream>>(
+            new Dictionary<string, Converter<Stream, ICodedOutputStream>>(StringComparer.OrdinalIgnoreCase)
+                {
+                    {"application/json", JsonFormatWriter.CreateInstance},
+                    {"application/x-json", JsonFormatWriter.CreateInstance},
+                    {"application/x-javascript", JsonFormatWriter.CreateInstance},
+                    {"text/javascript", JsonFormatWriter.CreateInstance},
+                    {"text/x-javascript", JsonFormatWriter.CreateInstance},
+                    {"text/x-json", JsonFormatWriter.CreateInstance},
+                    {"text/json", JsonFormatWriter.CreateInstance},
+                    {"text/xml", XmlFormatWriter.CreateInstance},
+                    {"application/xml", XmlFormatWriter.CreateInstance},
+                    {"application/binary", CodedOutputStream.CreateInstance},
+                    {"application/x-protobuf", CodedOutputStream.CreateInstance},
+                    {"application/vnd.google.protobuf", CodedOutputStream.CreateInstance},
+                }
+            );
+
+
+
+
+        private string _defaultContentType;
+        private string _xmlReaderRootElementName;
+        private string _xmlWriterRootElementName;
+        private ExtensionRegistry _extensionRegistry;
+        private Dictionary<string, Converter<Stream, ICodedInputStream>> _mimeInputTypes;
+        private Dictionary<string, Converter<Stream, ICodedOutputStream>> _mimeOutputTypes;
+
+        /// <summary> Provides access to modify the mime-type input stream construction </summary>
+        public IDictionary<string, Converter<Stream, ICodedInputStream>> MimeInputTypes
+        {
+            get
+            {
+                return _mimeInputTypes ??
+                    (_mimeInputTypes = new Dictionary<string, Converter<Stream, ICodedInputStream>>(
+                                           MimeInputDefaults, StringComparer.OrdinalIgnoreCase));
+            }
+        }
+
+        /// <summary> Provides access to modify the mime-type input stream construction </summary>
+        public IDictionary<string, Converter<Stream, ICodedOutputStream>> MimeOutputTypes
+        {
+            get
+            {
+                return _mimeOutputTypes ??
+                    (_mimeOutputTypes = new Dictionary<string, Converter<Stream, ICodedOutputStream>>(
+                                           MimeOutputDefaults, StringComparer.OrdinalIgnoreCase));
+            }
+        }
+
+        internal IDictionary<string, Converter<Stream, ICodedInputStream>> MimeInputTypesReadOnly
+        { get { return _mimeInputTypes ?? MimeInputDefaults; } }
+
+        internal IDictionary<string, Converter<Stream, ICodedOutputStream>> MimeOutputTypesReadOnly
+        { get { return _mimeOutputTypes ?? MimeOutputDefaults; } }
+
+        /// <summary>
+        /// The default content type to use if the input type is null or empty.  If this
+        /// value is not supplied an ArgumentOutOfRangeException exception will be raised.
+        /// </summary>
+        public string DefaultContentType
+        {
+            get { return _defaultContentType ?? String.Empty; }
+            set { _defaultContentType = value; }
+        }
+
+        /// <summary>
+        /// The extension registry to use when reading messages
+        /// </summary>
+        public ExtensionRegistry ExtensionRegistry
+        {
+            get { return _extensionRegistry ?? ExtensionRegistry.Empty; }
+            set { _extensionRegistry = value; }
+        }
+
+        /// <summary>
+        /// The name of the xml root element when reading messages
+        /// </summary>
+        public string XmlReaderRootElementName
+        {
+            get { return _xmlReaderRootElementName ?? XmlFormatReader.DefaultRootElementName; }
+            set { _xmlReaderRootElementName = value; }
+        }
+
+        /// <summary>
+        /// Xml reader options
+        /// </summary>
+        public XmlReaderOptions XmlReaderOptions { get; set; }
+
+        /// <summary>
+        /// True to use formatted output including new-lines and default indentation
+        /// </summary>
+        public bool FormattedOutput { get; set; }
+
+        /// <summary>
+        /// The name of the xml root element when writing messages
+        /// </summary>
+        public string XmlWriterRootElementName
+        {
+            get { return _xmlWriterRootElementName ?? XmlFormatWriter.DefaultRootElementName; }
+            set { _xmlWriterRootElementName = value; }
+        }
+
+        /// <summary>
+        /// Xml writer options
+        /// </summary>
+        public XmlWriterOptions XmlWriterOptions { get; set; }
+    }
+}

+ 34 - 0
src/ProtocolBuffers.Serialization/Http/ServiceExtensions.cs

@@ -0,0 +1,34 @@
+using System.Collections.Generic;
+using System.Text;
+using Google.ProtocolBuffers;
+using System.IO;
+
+namespace Google.ProtocolBuffers.Serialization.Http
+{
+    /// <summary>
+    /// Extensions for the IRpcServerStub
+    /// </summary>
+    public static class ServiceExtensions
+    {
+        /// <summary>
+        /// Used to implement a service endpoint on an HTTP server.  This works with services generated with the
+        /// service_generator_type option set to IRPCDISPATCH.
+        /// </summary>
+        /// <param name="stub">The service execution stub</param>
+        /// <param name="methodName">The name of the method being invoked</param>
+        /// <param name="options">optional arguments for the format reader/writer</param>
+        /// <param name="contentType">The mime type for the input stream</param>
+        /// <param name="input">The input stream</param>
+        /// <param name="responseType">The mime type for the output stream</param>
+        /// <param name="output">The output stream</param>
+        public static void HttpCallMethod(this IRpcServerStub stub, string methodName, MessageFormatOptions options, 
+            string contentType, Stream input, string responseType, Stream output)
+        {
+            ICodedInputStream codedInput = MessageFormatFactory.CreateInputStream(options, contentType, input);
+            codedInput.ReadMessageStart();
+            IMessageLite response = stub.CallMethod(methodName, codedInput, options.ExtensionRegistry);
+            codedInput.ReadMessageEnd();
+            response.WriteTo(options, responseType, output);
+        }
+    }
+}

+ 22 - 5
src/ProtocolBuffers.Serialization/JsonFormatReader.cs

@@ -1,4 +1,4 @@
-using System;
+using System;
 using System.Collections.Generic;
 using System.IO;
 using System.Xml;
@@ -11,6 +11,7 @@ namespace Google.ProtocolBuffers.Serialization
     public class JsonFormatReader : AbstractTextReader
     {
         private readonly JsonCursor _input;
+        // The expected token that ends the current item, either ']' or '}'
         private readonly Stack<int> _stopChar;
 
         private enum ReaderState
@@ -101,17 +102,33 @@ namespace Google.ProtocolBuffers.Serialization
         }
 
         /// <summary>
-        /// Merges the contents of stream into the provided message builder
+        /// Reads the root-message preamble specific to this formatter
         /// </summary>
-        public override TBuilder Merge<TBuilder>(TBuilder builder, ExtensionRegistry registry)
+        public override void ReadMessageStart()
         {
             _input.Consume('{');
             _stopChar.Push('}');
 
             _state = ReaderState.BeginObject;
-            builder.WeakMergeFrom(this, registry);
-            _input.Consume((char) _stopChar.Pop());
+        }
+
+        /// <summary>
+        /// Reads the root-message close specific to this formatter
+        /// </summary>
+        public override void ReadMessageEnd()
+        {
+            _input.Consume((char)_stopChar.Pop());
             _state = ReaderState.EndValue;
+        }
+
+        /// <summary>
+        /// Merges the contents of stream into the provided message builder
+        /// </summary>
+        public override TBuilder Merge<TBuilder>(TBuilder builder, ExtensionRegistry registry)
+        {
+            ReadMessageStart();
+            builder.WeakMergeFrom(this, registry);
+            ReadMessageEnd();
             return builder;
         }
 

+ 24 - 4
src/ProtocolBuffers.Serialization/JsonFormatWriter.cs

@@ -1,4 +1,4 @@
-using System;
+using System;
 using System.Collections;
 using System.Collections.Generic;
 using System.IO;
@@ -101,7 +101,7 @@ namespace Google.ProtocolBuffers.Serialization
         private class JsonStreamWriter : JsonFormatWriter
         {
 #if SILVERLIGHT2 || COMPACT_FRAMEWORK_35
-            static readonly Encoding Encoding = Encoding.UTF8;
+            static readonly Encoding Encoding = new UTF8Encoding(false);
 #else
             private static readonly Encoding Encoding = Encoding.ASCII;
 #endif
@@ -168,7 +168,9 @@ namespace Google.ProtocolBuffers.Serialization
 
         #endregion
 
+        //Tracks the writer depth and the array element count at that depth.
         private readonly List<int> _counter;
+        //True if the top-level of the writer is an array as opposed to a single message.
         private bool _isArray;
 
         /// <summary>
@@ -243,7 +245,7 @@ namespace Google.ProtocolBuffers.Serialization
         {
             if (_counter.Count == 0)
             {
-                throw new InvalidOperationException("Missmatched open/close in Json writer.");
+                throw new InvalidOperationException("Mismatched open/close in Json writer.");
             }
 
             int index = _counter.Count - 1;
@@ -444,6 +446,18 @@ namespace Google.ProtocolBuffers.Serialization
         /// Writes the message to the the formatted stream.
         /// </summary>
         public override void WriteMessage(IMessageLite message)
+        {
+            WriteMessageStart();
+            message.WriteTo(this);
+            WriteMessageEnd();
+        }
+
+        /// <summary>
+        /// Used to write the root-message preamble, in json this is the left-curly brace '{'.
+        /// After this call you can call IMessageLite.MergeTo(...) and complete the message with
+        /// a call to WriteMessageEnd().
+        /// </summary>
+        public override void WriteMessageStart()
         {
             if (_isArray)
             {
@@ -451,7 +465,13 @@ namespace Google.ProtocolBuffers.Serialization
             }
             WriteToOutput("{");
             _counter.Add(0);
-            message.WriteTo(this);
+        }
+
+        /// <summary>
+        /// Used to complete a root-message previously started with a call to WriteMessageStart()
+        /// </summary>
+        public override void WriteMessageEnd()
+        {
             _counter.RemoveAt(_counter.Count - 1);
             WriteLine("}");
             Flush();

+ 4 - 0
src/ProtocolBuffers.Serialization/ProtocolBuffers.Serialization.csproj

@@ -98,6 +98,10 @@
   </ItemGroup>
   <ItemGroup>
     <Compile Include="Extensions.cs" />
+    <Compile Include="Http\FormUrlEncodedReader.cs" />
+    <Compile Include="Http\MessageFormatFactory.cs" />
+    <Compile Include="Http\MessageFormatOptions.cs" />
+    <Compile Include="Http\ServiceExtensions.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
     <Compile Include="AbstractReader.cs" />
     <Compile Include="AbstractTextReader.cs" />

+ 92 - 53
src/ProtocolBuffers.Serialization/XmlFormatReader.cs

@@ -1,7 +1,8 @@
-using System;
+using System;
 using System.Collections.Generic;
 using System.IO;
 using System.Xml;
+using System.Diagnostics;
 
 namespace Google.ProtocolBuffers.Serialization
 {
@@ -14,8 +15,25 @@ namespace Google.ProtocolBuffers.Serialization
     {
         public const string DefaultRootElementName = XmlFormatWriter.DefaultRootElementName;
         private readonly XmlReader _input;
+        // Tracks the message element for each nested message read
+        private readonly Stack<ElementStackEntry> _elements;
+        // The default element name for ReadMessageStart
         private string _rootElementName;
 
+        private struct ElementStackEntry
+        {
+            public readonly string LocalName;
+            public readonly int Depth;
+            public readonly bool IsEmpty;
+
+            public ElementStackEntry(string localName, int depth, bool isEmpty) : this()
+            {
+                LocalName = localName;
+                IsEmpty = isEmpty;
+                Depth = depth;
+            }
+        }
+
         private static XmlReaderSettings DefaultSettings
         {
             get
@@ -72,20 +90,10 @@ namespace Google.ProtocolBuffers.Serialization
         {
             _input = input;
             _rootElementName = DefaultRootElementName;
+            _elements = new Stack<ElementStackEntry>();
             Options = XmlReaderOptions.None;
         }
 
-        /// <summary>
-        /// Constructs the XmlFormatReader with the XmlReader and options
-        /// </summary>
-        protected XmlFormatReader(XmlFormatReader copyFrom, XmlReader input)
-            : base(copyFrom)
-        {
-            _input = input;
-            _rootElementName = copyFrom._rootElementName;
-            Options = copyFrom.Options;
-        }
-
         /// <summary>
         /// Gets or sets the options to use when reading the xml
         /// </summary>
@@ -113,26 +121,61 @@ namespace Google.ProtocolBuffers.Serialization
             }
         }
 
-        private XmlFormatReader CloneWith(XmlReader rdr)
+        [DebuggerNonUserCode]
+        private static void Assert(bool cond)
         {
-            XmlFormatReader copy = new XmlFormatReader(this, rdr);
-            return copy;
+            if (!cond)
+            {
+                throw new FormatException();
+            }
         }
 
-        private void NextElement()
+        /// <summary>
+        /// Reads the root-message preamble specific to this formatter
+        /// </summary>
+        public override void ReadMessageStart()
+        {
+            ReadMessageStart(_rootElementName);
+        }
+
+        /// <summary>
+        /// Reads the root-message preamble specific to this formatter
+        /// </summary>
+        public void ReadMessageStart(string element)
         {
             while (!_input.IsStartElement() && _input.Read())
             {
                 continue;
             }
+            Assert(_input.IsStartElement() && _input.LocalName == element);
+            _elements.Push(new ElementStackEntry(element, _input.Depth, _input.IsEmptyElement));
+            _input.Read();
         }
 
-        private static void Assert(bool cond)
+        /// <summary>
+        /// Reads the root-message close specific to this formatter, MUST be called
+        /// on the reader obtained from ReadMessageStart(string element).
+        /// </summary>
+        public override void ReadMessageEnd()
         {
-            if (!cond)
+            Assert(_elements.Count > 0);
+
+            ElementStackEntry stop = _elements.Peek();
+            while (_input.NodeType != XmlNodeType.EndElement && _input.NodeType != XmlNodeType.Element
+                   && _input.Depth > stop.Depth && _input.Read())
             {
-                throw new FormatException();
+                continue;
+            }
+
+            if (!stop.IsEmpty)
+            {
+                Assert(_input.NodeType == XmlNodeType.EndElement
+                       && _input.LocalName == stop.LocalName
+                       && _input.Depth == stop.Depth);
+
+                _input.Read();
             }
+            _elements.Pop();
         }
 
         /// <summary>
@@ -157,9 +200,9 @@ namespace Google.ProtocolBuffers.Serialization
         public TBuilder Merge<TBuilder>(string element, TBuilder builder, ExtensionRegistry registry)
             where TBuilder : IBuilderLite
         {
-            string field;
-            Assert(PeekNext(out field) && field == element);
-            ReadMessage(builder, registry);
+            ReadMessageStart(element);
+            builder.WeakMergeFrom(this, registry);
+            ReadMessageEnd();
             return builder;
         }
 
@@ -172,7 +215,21 @@ namespace Google.ProtocolBuffers.Serialization
         /// </remarks>
         protected override bool PeekNext(out string field)
         {
-            NextElement();
+            ElementStackEntry stopNode;
+            if (_elements.Count == 0)
+            {
+                stopNode = new ElementStackEntry(null, _input.Depth - 1, false);
+            }
+            else
+            {
+                stopNode = _elements.Peek();
+            }
+
+            while (!_input.IsStartElement() && _input.Depth > stopNode.Depth && _input.Read())
+            {
+                continue;
+            }
+
             if (_input.IsStartElement())
             {
                 field = _input.LocalName;
@@ -235,20 +292,9 @@ namespace Google.ProtocolBuffers.Serialization
         protected override bool ReadMessage(IBuilderLite builder, ExtensionRegistry registry)
         {
             Assert(_input.IsStartElement());
-
-            if (!_input.IsEmptyElement)
-            {
-                int depth = _input.Depth;
-                XmlReader child = _input.ReadSubtree();
-                while (!child.IsStartElement() && child.Read())
-                {
-                    continue;
-                }
-                child.Read();
-                builder.WeakMergeFrom(CloneWith(child), registry);
-                Assert(depth == _input.Depth && _input.NodeType == XmlNodeType.EndElement);
-            }
-            _input.Read();
+            ReadMessageStart(_input.LocalName);
+            builder.WeakMergeFrom(this, registry);
+            ReadMessageEnd();
             return true;
         }
 
@@ -270,27 +316,20 @@ namespace Google.ProtocolBuffers.Serialization
                 {
                     yield return item;
                 }
-                yield break;
             }
-            if (!_input.IsEmptyElement)
+            else
             {
-                int depth = _input.Depth;
-                XmlReader child = _input.ReadSubtree();
-
-                while (!child.IsStartElement() && child.Read())
+                string found;
+                ReadMessageStart(field);
+                if (PeekNext(out found) && found == "item")
                 {
-                    continue;
-                }
-                child.Read();
-
-                foreach (string item in CloneWith(child).NonNestedArrayItems("item"))
-                {
-                    yield return item;
+                    foreach (string item in NonNestedArrayItems("item"))
+                    {
+                        yield return item;
+                    }
                 }
-                Assert(depth == _input.Depth && _input.NodeType == XmlNodeType.EndElement);
+                ReadMessageEnd();
             }
-            _input.Read();
-            yield break;
         }
     }
 }

+ 57 - 20
src/ProtocolBuffers.Serialization/XmlFormatWriter.cs

@@ -1,4 +1,4 @@
-using System;
+using System;
 using System.Collections;
 using System.IO;
 using System.Text;
@@ -14,10 +14,14 @@ namespace Google.ProtocolBuffers.Serialization
     /// </summary>
     public class XmlFormatWriter : AbstractTextWriter
     {
+        private static readonly Encoding DefaultEncoding = new UTF8Encoding(false);
         public const string DefaultRootElementName = "root";
-        private const int NestedArrayFlag = 0x0001;
+
         private readonly XmlWriter _output;
+        // The default element name used for WriteMessageStart
         private string _rootElementName;
+        // Used to assert matching WriteMessageStart/WriteMessageEnd calls
+        private int _messageOpenCount;
 
         private static XmlWriterSettings DefaultSettings(Encoding encoding)
         {
@@ -43,7 +47,7 @@ namespace Google.ProtocolBuffers.Serialization
         /// </summary>
         public static XmlFormatWriter CreateInstance(Stream output)
         {
-            return new XmlFormatWriter(XmlWriter.Create(output, DefaultSettings(Encoding.UTF8)));
+            return new XmlFormatWriter(XmlWriter.Create(output, DefaultSettings(DefaultEncoding)));
         }
 
         /// <summary>
@@ -65,20 +69,10 @@ namespace Google.ProtocolBuffers.Serialization
         protected XmlFormatWriter(XmlWriter output)
         {
             _output = output;
+            _messageOpenCount = 0;
             _rootElementName = DefaultRootElementName;
         }
 
-        /// <summary>
-        /// Closes the underlying XmlTextWriter
-        /// </summary>
-        protected override void Dispose(bool disposing)
-        {
-            if (disposing)
-            {
-                _output.Close();
-            }
-        }
-
         /// <summary>
         /// Gets or sets the default element name to use when using the Merge&lt;TBuilder>()
         /// </summary>
@@ -112,17 +106,30 @@ namespace Google.ProtocolBuffers.Serialization
         }
 
         /// <summary>
-        /// Writes a message as an element using the name defined in <see cref="RootElementName"/>
+        /// Completes any pending write operations
         /// </summary>
-        public override void WriteMessage(IMessageLite message)
+        public override void Flush()
         {
-            WriteMessage(_rootElementName, message);
+            _output.Flush();
+            base.Flush();
         }
 
         /// <summary>
-        /// Writes a message as an element with the given name
+        /// Used to write the root-message preamble, in xml this is open element for RootElementName,
+        /// by default "&lt;root&gt;". After this call you can call IMessageLite.MergeTo(...) and 
+        /// complete the message with a call to WriteMessageEnd().
         /// </summary>
-        public void WriteMessage(string elementName, IMessageLite message)
+        public override void WriteMessageStart()
+        {
+            WriteMessageStart(_rootElementName);
+        }
+
+        /// <summary>
+        /// Used to write the root-message preamble, in xml this is open element for elementName. 
+        /// After this call you can call IMessageLite.MergeTo(...) and  complete the message with 
+        /// a call to WriteMessageEnd().
+        /// </summary>
+        public void WriteMessageStart(string elementName)
         {
             if (TestOption(XmlWriterOptions.OutputJsonTypes))
             {
@@ -133,10 +140,40 @@ namespace Google.ProtocolBuffers.Serialization
             {
                 _output.WriteStartElement(elementName);
             }
+            _messageOpenCount++;
+        }
+
+        /// <summary>
+        /// Used to complete a root-message previously started with a call to WriteMessageStart()
+        /// </summary>
+        public override void WriteMessageEnd()
+        {
+            if (_messageOpenCount <= 0)
+            {
+                throw new InvalidOperationException();
+            }
 
-            message.WriteTo(this);
             _output.WriteEndElement();
             _output.Flush();
+            _messageOpenCount--;
+        }
+
+        /// <summary>
+        /// Writes a message as an element using the name defined in <see cref="RootElementName"/>
+        /// </summary>
+        public override void WriteMessage(IMessageLite message)
+        {
+            WriteMessage(_rootElementName, message);
+        }
+
+        /// <summary>
+        /// Writes a message as an element with the given name
+        /// </summary>
+        public void WriteMessage(string elementName, IMessageLite message)
+        {
+            WriteMessageStart(elementName);
+            message.WriteTo(this);
+            WriteMessageEnd();
         }
 
         /// <summary>

+ 18 - 0
src/ProtocolBuffers.Test/Compatibility/JsonCompatibilityTests.cs

@@ -7,6 +7,24 @@ namespace Google.ProtocolBuffers.Compatibility
 {
     [TestFixture]
     public class JsonCompatibilityTests : CompatibilityTests
+    {
+        protected override object SerializeMessage<TMessage, TBuilder>(TMessage message)
+        {
+            StringWriter sw = new StringWriter();
+            JsonFormatWriter.CreateInstance(sw)
+                .WriteMessage(message);
+            return sw.ToString();
+        }
+
+        protected override TBuilder DeserializeMessage<TMessage, TBuilder>(object message, TBuilder builder, ExtensionRegistry registry)
+        {
+            JsonFormatReader.CreateInstance((string)message).Merge(builder);
+            return builder;
+        }
+    }
+
+    [TestFixture]
+    public class JsonCompatibilityFormattedTests : CompatibilityTests
     {
         protected override object SerializeMessage<TMessage, TBuilder>(TMessage message)
         {

+ 21 - 0
src/ProtocolBuffers.Test/Compatibility/XmlCompatibilityTests.cs

@@ -1,4 +1,5 @@
 using System.IO;
+using System.Xml;
 using Google.ProtocolBuffers.Serialization;
 using Google.ProtocolBuffers.TestProtos;
 using NUnit.Framework;
@@ -22,4 +23,24 @@ namespace Google.ProtocolBuffers.Compatibility
             return reader.Merge("root", builder, registry);
         }
     }
+
+    [TestFixture]
+    public class XmlCompatibilityFormattedTests : CompatibilityTests
+    {
+        protected override object SerializeMessage<TMessage, TBuilder>(TMessage message)
+        {
+            StringWriter text = new StringWriter();
+            XmlWriter xwtr = XmlWriter.Create(text, new XmlWriterSettings { Indent = true, IndentChars = "  " });
+
+            XmlFormatWriter writer = XmlFormatWriter.CreateInstance(xwtr).SetOptions(XmlWriterOptions.OutputNestedArrays);
+            writer.WriteMessage("root", message);
+            return text.ToString();
+        }
+
+        protected override TBuilder DeserializeMessage<TMessage, TBuilder>(object message, TBuilder builder, ExtensionRegistry registry)
+        {
+            XmlFormatReader reader = XmlFormatReader.CreateInstance((string)message).SetOptions(XmlReaderOptions.ReadNestedArrays);
+            return reader.Merge("root", builder, registry);
+        }
+    }
 }

+ 1 - 1
src/ProtocolBuffers.Test/Properties/AssemblyInfo.cs

@@ -12,7 +12,7 @@ using System.Runtime.InteropServices;
 [assembly: AssemblyConfiguration("")]
 [assembly: AssemblyCompany("")]
 [assembly: AssemblyProduct("ProtocolBuffers.Test")]
-[assembly: AssemblyCopyright("Copyright ©  2008")]
+[assembly: AssemblyCopyright("Copyright �  2008")]
 [assembly: AssemblyTrademark("")]
 [assembly: AssemblyCulture("")]
 

+ 3 - 0
src/ProtocolBuffers.Test/ProtocolBuffers.Test.csproj

@@ -88,6 +88,8 @@
     </Compile>
     <Compile Include="Compatibility\TextCompatibilityTests.cs" />
     <Compile Include="Compatibility\XmlCompatibilityTests.cs" />
+    <Compile Include="TestRpcForMimeTypes.cs" />
+    <Compile Include="TestReaderForUrlEncoded.cs" />
     <Compile Include="CSharpOptionsTest.cs" />
     <Compile Include="DeprecatedMemberTest.cs" />
     <Compile Include="DescriptorsTest.cs" />
@@ -107,6 +109,7 @@
     <Compile Include="SerializableTest.cs" />
     <Compile Include="ServiceTest.cs" />
     <Compile Include="TestCornerCases.cs" />
+    <Compile Include="TestMimeMessageFormats.cs" />
     <Compile Include="TestProtos\UnitTestCSharpOptionsProtoFile.cs" />
     <Compile Include="TestProtos\UnitTestCustomOptionsProtoFile.cs" />
     <Compile Include="TestProtos\UnitTestEmbedOptimizeForProtoFile.cs" />

+ 264 - 0
src/ProtocolBuffers.Test/TestMimeMessageFormats.cs

@@ -0,0 +1,264 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using Google.ProtocolBuffers.Serialization;
+using Google.ProtocolBuffers.Serialization.Http;
+using Google.ProtocolBuffers.TestProtos;
+using NUnit.Framework;
+
+namespace Google.ProtocolBuffers
+{
+    [TestFixture]
+    public class TestMimeMessageFormats
+    {
+        // There is a whole host of various json mime types in use around the net, this is the set we accept...
+        readonly IEnumerable<string> JsonTypes = new string[] { "application/json", "application/x-json", "application/x-javascript", "text/javascript", "text/x-javascript", "text/x-json", "text/json" };
+        readonly IEnumerable<string> XmlTypes = new string[] { "text/xml", "application/xml" };
+        readonly IEnumerable<string> ProtobufTypes = new string[] { "application/binary", "application/x-protobuf", "application/vnd.google.protobuf" };
+
+        [Test]
+        public void TestReadJsonMimeTypes()
+        {
+            foreach (string type in JsonTypes)
+            {
+                Assert.IsTrue(
+                    MessageFormatFactory.CreateInputStream(new MessageFormatOptions(), type, Stream.Null)
+                    is JsonFormatReader);
+            }
+            Assert.IsTrue(
+                MessageFormatFactory.CreateInputStream(new MessageFormatOptions() { DefaultContentType = "application/json" }, null, Stream.Null)
+                is JsonFormatReader);
+        }
+        [Test]
+        public void TestWriteJsonMimeTypes()
+        {
+            foreach (string type in JsonTypes)
+            {
+                Assert.IsTrue(
+                    MessageFormatFactory.CreateOutputStream(new MessageFormatOptions(), type, Stream.Null)
+                    is JsonFormatWriter);
+            }
+            Assert.IsTrue(
+                MessageFormatFactory.CreateOutputStream(new MessageFormatOptions() { DefaultContentType = "application/json" }, null, Stream.Null)
+                is JsonFormatWriter);
+        }
+        [Test]
+        public void TestReadXmlMimeTypes()
+        {
+            foreach (string type in XmlTypes)
+            {
+                Assert.IsTrue(
+                    MessageFormatFactory.CreateInputStream(new MessageFormatOptions(), type, Stream.Null)
+                    is XmlFormatReader);
+            }
+            Assert.IsTrue(
+                MessageFormatFactory.CreateInputStream(new MessageFormatOptions() { DefaultContentType = "application/xml" }, null, Stream.Null)
+                is XmlFormatReader);
+        }
+        [Test]
+        public void TestWriteXmlMimeTypes()
+        {
+            foreach (string type in XmlTypes)
+            {
+                Assert.IsTrue(
+                    MessageFormatFactory.CreateOutputStream(new MessageFormatOptions(), type, Stream.Null)
+                    is XmlFormatWriter);
+            }
+            Assert.IsTrue(
+                MessageFormatFactory.CreateOutputStream(new MessageFormatOptions() { DefaultContentType = "application/xml" }, null, Stream.Null)
+                is XmlFormatWriter);
+        }
+        [Test]
+        public void TestReadProtoMimeTypes()
+        {
+            foreach (string type in ProtobufTypes)
+            {
+                Assert.IsTrue(
+                    MessageFormatFactory.CreateInputStream(new MessageFormatOptions(), type, Stream.Null)
+                    is CodedInputStream);
+            }
+            Assert.IsTrue(
+                MessageFormatFactory.CreateInputStream(new MessageFormatOptions() { DefaultContentType = "application/vnd.google.protobuf" }, null, Stream.Null)
+                is CodedInputStream);
+        }
+        [Test]
+        public void TestWriteProtoMimeTypes()
+        {
+            foreach (string type in ProtobufTypes)
+            {
+                Assert.IsTrue(
+                    MessageFormatFactory.CreateOutputStream(new MessageFormatOptions(), type, Stream.Null)
+                    is CodedOutputStream);
+            }
+            Assert.IsTrue(
+                MessageFormatFactory.CreateOutputStream(new MessageFormatOptions() { DefaultContentType = "application/vnd.google.protobuf" }, null, Stream.Null)
+                is CodedOutputStream);
+        }
+        [Test]
+        public void TestMergeFromJsonType()
+        {
+            TestXmlMessage msg = new TestXmlMessage.Builder().MergeFrom(
+                new MessageFormatOptions(), "application/json", new MemoryStream(Encoding.ASCII.GetBytes(
+                    TestXmlMessage.CreateBuilder().SetText("a").SetNumber(1).Build().ToJson()
+                    )))
+                .Build();
+            Assert.AreEqual("a", msg.Text);
+            Assert.AreEqual(1, msg.Number);
+        }
+        [Test]
+        public void TestMergeFromXmlType()
+        {
+            TestXmlMessage msg = new TestXmlMessage.Builder().MergeFrom(
+                new MessageFormatOptions(), "application/xml", new MemoryStream(Encoding.ASCII.GetBytes(
+                    TestXmlMessage.CreateBuilder().SetText("a").SetNumber(1).Build().ToXml()
+                    )))
+                .Build();
+            Assert.AreEqual("a", msg.Text);
+            Assert.AreEqual(1, msg.Number);
+        }
+        [Test]
+        public void TestMergeFromProtoType()
+        {
+            TestXmlMessage msg = new TestXmlMessage.Builder().MergeFrom(
+                new MessageFormatOptions(), "application/vnd.google.protobuf", new MemoryStream(
+                    TestXmlMessage.CreateBuilder().SetText("a").SetNumber(1).Build().ToByteArray()
+                    ))
+                .Build();
+            Assert.AreEqual("a", msg.Text);
+            Assert.AreEqual(1, msg.Number);
+        }
+        [Test]
+        public void TestWriteToJsonType()
+        {
+            MemoryStream ms = new MemoryStream();
+            TestXmlMessage.CreateBuilder().SetText("a").SetNumber(1).Build()
+                .WriteTo(new MessageFormatOptions(), "application/json", ms);
+
+            Assert.AreEqual(@"{""text"":""a"",""number"":1}", Encoding.UTF8.GetString(ms.ToArray()));
+        }
+        [Test]
+        public void TestWriteToXmlType()
+        {
+            MemoryStream ms = new MemoryStream();
+            TestXmlMessage.CreateBuilder().SetText("a").SetNumber(1).Build()
+                .WriteTo(new MessageFormatOptions(), "application/xml", ms);
+
+            Assert.AreEqual("<root><text>a</text><number>1</number></root>", Encoding.UTF8.GetString(ms.ToArray()));
+        }
+        [Test]
+        public void TestWriteToProtoType()
+        {
+            MemoryStream ms = new MemoryStream();
+            TestXmlMessage.CreateBuilder().SetText("a").SetNumber(1).Build()
+                .WriteTo(new MessageFormatOptions(), "application/vnd.google.protobuf", ms);
+
+            byte[] bytes = TestXmlMessage.CreateBuilder().SetText("a").SetNumber(1).Build().ToByteArray();
+            Assert.AreEqual(bytes, ms.ToArray());
+        }
+        [Test]
+        public void TestXmlReaderOptions()
+        {
+            MemoryStream ms = new MemoryStream();
+            XmlFormatWriter.CreateInstance(ms)
+                .SetOptions(XmlWriterOptions.OutputNestedArrays)
+                .WriteMessage("my-root-node", TestXmlMessage.CreateBuilder().SetText("a").AddNumbers(1).AddNumbers(2).Build());
+            ms.Position = 0;
+
+            MessageFormatOptions options = new MessageFormatOptions()
+            {
+                XmlReaderOptions = XmlReaderOptions.ReadNestedArrays,
+                XmlReaderRootElementName = "my-root-node"
+            };
+
+            TestXmlMessage msg = new TestXmlMessage.Builder().MergeFrom(
+                options, "application/xml", ms)
+                .Build();
+
+            Assert.AreEqual("a", msg.Text);
+            Assert.AreEqual(1, msg.NumbersList[0]);
+            Assert.AreEqual(2, msg.NumbersList[1]);
+
+        }
+        [Test]
+        public void TestXmlWriterOptions()
+        {
+            TestXmlMessage message = TestXmlMessage.CreateBuilder().SetText("a").AddNumbers(1).AddNumbers(2).Build();
+            MessageFormatOptions options = new MessageFormatOptions()
+            {
+                XmlWriterOptions = XmlWriterOptions.OutputNestedArrays,
+                XmlWriterRootElementName = "root-node"
+            };
+
+            MemoryStream ms = new MemoryStream();
+            message.WriteTo(options, "application/xml", ms);
+            ms.Position = 0;
+            
+            TestXmlMessage.Builder builder = TestXmlMessage.CreateBuilder();
+            XmlFormatReader.CreateInstance(ms)
+                .SetOptions(XmlReaderOptions.ReadNestedArrays)
+                .Merge("root-node", builder);
+
+            Assert.AreEqual("a", builder.Text);
+            Assert.AreEqual(1, builder.NumbersList[0]);
+            Assert.AreEqual(2, builder.NumbersList[1]);
+        }
+        [Test]
+        public void TestJsonFormatted()
+        {
+            MemoryStream ms = new MemoryStream();
+            TestXmlMessage.CreateBuilder().SetText("a").SetNumber(1).Build()
+                .WriteTo(new MessageFormatOptions() { FormattedOutput = true }, "application/json", ms);
+
+            Assert.AreEqual("{\r\n    \"text\": \"a\",\r\n    \"number\": 1\r\n}", Encoding.UTF8.GetString(ms.ToArray()));
+        }
+        [Test]
+        public void TestXmlFormatted()
+        {
+            MemoryStream ms = new MemoryStream();
+            TestXmlMessage.CreateBuilder().SetText("a").SetNumber(1).Build()
+                .WriteTo(new MessageFormatOptions() { FormattedOutput = true }, "application/xml", ms);
+
+            Assert.AreEqual("<root>\r\n    <text>a</text>\r\n    <number>1</number>\r\n</root>", Encoding.UTF8.GetString(ms.ToArray()));
+        }
+
+        [Test]
+        public void TestReadCustomMimeTypes()
+        {
+            var options = new MessageFormatOptions();
+            //Remove existing mime-type mappings
+            options.MimeInputTypes.Clear();
+            //Add our own
+            options.MimeInputTypes.Add("-custom-XML-mime-type-", XmlFormatReader.CreateInstance);
+            Assert.AreEqual(1, options.MimeInputTypes.Count);
+
+            Stream xmlStream = new MemoryStream(Encoding.ASCII.GetBytes(
+                TestXmlMessage.CreateBuilder().SetText("a").SetNumber(1).Build().ToXml()
+                                                    ));
+
+            TestXmlMessage msg = new TestXmlMessage.Builder().MergeFrom(
+                options, "-custom-XML-mime-type-", xmlStream)
+                .Build();
+            Assert.AreEqual("a", msg.Text);
+            Assert.AreEqual(1, msg.Number);
+        }
+
+        [Test]
+        public void TestWriteToCustomType()
+        {
+            var options = new MessageFormatOptions();
+            //Remove existing mime-type mappings
+            options.MimeOutputTypes.Clear();
+            //Add our own
+            options.MimeOutputTypes.Add("-custom-XML-mime-type-", XmlFormatWriter.CreateInstance);
+            
+            Assert.AreEqual(1, options.MimeOutputTypes.Count);
+
+            MemoryStream ms = new MemoryStream();
+            TestXmlMessage.CreateBuilder().SetText("a").SetNumber(1).Build()
+                .WriteTo(options, "-custom-XML-mime-type-", ms);
+
+            Assert.AreEqual("<root><text>a</text><number>1</number></root>", Encoding.UTF8.GetString(ms.ToArray()));
+        }
+    }
+}

+ 84 - 0
src/ProtocolBuffers.Test/TestReaderForUrlEncoded.cs

@@ -0,0 +1,84 @@
+using System;
+using System.IO;
+using System.Text;
+using NUnit.Framework;
+using Google.ProtocolBuffers.TestProtos;
+using Google.ProtocolBuffers.Serialization.Http;
+
+namespace Google.ProtocolBuffers
+{
+    [TestFixture]
+    public class TestReaderForUrlEncoded
+    {
+        [Test]
+        public void Example_FromQueryString()
+        {
+            Uri sampleUri = new Uri("http://sample.com/Path/File.ext?text=two+three%20four&valid=true&numbers=1&numbers=2", UriKind.Absolute);
+
+            ICodedInputStream input = FormUrlEncodedReader.CreateInstance(sampleUri.Query);
+
+            TestXmlMessage.Builder builder = TestXmlMessage.CreateBuilder();
+            builder.MergeFrom(input);
+            
+            TestXmlMessage message = builder.Build();
+            Assert.AreEqual(true, message.Valid);
+            Assert.AreEqual("two three four", message.Text);
+            Assert.AreEqual(2, message.NumbersCount);
+            Assert.AreEqual(1, message.NumbersList[0]);
+            Assert.AreEqual(2, message.NumbersList[1]);
+        }
+
+        [Test]
+        public void Example_FromFormData()
+        {
+            Stream rawPost = new MemoryStream(Encoding.UTF8.GetBytes("text=two+three%20four&valid=true&numbers=1&numbers=2"), false);
+
+            ICodedInputStream input = FormUrlEncodedReader.CreateInstance(rawPost);
+
+            TestXmlMessage.Builder builder = TestXmlMessage.CreateBuilder();
+            builder.MergeFrom(input);
+
+            TestXmlMessage message = builder.Build();
+            Assert.AreEqual(true, message.Valid);
+            Assert.AreEqual("two three four", message.Text);
+            Assert.AreEqual(2, message.NumbersCount);
+            Assert.AreEqual(1, message.NumbersList[0]);
+            Assert.AreEqual(2, message.NumbersList[1]);
+        }
+
+        [Test]
+        public void TestEmptyValues()
+        {
+            ICodedInputStream input = FormUrlEncodedReader.CreateInstance("valid=true&text=&numbers=1");
+            TestXmlMessage.Builder builder = TestXmlMessage.CreateBuilder();
+            builder.MergeFrom(input);
+
+            Assert.IsTrue(builder.Valid);
+            Assert.IsTrue(builder.HasText);
+            Assert.AreEqual("", builder.Text);
+            Assert.AreEqual(1, builder.NumbersCount);
+            Assert.AreEqual(1, builder.NumbersList[0]);
+        }
+
+        [Test]
+        public void TestNoValue()
+        {
+            ICodedInputStream input = FormUrlEncodedReader.CreateInstance("valid=true&text&numbers=1");
+            TestXmlMessage.Builder builder = TestXmlMessage.CreateBuilder();
+            builder.MergeFrom(input);
+
+            Assert.IsTrue(builder.Valid);
+            Assert.IsTrue(builder.HasText);
+            Assert.AreEqual("", builder.Text);
+            Assert.AreEqual(1, builder.NumbersCount);
+            Assert.AreEqual(1, builder.NumbersList[0]);
+        }
+
+        [Test, ExpectedException(typeof(NotSupportedException))]
+        public void FormUrlEncodedReaderDoesNotSupportChildren()
+        {
+            ICodedInputStream input = FormUrlEncodedReader.CreateInstance("child=uh0");
+            TestXmlMessage.CreateBuilder().MergeFrom(input);
+        }
+    }
+}

+ 386 - 0
src/ProtocolBuffers.Test/TestRpcForMimeTypes.cs

@@ -0,0 +1,386 @@
+#region Copyright notice and license
+
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// http://github.com/jskeet/dotnet-protobufs/
+// Original C++/Java/Python code:
+// http://code.google.com/p/protobuf/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#endregion
+
+using System;
+using Google.ProtocolBuffers;
+using Google.ProtocolBuffers.Serialization.Http;
+using Google.ProtocolBuffers.TestProtos;
+using NUnit.Framework;
+using System.IO;
+using Google.ProtocolBuffers.Serialization;
+using System.Text;
+
+namespace Google.ProtocolBuffers
+{
+    /// <summary>
+    /// This class verifies the correct code is generated from unittest_rpc_interop.proto and provides a small demonstration
+    /// of using the new IRpcDispatch to write a client/server
+    /// </summary>
+    [TestFixture]
+    public class TestRpcForMimeTypes
+    {
+        /// <summary>
+        /// A sample implementation of the ISearchService for testing
+        /// </summary>
+        private class ExampleSearchImpl : ISearchService
+        {
+            SearchResponse ISearchService.Search(SearchRequest searchRequest)
+            {
+                if (searchRequest.CriteriaCount == 0)
+                {
+                    throw new ArgumentException("No criteria specified.", new InvalidOperationException());
+                }
+                SearchResponse.Builder resp = SearchResponse.CreateBuilder();
+                foreach (string criteria in searchRequest.CriteriaList)
+                {
+                    resp.AddResults(
+                        SearchResponse.Types.ResultItem.CreateBuilder().SetName(criteria).SetUrl("http://search.com").
+                            Build());
+                }
+                return resp.Build();
+            }
+
+            SearchResponse ISearchService.RefineSearch(RefineSearchRequest refineSearchRequest)
+            {
+                SearchResponse.Builder resp = refineSearchRequest.PreviousResults.ToBuilder();
+                foreach (string criteria in refineSearchRequest.CriteriaList)
+                {
+                    resp.AddResults(
+                        SearchResponse.Types.ResultItem.CreateBuilder().SetName(criteria).SetUrl("http://refine.com").
+                            Build());
+                }
+                return resp.Build();
+            }
+        }
+
+        /// <summary>
+        /// An example extraction of the wire protocol
+        /// </summary>
+        private interface IHttpTransfer
+        {
+            void Execute(string method, string contentType, Stream input, string acceptType, Stream output);
+        }
+
+        /// <summary>
+        /// An example of a server responding to a web/http request
+        /// </summary>
+        private class ExampleHttpServer : IHttpTransfer
+        {
+            public readonly MessageFormatOptions Options =
+                new MessageFormatOptions
+                {
+                    ExtensionRegistry = ExtensionRegistry.Empty,
+                    FormattedOutput = true,
+                    XmlReaderOptions = XmlReaderOptions.ReadNestedArrays,
+                    XmlReaderRootElementName = "request",
+                    XmlWriterOptions = XmlWriterOptions.OutputNestedArrays,
+                    XmlWriterRootElementName = "response"
+                };
+
+            private readonly IRpcServerStub _stub;
+
+            public ExampleHttpServer(ISearchService implementation)
+            {
+                //on the server, we create a dispatch to call the appropriate method by name
+                IRpcDispatch dispatch = new SearchService.Dispatch(implementation);
+                //we then wrap that dispatch in a server stub which will deserialize the wire bytes to the message
+                //type appropriate for the method name being invoked.
+                _stub = new SearchService.ServerStub(dispatch);
+            }
+
+            void IHttpTransfer.Execute(string method, string contentType, Stream input, string acceptType, Stream output)
+            {
+                //Extension for: Google.ProtocolBuffers.Serialization.Http.ServiceExtensions.HttpCallMethod(_stub,
+                _stub.HttpCallMethod(
+                    method, Options,
+                    contentType, input,
+                    acceptType, output
+                    );
+            }
+        }
+
+        /// <summary>
+        /// An example of a client sending a wire request
+        /// </summary>
+        private class ExampleClient : IRpcDispatch
+        {
+            public readonly MessageFormatOptions Options =
+                new MessageFormatOptions
+                {
+                    ExtensionRegistry = ExtensionRegistry.Empty,
+                    FormattedOutput = true,
+                    XmlReaderOptions = XmlReaderOptions.ReadNestedArrays,
+                    XmlReaderRootElementName = "response",
+                    XmlWriterOptions = XmlWriterOptions.OutputNestedArrays,
+                    XmlWriterRootElementName = "request"
+                };
+
+
+            private readonly IHttpTransfer _wire;
+            private readonly string _mimeType;
+
+            public ExampleClient(IHttpTransfer wire, string mimeType)
+            {
+                _wire = wire;
+                _mimeType = mimeType;
+            }
+
+            TMessage IRpcDispatch.CallMethod<TMessage, TBuilder>(string method, IMessageLite request,
+                                                                 IBuilderLite<TMessage, TBuilder> response)
+            {
+                MemoryStream input = new MemoryStream();
+                MemoryStream output = new MemoryStream();
+
+                //Write to _mimeType format
+                request.WriteTo(Options, _mimeType, input);
+
+                input.Position = 0;
+                _wire.Execute(method, _mimeType, input, _mimeType, output);
+
+                //Read from _mimeType format
+                output.Position = 0;
+                response.MergeFrom(Options, _mimeType, output);
+                
+                return response.Build();
+            }
+        }
+
+        /// <summary>
+        /// Test sending and recieving messages via text/json
+        /// </summary>
+        [Test]
+        public void TestClientServerWithJsonFormat()
+        {
+            ExampleHttpServer server = new ExampleHttpServer(new ExampleSearchImpl());
+            //obviously if this was a 'real' transport we would not use the server, rather the server would be listening, the client transmitting
+            IHttpTransfer wire = server;
+
+            ISearchService client = new SearchService(new ExampleClient(wire, "text/json"));
+            //now the client has a real, typed, interface to work with:
+            SearchResponse result = client.Search(SearchRequest.CreateBuilder().AddCriteria("Test").Build());
+            Assert.AreEqual(1, result.ResultsCount);
+            Assert.AreEqual("Test", result.ResultsList[0].Name);
+            Assert.AreEqual("http://search.com", result.ResultsList[0].Url);
+
+            //The test part of this, call the only other method
+            result =
+                client.RefineSearch(
+                    RefineSearchRequest.CreateBuilder().SetPreviousResults(result).AddCriteria("Refine").Build());
+            Assert.AreEqual(2, result.ResultsCount);
+            Assert.AreEqual("Test", result.ResultsList[0].Name);
+            Assert.AreEqual("http://search.com", result.ResultsList[0].Url);
+
+            Assert.AreEqual("Refine", result.ResultsList[1].Name);
+            Assert.AreEqual("http://refine.com", result.ResultsList[1].Url);
+        }
+
+        /// <summary>
+        /// Test sending and recieving messages via text/json
+        /// </summary>
+        [Test]
+        public void TestClientServerWithXmlFormat()
+        {
+            ExampleHttpServer server = new ExampleHttpServer(new ExampleSearchImpl());
+            //obviously if this was a 'real' transport we would not use the server, rather the server would be listening, the client transmitting
+            IHttpTransfer wire = server;
+
+            ISearchService client = new SearchService(new ExampleClient(wire, "text/xml"));
+            //now the client has a real, typed, interface to work with:
+            SearchResponse result = client.Search(SearchRequest.CreateBuilder().AddCriteria("Test").Build());
+            Assert.AreEqual(1, result.ResultsCount);
+            Assert.AreEqual("Test", result.ResultsList[0].Name);
+            Assert.AreEqual("http://search.com", result.ResultsList[0].Url);
+
+            //The test part of this, call the only other method
+            result =
+                client.RefineSearch(
+                    RefineSearchRequest.CreateBuilder().SetPreviousResults(result).AddCriteria("Refine").Build());
+            Assert.AreEqual(2, result.ResultsCount);
+            Assert.AreEqual("Test", result.ResultsList[0].Name);
+            Assert.AreEqual("http://search.com", result.ResultsList[0].Url);
+
+            Assert.AreEqual("Refine", result.ResultsList[1].Name);
+            Assert.AreEqual("http://refine.com", result.ResultsList[1].Url);
+        }
+
+        /// <summary>
+        /// Test sending and recieving messages via text/json
+        /// </summary>
+        [Test]
+        public void TestClientServerWithProtoFormat()
+        {
+            ExampleHttpServer server = new ExampleHttpServer(new ExampleSearchImpl());
+            //obviously if this was a 'real' transport we would not use the server, rather the server would be listening, the client transmitting
+            IHttpTransfer wire = server;
+
+            ISearchService client = new SearchService(new ExampleClient(wire, "application/x-protobuf"));
+            //now the client has a real, typed, interface to work with:
+            SearchResponse result = client.Search(SearchRequest.CreateBuilder().AddCriteria("Test").Build());
+            Assert.AreEqual(1, result.ResultsCount);
+            Assert.AreEqual("Test", result.ResultsList[0].Name);
+            Assert.AreEqual("http://search.com", result.ResultsList[0].Url);
+
+            //The test part of this, call the only other method
+            result =
+                client.RefineSearch(
+                    RefineSearchRequest.CreateBuilder().SetPreviousResults(result).AddCriteria("Refine").Build());
+            Assert.AreEqual(2, result.ResultsCount);
+            Assert.AreEqual("Test", result.ResultsList[0].Name);
+            Assert.AreEqual("http://search.com", result.ResultsList[0].Url);
+
+            Assert.AreEqual("Refine", result.ResultsList[1].Name);
+            Assert.AreEqual("http://refine.com", result.ResultsList[1].Url);
+        }
+
+        /// <summary>
+        /// Test sending and recieving messages via text/json
+        /// </summary>
+        [Test]
+        public void TestClientServerWithCustomFormat()
+        {
+            ExampleHttpServer server = new ExampleHttpServer(new ExampleSearchImpl());
+            //Setup our custom mime-type format as the only format supported:
+            server.Options.MimeInputTypes.Clear();
+            server.Options.MimeInputTypes.Add("foo/bar", CodedInputStream.CreateInstance);
+            server.Options.MimeOutputTypes.Clear();
+            server.Options.MimeOutputTypes.Add("foo/bar", CodedOutputStream.CreateInstance);
+
+            //obviously if this was a 'real' transport we would not use the server, rather the server would be listening, the client transmitting
+            IHttpTransfer wire = server;
+
+            ExampleClient exclient = new ExampleClient(wire, "foo/bar");
+            //Add our custom mime-type format
+            exclient.Options.MimeInputTypes.Add("foo/bar", CodedInputStream.CreateInstance);
+            exclient.Options.MimeOutputTypes.Add("foo/bar", CodedOutputStream.CreateInstance);
+            ISearchService client = new SearchService(exclient);
+
+            //now the client has a real, typed, interface to work with:
+            SearchResponse result = client.Search(SearchRequest.CreateBuilder().AddCriteria("Test").Build());
+            Assert.AreEqual(1, result.ResultsCount);
+            Assert.AreEqual("Test", result.ResultsList[0].Name);
+            Assert.AreEqual("http://search.com", result.ResultsList[0].Url);
+
+            //The test part of this, call the only other method
+            result =
+                client.RefineSearch(
+                    RefineSearchRequest.CreateBuilder().SetPreviousResults(result).AddCriteria("Refine").Build());
+            Assert.AreEqual(2, result.ResultsCount);
+            Assert.AreEqual("Test", result.ResultsList[0].Name);
+            Assert.AreEqual("http://search.com", result.ResultsList[0].Url);
+
+            Assert.AreEqual("Refine", result.ResultsList[1].Name);
+            Assert.AreEqual("http://refine.com", result.ResultsList[1].Url);
+        }
+
+        /// <summary>
+        /// Test sending and recieving messages via text/json
+        /// </summary>
+        [Test]
+        public void TestServerWithUriFormat()
+        {
+            ExampleHttpServer server = new ExampleHttpServer(new ExampleSearchImpl());
+            //obviously if this was a 'real' transport we would not use the server, rather the server would be listening, the client transmitting
+            IHttpTransfer wire = server;
+
+            MemoryStream input = new MemoryStream(Encoding.UTF8.GetBytes("?Criteria=Test&Criteria=Test+of%20URI"));
+            MemoryStream output = new MemoryStream();
+
+            //Call the server
+            wire.Execute("Search",
+                         MessageFormatOptions.ContentFormUrlEncoded, input,
+                         MessageFormatOptions.ContentTypeProtoBuffer, output
+                         );
+
+            SearchResponse result = SearchResponse.ParseFrom(output.ToArray());
+            Assert.AreEqual(2, result.ResultsCount);
+            Assert.AreEqual("Test", result.ResultsList[0].Name);
+            Assert.AreEqual("http://search.com", result.ResultsList[0].Url);
+
+            Assert.AreEqual("Test of URI", result.ResultsList[1].Name);
+            Assert.AreEqual("http://search.com", result.ResultsList[1].Url);
+        }
+
+        /// <summary>
+        /// Test sending and recieving messages via text/json
+        /// </summary>
+        [Test, ExpectedException(typeof(ArgumentOutOfRangeException))]
+        public void TestInvalidMimeType()
+        {
+            ExampleHttpServer server = new ExampleHttpServer(new ExampleSearchImpl());
+            //obviously if this was a 'real' transport we would not use the server, rather the server would be listening, the client transmitting
+            IHttpTransfer wire = server;
+
+            MemoryStream input = new MemoryStream();
+            MemoryStream output = new MemoryStream();
+
+            //Call the server
+            wire.Execute("Search",
+                         "bad/mime", input,
+                         MessageFormatOptions.ContentTypeProtoBuffer, output
+                         );
+            Assert.Fail();
+        }
+
+        /// <summary>
+        /// Test sending and recieving messages via text/json
+        /// </summary>
+        [Test]
+        public void TestDefaultMimeType()
+        {
+            ExampleHttpServer server = new ExampleHttpServer(new ExampleSearchImpl());
+            
+            //obviously if this was a 'real' transport we would not use the server, rather the server would be listening, the client transmitting
+            IHttpTransfer wire = server;
+
+
+            MemoryStream input = new MemoryStream(new SearchRequest.Builder().AddCriteria("Test").Build().ToByteArray());
+            MemoryStream output = new MemoryStream();
+
+            //With this default set, any invalid/unknown mime-type will be mapped to use that format
+            server.Options.DefaultContentType = MessageFormatOptions.ContentTypeProtoBuffer;
+
+            wire.Execute("Search",
+                         "foo", input,
+                         "bar", output
+                         );
+
+            SearchResponse result = SearchResponse.ParseFrom(output.ToArray());
+            Assert.AreEqual(1, result.ResultsCount);
+            Assert.AreEqual("Test", result.ResultsList[0].Name);
+            Assert.AreEqual("http://search.com", result.ResultsList[0].Url);
+        }
+    }
+}

+ 86 - 0
src/ProtocolBuffers.Test/TestWriterFormatJson.cs

@@ -10,6 +10,70 @@ namespace Google.ProtocolBuffers
     [TestFixture]
     public class TestWriterFormatJson
     {
+        [Test]
+        public void Example_FromJson()
+        {
+            TestXmlMessage.Builder builder = TestXmlMessage.CreateBuilder();
+
+            builder.MergeFromJson(@"{""valid"":true}");
+            
+            TestXmlMessage message = builder.Build();
+            Assert.AreEqual(true, message.Valid);
+        }
+
+        [Test]
+        public void Example_ToJson()
+        {
+            TestXmlMessage message = 
+                TestXmlMessage.CreateBuilder()
+                .SetValid(true)
+                .Build();
+
+            string json = message.ToJson();
+
+            Assert.AreEqual(@"{""valid"":true}", json);
+        }
+
+        [Test]
+        public void Example_WriteJsonUsingICodedOutputStream()
+        {
+            TestXmlMessage message =
+                TestXmlMessage.CreateBuilder()
+                .SetValid(true)
+                .Build();
+
+            using (TextWriter output = new StringWriter())
+            {
+                ICodedOutputStream writer = JsonFormatWriter.CreateInstance(output);
+                writer.WriteMessageStart();      //manually begin the message, output is '{'
+                
+                writer.Flush();
+                Assert.AreEqual("{", output.ToString());
+
+                ICodedOutputStream stream = writer;
+                message.WriteTo(stream);    //write the message normally
+
+                writer.Flush();
+                Assert.AreEqual(@"{""valid"":true", output.ToString());
+
+                writer.WriteMessageEnd();        //manually write the end message '}'
+                Assert.AreEqual(@"{""valid"":true}", output.ToString());
+            }
+        }
+
+        [Test]
+        public void Example_ReadJsonUsingICodedInputStream()
+        {
+            TestXmlMessage.Builder builder = TestXmlMessage.CreateBuilder();
+            ICodedInputStream reader = JsonFormatReader.CreateInstance(@"{""valid"":true}");
+
+            reader.ReadMessageStart();  //manually read the begin the message '{'
+
+            builder.MergeFrom(reader);  //write the message normally
+
+            reader.ReadMessageEnd();    //manually read the end message '}'
+        }
+
         protected string Content;
         [System.Diagnostics.DebuggerNonUserCode]
         protected void FormatterAssert<TMessage>(TMessage message, params string[] expecting) where TMessage : IMessageLite
@@ -337,6 +401,28 @@ namespace Google.ProtocolBuffers
             Assert.AreEqual(3, ordinal);
             Assert.AreEqual(3, builder.TextlinesCount);
         }
+        [Test]
+        public void TestReadWriteJsonWithoutRoot()
+        {
+            TestXmlMessage.Builder builder = TestXmlMessage.CreateBuilder();
+            TestXmlMessage message = builder.SetText("abc").SetNumber(123).Build();
+
+            string Json;
+            using (StringWriter sw = new StringWriter())
+            {
+                ICodedOutputStream output = JsonFormatWriter.CreateInstance(sw);
+
+                message.WriteTo(output);
+                output.Flush();
+                Json = sw.ToString();
+            }
+            Assert.AreEqual(@"""text"":""abc"",""number"":123", Json);
+
+            ICodedInputStream input = JsonFormatReader.CreateInstance(Json);
+            TestXmlMessage copy = TestXmlMessage.CreateBuilder().MergeFrom(input).Build();
+
+            Assert.AreEqual(message, copy);
+        }
         [Test,ExpectedException(typeof(RecursionLimitExceededException))]
         public void TestRecursiveLimit()
         {

+ 119 - 2
src/ProtocolBuffers.Test/TestWriterFormatXml.cs

@@ -12,6 +12,65 @@ namespace Google.ProtocolBuffers
     [TestFixture]
     public class TestWriterFormatXml
     {
+        [Test]
+        public void Example_FromXml()
+        {
+            TestXmlMessage.Builder builder = TestXmlMessage.CreateBuilder();
+
+            XmlReader rdr = XmlReader.Create(new StringReader(@"<root><valid>true</valid></root>"));
+            builder.MergeFromXml(rdr);
+
+            TestXmlMessage message = builder.Build();
+            Assert.AreEqual(true, message.Valid);
+        }
+
+        [Test]
+        public void Example_ToXml()
+        {
+            TestXmlMessage message =
+                TestXmlMessage.CreateBuilder()
+                .SetValid(true)
+                .Build();
+
+            string Xml = message.ToXml();
+
+            Assert.AreEqual(@"<root><valid>true</valid></root>", Xml);
+        }
+
+        [Test]
+        public void Example_WriteXmlUsingICodedOutputStream()
+        {
+            TestXmlMessage message =
+                TestXmlMessage.CreateBuilder()
+                .SetValid(true)
+                .Build();
+
+            using (TextWriter output = new StringWriter())
+            {
+                ICodedOutputStream writer = XmlFormatWriter.CreateInstance(output);
+                writer.WriteMessageStart();      //manually begin the message, output is '{'
+
+                ICodedOutputStream stream = writer;
+                message.WriteTo(stream);         //write the message normally
+
+                writer.WriteMessageEnd();        //manually write the end message '}'
+                Assert.AreEqual(@"<root><valid>true</valid></root>", output.ToString());
+            }
+        }
+
+        [Test]
+        public void Example_ReadXmlUsingICodedInputStream()
+        {
+            TestXmlMessage.Builder builder = TestXmlMessage.CreateBuilder();
+            ICodedInputStream reader = XmlFormatReader.CreateInstance(@"<root><valid>true</valid></root>");
+
+            reader.ReadMessageStart();  //manually read the begin the message '{'
+
+            builder.MergeFrom(reader);  //read the message normally
+
+            reader.ReadMessageEnd();    //manually read the end message '}'
+        }
+
         [Test]
         public void TestToXmlParseFromXml()
         {
@@ -135,7 +194,9 @@ namespace Google.ProtocolBuffers
                 .Build();
 
             StringWriter sw = new StringWriter();
-            XmlFormatWriter.CreateInstance(sw).WriteMessage("root", message);
+            XmlWriter xwtr = XmlWriter.Create(sw, new XmlWriterSettings {Indent = true, IndentChars = "  "});
+
+            XmlFormatWriter.CreateInstance(xwtr).WriteMessage("root", message);
 
             string xml = sw.ToString();
 
@@ -162,7 +223,9 @@ namespace Google.ProtocolBuffers
                 .Build();
 
             StringWriter sw = new StringWriter();
-            XmlFormatWriter.CreateInstance(sw)
+            XmlWriter xwtr = XmlWriter.Create(sw, new XmlWriterSettings { Indent = true, IndentChars = "  " });
+
+            XmlFormatWriter.CreateInstance(xwtr)
                 .SetOptions(XmlWriterOptions.OutputNestedArrays | XmlWriterOptions.OutputEnumValues)
                 .WriteMessage("root", message);
 
@@ -324,6 +387,60 @@ namespace Google.ProtocolBuffers
             TestXmlMessage copy = rdr.Merge(TestXmlMessage.CreateBuilder(), registry).Build();
             Assert.AreEqual(message, copy);
         }
+        [Test]
+        public void TestXmlReadEmptyRoot()
+        {
+            TestXmlMessage.Builder builder = TestXmlMessage.CreateBuilder();
+            ICodedInputStream reader = XmlFormatReader.CreateInstance(@"<root/>");
+
+            reader.ReadMessageStart();  //manually read the begin the message '{'
+
+            builder.MergeFrom(reader);  //write the message normally
+
+            reader.ReadMessageEnd();    //manually read the end message '}'
+        }
+
+        [Test]
+        public void TestXmlReadEmptyChild()
+        {
+            TestXmlMessage.Builder builder = TestXmlMessage.CreateBuilder();
+            ICodedInputStream reader = XmlFormatReader.CreateInstance(@"<root><text /></root>");
+
+            reader.ReadMessageStart();  //manually read the begin the message '{'
+
+            builder.MergeFrom(reader);  //write the message normally
+            Assert.IsTrue(builder.HasText);
+            Assert.AreEqual(String.Empty, builder.Text);
+        }
+
+        [Test]
+        public void TestXmlReadWriteWithoutRoot()
+        {
+            TestXmlMessage.Builder builder = TestXmlMessage.CreateBuilder();
+            TestXmlMessage message = builder.SetText("abc").SetNumber(123).Build();
+
+            string xml;
+            using (StringWriter sw = new StringWriter())
+            {
+                ICodedOutputStream output = XmlFormatWriter.CreateInstance(
+                    XmlWriter.Create(sw, new XmlWriterSettings() { ConformanceLevel = ConformanceLevel.Fragment }));
+
+                message.WriteTo(output);
+                output.Flush();
+                xml = sw.ToString();
+            }
+            Assert.AreEqual("<text>abc</text><number>123</number>", xml);
+
+            TestXmlMessage copy;
+            using (XmlReader xr = XmlReader.Create(new StringReader(xml), new XmlReaderSettings() { ConformanceLevel = ConformanceLevel.Fragment }))
+            {
+                ICodedInputStream input = XmlFormatReader.CreateInstance(xr);
+                copy = TestXmlMessage.CreateBuilder().MergeFrom(input).Build();
+            }
+
+            Assert.AreEqual(message, copy);
+        }
+
         [Test, ExpectedException(typeof(RecursionLimitExceededException))]
         public void TestRecursiveLimit()
         {

+ 3 - 0
src/ProtocolBuffers/CodedInputStream.cs

@@ -144,6 +144,9 @@ namespace Google.ProtocolBuffers
 
         #endregion
 
+        void ICodedInputStream.ReadMessageStart() { }
+        void ICodedInputStream.ReadMessageEnd() { }
+
         #region Validation
 
         /// <summary>

+ 3 - 0
src/ProtocolBuffers/CodedOutputStream.cs

@@ -125,6 +125,9 @@ namespace Google.ProtocolBuffers
         }
 
         #endregion
+        
+        void ICodedOutputStream.WriteMessageStart() { }
+        void ICodedOutputStream.WriteMessageEnd() { Flush(); }
 
         #region Writing of unknown fields
 

+ 18 - 0
src/ProtocolBuffers/ICodedInputStream.cs

@@ -45,6 +45,24 @@ namespace Google.ProtocolBuffers
 {
     public interface ICodedInputStream
     {
+        /// <summary>
+        /// Reads any message initialization data expected from the input stream
+        /// </summary>
+        /// <remarks>
+        /// This is primarily used by text formats and unnecessary for protobuffers' own
+        /// binary format.  The API for MessageStart/End was added for consistent handling
+        /// of output streams regardless of the actual writer implementation.
+        /// </remarks>
+        void ReadMessageStart();
+        /// <summary>
+        /// Reads any message finalization data expected from the input stream
+        /// </summary>
+        /// <remarks>
+        /// This is primarily used by text formats and unnecessary for protobuffers' own
+        /// binary format.  The API for MessageStart/End was added for consistent handling
+        /// of output streams regardless of the actual writer implementation.
+        /// </remarks>
+        void ReadMessageEnd();
         /// <summary>
         /// Attempt to read a field tag, returning false if we have reached the end
         /// of the input data.

+ 18 - 0
src/ProtocolBuffers/ICodedOutputStream.cs

@@ -51,6 +51,24 @@ namespace Google.ProtocolBuffers
     /// </summary>
     public interface ICodedOutputStream
     {
+        /// <summary>
+        /// Writes any message initialization data needed to the output stream
+        /// </summary>
+        /// <remarks>
+        /// This is primarily used by text formats and unnecessary for protobuffers' own
+        /// binary format.  The API for MessageStart/End was added for consistent handling
+        /// of output streams regardless of the actual writer implementation.
+        /// </remarks>
+        void WriteMessageStart();
+        /// <summary>
+        /// Writes any message finalization data needed to the output stream
+        /// </summary>
+        /// <remarks>
+        /// This is primarily used by text formats and unnecessary for protobuffers' own
+        /// binary format.  The API for MessageStart/End was added for consistent handling
+        /// of output streams regardless of the actual writer implementation.
+        /// </remarks>
+        void WriteMessageEnd();
         /// <summary>
         /// Indicates that all temporary buffers be written to the final output.
         /// </summary>