我想在需要DataContract
序列化方法(readonly
字段等)的更广泛序列化的上下文中将IParsable<MyType>
序列化为<mns:MyType>MyType.ToString()</mns:MyType>
。
我已经尝试了几种方法,包括ISerializable
和ISerializationSurrogate
,但它们都导致了许多代码看起来与整个过程中使用的DataContract
属性有着令人困惑的不同,这让我确信我使用了错误的工具。MyType
与string
之间的转换应该很容易。最简单的方法是什么?
在.NET Core中为某些类型的MyType
实现数据契约序列化代理的步骤如下:
-
创建一个代理类型,该代理类型可以通过数据协定序列化程序进行序列化,并且可以从原始类型
MyType
来回转换。 -
创建一个实现以下方法的代理提供程序类型
ISerializationSurrogateProvider
:-
GetSurrogateType(Type)
-在传递typeof(MyType)
时返回代理项类型。 -
GetObjectToSerialize(Object, Type)
-在传递MyType
的实例时返回代理项的实例。 -
GetDeserializedObject(Object, Type)
-在传递代理项时返回MyType
的实例。
-
-
在序列化或反序列化之前,构造您自己的
DataContractSerializer
并调用DataContractSerializerExtensions.SetSerializationSurrogateProvider(provider)
,以便在序列化程序中注册步骤#2中提供程序的实例。
重要注意事项
-
在.NET Framework中使用数据协定代理的方法与.NET Core完全不同。如果您在.NET Framework中工作,请忽略此答案,并参阅数据契约代理。
-
在
ToString()
方法中,请确保使用CultureInfo.InvariantCulture
格式化所有数值和其他值。如果不这样做,在一个区域设置中生成的XML在另一个区域中将无法读取。如果出于某种原因需要本地化的ToString()
,请使MyType
实现IFormattable
,并在序列化时使用ToString(default, CultureInfo.InvariantCulture)
。(format
字符串可以忽略。) -
您提到尝试将
ISerializable
用于您的MyType
。这种技术将数据存储为XML序列化流中的元素/值对。由于所需的XML只包含文本而不包含标记元素,因此这种方法可能不合适。
考虑到这一点,我们可以为实现IParsable<TSelf>
的任何类型创建一个通用代理,如下所示:
public sealed class ParsableSurrogate<TSelf> where TSelf : IParsable<TSelf>
{
[IgnoreDataMember]
public TSelf? Value { get; set; }
public string? TextValue { get { return Value?.ToString(); } set { Value = (value == null ? default : TSelf.Parse(value, default)); } }
}
接下来,制作以下ISerializationSurrogateProvider
:
public class SerializationSurrogateProvider : ISerializationSurrogateProvider
{
readonly Dictionary<Type, (Type SurrogateType, Func<object, Type, object> ToSurrogate)> toSurrogateDictionary = new();
readonly Dictionary<Type, (Type OriginalType, Func<object, Type, object> ToOriginal)> fromSurrogateDictionary = new();
public Type GetSurrogateType(Type type) =>
toSurrogateDictionary.TryGetValue(type, out var entry) ? entry.SurrogateType : type;
// TODO: check to see whether null objects are handled correctly
public object GetObjectToSerialize(object obj, Type targetType) =>
toSurrogateDictionary.TryGetValue(obj.GetType(), out var entry) ? entry.ToSurrogate(obj, targetType) : obj;
public object GetDeserializedObject(object obj, Type targetType) =>
fromSurrogateDictionary.TryGetValue(obj.GetType(), out var entry) ? entry.ToOriginal(obj, targetType) : obj;
public SerializationSurrogateProvider AddParsable<TParsable>() where TParsable : IParsable<TParsable>
{
toSurrogateDictionary.Add(typeof(TParsable), (typeof(ParsableSurrogate<TParsable>), (obj, t) => new ParsableSurrogate<TParsable> { Value = (TParsable)obj }));
fromSurrogateDictionary.Add(typeof(ParsableSurrogate<TParsable>), (typeof(TParsable), (obj, t) => ((ParsableSurrogate<TParsable>)obj).Value!));
return this;
}
}
现在,假设您的MyType
看起来像这样:
public class MyType : IParsable<MyType>
{
public MyType(string? value1, string? value2) => (this.Value1, this.Value2) = (value1, value2);
public string? Value1 { get; }
public string? Value2 { get; }
public override string ToString() => JsonSerializer.Serialize(this);
public static MyType Parse (string s, IFormatProvider? provider) => JsonSerializer.Deserialize<MyType>(s) ?? throw new ArgumentException();
public static bool TryParse (string? s, IFormatProvider? provider, out MyType result) => throw new NotImplementedException("not needed for the question");
}
然后,要从XML序列化到XML,您可以为包含MyType
实例的任何模型类型创建一个DataContractSerializer
,如下所示:
var serializer = new DataContractSerializer(model.GetType());
serializer.SetSerializationSurrogateProvider(new SerializationSurrogateProvider().AddParsable<MyType>());
var xml = model.ToContractXml(serializer : serializer);
var model2 = DataContractSerializerHelper.FromContractXml<Model>(xml, serializer);
使用扩展方法:
public static partial class DataContractSerializerHelper
{
public static string ToContractXml<T>(this T obj, DataContractSerializer? serializer = null, XmlWriterSettings? settings = null)
{
serializer = serializer ?? new DataContractSerializer(obj == null ? typeof(T) : obj.GetType());
using (var textWriter = new StringWriter())
{
settings = settings ?? new XmlWriterSettings { Indent = true };
using (var xmlWriter = XmlWriter.Create(textWriter, settings))
{
serializer.WriteObject(xmlWriter, obj);
}
return textWriter.ToString();
}
}
public static T? FromContractXml<T>(string xml, DataContractSerializer? serializer = null)
{
using (var textReader = new StringReader(xml ?? ""))
using (var xmlReader = XmlReader.Create(textReader))
{
return (T?)(serializer ?? new DataContractSerializer(typeof(T))).ReadObject(xmlReader);
}
}
}
当MyType
是根模型本身时,生成的XML将如下所示:
<ParsableSurrogateOfMyTypeMTRdQN6P xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/">
<TextValue>{"Value1":"hello","Value2":"there"}</TextValue>
</ParsableSurrogateOfMyTypeMTRdQN6P>
在这里演示小提琴#1。
当这起作用时,你会注意到几个问题:
文本值嵌套在
<TextValue>
元素中。在你的问题中,你明确表示你不想那样。由于
DataContractSerializer
不支持与XmlSerializer
的[XmlText]
属性等效的属性,因此有必要实现IXmlSerializable
。根元素名称
<ParsableSurrogateOfMyTypeMTRdQN6P>
很愚蠢,与您的需求<mns:MyType>
不匹配。由于我们将实现
IXmlSerializable
,因此有必要使用从这个答案到的解决方案。当使用数据协定序列化程序序列化IXmlSerializable对象时,我如何控制根元素命名空间和名称。
这需要如下修改ParsableSurrogate<TSelf>
:
[XmlSchemaProvider("GetSchemaMethod")]
public sealed class ParsableSurrogate<TSelf> : IXmlSerializable where TSelf : IParsable<TSelf>
{
public TSelf? Value { get; set; }
public void ReadXml(XmlReader reader)
{
reader.MoveToContent();
Value = TSelf.Parse(reader.ReadElementContentAsString(), CultureInfo.InvariantCulture);
}
public void WriteXml(XmlWriter writer) => writer.WriteValue(Value?.ToString());
public XmlSchema? GetSchema() => null;
const string DataContractNamespacePrefix = "http://schemas.datacontract.org/2004/07/";
static void GetDataContractNamespaceAndName(Type type, out string name, out string @namespace)
{
// TODO: tweak this logic as required to get the correct namespace and name
(name, @namespace) = (type.Name, DataContractNamespacePrefix + type.Namespace);
if (type.GetCustomAttribute<DataContractAttribute>() is {} attr)
(name, @namespace) = (attr.Name ?? name, attr.Namespace ?? @namespace);
}
// This is the method named by the XmlSchemaProviderAttribute applied to the type.
public static XmlQualifiedName GetSchemaMethod(XmlSchemaSet xs)
{
GetDataContractNamespaceAndName(typeof(TSelf), out var name, out var @namespace);
// Create the TSelf type as a restriction of string
var tSelfType = new XmlSchemaSimpleType
{
Name = name,
Content = new XmlSchemaSimpleTypeRestriction { BaseTypeName = XmlSchemaType.GetBuiltInSimpleType(XmlTypeCode.String).QualifiedName },
};
// Create the top-level TSelf element.
var tSelfElement = new XmlSchemaElement
{
Name = name,
SchemaTypeName = new XmlQualifiedName(tSelfType.Name, @namespace),
};
// Create a schema with the type & element
var tSelfSchema = new XmlSchema
{
TargetNamespace = @namespace,
// Add the top-level element and types to the schema
Items = { tSelfElement, tSelfType },
};
xs.Add(tSelfSchema);
return new XmlQualifiedName(name, @namespace);
}
}
并将所需的数据契约名称空间添加到MyType
中,如下所示:
[DataContract(Namespace = "https://stackoverflow.com/questions/75912029")] // Replace with whatever namespace you need
public class MyType : IParsable<MyType>
{
完成此操作后,将为MyType
生成以下XML:
<MyType xmlns="https://stackoverflow.com/questions/75912029">{"Value1":"hello","Value2":"there"}</MyType>
对于包含MyType
列表的模型,如以下所示:
public class Model
{
public List<MyType?> MyTypes { get; set; } = new ();
}
根据需要生成以下XML:
<Model xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/">
<MyTypes xmlns:d2p1="https://stackoverflow.com/questions/75912029">
<d2p1:MyType>{"Value1":"hello","Value2":"there"}</d2p1:MyType>
<d2p1:MyType i:nil="true" />
<d2p1:MyType>{"Value1":"foo","Value2":"bar"}</d2p1:MyType>
</MyTypes>
</Model>
在这里演示小提琴#2。