diff --git a/mcs/class/System.Runtime.Serialization/System.Runtime.Serialization/DataContractResolver.cs b/mcs/class/System.Runtime.Serialization/System.Runtime.Serialization/DataContractResolver.cs index eac98ed4eb20470cf5c3294927f01e8de5e8cdeb..60a3e66ba28aaed8b581745c85311112ef9f32ca 100644 --- a/mcs/class/System.Runtime.Serialization/System.Runtime.Serialization/DataContractResolver.cs +++ b/mcs/class/System.Runtime.Serialization/System.Runtime.Serialization/DataContractResolver.cs @@ -33,7 +33,6 @@ namespace System.Runtime.Serialization { // See http://msdn.microsoft.com/en-us/library/ee358759.aspx #if NET_4_0 - [MonoTODO ("not in use yet")] public #else internal @@ -44,4 +43,40 @@ namespace System.Runtime.Serialization public abstract bool TryResolveType (Type type, Type declaredType, DataContractResolver knownTypeResolver, out XmlDictionaryString typeName, out XmlDictionaryString typeNamespace); } + + internal class DefaultDataContractResolver : DataContractResolver + { + public DefaultDataContractResolver (DataContractSerializer serializer) + { + this.serializer = serializer; + } + + DataContractSerializer serializer; + XmlDictionary dictionary; + + public override Type ResolveName (string typeName, string typeNamespace, Type declaredType, DataContractResolver knownTypeResolver) + { + var map = serializer.InternalKnownTypes.FindUserMap (new XmlQualifiedName (typeName, typeNamespace)); + if (map == null) + serializer.InternalKnownTypes.Add (declaredType); + if (map != null) + return map.RuntimeType; + return null; + } + + public override bool TryResolveType (Type type, Type declaredType, DataContractResolver knownTypeResolver, out XmlDictionaryString typeName, out XmlDictionaryString typeNamespace) + { + var map = serializer.InternalKnownTypes.FindUserMap (type); + if (map == null) { + typeName = null; + typeNamespace = null; + return false; + } else { + dictionary = dictionary ?? new XmlDictionary (); + typeName = dictionary.Add (map.XmlName.Name); + typeNamespace = dictionary.Add (map.XmlName.Namespace); + return true; + } + } + } } diff --git a/mcs/class/System.Runtime.Serialization/System.Runtime.Serialization/DataContractSerializer.cs b/mcs/class/System.Runtime.Serialization/System.Runtime.Serialization/DataContractSerializer.cs index 6693daa0e7260fdb3e84a718a80546996e86f3e4..d44a4162a06e59db2df2d8b8d7f2a1c1ae28dc7c 100755 --- a/mcs/class/System.Runtime.Serialization/System.Runtime.Serialization/DataContractSerializer.cs +++ b/mcs/class/System.Runtime.Serialization/System.Runtime.Serialization/DataContractSerializer.cs @@ -52,6 +52,7 @@ namespace System.Runtime.Serialization ReadOnlyCollection returned_known_types; KnownTypeCollection known_types; IDataContractSurrogate surrogate; + DataContractResolver resolver, default_resolver; int max_items = 0x10000; // FIXME: could be from config. @@ -70,9 +71,8 @@ namespace System.Runtime.Serialization if (type == null) throw new ArgumentNullException ("type"); this.type = type; - known_types = new KnownTypeCollection (); PopulateTypes (knownTypes); - known_types.TryRegister (type); + known_types.Add (type); QName qname = known_types.GetQName (type); FillDictionaryString (qname.Name, qname.Namespace); @@ -220,7 +220,7 @@ namespace System.Runtime.Serialization if (knownTypes != null) { foreach (Type t in knownTypes) - known_types.TryRegister (t); + known_types.Add (t); } Type elementType = type; @@ -231,7 +231,7 @@ namespace System.Runtime.Serialization object [] attrs = elementType.GetCustomAttributes (typeof (KnownTypeAttribute), true); for (int i = 0; i < attrs.Length; i ++) { KnownTypeAttribute kt = (KnownTypeAttribute) attrs [i]; - known_types.TryRegister (kt.Type); + known_types.Add (kt.Type); } } @@ -255,8 +255,6 @@ namespace System.Runtime.Serialization ignore_ext = ignoreExtensionDataObject; preserve_refs = preserveObjectReferences; surrogate = dataContractSurrogate; - - PopulateTypes (Type.EmptyTypes); } #if NET_4_0 @@ -264,7 +262,13 @@ namespace System.Runtime.Serialization #else internal #endif - DataContractResolver DataContractResolver { get; private set; } + DataContractResolver DataContractResolver { + get { return resolver; } + private set { + resolver = value; + default_resolver = default_resolver ?? new DefaultDataContractResolver (this); + } + } public bool IgnoreExtensionDataObject { get { return ignore_ext; } @@ -278,6 +282,10 @@ namespace System.Runtime.Serialization } } + internal KnownTypeCollection InternalKnownTypes { + get { return known_types; } + } + public IDataContractSurrogate DataContractSurrogate { get { return surrogate; } } @@ -323,7 +331,7 @@ namespace System.Runtime.Serialization bool isEmpty = reader.IsEmptyElement; object ret = XmlFormatterDeserializer.Deserialize (reader, type, - known_types, surrogate, DataContractResolver, root_name.Value, root_ns.Value, verifyObjectName); + known_types, surrogate, DataContractResolver, default_resolver, root_name.Value, root_ns.Value, verifyObjectName); // remove temporarily-added known types for // rootType and object graph type. @@ -399,8 +407,8 @@ namespace System.Runtime.Serialization int startTypeCount = known_types.Count; XmlFormatterSerializer.Serialize (writer, graph, - known_types, - ignore_ext, max_items, root_ns.Value, preserve_refs); + type, known_types, + ignore_ext, max_items, root_ns.Value, preserve_refs, DataContractResolver, default_resolver); // remove temporarily-added known types for // rootType and object graph type. @@ -440,8 +448,21 @@ namespace System.Runtime.Serialization return; } - QName instName = null; - QName root_qname = known_types.GetQName (rootType); + QName rootQName = null; + XmlDictionaryString name, ns; + if (DataContractResolver != null && DataContractResolver.TryResolveType (graph.GetType (), type, default_resolver, out name, out ns)) + rootQName = new QName (name.Value, ns.Value); + + // 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. + + if (rootQName == null && + graph.GetType () != type && + !known_types.Contains (graph.GetType ()) && + KnownTypeCollection.GetPrimitiveTypeName (graph.GetType ()) == QName.Empty) + 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 ())); + + QName instName = rootQName; + rootQName = rootQName ?? known_types.GetQName (rootType); QName graph_qname = known_types.GetQName (graph.GetType ()); known_types.Add (graph.GetType ()); @@ -450,24 +471,23 @@ namespace System.Runtime.Serialization writer.WriteStartElement (root_name.Value, root_ns.Value); else writer.WriteStartElement (root_name, root_ns); - if (root_ns.Value != root_qname.Namespace) - if (root_qname.Namespace != KnownTypeCollection.MSSimpleNamespace) - writer.WriteXmlnsAttribute (null, root_qname.Namespace); - if (root_qname == graph_qname) { - if (root_qname.Namespace != KnownTypeCollection.MSSimpleNamespace && - !rootType.IsEnum) - //FIXME: Hack, when should the "i:type" be written? - //Not used in case of enums - writer.WriteXmlnsAttribute ("i", XmlSchema.InstanceNamespace); + if (rootQName != graph_qname || rootQName.Namespace != KnownTypeCollection.MSSimpleNamespace && !rootType.IsEnum) + //FIXME: Hack, when should the "i:type" be written? + //Not used in case of enums + writer.WriteXmlnsAttribute ("i", XmlSchema.InstanceNamespace); + if (root_ns.Value != rootQName.Namespace) + if (rootQName.Namespace != KnownTypeCollection.MSSimpleNamespace) + writer.WriteXmlnsAttribute (null, rootQName.Namespace); + + if (rootQName == graph_qname) return; - } /* Different names */ known_types.Add (rootType); - instName = KnownTypeCollection.GetPredefinedTypeName (graph.GetType ()); + instName = instName ?? KnownTypeCollection.GetPredefinedTypeName (graph.GetType ()); if (instName == QName.Empty) /* Not a primitive type */ instName = graph_qname; @@ -475,10 +495,12 @@ namespace System.Runtime.Serialization /* FIXME: Hack, .. see test WriteObject7 () */ instName = new QName (instName.Name, XmlSchema.Namespace); +/* // disabled as it now generates extraneous i:type output. // output xsi:type as rootType is not equivalent to the graph's type. writer.WriteStartAttribute ("i", "type", XmlSchema.InstanceNamespace); writer.WriteQualifiedName (instName.Name, instName.Namespace); writer.WriteEndAttribute (); +*/ } public override void WriteEndObject (XmlDictionaryWriter writer) diff --git a/mcs/class/System.Runtime.Serialization/System.Runtime.Serialization/SerializationMap.cs b/mcs/class/System.Runtime.Serialization/System.Runtime.Serialization/SerializationMap.cs index 20b4297ed6f756d39f5d56e95abd802dcaa5bc77..4b9e0e379ebfc88aec0c1db2ef281baf4f141b01 100644 --- a/mcs/class/System.Runtime.Serialization/System.Runtime.Serialization/SerializationMap.cs +++ b/mcs/class/System.Runtime.Serialization/System.Runtime.Serialization/SerializationMap.cs @@ -451,7 +451,7 @@ namespace System.Runtime.Serialization GetDataMemberAttribute (pi); if (dma == null) continue; - KnownTypes.TryRegister (pi.PropertyType); + KnownTypes.Add (pi.PropertyType); var map = KnownTypes.FindUserMap (pi.PropertyType); if (!pi.CanRead || (!pi.CanWrite && !(map is ICollectionTypeMap))) throw new InvalidDataContractException (String.Format ( diff --git a/mcs/class/System.Runtime.Serialization/System.Runtime.Serialization/XmlFormatterDeserializer.cs b/mcs/class/System.Runtime.Serialization/System.Runtime.Serialization/XmlFormatterDeserializer.cs index 3491c2f0ef78ab539082fe7e05a40730626ba04d..945ac651961d2cdf806b919dcca39d95e9fcf1f3 100644 --- a/mcs/class/System.Runtime.Serialization/System.Runtime.Serialization/XmlFormatterDeserializer.cs +++ b/mcs/class/System.Runtime.Serialization/System.Runtime.Serialization/XmlFormatterDeserializer.cs @@ -43,15 +43,15 @@ namespace System.Runtime.Serialization { KnownTypeCollection types; IDataContractSurrogate surrogate; - DataContractResolver resolver; // new in 4.0. + DataContractResolver resolver, default_resolver; // new in 4.0. // 3.5 SP1 supports deserialization by reference (id->obj). // Though unlike XmlSerializer, it does not support forward- // reference resolution i.e. a referenced object must appear // before any references to it. Hashtable references = new Hashtable (); - public static object Deserialize (XmlReader reader, Type type, - KnownTypeCollection knownTypes, IDataContractSurrogate surrogate, DataContractResolver resolver, + public static object Deserialize (XmlReader reader, Type declaredType, + KnownTypeCollection knownTypes, IDataContractSurrogate surrogate, DataContractResolver resolver, DataContractResolver defaultResolver, string name, string ns, bool verifyObjectName) { reader.MoveToContent (); @@ -60,14 +60,14 @@ namespace System.Runtime.Serialization reader.LocalName != name || reader.NamespaceURI != ns) throw new SerializationException (String.Format ("Expected element '{0}' in namespace '{1}', but found {2} node '{3}' in namespace '{4}'", name, ns, reader.NodeType, reader.LocalName, reader.NamespaceURI)); -// Verify (knownTypes, type, name, ns, reader); - return new XmlFormatterDeserializer (knownTypes, surrogate, resolver).Deserialize (type, reader); +// Verify (knownTypes, declaredType, name, ns, reader); + return new XmlFormatterDeserializer (knownTypes, surrogate, resolver, defaultResolver).Deserialize (declaredType, reader); } // Verify the top element name and namespace. private static void Verify (KnownTypeCollection knownTypes, Type type, string name, string Namespace, XmlReader reader) { - QName graph_qname = new QName (reader.Name, reader.NamespaceURI); + QName graph_qname = new QName (reader.LocalName, reader.NamespaceURI); if (graph_qname.Name == name && graph_qname.Namespace == Namespace) return; @@ -90,11 +90,13 @@ namespace System.Runtime.Serialization private XmlFormatterDeserializer ( KnownTypeCollection knownTypes, IDataContractSurrogate surrogate, - DataContractResolver resolver) + DataContractResolver resolver, + DataContractResolver defaultResolver) { this.types = knownTypes; this.surrogate = surrogate; this.resolver = resolver; + this.default_resolver = defaultResolver; } public Hashtable References { @@ -135,6 +137,9 @@ namespace System.Runtime.Serialization throw new SerializationException (String.Format ("Value type {0} cannot be null.", type)); } + if (resolver != null) + type = resolver.ResolveName (graph_qname.Name, graph_qname.Namespace, type, default_resolver) ?? type; + if (KnownTypeCollection.GetPrimitiveTypeFromName (graph_qname.Name) != null) { string id = reader.GetAttribute ("Id", KnownTypeCollection.MSSimpleNamespace); @@ -168,7 +173,7 @@ namespace System.Runtime.Serialization name.Namespace == KnownTypeCollection.MSArraysNamespace || name.Namespace.StartsWith (KnownTypeCollection.DefaultClrNamespaceBase, StringComparison.Ordinal))) { var it = GetTypeFromNamePair (name.Name, name.Namespace); - types.TryRegister (it); + types.Add (it); map = types.FindUserMap (name); } if (map == null) diff --git a/mcs/class/System.Runtime.Serialization/System.Runtime.Serialization/XmlFormatterSerializer.cs b/mcs/class/System.Runtime.Serialization/System.Runtime.Serialization/XmlFormatterSerializer.cs index d137ed8374cb00c2fdad77a0225ebf13a1ce739f..cd37388452e3f1acf309fb3e8b6d4e3d9797010f 100644 --- a/mcs/class/System.Runtime.Serialization/System.Runtime.Serialization/XmlFormatterSerializer.cs +++ b/mcs/class/System.Runtime.Serialization/System.Runtime.Serialization/XmlFormatterSerializer.cs @@ -47,28 +47,30 @@ namespace System.Runtime.Serialization bool save_id; bool ignore_unknown; IDataContractSurrogate surrogate; + DataContractResolver resolver, default_resolver; // new in 4.0 int max_items; ArrayList objects = new ArrayList (); Hashtable references = new Hashtable (); // preserve possibly referenced objects to ids. (new in 3.5 SP1) - public static void Serialize (XmlDictionaryWriter writer, object graph, - KnownTypeCollection types, - bool ignoreUnknown, int maxItems, string root_ns, bool preserveObjectReferences) + public static void Serialize (XmlDictionaryWriter writer, object graph, Type declaredType, KnownTypeCollection types, + bool ignoreUnknown, int maxItems, string root_ns, bool preserveObjectReferences, DataContractResolver resolver, DataContractResolver defaultResolver) { - new XmlFormatterSerializer (writer, types, ignoreUnknown, maxItems, root_ns, preserveObjectReferences) - .Serialize (graph != null ? graph.GetType () : null, graph); + new XmlFormatterSerializer (writer, types, ignoreUnknown, maxItems, root_ns, preserveObjectReferences, resolver, defaultResolver) + .Serialize (/*graph != null ? graph.GetType () : */declaredType, graph); // FIXME: I believe it should always use declaredType, but such a change brings some test breakages. } - public XmlFormatterSerializer (XmlDictionaryWriter writer, - KnownTypeCollection types, - bool ignoreUnknown, int maxItems, string root_ns, bool preserveObjectReferences) + public XmlFormatterSerializer (XmlDictionaryWriter writer, KnownTypeCollection types, bool ignoreUnknown, + int maxItems, string root_ns, bool preserveObjectReferences, + DataContractResolver resolver, DataContractResolver defaultResolver) { this.writer = writer; this.types = types; ignore_unknown = ignoreUnknown; max_items = maxItems; PreserveObjectReferences = preserveObjectReferences; + this.resolver = resolver; + this.default_resolver = defaultResolver; } public bool PreserveObjectReferences { get; private set; } @@ -90,9 +92,17 @@ namespace System.Runtime.Serialization if (graph == null) writer.WriteAttributeString ("nil", XmlSchema.InstanceNamespace, "true"); else { + QName resolvedQName = null; + if (resolver != null) { + XmlDictionaryString rname, rns; + if (resolver.TryResolveType (graph != null ? graph.GetType () : typeof (object), type, default_resolver, out rname, out rns)) + resolvedQName = new QName (rname.Value, rns.Value); + } + Type actualType = graph.GetType (); - SerializationMap map = types.FindUserMap (actualType); + SerializationMap map; + map = types.FindUserMap (actualType); // For some collection types, the actual type does not matter. So get nominal serialization type instead. // (The code below also covers the lines above, but I don't remove above lines to avoid extra search cost.) if (map == null) { @@ -106,7 +116,7 @@ namespace System.Runtime.Serialization } if (actualType != type && (map == null || map.OutputXsiType)) { - QName qname = types.GetXmlName (actualType); + QName qname = resolvedQName ?? types.GetXmlName (actualType); string name = qname.Name; string ns = qname.Namespace; if (qname == QName.Empty) { @@ -116,7 +126,7 @@ namespace System.Runtime.Serialization ns = XmlSchema.Namespace; if (writer.LookupPrefix (ns) == null) // it goes first (extraneous, but it makes att order compatible) writer.WriteXmlnsAttribute (null, ns); - writer.WriteStartAttribute ("type", XmlSchema.InstanceNamespace); + writer.WriteStartAttribute ("i", "type", XmlSchema.InstanceNamespace); writer.WriteQualifiedName (name, ns); writer.WriteEndAttribute (); } diff --git a/mcs/class/System.Runtime.Serialization/System.Runtime.Serialization/XsdDataContractExporter.cs b/mcs/class/System.Runtime.Serialization/System.Runtime.Serialization/XsdDataContractExporter.cs index 27bde112d6423ac993fd8a20d11a67480cc03cbd..d752a4afb51e9ae521b2f985cbc3ca94a1462d78 100644 --- a/mcs/class/System.Runtime.Serialization/System.Runtime.Serialization/XsdDataContractExporter.cs +++ b/mcs/class/System.Runtime.Serialization/System.Runtime.Serialization/XsdDataContractExporter.cs @@ -172,7 +172,7 @@ namespace System.Runtime.Serialization if (predefined_types.FirstOrDefault (i => i.ClrType == type) != null) return true; - known_types.TryRegister (type); + known_types.Add (type); return known_types.FindUserMap (type) != null; } @@ -219,7 +219,7 @@ namespace System.Runtime.Serialization if (imported_types.FirstOrDefault (i => i.ClrType == type) != null) return false; - known_types.TryRegister (type); + known_types.Add (type); var map = known_types.FindUserMap (type); if (map == null) return false; diff --git a/mcs/class/System.Runtime.Serialization/System.Runtime.Serialization_test.dll.sources b/mcs/class/System.Runtime.Serialization/System.Runtime.Serialization_test.dll.sources index 9a705b859d23de0d2057508ab6ff443004d7fc5d..31881839db532c5f283ddd9a25ffc782ef1681dc 100644 --- a/mcs/class/System.Runtime.Serialization/System.Runtime.Serialization_test.dll.sources +++ b/mcs/class/System.Runtime.Serialization/System.Runtime.Serialization_test.dll.sources @@ -1,3 +1,4 @@ +System.Runtime.Serialization/DataContractResolverTest.cs System.Runtime.Serialization/XmlObjectSerializerTest.cs System.Runtime.Serialization/XsdDataContractExporterTest.cs System.Runtime.Serialization/XsdDataContractImporterTest.cs diff --git a/mcs/class/System.Runtime.Serialization/Test/System.Runtime.Serialization/DataContractResolverTest.cs b/mcs/class/System.Runtime.Serialization/Test/System.Runtime.Serialization/DataContractResolverTest.cs new file mode 100644 index 0000000000000000000000000000000000000000..dc9870b1c76b1a30cb00a423b5639d65585ec1aa --- /dev/null +++ b/mcs/class/System.Runtime.Serialization/Test/System.Runtime.Serialization/DataContractResolverTest.cs @@ -0,0 +1,106 @@ +// +// XmlObjectSerializerTest.cs +// +// Author: +// Atsushi Enomoto +// Ankit Jain +// +// Copyright (C) 2005 Novell, Inc. http://www.novell.com + +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +#if NET_4_0 + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Data; +using System.IO; +using System.Net; +using System.Runtime.Serialization; +using System.Text; +using System.Xml; +using System.Xml.Schema; +using System.Xml.Serialization; +using NUnit.Framework; + +namespace MonoTests.System.Runtime.Serialization +{ + [TestFixture] + public class DataContractResolverTest + { + [Test] + public void UseCase1 () + { + var ds = new DataContractSerializer (typeof (Colors), null, 10000, false, false, null, new MyResolver ()); + var sw = new StringWriter (); + using (var xw = XmlWriter.Create (sw)) + ds.WriteObject (xw, new ResolvedClass ()); + + // xml and xml2 are equivalent in infoset, except for prefixes and position of namespace nodes. So the difference should not matter. + string xml = @"c74376f0-5517-4cb7-8a07-35026423f565".Replace ('\'', '"'); + string xml2 = @"c74376f0-5517-4cb7-8a07-35026423f565".Replace ('\'', '"'); + try { + Assert.AreEqual (xml, sw.ToString ()); + } catch (AssertionException) { + Assert.AreEqual (xml2, sw.ToString ()); + } + } + } + + public class MyResolver : DataContractResolver + { + public override bool TryResolveType (Type type, Type declaredType, DataContractResolver knownTypeResolver, out XmlDictionaryString typeName, out XmlDictionaryString typeNamespace) + { + //Console.WriteLine ("TryResolveType: {0} {1}", type, declaredType); + if (knownTypeResolver.TryResolveType (type, declaredType, null, out typeName, out typeNamespace)) + return true; + return SafeResolveType (type, out typeName, out typeNamespace); + } + + XmlDictionary dic = new XmlDictionary (); + + bool SafeResolveType (Type type, out XmlDictionaryString name, out XmlDictionaryString ns) + { + // Console.WriteLine ("SafeResolveType: {0}", type); + name = dic.Add (type.Name); + ns = dic.Add (type.Namespace ?? "urn:dummy"); + return true; + } + + public override Type ResolveName (string typeName, string typeNamespace, Type declaredType, DataContractResolver knownTypeResolver) + { + //Console.WriteLine ("ResolveName: {0} {1} {2}", typeName, typeNamespace, declaredType); + return knownTypeResolver.ResolveName (typeName, typeNamespace, declaredType, null); + } + } +} + +[DataContract] +public class ResolvedClass +{ + [DataMember] + public Guid Baz = Guid.Parse ("c74376f0-5517-4cb7-8a07-35026423f565"); +} + +#endif