2 // DataContractSerializer.cs
5 // Atsushi Enomoto <atsushi@ximian.com>
7 // Copyright (C) 2005-2007 Novell, Inc. http://www.novell.com
9 // Permission is hereby granted, free of charge, to any person obtaining
10 // a copy of this software and associated documentation files (the
11 // "Software"), to deal in the Software without restriction, including
12 // without limitation the rights to use, copy, modify, merge, publish,
13 // distribute, sublicense, and/or sell copies of the Software, and to
14 // permit persons to whom the Software is furnished to do so, subject to
15 // the following conditions:
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30 using System.Collections;
31 using System.Collections.Generic;
32 using System.Collections.ObjectModel;
34 using System.Reflection;
35 using System.Runtime.Serialization.Formatters.Binary;
37 using System.Xml.Schema;
39 using QName = System.Xml.XmlQualifiedName;
41 namespace System.Runtime.Serialization
43 public sealed class DataContractSerializer : XmlObjectSerializer
45 const string xmlns = "http://www.w3.org/2000/xmlns/";
48 bool ignore_ext, preserve_refs;
50 // This is only for compatible mode.
51 StreamingContext context;
52 ReadOnlyCollection<Type> returned_known_types;
53 KnownTypeCollection known_types;
54 IDataContractSurrogate surrogate;
55 DataContractResolver resolver, default_resolver;
57 int max_items = 0x10000; // FIXME: could be from config.
60 XmlDictionaryString root_name, root_ns;
62 public DataContractSerializer (Type type)
63 : this (type, Type.EmptyTypes)
65 // nothing to do here.
68 public DataContractSerializer (Type type,
69 IEnumerable<Type> knownTypes)
72 throw new ArgumentNullException ("type");
74 PopulateTypes (knownTypes);
75 known_types.Add (type);
76 QName qname = known_types.GetQName (type);
78 FillDictionaryString (qname.Name, qname.Namespace);
82 public DataContractSerializer (Type type, string rootName,
84 : this (type, rootName, rootNamespace, Type.EmptyTypes)
86 // nothing to do here.
89 public DataContractSerializer (Type type,
90 XmlDictionaryString rootName,
91 XmlDictionaryString rootNamespace)
92 : this (type, rootName, rootNamespace, Type.EmptyTypes)
94 // nothing to do here.
97 public DataContractSerializer (Type type, string rootName,
98 string rootNamespace, IEnumerable<Type> knownTypes)
101 throw new ArgumentNullException ("type");
102 if (rootName == null)
103 throw new ArgumentNullException ("rootName");
104 if (rootNamespace == null)
105 throw new ArgumentNullException ("rootNamespace");
107 PopulateTypes (knownTypes);
108 FillDictionaryString (rootName, rootNamespace);
111 public DataContractSerializer (Type type,
112 XmlDictionaryString rootName,
113 XmlDictionaryString rootNamespace,
114 IEnumerable<Type> knownTypes)
117 throw new ArgumentNullException ("type");
118 if (rootName == null)
119 throw new ArgumentNullException ("rootName");
120 if (rootNamespace == null)
121 throw new ArgumentNullException ("rootNamespace");
123 PopulateTypes (knownTypes);
124 root_name = rootName;
125 root_ns = rootNamespace;
128 public DataContractSerializer (Type type,
129 IEnumerable<Type> knownTypes,
130 int maxObjectsInGraph,
131 bool ignoreExtensionDataObject,
132 bool preserveObjectReferences,
133 IDataContractSurrogate dataContractSurrogate)
134 : this (type, knownTypes)
136 Initialize (maxObjectsInGraph,
137 ignoreExtensionDataObject,
138 preserveObjectReferences,
139 dataContractSurrogate);
142 public DataContractSerializer (Type type,
144 string rootNamespace,
145 IEnumerable<Type> knownTypes,
146 int maxObjectsInGraph,
147 bool ignoreExtensionDataObject,
148 bool preserveObjectReferences,
149 IDataContractSurrogate dataContractSurrogate)
150 : this (type, rootName, rootNamespace, knownTypes)
152 Initialize (maxObjectsInGraph,
153 ignoreExtensionDataObject,
154 preserveObjectReferences,
155 dataContractSurrogate);
158 public DataContractSerializer (Type type,
159 XmlDictionaryString rootName,
160 XmlDictionaryString rootNamespace,
161 IEnumerable<Type> knownTypes,
162 int maxObjectsInGraph,
163 bool ignoreExtensionDataObject,
164 bool preserveObjectReferences,
165 IDataContractSurrogate dataContractSurrogate)
166 : this (type, rootName, rootNamespace, knownTypes)
168 Initialize (maxObjectsInGraph,
169 ignoreExtensionDataObject,
170 preserveObjectReferences,
171 dataContractSurrogate);
175 public DataContractSerializer (Type type,
176 IEnumerable<Type> knownTypes,
177 int maxObjectsInGraph,
178 bool ignoreExtensionDataObject,
179 bool preserveObjectReferences,
180 IDataContractSurrogate dataContractSurrogate,
181 DataContractResolver dataContractResolver)
182 : this (type, knownTypes, maxObjectsInGraph, ignoreExtensionDataObject, preserveObjectReferences, dataContractSurrogate)
184 DataContractResolver = dataContractResolver;
187 public DataContractSerializer (Type type,
189 string rootNamespace,
190 IEnumerable<Type> knownTypes,
191 int maxObjectsInGraph,
192 bool ignoreExtensionDataObject,
193 bool preserveObjectReferences,
194 IDataContractSurrogate dataContractSurrogate,
195 DataContractResolver dataContractResolver)
196 : this (type, rootName, rootNamespace, knownTypes, maxObjectsInGraph, ignoreExtensionDataObject, preserveObjectReferences, dataContractSurrogate)
198 DataContractResolver = dataContractResolver;
201 public DataContractSerializer (Type type,
202 XmlDictionaryString rootName,
203 XmlDictionaryString rootNamespace,
204 IEnumerable<Type> knownTypes,
205 int maxObjectsInGraph,
206 bool ignoreExtensionDataObject,
207 bool preserveObjectReferences,
208 IDataContractSurrogate dataContractSurrogate,
209 DataContractResolver dataContractResolver)
210 : this (type, rootName, rootNamespace, knownTypes, maxObjectsInGraph, ignoreExtensionDataObject, preserveObjectReferences, dataContractSurrogate)
212 DataContractResolver = dataContractResolver;
216 void PopulateTypes (IEnumerable<Type> knownTypes)
218 if (known_types == null)
219 known_types= new KnownTypeCollection ();
221 if (knownTypes != null) {
222 foreach (Type t in knownTypes)
226 RegisterTypeAsKnown (type);
229 void RegisterTypeAsKnown (Type type)
231 if (known_types.Contains (type))
234 Type elementType = type;
235 if (type.HasElementType)
236 elementType = type.GetElementType ();
238 known_types.Add (elementType);
240 /* Get all KnownTypeAttribute-s, including inherited ones */
241 object [] attrs = elementType.GetCustomAttributes (typeof (KnownTypeAttribute), true);
242 for (int i = 0; i < attrs.Length; i ++) {
243 KnownTypeAttribute kt = (KnownTypeAttribute) attrs [i];
244 foreach (var t in kt.GetTypes (elementType))
245 RegisterTypeAsKnown (t);
249 void FillDictionaryString (string name, string ns)
251 XmlDictionary d = new XmlDictionary ();
252 root_name = d.Add (name);
253 root_ns = d.Add (ns);
258 int maxObjectsInGraph,
259 bool ignoreExtensionDataObject,
260 bool preserveObjectReferences,
261 IDataContractSurrogate dataContractSurrogate)
263 if (maxObjectsInGraph < 0)
264 throw new ArgumentOutOfRangeException ("maxObjectsInGraph must not be negative.");
265 max_items = maxObjectsInGraph;
266 ignore_ext = ignoreExtensionDataObject;
267 preserve_refs = preserveObjectReferences;
268 surrogate = dataContractSurrogate;
276 DataContractResolver DataContractResolver {
277 get { return resolver; }
280 default_resolver = default_resolver ?? new DefaultDataContractResolver (this);
284 public bool IgnoreExtensionDataObject {
285 get { return ignore_ext; }
288 public ReadOnlyCollection<Type> KnownTypes {
290 if (returned_known_types == null)
291 returned_known_types = new ReadOnlyCollection<Type> (known_types);
292 return returned_known_types;
296 internal KnownTypeCollection InternalKnownTypes {
297 get { return known_types; }
300 public IDataContractSurrogate DataContractSurrogate {
301 get { return surrogate; }
304 public int MaxItemsInObjectGraph {
305 get { return max_items; }
308 public bool PreserveObjectReferences {
309 get { return preserve_refs; }
312 public override bool IsStartObject (XmlDictionaryReader reader)
315 throw new ArgumentNullException ("reader");
316 reader.MoveToContent ();
317 return reader.IsStartElement (root_name, root_ns);
321 public override bool IsStartObject (XmlReader reader)
323 return IsStartObject (XmlDictionaryReader.CreateDictionaryReader (reader));
327 public override object ReadObject (XmlReader reader)
329 return ReadObject (XmlDictionaryReader.CreateDictionaryReader (reader));
332 public override object ReadObject (XmlReader reader, bool verifyObjectName)
334 return ReadObject (XmlDictionaryReader.CreateDictionaryReader (reader), verifyObjectName);
337 public override object ReadObject (XmlDictionaryReader reader, bool verifyObjectName)
339 int startTypeCount = known_types.Count;
340 known_types.Add (type);
342 bool isEmpty = reader.IsEmptyElement;
344 object ret = XmlFormatterDeserializer.Deserialize (reader, type,
345 known_types, surrogate, DataContractResolver, default_resolver, root_name.Value, root_ns.Value, verifyObjectName);
347 // remove temporarily-added known types for
348 // rootType and object graph type.
349 while (known_types.Count > startTypeCount)
350 known_types.RemoveAt (startTypeCount);
356 public object ReadObject (XmlDictionaryReader reader, bool verifyObjectName, DataContractResolver resolver)
358 var bak = DataContractResolver;
360 DataContractResolver = resolver;
361 return ReadObject (reader, verifyObjectName);
363 DataContractResolver = bak;
368 private void ReadRootStartElement (XmlReader reader, Type type)
370 SerializationMap map =
371 known_types.FindUserMap (type);
372 QName name = map != null ? map.XmlName :
373 KnownTypeCollection.GetPredefinedTypeName (type);
374 reader.MoveToContent ();
375 reader.ReadStartElement (name.Name, name.Namespace);
376 // FIXME: could there be any attributes to handle here?
381 public override void WriteObject (XmlWriter writer, object graph)
383 XmlDictionaryWriter w = XmlDictionaryWriter.CreateDictionaryWriter (writer);
384 WriteObject (w, graph);
388 public void WriteObject (XmlDictionaryWriter writer, object graph, DataContractResolver resolver)
390 var bak = DataContractResolver;
392 DataContractResolver = resolver;
393 WriteObject (writer, graph);
395 DataContractResolver = bak;
400 [MonoTODO ("use DataContractSurrogate")]
402 when writeContentOnly is true, then the input XmlWriter
403 must be at element state. This is to write possible
406 rootType determines the top-level element QName (thus
407 it is ignored when writeContentOnly is true).
409 preserveObjectReferences indicates that whether the
410 output should contain ms:Id or not.
411 (http://schemas.microsoft.com/2003/10/Serialization/)
413 public override void WriteObjectContent (XmlDictionaryWriter writer, object graph)
418 int startTypeCount = known_types.Count;
420 XmlFormatterSerializer.Serialize (writer, graph,
422 ignore_ext, max_items, root_ns.Value, preserve_refs, DataContractResolver, default_resolver);
424 // remove temporarily-added known types for
425 // rootType and object graph type.
426 while (known_types.Count > startTypeCount)
427 known_types.RemoveAt (startTypeCount);
430 public override void WriteObjectContent (XmlWriter writer, object graph)
432 XmlDictionaryWriter w = XmlDictionaryWriter.CreateDictionaryWriter (writer);
433 WriteObjectContent (w, graph);
437 public override void WriteStartObject (
438 XmlWriter writer, object graph)
440 WriteStartObject (XmlDictionaryWriter.CreateDictionaryWriter (writer), graph);
443 public override void WriteStartObject (
444 XmlDictionaryWriter writer, object graph)
446 Type rootType = type;
448 if (root_name.Value == "")
449 throw new InvalidDataContractException ("Type '" + type.ToString () +
450 "' cannot have a DataContract attribute Name set to null or empty string.");
455 writer.WriteStartElement (root_name.Value, root_ns.Value);
457 writer.WriteStartElement (root_name, root_ns);
458 writer.WriteAttributeString ("i", "nil", XmlSchema.InstanceNamespace, "true");
462 QName rootQName = null;
463 XmlDictionaryString name, ns;
464 if (DataContractResolver != null && DataContractResolver.TryResolveType (graph.GetType (), type, default_resolver, out name, out ns))
465 rootQName = new QName (name.Value, ns.Value);
467 // It is error unless 1) TypeResolver resolved the type name, 2) the object is the exact type, 3) the object is known or 4) the type is primitive.
469 if (rootQName == null &&
470 graph.GetType () != type &&
471 IsUnknownType (graph.GetType ()))
472 throw new SerializationException (String.Format ("Type '{0}' is unexpected. The type should either be registered as a known type, or DataContractResolver should be used.", graph.GetType ()));
474 QName instName = rootQName;
475 rootQName = rootQName ?? known_types.GetQName (rootType);
476 QName graph_qname = known_types.GetQName (graph.GetType ());
478 known_types.Add (graph.GetType ());
481 writer.WriteStartElement (root_name.Value, root_ns.Value);
483 writer.WriteStartElement (root_name, root_ns);
485 if (rootQName != graph_qname || rootQName.Namespace != KnownTypeCollection.MSSimpleNamespace && !rootType.IsEnum)
486 //FIXME: Hack, when should the "i:type" be written?
487 //Not used in case of enums
488 writer.WriteXmlnsAttribute ("i", XmlSchema.InstanceNamespace);
490 if (root_ns.Value != rootQName.Namespace)
491 if (rootQName.Namespace != KnownTypeCollection.MSSimpleNamespace)
492 writer.WriteXmlnsAttribute (null, rootQName.Namespace);
494 if (rootQName == graph_qname)
497 /* Different names */
498 known_types.Add (rootType);
500 instName = instName ?? KnownTypeCollection.GetPredefinedTypeName (graph.GetType ());
501 if (instName == QName.Empty)
502 /* Not a primitive type */
503 instName = graph_qname;
505 /* FIXME: Hack, .. see test WriteObject7 () */
506 instName = new QName (instName.Name, XmlSchema.Namespace);
508 /* // disabled as it now generates extraneous i:type output.
509 // output xsi:type as rootType is not equivalent to the graph's type.
510 writer.WriteStartAttribute ("i", "type", XmlSchema.InstanceNamespace);
511 writer.WriteQualifiedName (instName.Name, instName.Namespace);
512 writer.WriteEndAttribute ();
516 bool IsUnknownType (Type type)
518 if (known_types.Contains (type) ||
519 KnownTypeCollection.GetPrimitiveTypeName (type) != QName.Empty)
522 return IsUnknownType (type.GetElementType ());
526 public override void WriteEndObject (XmlDictionaryWriter writer)
528 writer.WriteEndElement ();
532 public override void WriteEndObject (XmlWriter writer)
534 WriteEndObject (XmlDictionaryWriter.CreateDictionaryWriter (writer));