XmlProtoSerializer.cs 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. #if FEAT_SERVICEMODEL && PLAT_XMLSERIALIZER
  2. using System;
  3. using System.IO;
  4. using System.Runtime.Serialization;
  5. using System.Xml;
  6. using ProtoBuf.Meta;
  7. namespace ProtoBuf.ServiceModel
  8. {
  9. /// <summary>
  10. /// An xml object serializer that can embed protobuf data in a base-64 hunk (looking like a byte[])
  11. /// </summary>
  12. public sealed class XmlProtoSerializer : XmlObjectSerializer
  13. {
  14. private readonly TypeModel model;
  15. private readonly int key;
  16. private readonly bool isList, isEnum;
  17. private readonly Type type;
  18. internal XmlProtoSerializer(TypeModel model, int key, Type type, bool isList)
  19. {
  20. if (key < 0) throw new ArgumentOutOfRangeException(nameof(key));
  21. this.model = model ?? throw new ArgumentNullException(nameof(model));
  22. this.key = key;
  23. this.isList = isList;
  24. this.type = type ?? throw new ArgumentOutOfRangeException(nameof(type));
  25. this.isEnum = Helpers.IsEnum(type);
  26. }
  27. /// <summary>
  28. /// Attempt to create a new serializer for the given model and type
  29. /// </summary>
  30. /// <returns>A new serializer instance if the type is recognised by the model; null otherwise</returns>
  31. public static XmlProtoSerializer TryCreate(TypeModel model, Type type)
  32. {
  33. if (model == null) throw new ArgumentNullException(nameof(model));
  34. if (type == null) throw new ArgumentNullException(nameof(type));
  35. int key = GetKey(model, ref type, out bool isList);
  36. if (key >= 0)
  37. {
  38. return new XmlProtoSerializer(model, key, type, isList);
  39. }
  40. return null;
  41. }
  42. /// <summary>
  43. /// Creates a new serializer for the given model and type
  44. /// </summary>
  45. public XmlProtoSerializer(TypeModel model, Type type)
  46. {
  47. if (model == null) throw new ArgumentNullException(nameof(model));
  48. if (type == null) throw new ArgumentNullException(nameof(type));
  49. key = GetKey(model, ref type, out isList);
  50. this.model = model;
  51. this.type = type;
  52. this.isEnum = Helpers.IsEnum(type);
  53. if (key < 0) throw new ArgumentOutOfRangeException(nameof(type), "Type not recognised by the model: " + type.FullName);
  54. }
  55. static int GetKey(TypeModel model, ref Type type, out bool isList)
  56. {
  57. if (model != null && type != null)
  58. {
  59. int key = model.GetKey(ref type);
  60. if (key >= 0)
  61. {
  62. isList = false;
  63. return key;
  64. }
  65. Type itemType = TypeModel.GetListItemType(model, type);
  66. if (itemType != null)
  67. {
  68. key = model.GetKey(ref itemType);
  69. if (key >= 0)
  70. {
  71. isList = true;
  72. return key;
  73. }
  74. }
  75. }
  76. isList = false;
  77. return -1;
  78. }
  79. /// <summary>
  80. /// Ends an object in the output
  81. /// </summary>
  82. public override void WriteEndObject(XmlDictionaryWriter writer)
  83. {
  84. if (writer == null) throw new ArgumentNullException(nameof(writer));
  85. writer.WriteEndElement();
  86. }
  87. /// <summary>
  88. /// Begins an object in the output
  89. /// </summary>
  90. public override void WriteStartObject(XmlDictionaryWriter writer, object graph)
  91. {
  92. if (writer == null) throw new ArgumentNullException(nameof(writer));
  93. writer.WriteStartElement(PROTO_ELEMENT);
  94. }
  95. private const string PROTO_ELEMENT = "proto";
  96. /// <summary>
  97. /// Writes the body of an object in the output
  98. /// </summary>
  99. public override void WriteObjectContent(XmlDictionaryWriter writer, object graph)
  100. {
  101. if (writer == null) throw new ArgumentNullException(nameof(writer));
  102. if (graph == null)
  103. {
  104. writer.WriteAttributeString("nil", "true");
  105. }
  106. else
  107. {
  108. using (MemoryStream ms = new MemoryStream())
  109. {
  110. if (isList)
  111. {
  112. model.Serialize(ms, graph, null);
  113. }
  114. else
  115. {
  116. using (ProtoWriter protoWriter = ProtoWriter.Create(ms, model, null))
  117. {
  118. model.Serialize(key, graph, protoWriter);
  119. }
  120. }
  121. byte[] buffer = ms.GetBuffer();
  122. writer.WriteBase64(buffer, 0, (int)ms.Length);
  123. }
  124. }
  125. }
  126. /// <summary>
  127. /// Indicates whether this is the start of an object we are prepared to handle
  128. /// </summary>
  129. public override bool IsStartObject(XmlDictionaryReader reader)
  130. {
  131. if (reader == null) throw new ArgumentNullException(nameof(reader));
  132. reader.MoveToContent();
  133. return reader.NodeType == XmlNodeType.Element && reader.Name == PROTO_ELEMENT;
  134. }
  135. /// <summary>
  136. /// Reads the body of an object
  137. /// </summary>
  138. public override object ReadObject(XmlDictionaryReader reader, bool verifyObjectName)
  139. {
  140. if (reader == null) throw new ArgumentNullException(nameof(reader));
  141. reader.MoveToContent();
  142. bool isSelfClosed = reader.IsEmptyElement, isNil = reader.GetAttribute("nil") == "true";
  143. reader.ReadStartElement(PROTO_ELEMENT);
  144. // explicitly null
  145. if (isNil)
  146. {
  147. if (!isSelfClosed) reader.ReadEndElement();
  148. return null;
  149. }
  150. if (isSelfClosed) // no real content
  151. {
  152. if (isList || isEnum)
  153. {
  154. return model.Deserialize(Stream.Null, null, type, null);
  155. }
  156. ProtoReader protoReader = null;
  157. try
  158. {
  159. protoReader = ProtoReader.Create(Stream.Null, model, null, ProtoReader.TO_EOF);
  160. return model.Deserialize(key, null, protoReader);
  161. }
  162. finally
  163. {
  164. ProtoReader.Recycle(protoReader);
  165. }
  166. }
  167. object result;
  168. Helpers.DebugAssert(reader.CanReadBinaryContent, "CanReadBinaryContent");
  169. using (MemoryStream ms = new MemoryStream(reader.ReadContentAsBase64()))
  170. {
  171. if (isList || isEnum)
  172. {
  173. result = model.Deserialize(ms, null, type, null);
  174. }
  175. else
  176. {
  177. ProtoReader protoReader = null;
  178. try
  179. {
  180. protoReader = ProtoReader.Create(ms, model, null, ProtoReader.TO_EOF);
  181. result = model.Deserialize(key, null, protoReader);
  182. }
  183. finally
  184. {
  185. ProtoReader.Recycle(protoReader);
  186. }
  187. }
  188. }
  189. reader.ReadEndElement();
  190. return result;
  191. }
  192. }
  193. }
  194. #endif