【问题标题】:Custom DataContractSerializer自定义 DataContractSerializer
【发布时间】:2017-02-25 19:32:14
【问题描述】:
我想序列化一个带有 DataMember 属性的对象,以便在某些属性上被忽略。
假设我有自定义属性 MyIgnoreDataMember。
我希望用它标记的属性对于我的自定义 DataContractSerializer 不可见,但对于普通 DataContractSerializer 可见。
而且我必须使用 DataContractSerializer 而没有别的。
代码是 Silverlight 应用程序。
有人成功地完成了 DataContractSerializer 的子类化吗?
【问题讨论】:
标签:
c#
.net
silverlight
serialization
datacontractserializer
【解决方案1】:
您的问题的答案因以下问题而变得复杂:
DataContractSerializer 是密封的,因此不能被子类化以检查像 MyIgnoreDataMember 这样的属性。
在序列化过程中使用serialization surrogate 将适当的DTO 注入对象图中通常是可行的方法——但它看起来在silverlight 上不可用,请参阅this answer。
DataContractSerializer 不支持ShouldSerialize 模式,正如here 所解释的那样,因此您不能只通过回调方法抑制不需要的属性的序列化。
那么,您有哪些选择?假设您的对象图如下所示:
[DataContract(Name = "Root", Namespace = "http://www.MyNamespace.com")]
public class RootObject
{
[DataMember]
public NestedObject NestedObject { get; set; }
}
[DataContract(Name = "Nested", Namespace = "http://www.MyNamespace.com")]
public class NestedObject
{
[DataMember]
public string SensitiveData { get; set; }
[DataMember]
public string PublicData { get; set; }
}
并且您想有条件地抑制SensitiveData 的输出。那么有以下几种可能:
-
如果你只需要删除一些属性,你可以用EmitDefaultValue = false标记它们,并在某些线程静态为true时返回一个默认值,例如:
[DataContract(Name = "Root", Namespace = "http://www.MyNamespace.com")]
public class RootObject
{
[DataMember]
public NestedObject NestedObject { get; set; }
}
[DataContract(Name = "Nested", Namespace = "http://www.MyNamespace.com")]
public class NestedObject
{
string sensitiveData;
[DataMember(IsRequired = false, EmitDefaultValue = false)]
public string SensitiveData
{
get
{
if (SerializationState.InCustomSerialization())
return null;
return sensitiveData;
}
set
{
sensitiveData = value;
}
}
[DataMember]
public string PublicData { get; set; }
}
public static class SerializationState
{
[ThreadStatic]
static bool inCustomSerialization;
public static bool InCustomSerialization()
{
return inCustomSerialization;
}
public static IDisposable SetInCustomDeserialization(bool value)
{
return new PushValue<bool>(value, () => inCustomSerialization, b => inCustomSerialization = b);
}
}
public struct PushValue<T> : IDisposable
{
Action<T> setValue;
T oldValue;
public PushValue(T value, Func<T> getValue, Action<T> setValue)
{
if (getValue == null || setValue == null)
throw new ArgumentNullException();
this.setValue = setValue;
this.oldValue = getValue();
setValue(value);
}
#region IDisposable Members
// By using a disposable struct we avoid the overhead of allocating and freeing an instance of a finalizable class.
public void Dispose()
{
if (setValue != null)
setValue(oldValue);
}
#endregion
}
然后,在序列化时,执行以下操作:
using (SerializationState.SetInCustomDeserialization(true))
{
// Serialize with data contract serializer.
}
真的很丑。
-
您可以使用与实际类型相同的合同名称和命名空间来创建整个 DTO 层次结构,使用 automapper 之类的东西将实际类映射到 DTO,然后序列化 DTO:
[DataContract(Name = "Root", Namespace = "http://www.MyNamespace.com")]
class RootObjectDTO
{
[DataMember]
public NestedObjectDTO NestedObject { get; set; }
}
[DataContract(Name = "Nested", Namespace = "http://www.MyNamespace.com")]
class NestedObjectDTO
{
[DataMember]
public string PublicData { get; set; }
}
如果 automapper 在 silverlight 上不可用,您可以使用 DataContractSerializer 本身进行映射,因为合约名称和命名空间是相同的。 IE。 - 将真正的根对象序列化为 XML 字符串(或XDocument,如下所示),将中间 XML 反序列化为 DTO 根,然后序列化出 DTO。
-
您可以使用以下扩展类序列化到内存中的XDocument(is available in silverlight):
public static partial class DataContractSerializerHelper
{
public static XDocument SerializeContractToXDocument<T>(this T obj)
{
return obj.SerializeContractToXDocument(null);
}
public static XDocument SerializeContractToXDocument<T>(this T obj, DataContractSerializer serializer)
{
var doc = new XDocument();
using (var writer = doc.CreateWriter())
{
(serializer ?? new DataContractSerializer(obj.GetType())).WriteObject(writer, obj);
}
return doc;
}
public static T DeserializeContract<T>(this XDocument doc)
{
return doc.DeserializeContract<T>(null);
}
public static T DeserializeContract<T>(this XDocument doc, DataContractSerializer serializer)
{
if (doc == null)
throw new ArgumentNullException();
using (var reader = doc.CreateReader())
{
return (T)(serializer ?? new DataContractSerializer(typeof(T))).ReadObject(reader);
}
}
}
接下来,使用XPATH queries 修剪不需要的元素,然后将XDocument 序列化为最终的XML 表示。
最后,如果性能和内存使用非常重要,您可以使用 this answer 中的 ElementSkippingXmlTextWriter 在编写时删除不需要的元素。