提交 47fd1c3a 编写于 作者: A Atsushi Eno

Implement DataContractResolver support. At least TryResolveType in serializer should work.

上级 2e0e15d7
......@@ -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;
}
}
}
}
......@@ -52,6 +52,7 @@ namespace System.Runtime.Serialization
ReadOnlyCollection<Type> 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)
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)
......
......@@ -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 (
......
......@@ -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)
......
......@@ -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 ();
}
......
......@@ -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;
......
System.Runtime.Serialization/DataContractResolverTest.cs
System.Runtime.Serialization/XmlObjectSerializerTest.cs
System.Runtime.Serialization/XsdDataContractExporterTest.cs
System.Runtime.Serialization/XsdDataContractImporterTest.cs
......
//
// XmlObjectSerializerTest.cs
//
// Author:
// Atsushi Enomoto <atsushi@ximian.com>
// Ankit Jain <JAnkit@novell.com>
//
// 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 = @"<?xml version='1.0' encoding='utf-16'?><Colors xmlns:i='http://www.w3.org/2001/XMLSchema-instance' xmlns:d1p1='urn:dummy' i:type='d1p1:ResolvedClass' xmlns='http://schemas.datacontract.org/2004/07/MonoTests.System.Runtime.Serialization'><Baz xmlns='http://schemas.datacontract.org/2004/07/'>c74376f0-5517-4cb7-8a07-35026423f565</Baz></Colors>".Replace ('\'', '"');
string xml2 = @"<?xml version='1.0' encoding='utf-16'?><Colors xmlns:i='http://www.w3.org/2001/XMLSchema-instance' xmlns:d1p1='urn:dummy' xmlns:d1p2='http://schemas.datacontract.org/2004/07/' i:type='d1p2:ResolvedClass' xmlns='http://schemas.datacontract.org/2004/07/MonoTests.System.Runtime.Serialization'><d1p2:Baz>c74376f0-5517-4cb7-8a07-35026423f565</d1p2:Baz></Colors>".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
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册